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*.
+
+
+ `);
+ });
});
});