Skip to content

Commit

Permalink
[CSS] Add nested rules support (#3785)
Browse files Browse the repository at this point in the history
* [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.
  • Loading branch information
deathaxe committed Jul 15, 2023
1 parent 8cdff4d commit dd81bb8
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 31 deletions.
2 changes: 1 addition & 1 deletion ASP/syntax_test_asp.asp
Expand Up @@ -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
Expand Down
24 changes: 21 additions & 3 deletions CSS/CSS.sublime-syntax
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1117,6 +1125,8 @@ contexts:
pop: 1

selector-variables:
- match: \&
scope: variable.language.parent.css
- match: \*
scope: variable.language.wildcard.asterisk.css

Expand Down Expand Up @@ -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
Expand Down
138 changes: 128 additions & 10 deletions CSS/syntax_test_css.css
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
10 changes: 7 additions & 3 deletions HTML/syntax_test_html.html
Expand Up @@ -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
}
--> </style>
## <- comment.block.html punctuation.definition.comment.end.html
Expand Down
2 changes: 1 addition & 1 deletion Haskell/tests/syntax_test_haskell.hs
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Java/tests/syntax_test_jsp.jsp
Expand Up @@ -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
Expand All @@ -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
Expand Down
25 changes: 23 additions & 2 deletions PHP/tests/syntax_test_php.php
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -5881,6 +5881,27 @@ function foo() {
// ^^^^^^^^^^^ source.php.embedded.css
// ^^ punctuation.section.embedded.end.php

.<? $selector ?> { <? $attr ?>: <? $value ?>; }
// ^^^^^^^^^^^^^^^^ 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-<?php echo $class;?>-name:my-<?php echo $class;?>-class { my-<?php echo $class;?>-name: black }
// <- meta.selector.css entity.other.attribute-name.class.css punctuation.definition.entity.css
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ source.css.embedded.html meta.selector.css
Expand Down
4 changes: 3 additions & 1 deletion Rails/tests/syntax_test_rails.css.erb
Expand Up @@ -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 */
Expand Down
2 changes: 1 addition & 1 deletion Rails/tests/syntax_test_rails.haml
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions Ruby/Embeddings/CSS (for Ruby).sublime-syntax
Expand Up @@ -6,6 +6,10 @@ hidden: true

extends: Packages/CSS/CSS.sublime-syntax

variables:

ident_start: (?:{{nmstart}}|#{)

contexts:

prototype:
Expand Down

0 comments on commit dd81bb8

Please sign in to comment.