tree-sitters/javascript/grammar.js

1243 lines
30 KiB
JavaScript
Raw Normal View History

2023-12-21 14:00:00 +01:00
/**
* @file JavaScript grammar for tree-sitter
* @author Max Brunsfeld <maxbrunsfeld@gmail.com>
* @license MIT
*/
/* eslint-disable arrow-parens */
/* eslint-disable camelcase */
/* eslint-disable-next-line spaced-comment */
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
module.exports = grammar({
name: 'javascript',
externals: $ => [
$._automatic_semicolon,
$._template_chars,
$._ternary_qmark,
],
extras: $ => [
$.comment,
/[\s\p{Zs}\uFEFF\u2028\u2029\u2060\u200B]/,
],
supertypes: $ => [
$.statement,
$.declaration,
$.expression,
$.primary_expression,
$.pattern,
],
inline: $ => [
$._call_signature,
$._formal_parameter,
$.statement,
$._expressions,
$._semicolon,
$._identifier,
$._reserved_identifier,
$._jsx_attribute,
$._jsx_element_name,
$._jsx_child,
$._jsx_element,
$._jsx_attribute_name,
$._jsx_attribute_value,
$._jsx_identifier,
$._lhs_expression,
],
precedences: $ => [
[
'member',
'call',
$.update_expression,
'unary_void',
'binary_exp',
'binary_times',
'binary_plus',
'binary_shift',
'binary_compare',
'binary_relation',
'binary_equality',
'bitwise_and',
'bitwise_xor',
'bitwise_or',
'logical_and',
'logical_or',
'ternary',
$.sequence_expression,
$.arrow_function,
],
['assign', $.primary_expression],
['member', 'new', 'call', $.expression],
['declaration', 'literal'],
[$.primary_expression, $.statement_block, 'object'],
[$.import_statement, $.import],
[$.export_statement, $.primary_expression],
],
conflicts: $ => [
[$.primary_expression, $._property_name],
[$.primary_expression, $._property_name, $.arrow_function],
[$.primary_expression, $.arrow_function],
[$.primary_expression, $.method_definition],
[$.primary_expression, $.rest_pattern],
[$.primary_expression, $.pattern],
[$.primary_expression, $._for_header],
[$.array, $.array_pattern],
[$.object, $.object_pattern],
[$.assignment_expression, $.pattern],
[$.assignment_expression, $.object_assignment_pattern],
[$.labeled_statement, $._property_name],
[$.computed_property_name, $.array],
[$.binary_expression, $._initializer],
],
word: $ => $.identifier,
rules: {
program: $ => seq(
optional($.hash_bang_line),
repeat($.statement),
),
hash_bang_line: _ => /#!.*/,
//
// Export declarations
//
export_statement: $ => choice(
seq(
'export',
choice(
seq('*', $._from_clause),
seq($.namespace_export, $._from_clause),
seq($.export_clause, $._from_clause),
$.export_clause,
),
$._semicolon,
),
seq(
repeat(field('decorator', $.decorator)),
'export',
choice(
field('declaration', $.declaration),
seq(
'default',
choice(
field('declaration', $.declaration),
seq(
field('value', $.expression),
$._semicolon,
),
),
),
),
),
),
namespace_export: $ => seq(
'*', 'as', $._module_export_name,
),
export_clause: $ => seq(
'{',
commaSep($.export_specifier),
optional(','),
'}',
),
export_specifier: $ => seq(
field('name', $._module_export_name),
optional(seq(
'as',
field('alias', $._module_export_name),
)),
),
_module_export_name: $ => choice(
$.identifier,
$.string,
),
declaration: $ => choice(
$.function_declaration,
$.generator_function_declaration,
$.class_declaration,
$.lexical_declaration,
$.variable_declaration,
),
//
// Import declarations
//
import: _ => token('import'),
import_statement: $ => seq(
'import',
choice(
seq($.import_clause, $._from_clause),
field('source', $.string),
),
$._semicolon,
),
import_clause: $ => choice(
$.namespace_import,
$.named_imports,
seq(
$.identifier,
optional(seq(
',',
choice(
$.namespace_import,
$.named_imports,
),
)),
),
),
_from_clause: $ => seq(
'from', field('source', $.string),
),
namespace_import: $ => seq(
'*', 'as', $.identifier,
),
named_imports: $ => seq(
'{',
commaSep($.import_specifier),
optional(','),
'}',
),
import_specifier: $ => choice(
field('name', $.identifier),
seq(
field('name', $._module_export_name),
'as',
field('alias', $.identifier),
),
),
//
// Statements
//
statement: $ => choice(
$.export_statement,
$.import_statement,
$.debugger_statement,
$.expression_statement,
$.declaration,
$.statement_block,
$.if_statement,
$.switch_statement,
$.for_statement,
$.for_in_statement,
$.while_statement,
$.do_statement,
$.try_statement,
$.with_statement,
$.break_statement,
$.continue_statement,
$.return_statement,
$.throw_statement,
$.empty_statement,
$.labeled_statement,
),
expression_statement: $ => seq(
$._expressions,
$._semicolon,
),
variable_declaration: $ => seq(
'var',
commaSep1($.variable_declarator),
$._semicolon,
),
lexical_declaration: $ => seq(
field('kind', choice('let', 'const')),
commaSep1($.variable_declarator),
$._semicolon,
),
variable_declarator: $ => seq(
field('name', choice($.identifier, $._destructuring_pattern)),
optional($._initializer),
),
statement_block: $ => prec.right(seq(
'{',
repeat($.statement),
'}',
optional($._automatic_semicolon),
)),
else_clause: $ => seq('else', $.statement),
if_statement: $ => prec.right(seq(
'if',
field('condition', $.parenthesized_expression),
field('consequence', $.statement),
optional(field('alternative', $.else_clause)),
)),
switch_statement: $ => seq(
'switch',
field('value', $.parenthesized_expression),
field('body', $.switch_body),
),
for_statement: $ => seq(
'for',
'(',
field('initializer', choice(
$.lexical_declaration,
$.variable_declaration,
$.expression_statement,
$.empty_statement,
)),
field('condition', choice(
$.expression_statement,
$.empty_statement,
)),
field('increment', optional($._expressions)),
')',
field('body', $.statement),
),
for_in_statement: $ => seq(
'for',
optional('await'),
$._for_header,
field('body', $.statement),
),
_for_header: $ => seq(
'(',
choice(
field('left', choice(
$._lhs_expression,
$.parenthesized_expression,
)),
seq(
field('kind', 'var'),
field('left', choice(
$.identifier,
$._destructuring_pattern,
)),
optional($._initializer),
),
seq(
field('kind', choice('let', 'const')),
field('left', choice(
$.identifier,
$._destructuring_pattern,
)),
),
),
field('operator', choice('in', 'of')),
field('right', $._expressions),
')',
),
while_statement: $ => seq(
'while',
field('condition', $.parenthesized_expression),
field('body', $.statement),
),
do_statement: $ => seq(
'do',
field('body', $.statement),
'while',
field('condition', $.parenthesized_expression),
),
try_statement: $ => seq(
'try',
field('body', $.statement_block),
optional(field('handler', $.catch_clause)),
optional(field('finalizer', $.finally_clause)),
),
with_statement: $ => seq(
'with',
field('object', $.parenthesized_expression),
field('body', $.statement),
),
break_statement: $ => seq(
'break',
field('label', optional(alias($.identifier, $.statement_identifier))),
$._semicolon,
),
continue_statement: $ => seq(
'continue',
field('label', optional(alias($.identifier, $.statement_identifier))),
$._semicolon,
),
debugger_statement: $ => seq(
'debugger',
$._semicolon,
),
return_statement: $ => seq(
'return',
optional($._expressions),
$._semicolon,
),
throw_statement: $ => seq(
'throw',
$._expressions,
$._semicolon,
),
empty_statement: _ => ';',
labeled_statement: $ => prec.dynamic(-1, seq(
field('label', alias(choice($.identifier, $._reserved_identifier), $.statement_identifier)),
':',
field('body', $.statement),
)),
//
// Statement components
//
switch_body: $ => seq(
'{',
repeat(choice($.switch_case, $.switch_default)),
'}',
),
switch_case: $ => seq(
'case',
field('value', $._expressions),
':',
field('body', repeat($.statement)),
),
switch_default: $ => seq(
'default',
':',
field('body', repeat($.statement)),
),
catch_clause: $ => seq(
'catch',
optional(seq('(', field('parameter', choice($.identifier, $._destructuring_pattern)), ')')),
field('body', $.statement_block),
),
finally_clause: $ => seq(
'finally',
field('body', $.statement_block),
),
parenthesized_expression: $ => seq(
'(',
$._expressions,
')',
),
//
// Expressions
//
_expressions: $ => choice(
$.expression,
$.sequence_expression,
),
expression: $ => choice(
$.primary_expression,
$.glimmer_template,
$._jsx_element,
$.assignment_expression,
$.augmented_assignment_expression,
$.await_expression,
$.unary_expression,
$.binary_expression,
$.ternary_expression,
$.update_expression,
$.new_expression,
$.yield_expression,
),
primary_expression: $ => choice(
$.subscript_expression,
$.member_expression,
$.parenthesized_expression,
$._identifier,
alias($._reserved_identifier, $.identifier),
$.this,
$.super,
$.number,
$.string,
$.template_string,
$.regex,
$.true,
$.false,
$.null,
$.import,
$.object,
$.array,
$.function,
$.arrow_function,
$.generator_function,
$.class,
$.meta_property,
$.call_expression,
),
yield_expression: $ => prec.right(seq(
'yield',
choice(
seq('*', $.expression),
optional($.expression),
))),
object: $ => prec('object', seq(
'{',
commaSep(optional(choice(
$.pair,
$.spread_element,
$.method_definition,
alias(
choice($.identifier, $._reserved_identifier),
$.shorthand_property_identifier,
),
))),
'}',
)),
object_pattern: $ => prec('object', seq(
'{',
commaSep(optional(choice(
$.pair_pattern,
$.rest_pattern,
$.object_assignment_pattern,
alias(
choice($.identifier, $._reserved_identifier),
$.shorthand_property_identifier_pattern,
),
))),
'}',
)),
assignment_pattern: $ => seq(
field('left', $.pattern),
'=',
field('right', $.expression),
),
object_assignment_pattern: $ => seq(
field('left', choice(
alias(choice($._reserved_identifier, $.identifier), $.shorthand_property_identifier_pattern),
$._destructuring_pattern,
)),
'=',
field('right', $.expression),
),
array: $ => seq(
'[',
commaSep(optional(choice(
$.expression,
$.spread_element,
))),
']',
),
array_pattern: $ => seq(
'[',
commaSep(optional(choice(
$.pattern,
$.assignment_pattern,
))),
']',
),
glimmer_template: $ => choice(
seq(
field('open_tag', $.glimmer_opening_tag),
field('content', repeat($._glimmer_template_content)),
field('close_tag', $.glimmer_closing_tag),
),
// empty template has no content
// <template></template>
seq(
field('open_tag', $.glimmer_opening_tag),
field('close_tag', $.glimmer_closing_tag),
),
),
_glimmer_template_content: _ => /.{1,}/,
glimmer_opening_tag: _ => seq('<template>'),
glimmer_closing_tag: _ => seq('</template>'),
_jsx_element: $ => choice($.jsx_element, $.jsx_self_closing_element),
jsx_element: $ => seq(
field('open_tag', $.jsx_opening_element),
repeat($._jsx_child),
field('close_tag', $.jsx_closing_element),
),
// Should not contain new lines and should not start or end with a space
jsx_text: _ => choice(
/[^{}<>\n ]([^{}<>\n]*[^{}<>\n ])?/,
/\/\/[^\n]*/,
),
jsx_expression: $ => seq(
'{',
optional(choice(
$.expression,
$.sequence_expression,
$.spread_element,
)),
'}',
),
_jsx_child: $ => choice(
$.jsx_text,
$._jsx_element,
$.jsx_expression,
),
jsx_opening_element: $ => prec.dynamic(-1, seq(
'<',
optional(seq(
field('name', $._jsx_element_name),
repeat(field('attribute', $._jsx_attribute)),
)),
'>',
)),
jsx_identifier: _ => /[a-zA-Z_$][a-zA-Z\d_$]*-[a-zA-Z\d_$\-]*/,
_jsx_identifier: $ => choice(
alias($.jsx_identifier, $.identifier),
$.identifier,
),
nested_identifier: $ => prec('member', seq(
choice($.identifier, alias($.nested_identifier, $.member_expression)),
'.',
alias($.identifier, $.property_identifier),
)),
jsx_namespace_name: $ => seq($._jsx_identifier, ':', $._jsx_identifier),
_jsx_element_name: $ => choice(
$._jsx_identifier,
alias($.nested_identifier, $.member_expression),
$.jsx_namespace_name,
),
jsx_closing_element: $ => seq(
'</',
optional(field('name', $._jsx_element_name)),
'>',
),
jsx_self_closing_element: $ => seq(
'<',
field('name', $._jsx_element_name),
repeat(field('attribute', $._jsx_attribute)),
'/>',
),
_jsx_attribute: $ => choice($.jsx_attribute, $.jsx_expression),
_jsx_attribute_name: $ => choice(alias($._jsx_identifier, $.property_identifier), $.jsx_namespace_name),
jsx_attribute: $ => seq(
$._jsx_attribute_name,
optional(seq(
'=',
$._jsx_attribute_value,
)),
),
_jsx_attribute_value: $ => choice(
$.string,
$.jsx_expression,
$._jsx_element,
),
class: $ => prec('literal', seq(
repeat(field('decorator', $.decorator)),
'class',
field('name', optional($.identifier)),
optional($.class_heritage),
field('body', $.class_body),
)),
class_declaration: $ => prec('declaration', seq(
repeat(field('decorator', $.decorator)),
'class',
field('name', $.identifier),
optional($.class_heritage),
field('body', $.class_body),
optional($._automatic_semicolon),
)),
class_heritage: $ => seq('extends', $.expression),
function: $ => prec('literal', seq(
optional('async'),
'function',
field('name', optional($.identifier)),
$._call_signature,
field('body', $.statement_block),
)),
function_declaration: $ => prec.right('declaration', seq(
optional('async'),
'function',
field('name', $.identifier),
$._call_signature,
field('body', $.statement_block),
optional($._automatic_semicolon),
)),
generator_function: $ => prec('literal', seq(
optional('async'),
'function',
'*',
field('name', optional($.identifier)),
$._call_signature,
field('body', $.statement_block),
)),
generator_function_declaration: $ => prec.right('declaration', seq(
optional('async'),
'function',
'*',
field('name', $.identifier),
$._call_signature,
field('body', $.statement_block),
optional($._automatic_semicolon),
)),
arrow_function: $ => seq(
optional('async'),
choice(
field('parameter', choice(
alias($._reserved_identifier, $.identifier),
$.identifier,
)),
$._call_signature,
),
'=>',
field('body', choice(
$.expression,
$.statement_block,
)),
),
// Override
_call_signature: $ => field('parameters', $.formal_parameters),
_formal_parameter: $ => choice($.pattern, $.assignment_pattern),
optional_chain: _ => '?.',
call_expression: $ => choice(
prec('call', seq(
field('function', $.expression),
field('arguments', choice($.arguments, $.template_string)),
)),
prec('member', seq(
field('function', $.primary_expression),
field('optional_chain', $.optional_chain),
field('arguments', $.arguments),
)),
),
new_expression: $ => prec.right('new', seq(
'new',
field('constructor', choice($.primary_expression, $.new_expression)),
field('arguments', optional(prec.dynamic(1, $.arguments))),
)),
await_expression: $ => prec('unary_void', seq(
'await',
$.expression,
)),
member_expression: $ => prec('member', seq(
field('object', choice($.expression, $.primary_expression)),
choice('.', field('optional_chain', $.optional_chain)),
field('property', choice(
$.private_property_identifier,
alias($.identifier, $.property_identifier))),
)),
subscript_expression: $ => prec.right('member', seq(
field('object', choice($.expression, $.primary_expression)),
optional(field('optional_chain', $.optional_chain)),
'[', field('index', $._expressions), ']',
)),
_lhs_expression: $ => choice(
$.member_expression,
$.subscript_expression,
$._identifier,
alias($._reserved_identifier, $.identifier),
$._destructuring_pattern,
),
assignment_expression: $ => prec.right('assign', seq(
field('left', choice($.parenthesized_expression, $._lhs_expression)),
'=',
field('right', $.expression),
)),
_augmented_assignment_lhs: $ => choice(
$.member_expression,
$.subscript_expression,
alias($._reserved_identifier, $.identifier),
$.identifier,
$.parenthesized_expression,
),
augmented_assignment_expression: $ => prec.right('assign', seq(
field('left', $._augmented_assignment_lhs),
field('operator', choice('+=', '-=', '*=', '/=', '%=', '^=', '&=', '|=', '>>=', '>>>=',
'<<=', '**=', '&&=', '||=', '??=')),
field('right', $.expression),
)),
_initializer: $ => seq(
'=',
field('value', $.expression),
),
_destructuring_pattern: $ => choice(
$.object_pattern,
$.array_pattern,
),
spread_element: $ => seq('...', $.expression),
ternary_expression: $ => prec.right('ternary', seq(
field('condition', $.expression),
alias($._ternary_qmark, '?'),
field('consequence', $.expression),
':',
field('alternative', $.expression),
)),
binary_expression: $ => choice(
...[
['&&', 'logical_and'],
['||', 'logical_or'],
['>>', 'binary_shift'],
['>>>', 'binary_shift'],
['<<', 'binary_shift'],
['&', 'bitwise_and'],
['^', 'bitwise_xor'],
['|', 'bitwise_or'],
['+', 'binary_plus'],
['-', 'binary_plus'],
['*', 'binary_times'],
['/', 'binary_times'],
['%', 'binary_times'],
['**', 'binary_exp', 'right'],
['<', 'binary_relation'],
['<=', 'binary_relation'],
['==', 'binary_equality'],
['===', 'binary_equality'],
['!=', 'binary_equality'],
['!==', 'binary_equality'],
['>=', 'binary_relation'],
['>', 'binary_relation'],
['??', 'ternary'],
['instanceof', 'binary_relation'],
['in', 'binary_relation'],
].map(([operator, precedence, associativity]) =>
(associativity === 'right' ? prec.right : prec.left)(precedence, seq(
field('left', operator === 'in' ? choice($.expression, $.private_property_identifier) : $.expression),
field('operator', operator),
field('right', $.expression),
)),
),
),
unary_expression: $ => prec.left('unary_void', seq(
field('operator', choice('!', '~', '-', '+', 'typeof', 'void', 'delete')),
field('argument', $.expression),
)),
update_expression: $ => prec.left(choice(
seq(
field('argument', $.expression),
field('operator', choice('++', '--')),
),
seq(
field('operator', choice('++', '--')),
field('argument', $.expression),
),
)),
sequence_expression: $ => seq(
field('left', $.expression),
',',
field('right', choice($.sequence_expression, $.expression)),
),
//
// Primitives
//
// Here we tolerate unescaped newlines in double-quoted and
// single-quoted string literals.
// This is legal in typescript as jsx/tsx attribute values (as of
// 2020), and perhaps will be valid in javascript as well in the
// future.
//
string: $ => choice(
seq(
'"',
repeat(choice(
alias($.unescaped_double_string_fragment, $.string_fragment),
$.escape_sequence,
)),
'"',
),
seq(
'\'',
repeat(choice(
alias($.unescaped_single_string_fragment, $.string_fragment),
$.escape_sequence,
)),
'\'',
),
),
// Workaround to https://github.com/tree-sitter/tree-sitter/issues/1156
// We give names to the token() constructs containing a regexp
// so as to obtain a node in the CST.
//
unescaped_double_string_fragment: _ => token.immediate(prec(1, /[^"\\]+/)),
// same here
unescaped_single_string_fragment: _ => token.immediate(prec(1, /[^'\\]+/)),
escape_sequence: _ => token.immediate(seq(
'\\',
choice(
/[^xu0-7]/,
/[0-7]{1,3}/,
/x[0-9a-fA-F]{2}/,
/u[0-9a-fA-F]{4}/,
/u{[0-9a-fA-F]+}/,
),
)),
// http://stackoverflow.com/questions/13014947/regex-to-match-a-c-style-multiline-comment/36328890#36328890
comment: _ => token(choice(
seq('//', /.*/),
seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/',
),
// https://tc39.es/ecma262/#sec-html-like-comments
seq('<!--', /.*/),
// This allows code to exist before this token on the same line.
//
// Technically, --> is supposed to have nothing before it on the same line
// except for comments and whitespace, but that is difficult to express,
// and in general tree sitter grammars tend to prefer to be overly
// permissive anyway.
//
// This approach does not appear to cause problems in practice.
seq('-->', /.*/),
)),
template_string: $ => seq(
'`',
repeat(choice(
$._template_chars,
$.escape_sequence,
$.template_substitution,
)),
'`',
),
template_substitution: $ => seq(
'${',
$._expressions,
'}',
),
regex: $ => seq(
'/',
field('pattern', $.regex_pattern),
token.immediate('/'),
optional(field('flags', $.regex_flags)),
),
regex_pattern: _ => token.immediate(prec(-1,
repeat1(choice(
seq(
'[',
repeat(choice(
seq('\\', /./), // escaped character
/[^\]\n\\]/, // any character besides ']' or '\n'
)),
']',
), // square-bracket-delimited character class
seq('\\', /./), // escaped character
/[^/\\\[\n]/, // any character besides '[', '\', '/', '\n'
)),
)),
regex_flags: _ => token.immediate(/[a-z]+/),
number: _ => {
const hex_literal = seq(
choice('0x', '0X'),
/[\da-fA-F](_?[\da-fA-F])*/,
);
const decimal_digits = /\d(_?\d)*/;
const signed_integer = seq(optional(choice('-', '+')), decimal_digits);
const exponent_part = seq(choice('e', 'E'), signed_integer);
const binary_literal = seq(choice('0b', '0B'), /[0-1](_?[0-1])*/);
const octal_literal = seq(choice('0o', '0O'), /[0-7](_?[0-7])*/);
const bigint_literal = seq(choice(hex_literal, binary_literal, octal_literal, decimal_digits), 'n');
const decimal_integer_literal = choice(
'0',
seq(optional('0'), /[1-9]/, optional(seq(optional('_'), decimal_digits))),
);
const decimal_literal = choice(
seq(decimal_integer_literal, '.', optional(decimal_digits), optional(exponent_part)),
seq('.', decimal_digits, optional(exponent_part)),
seq(decimal_integer_literal, exponent_part),
seq(decimal_digits),
);
return token(choice(
hex_literal,
decimal_literal,
binary_literal,
octal_literal,
bigint_literal,
));
},
// 'undefined' is syntactically a regular identifier in JavaScript.
// However, its main use is as the read-only global variable whose
// value is [undefined], for which there's no literal representation
// unlike 'null'. We gave it its own rule so it's easy to
// highlight in text editors and other applications.
_identifier: $ => choice(
$.undefined,
$.identifier,
),
identifier: _ => {
// eslint-disable-next-line max-len
const alpha = /[^\x00-\x1F\s\p{Zs}0-9:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B]|\\u[0-9a-fA-F]{4}|\\u\{[0-9a-fA-F]+\}/;
// eslint-disable-next-line max-len
const alphanumeric = /[^\x00-\x1F\s\p{Zs}:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B]|\\u[0-9a-fA-F]{4}|\\u\{[0-9a-fA-F]+\}/;
return token(seq(alpha, repeat(alphanumeric)));
},
private_property_identifier: _ => {
// eslint-disable-next-line max-len
const alpha = /[^\x00-\x1F\s\p{Zs}0-9:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B]|\\u[0-9a-fA-F]{4}|\\u\{[0-9a-fA-F]+\}/;
// eslint-disable-next-line max-len
const alphanumeric = /[^\x00-\x1F\s\p{Zs}:;`"'@#.,|^&<=>+\-*/\\%?!~()\[\]{}\uFEFF\u2060\u200B]|\\u[0-9a-fA-F]{4}|\\u\{[0-9a-fA-F]+\}/;
return token(seq('#', alpha, repeat(alphanumeric)));
},
meta_property: _ => seq('new', '.', 'target'),
this: _ => 'this',
super: _ => 'super',
true: _ => 'true',
false: _ => 'false',
null: _ => 'null',
undefined: _ => 'undefined',
//
// Expression components
//
arguments: $ => seq(
'(',
commaSep(optional(choice($.expression, $.spread_element))),
')',
),
decorator: $ => seq(
'@',
choice(
$.identifier,
alias($.decorator_member_expression, $.member_expression),
alias($.decorator_call_expression, $.call_expression),
),
),
decorator_member_expression: $ => prec('member', seq(
field('object', choice(
$.identifier,
alias($.decorator_member_expression, $.member_expression),
)),
'.',
field('property', alias($.identifier, $.property_identifier)),
)),
decorator_call_expression: $ => prec('call', seq(
field('function', choice(
$.identifier,
alias($.decorator_member_expression, $.member_expression),
)),
field('arguments', $.arguments),
)),
class_body: $ => seq(
'{',
repeat(choice(
seq(field('member', $.method_definition), optional(';')),
seq(field('member', $.field_definition), $._semicolon),
field('member', $.class_static_block),
field('template', $.glimmer_template),
)),
'}',
),
field_definition: $ => seq(
repeat(field('decorator', $.decorator)),
optional('static'),
field('property', $._property_name),
optional($._initializer),
),
formal_parameters: $ => seq(
'(',
optional(seq(
commaSep1($._formal_parameter),
optional(','),
)),
')',
),
class_static_block: $ => seq(
'static',
field('body', $.statement_block),
),
// This negative dynamic precedence ensures that during error recovery,
// unfinished constructs are generally treated as literal expressions,
// not patterns.
pattern: $ => prec.dynamic(-1, choice(
$._lhs_expression,
$.rest_pattern,
)),
rest_pattern: $ => prec.right(seq(
'...',
$._lhs_expression,
)),
method_definition: $ => seq(
repeat(field('decorator', $.decorator)),
optional(choice('static', alias(token(seq('static', /\s+/, 'get', /\s*\n/)), 'static get'))),
optional('async'),
optional(choice('get', 'set', '*')),
field('name', $._property_name),
field('parameters', $.formal_parameters),
field('body', $.statement_block),
),
pair: $ => seq(
field('key', $._property_name),
':',
field('value', $.expression),
),
pair_pattern: $ => seq(
field('key', $._property_name),
':',
field('value', choice($.pattern, $.assignment_pattern)),
),
_property_name: $ => choice(
alias(choice(
$.identifier,
$._reserved_identifier,
), $.property_identifier),
$.private_property_identifier,
$.string,
$.number,
$.computed_property_name,
),
computed_property_name: $ => seq(
'[',
$.expression,
']',
),
_reserved_identifier: _ => choice(
'get',
'set',
'async',
'static',
'export',
),
_semicolon: $ => choice($._automatic_semicolon, ';'),
},
});
/**
* Creates a rule to match one or more of the rules separated by a comma
*
* @param {Rule} rule
*
* @return {SeqRule}
*
*/
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}
/**
* Creates a rule to optionally match one or more of the rules separated by a comma
*
* @param {Rule} rule
*
* @return {ChoiceRule}
*
*/
function commaSep(rule) {
return optional(commaSep1(rule));
}