module.exports = grammar({
name: 'jsdoc',
externals: $ => [
$.type,
$.code_block_line,
],
extras: _ => [
token(choice(
seq(/\n/, /[ \t]*/, repeat(seq('*', /[ \t]*/))),
/\s/,
)),
],
supertypes: $ => [
$.expression,
],
rules: {
document: $ => seq(
$._begin,
optional($.description),
repeat($.tag),
$._end,
),
description: $ => choice(
seq(
choice($._text, $.code_block),
repeat(choice($._text, $.inline_tag, $._inline_tag_false_positive, $.code_block)),
),
),
tag: $ => choice(
seq(
alias($.tag_name_with_argument, $.tag_name),
optional(seq('{', $.type, '}')),
optional(choice($.expression, $.optional_identifier)),
optional($.description),
),
seq(
alias($.tag_name_with_type, $.tag_name),
optional(seq('{', $.type, '}')),
optional($.description),
),
seq(
$.tag_name,
optional($.description),
),
),
inline_tag: $ => seq(
'{',
$.tag_name,
$.description,
'}',
),
_inline_tag_false_positive: _ => token(prec.left(1, /\{[^@}]+\}?/)),
tag_name_with_argument: _ => token(choice(
'@access',
'@alias',
'@api',
'@augments',
'@borrows',
'@callback',
'@constructor',
'@event',
'@exports',
'@external',
'@extends',
'@fires',
'@function',
'@import',
'@member',
'@mixes',
'@name',
'@namespace',
'@param',
'@property',
'@prop',
'@satisfies',
'@typedef',
)),
tag_name_with_type: _ => token(choice(
'@return',
'@returns',
'@this',
'@throw',
'@throws',
'@type',
)),
tag_name: _ => /@[a-zA-Z_]+/,
expression: $ => choice(
$.identifier,
$.number,
$.member_expression,
$.path_expression,
$.qualified_expression,
$.array_expression,
),
qualified_expression: $ => prec(1, seq(
$.identifier,
':',
$.expression,
)),
path_expression: $ => prec(2, seq(
$.identifier,
token.immediate('/'),
$.identifier,
)),
member_expression: $ => seq(
$.expression,
choice(
'.',
'#',
'~',
),
choice(
$.identifier,
$.qualified_expression,
),
),
array_expression: $ => seq(
'[',
commaSep($.expression),
']',
),
code_block: $ => seq(
'```',
optional($.code_block_language),
repeat($.code_block_line),
'```',
),
code_block_language: _ => /[a-z]+/,
code_block_line: _ => /(([^`\n][^`\n][^`\n]).)+/,
optional_identifier: $ => prec(1, seq(
'[',
$.identifier,
optional(seq('=', field('value', $.expression))),
']',
)),
identifier: _ => /[a-zA-Z_$][a-zA-Z_$0-9]*/,
number: _ => {
const hexLiteral = seq(
choice('0x', '0X'),
/[\da-fA-F](_?[\da-fA-F])*/,
);
const decimalDigits = /\d(_?\d)*/;
const signedInteger = seq(optional(choice('-', '+')), decimalDigits);
const exponentPart = seq(choice('e', 'E'), signedInteger);
const binaryLiteral = seq(choice('0b', '0B'), /[0-1](_?[0-1])*/);
const octalLiteral = seq(choice('0o', '0O'), /[0-7](_?[0-7])*/);
const bigintLiteral = seq(choice(hexLiteral, binaryLiteral, octalLiteral, decimalDigits), 'n');
const decimalIntegerLiteral = choice(
'0',
seq(optional('0'), /[1-9]/, optional(seq(optional('_'), decimalDigits))),
);
const decimalLiteral = choice(
seq(decimalIntegerLiteral, '.', optional(decimalDigits), optional(exponentPart)),
seq('.', decimalDigits, optional(exponentPart)),
seq(decimalIntegerLiteral, exponentPart),
decimalDigits,
);
return token(choice(
hexLiteral,
decimalLiteral,
binaryLiteral,
octalLiteral,
bigintLiteral,
));
},
_text: _ => token(prec(-1, /[^*{}@\s][^*{}\n]*([^*/{}\n][^*{}\n]*\*+)*/)),
_begin: _ => seq('/', repeat('*')),
_end: _ => '/',
},
});
function commaSep(rule) {
return optional(commaSep1(rule));
}
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)));
}