Skip to content

Fix: allow reserved words as property identifiers after optional chaining#378

Open
rbonestell wants to merge 3 commits intotree-sitter:masterfrom
rbonestell:bug/377-opt-chaining-res-words
Open

Fix: allow reserved words as property identifiers after optional chaining#378
rbonestell wants to merge 3 commits intotree-sitter:masterfrom
rbonestell:bug/377-opt-chaining-res-words

Conversation

@rbonestell
Copy link
Copy Markdown

@rbonestell rbonestell commented Mar 7, 2026

Summary

Fixes #377.

Reserved words (delete, class, return, etc.) are correctly treated as property_identifier nodes after . but produce ERROR nodes after ?. (optional chaining). This is because the reserved('properties', ...) context is not propagated through the ?. token path — likely a tree-sitter core issue.

This PR works around it by explicitly listing all reserved words as valid alternatives in the member_expression property field via a new _keyword_identifier rule.

Additionally, this PR fixes JSX parsing of reserved/contextual keywords as tag names, attribute names, and member expression properties (a pre-existing issue). Credit to @SegaraRai for identifying and implementing the JSX fix (commit).

Before:

a?.delete(b);  →  ERROR
a?.class;      →  ERROR
a?.return;     →  ERROR
<class />      →  ERROR
<Foo.class />  →  ERROR
<a class="v"/> →  ERROR

After:

a?.delete(b);  →  (call_expression (member_expression … (optional_chain) (property_identifier)) (arguments …))
a?.class;      →  (member_expression … (optional_chain) (property_identifier))
a?.return;     →  (member_expression … (optional_chain) (property_identifier))
<class />      →  (jsx_self_closing_element (identifier))
<Foo.class />  →  (jsx_self_closing_element (member_expression (identifier) (property_identifier)))
<a class="v"/> →  (jsx_self_closing_element (identifier) (jsx_attribute (property_identifier) (string …)))

Changes

  • grammar.js: Extract RESERVED_WORDS and CONTEXTUAL_KEYWORDS as shared constants to eliminate duplication across reserved.global, _reserved_identifier, and the new _keyword_identifier rule. Add _keyword_identifier (inlined) to the member_expression property field alongside $.identifier inside the existing reserved('properties', ...) wrapper. Add jsx_keyword_identifier and jsx_member_expression rules to support reserved words in JSX contexts.
  • test/corpus/expressions.txt: Add test case for optional chaining with reserved words.
  • test/corpus/jsx_identifiers.txt: Add test cases for JSX reserved word identifiers (contributed by @SegaraRai).
  • package.json: Add tree-sitter to devDependencies (required by bindings/node/binding_test.js).
  • .github/workflows/ci.yml: Pin node-version: 22 for Node binding tests (tree-sitter@0.25.0 cannot compile with Node 24).
  • src/: Regenerated via tree-sitter generate.

Acknowledgments

Thanks to @SegaraRai for testing this branch and contributing the JSX reserved word fix (#378 comment).

Notes

  • All 118 corpus tests pass on all platforms.
  • The reserved('properties', ...) wrapper is retained for the . path and forward compatibility — when tree-sitter core fixes the propagation issue, the explicit alternatives become redundant but harmless.
  • The JSX fix introduces a dedicated jsx_member_expression rule (separate from nested_identifier) so that _jsx_identifier (which includes reserved words) is used for property resolution in JSX member expressions like <Foo.class />.

…ning

Reserved words like 'delete', 'class', 'return' were producing ERROR nodes when used as property identifiers after '?.' (optional chaining), while working correctly after '.'. This adds a '_keyword_identifier' rule that explicitly lists all reserved words as valid alternatives in the member_expression property field, working around a tree-sitter core issue where the 'reserved()' context wasn't propagated through the '?.' token path.

Also extracts RESERVED_WORDS and CONTEXTUAL_KEYWORDS as shared constants to eliminate keyword list duplication across reserved.global, _reserved_identifier, and _keyword_identifier.

Fixes tree-sitter#377
Add tree-sitter to devDependencies (required by bindings/node/binding_test.js) and pin node-version to 22 (tree-sitter@0.25.0 cannot compile with Node 24).
@SegaraRai
Copy link
Copy Markdown

Thanks for putting this together. I tested this branch locally and noticed a follow-up issue on the JSX side:
reserved/contextual keywords no longer parse correctly as JSX tag and attribute names.

For example, cases like these no longer parse correctly in JSX:

  • <class />
  • <svg:true />
  • <Foo.class />
  • <a class="v" for="x" const="y" await="z" />

I put together a small follow-up fix on top of your branch here:
SegaraRai@e6d6e42

Please feel free to reuse or cherry-pick that commit if it is useful. I validated this locally against the affected JSX corpus cases, but I did not run the full CI matrix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Reserved words not recognized as property identifiers after optional chaining (?.)

2 participants