Push some broken code, added block nested groupings

pull/9549/head
Albert 3 years ago
parent 4d4d973622
commit b884daec36
No known key found for this signature in database
GPG Key ID: 639EBA2F18800B0E

@ -107,7 +107,8 @@ const css = {
'invalid-css-global-selector-list': () => 'invalid-css-global-selector-list': () =>
`:global(...) must not contain type or universal selectors when used in a compound selector`, `:global(...) must not contain type or universal selectors when used in a compound selector`,
'invalid-css-selector': () => `Invalid selector`, 'invalid-css-selector': () => `Invalid selector`,
'invalid-css-identifier': () => 'Expected a valid CSS identifier' 'invalid-css-identifier': () => 'Expected a valid CSS identifier',
'nesting-selector-not-allowed': () => 'Nesting selector is not allowed in top level rules',
}; };
/** @satisfies {Errors} */ /** @satisfies {Errors} */

@ -25,8 +25,8 @@ export default class Selector {
/** @type {import('./Stylesheet.js').default} */ /** @type {import('./Stylesheet.js').default} */
stylesheet; stylesheet;
/** @type {Block[]} */ /** @type {BlockUse[][]} */
blocks; block_groups;
/** @type {Block[]} */ /** @type {Block[]} */
local_blocks; local_blocks;
@ -37,12 +37,12 @@ export default class Selector {
/** /**
* @param {import('#compiler').Css.Selector} node * @param {import('#compiler').Css.Selector} node
* @param {import('./Stylesheet.js').default} stylesheet * @param {import('./Stylesheet.js').default} stylesheet
* @param {Selector | null} parent_selector * @param {BlockUse[][] | null} parent_blocks
*/ */
constructor(node, stylesheet, parent_selector) { constructor(node, stylesheet, parent_blocks) {
this.node = node; this.node = node;
this.stylesheet = stylesheet; this.stylesheet = stylesheet;
this.blocks = group_selectors(node, parent_selector); this.block_groups = group_selectors(node, parent_blocks);
// take trailing :global(...) selectors out of consideration // take trailing :global(...) selectors out of consideration
let i = this.blocks.length; let i = this.blocks.length;
while (i > 0) { while (i > 0) {
@ -846,13 +846,30 @@ class Block {
} }
/** @type {import('#compiler').Css.Combinator} */ /** @type {import('#compiler').Css.Combinator} */
const InvisibleCombinator = { const FakeCombinator = {
type: 'Combinator', type: 'Combinator',
name: ' ', name: ' ',
start: -1, start: -1,
end: -1 end: -1
}; };
class BlockUse {
/** @type {Block} */
block;
/** @type {boolean} */
visible;
/**
* @param {Block} block
* @param {boolean} visible
*/
constructor(block, visible) {
this.block = block;
this.visible = visible;
}
}
/** /**
* Groups selectors by combinator into blocks * Groups selectors by combinator into blocks
* *
@ -861,54 +878,74 @@ const InvisibleCombinator = {
* - Then insert the parent_selector's blocks at that position * - Then insert the parent_selector's blocks at that position
* *
* @param {import('#compiler').Css.Selector} selector * @param {import('#compiler').Css.Selector} selector
* @param {Selector | null} parent_selector * @param {BlockUse[][] | null} parent_blocks_group
*/ */
function group_selectors(selector, parent_selector) { function group_selectors(selector, parent_blocks_group) {
let block = new Block(null); // If it isn't a nested rule, then we add an empty block group
if (parent_blocks_group === null) {
return [
selector_to_blocks(selector, null, false).map((block) => new BlockUse(block, true))
];
}
// This is a nested rule, so we need to insert the parent selector's blocks at the position of the `&` selector
// or at the front if there is no `&` selector
// TODO: handle multiple `&` selectors
let nested_rule_index = selector.children.findIndex((child) => child.type === 'NestingSelector');
// if there is no `&` selector, we need to add a fake combinator at the start
if (nested_rule_index === -1) {
nested_rule_index = 0;
} else {
// if there is a `&` selector, we need to delete it
// and insert the parent's blocks there
selector.children.splice(nested_rule_index, 1);
}
const blocks = [block]; // Create the new blocks for the nested rule, visible by default
const blocks = selector_to_blocks(
selector,
nested_rule_index === 0 ? null : FakeCombinator,
false
);
const real_selectors_start = parent_selector?.node.children.length || 0; return parent_blocks_group.map(parent_blocks => {
// create a new block use for each parent block and set them to invisible
let parent_block_uses = parent_blocks.map((block_use) => new BlockUse(block_use.block, false));
if (parent_selector) { // Create a new block use for each block in the nested rule, set them to visible
const nested_rule_indices = selector.children let block_uses = blocks.map((block) => new BlockUse(block, true));
.map((child, index) => (child.type === 'NestingSelector' ? index : -1))
.filter((index) => index !== -1);
const parent_children = parent_selector.node.children.map((child) => ({ // insert the parent blocks at the position of the `&` selector
...child, block_uses.splice(nested_rule_index, 0, ...parent_block_uses);
invisible: true
})); return block_uses;
})
}
if (nested_rule_indices.length === 0) {
// if the next selector is a combinator, we must not unshift a child combinator
const next_is_combinator = selector.children[0]?.type === 'Combinator';
if (!next_is_combinator) {
selector.children.unshift(InvisibleCombinator);
}
selector.children.unshift(...parent_children);
} else {
// There's an & nesting selectors somewhere
// so we delete it and insert invisible parent's children there
nested_rule_indices.forEach((nested_rule_index) =>
selector.children.splice(nested_rule_index, 1, ...parent_children)
);
}
}
selector.children.forEach((child, i) => { /**
* @param {import('#compiler').Css.Selector} selector
* @param {import('../../../types/css.js').Combinator | null} combinator
* @param {boolean} allow_nesting
*/
function selector_to_blocks(selector, combinator, allow_nesting) {
let block = new Block(combinator);
const blocks = [block];
selector.children.forEach((child) => {
if (child.type === 'Combinator') { if (child.type === 'Combinator') {
block = new Block(child); block = new Block(child);
blocks.push(block); blocks.push(block);
} else if (child.type === 'NestingSelector') { } else if (child.type === 'NestingSelector') {
// Don't think we need to add it here if (!allow_nesting) {
error(child, 'nesting-selector-not-allowed');
} else { } else {
block.add(child); // We should've already removed the `&` selector
throw new Error("Unexpected nesting selector");
} }
if (real_selectors_start > i) { } else {
block.invisible = true; block.add(child);
} }
}); });
return blocks; return blocks;
} }

@ -74,8 +74,8 @@ class Rule {
this.parent = parent; this.parent = parent;
/** /**
* We need to add selectors for each parent rule's selectors * If there's a parent, we need to pass that parent's block_groups into the child
* because of CSS nesting. For example: * selector because of CSS nesting. For example:
* ```css * ```css
* .a, .b { * .a, .b {
* .c { * .c {
@ -88,11 +88,8 @@ class Rule {
* - .b .c * - .b .c
*/ */
if (parent && parent.node.type === 'Rule') { if (parent && parent.node.type === 'Rule') {
this.selectors = /** @type {Rule} **/ (parent).selectors let block_groups = /** @type {Rule} **/ (parent).selectors.map(selector => selector.block_groups).flat();
.map((parent_selector) => this.selectors = node.prelude.children.map(node => new Selector(node, stylesheet, block_groups));
node.prelude.children.map((node) => new Selector(node, stylesheet, parent_selector))
)
.flat();
} else { } else {
this.selectors = node.prelude.children.map((node) => new Selector(node, stylesheet, null)); this.selectors = node.prelude.children.map((node) => new Selector(node, stylesheet, null));
} }

Loading…
Cancel
Save