diff --git a/src/lint/utils.ts b/src/lint/utils.ts index d36df0ff..db522579 100644 --- a/src/lint/utils.ts +++ b/src/lint/utils.ts @@ -73,7 +73,10 @@ export function rhsMatches(a: RightHandSide | OneOfList, b: RightHandSide | OneO } return symbolSpanMatches(aHead, bHead); } + case SyntaxKind.OneOfList: + return oneOfListMatches(a, b as OneOfList); default: + // @ts-expect-error: Callers might pass other kinds of nodes. throw new Error('unknown rhs type ' + a.constructor.name); } } @@ -171,6 +174,14 @@ function argumentListMatches(a: ArgumentList, b: ArgumentList) { ); } +function oneOfListMatches(a: OneOfList, b: OneOfList) { + if (a.terminals === undefined || b.terminals === undefined) { + throw new Error('OneOfList must have terminals'); + } + // The terminals in a must be a subset of the terminals in b. + return a.terminals.every(ae => b.terminals!.find(be => ae.text === be.text)); +} + // this is only for use with single-file grammars export function getLocationInGrammarFile(file: SourceFile, pos: number) { const posWithoutWhitespace = skipTrivia(file.text, pos, file.text.length); diff --git a/test/errors.js b/test/errors.js index 4c1ff13a..a99bfb5c 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1106,6 +1106,31 @@ ${M} ); }); + it('unknown oneof', async () => { + await assertError( + positioned` + + Foo :: one of \`a\` \`b\` \`c\` + + + +

Static Semantics: Example

+
+ + Foo :: ${M}one of \`d\` \`e\` + + + 1. Return *true*. + +
`, + { + ruleId: 'grammar-shape', + nodeType: 'emu-grammar', + message: 'could not find definition for rhs "d e"', + }, + ); + }); + it('negative', async () => { await assertErrorFree(` @@ -1167,5 +1192,49 @@ ${M} }, ); }); + + it('negative: oneof', async () => { + await assertErrorFree(` + + Foo :: one of \`a\` \`b\` \`c\` + + + +

Static Semantics: Example

+
+ + Foo :: one of \`a\` \`b\` \`c\` + + + 1. Return *true*. + +
+ `); + }); + + it('negative: oneof (subset)', async () => { + await assertErrorFree(` + + Foo :: one of \`a\` \`b\` \`c\` \`d\` + + + +

Static Semantics: Example

+
+ + Foo :: one of \`a\` \`b\` + + + 1. Return *true*. + + + Foo :: one of \`c\` \`d\` + + + 1. Return *false*. + +
+ `); + }); }); });