-
-
Notifications
You must be signed in to change notification settings - Fork 173
Description
The following pieces of code are valid but parsed incorrectly:
a?.delete(b);myMap?.delete(key);a?.class;
a?.return;The output of tree-sitter parse is the following:
a.delete(b); parses correctly:
(program [0, 0] - [1, 0]
(expression_statement [0, 0] - [0, 12]
(call_expression [0, 0] - [0, 11]
function: (member_expression [0, 0] - [0, 8]
object: (identifier [0, 0] - [0, 1])
property: (property_identifier [0, 2] - [0, 8]))
arguments: (arguments [0, 8] - [0, 11]
(identifier [0, 9] - [0, 10])))))
a?.delete(b); produces an ERROR node:
(program [0, 0] - [1, 0]
(expression_statement [0, 0] - [0, 13]
(call_expression [0, 0] - [0, 12]
function: (identifier [0, 0] - [0, 1])
(ERROR [0, 3] - [0, 9])
arguments: (arguments [0, 9] - [0, 12]
(identifier [0, 10] - [0, 11])))))
The delete keyword (and all other globally-reserved words) is correctly treated as a property_identifier after . but produces an ERROR node after ?. (optional chaining).
All globally-reserved words are affected:
a?.delete(b); // ERROR
a?.class; // ERROR
a?.return; // ERRORAnalysis
The member_expression rule in grammar.js (lines 884-891) uses reserved('properties', ...) to allow reserved words as property identifiers:
member_expression: $ => prec('member', seq(
field('object', choice($.expression, $.primary_expression, $.import)),
choice('.', field('optional_chain', $.optional_chain)),
field('property', choice(
$.private_property_identifier,
reserved('properties', alias($.identifier, $.property_identifier)),
)),
)),The properties reserved list is empty (line 72), meaning all globally-reserved words should be permitted in property position. This works correctly when the preceding token is ., but not when it is ?. (optional_chain, which is an external token defined at line 855).
The reserved feature (introduced in tree-sitter v0.25) may not be applying its keyword-to-identifier demotion when the context involves external tokens like optional_chain. This could also be a tree-sitter core issue rather than a grammar issue.
Real-World Impact
This affects real-world codebases significantly. Map.prototype.delete() and Set.prototype.delete() are commonly called via optional chaining (map?.delete(key)). Parsing the current microsoft/vscode repository with tree-sitter-typescript (which inherits this grammar) produces 40+ parse errors, nearly all from ?.delete() calls.
Environment
- tree-sitter CLI: v0.26.6
- tree-sitter-javascript: v0.25.0
- Platform: macOS (darwin arm64), also reproduced on Linux and Windows via CI