/** * @file Tree-sitter grammar definition * @author ObserverOfTime * @license MIT */ /** Built-in attribute names */ const BUILTIN_ATTRIBUTES = [ 'text', 'eol', 'crlf', 'working-tree-encoding', 'ident', 'filter', 'diff', 'merge', 'whitespace', 'export-ignore', 'export-subst', 'delta', 'encoding', 'binary' ]; /** POSIX character classes */ const POSIX_CLASSES = [ 'alnum', 'alpha', 'blank', 'cntrl', 'digit', 'graph', 'lower', 'print', 'punct', 'space', 'upper', 'xdigit' ]; /** * Returns a quoted or unquoted pattern sequence * @param {GrammarSymbols} $ the symbols of the grammar * @param {boolean} quoted `true` if the pattern is in quotes */ const __pattern = ($, quoted) => [ optional(alias('!', $.pattern_negation)), // error optional(field('absolute', $.dir_sep)), quoted ? $._quoted_pattern : $._pattern, repeat(seq(field('relative', $.dir_sep), $._pattern)), optional(alias($.dir_sep, $.trailing_slash)) // error ] module.exports = grammar({ name: 'gitattributes', extras: _ => [], word: $ => $.attr_name, rules: { file: $ => repeat($._line), _line: $ => seq( optional($._space), optional(choice( $.comment, $._attr_list, $.macro_def )), choice($._eol, $._eof) ), _attr_list: $ => seq( prec.left(choice($.pattern, $.quoted_pattern)), repeat1(seq($._space, $.attribute)), optional($._space) ), pattern: $ => seq(...__pattern($, false)), _pattern: $ => repeat1(choice( $._pattern_char, $.wildcard, $.escaped_char, $.range_notation, alias('\\', $.redundant_escape) // error )), quoted_pattern: $ => seq( '"', ...__pattern($, true), '"' ), _quoted_pattern: $ => repeat1(choice( /[^\n/]/, choice($.ansi_c_escape, $.escaped_char), alias('\\', $.redundant_escape), )), _pattern_char: _ => /[^\s/?*]/, escaped_char: _ => /\\[\\\[\]!?*]/, ansi_c_escape: $ => prec.right( 1, choice( $._special_char, $._char_code ) ), _special_char: _ => /\\[abeEfnrtv\\'"?]/, _char_code: $ => choice( $._octal_code, $._hex_code, $._unicode_code, $._control_code ), _octal_code: _ => /\\\d{1,3}/, _hex_code: _ => /\\x[0-9A-Fa-f]{2}/, _unicode_code: _ => choice( /\\u[0-9A-Fa-f]{4}/, /\\U[0-9A-Fa-f]{8}/ ), _control_code: _ => token(choice( /\\c[\x00-\x5B\x5D-\x7F]/, /\\c\\\\/ )), range_notation: $ => prec.left(seq( '[', optional(alias( token(choice('!', '^')), $.range_negation )), repeat1(choice( $.class_range, $.character_class, $._class_char, $.ansi_c_escape, '-' )), ']' )), class_range: $ => prec.right( 2, seq( choice($._class_char, $._char_code), '-', choice($._class_char, $._char_code) ) ), _class_char: _ => token(choice( /[^-\\\]\n]/, /\\[-\\\[\]!^]/ )), character_class: _ => token(seq( '[:', choice(...POSIX_CLASSES), ':]' )), wildcard: _ => token(choice('?', '*', '**')), dir_sep: _ => '/', attribute: $ => choice( seq( choice($.attr_name, $.builtin_attr), $._attr_value ), $._prefixed_attr ), _prefixed_attr: $ => seq( optional(choice( alias('!', $.attr_reset), alias('-', $.attr_unset), )), choice($.attr_name, $.builtin_attr), prec(-1, optional( alias($._attr_value, $.ignored_value) )) ), _attr_value: $ => seq( alias('=', $.attr_set), choice( prec(2, alias( token(choice('true', 'false')), $.boolean_value )), prec(1, alias(/\S+/, $.string_value)) ) ), attr_name: _ => /[A-Za-z0-9_.][-A-Za-z0-9_.]*/, builtin_attr: _ => prec(1, choice(...BUILTIN_ATTRIBUTES)), macro_def: $ => seq( alias('[attr]', $.macro_tag), field('macro_name', $.attr_name), repeat1(seq($._space, $.attribute)), optional($._space) ), comment: _ => seq('#', repeat(/[^\n]/)), _space: _ => prec(-1, /[ \t]+/), _eol: _ => /\r?\n/, _eof: _ => '\0' } });