From 2722de8461fdbc2702d8ef9c6e60c40d4d12ad60 Mon Sep 17 00:00:00 2001 From: deathaxe Date: Tue, 31 Dec 2024 12:21:44 +0100 Subject: [PATCH] [JavaScript] Fix anonymous class instantiation Fixes #4125 This commit adds "future reserved words" from [1] to prevent them being scoped as normal identifiers which fixes anonymous class instantiations in TypeScript. They are also marked reserved in JavaScript as specified as such by [1], and `implements` is scoped illegal in class declarations. [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#keywords --- JavaScript/JavaScript.sublime-syntax | 62 ++++++++++++++++++++-- JavaScript/TypeScript.sublime-syntax | 12 +---- JavaScript/tests/syntax_test_js.js | 10 ++++ JavaScript/tests/syntax_test_typescript.ts | 49 +++++++++++++++++ 4 files changed, 117 insertions(+), 16 deletions(-) diff --git a/JavaScript/JavaScript.sublime-syntax b/JavaScript/JavaScript.sublime-syntax index 395f11a35b..f3b906b770 100644 --- a/JavaScript/JavaScript.sublime-syntax +++ b/JavaScript/JavaScript.sublime-syntax @@ -45,11 +45,56 @@ variables: # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar reserved_word: |- (?x: - break|case|catch|class|const|continue|debugger|default|delete|do|else| - export|extends|finally|for|function|if|import|in|instanceof|new|return| - super|switch|this|throw|try|typeof|var|void|while|with|yield| - enum| - null|true|false + # standard mode + break + | case + | catch + | class + | const + | continue + | debugger + | default + | delete + | do + | else + | export + | extends + | false + | finally + | for + | function + | if + | import + | in + | instanceof + | new + | null + | return + | super + | switch + | this + | throw + | true + | try + | typeof + | var + | void + | while + | with + # strict mode + # | let # currently allowed as variable + | static + | yield + # async code + | await + # future reserved words + | enum + | implements + | interface + | package + | private + | protected + | public ){{identifier_break}} non_reserved_identifier: (?:(?!{{reserved_word}}){{identifier_name}}) @@ -1563,6 +1608,7 @@ contexts: set: - class-meta - class-body + - class-implements - class-extends - class-name @@ -1622,6 +1668,12 @@ contexts: - inherited-class-expression-begin - include: else-pop + class-implements: + - match: implements{{identifier_break}} + scope: invalid.illegal.unexpected-token.js + pop: 1 + - include: else-pop + inherited-class-name: - match: '{{non_reserved_identifier}}{{left_expression_end_lookahead}}' scope: entity.other.inherited-class.js diff --git a/JavaScript/TypeScript.sublime-syntax b/JavaScript/TypeScript.sublime-syntax index 279a98530e..69b86eae60 100644 --- a/JavaScript/TypeScript.sublime-syntax +++ b/JavaScript/TypeScript.sublime-syntax @@ -263,17 +263,7 @@ contexts: - ts-abstract-class - expression-statement - class: - - match: class{{identifier_break}} - scope: keyword.declaration.class.js - set: - - class-meta - - class-body - - ts-class-implements - - class-extends - - class-name - - ts-class-implements: + class-implements: - match: implements{{identifier_break}} scope: storage.modifier.implements.js set: ts-inherited-class-expression-list diff --git a/JavaScript/tests/syntax_test_js.js b/JavaScript/tests/syntax_test_js.js index 54d90d8f9a..92a1a70643 100644 --- a/JavaScript/tests/syntax_test_js.js +++ b/JavaScript/tests/syntax_test_js.js @@ -1111,6 +1111,16 @@ var instance = new Constructor(param1, param2) var obj = new function() {}(); // ^^^^^^^^ keyword.declaration.function +var obj = new class extends Foo {}(); +// ^^^^^ keyword.declaration.class.js +// ^^^^^^^ storage.modifier.extends.js +// ^^^ entity.other.inherited-class.js + +var obj = new class implements IFoo {}(); +// ^^^^^ keyword.declaration.class.js +// ^^^^^^^^^^ invalid.illegal.unexpected-token.js +// ^^^^ - entity.other + var obj2 = new class Foo{}(); // ^^^^^ keyword.declaration.class diff --git a/JavaScript/tests/syntax_test_typescript.ts b/JavaScript/tests/syntax_test_typescript.ts index 6e28541b3c..41c4a98749 100644 --- a/JavaScript/tests/syntax_test_typescript.ts +++ b/JavaScript/tests/syntax_test_typescript.ts @@ -1680,3 +1680,52 @@ type T = Foo | ... 100 more ... | Bar; // ^^^^^^^ meta.function-call // ^^ keyword.operator.type // ^^ meta.function-call.arguments + +// instantiations + + new class extends Bar {}; +// ^^^ keyword.operator.word.new.js +// ^^^^^^^^^^^^^^^^^^^^^ meta.function-call.constructor.js +// ^^^^^^^^^^^^^^^^^^^^ meta.class.js +// ^^^^^ keyword.declaration.class.js +// ^^^^^^^ storage.modifier.extends.js +// ^^^ entity.other.inherited-class.js +// ^^ meta.block.js +// ^ punctuation.section.block.begin.js +// ^ punctuation.section.block.end.js + + new class Foo extends Bar {}; +// ^^^ keyword.operator.word.new.js +// ^^^^^^^^^^^^^^^^^^^^^^^^^ meta.function-call.constructor.js +// ^^^^^^^^^^^^^^^^^^^^^^^^ meta.class.js +// ^^^^^ keyword.declaration.class.js +// ^^^ entity.name.class.js +// ^^^^^^^ storage.modifier.extends.js +// ^^^ entity.other.inherited-class.js +// ^^ meta.block.js +// ^ punctuation.section.block.begin.js +// ^ punctuation.section.block.end.js + + new class implements IBar {}; +// ^^^ keyword.operator.word.new.js +// ^^^^^^^^^^^^^^^^^^^^^^^^^ meta.function-call.constructor.js +// ^^^^^^^^^^^^^^^^^^^^^^^^ meta.class.js +// ^^^^^ keyword.declaration.class.js +// ^^^^^^^^^^ storage.modifier.implements.js +// ^^^^ entity.other.inherited-class.js +// ^^ meta.block.js +// ^ punctuation.section.block.begin.js +// ^ punctuation.section.block.end.js + + new class Foo implements IBar {}; +// ^^^ keyword.operator.word.new.js +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.function-call.constructor.js +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.class.js +// ^^^^^ keyword.declaration.class.js +// ^^^ entity.name.class.js +// ^^^^^^^^^^ storage.modifier.implements.js +// ^^^^ entity.other.inherited-class.js +// ^^ meta.block.js +// ^ punctuation.section.block.begin.js +// ^ punctuation.section.block.end.js +// ^ punctuation.terminator.statement.js