Skip to content

chore: tidy up CSS stuff #10478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/svelte/src/compiler/phases/1-parse/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class Parser {
throw new TypeError('Template must be a string');
}

this.template = template.trimRight();
this.template = template.trimEnd();

let match_lang;

Expand Down Expand Up @@ -147,9 +147,9 @@ export class Parser {

/**
* @param {string} str
* @param {boolean} [required]
* @param {boolean} required
*/
eat(str, required) {
eat(str, required = false) {
if (this.match(str)) {
this.index += str.length;
return true;
Expand Down
101 changes: 54 additions & 47 deletions packages/svelte/src/compiler/phases/2-analyze/css/Selector.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { get_possible_values } from './gather_possible_values.js';
import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../patterns.js';
import { error } from '../../../errors.js';
import { Stylesheet } from './Stylesheet.js';

const NO_MATCH = 'NO_MATCH';
const POSSIBLE_MATCH = 'POSSIBLE_MATCH';
Expand All @@ -22,7 +23,7 @@ export default class Selector {
/** @type {import('#compiler').Css.Selector} */
node;

/** @type {import('./Stylesheet.js').default} */
/** @type {import('./Stylesheet.js').Stylesheet} */
stylesheet;

/** @type {Block[]} */
Expand All @@ -31,39 +32,28 @@ export default class Selector {
/** @type {Block[]} */
local_blocks;

/** @type {boolean} */
used;
used = false;

/**
* @param {import('#compiler').Css.Selector} node
* @param {import('./Stylesheet.js').default} stylesheet
* @param {import('./Stylesheet.js').Stylesheet} stylesheet
*/
constructor(node, stylesheet) {
this.node = node;
this.stylesheet = stylesheet;
this.blocks = group_selectors(node);
// take trailing :global(...) selectors out of consideration
let i = this.blocks.length;
while (i > 0) {
if (!this.blocks[i - 1].global) break;
i -= 1;
}
this.local_blocks = this.blocks.slice(0, i);
const host_only = this.blocks.length === 1 && this.blocks[0].host;
const root_only = this.blocks.length === 1 && this.blocks[0].root;
this.used = this.local_blocks.length === 0 || host_only || root_only;
const i = this.blocks.findLastIndex((block) => !block.can_ignore());
this.local_blocks = this.blocks.slice(0, i + 1);

// if we have a `:root {...}` or `:global(...) {...}` selector, we need to mark
// this selector as `used` even if the component doesn't contain any nodes
this.used = this.local_blocks.length === 0;
}

/** @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node */
apply(node) {
/** @type {Array<{ node: import('#compiler').RegularElement | import('#compiler').SvelteElement; block: Block }>} */
const to_encapsulate = [];
apply_selector(this.local_blocks.slice(), node, to_encapsulate);
if (to_encapsulate.length > 0) {
to_encapsulate.forEach(({ node, block }) => {
this.stylesheet.nodes_with_css_class.add(node);
block.should_encapsulate = true;
});
if (apply_selector(this.local_blocks.slice(), node, this.stylesheet)) {
this.used = true;
}
}
Expand Down Expand Up @@ -130,6 +120,13 @@ export default class Selector {

/** @param {import('../../types.js').ComponentAnalysis} analysis */
validate(analysis) {
this.validate_global_placement();
this.validate_global_with_multiple_selectors();
this.validate_global_compound_selector();
this.validate_invalid_combinator_without_selector(analysis);
}

validate_global_placement() {
let start = 0;
let end = this.blocks.length;
for (; start < end; start += 1) {
Expand All @@ -143,9 +140,6 @@ export default class Selector {
error(this.blocks[i].selectors[0], 'invalid-css-global-placement');
}
}
this.validate_global_with_multiple_selectors();
this.validate_global_compound_selector();
this.validate_invalid_combinator_without_selector(analysis);
}

validate_global_with_multiple_selectors() {
Expand Down Expand Up @@ -207,10 +201,10 @@ export default class Selector {
/**
* @param {Block[]} blocks
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement | null} node
* @param {Array<{ node: import('#compiler').RegularElement | import('#compiler').SvelteElement; block: Block }>} to_encapsulate
* @param {Stylesheet} stylesheet
* @returns {boolean}
*/
function apply_selector(blocks, node, to_encapsulate) {
function apply_selector(blocks, node, stylesheet) {
const block = blocks.pop();
if (!block) return false;
if (!node) {
Expand All @@ -224,43 +218,52 @@ function apply_selector(blocks, node, to_encapsulate) {
return false;
}

if (applies === UNKNOWN_SELECTOR) {
to_encapsulate.push({ node, block });
/**
* Mark both the compound selector and the node it selects as encapsulated,
* for transformation in a later step
* @param {Block} block
* @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node
*/
function mark(block, node) {
block.should_encapsulate = true;
stylesheet.nodes_with_css_class.add(node);
return true;
}

if (applies === UNKNOWN_SELECTOR) {
return mark(block, node);
}

if (block.combinator) {
if (block.combinator.type === 'Combinator' && block.combinator.name === ' ') {
for (const ancestor_block of blocks) {
if (ancestor_block.global) {
continue;
}
if (ancestor_block.host) {
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
/** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | null} */
let parent = node;
let matched = false;
while ((parent = get_element_parent(parent))) {
if (block_might_apply_to_node(ancestor_block, parent) !== NO_MATCH) {
to_encapsulate.push({ node: parent, block: ancestor_block });
mark(ancestor_block, parent);
matched = true;
}
}
if (to_encapsulate.length) {
to_encapsulate.push({ node, block });
return true;
if (matched) {
return mark(block, node);
}
}
if (blocks.every((block) => block.global)) {
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
return false;
} else if (block.combinator.name === '>') {
const has_global_parent = blocks.every((block) => block.global);
if (has_global_parent || apply_selector(blocks, get_element_parent(node), to_encapsulate)) {
to_encapsulate.push({ node, block });
return true;
if (has_global_parent || apply_selector(blocks, get_element_parent(node), stylesheet)) {
return mark(block, node);
}
return false;
} else if (block.combinator.name === '+' || block.combinator.name === '~') {
Expand All @@ -274,23 +277,22 @@ function apply_selector(blocks, node, to_encapsulate) {
if (siblings.size === 0 && get_element_parent(node) !== null) {
return false;
}
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
for (const possible_sibling of siblings.keys()) {
if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) {
to_encapsulate.push({ node, block });
if (apply_selector(blocks.slice(), possible_sibling, stylesheet)) {
mark(block, node);
has_match = true;
}
}
return has_match;
}

// TODO other combinators
to_encapsulate.push({ node, block });
return true;
return mark(block, node);
}
to_encapsulate.push({ node, block });
return true;

return mark(block, node);
}

const regex_backslash_and_following_character = /\\(.)/g;
Expand Down Expand Up @@ -815,6 +817,11 @@ class Block {
this.selectors.push(selector);
this.end = selector.end;
}

can_ignore() {
return this.global || this.host || this.root;
}

get global() {
return (
this.selectors.length >= 1 &&
Expand Down
Loading