Skip to content

Commit d01c156

Browse files
committed
expr-parser: skip tags in records
1 parent 4b60127 commit d01c156

File tree

2 files changed

+64
-10
lines changed

2 files changed

+64
-10
lines changed

src/expr-parser.ts

+52-9
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,14 @@ class ExprParser {
206206
},
207207
};
208208
const match = tok.name === 'text' ? tok.contents.match(tokMatcher) : null;
209+
// the `tok.name !== 'text'` part in the test below is redundant but makes TS happier
209210
if (tok.name !== 'text' || match == null) {
210-
if (!(tok.name === 'text' && tok.contents.length === 0)) {
211+
const empty =
212+
(tok.name === 'text' && tok.contents.length === 0) ||
213+
tok.name === 'tag' ||
214+
tok.name === 'opaqueTag' ||
215+
tok.name === 'comment';
216+
if (!empty) {
211217
currentProse.push(tok);
212218
}
213219
++this.srcIndex;
@@ -241,6 +247,36 @@ class ExprParser {
241247
});
242248
}
243249

250+
// returns true if this ate a newline
251+
private eatWhitespace(): boolean {
252+
let next;
253+
let hadNewline = false;
254+
while ((next = this.peek())?.type === 'prose') {
255+
const firstNonWhitespaceIdx = next.parts.findIndex(
256+
part => part.name !== 'text' || /\S/.test(part.contents)
257+
);
258+
if (firstNonWhitespaceIdx === -1) {
259+
hadNewline ||= next.parts.some(
260+
part => part.name === 'text' && part.contents.includes('\n')
261+
);
262+
this.next.shift();
263+
} else if (firstNonWhitespaceIdx === 0) {
264+
return hadNewline;
265+
} else {
266+
hadNewline ||= next.parts.some(
267+
(part, i) =>
268+
i < firstNonWhitespaceIdx && part.name === 'text' && part.contents.includes('\n')
269+
);
270+
this.next[0] = {
271+
type: 'prose',
272+
parts: next.parts.slice(firstNonWhitespaceIdx),
273+
};
274+
return hadNewline;
275+
}
276+
}
277+
return hadNewline;
278+
}
279+
244280
// guarantees the next token is an element of close
245281
parseSeq(close: CloseTokenType[]): Seq {
246282
const items: NonSeq[] = [];
@@ -421,7 +457,20 @@ class ExprParser {
421457
let type: 'record' | 'record-spec' | null = null;
422458
const members: ({ name: string; value: Seq } | { name: string })[] = [];
423459
while (true) {
460+
const hadNewline = this.eatWhitespace();
424461
const nextTok = this.peek();
462+
if (nextTok.type === 'crec') {
463+
if (!hadNewline) {
464+
// ideally this would be a lint failure, or better yet a formatting thing, but whatever
465+
throw new ParseFailure(
466+
members.length > 0
467+
? 'trailing commas are only allowed when followed by a newline'
468+
: 'records cannot be empty',
469+
nextTok.offset
470+
);
471+
}
472+
break;
473+
}
425474
if (nextTok.type !== 'prose') {
426475
throw new ParseFailure('expected to find record field name', nextTok.offset);
427476
}
@@ -434,14 +483,6 @@ class ExprParser {
434483
const { contents } = nextTok.parts[0];
435484
const nameMatch = contents.match(/^\s*\[\[(?<name>\w+)\]\]\s*(?<colon>:?)/);
436485
if (nameMatch == null) {
437-
if (members.length > 0 && /^\s*$/.test(contents) && contents.includes('\n')) {
438-
// allow trailing commas when followed by a newline
439-
this.next.shift(); // eat the whitespace
440-
if (this.peek().type === 'crec') {
441-
this.next.shift();
442-
break;
443-
}
444-
}
445486
throw new ParseFailure(
446487
'expected to find record field',
447488
nextTok.parts[0].location.start.offset + contents.match(/^\s*/)![0].length
@@ -581,6 +622,8 @@ class ExprParser {
581622
}
582623
}
583624

625+
// Note: this does not necessarily represent the entire input
626+
// in particular it may omit some whitespace, tags, and comments
584627
export function parse(src: FragmentNode[], opNames: Set<String>): Seq | Failure {
585628
const parser = new ExprParser(src, opNames);
586629
try {

test/expr-parser.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ describe('expression parsing', () => {
7979
</emu-alg>
8080
`);
8181
});
82+
83+
it('tags in record', async () => {
84+
await assertLintFree(`
85+
<emu-alg>
86+
1. Let _x_ be a new Record { [[Foo]]: 0, <!-- comment --> [[Bar]]: 1 }.
87+
1. Let _x_ be a new Record { [[Foo]]: 0, <ins>[[Bar]]: 1</ins> }.
88+
1. Let _x_ be a new Record { [[Foo]]: 0, <ins>[[Bar]]: 1, [[Baz]]: 2</ins> }.
89+
1. Let _x_ be a new Record { [[Foo]]: 0, <ins>[[Bar]]: 1,</ins> [[Baz]]: 2 }.
90+
</emu-alg>
91+
`);
92+
});
8293
});
8394

8495
describe('errors', () => {
@@ -193,7 +204,7 @@ describe('expression parsing', () => {
193204
{
194205
ruleId: 'expression-parsing',
195206
nodeType: 'emu-alg',
196-
message: 'expected to find record field',
207+
message: 'records cannot be empty',
197208
}
198209
);
199210
});

0 commit comments

Comments
 (0)