mirror of https://github.com/Wilfred/difftastic/
427 lines
8.6 KiB
JavaScript
427 lines
8.6 KiB
JavaScript
/**
|
|
* @file CSS grammar for tree-sitter
|
|
* @author Max Brunsfeld <maxbrunsfeld@gmail.com>
|
|
* @author Amaan Qureshi <amaanq12@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: 'css',
|
|
|
|
extras: $ => [
|
|
/\s/,
|
|
$.comment,
|
|
$.js_comment,
|
|
],
|
|
|
|
externals: $ => [
|
|
$._descendant_operator,
|
|
$._pseudo_class_selector_colon,
|
|
$.__error_recovery,
|
|
],
|
|
|
|
inline: $ => [
|
|
$._top_level_item,
|
|
$._block_item,
|
|
],
|
|
|
|
rules: {
|
|
stylesheet: $ => repeat($._top_level_item),
|
|
|
|
_top_level_item: $ => choice(
|
|
$.declaration,
|
|
$.rule_set,
|
|
$.import_statement,
|
|
$.media_statement,
|
|
$.charset_statement,
|
|
$.namespace_statement,
|
|
$.keyframes_statement,
|
|
$.supports_statement,
|
|
$.at_rule,
|
|
),
|
|
|
|
// Statements
|
|
|
|
import_statement: $ => seq(
|
|
'@import',
|
|
$._value,
|
|
sep(',', $._query),
|
|
';',
|
|
),
|
|
|
|
media_statement: $ => seq(
|
|
'@media',
|
|
sep1(',', $._query),
|
|
$.block,
|
|
),
|
|
|
|
charset_statement: $ => seq(
|
|
'@charset',
|
|
$._value,
|
|
';',
|
|
),
|
|
|
|
namespace_statement: $ => seq(
|
|
'@namespace',
|
|
optional(alias($.identifier, $.namespace_name)),
|
|
choice($.string_value, $.call_expression),
|
|
';',
|
|
),
|
|
|
|
keyframes_statement: $ => seq(
|
|
choice(
|
|
'@keyframes',
|
|
alias(/@[-a-z]+keyframes/, $.at_keyword),
|
|
),
|
|
alias($.identifier, $.keyframes_name),
|
|
$.keyframe_block_list,
|
|
),
|
|
|
|
keyframe_block_list: $ => seq(
|
|
'{',
|
|
repeat($.keyframe_block),
|
|
'}',
|
|
),
|
|
|
|
keyframe_block: $ => seq(
|
|
choice($.from, $.to, $.integer_value),
|
|
$.block,
|
|
),
|
|
|
|
from: _ => 'from',
|
|
to: _ => 'to',
|
|
|
|
supports_statement: $ => seq(
|
|
'@supports',
|
|
$._query,
|
|
$.block,
|
|
),
|
|
|
|
postcss_statement: $ => prec(-1, seq(
|
|
$.at_keyword,
|
|
repeat($._value),
|
|
';',
|
|
)),
|
|
|
|
at_rule: $ => seq(
|
|
$.at_keyword,
|
|
sep(',', $._query),
|
|
choice(';', $.block),
|
|
),
|
|
|
|
// Rule sets
|
|
|
|
rule_set: $ => seq(
|
|
$.selectors,
|
|
$.block,
|
|
),
|
|
|
|
selectors: $ => sep1(',', $._selector),
|
|
|
|
block: $ => seq(
|
|
'{',
|
|
repeat($._block_item),
|
|
optional(alias($.last_declaration, $.declaration)),
|
|
'}',
|
|
),
|
|
|
|
_block_item: $ => choice(
|
|
$.declaration,
|
|
$.rule_set,
|
|
$.import_statement,
|
|
$.media_statement,
|
|
$.charset_statement,
|
|
$.namespace_statement,
|
|
$.keyframes_statement,
|
|
$.supports_statement,
|
|
$.postcss_statement,
|
|
$.at_rule,
|
|
),
|
|
|
|
// Selectors
|
|
|
|
_selector: $ => choice(
|
|
$.universal_selector,
|
|
alias($.identifier, $.tag_name),
|
|
$.class_selector,
|
|
$.nesting_selector,
|
|
$.pseudo_class_selector,
|
|
$.pseudo_element_selector,
|
|
$.id_selector,
|
|
$.attribute_selector,
|
|
$.string_value,
|
|
$.child_selector,
|
|
$.descendant_selector,
|
|
$.sibling_selector,
|
|
$.adjacent_sibling_selector,
|
|
$.namespace_selector,
|
|
),
|
|
|
|
nesting_selector: _ => '&',
|
|
|
|
universal_selector: _ => '*',
|
|
|
|
class_selector: $ => prec(1, seq(
|
|
optional($._selector),
|
|
'.',
|
|
alias($.identifier, $.class_name),
|
|
)),
|
|
|
|
pseudo_class_selector: $ => seq(
|
|
optional($._selector),
|
|
alias($._pseudo_class_selector_colon, ':'),
|
|
alias($.identifier, $.class_name),
|
|
optional(alias($.pseudo_class_arguments, $.arguments)),
|
|
),
|
|
|
|
pseudo_element_selector: $ => seq(
|
|
optional($._selector),
|
|
'::',
|
|
alias($.identifier, $.tag_name),
|
|
optional(alias($.pseudo_element_arguments, $.arguments)),
|
|
),
|
|
|
|
id_selector: $ => seq(
|
|
optional($._selector),
|
|
'#',
|
|
alias($.identifier, $.id_name),
|
|
),
|
|
|
|
attribute_selector: $ => seq(
|
|
optional($._selector),
|
|
'[',
|
|
alias(choice($.identifier, $.namespace_selector), $.attribute_name),
|
|
optional(seq(
|
|
choice('=', '~=', '^=', '|=', '*=', '$='),
|
|
$._value,
|
|
)),
|
|
']',
|
|
),
|
|
|
|
child_selector: $ => prec.left(seq($._selector, '>', $._selector)),
|
|
|
|
descendant_selector: $ => prec.left(seq($._selector, $._descendant_operator, $._selector)),
|
|
|
|
sibling_selector: $ => prec.left(seq($._selector, '~', $._selector)),
|
|
|
|
adjacent_sibling_selector: $ => prec.left(seq($._selector, '+', $._selector)),
|
|
|
|
namespace_selector: $ => prec.left(seq($._selector, '|', $._selector)),
|
|
|
|
pseudo_class_arguments: $ => seq(
|
|
token.immediate('('),
|
|
sep(',', choice($._selector, repeat1($._value))),
|
|
')',
|
|
),
|
|
|
|
pseudo_element_arguments: $ => seq(
|
|
token.immediate('('),
|
|
sep(',', choice($._selector, repeat1($._value))),
|
|
')',
|
|
),
|
|
|
|
// Declarations
|
|
|
|
declaration: $ => seq(
|
|
alias($.identifier, $.property_name),
|
|
':',
|
|
$._value,
|
|
repeat(seq(
|
|
optional(','),
|
|
$._value,
|
|
)),
|
|
optional($.important),
|
|
';',
|
|
),
|
|
|
|
last_declaration: $ => prec(1, seq(
|
|
alias($.identifier, $.property_name),
|
|
':',
|
|
$._value,
|
|
repeat(seq(
|
|
optional(','),
|
|
$._value,
|
|
)),
|
|
optional($.important),
|
|
)),
|
|
|
|
important: _ => '!important',
|
|
|
|
// Media queries
|
|
|
|
_query: $ => choice(
|
|
alias($.identifier, $.keyword_query),
|
|
$.feature_query,
|
|
$.binary_query,
|
|
$.unary_query,
|
|
$.selector_query,
|
|
$.parenthesized_query,
|
|
),
|
|
|
|
feature_query: $ => seq(
|
|
'(',
|
|
alias($.identifier, $.feature_name),
|
|
':',
|
|
repeat1($._value),
|
|
')',
|
|
),
|
|
|
|
parenthesized_query: $ => seq(
|
|
'(',
|
|
$._query,
|
|
')',
|
|
),
|
|
|
|
binary_query: $ => prec.left(seq(
|
|
$._query,
|
|
choice('and', 'or'),
|
|
$._query,
|
|
)),
|
|
|
|
unary_query: $ => prec(1, seq(
|
|
choice('not', 'only'),
|
|
$._query,
|
|
)),
|
|
|
|
selector_query: $ => seq(
|
|
'selector',
|
|
'(',
|
|
$._selector,
|
|
')',
|
|
),
|
|
|
|
// Property Values
|
|
|
|
_value: $ => prec(-1, choice(
|
|
alias($.identifier, $.plain_value),
|
|
$.plain_value,
|
|
$.color_value,
|
|
$.integer_value,
|
|
$.float_value,
|
|
$.string_value,
|
|
$.grid_value,
|
|
$.binary_expression,
|
|
$.parenthesized_value,
|
|
$.call_expression,
|
|
$.important,
|
|
)),
|
|
|
|
parenthesized_value: $ => seq(
|
|
'(',
|
|
$._value,
|
|
')',
|
|
),
|
|
|
|
color_value: _ => seq('#', token.immediate(/[0-9a-fA-F]{3,8}/)),
|
|
|
|
string_value: _ => choice(
|
|
seq('\'', /([^'\n]|\\(.|\n))*/, '\''),
|
|
seq('"', /([^"\n]|\\(.|\n))*/, '"'),
|
|
),
|
|
|
|
integer_value: $ => seq(
|
|
token(seq(
|
|
optional(choice('+', '-')),
|
|
/\d+/,
|
|
)),
|
|
optional($.unit),
|
|
),
|
|
|
|
float_value: $ => seq(
|
|
token(seq(
|
|
optional(choice('+', '-')),
|
|
/\d*/,
|
|
choice(
|
|
seq('.', /\d+/),
|
|
seq(/[eE]/, optional('-'), /\d+/),
|
|
seq('.', /\d+/, /[eE]/, optional('-'), /\d+/),
|
|
),
|
|
)),
|
|
optional($.unit),
|
|
),
|
|
|
|
unit: _ => token.immediate(/[a-zA-Z%]+/),
|
|
|
|
grid_value: $ => seq(
|
|
'[',
|
|
sep1(',', $._value),
|
|
']',
|
|
),
|
|
|
|
call_expression: $ => seq(
|
|
alias($.identifier, $.function_name),
|
|
$.arguments,
|
|
),
|
|
|
|
binary_expression: $ => prec.left(seq(
|
|
$._value,
|
|
choice('+', '-', '*', '/'),
|
|
$._value,
|
|
)),
|
|
|
|
arguments: $ => seq(
|
|
token.immediate('('),
|
|
sep(choice(',', ';'), repeat1($._value)),
|
|
')',
|
|
),
|
|
|
|
identifier: _ => /(--|-?[a-zA-Z_])[a-zA-Z0-9-_]*/,
|
|
|
|
at_keyword: _ => /@[a-zA-Z-_]+/,
|
|
|
|
js_comment: _ => token(prec(-1, seq('//', /.*/))),
|
|
|
|
comment: _ => token(seq(
|
|
'/*',
|
|
/[^*]*\*+([^/*][^*]*\*+)*/,
|
|
'/',
|
|
)),
|
|
|
|
plain_value: _ => token(seq(
|
|
repeat(choice(
|
|
/[-_]/,
|
|
/\/[^\*\s,;!{}()\[\]]/, // Slash not followed by a '*' (which would be a comment)
|
|
)),
|
|
/[a-zA-Z]/,
|
|
repeat(choice(
|
|
/[^/\s,;!{}()\[\]]/, // Not a slash, not a delimiter character
|
|
/\/[^\*\s,;!{}()\[\]]/, // Slash not followed by a '*' (which would be a comment)
|
|
)),
|
|
)),
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Creates a rule to optionally match one or more of the rules separated by `separator`
|
|
*
|
|
* @param {RuleOrLiteral} separator
|
|
*
|
|
* @param {RuleOrLiteral} rule
|
|
*
|
|
* @return {ChoiceRule}
|
|
*
|
|
*/
|
|
function sep(separator, rule) {
|
|
return optional(sep1(separator, rule));
|
|
}
|
|
|
|
/**
|
|
* Creates a rule to match one or more of the rules separated by `separator`
|
|
*
|
|
* @param {RuleOrLiteral} separator
|
|
*
|
|
* @param {RuleOrLiteral} rule
|
|
*
|
|
* @return {SeqRule}
|
|
*
|
|
*/
|
|
function sep1(separator, rule) {
|
|
return seq(rule, repeat(seq(separator, rule)));
|
|
}
|