biome/xtask/codegen/css.ungram
Denis Bezrukov 0059cd9b5e
Some checks are pending
Benchmarks / Bench (push) Waiting to run
CI on main / Format Rust Files (push) Waiting to run
CI on main / Lint Rust Files (push) Waiting to run
CI on main / Check Dependencies (push) Waiting to run
CI on main / Test (push) Waiting to run
CI on main / Test262 Coverage (push) Waiting to run
Release / Release (push) Waiting to run
Release / version (push) Blocked by required conditions
Release / Package darwin-arm64 (push) Blocked by required conditions
Release / Package darwin-x64 (push) Blocked by required conditions
Release / Package linux-arm64-musl (push) Blocked by required conditions
Release / Package linux-x64-musl (push) Blocked by required conditions
Release / Package win32-arm64 (push) Blocked by required conditions
Release / Package win32-x64 (push) Blocked by required conditions
Release / Package linux-arm64 (push) Blocked by required conditions
Release / Package linux-x64 (push) Blocked by required conditions
Release / Build WASM (push) Blocked by required conditions
Release / Package JavaScript APIs (push) Blocked by required conditions
Release / Publish CLI (push) Blocked by required conditions
Release / Publish JS API (push) Blocked by required conditions
Repository dispatch on main / Build @biomejs/wasm-web (push) Waiting to run
Repository dispatch on main / Repository dispatch (push) Blocked by required conditions
refactor(css_parser): flatten AnyCssDeclarationWithSemicolon (#6912) (#6879)
2025-07-14 14:36:45 +03:00

1840 lines
48 KiB
Text

// CSS Un-Grammar.
//
// This grammar specifies the structure of Rust's concrete syntax tree.
// It does not specify parsing rules (ambiguities, precedence, etc are out of scope).
// Tokens are processed -- contextual keywords are recognised, compound operators glued.
//
// Legend:
//
// // -- comment
// Name = -- non-terminal definition
// 'ident' -- token (terminal)
// A B -- sequence
// A | B -- alternation
// A* -- zero or more repetition
// (A (',' A)* ','?) -- repetition of node A separated by ',' and allowing a trailing comma
// (A (',' A)*) -- repetition of node A separated by ',' without a trailing comma
// A? -- zero or one repetition
// (A) -- same as A
// label:A -- suggested name for field of AST node
// NOTES
//
// - SyntaxNode, SyntaxToken and SyntaxElement will be stripped from the codegen
// - Bogus nodes are special nodes used to keep track of broken code; they are
// not part of the grammar but they will appear inside the green tree
///////////////
// BOGUS NODES
///////////////
// SyntaxElement is a generic data structure that is meant to track nodes and tokens
// in cases where we care about both types
//
// As Bogus* node will need to yield both tokens and nodes without discrimination,
// and their children will need to yield nodes and tokens as well.
// For this reason, SyntaxElement = SyntaxElement
SyntaxElement = SyntaxElement
CssBogus = SyntaxElement*
CssBogusSelector = SyntaxElement*
CssBogusSubSelector = SyntaxElement*
CssBogusPseudoClass = SyntaxElement*
CssBogusPageSelectorPseudo = SyntaxElement*
CssBogusPseudoElement = SyntaxElement*
CssBogusAtRule = SyntaxElement*
CssBogusLayer = SyntaxElement*
CssBogusBlock = SyntaxElement*
CssBogusScopeRange = SyntaxElement*
CssBogusFontFeatureValuesItem = SyntaxElement*
CssBogusKeyframesItem = SyntaxElement*
CssBogusRule = SyntaxElement*
CssBogusParameter = SyntaxElement*
CssBogusDeclarationItem = SyntaxElement*
CssBogusMediaQuery = SyntaxElement*
CssBogusProperty = SyntaxElement*
CssBogusUrlModifier = SyntaxElement*
CssBogusPropertyValue = SyntaxElement*
CssBogusDocumentMatcher = SyntaxElement*
CssBogusFontFamilyName = SyntaxElement*
CssBogusCustomIdentifier = SyntaxElement*
CssBogusKeyframesName = SyntaxElement*
CssBogusUnicodeRangeValue = SyntaxElement*
CssBogusSupportsCondition = SyntaxElement*
CssRoot =
bom: 'UNICODE_BOM'?
rules: CssRuleList
eof: 'EOF'
CssRuleList = AnyCssRule*
AnyCssRule =
CssQualifiedRule
| CssNestedQualifiedRule
| CssAtRule
| CssBogusRule
// .header { color: red }
// ^^^^^^^^^^^^^^^^^^^^^
CssQualifiedRule =
prelude: CssSelectorList
block: AnyCssDeclarationOrRuleBlock
CssNestedQualifiedRule =
prelude: CssRelativeSelectorList
block: AnyCssDeclarationOrRuleBlock
/////////////
/// SELECTORS
/////////////
// .header, .app {}
// ^^^^^^^^^^^^^
CssSelectorList = (AnyCssSelector (',' AnyCssSelector)*)
AnyCssSelector =
CssComplexSelector
| CssCompoundSelector
| CssBogusSelector
| CssMetavariable
// div a {}
// ^^^^^
// div > a {}
// ^^^^^^^
CssComplexSelector =
left: AnyCssSelector
combinator: ('>' | '+' | '~' | '||' | 'css_space_literal')
right: AnyCssSelector
// .class {}
// ^^^^^^^
// a.class {}
// ^^^^^^^
CssCompoundSelector =
nesting_selectors: CssNestedSelectorList
simple_selector: AnyCssSimpleSelector?
sub_selectors: CssSubSelectorList
// .class {
// &&&&&&&.class {}
// ^^^^^^^
// }
CssNestedSelectorList = CssNestedSelector*
CssNestedSelector =
'&'
CssSubSelectorList = AnyCssSubSelector*
AnyCssSimpleSelector =
CssUniversalSelector
| CssTypeSelector
AnyCssSubSelector =
CssIdSelector
| CssClassSelector
| CssAttributeSelector
| CssPseudoClassSelector
| CssPseudoElementSelector
| CssBogusSubSelector
// * {}
// ^
// *|* {}
// ^^^
// |* {}
// ^^
CssUniversalSelector =
namespace: CssNamespace?
'*'
// foo|h1 {}
// ^^^^^^
// |h1 {}
// ^^^
// div {}
// ^^^
CssTypeSelector =
namespace: CssNamespace?
ident: CssIdentifier
CssNamespace =
prefix: AnyCssNamespacePrefix?
'|'
AnyCssNamespacePrefix =
CssNamedNamespacePrefix
| CssUniversalNamespacePrefix
CssNamedNamespacePrefix =
name: CssIdentifier
CssUniversalNamespacePrefix =
'*'
// #app {}
// ^^^^
CssIdSelector =
'#'
name: CssCustomIdentifier
// .app {}
// ^^^^
CssClassSelector =
'.'
name: CssCustomIdentifier
// [title = "title" i] {}
// ^^^^^^^^^^^^^^^^^^^
CssAttributeSelector =
'['
name: CssAttributeName
matcher: CssAttributeMatcher?
']'
CssAttributeName =
namespace: CssNamespace?
name: CssIdentifier
// [title = "title" i] {}
// ^^^^^^^^^^^
CssAttributeMatcher =
operator: ('~=' | '|=' | '^=' | '$=' | '*=' | '=')
value: CssAttributeMatcherValue
modifier: ('i' | 's')?
CssAttributeMatcherValue =
name: AnyCssAttributeMatcherValue
AnyCssAttributeMatcherValue = CssString | CssIdentifier
// :nth-type(2) {}
// ^^^^^^^^^^^^
CssPseudoClassSelector =
':'
class: AnyCssPseudoClass
AnyCssPseudoClass =
CssPseudoClassIdentifier
| CssPseudoClassFunctionIdentifier
| CssPseudoClassFunctionSelector
| CssPseudoClassFunctionSelectorList
| CssPseudoClassFunctionCompoundSelector
| CssPseudoClassFunctionCompoundSelectorList
| CssPseudoClassFunctionRelativeSelectorList
| CssPseudoClassFunctionValueList
| CssPseudoClassFunctionNth
| CssBogusPseudoClass
CssPseudoClassIdentifier =
name: CssIdentifier
// :first-of-type {}
// ^^^^^^^^^^^^^
CssPseudoClassFunctionIdentifier =
name: 'dir'
'('
ident: CssIdentifier
')'
// :global(.class div) {}
// ^^^^^^^^^^^^^^^^^^
CssPseudoClassFunctionSelector =
name: ('global' | 'local')
'('
selector: AnyCssSelector
')'
// :not(div + #id:hover) {}
// ^^^^^^^^^^^^^^^^^^^^
CssPseudoClassFunctionSelectorList =
name: ('matches' | 'not' | 'is' | 'where')
'('
selectors: CssSelectorList
')'
// :-webkit-any(i,p,:link,span:focus) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssPseudoClassFunctionCompoundSelectorList =
name: ('any' | 'past' | 'current' | 'future')
'('
compound_selectors: CssCompoundSelectorList
')'
// :-webkit-any(i,p,:link,span:focus) {}
// ^^^^^^^^^^^^^^^^^^^^
CssCompoundSelectorList = (AnyCssCompoundSelector (',' AnyCssCompoundSelector)*)
// :host(span:focus) {}
// ^^^^^^^^^^^^^^^^
CssPseudoClassFunctionCompoundSelector =
name: ('host' | 'host-context')
'('
selector: AnyCssCompoundSelector
')'
AnyCssCompoundSelector =
CssCompoundSelector
| CssBogusSelector
// :has(> img, +dt) {}
// ^^^^^^^^^^^^^^^^
CssPseudoClassFunctionRelativeSelectorList =
name: 'has'
'('
relative_selectors: CssRelativeSelectorList
')'
// :has(> img, +dt) {}
// ^^^^^^^^^^
CssRelativeSelectorList = (AnyCssRelativeSelector (',' AnyCssRelativeSelector)*)
AnyCssRelativeSelector =
CssRelativeSelector
| CssBogusSelector
// :has(> img, +dt) {}
// ^^^^^ ^^^
CssRelativeSelector =
combinator: ('>' | '+' | '~' | '||')?
selector: AnyCssSelector
// :lang(de, fr) {}
// ^^^^^^^^^^^^^
CssPseudoClassFunctionValueList =
name: 'lang'
'('
values: CssPseudoValueList
')'
// :lang(de, fr) {}
// ^^^^^^^^
CssPseudoValueList = (AnyCssPseudoValue (',' AnyCssPseudoValue)*)
// :lang(de, fr) {}
// ^^ ^^
AnyCssPseudoValue =
CssIdentifier
| CssString
// :nth-child(2n+1 of li, .test) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssPseudoClassFunctionNth =
name: ('nth-child' | 'nth-last-child' | 'nth-of-type' | 'nth-last-of-type' | 'nth-col' | 'nth-last-col')
'('
selector: AnyCssPseudoClassNthSelector
')'
AnyCssPseudoClassNthSelector =
CssPseudoClassNthSelector
| CssBogusSelector
// :nth-child(2n+1 of li, .test) {}
// ^^^^^^^^^^^^^^^^^
CssPseudoClassNthSelector =
nth: AnyCssPseudoClassNth
of_selector: CssPseudoClassOfNthSelector?
// :nth-child(odd) {}
// ^^^
// :nth-child(2n) {}
// ^^^
// :nth-child(2n+1) {}
// ^^^^
AnyCssPseudoClassNth =
CssPseudoClassNthNumber
| CssPseudoClassNthIdentifier
| CssPseudoClassNth
// :nth-child(+2) {}
// ^^
// :nth-child(2) {}
// ^
CssPseudoClassNthNumber =
sign: ('+' | '-')?
value: CssNumber
// :nth-child(odd) {}
// ^^^
CssPseudoClassNthIdentifier =
value: ('even' | 'odd')
// :nth-child(n+8) {}
// ^^^
// :nth-child(2n+1) {}
// ^^^^
// :nth-child(+2n+1) {}
// ^^^^^
CssPseudoClassNth =
sign: ('+' | '-')?
value: CssNumber?
symbol: 'n'
offset: CssNthOffset?
// :nth-child(2n+1) {}
// ^^
CssNthOffset =
sign: ('+' | '-')
value: CssNumber
// :nth-child(2n+1 of li, .test) {}
// ^^^^^^^^^^^^
CssPseudoClassOfNthSelector =
'of'
selectors: CssSelectorList
// a::after {}
// ^^^^^^^^
// video::cue(b) {}
// ^^^^^^^^
// ::highlight(sample) {}
// ^^^^^^^^^^^^^^^^^^^
CssPseudoElementSelector =
'::'
element: AnyCssPseudoElement
AnyCssPseudoElement =
CssPseudoElementIdentifier
| CssPseudoElementFunctionSelector
| CssPseudoElementFunctionCustomIdentifier
| CssPseudoElementFunction
| CssBogusPseudoElement
// a::after {}
// ^^^^^
CssPseudoElementIdentifier =
name: CssIdentifier
// video::cue-region(#scroll > .div) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
CssPseudoElementFunctionSelector =
name: CssIdentifier
'('
selector: AnyCssSelector
')'
// ::highlight(sample) {}
// ^^^^^^^^^^^^^^^^^
CssPseudoElementFunctionCustomIdentifier =
name: CssIdentifier
'('
ident: CssCustomIdentifier
')'
// ::part(active button) {}
// ^^^^^^^^^^^^^^^^^^^
CssPseudoElementFunction =
name: CssIdentifier
'('
items: CssPseudoElementFunctionParameterList
')'
CssPseudoElementFunctionParameterList = CssIdentifier*
/////////////
// COMMON BLOCKS
// https://drafts.csswg.org/css-syntax-3/#typedef-declaration-rule-list
////////////
AnyCssDeclarationOrRuleBlock =
CssDeclarationOrRuleBlock
| CssBogusBlock
CssDeclarationOrRuleBlock =
'{'
items: CssDeclarationOrRuleList
'}'
CssDeclarationOrRuleList = AnyCssDeclarationOrRule*
AnyCssDeclarationOrRule =
AnyCssRule
| CssDeclarationWithSemicolon
| CssEmptyDeclaration
| CssBogus
| CssMetavariable
// @page :left { background: red; @media (500px <= width <= 500px) { } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AnyCssDeclarationOrAtRuleBlock =
CssDeclarationOrAtRuleBlock
| CssBogusBlock
CssDeclarationOrAtRuleBlock =
'{'
items: CssDeclarationOrAtRuleList
'}'
CssDeclarationOrAtRuleList = AnyCssDeclarationOrAtRule*
AnyCssDeclarationOrAtRule =
CssDeclarationWithSemicolon
| CssEmptyDeclaration
| CssAtRule
// When nested in this way, the contents of a nested group rule's block are parsed as <block-contents> rather than <rule-list>:
// https://drafts.csswg.org/css-nesting-1/#conditionals
AnyCssConditionalBlock =
CssDeclarationOrRuleBlock
| CssRuleBlock
| CssBogusBlock
// @page :left { background: red; @media (500px <= width <= 500px) { } }
// ^^^^^^^^^^^^^^^^
// NOTE: ';' is optional for the last declaration in a block. For declarations not at the end, the parser will raise an error if ';' is missing.
CssDeclarationWithSemicolon =
declaration: CssDeclaration
';'?
CssEmptyDeclaration =
';'
AnyCssDeclarationBlock =
CssDeclarationBlock
| CssBogusBlock
CssDeclarationBlock =
'{'
declarations: CssDeclarationList
'}'
CssDeclarationList = AnyCssDeclaration*
AnyCssDeclaration =
CssDeclarationWithSemicolon
| CssEmptyDeclaration
AnyCssRuleBlock =
CssRuleBlock
| CssBogusBlock
CssRuleBlock =
'{'
rules: CssRuleList
'}'
CssDeclaration =
property: AnyCssProperty
important: CssDeclarationImportant?
/////////////
// PROPERTIES
////////////
AnyCssProperty =
CssGenericProperty
| CssComposesProperty
| CssBogusProperty
CssGenericProperty =
name: AnyCssDeclarationName
':'
value: CssGenericComponentValueList
// This grammar is specific to CSS Modules and defines how composes classes.
// https://github.com/css-modules/css-modules/blob/master/docs/composition.md
// .otherClassName {
// composes: globalClassName from global;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// }
// .otherClassName {
// composes: className;
// ^^^^^^^^^^^^^^^^^^^
// }
// .otherClassName {
// composes: className from './style.css';
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// }
CssComposesProperty =
name: CssIdentifier
':'
value: CssComposesPropertyValue
CssComposesPropertyValue =
classes: CssComposesClassList
specifier: CssComposesImportSpecifier?
CssComposesClassList = CssCustomIdentifier*
// composes: className from './style.css';
// ^^^^^^^^^^^^^^^^^^
// composes: className from global;
// ^^^^^^^^^^^
CssComposesImportSpecifier =
'from'
source: AnyCssComposesImportSource
// composes: className from './style.css';
// ^^^^^^^^^^^^^
// composes: className from global;
// ^^^^^^
AnyCssComposesImportSource =
CssString | CssIdentifier
// div {
// --bs-btn-focus-shadow-rgb: 33, 37, 41;
// ^^^^^^^^^^
// background: transparent center/1em auto no-repeat;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// }
CssGenericComponentValueList = AnyCssGenericComponentValue*
AnyCssGenericComponentValue =
AnyCssValue
| CssGenericDelimiter
// div {
// --bs-btn-focus-shadow-rgb: 33, 37, 41;
// ^ ^
// background: transparent center/1em auto no-repeat;
// ^
// }
CssGenericDelimiter =
value: (',' | '/')
CssComponentValueList = AnyCssValue*
AnyCssDeclarationName = CssIdentifier | CssDashedIdentifier
CssDeclarationImportant =
'!'
'important'
/////////////
// AT RULES
////////////
CssAtRule =
'@'
rule: AnyCssAtRule
AnyCssAtRule =
CssCharsetAtRule
| CssColorProfileAtRule
| CssCounterStyleAtRule
| CssContainerAtRule
| CssFontFaceAtRule
| CssFontFeatureValuesAtRule
| CssFontPaletteValuesAtRule
| CssKeyframesAtRule
| CssMediaAtRule
| CssPageAtRule
| CssLayerAtRule
| CssSupportsAtRule
| CssScopeAtRule
| CssImportAtRule
| CssNamespaceAtRule
| CssStartingStyleAtRule
| CssDocumentAtRule
| CssPropertyAtRule
| CssValueAtRule
| CssPositionTryAtRule
| CssViewTransitionAtRule
| CssUnknownBlockAtRule
| CssUnknownValueAtRule
| CssBogusAtRule
// @charset "UTF-8";
// ^^^^^^^^^^^^^^^^^
CssCharsetAtRule =
'charset'
encoding: CssString
';'
// @color-profile --fogra39 {}
// ^^^^^^^^^^^^^^^^^^^^^^^^
// @color-profile device-cmyk {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
// TODO: `name` should be `<dashed-ident> | device-cmyk` instead
CssColorProfileAtRule =
'color-profile'
name: CssCustomIdentifier
block: AnyCssDeclarationBlock
CssCounterStyleAtRule =
'counter-style'
name: CssCustomIdentifier
block: AnyCssDeclarationBlock
CssPropertyAtRule =
'property'
name: CssDashedIdentifier
block: AnyCssDeclarationBlock
// @font-face {}
// ^^^^^^^^^^^^^
CssFontFaceAtRule =
'font-face'
block: AnyCssDeclarationBlock
// https://drafts.csswg.org/css-fonts/#font-feature-values
// @font-feature-values = @font-feature-values <family-name># { <declaration-rule-list> }
// font-feature-value-type = <@stylistic> | <@historical-forms> | <@styleset> | <@character-variant>
// | <@swash> | <@ornaments> | <@annotation>
// @stylistic = @stylistic { <declaration-list> }
// @historical-forms = @historical-forms { <declaration-list> }
// @styleset = @styleset { <declaration-list> }
// @character-variant = @character-variant { <declaration-list> }
// @swash = @swash { <declaration-list> }
// @ornaments = @ornaments { <declaration-list> }
// @annotation = @annotation { <declaration-list> }
// @font-feature-values Font, Second { @styleset { nice-style: 12; } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssFontFeatureValuesAtRule =
'font-feature-values'
names: CssFontFamilyNameList
block: AnyCssFontFeatureValuesBlock
// @font-feature-values Font Second, Gothic, "Otaru Kisa" { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssFontFamilyNameList = (AnyCssFontFamilyName (',' AnyCssFontFamilyName)*)
AnyCssFontFamilyName =
CssFontFamilyName
| CssString
| CssBogusFontFamilyName
CssFontFamilyName =
names: CssCustomIdentifierList
// @font-feature-values Font Second Gothic { }
// ^^^^^^^^^^^^^^^^^^
CssCustomIdentifierList = AnyCssCustomIdentifier*
AnyCssFontFeatureValuesBlock =
CssFontFeatureValuesBlock
| CssBogusBlock
// @font-feature-values Font One { @styleset { nice-style: 12; } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssFontFeatureValuesBlock =
'{'
items: CssFontFeatureValuesItemList
'}'
CssFontFeatureValuesItemList = AnyCssFontFeatureValuesItem*
AnyCssFontFeatureValuesItem =
CssFontFeatureValuesItem
| CssBogusFontFeatureValuesItem
// @font-feature-values Font One { @styleset { nice-style: 12; } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssFontFeatureValuesItem =
'@'
name: (
'stylistic'
| 'historical-forms'
| 'styleset'
| 'character-variant'
| 'swash'
| 'ornaments'
| 'annotation'
)
block: AnyCssDeclarationBlock
// @font-palette-values --ident {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssFontPaletteValuesAtRule =
'font-palette-values'
name: CssDashedIdentifier
block: AnyCssDeclarationBlock
// https://drafts.csswg.org/css-contain-3/#container-rule
// <container-condition> = [ <container-name> ]? <container-query>
// <container-name> = <custom-ident>
// <container-query> = not <query-in-parens>
// | <query-in-parens> [ [ and <query-in-parens> ]* | [ or <query-in-parens> ]* ]
//
// <query-in-parens> = ( <container-query> )
// | ( <size-feature> )
// | style( <style-query> )
// | <general-enclosed>
// <general-enclosed> exists to allow for future expansion of the grammar in a reasonably compatible way.
//
// <style-query> = not <style-in-parens>
// | <style-in-parens> [ [ and <style-in-parens> ]* | [ or <style-in-parens> ]* ]
// | <style-feature>
//
// <style-in-parens> = ( <style-query> )
// | ( <style-feature> )
// | <general-enclosed>
// <general-enclosed> exists to allow for future expansion of the grammar in a reasonably compatible way.
// <container-condition> = [ <container-name> ]? <container-query>
// @container name (width <= 500px) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerAtRule =
'container'
name: CssCustomIdentifier?
query: AnyCssContainerQuery
block: AnyCssConditionalBlock
// <container-query>
AnyCssContainerQuery =
CssContainerNotQuery
| CssContainerOrQuery
| CssContainerAndQuery
| AnyCssContainerQueryInParens
// @container name not (width <= 500px) { }
// ^^^^^^^^^^^^^^^^^^^^
CssContainerNotQuery =
'not'
query: AnyCssContainerQueryInParens
// @container name (width <= 500px) and (width <= 500px) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerAndQuery =
left: AnyCssContainerQueryInParens
'and'
right: AnyCssContainerAndCombinableQuery
AnyCssContainerAndCombinableQuery =
CssContainerAndQuery
| AnyCssContainerQueryInParens
// @container name (width <= 500px) or (width <= 500px) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerOrQuery =
left: AnyCssContainerQueryInParens
'or'
right: AnyCssContainerOrCombinableQuery
AnyCssContainerOrCombinableQuery =
CssContainerOrQuery
| AnyCssContainerQueryInParens
// <query-in-parens>
AnyCssContainerQueryInParens =
CssContainerQueryInParens
| CssContainerSizeFeatureInParens
| CssContainerStyleQueryInParens
// ( <container-query> )
// @container name (width <= 500px) and ((width <= 500px) or (width <= 500px)) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerQueryInParens =
'('
query: AnyCssContainerQuery
')'
// ( <size-feature> )
// @container name (width <= 500px) and ((width <= 500px) or (width <= 500px)) { }
// ^^^^^^^^^^^^^^^
CssContainerSizeFeatureInParens =
'('
feature: AnyCssQueryFeature
')'
// style( <style-query> )
// @container style(--accent-color: blue) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerStyleQueryInParens =
'style'
'('
query: AnyCssContainerStyleQuery
')'
// <style-query>
AnyCssContainerStyleQuery =
CssContainerStyleNotQuery
| CssContainerStyleAndQuery
| CssContainerStyleOrQuery
| CssDeclaration
| CssContainerStyleInParens
// @container name style(not (--b: red)) { }
// ^^^^^^^^^^^^^^
CssContainerStyleNotQuery =
'not'
query: CssContainerStyleInParens
// @container name style((--a: blue) and (--b: red)) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerStyleAndQuery =
left: CssContainerStyleInParens
'and'
right: AnyCssContainerStyleAndCombinableQuery
AnyCssContainerStyleAndCombinableQuery =
CssContainerStyleAndQuery
| CssContainerStyleInParens
// @container name style((--a: blue) or (--b: red)) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerStyleOrQuery =
left: CssContainerStyleInParens
'or'
right: AnyCssContainerStyleOrCombinableQuery
AnyCssContainerStyleOrCombinableQuery =
CssContainerStyleOrQuery
| CssContainerStyleInParens
// <style-in-parens>
// @container name style((--a: blue) and ((--a: blue) or (--b: red))) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssContainerStyleInParens =
'('
query: AnyCssContainerStyleInParens
')'
// ( <style-query> )
// ( <style-feature> )
// @container name style((--a: blue) or (--b: red)) { }
// ^^^^^^^^^
AnyCssContainerStyleInParens =
AnyCssContainerStyleQuery
| CssDeclaration
// https://drafts.csswg.org/css-animations/#keyframes
// @keyframes = @keyframes <keyframes-name> { <qualified-rule-list> }
// <keyframes-name> = <custom-ident> | <string>
// | :<module-scope>(<keyframes-name>) | :<module-scope> <keyframes-name> // for CSS Modules
// <module-scope> = global | local
// <keyframe-block> = <keyframe-selector># { <declaration-list> }
// <keyframe-selector> = from | to | <percentage [0,100]>
// @keyframes "something" { from {} to {} }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssKeyframesAtRule =
'keyframes'
name: AnyCssKeyframesName
block: AnyCssKeyframesBlock
AnyCssKeyframesName =
CssKeyframesScopedName
| AnyCssKeyframesIdentifier
| CssBogusKeyframesName
AnyCssKeyframesBlock =
CssKeyframesBlock
| CssBogusBlock
// This grammar is specific to CSS Modules and defines how scoped keyframes are parsed.
// In CSS Modules, keyframes can be scoped locally or globally using the `:local` and `:global` pseudo-classes.
// @keyframes :local("test") {}
// ^^^^^^^^^^^^^^^
// @keyframes :global test {}
// ^^^^^^^^^^^^^
CssKeyframesScopedName =
':'
scope: AnyCssKeyframesScope
AnyCssKeyframesScope = CssKeyframesScopeFunction | CssKeyframesScopePrefix
// @keyframes :local("test") {}
// ^^^^^^^^^^^^^^^
CssKeyframesScopeFunction =
scope: ('global' | 'local')
'('
name: AnyCssKeyframesIdentifier
')'
// @keyframes :global test {}
// ^^^^^^^^^^^^^
CssKeyframesScopePrefix =
scope: ('global' | 'local')
name: AnyCssKeyframesIdentifier
// End of CSS Modules specific grammar
// @keyframes "something" { from {} to {} }
// ^^^^^^^^^^^
AnyCssKeyframesIdentifier = CssCustomIdentifier | CssString
// @keyframes "something" { from {} to {} }
// ^^^^^^^^^^^^^^^^^
CssKeyframesBlock =
'{'
items: CssKeyframesItemList
'}'
CssKeyframesItemList = AnyCssKeyframesItem*
AnyCssKeyframesItem =
CssKeyframesItem
| CssBogusKeyframesItem
// @keyframes "something" { 30%, 60% {} }
// ^^^^^^^^^^^
CssKeyframesItem =
selectors: CssKeyframesSelectorList
block: AnyCssDeclarationBlock
CssKeyframesSelectorList = (AnyCssKeyframesSelector (',' AnyCssKeyframesSelector)*)
AnyCssKeyframesSelector =
CssKeyframesIdentSelector
| CssKeyframesPercentageSelector
| CssBogusSelector
// @keyframes "something" { from {} to {} }
// ^^^^ ^^
CssKeyframesIdentSelector =
// The `from` keyword. Equivalent to 0%.
// The `to` keyword. Equivalent to 100%.
selector: ('from' | 'to')
// @keyframes "something" { 30%, 60% {} }
// ^^^ ^^^
CssKeyframesPercentageSelector =
selector: CssPercentage
// https://drafts.csswg.org/mediaqueries-5/#media-query
// <media-query> = <media-condition>
// | [ not | only ]? <media-type> [ and <media-condition-without-or> ]?
// <media-type> = <ident>
//
// <media-condition> = <media-not> | <media-in-parens> [ <media-and>* | <media-or>* ]
// <media-condition-without-or> = <media-not> | <media-in-parens> <media-and>*
// <media-not> = not <media-in-parens>
// <media-and> = and <media-in-parens>
// <media-or> = or <media-in-parens>
// <media-in-parens> = ( <media-condition> ) | <media-feature> | <general-enclosed>
//
// <media-feature> = ( [ <mf-plain> | <mf-boolean> | <mf-range> ] )
// <mf-plain> = <mf-name> : <mf-value>
// <mf-boolean> = <mf-name>
// <mf-range> = <mf-name> <mf-comparison> <mf-value>
// | <mf-value> <mf-comparison> <mf-name>
// | <mf-value> <mf-lt> <mf-name> <mf-lt> <mf-value>
// | <mf-value> <mf-gt> <mf-name> <mf-gt> <mf-value>
// <mf-name> = <ident>
// <mf-value> = <number> | <dimension> | <ident> | <ratio>
// <mf-lt> = '<' '='?
// <mf-gt> = '>' '='?
// <mf-eq> = '='
// <mf-comparison> = <mf-lt> | <mf-gt> | <mf-eq>
//
// <general-enclosed> branch must only be chosen if the input does not match either of the preceding branches.
// <general-enclosed> exists to allow for future expansion of the grammar in a reasonably compatible way.
// <general-enclosed> = [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ]
// @media screen, all, print {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssMediaAtRule =
'media'
queries: CssMediaQueryList
block: AnyCssConditionalBlock
// @media screen, (width > 500px), print {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssMediaQueryList = (AnyCssMediaQuery (',' AnyCssMediaQuery)*)
AnyCssMediaQuery =
CssMediaConditionQuery
| AnyCssMediaTypeQuery
| CssBogusMediaQuery
| CssMetavariable
// @media screen, (width > 500px), print {}
// ^^^^^^^^^^^^^^^
CssMediaConditionQuery =
condition: AnyCssMediaCondition
AnyCssMediaTypeQuery =
CssMediaAndTypeQuery
| CssMediaTypeQuery
// [ not | only ]? <media-type> [ and <media-condition-without-or> ]?
// @media not all and not (color) { }
// ^^^^^^^^^^^^^^^^^^^^^^^
// @media not all and (color) and (color) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssMediaAndTypeQuery =
left: CssMediaTypeQuery
'and'
right: AnyCssMediaTypeCondition
// @media not all and not (color) { }
// ^^^^^^^
CssMediaTypeQuery =
modifier: ('only' | 'not')?
type: CssMediaType
// @media not all and not (color) { }
// ^^^
CssMediaType =
value: CssIdentifier
AnyCssMediaTypeCondition =
CssMediaNotCondition
| CssMediaAndCondition
| AnyCssMediaInParens
// <media-condition> = <media-not> | <media-in-parens> [ <media-and>* | <media-or>* ]
AnyCssMediaCondition =
CssMediaNotCondition
| CssMediaAndCondition
| CssMediaOrCondition
| AnyCssMediaInParens
// @media not all and not (color) { }
// ^^^^^^^^^^^
CssMediaNotCondition =
'not'
condition: AnyCssMediaInParens
// @media (width > 400px) or (color) or (inline-width >= 30em) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssMediaOrCondition =
left: AnyCssMediaInParens
'or'
right: AnyCssMediaOrCombinableCondition
AnyCssMediaOrCombinableCondition =
CssMediaOrCondition
| AnyCssMediaInParens
// @media (width > 400px) and (color) and (inline-width >= 30em) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssMediaAndCondition =
left: AnyCssMediaInParens
'and'
right: AnyCssMediaAndCombinableCondition
AnyCssMediaAndCombinableCondition =
CssMediaAndCondition
| AnyCssMediaInParens
AnyCssMediaInParens =
CssMediaConditionInParens
| CssMediaFeatureInParens
// @media (width > 400px) or ((color) and (inline-width >= 30em)) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssMediaConditionInParens =
'('
condition: AnyCssMediaCondition
')'
// @media (width > 400px) or ((color) and (inline-width >= 30em)) { }
// ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
CssMediaFeatureInParens =
'('
feature: AnyCssQueryFeature
')'
AnyCssQueryFeature =
CssQueryFeaturePlain
| CssQueryFeatureBoolean
| CssQueryFeatureRange
| CssQueryFeatureReverseRange
| CssQueryFeatureRangeInterval
// @container (--responsive: true) { }
// ^^^^^^^^^^^^^^^^^^
CssQueryFeaturePlain =
name: CssIdentifier
':'
value: AnyCssQueryFeatureValue
// @container (any-hover) { }
// ^^^^^^^^^
CssQueryFeatureBoolean =
name: CssIdentifier
// @media (width <= 500px) { }
// ^^^^^^^^^^^^^^
CssQueryFeatureRange =
left: CssIdentifier
comparison: CssQueryFeatureRangeComparison
right: AnyCssQueryFeatureValue
// @media (500px <= width) { }
// ^^^^^^^^^^^^^^
CssQueryFeatureReverseRange =
left: AnyCssQueryFeatureValue
comparison: CssQueryFeatureRangeComparison
right: CssIdentifier
// @media (500px <= width <= 500px) { }
// ^^^^^^^^^^^^^^^^^^^^^^^
CssQueryFeatureRangeInterval =
left: AnyCssQueryFeatureValue
left_comparison: CssQueryFeatureRangeComparison
name: CssIdentifier
right_comparison: CssQueryFeatureRangeComparison
right: AnyCssQueryFeatureValue
// @media (500px <= width <= 500px) { }
// ^^ ^^
CssQueryFeatureRangeComparison =
operator: ('>' | '<' | '=' | '>=' | '<=')
// @media (500px <= width <= 500px) { }
// ^^^^^ ^^^^^
AnyCssQueryFeatureValue =
CssNumber
| AnyCssDimension
| CssIdentifier
| CssRatio
| AnyCssFunction
// https://drafts.csswg.org/css-page/#at-ruledef-page
// @page = @page <page-selector-list>? { <declaration-rule-list> }
// <page-selector-list> = <page-selector>#
// <page-selector> = [ <ident-token>? <pseudo-page>* ]!
// <pseudo-page> = ':' [ left | right | first | blank ]
// @page :first { }
// ^^^^^^^^^^^^^^^^^
CssPageAtRule =
'page'
selectors: CssPageSelectorList
block: AnyCssPageAtRuleBlock
// @page name:first,:blank:first { }
// ^^^^^^^^^^^^^^^^^^^^^^^
CssPageSelectorList = (AnyCssPageSelector (',' AnyCssPageSelector)*)
AnyCssPageSelector =
CssPageSelector
| CssBogusSelector
// @page name:first,:blank:first { }
// ^^^^^^^^^^ ^^^^^^^^^^^^
// NOTE: The CSS Spec uses `<ident>` for the name, but also explicitly states
// that the name is case sensitive. For simplicity, `<custom-ident>` gets the
// exact same behavior.
CssPageSelector =
type: CssCustomIdentifier?
pseudos: CssPageSelectorPseudoList
CssPageSelectorPseudoList = AnyCssPageSelectorPseudo*
AnyCssPageSelectorPseudo =
CssPageSelectorPseudo
| CssBogusPageSelectorPseudo
// @page name:first,:blank:first { }
// ^^^^^^ ^^^^^^^^^^^^
CssPageSelectorPseudo =
':'
selector: ('left' | 'right' | 'first' | 'blank')
AnyCssPageAtRuleBlock =
CssPageAtRuleBlock
| CssBogusBlock
// @page :left { @left-middle {} background: red; @media (500px <= width <= 500px) { } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssPageAtRuleBlock =
'{'
items: CssPageAtRuleItemList
'}'
CssPageAtRuleItemList = AnyCssPageAtRuleItem*
AnyCssPageAtRuleItem =
CssDeclarationWithSemicolon
| CssEmptyDeclaration
| CssAtRule
| CssMarginAtRule
// @page :left { @left-middle {} background: red; }
// ^^^^^^^^^^^^^^^
CssMarginAtRule =
'@'
name: (
'top-left-corner'
| 'top-left'
| 'top-center'
| 'top-right'
| 'top-right-corner'
| 'bottom-left-corner'
| 'bottom-left'
| 'bottom-center'
| 'bottom-right'
| 'bottom-right-corner'
| 'left-top'
| 'left-middle'
| 'left-bottom'
| 'right-top'
| 'right-middle'
| 'right-bottom'
)
block: AnyCssDeclarationOrAtRuleBlock
// https://drafts.csswg.org/css-cascade-5/#layering
// @layer =
// @layer <layer-name>? { <rule-list> } |
// @layer <layer-name># ;
//
// <layer-name> =
// <ident> [ '.' <ident> ]*
// @layer override { }
// ^^^^^^^^^^^^^^^^^^
// @layer framework, bar.baz;
// ^^^^^^^^^^^^^^^^^^^^^^^^^
CssLayerAtRule =
'layer'
layer: AnyCssLayer
AnyCssLayer =
CssLayerDeclaration
| CssLayerReference
| CssBogusLayer
// @layer override.bar { }
// ^^^^^^^^^^^^^^^^
CssLayerDeclaration =
references: CssLayerReferenceList
block: AnyCssConditionalBlock
// @layer framework, bar.baz;
// ^^^^^^^^^^^^^^^^^^^
CssLayerReference =
references: CssLayerReferenceList
';'
// @layer framework, bar.baz;
// ^^^^^^^^^^^^^^^^^^
CssLayerReferenceList = (CssLayerNameList (',' CssLayerNameList)*)
// @layer framework, bar.baz;
// ^^^^^^^
CssLayerNameList = (CssIdentifier ('.' CssIdentifier)*)
// https://drafts.csswg.org/css-conditional-3/#at-supports
// https://drafts.csswg.org/css-conditional-4/#at-supports-ext
// @supports <supports-condition> {
// <rule-list>
// }
// <supports-condition> = not <supports-in-parens>
// | <supports-in-parens> [ and <supports-in-parens> ]*
// | <supports-in-parens> [ or <supports-in-parens> ]*
// <supports-in-parens> = ( <supports-condition> ) | <supports-feature> | <general-enclosed>
// <supports-feature> = <supports-selector-fn> | <supports-decl>
// <supports-selector-fn> = selector( <complex-selector> )
// <supports-decl> = ( <declaration> )
// <general-enclosed> branch must only be chosen if the input does not match either of the preceding branches.
// <general-enclosed> exists to allow for future expansion of the grammar in a reasonably compatible way.
// @supports (display: grid) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssSupportsAtRule =
'supports'
condition: AnyCssSupportsCondition
block: AnyCssConditionalBlock
AnyCssSupportsCondition =
CssSupportsNotCondition
| CssSupportsOrCondition
| CssSupportsAndCondition
| AnyCssSupportsInParens
| CssBogusSupportsCondition
// @supports not (display: grid) {}
// ^^^^^^^^^^^^^^^^^^^
CssSupportsNotCondition =
'not'
query: AnyCssSupportsInParens
// @supports (transition-property: color) and (animation-name: foo) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssSupportsAndCondition =
left: AnyCssSupportsInParens
'and'
right: AnyCssSupportsAndCombinableCondition
// @supports (transition-property: color) and ((animation-name: foo) or (display: flex)) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AnyCssSupportsAndCombinableCondition =
CssSupportsAndCondition
| AnyCssSupportsInParens
// @supports (transition-property: color) or (animation-name: foo) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssSupportsOrCondition =
left: AnyCssSupportsInParens
'or'
right: AnyCssSupportsOrCombinableCondition
// @supports (transition-property: color) or ((animation-name: foo) and (display: flex)) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AnyCssSupportsOrCombinableCondition =
CssSupportsOrCondition
| AnyCssSupportsInParens
AnyCssSupportsInParens =
CssSupportsConditionInParens
| CssSupportsFeatureDeclaration
| CssSupportsFeatureSelector
| AnyCssValue // general-enclosed
// @supports (transition-property: color) or (not (animation-name: foo)) {}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssSupportsConditionInParens =
'('
condition: AnyCssSupportsCondition
')'
// @supports not (display: flex) {}
// ^^^^^^^^^^^^^^^
CssSupportsFeatureDeclaration =
'('
declaration: CssDeclaration
')'
// @supports selector(:focus-visible) { }
// ^^^^^^^^^^^^^^^^^^^^^^^^
CssSupportsFeatureSelector =
'selector'
'('
selector: AnyCssSelector
')'
// https://drafts.csswg.org/css-cascade-6/#at-ruledef-scope
// @scope [(<scope-start>)]? [to (<scope-end>)]? {
// <rule-list>
// }
CssScopeAtRule =
'scope'
range: AnyCssScopeRange?
block: AnyCssConditionalBlock
AnyCssScopeRange =
CssScopeRangeStart
| CssScopeRangeEnd
| CssScopeRangeInterval
| CssBogusScopeRange
CssScopeRangeStart =
start: CssScopeEdge
CssScopeRangeEnd =
'to'
end: CssScopeEdge
CssScopeRangeInterval =
start: CssScopeEdge
'to'
end: CssScopeEdge
CssScopeEdge =
'('
selectors: CssSelectorList
')'
// https://drafts.csswg.org/css-cascade/#at-import
// @import = @import [ <url> | <string> ] [ layer | layer( <layer-name> ) ]? <import-conditions> ;
// <url> = <url()> | <src()>
// <url()> = url( <string> <url-modifier>* ) | <url-token>
// <src()> = src( <string> <url-modifier>* )
// <layer-name> = <ident> [ '.' <ident> ]*
//
// <import-conditions> = [ supports( [ <supports-condition> | <declaration> ] ) ]? <media-query-list>?
// @import url("./test.css") layer(default) supports(display: flex) screen and (min-width: 400px);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssImportAtRule =
'import'
url: AnyCssImportUrl
layer: AnyCssImportLayer?
supports: CssImportSupports?
media: CssMediaQueryList
';'
// @import url("./test.css");
// ^^^^^^^^^^^^
// @import "./test.scss";
// ^^^^^^^^^^^^^
AnyCssImportUrl = CssUrlFunction | CssString
AnyCssImportLayer =
CssImportAnonymousLayer
| CssImportNamedLayer
// @import url("./test.css") layer;
// ^^^^^
CssImportAnonymousLayer =
'layer'
// @import url("./test.css") layer(module.foo);
// ^^^^^^^^^^^^^^^^^
CssImportNamedLayer =
'layer'
'('
name: CssLayerNameList
')'
// @import url("./test.css") layer(default) supports(display: flex);
// ^^^^^^^^^^^^^^^^^^^^^^^
CssImportSupports =
'supports'
'('
condition: AnyCssImportSupportsCondition
')'
AnyCssImportSupportsCondition =
AnyCssSupportsCondition
| CssDeclaration
// https://drafts.csswg.org/css-namespaces/#declaration
// @namespace = @namespace <namespace-prefix>? [ <string> | <url> ] ;
// <namespace-prefix> = <ident>
CssNamespaceAtRule =
'namespace'
prefix: CssIdentifier?
url: AnyCssNamespaceUrl
';'
AnyCssNamespaceUrl = CssUrlFunction | CssString
// https://drafts.csswg.org/css-transitions-2/#at-ruledef-starting-style
// @starting-style {
// rulesets
// }
//
// selector {
// @starting-style {
// declarations
// }
// }
CssStartingStyleAtRule =
'starting-style'
block: AnyCssConditionalBlock
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document
// @document [ <url> |
// url-prefix(<string>) |
// domain(<string>) |
// media-document(<string>) |
// regexp(<string>)
// ]# {
// <group-rule-body>
// }
CssDocumentAtRule =
'document'
matchers: CssDocumentMatcherList
block: AnyCssRuleBlock
CssDocumentMatcherList = (AnyCssDocumentMatcher (',' AnyCssDocumentMatcher)*)
AnyCssDocumentMatcher =
CssUrlFunction
| CssDocumentCustomMatcher
| CssBogusDocumentMatcher
CssDocumentCustomMatcher =
name: ('url-prefix' | 'domain' | 'media-document' | 'regexp')
'('
value: AnyCssUrlValue?
')'
// https://github.com/css-modules/postcss-modules-values
// @value selectorValue: secondary-color;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// @value small as bp-small, large as bp-large from "./breakpoints.css";
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssValueAtRule =
'value'
clause: AnyCssValueAtRuleClause
';'
AnyCssValueAtRuleClause =
CssValueAtRuleImportClause
| CssValueAtRuleDeclarationClause
// @value small as bp-small, large as bp-large from "./breakpoints.css";
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssValueAtRuleImportClause =
specifiers: CssValueAtRuleImportSpecifierList
'from'
source: AnyCssValueAtRuleImportSource
// @value primary, secondary from colors;
// ^^^^^^
// @value small as bp-small, large as bp-large from "./breakpoints.css";
// ^^^^^^^^^^^^^^^^^^^
AnyCssValueAtRuleImportSource =
CssString
| CssIdentifier
CssValueAtRuleImportSpecifierList = (AnyCssValueAtRuleImportSpecifier (',' AnyCssValueAtRuleImportSpecifier)*)
AnyCssValueAtRuleImportSpecifier =
CssValueAtRuleImportSpecifier
| CssValueAtRuleNamedImportSpecifier
// @value small, large as bp-large from "./breakpoints.css";
// ^^^^^
CssValueAtRuleImportSpecifier =
name: CssIdentifier
// @value small, large as bp-large from "./breakpoints.css";
// ^^^^^^^^^^^^^^^^^
CssValueAtRuleNamedImportSpecifier =
name: CssIdentifier
'as'
local_name: CssIdentifier
// @value small: (max-width: 599px), selectorValue: secondary-color;
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssValueAtRuleDeclarationClause =
properties: CssValueAtRulePropertyList
CssValueAtRulePropertyList = (AnyCssValueAtRuleProperty (',' AnyCssValueAtRuleProperty)*)
AnyCssValueAtRuleProperty =
CssValueAtRuleGenericProperty
| CssBogusProperty
CssValueAtRuleGenericProperty =
name: AnyCssDeclarationName
':'
value: CssValueAtRuleGenericValue
// A generic property can be any sequence of tokens, which is why we parse it as an unformed tree.
// This approach allows for flexibility, as the exact structure of generic properties may not be well-defined.
// By parsing the property in this manner, we ensure that we can handle a wide variety of cases without
// requiring specific grammar rules for each possible property structure.
//
// If we want to extend the parsing with a specific grammar in the future, we can implement speculative parsing.
// Speculative parsing would attempt to parse the property according to the new grammar rules.
// If an error occurs during this process, we can fall back to the current implementation, which treats
// the property as an unformed tree. This approach ensures backward compatibility and allows for incremental
// improvements to the parser without breaking existing functionality.
CssValueAtRuleGenericValue = SyntaxElement*
/// Represents an unknown or unsupported CSS at-rule during parsing.
///
/// When encountered during parsing, `CssUnknownAtRule` serves as a fallback mechanism,
/// allowing the parser to continue processing the stylesheet by treating the unsupported rule
/// as part of an unformed tree. This enables graceful handling of CSS constructs that are not
/// supported by the parser, ensuring that the stylesheet can still be processed without errors.
CssUnknownBlockAtRule =
name: CssIdentifier
components: CssUnknownAtRuleComponentList
block: AnyCssDeclarationOrRuleBlock
CssUnknownValueAtRule =
name: CssIdentifier
components: CssUnknownAtRuleComponentList
';'
CssUnknownAtRuleComponentList = SyntaxElement*
// https://drafts.csswg.org/css-anchor-position-1/#at-ruledef-position-try
// @position-try = @position-try <dashed-ident> { <declaration-list> }
// @position-try --ident { }
// ^^^^^^^^^^^^^^^^^^^^^^^^^
CssPositionTryAtRule =
'position-try'
name: CssDashedIdentifier
block: AnyCssDeclarationBlock
// https://drafts.csswg.org/css-view-transitions-2/#view-transition-rule
// @view-transition = @view-transition { <declaration-list> }
// @view-transition { }
// ^^^^^^^^^^^^^^^^^^^^
CssViewTransitionAtRule =
'view-transition'
block: AnyCssDeclarationBlock
///////////////
// AUXILIARY
///////////////
// When adding values here, add them as `NodeConcept::Value` in the
// `name_to_module` function in `src/formatter.rs`.
AnyCssValue =
CssIdentifier
| CssCustomIdentifier
| CssDashedIdentifier
| CssString
| CssNumber
| AnyCssDimension
| CssRatio
| AnyCssFunction
| CssColor
| CssBracketedValue
| CssUnicodeRange
| CssMetavariable
// https://drafts.csswg.org/css-syntax/#typedef-dimension-token
//
// Known, valid units like `px` or `vh` will be parsed as `CssRegularDimension`,
// but the spec considers any identifier after the number to be the "unit".
// These cases will instead be parsed into `CssUnknownDimension`.
//
// Dimensions and percentages are considered unique tokens in CSS, and so these
// nodes just represent the tokens directly, rather than wrapping them in
// `CssNumber` and `CssIdentifier`. This helps users easily distinguish between
// things like `100`, `100px`, and `100%`, where they are all distinct, non-
// overlapping types. If these instead used the number and identifier nodes, a
// visitor would incidentally visit them even though they cannot be safely
// operated on individually (i.e., the whole dimension must be treated as a
// whole and not two individual pieces).
// 10px
// 100vh
// 4rem
// 1e-2
// 0\0
// 0Unknown
AnyCssDimension =
CssRegularDimension | CssUnknownDimension | CssPercentage
CssRegularDimension =
value: 'css_number_literal'
unit: 'ident'
CssUnknownDimension =
value: 'css_number_literal'
unit: 'ident'
CssPercentage =
value: 'css_number_literal'
'%'
// 3 / 2
CssRatio =
numerator: CssNumber
'/'
denominator: CssNumber
AnyCssFunction =
CssFunction
| CssUrlFunction
// content: counter(section);
// ^^^^^^^^^^^^^^^^
CssFunction =
name: CssIdentifier
'('
items: CssParameterList
')'
// https://drafts.csswg.org/css-values-4/#url-value
// <url> = <url()> | <src()>
//
// <url()> = url( <string> <url-modifier>* ) | <url-token>
// <src()> = src( <string> <url-modifier>* )
//
// url("//aa.com/img.svg" foo bar baz func(test))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CssUrlFunction =
name: ('url' | 'src')
'('
value: AnyCssUrlValue?
modifiers: CssUrlModifierList
')'
// url("//aa.com/img.svg" foo bar baz func(test))
// ^^^^^^^^^^^^^^^^^^^^^^^
CssUrlModifierList = AnyCssUrlModifier*
AnyCssUrlModifier =
CssIdentifier
| CssFunction
| CssBogusUrlModifier
AnyCssUrlValue =
CssUrlValueRaw
| CssString
CssParameterList = CssParameter (',' CssParameter)* ','?
// cubic-bezier(0.1, 0.7, 1.0, 0.1)
// ^^^
// repeating-radial-gradient(red, yellow 10%, green 15%);
// ^^^^^^^^^^
CssParameter = AnyCssExpression
AnyCssExpression =
CssBinaryExpression
| CssParenthesizedExpression
| CssListOfComponentValuesExpression
CssListOfComponentValuesExpression = CssComponentValueList
CssBinaryExpression =
left: AnyCssExpression
operator_token: (
'+' | '-' | '*' | '/'
)
right: AnyCssExpression
CssParenthesizedExpression =
'(' expression: AnyCssExpression? ')'
// #fff
// ^^^^
CssColor =
'#'
value:'css_color_literal'
// The CssBracketedValue is a special construct used to represent CSS values that are enclosed in brackets.
// This is commonly seen in properties like grid-template-areas where the value is a list of identifiers representing grid areas.
// [full-start] 1fr [main-start end] 480px [main-end] 1fr [full-end]
// ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^
// https://drafts.csswg.org/css-grid/#named-lines
CssBracketedValue =
'['
items: CssBracketedValueList
']'
CssBracketedValueList = AnyCssCustomIdentifier*
AnyCssCustomIdentifier =
CssCustomIdentifier
| CssBogusCustomIdentifier
// https://drafts.csswg.org/css-fonts/#unicode-range-desc
// https://www.w3.org/TR/css-syntax-3/#typedef-urange
// <urange> =
// u '+' <ident-token> '?'* |
// u <dimension-token> '?'* |
// u <number-token> '?'* |
// u <number-token> <dimension-token> |
// u <number-token> <number-token> |
// u '+' '?'+
// unicode-range: U+000-49F, U+2000-27FF, U+2900-2BFF, U+1D400-1D7FF, U+ff??;
// ^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^
CssUnicodeRange =
prefix: 'U+'
value: AnyCssUnicodeValue
AnyCssUnicodeValue =
CssUnicodeCodepoint
| CssUnicodeRangeWildcard
| CssUnicodeRangeInterval
| CssBogusUnicodeRangeValue
CssUnicodeRangeInterval =
start: CssUnicodeCodepoint
'-'
end: CssUnicodeCodepoint
// Any identifier. Case insensitve, used for standard property names, values, type selectors, etc.
CssIdentifier = value: 'ident'
// Any non-standard identifier. Case sensitive, used for class names, ids, etc. Custom identifiers
// _may_ overlap with standard identifiers defined by CSS (e.g., `color`).
CssCustomIdentifier = value: 'ident'
// An identifier starting with two dashes, `--`. Case sensitive, used for custom property names.
// Dashed identifiers are guaranteed to never overlap with an identifier defined by CSS.
CssDashedIdentifier = value: 'ident'
CssString = value: 'css_string_literal'
CssNumber = value: 'css_number_literal'
CssUnicodeCodepoint = value: 'css_unicode_codepoint_literal'
CssUnicodeRangeWildcard = value: 'css_unicode_range_wildcard_literal'
CssUrlValueRaw = value: 'css_url_value_raw_literal'
// https://github.com/getgrit/gritql/blob/main/resources/language-metavariables/tree-sitter-css/grammar.js
CssMetavariable = value: 'grit_metavariable'