Implement full CSS nesting support

- parsing of at-rules
- parsing of element type selectors
pull/9549/head
Albert Marashi 3 years ago
parent dab01b107a
commit 05e959c77d

@ -10,6 +10,7 @@ const REGEX_PERCENTAGE = /^\d+(\.\d+)?%/;
const REGEX_WHITESPACE_OR_COLON = /[\s:]/; const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
const REGEX_BRACE_OR_SEMICOLON = /[{;]/; const REGEX_BRACE_OR_SEMICOLON = /[{;]/;
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/; const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/;
const REGEX_SEMICOLON_OR_OPEN_BRACE_OR_CLOSE_BRACE = /[;{}]/;
const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/; const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
const REGEX_COMMENT_CLOSE = /\*\//; const REGEX_COMMENT_CLOSE = /\*\//;
const REGEX_HTML_COMMENT_CLOSE = /-->/; const REGEX_HTML_COMMENT_CLOSE = /-->/;
@ -84,36 +85,23 @@ function read_at_rule(parser) {
let block = null; let block = null;
if (parser.match('{')) { if (parser.match('{')) {
// if the parser could easily distinguish between rules and declarations, this wouldn't be necessary. // // if the parser could easily distinguish between rules and declarations, this wouldn't be necessary.
// but this approach is much simpler. in future, when we support CSS nesting, the parser _will_ need // // but this approach is much simpler. in future, when we support CSS nesting, the parser _will_ need
// to be able to distinguish between them, but since we'll also need other changes to support that // // to be able to distinguish between them, but since we'll also need other changes to support that
// this remains a TODO // // this remains a TODO
const contains_declarations = [ // const contains_declarations = [
'color-profile', // 'color-profile',
'counter-style', // 'counter-style',
'font-face', // 'font-face',
'font-palette-values', // 'font-palette-values',
'page', // 'page',
'property' // 'property'
].includes(name); // ].includes(name);
if (contains_declarations) { // eg: `@media (max-width: 600px) { ... }`
block = read_block(parser); block = read_block(parser);
} else { } else {
const start = parser.index; // eg: `@import 'foo';`
parser.eat('{', true);
const children = read_body(parser, '}');
parser.eat('}', true);
block = {
type: 'Block',
start,
end: parser.index,
children
};
}
} else {
parser.eat(';', true); parser.eat(';', true);
} }
@ -129,15 +117,14 @@ function read_at_rule(parser) {
/** /**
* @param {import('../index.js').Parser} parser * @param {import('../index.js').Parser} parser
* @param {boolean} nested Whether this rule is nested inside another rule
* @returns {import('#compiler').Css.Rule} * @returns {import('#compiler').Css.Rule}
*/ */
function read_rule(parser, nested = false) { function read_rule(parser) {
const start = parser.index; const start = parser.index;
return { return {
type: 'Rule', type: 'Rule',
prelude: read_selector_list(parser, nested), prelude: read_selector_list(parser),
block: read_block(parser), block: read_block(parser),
start, start,
end: parser.index end: parser.index
@ -146,17 +133,16 @@ function read_rule(parser, nested = false) {
/** /**
* @param {import('../index.js').Parser} parser * @param {import('../index.js').Parser} parser
* @param {boolean} nested Whether this selector list is nested inside another rule
* @returns {import('#compiler').Css.SelectorList} * @returns {import('#compiler').Css.SelectorList}
*/ */
function read_selector_list(parser, nested) { function read_selector_list(parser) {
/** @type {import('#compiler').Css.Selector[]} */ /** @type {import('#compiler').Css.Selector[]} */
const children = []; const children = [];
const start = parser.index; const start = parser.index;
while (parser.index < parser.template.length) { while (parser.index < parser.template.length) {
children.push(read_selector(parser, nested)); children.push(read_selector(parser));
const end = parser.index; const end = parser.index;
@ -180,10 +166,9 @@ function read_selector_list(parser, nested) {
/** /**
* @param {import('../index.js').Parser} parser * @param {import('../index.js').Parser} parser
* @param {boolean} nested Whether this selector is nested inside another rule
* @returns {import('#compiler').Css.Selector} * @returns {import('#compiler').Css.Selector}
*/ */
function read_selector(parser, nested) { function read_selector(parser) {
const list_start = parser.index; const list_start = parser.index;
/** @type {Array<import('#compiler').Css.SimpleSelector | import('#compiler').Css.Combinator | import('#compiler').Css.NestingSelector>} */ /** @type {Array<import('#compiler').Css.SimpleSelector | import('#compiler').Css.Combinator | import('#compiler').Css.NestingSelector>} */
@ -192,7 +177,7 @@ function read_selector(parser, nested) {
while (parser.index < parser.template.length) { while (parser.index < parser.template.length) {
const start = parser.index; const start = parser.index;
if (nested && parser.eat('&')){ if (parser.eat('&')){
children.push({ children.push({
type: 'NestedSelector', type: 'NestedSelector',
name: '&', name: '&',
@ -338,7 +323,7 @@ function read_block(parser) {
parser.eat('{', true); parser.eat('{', true);
/** @type {Array<import('#compiler').Css.Declaration | import('#compiler').Css.Rule>} */ /** @type {Array<import('#compiler').Css.Declaration | import('#compiler').Css.Rule | import('#compiler').Css.Atrule>} */
const children = []; const children = [];
while (parser.index < parser.template.length) { while (parser.index < parser.template.length) {
@ -347,7 +332,7 @@ function read_block(parser) {
if (parser.match('}')) { if (parser.match('}')) {
break; break;
} else { } else {
children.push(read_declaration_or_rule(parser)); children.push(read_block_item(parser));
} }
} }
@ -391,24 +376,28 @@ function read_declaration(parser) {
} }
/** /**
* Reads a declaration, rule or at-rule
*
* @param {import('../index.js').Parser} parser * @param {import('../index.js').Parser} parser
* @returns {import('#compiler').Css.Declaration | import('#compiler').Css.Rule} * @returns {import('#compiler').Css.Declaration | import('#compiler').Css.Rule | import('#compiler').Css.Atrule}
*/ */
function read_declaration_or_rule(parser) { function read_block_item(parser) {
// We need to know if this is a rule or a declaration if (parser.match('@')) {
// so we'll attempt to read an identifier first return read_at_rule(parser);
// if we can't, we'll assume it's a rule }
// This needs to change when we support type (element) selectors
// due to complexities with https://bugs.chromium.org/p/chromium/issues/detail?id=1427259
const start = parser.index; const start = parser.index;
const is_ident = !!parser.read(REGEX_VALID_IDENTIFIER_CHAR); parser.read_until(REGEX_SEMICOLON_OR_OPEN_BRACE_OR_CLOSE_BRACE);
// if we've run into a '{', it's a rule, otherwise we ran into
// a ';' or '}' so it's a declaration
if (parser.match('{')) {
// Rewind to the start of the rule
parser.index = start; parser.index = start;
return read_rule(parser);
if (!is_ident) {
return read_rule(parser, true);
} }
// Rewind to the start of the declaration
parser.index = start;
return read_declaration(parser); return read_declaration(parser);
} }

@ -172,7 +172,7 @@ export default class Selector {
validate_invalid_combinator_without_selector(analysis) { validate_invalid_combinator_without_selector(analysis) {
for (let i = 0; i < this.blocks.length; i++) { for (let i = 0; i < this.blocks.length; i++) {
const block = this.blocks[i]; const block = this.blocks[i];
if (block.selectors.length === 0) { if (block.selectors.length === 0 && !block.nested) {
error(this.node, 'invalid-css-selector'); error(this.node, 'invalid-css-selector');
} }
} }

@ -0,0 +1,9 @@
button.svelte-xyz {
color: red;
.xyz.svelte-xyz & {
color: yellow;
}
color: black;
}

@ -0,0 +1,16 @@
<div class="xyz">
<button>
</button>
</div>
<style>
button {
color: red;
.xyz & {
color: yellow;
}
color: black;
}
</style>

@ -0,0 +1,12 @@
button.svelte-xyz {
color: red;
@media (max-width: 500px) {
color: blue;
.xyz.svelte-xyz {
color: yellow;
}
}
color: black;
}

@ -0,0 +1,20 @@
<button>
Text
<div class="xyz">
Some text
</div>
</button>
<style>
button {
color: red;
@media (max-width: 500px) {
color: blue;
.xyz {
color: yellow;
}
}
color: black;
}
</style>

@ -0,0 +1,7 @@
div.svelte-xyz {
color: red;
button.svelte-xyz {
color: yellow;
}
}

@ -0,0 +1,14 @@
<div>
<button>
Test
</button>
</div>
<style>
div {
color: red;
button {
color: yellow;
}
}
</style>

@ -1,6 +1,5 @@
div.svelte-xyz { div.svelte-xyz {
/* DISABLED THIS FOR CSS NESTING */ @apply --funky-div;
/* @apply --funky-div; */
color: red; color: red;
} }

@ -2,8 +2,7 @@
<style> <style>
div { div {
/* DISABLED THIS FOR CSS NESTING */ @apply --funky-div;
/* @apply --funky-div; */
color: red; color: red;
} }

@ -1,5 +1,4 @@
div.svelte-xyz { div.svelte-xyz {
/* DISABLED THIS FOR CSS NESTING */ @apply --funky-div;
/* @apply --funky-div; */
color: red; color: red;
} }

@ -2,8 +2,7 @@
<style> <style>
div { div {
/* DISABLED THIS FOR CSS NESTING */ @apply --funky-div;
/* @apply --funky-div; */
color: red; color: red;
} }
</style> </style>
Loading…
Cancel
Save