const HTML = require('tree-sitter-html/grammar');
const PREC = {
CALL: 1,
ALIAS: 2,
PIPE: 3,
NULLISH: 4,
INTERPOLATION: 5,
};
module.exports = grammar(HTML, {
name: 'angular',
externals: ($, original) =>
original.concat([
$._interpolation_start,
$._interpolation_end,
$.if_start,
$.else_start,
$.for_start,
$.switch_start,
$.case_start,
$.default_start,
$.defer_start,
$.let_start,
$.empty_start,
$.placeholder_start,
$.loading_start,
$.error_start,
$.else_if_start,
$.at_sign,
]),
rules: {
_node: ($, original) =>
choice(
prec(1, $.icu_expression),
prec(1, $.interpolation),
prec(1, $._any_statement),
$.at_sign,
original,
),
attribute_name: (_) => /[^<>\*.\[\]\(\)"'=\s]+/,
text: (_) => /[^<>{}&@\s]([^<>{}&@]*[^<>{}&@\s])?/,
statement_block: ($) => prec.right(seq('{', repeat($._node), '}')),
_any_statement: ($) =>
choice(
$.if_statement,
$.for_statement,
$.defer_statement,
$.switch_statement,
$.let_statement,
$._alternative_statement,
),
_alternative_statement: ($) =>
choice(
field('alternative', $.else_statement),
field('alternative_condition', $.else_if_statement),
field('empty', $.empty_statement),
field('placeholder', $.placeholder_statement),
field('loading', $.loading_statement),
field('error', $.error_statement),
),
let_statement: ($) =>
prec.left(seq(alias($.let_start, $.control_keyword), $.assignment_expression, ';')),
switch_statement: ($) =>
prec.right(
seq(
alias($.switch_start, $.control_keyword),
'(',
field('value', $.expression),
')',
field('body', $.switch_body),
),
),
switch_body: ($) =>
seq('{', repeat1(choice($.case_statement, $.default_statement)), '}'),
case_statement: ($) =>
seq(
alias($.case_start, $.control_keyword),
'(',
field('value', $._any_expression),
')',
field('body', $.statement_block),
),
default_statement: ($) =>
seq(alias($.default_start, $.control_keyword), field('body', $.statement_block)),
defer_statement: ($) =>
prec.left(
seq(
alias($.defer_start, $.control_keyword),
optional($.defer_trigger),
field('body', $.statement_block),
),
),
placeholder_statement: ($) =>
prec.left(
seq(
alias($.placeholder_start, $.control_keyword),
optional($.placeholder_minimum),
field('body', $.statement_block),
),
),
loading_statement: ($) =>
prec.left(
seq(
alias($.loading_start, $.control_keyword),
optional($.loading_condition),
field('body', $.statement_block),
),
),
error_statement: ($) =>
seq(alias($.error_start, $.control_keyword), field('body', $.statement_block)),
defer_trigger: ($) =>
seq(
'(',
field('condition', $.defer_trigger_condition),
optional(repeat(seq(';', field('condition', $.defer_trigger_condition)))),
')',
),
placeholder_minimum: ($) => seq('(', field('minimum', $.timed_expression), ')'),
loading_condition: ($) =>
seq(
'(',
field('condition', $.timed_expression),
optional(seq(';', field('condition', $.timed_expression))),
')',
),
defer_trigger_condition: ($) =>
seq(
optional(alias('prefetch', $.prefetch_keyword)),
choice(
seq(alias('when', $.special_keyword), field('trigger', $._any_expression)),
seq(alias('on', $.special_keyword), field('trigger', $._primitive)),
),
),
timed_expression: ($) =>
seq(alias(choice('after', 'minimum'), $.special_keyword), field('value', $.number)),
for_statement: ($) =>
prec.left(
seq(
alias($.for_start, $.control_keyword),
'(',
field('declaration', $.for_declaration),
optional(field('references', $.for_references)),
')',
field('body', $.statement_block),
),
),
empty_statement: ($) =>
seq(alias($.empty_start, $.control_keyword), field('body', $.statement_block)),
for_declaration: ($) =>
seq(
field('name', $.identifier),
alias('of', $.special_keyword),
field('value', $.expression),
';',
alias('track', $.special_keyword),
field('track', $._any_expression),
),
for_references: ($) =>
seq(
';',
field('reference', $.for_reference),
repeat(seq(';', field('reference', $.for_reference))),
),
for_reference: ($) =>
seq(
alias('let', $.special_keyword),
field('alias', $.assignment_expression),
repeat(seq(',', field('alias', $.assignment_expression))),
),
if_statement: ($) =>
prec.right(
seq(
alias($.if_start, $.control_keyword),
'(',
field('condition', $.if_condition),
optional(field('reference', $.if_reference)),
')',
field('consequence', $.statement_block),
optional(repeat($._alternative_statement)),
),
),
else_if_statement: ($) =>
prec.right(
seq(
alias($.else_if_start, $.control_keyword),
'(',
field('condition', $.if_condition),
optional(field('reference', $.if_reference)),
')',
field('consequence', $.statement_block),
),
),
else_statement: ($) =>
prec.right(
seq(alias($.else_start, $.control_keyword), field('body', $.statement_block)),
),
if_condition: ($) => prec.right(PREC.CALL, $._any_expression),
if_reference: ($) => seq(';', alias('as', $.special_keyword), $.identifier),
_any_expression: ($) =>
choice(
$.binary_expression,
$.unary_expression,
$.expression,
$.ternary_expression,
$.nullish_coalescing_expression,
prec(3, $.conditional_expression),
),
assignment_expression: ($) =>
seq(field('name', $.identifier), '=', field('value', $._any_expression)),
icu_expression: ($) =>
seq(
'{',
choice($._any_expression),
',',
$.icu_clause,
',',
repeat1($.icu_case),
'}',
),
icu_clause: () => choice('plural', 'select'),
icu_case: ($) => seq($.icu_category, '{', repeat1($._node), '}'),
icu_category: () => /[^{}]+/i,
interpolation: ($) =>
prec.right(
PREC.INTERPOLATION,
seq(
alias($._interpolation_start, '{{'),
choice($._any_expression),
alias($._interpolation_end, '}}'),
),
),
attribute: ($) =>
choice(
prec(1, $.property_binding),
prec(1, $.two_way_binding),
prec(1, $.animation_binding),
prec(1, $.event_binding),
prec(1, $.structural_directive),
$._normal_attribute, ),
structural_directive: ($) =>
seq(
'*',
$.identifier,
optional(
seq(
'=',
$._double_quote,
choice($.structural_declaration, $.structural_expression),
$._double_quote,
),
),
),
structural_expression: ($) =>
seq(
$._any_expression,
optional($._alias),
choice(
$._structural_let_expression,
seq(
optional($._then_template_expression),
optional($._else_template_expression),
),
optional($._context_expression),
),
),
structural_declaration: ($) =>
seq(
alias('let', $.special_keyword),
seq(
$.structural_assignment,
repeat(seq(choice(';', ','), $.structural_assignment)),
),
),
structural_assignment: ($) =>
choice(
seq(field('name', $.identifier), ':', field('value', $.identifier)),
prec.left(
PREC.ALIAS,
seq(
optional(alias('let', $.special_keyword)),
field('name', $.identifier),
optional(
seq(
field('operator', choice('=', 'of')),
field('value', $.expression),
optional($._alias),
),
),
),
),
seq(field('name', $.identifier), optional($._alias)),
),
_alias: ($) => seq(alias('as', $.special_keyword), field('alias', $.identifier)),
_then_template_expression: ($) =>
seq(';', alias('then', $.special_keyword), $.identifier),
_else_template_expression: ($) =>
seq(';', alias('else', $.special_keyword), $.identifier),
_context_expression: ($) =>
seq(
';',
choice(alias('context', $.special_keyword), field('named', $.identifier)),
':',
$._any_expression,
),
_structural_let_expression: ($) =>
seq(
';',
alias('let', $.special_keyword),
field('name', $.identifier),
optional($._alias),
),
property_binding: ($) => seq('[', $.binding_name, ']', $._binding_assignment),
event_binding: ($) => seq('(', $.binding_name, ')', $._binding_assignment),
two_way_binding: ($) => seq('[(', $.binding_name, ')]', $._binding_assignment),
animation_binding: ($) =>
seq('[@', $.binding_name, ']', optional(field('trigger', $._binding_assignment))),
_binding_assignment: ($) =>
seq(
'=',
$._double_quote,
optional(choice($._any_expression, $.assignment_expression)),
repeat(seq(';', optional(choice($._any_expression, $.assignment_expression)))),
$._double_quote,
),
binding_name: ($) => seq(choice($.identifier, $.member_expression)),
_attribute_node: ($) =>
choice(
repeat(choice(token.immediate(/[^"]/), $._escape_sequence)),
prec(1, $.interpolation),
),
_normal_attribute: ($) =>
seq(
$.attribute_name,
optional(
seq(
'=',
choice(
seq(
$._double_quote,
repeat(
choice(
alias(token.immediate(/[^"{]+/), $.string_content),
$._escape_sequence,
$.interpolation,
),
),
$._double_quote,
),
seq(
$._single_quote,
repeat(
choice(
alias(token.immediate(/[^'{]+/), $.string_content),
$._escape_sequence,
$.interpolation,
),
),
$._single_quote,
),
),
),
),
),
expression: ($) =>
prec.left(seq($._primitive, optional(field('pipes', $.pipe_sequence)))),
unary_expression: ($) =>
seq(
field('operator', alias('!', $.unary_operator)),
field('value', choice($.expression, $.unary_expression)),
),
binary_expression: ($) =>
prec.left(
PREC.CALL + 1, seq(
field('left', choice($.expression, $.binary_expression)),
field('operator', $._binary_op),
field('right', $.expression),
),
),
ternary_expression: ($) =>
prec.right(
PREC.CALL,
seq(
field('condition', $._any_expression),
alias('?', $.ternary_operator),
field('consequence', choice($.group, $._any_expression)),
alias(':', $.ternary_operator),
field('alternative', choice($.group, $._any_expression)),
),
),
nullish_coalescing_expression: ($) =>
prec.right(
PREC.NULLISH,
seq(
field('condition', $._any_expression),
alias('??', $.coalescing_operator),
field('default', $._primitive),
),
),
conditional_expression: ($) =>
prec.right(
PREC.CALL,
seq(
field('left', choice($._primitive, $.unary_expression, $.binary_expression)),
alias(choice('||', '&&'), $.conditional_operator),
field(
'right',
choice(
$.expression,
$.unary_expression,
$.binary_expression,
$.conditional_expression,
),
),
),
),
pipe_sequence: ($) =>
prec.left(PREC.PIPE, repeat1(seq(alias('|', $.pipe_operator), $.pipe_call))),
pipe_call: ($) =>
prec.left(
PREC.PIPE,
seq(field('name', $.identifier), optional(field('arguments', $.pipe_arguments))),
),
pipe_arguments: ($) =>
prec.left(
PREC.PIPE,
seq(
':',
field('argument', $._any_expression), repeat(seq(':', field('argument', $._any_expression))), ),
),
_primitive: ($) =>
choice(
$.object,
$.array,
$.identifier,
$.string,
$.number,
$.group,
$.call_expression,
$.member_expression,
$.bracket_expression,
),
object: ($) => seq('{', commaSep($.pair), optional(','), '}'),
pair: ($) =>
seq(
field('key', choice($.identifier, $.string)),
optional(seq(':', field('value', $._any_expression))),
),
array: ($) => seq('[', commaSep($._any_expression), optional(','), ']'),
identifier: () => /[a-zA-Z_\$][a-zA-Z_0-9\-\$]*/,
_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]+\}/,
/[\r?][\n\u2028\u2029]/,
),
),
),
string: ($) =>
choice(
seq(
$._double_quote,
repeat(choice(token.immediate(/[^"]/), $._escape_sequence)),
$._double_quote,
),
seq(
$._single_quote,
repeat(choice(token.immediate(/[^']/), $._escape_sequence)),
$._single_quote,
),
),
number_fragment: () => /\-?[0-9]+\.?[0-9]*/,
number: ($) =>
choice($.number_fragment, seq($.number_fragment, alias(choice('ms', 's'), $.unit))),
group: ($) => seq('(', $._any_expression, ')'),
call_expression: ($) =>
prec.left(
PREC.CALL,
seq(
field('function', $.identifier),
'(',
optional(field('arguments', $.arguments)),
')',
),
),
arguments: ($) =>
commaSep1(choice($.expression, $.binary_expression, $.unary_expression)),
member_expression: ($) =>
seq(
field('object', $._primitive),
choice(
seq(
choice('.', '?.', '!.'),
choice(field('property', $.identifier), field('call', $.call_expression)),
),
),
),
bracket_expression: ($) =>
prec.left(
PREC.CALL,
seq(
field('object', $._primitive),
optional(choice('?.', '!')),
'[',
field(
'property',
choice(
$.identifier,
$.static_member_expression,
$.bracket_expression,
$.member_expression,
),
),
']',
),
),
static_member_expression: ($) => $._any_expression,
_closing_bracket: (_) => token(prec(-1, '}')),
_single_quote: () => "'",
_double_quote: () => '"',
_binary_op: () =>
choice(
'+',
'-',
'/',
'*',
'%',
'==',
'===',
'!=',
'!==',
'&&',
'||',
'<',
'<=',
'>',
'>=',
),
},
});
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}
function commaSep(rule) {
return optional(commaSep1(rule));
}