mirror of
https://github.com/tree-sitter/tree-sitter.git
synced 2026-09-10 07:36:22 -04:00
fix(generate): validate symbol-only grammar fields
Require `word`, `conflicts`, `inline`, and `supertypes` callbacks to return named grammar symbols instead of silently accepting values that produce undefined names. Restrict `precedences` lists to named rules and precedence names, and preserve the existing handling of undefined and duplicate `inline` rules.
This commit is contained in:
parent
7ca101cb7e
commit
1cb91662a5
|
|
@ -380,14 +380,16 @@ function grammar(baseGrammar, options) {
|
|||
|
||||
let word = baseGrammar.word;
|
||||
if (options.word) {
|
||||
word = options.word.call(ruleBuilder, ruleBuilder).name;
|
||||
if (typeof word != 'string') {
|
||||
const wordRule = options.word.call(ruleBuilder, ruleBuilder);
|
||||
if (wordRule?.name === 'ReferenceError') {
|
||||
throw new Error("Grammar's 'word' property must be a valid named rule.");
|
||||
}
|
||||
|
||||
if (wordRule?.type !== 'SYMBOL' || typeof wordRule.name !== 'string') {
|
||||
throw new Error("Grammar's 'word' property must be a named rule.");
|
||||
}
|
||||
|
||||
if (word === 'ReferenceError') {
|
||||
throw new Error("Grammar's 'word' property must be a valid rule name.");
|
||||
}
|
||||
word = wordRule.name;
|
||||
}
|
||||
|
||||
let conflicts = baseGrammar.conflicts;
|
||||
|
|
@ -408,7 +410,13 @@ function grammar(baseGrammar, options) {
|
|||
throw new Error("Grammar's conflicts must be an array of arrays of rules.");
|
||||
}
|
||||
|
||||
return conflictSet.map(symbol => normalize(symbol).name);
|
||||
return conflictSet.map(symbol => {
|
||||
const rule = normalize(symbol);
|
||||
if (rule.type !== 'SYMBOL') {
|
||||
throw new Error("Grammar's conflicts must contain only named rules.");
|
||||
}
|
||||
return rule.name;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -426,12 +434,15 @@ function grammar(baseGrammar, options) {
|
|||
}
|
||||
|
||||
inline = inlineRules.filter((symbol, index, self) => {
|
||||
if (self.findIndex(s => s.name === symbol.name) !== index) {
|
||||
console.log(`Warning: duplicate inline rule '${symbol.name}'`);
|
||||
if (symbol?.name === 'ReferenceError') {
|
||||
console.log(`Warning: inline rule '${symbol.symbol.name}' is not defined.`);
|
||||
return false;
|
||||
}
|
||||
if (symbol.name === 'ReferenceError') {
|
||||
console.log(`Warning: inline rule '${symbol.symbol.name}' is not defined.`);
|
||||
if (symbol?.type !== 'SYMBOL' || typeof symbol.name !== 'string') {
|
||||
throw new Error("Grammar's inline property must contain only named rules.");
|
||||
}
|
||||
if (self.findIndex(s => s?.name === symbol.name) !== index) {
|
||||
console.log(`Warning: duplicate inline rule '${symbol.name}'`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -452,9 +463,12 @@ function grammar(baseGrammar, options) {
|
|||
}
|
||||
|
||||
supertypes = supertypeRules.map(symbol => {
|
||||
if (symbol.name === 'ReferenceError') {
|
||||
if (symbol?.name === 'ReferenceError') {
|
||||
throw new Error(`Supertype rule \`${symbol.symbol.name}\` is not defined.`);
|
||||
}
|
||||
if (symbol?.type !== 'SYMBOL' || typeof symbol.name !== 'string') {
|
||||
throw new Error("Grammar's supertypes property must contain only named rules.");
|
||||
}
|
||||
return symbol.name;
|
||||
});
|
||||
}
|
||||
|
|
@ -472,7 +486,15 @@ function grammar(baseGrammar, options) {
|
|||
if (!Array.isArray(list)) {
|
||||
throw new Error("Grammar's precedences must be an array of arrays of rules.");
|
||||
}
|
||||
return list.map(normalize);
|
||||
return list.map(entry => {
|
||||
const rule = normalize(entry);
|
||||
if (rule.type !== 'STRING' && rule.type !== 'SYMBOL') {
|
||||
throw new Error(
|
||||
"Grammar's precedences must contain only precedence names or named rules."
|
||||
);
|
||||
}
|
||||
return rule;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -395,7 +395,11 @@ mod tests {
|
|||
r"
|
||||
module.exports = grammar({
|
||||
name: 'test',
|
||||
rules: { source_file: $ => 'hello' }
|
||||
word: $ => $.identifier,
|
||||
rules: {
|
||||
source_file: $ => $.identifier,
|
||||
identifier: $ => 'hello'
|
||||
}
|
||||
});
|
||||
",
|
||||
)
|
||||
|
|
@ -403,6 +407,7 @@ mod tests {
|
|||
|
||||
let json = execute_native_runtime(&grammar_path).expect("Failed to execute grammar");
|
||||
assert!(json.contains("\"name\": \"test\""));
|
||||
assert!(json.contains("\"word\": \"identifier\""));
|
||||
assert!(json.contains("\"hello\""));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue