From dd81bb854a37e5e2f7891fbc5e75d192b9ca2ad9 Mon Sep 17 00:00:00 2001 From: deathaxe Date: Sat, 15 Jul 2023 17:50:57 +0200 Subject: [PATCH] [CSS] Add nested rules support (#3785) * [CSS] Add nested rules support Resolves #3688 This commit implements support for nested selectors and rule lists according to current draft specification at https://drafts.csswg.org/css-nesting-1. * [CSS] Reduce unused patterns from property lists This commit removes obsolete includes from `property-list-body` context. - `property-values` is no longer matched as `:` pushes `nested-selectors` - `illegal-blocks` is replaced by nested `property-lists` * [CSS] Fix template interpolation in CSS property names By excluding `prototype` from rule-lists this commit ensures, template tags are correctly scoped as part of a selector, property-name or property-value. That's important as `:` no longer denotes beginning of property-values, but is the beginning of nested selectors. Some extra `;` are added in syntax tests to make sure the following content is scoped correctly. That's required as assertion comments are sometimes scoped as selectors, caused by comment styles not being supported within CSS. --- ASP/syntax_test_asp.asp | 2 +- CSS/CSS.sublime-syntax | 24 ++- CSS/syntax_test_css.css | 138 ++++++++++++++++-- HTML/syntax_test_html.html | 10 +- Haskell/tests/syntax_test_haskell.hs | 2 +- Java/tests/syntax_test_jsp.jsp | 4 +- PHP/tests/syntax_test_php.php | 25 +++- Rails/tests/syntax_test_rails.css.erb | 4 +- Rails/tests/syntax_test_rails.haml | 2 +- Ruby/Embeddings/CSS (for Ruby).sublime-syntax | 4 + Ruby/syntax_test_ruby.rb | 35 ++++- 11 files changed, 219 insertions(+), 31 deletions(-) diff --git a/ASP/syntax_test_asp.asp b/ASP/syntax_test_asp.asp index 19883dc648..1e35c36b56 100644 --- a/ASP/syntax_test_asp.asp +++ b/ASP/syntax_test_asp.asp @@ -208,7 +208,7 @@ ' ^^^^^^^^ source.asp.embedded.html variable.other.asp ' ^^ punctuation.section.embedded.end.asp ' ^ punctuation.section.block.begin.css - ' ^^^^^^^^^ meta.embedded.asp + ' ^^^^^^^^^ meta.property-name.css support.type.property-name.css meta.embedded.asp ' ^ punctuation.separator.key-value.css ' ^^^^^^^^^^ meta.property-value.css meta.embedded.asp ' ^ punctuation.terminator.rule.css diff --git a/CSS/CSS.sublime-syntax b/CSS/CSS.sublime-syntax index d4ae76ffd7..f62a023319 100644 --- a/CSS/CSS.sublime-syntax +++ b/CSS/CSS.sublime-syntax @@ -80,7 +80,9 @@ variables: # Selectors selector_begin: (?={{selector_start}}) selector_end: (?=[;@(){}]) - selector_start: '[[:alpha:].:#&*\[{{combinator_char}}]' + selector_start: '[[:alpha:]{{nested_selector_start}}]' + nested_selector_begin: (?={{nested_selector_start}}) + nested_selector_start: '[.:#&*\[{{combinator_char}}]' # Combinators # https://drafts.csswg.org/selectors-4/#combinators @@ -359,9 +361,9 @@ contexts: stylesheet: - include: comments + - include: property-lists - include: selectors - include: at-rules - - include: property-lists - include: rule-terminators - include: illegal-groups @@ -884,6 +886,7 @@ contexts: push: at-supports-group-body at-supports-group-body: + - meta_include_prototype: false - meta_scope: meta.group.css - include: group-end - include: at-rule-end @@ -1040,6 +1043,11 @@ contexts: - match: '{{selector_begin}}' push: selector-body + nested-selectors: + # https://drafts.csswg.org/css-nesting-1 + - match: '{{nested_selector_begin}}' + push: selector-body + selector-body: - meta_scope: meta.selector.css - include: selector-end @@ -1117,6 +1125,8 @@ contexts: pop: 1 selector-variables: + - match: \& + scope: variable.language.parent.css - match: \* scope: variable.language.wildcard.asterisk.css @@ -1366,12 +1376,20 @@ contexts: push: property-list-body property-list-body: + - meta_include_prototype: false - meta_scope: meta.property-list.css meta.block.css - include: block-end - - include: rule-list-body + - include: comments + - include: property-lists + - include: property-identifiers + - include: nested-selectors + - include: at-rules + - include: rule-terminators + - include: illegal-groups rule-list-body: # Note: This context is used by HTML.sublime-syntax + # No selectors supported in style attributes. - include: comments - include: property-identifiers - include: property-values diff --git a/CSS/syntax_test_css.css b/CSS/syntax_test_css.css index 2ac8987187..5be5a8be94 100644 --- a/CSS/syntax_test_css.css +++ b/CSS/syntax_test_css.css @@ -749,6 +749,16 @@ /* ^^ punctuation.section.group.end.css */ /* ^ punctuation.terminator.rule.css */ + .test-nested-media { @media (width >= 1024px) { span { font-size: 1.25rem; } } } +/* ^^ meta.property-list.css meta.block.css - meta.at-rule */ +/* ^^^^^^^ meta.property-list.css meta.block.css meta.at-rule.media.css - meta.group - meta.block meta.block */ +/* ^^^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.at-rule.media.css meta.group.css */ +/* ^ meta.property-list.css meta.block.css meta.at-rule.media.css - meta.group - meta.block meta.block */ +/* ^^^^^^^ meta.property-list.css meta.block.css meta.at-rule.media.css meta.block.css - meta.block.css meta.block.css meta.block.css */ +/* ^^^^^^^^^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.at-rule.media.css meta.block.css meta.property-list.css meta.block.css */ +/* ^^ meta.property-list.css meta.block.css meta.at-rule.media.css meta.block.css - meta.block.css meta.block.css meta.block.css */ +/* ^^ meta.property-list.css meta.block.css - meta.at-rule */ + @custom-media --a-b (width: 1px); /* ^^^^^^^^^^^^^^^^^^^^ meta.at-rule.custom-media.css - meta.group */ /* ^^^^^^^^^^^^ meta.at-rule.custom-media.css meta.group.css */ @@ -2392,24 +2402,34 @@ /* unexpected tokens { } => handle it as expected, using pairing */ .test-parsing-errors { color{;color:maroon} } -/* ^^^^^^^^^^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css */ +/* ^^^^^^^ meta.property-list.css meta.block.css - meta.property-list meta.property-list - meta.block meta.block */ +/* ^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css */ +/* ^^ meta.property-list.css meta.block.css - meta.property-list meta.property-list - meta.block meta.block */ /* ^ - meta.property-list */ /* ^ punctuation.section.block.begin.css */ -/* ^^^^^ support.type.property-name.css */ -/* ^ invalid.illegal.unexpected-token.css */ -/* ^^^^^^^^^^^^^ - constant - keyword - punctuation - support - variable */ -/* ^ invalid.illegal.unexpected-token.css */ +/* ^^^^^ meta.property-name.css support.type.property-name.css */ +/* ^ punctuation.section.block.begin.css */ +/* ^ punctuation.terminator.rule.css */ +/* ^^^^^ support.type.property-name.css */ +/* ^ punctuation.separator.key-value.css */ +/* ^^^^^^ support.constant.color.w3c.standard.css */ +/* ^ punctuation.section.block.end.css */ /* ^ punctuation.section.block.end.css */ /* same with recovery => handle it as expected, using pairing */ .test-parsing-errors { color{;color:maroon}; color:green } -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css */ +/* ^^^^^^^ meta.property-list.css meta.block.css - meta.property-list meta.property-list - meta.block meta.block */ +/* ^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css */ +/* ^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css - meta.property-list meta.property-list - meta.block meta.block */ /* ^ - meta.property-list */ /* ^ punctuation.section.block.begin.css */ -/* ^^^^^ support.type.property-name.css */ -/* ^ invalid.illegal.unexpected-token.css */ -/* ^^^^^^^^^^^^^ - constant - keyword - punctuation - support - variable */ -/* ^ invalid.illegal.unexpected-token.css */ +/* ^^^^^ meta.property-name.css support.type.property-name.css */ +/* ^ punctuation.section.block.begin.css */ +/* ^ punctuation.terminator.rule.css */ +/* ^^^^^ support.type.property-name.css */ +/* ^ punctuation.separator.key-value.css */ +/* ^^^^^^ support.constant.color.w3c.standard.css */ +/* ^ punctuation.section.block.end.css */ /* ^ punctuation.terminator.rule.css */ /* ^^^^^ support.type.property-name.css */ /* ^ punctuation.separator.key-value.css */ @@ -3881,3 +3901,101 @@ img{ /* ^^ meta.number.float.decimal.css constant.numeric.value.css */ /* ^ punctuation.terminator.rule.css */ } + +.test-nested-selectors { + .bar { ... } +/* ^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^^^^ entity.other.attribute-name.class.css */ +/* ^ punctuation.definition.entity.css */ + #baz { ...} +/* ^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^^^^ entity.other.attribute-name.id.css */ +/* ^ punctuation.definition.entity.css */ + :has(p) { ... } +/* ^^^^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^ punctuation.definition.pseudo-class.css */ +/* ^^^ entity.other.pseudo-class.css */ + ::backdrop { ... } +/* ^^^^^^^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^^ punctuation.definition.pseudo-element.css */ +/* ^^^^^^^^ entity.other.pseudo-element.css */ + [lang|="zh"] { ... } +/* ^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.selector.css meta.attribute-selector.css meta.brackets.css */ +/* ^ meta.property-list.css meta.block.css meta.selector.css - meta.brackets.css*/ + * { ... } +/* ^^ meta.property-list.css meta.block.css meta.selector.css */ + + article { ... } +/* ^^^^^^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^ keyword.operator.combinator.css */ + > p { ... } +/* ^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^ keyword.operator.combinator.css */ + ~ main { ... } +/* ^^^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^ keyword.operator.combinator.css */ + || main { ... } +/* ^^^^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^^ keyword.operator.combinator.css */ + & article { ... } +/* ^^^^^^^^^^ meta.property-list.css meta.block.css meta.selector.css */ +/* ^ variable.language.parent.css */ +/* ^^^^^^^ entity.name.tag.html.css */ + article { ... } +/* ^^^^^^^^ meta.property-list.css meta.block.css - meta.selector */ +} + +.foo:bar > tr.baz[test], div p { +/* <- meta.selector.css entity.other.attribute-name.class.css punctuation.definition.entity.css */ +/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.selector.css */ + foo:bar { +/* ^^^ meta.property-list.css meta.block.css meta.property-name.css support.type.property-name.css */ +/* ^ meta.property-list.css meta.block.css punctuation.separator.key-value.css*/ +/* ^^^ meta.property-list.css meta.block.css meta.property-value.css support.constant.property-value.css*/ +/* ^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css */ +/* ^ punctuation.section.block.begin.css */ + foo:bar; +/* ^^^^^^^^^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css */ +/* ^^^ meta.property-name.css support.type.property-name.css */ +/* ^ punctuation.separator.key-value.css*/ +/* ^^^ meta.property-value.css support.constant.property-value.css */ +/* ^ punctuation.terminator.rule.css */ + & { +/* ^^ meta.selector.css */ +/* ^ variable.language.parent.css */ + color: hwb(0, 100%, 50%, 1.0); +/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css meta.property-list.css meta.block.css */ +/* ^^^^^ meta.property-name.css support.type.property-name.css */ +/* ^ punctuation.separator.key-value.css */ +/* ^^^^^^^^^^^^^^^^^^^^^^ meta.property-value.css */ + } +/* ^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css meta.property-list.css meta.block.css punctuation.section.block.end.css */ + + &.baz &:hover {} +/* ^^^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css meta.selector.css */ +/* ^ variable.language.parent.css */ +/* ^^^^ entity.other.attribute-name.class.css */ +/* ^ variable.language.parent.css */ +/* ^ punctuation.definition.pseudo-class.css */ +/* ^^^^^ entity.other.pseudo-class.css */ + + .foo & & & {} +/* ^^^^^^^^^^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css meta.selector.css */ +/* ^ variable.language.parent.css */ +/* ^ variable.language.parent.css */ +/* ^ variable.language.parent.css */ + + /* & is not a string value, but an object in CSS */ + .foo { &bar { /* is not not .foobar! */ } +/* ^ variable.language.parent.css */ +/* ^^^ entity.name.tag.other.css*/ + + :is(article) & {} +/* ^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css meta.property-list.css meta.block.css meta.selector.css */ +/* ^ punctuation.definition.pseudo-class.css */ +/* ^^ meta.function-call.identifier.css entity.other.pseudo-class.css */ +/* ^^^^^^^^^ meta.function-call.arguments.css meta.group.css */ +/* ^ variable.language.parent.css */ + } +/* ^ meta.property-list.css meta.block.css meta.property-list.css meta.block.css punctuation.section.block.end.css */ +} +/* <- meta.property-list.css meta.block.css punctuation.section.block.end.css */ \ No newline at end of file diff --git a/HTML/syntax_test_html.html b/HTML/syntax_test_html.html index 8156958006..1c49c5cac1 100644 --- a/HTML/syntax_test_html.html +++ b/HTML/syntax_test_html.html @@ -354,9 +354,13 @@ ## ^^^^ comment.block.html punctuation.definition.comment.begin.html h2 { ## <- source.css.embedded.html - source.css source.css - ## <- entity.name.tag.html.css - font-family: "Arial"; - ## ^ string.quoted.double.css + ## ^^ entity.name.tag.html.css + ## ^ punctuation.section.block.begin.css + ; font-family: "Arial"; + ## ^^^^^^^^^^^ meta.property-name.css support.type.property-name.css + ## ^ punctuation.separator.key-value.css + ## ^^^^^^^ meta.property-value.css meta.string.css string.quoted.double.css + ## ^ punctuation.terminator.rule.css } --> ## <- comment.block.html punctuation.definition.comment.end.html diff --git a/Haskell/tests/syntax_test_haskell.hs b/Haskell/tests/syntax_test_haskell.hs index 9ee7203544..5e9628ce08 100644 --- a/Haskell/tests/syntax_test_haskell.hs +++ b/Haskell/tests/syntax_test_haskell.hs @@ -3131,7 +3131,7 @@ main = do p { -- ^^ source.css.embedded.html meta.property-list.css meta.block.css -- ^ punctuation.section.block.begin.css - font-family: Helvetica; +; font-family: Helvetica; -- ^^^^^^^^^^^ meta.property-name.css support.type.property-name.css } -- ^ source.css.embedded.html meta.property-list.css meta.block.css punctuation.section.block.end.css diff --git a/Java/tests/syntax_test_jsp.jsp b/Java/tests/syntax_test_jsp.jsp index 9f963e80a5..13a14b57ac 100644 --- a/Java/tests/syntax_test_jsp.jsp +++ b/Java/tests/syntax_test_jsp.jsp @@ -22,7 +22,7 @@ // ^^ punctuation.section.embedded.begin.jsp // ^^^^^^^^^^^^^^^^^ source.java.embedded.jsp // ^^ punctuation.section.embedded.end.jsp - color: <% print("<\%foo%\>"); %>; +; color: <% print("<\%foo%\>"); %>; // ^^^^^ support.type.property-name.css // ^ punctuation.separator.key-value.css // ^^^^^^^^^^^^^^^^^^^^^^^^^ meta.property-value.css meta.embedded.scriptlet.jsp @@ -32,7 +32,7 @@ // ^^^ constant.character.escape.jsp // ^^^ constant.character.escape.jsp // ^^ punctuation.section.embedded.end.jsp - font-family: "Helve<% print("tic")%>a"; +; font-family: "Helve<% print("tic")%>a"; // ^^^^^^ meta.string.css - meta.interpolation - meta.embedded // ^^^^^^^^^^^^^^^^^ meta.string.css meta.interpolation.jsp meta.embedded.scriptlet.jsp // ^^ meta.string.css - meta.interpolation - meta.embedded diff --git a/PHP/tests/syntax_test_php.php b/PHP/tests/syntax_test_php.php index ca87ade962..0b582df6d6 100644 --- a/PHP/tests/syntax_test_php.php +++ b/PHP/tests/syntax_test_php.php @@ -5831,7 +5831,7 @@ function foo() { // ^ punctuation.section.block.begin.php // ^^ punctuation.section.embedded.end.php - font-size: 2em; +; font-size: 2em; // ^^^^^^^^^^^^^^^^^ text.html.php source.css.embedded // ^^^^^^^^^ support.type.property-name // ^ constant.numeric @@ -5846,7 +5846,7 @@ function foo() { // ^ punctuation.section.block.begin.php // ^^ punctuation.section.embedded.end.php - font-size: 3em; +; font-size: 3em; // ^^^^^^^^^^^^^^^^^ text.html.php source.css.embedded // ^^^^^^^^^ support.type.property-name // ^ constant.numeric @@ -5881,6 +5881,27 @@ function foo() { // ^^^^^^^^^^^ source.php.embedded.css // ^^ punctuation.section.embedded.end.php + . { : ; } +// ^^^^^^^^^^^^^^^^ source.css.embedded.html - meta.property-list - meta.block +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.css.embedded.html meta.property-list.css meta.block.css +// ^ meta.selector.css entity.other.attribute-name.class.css punctuation.definition.entity.css +// ^^^^^^^^^^^^^^^ meta.selector.css entity.other.attribute-name.class.css meta.embedded.php +// ^^ punctuation.section.embedded.begin.php +// ^^^^^^^^^^^ source.php.embedded.css +// ^^ punctuation.section.embedded.end.php +// ^ punctuation.section.block.begin.css +// ^^^^^^^^^^^ meta.property-name.css support.type.property-name.css meta.embedded.php +// ^^ punctuation.section.embedded.begin.php +// ^^^^^^^ source.php.embedded.css +// ^^ punctuation.section.embedded.end.php +// ^ punctuation.separator.key-value.css +// ^^^^^^^^^^^^ meta.property-value.css meta.embedded.php +// ^^ punctuation.section.embedded.begin.php +// ^^^^^^^^ source.php.embedded.css +// ^^ punctuation.section.embedded.end.php +// ^ punctuation.terminator.rule.css +// ^ punctuation.section.block.end.css + .my--name:my--class { my--name: black } // <- meta.selector.css entity.other.attribute-name.class.css punctuation.definition.entity.css //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.css.embedded.html meta.selector.css diff --git a/Rails/tests/syntax_test_rails.css.erb b/Rails/tests/syntax_test_rails.css.erb index 53c96bc4ac..dec47e6c95 100644 --- a/Rails/tests/syntax_test_rails.css.erb +++ b/Rails/tests/syntax_test_rails.css.erb @@ -10,11 +10,13 @@ h1 { <% font %>: "Na <% @me %>"; /* ^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.property-list.css meta.block.css */ -/* ^^^^^^^^^^ meta.embedded.rails */ +/* ^^^^^^^^^^ meta.property-name.css support.type.property-name.css meta.embedded.rails */ /* ^ - meta.embedded */ /* ^^^^ meta.property-value.css meta.string.css - meta.interpolation */ /* ^^^^^^^^^ meta.property-value.css meta.string.css meta.interpolation.rails meta.embedded.rails */ /* ^ meta.property-value.css meta.string.css - meta.interpolation */ +/* ^^ punctuation.section.embedded.begin.rails */ +/* ^^ punctuation.section.embedded.end.rails */ /* ^ punctuation.separator.key-value.css */ /* ^^^^ string.quoted.double.css */ /* ^^ punctuation.section.embedded.begin.rails */ diff --git a/Rails/tests/syntax_test_rails.haml b/Rails/tests/syntax_test_rails.haml index f1c55514b3..53575c85fa 100644 --- a/Rails/tests/syntax_test_rails.haml +++ b/Rails/tests/syntax_test_rails.haml @@ -389,7 +389,7 @@ / <- meta.filter.haml meta.embedded.haml source.css.embedded.haml / <- meta.selector.css entity.name.tag.html.css / ^ meta.property-list.css meta.block.css punctuation.section.block.begin.css - color: red; + ; color: red; / ^^^^^ support.type.property-name.css / ^ punctuation.separator.key-value.css / ^^^ support.constant.color.w3c.standard.css diff --git a/Ruby/Embeddings/CSS (for Ruby).sublime-syntax b/Ruby/Embeddings/CSS (for Ruby).sublime-syntax index d613899011..d81ad98c81 100644 --- a/Ruby/Embeddings/CSS (for Ruby).sublime-syntax +++ b/Ruby/Embeddings/CSS (for Ruby).sublime-syntax @@ -6,6 +6,10 @@ hidden: true extends: Packages/CSS/CSS.sublime-syntax +variables: + + ident_start: (?:{{nmstart}}|#{) + contexts: prototype: diff --git a/Ruby/syntax_test_ruby.rb b/Ruby/syntax_test_ruby.rb index 3430c7380c..5803a7236d 100644 --- a/Ruby/syntax_test_ruby.rb +++ b/Ruby/syntax_test_ruby.rb @@ -140,9 +140,20 @@ @@ -173,10 +184,20 @@ def CssHeredoc() # ^^ meta.string.heredoc.ruby meta.tag.heredoc.ruby entity.name.tag.ruby .class[att=#{@ruby_sel}] { -# ^^^^^^^^^^^^ meta.string.heredoc.ruby source.css.embedded.ruby meta.selector.css meta.interpolation.ruby - - font-family: #{@ruby_font}; -# ^^^^^^^^^^^^^ meta.string.heredoc.ruby source.css.embedded.ruby meta.property-list.css meta.property-value.css meta.interpolation.ruby +# ^^^^^^^^^^^ meta.selector.css - meta.interpolation +# ^^^^^^^^^^^^ meta.selector.css meta.interpolation.ruby +# ^^ meta.selector.css + +; font-family: "#{@ruby_font}"; +# ^^^^^^^^^^^^^^^ meta.property-value.css meta.string.css +# ^ string.quoted.double.css punctuation.definition.string.begin.css +# ^^^^^^^^^^^^^ meta.interpolation.ruby +# ^ string.quoted.double.css punctuation.definition.string.end.css + +; #{@prop_name}: #{@prop_value}; +# ^^^^^^^^^^^^^ meta.property-name.css support.type.property-name.css meta.interpolation.ruby +# ^ punctuation.separator.key-value.css +# ^^^^^^^^^^^^^^ meta.property-value.css meta.interpolation.ruby } CSS # ^^^ meta.string.heredoc.ruby meta.tag.heredoc.ruby entity.name.tag.ruby