From d65d67a60ca50d4b0e5da2af76c6f2717c3f8a5e Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:14:20 -0500 Subject: [PATCH 1/9] move files --- .../{phases/2-analyze => }/css/Selector.js | 10 +++++----- .../{phases/2-analyze => }/css/Stylesheet.js | 13 ++++++------- .../gather_possible_values.js => css/utils.js} | 15 +++++++++++++++ .../svelte/src/compiler/phases/2-analyze/index.js | 2 +- packages/svelte/src/compiler/phases/types.d.ts | 2 +- 5 files changed, 28 insertions(+), 14 deletions(-) rename packages/svelte/src/compiler/{phases/2-analyze => }/css/Selector.js (98%) rename packages/svelte/src/compiler/{phases/2-analyze => }/css/Stylesheet.js (96%) rename packages/svelte/src/compiler/{phases/2-analyze/css/gather_possible_values.js => css/utils.js} (71%) diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js b/packages/svelte/src/compiler/css/Selector.js similarity index 98% rename from packages/svelte/src/compiler/phases/2-analyze/css/Selector.js rename to packages/svelte/src/compiler/css/Selector.js index 098b632e84fb..6f1a35c1c5c3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/Selector.js +++ b/packages/svelte/src/compiler/css/Selector.js @@ -1,6 +1,6 @@ -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 { get_possible_values } from './utils.js'; +import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../phases/patterns.js'; +import { error } from '../errors.js'; import { Stylesheet } from './Stylesheet.js'; const NO_MATCH = 'NO_MATCH'; @@ -118,7 +118,7 @@ export default class Selector { } } - /** @param {import('../../types.js').ComponentAnalysis} analysis */ + /** @param {import('../phases/types.js').ComponentAnalysis} analysis */ validate(analysis) { this.validate_global_placement(); this.validate_global_with_multiple_selectors(); @@ -161,7 +161,7 @@ export default class Selector { } } - /** @param {import('../../types.js').ComponentAnalysis} analysis */ + /** @param {import('../phases/types.js').ComponentAnalysis} analysis */ validate_invalid_combinator_without_selector(analysis) { for (let i = 0; i < this.blocks.length; i++) { const block = this.blocks[i]; diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js b/packages/svelte/src/compiler/css/Stylesheet.js similarity index 96% rename from packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js rename to packages/svelte/src/compiler/css/Stylesheet.js index 84914485a0e5..eacb5cc1f169 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js +++ b/packages/svelte/src/compiler/css/Stylesheet.js @@ -1,11 +1,10 @@ import MagicString from 'magic-string'; import { walk } from 'zimmerframe'; import Selector from './Selector.js'; -import hash from '../utils/hash.js'; +import { hash } from './utils.js'; // import compiler_warnings from '../compiler_warnings.js'; // import { extract_ignores_above_position } from '../utils/extract_svelte_ignore.js'; -import { push_array } from '../utils/push_array.js'; -import { create_attribute } from '../../nodes.js'; +import { create_attribute } from '../phases/nodes.js'; // TODO move this const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/; const regex_name_boundary = /^[\s,;}]$/; @@ -116,7 +115,7 @@ class Rule { this.declarations.forEach((declaration) => declaration.transform(code, keyframes)); } - /** @param {import('../../types.js').ComponentAnalysis} analysis */ + /** @param {import('../phases/types.js').ComponentAnalysis} analysis */ validate(analysis) { this.selectors.forEach((selector) => { selector.validate(analysis); @@ -310,7 +309,7 @@ class Atrule { }); } - /** @param {import('../../types.js').ComponentAnalysis} analysis */ + /** @param {import('../phases/types.js').ComponentAnalysis} analysis */ validate(analysis) { this.children.forEach((child) => { child.validate(analysis); @@ -511,14 +510,14 @@ export class Stylesheet { }; } - /** @param {import('../../types.js').ComponentAnalysis} analysis */ + /** @param {import('../phases/types.js').ComponentAnalysis} analysis */ validate(analysis) { this.children.forEach((child) => { child.validate(analysis); }); } - /** @param {import('../../types.js').ComponentAnalysis} analysis */ + /** @param {import('../phases/types.js').ComponentAnalysis} analysis */ warn_on_unused_selectors(analysis) { // const ignores = !this.ast // ? [] diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/gather_possible_values.js b/packages/svelte/src/compiler/css/utils.js similarity index 71% rename from packages/svelte/src/compiler/phases/2-analyze/css/gather_possible_values.js rename to packages/svelte/src/compiler/css/utils.js index 893ed5871bea..d3554c343713 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/gather_possible_values.js +++ b/packages/svelte/src/compiler/css/utils.js @@ -1,3 +1,18 @@ +const regex_return_characters = /\r/g; + +/** + * @param {string} str + * @returns {string} + */ +export function hash(str) { + str = str.replace(regex_return_characters, ''); + let hash = 5381; + let i = str.length; + + while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); + return (hash >>> 0).toString(36); +} + const UNKNOWN = {}; /** diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index e8384931f79a..7ecde4b001b9 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -13,7 +13,7 @@ import * as b from '../../utils/builders.js'; import { ReservedKeywords, Runes, SVGElements } from '../constants.js'; import { Scope, ScopeRoot, create_scopes, get_rune, set_scope } from '../scope.js'; import { merge } from '../visitors.js'; -import { Stylesheet } from './css/Stylesheet.js'; +import { Stylesheet } from '../../css/Stylesheet.js'; import { validation_legacy, validation_runes, validation_runes_js } from './validation.js'; import { warn } from '../../warnings.js'; import check_graph_for_cycles from './utils/check_graph_for_cycles.js'; diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 0a92486c85e5..c8397e371ea3 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -7,7 +7,7 @@ import type { SvelteOptions } from '#compiler'; import type { Identifier, LabeledStatement, Program } from 'estree'; -import { Stylesheet } from './2-analyze/css/Stylesheet.js'; +import { Stylesheet } from '../css/Stylesheet.js'; import type { Scope, ScopeRoot } from './scope.js'; export interface Js { From 46f43402112b49d36875d18463d4441e4855cb6b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:17:47 -0500 Subject: [PATCH 2/9] move types --- packages/svelte/src/compiler/{types/css.d.ts => css/types.d.ts} | 2 +- packages/svelte/src/compiler/types/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/svelte/src/compiler/{types/css.d.ts => css/types.d.ts} (97%) diff --git a/packages/svelte/src/compiler/types/css.d.ts b/packages/svelte/src/compiler/css/types.d.ts similarity index 97% rename from packages/svelte/src/compiler/types/css.d.ts rename to packages/svelte/src/compiler/css/types.d.ts index 7258e7748c29..67b303430ff7 100644 --- a/packages/svelte/src/compiler/types/css.d.ts +++ b/packages/svelte/src/compiler/css/types.d.ts @@ -1,4 +1,4 @@ -import type { Style } from './template'; +import type { Style } from '../types/template'; export interface BaseNode { start: number; diff --git a/packages/svelte/src/compiler/types/index.d.ts b/packages/svelte/src/compiler/types/index.d.ts index 83197354e2d4..8559254b72a5 100644 --- a/packages/svelte/src/compiler/types/index.d.ts +++ b/packages/svelte/src/compiler/types/index.d.ts @@ -10,7 +10,7 @@ import type { Location } from 'locate-character'; import type { SourceMap } from 'magic-string'; import type { Context } from 'zimmerframe'; import type { Scope } from '../phases/scope.js'; -import * as Css from './css.js'; +import * as Css from '../css/types.js'; import type { EachBlock, Namespace, SvelteNode } from './template.js'; /** The return value of `compile` from `svelte/compiler` */ From 51a664116e948c767899afab995d01214aa7f554 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:21:58 -0500 Subject: [PATCH 3/9] rename Selector -> ComplexSelector --- packages/svelte/src/compiler/css/Selector.js | 2 +- packages/svelte/src/compiler/css/Stylesheet.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/svelte/src/compiler/css/Selector.js b/packages/svelte/src/compiler/css/Selector.js index 6f1a35c1c5c3..96811acc0e2b 100644 --- a/packages/svelte/src/compiler/css/Selector.js +++ b/packages/svelte/src/compiler/css/Selector.js @@ -19,7 +19,7 @@ const whitelist_attribute_selector = new Map([ ['dialog', new Set(['open'])] ]); -export default class Selector { +export class ComplexSelector { /** @type {import('#compiler').Css.Selector} */ node; diff --git a/packages/svelte/src/compiler/css/Stylesheet.js b/packages/svelte/src/compiler/css/Stylesheet.js index eacb5cc1f169..e632a3692e7c 100644 --- a/packages/svelte/src/compiler/css/Stylesheet.js +++ b/packages/svelte/src/compiler/css/Stylesheet.js @@ -1,6 +1,6 @@ import MagicString from 'magic-string'; import { walk } from 'zimmerframe'; -import Selector from './Selector.js'; +import { ComplexSelector } from './Selector.js'; import { hash } from './utils.js'; // import compiler_warnings from '../compiler_warnings.js'; // import { extract_ignores_above_position } from '../utils/extract_svelte_ignore.js'; @@ -48,7 +48,7 @@ function escape_comment_close(node, code) { } class Rule { - /** @type {import('./Selector.js').default[]} */ + /** @type {ComplexSelector[]} */ selectors; /** @type {import('#compiler').Css.Rule} */ @@ -68,7 +68,7 @@ class Rule { constructor(node, stylesheet, parent) { this.node = node; this.parent = parent; - this.selectors = node.prelude.children.map((node) => new Selector(node, stylesheet)); + this.selectors = node.prelude.children.map((node) => new ComplexSelector(node, stylesheet)); this.declarations = /** @type {import('#compiler').Css.Declaration[]} */ ( node.block.children @@ -122,7 +122,7 @@ class Rule { }); } - /** @param {(selector: import('./Selector.js').default) => void} handler */ + /** @param {(selector: ComplexSelector) => void} handler */ warn_on_unused_selector(handler) { this.selectors.forEach((selector) => { if (!selector.used) handler(selector); @@ -316,7 +316,7 @@ class Atrule { }); } - /** @param {(selector: import('./Selector.js').default) => void} handler */ + /** @param {(selector: ComplexSelector) => void} handler */ warn_on_unused_selector(handler) { if (this.node.name !== 'media') return; this.children.forEach((child) => { From b79ad7d951f90d8ba9787d395f0e96ca33188f8b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:23:10 -0500 Subject: [PATCH 4/9] ditto --- packages/svelte/src/compiler/css/Selector.js | 6 +++--- packages/svelte/src/compiler/css/types.d.ts | 4 ++-- packages/svelte/src/compiler/phases/1-parse/read/style.js | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/compiler/css/Selector.js b/packages/svelte/src/compiler/css/Selector.js index 96811acc0e2b..5b19c878bade 100644 --- a/packages/svelte/src/compiler/css/Selector.js +++ b/packages/svelte/src/compiler/css/Selector.js @@ -20,7 +20,7 @@ const whitelist_attribute_selector = new Map([ ]); export class ComplexSelector { - /** @type {import('#compiler').Css.Selector} */ + /** @type {import('#compiler').Css.ComplexSelector} */ node; /** @type {import('./Stylesheet.js').Stylesheet} */ @@ -35,7 +35,7 @@ export class ComplexSelector { used = false; /** - * @param {import('#compiler').Css.Selector} node + * @param {import('#compiler').Css.ComplexSelector} node * @param {import('./Stylesheet.js').Stylesheet} stylesheet */ constructor(node, stylesheet) { @@ -835,7 +835,7 @@ class Block { } } -/** @param {import('#compiler').Css.Selector} selector */ +/** @param {import('#compiler').Css.ComplexSelector} selector */ function group_selectors(selector) { let block = new Block(null); const blocks = [block]; diff --git a/packages/svelte/src/compiler/css/types.d.ts b/packages/svelte/src/compiler/css/types.d.ts index 67b303430ff7..f275e114ba96 100644 --- a/packages/svelte/src/compiler/css/types.d.ts +++ b/packages/svelte/src/compiler/css/types.d.ts @@ -20,10 +20,10 @@ export interface Rule extends BaseNode { export interface SelectorList extends BaseNode { type: 'SelectorList'; - children: Selector[]; + children: ComplexSelector[]; } -export interface Selector extends BaseNode { +export interface ComplexSelector extends BaseNode { type: 'Selector'; children: Array; } diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index ff9f9a8fecca..8f30b5516241 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -149,7 +149,7 @@ function read_rule(parser) { * @returns {import('#compiler').Css.SelectorList} */ function read_selector_list(parser, inside_pseudo_class = false) { - /** @type {import('#compiler').Css.Selector[]} */ + /** @type {import('#compiler').Css.ComplexSelector[]} */ const children = []; allow_comment_or_whitespace(parser); @@ -182,7 +182,7 @@ function read_selector_list(parser, inside_pseudo_class = false) { /** * @param {import('../index.js').Parser} parser * @param {boolean} [inside_pseudo_class] - * @returns {import('#compiler').Css.Selector} + * @returns {import('#compiler').Css.ComplexSelector} */ function read_selector(parser, inside_pseudo_class = false) { const list_start = parser.index; From a30a12cc51b6cd644cc52fca5b1ed8f82cce3e01 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:34:02 -0500 Subject: [PATCH 5/9] rename block -> relative_selector --- packages/svelte/src/compiler/css/Selector.js | 224 +++++++++++-------- 1 file changed, 132 insertions(+), 92 deletions(-) diff --git a/packages/svelte/src/compiler/css/Selector.js b/packages/svelte/src/compiler/css/Selector.js index 5b19c878bade..1d1990840797 100644 --- a/packages/svelte/src/compiler/css/Selector.js +++ b/packages/svelte/src/compiler/css/Selector.js @@ -26,11 +26,16 @@ export class ComplexSelector { /** @type {import('./Stylesheet.js').Stylesheet} */ stylesheet; - /** @type {Block[]} */ - blocks; + /** @type {RelativeSelector[]} */ + relative_selectors; - /** @type {Block[]} */ - local_blocks; + /** + * The `relative_selectors`, minus any trailing global selectors + * (which includes `:root` and `:host`) since we ignore these + * when determining if a selector is used. + * @type {RelativeSelector[]} + */ + local_relative_selectors; used = false; @@ -41,19 +46,23 @@ export class ComplexSelector { constructor(node, stylesheet) { this.node = node; this.stylesheet = stylesheet; - this.blocks = group_selectors(node); + + this.relative_selectors = group_selectors(node); + // take trailing :global(...) selectors out of consideration - const i = this.blocks.findLastIndex((block) => !block.can_ignore()); - this.local_blocks = this.blocks.slice(0, i + 1); + const i = this.relative_selectors.findLastIndex( + (relative_selector) => !relative_selector.can_ignore() + ); + this.local_relative_selectors = this.relative_selectors.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; + this.used = this.local_relative_selectors.length === 0; } /** @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node */ apply(node) { - if (apply_selector(this.local_blocks.slice(), node, this.stylesheet)) { + if (apply_selector(this.local_relative_selectors.slice(), node, this.stylesheet)) { this.used = true; } } @@ -71,19 +80,19 @@ export class ComplexSelector { } /** - * @param {Block} block + * @param {RelativeSelector} relative_selector * @param {string} modifier */ - function encapsulate_block(block, modifier) { - for (const selector of block.selectors) { + function encapsulate_block(relative_selector, modifier) { + for (const selector of relative_selector.selectors) { if (selector.type === 'PseudoClassSelector' && selector.name === 'global') { remove_global_pseudo_class(selector); } } - let i = block.selectors.length; + let i = relative_selector.selectors.length; while (i--) { - const selector = block.selectors[i]; + const selector = relative_selector.selectors[i]; if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') { if (selector.name !== 'root' && selector.name !== 'host') { @@ -103,16 +112,16 @@ export class ComplexSelector { } let first = true; - for (const block of this.blocks) { - if (block.global) { - remove_global_pseudo_class(block.selectors[0]); + for (const relative_selector of this.relative_selectors) { + if (relative_selector.global) { + remove_global_pseudo_class(relative_selector.selectors[0]); } - if (block.should_encapsulate) { + if (relative_selector.should_encapsulate) { // for the first occurrence, we use a classname selector, so that every // encapsulated selector gets a +0-1-0 specificity bump. thereafter, // we use a `:where` selector, which does not affect specificity - encapsulate_block(block, first ? modifier : `:where(${modifier})`); + encapsulate_block(relative_selector, first ? modifier : `:where(${modifier})`); first = false; } } @@ -128,27 +137,27 @@ export class ComplexSelector { validate_global_placement() { let start = 0; - let end = this.blocks.length; + let end = this.relative_selectors.length; for (; start < end; start += 1) { - if (!this.blocks[start].global) break; + if (!this.relative_selectors[start].global) break; } for (; end > start; end -= 1) { - if (!this.blocks[end - 1].global) break; + if (!this.relative_selectors[end - 1].global) break; } for (let i = start; i < end; i += 1) { - if (this.blocks[i].global) { - error(this.blocks[i].selectors[0], 'invalid-css-global-placement'); + if (this.relative_selectors[i].global) { + error(this.relative_selectors[i].selectors[0], 'invalid-css-global-placement'); } } } validate_global_with_multiple_selectors() { - if (this.blocks.length === 1 && this.blocks[0].selectors.length === 1) { + if (this.relative_selectors.length === 1 && this.relative_selectors[0].selectors.length === 1) { // standalone :global() with multiple selectors is OK return; } - for (const block of this.blocks) { - for (const selector of block.selectors) { + for (const relative_selector of this.relative_selectors) { + for (const selector of relative_selector.selectors) { if ( selector.type === 'PseudoClassSelector' && selector.name === 'global' && @@ -163,20 +172,20 @@ export class ComplexSelector { /** @param {import('../phases/types.js').ComponentAnalysis} analysis */ validate_invalid_combinator_without_selector(analysis) { - for (let i = 0; i < this.blocks.length; i++) { - const block = this.blocks[i]; - if (block.selectors.length === 0) { + for (let i = 0; i < this.relative_selectors.length; i++) { + const relative_selector = this.relative_selectors[i]; + if (relative_selector.selectors.length === 0) { error(this.node, 'invalid-css-selector'); } } } validate_global_compound_selector() { - for (const block of this.blocks) { - if (block.selectors.length === 1) continue; + for (const relative_selector of this.relative_selectors) { + if (relative_selector.selectors.length === 1) continue; - for (let i = 0; i < block.selectors.length; i++) { - const selector = block.selectors[i]; + for (let i = 0; i < relative_selector.selectors.length; i++) { + const selector = relative_selector.selectors[i]; if (selector.type === 'PseudoClassSelector' && selector.name === 'global') { const child = selector.args?.children[0].children[0]; @@ -184,7 +193,7 @@ export class ComplexSelector { child?.type === 'TypeSelector' && !/[.:#]/.test(child.name[0]) && (i !== 0 || - block.selectors + relative_selector.selectors .slice(1) .some( (s) => s.type !== 'PseudoElementSelector' && s.type !== 'PseudoClassSelector' @@ -199,20 +208,22 @@ export class ComplexSelector { } /** - * @param {Block[]} blocks + * @param {RelativeSelector[]} relative_selectors * @param {import('#compiler').RegularElement | import('#compiler').SvelteElement | null} node * @param {Stylesheet} stylesheet * @returns {boolean} */ -function apply_selector(blocks, node, stylesheet) { - const block = blocks.pop(); - if (!block) return false; +function apply_selector(relative_selectors, node, stylesheet) { + const relative_selector = relative_selectors.pop(); + if (!relative_selector) return false; if (!node) { return ( - (block.global && blocks.every((block) => block.global)) || (block.host && blocks.length === 0) + (relative_selector.global && + relative_selectors.every((relative_selector) => relative_selector.global)) || + (relative_selector.host && relative_selectors.length === 0) ); } - const applies = block_might_apply_to_node(block, node); + const applies = block_might_apply_to_node(relative_selector, node); if (applies === NO_MATCH) { return false; @@ -221,27 +232,30 @@ function apply_selector(blocks, node, stylesheet) { /** * Mark both the compound selector and the node it selects as encapsulated, * for transformation in a later step - * @param {Block} block + * @param {RelativeSelector} relative_selector * @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node */ - function mark(block, node) { - block.should_encapsulate = true; + function mark(relative_selector, node) { + relative_selector.should_encapsulate = true; stylesheet.nodes_with_css_class.add(node); return true; } if (applies === UNKNOWN_SELECTOR) { - return mark(block, node); + return mark(relative_selector, node); } - if (block.combinator) { - if (block.combinator.type === 'Combinator' && block.combinator.name === ' ') { - for (const ancestor_block of blocks) { + if (relative_selector.combinator) { + if ( + relative_selector.combinator.type === 'Combinator' && + relative_selector.combinator.name === ' ' + ) { + for (const ancestor_block of relative_selectors) { if (ancestor_block.global) { continue; } if (ancestor_block.host) { - return mark(block, node); + return mark(relative_selector, node); } /** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | null} */ let parent = node; @@ -253,35 +267,46 @@ function apply_selector(blocks, node, stylesheet) { } } if (matched) { - return mark(block, node); + return mark(relative_selector, node); } } - if (blocks.every((block) => block.global)) { - return mark(block, node); + if (relative_selectors.every((relative_selector) => relative_selector.global)) { + return mark(relative_selector, 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), stylesheet)) { - return mark(block, node); + } else if (relative_selector.combinator.name === '>') { + const has_global_parent = relative_selectors.every( + (relative_selector) => relative_selector.global + ); + if ( + has_global_parent || + apply_selector(relative_selectors, get_element_parent(node), stylesheet) + ) { + return mark(relative_selector, node); } return false; - } else if (block.combinator.name === '+' || block.combinator.name === '~') { - const siblings = get_possible_element_siblings(node, block.combinator.name === '+'); + } else if ( + relative_selector.combinator.name === '+' || + relative_selector.combinator.name === '~' + ) { + const siblings = get_possible_element_siblings( + node, + relative_selector.combinator.name === '+' + ); let has_match = false; // NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the // css-tree limitation that does not parse the inner selector of :global // so unless we are sure there will be no sibling to match, we will consider it as matched - const has_global = blocks.some((block) => block.global); + const has_global = relative_selectors.some((relative_selector) => relative_selector.global); if (has_global) { if (siblings.size === 0 && get_element_parent(node) !== null) { return false; } - return mark(block, node); + return mark(relative_selector, node); } for (const possible_sibling of siblings.keys()) { - if (apply_selector(blocks.slice(), possible_sibling, stylesheet)) { - mark(block, node); + if (apply_selector(relative_selectors.slice(), possible_sibling, stylesheet)) { + mark(relative_selector, node); has_match = true; } } @@ -289,25 +314,25 @@ function apply_selector(blocks, node, stylesheet) { } // TODO other combinators - return mark(block, node); + return mark(relative_selector, node); } - return mark(block, node); + return mark(relative_selector, node); } const regex_backslash_and_following_character = /\\(.)/g; /** - * @param {Block} block + * @param {RelativeSelector} relative_selector * @param {import('#compiler').RegularElement | import('#compiler').SvelteElement} node * @returns {NO_MATCH | POSSIBLE_MATCH | UNKNOWN_SELECTOR} */ -function block_might_apply_to_node(block, node) { - if (block.host || block.root) return NO_MATCH; +function block_might_apply_to_node(relative_selector, node) { + if (relative_selector.host || relative_selector.root) return NO_MATCH; - let i = block.selectors.length; + let i = relative_selector.selectors.length; while (i--) { - const selector = block.selectors[i]; + const selector = relative_selector.selectors[i]; if (selector.type === 'Percentage' || selector.type === 'Nth') continue; @@ -317,7 +342,7 @@ function block_might_apply_to_node(block, node) { return NO_MATCH; } if ( - block.selectors.length === 1 && + relative_selector.selectors.length === 1 && selector.type === 'PseudoClassSelector' && name === 'global' ) { @@ -636,22 +661,22 @@ function get_possible_element_siblings(node, adjacent_only) { } /** - * @param {import('#compiler').EachBlock | import('#compiler').IfBlock | import('#compiler').AwaitBlock} block + * @param {import('#compiler').EachBlock | import('#compiler').IfBlock | import('#compiler').AwaitBlock} relative_selector * @param {boolean} adjacent_only * @returns {Map} */ -function get_possible_last_child(block, adjacent_only) { +function get_possible_last_child(relative_selector, adjacent_only) { /** @typedef {Map} NodeMap */ /** @type {NodeMap} */ const result = new Map(); - if (block.type === 'EachBlock') { + if (relative_selector.type === 'EachBlock') { /** @type {NodeMap} */ - const each_result = loop_child(block.body.nodes, adjacent_only); + const each_result = loop_child(relative_selector.body.nodes, adjacent_only); /** @type {NodeMap} */ - const else_result = block.fallback - ? loop_child(block.fallback.nodes, adjacent_only) + const else_result = relative_selector.fallback + ? loop_child(relative_selector.fallback.nodes, adjacent_only) : new Map(); const not_exhaustive = !has_definite_elements(else_result); if (not_exhaustive) { @@ -660,13 +685,13 @@ function get_possible_last_child(block, adjacent_only) { } add_to_map(each_result, result); add_to_map(else_result, result); - } else if (block.type === 'IfBlock') { + } else if (relative_selector.type === 'IfBlock') { /** @type {NodeMap} */ - const if_result = loop_child(block.consequent.nodes, adjacent_only); + const if_result = loop_child(relative_selector.consequent.nodes, adjacent_only); /** @type {NodeMap} */ - const else_result = block.alternate - ? loop_child(block.alternate.nodes, adjacent_only) + const else_result = relative_selector.alternate + ? loop_child(relative_selector.alternate.nodes, adjacent_only) : new Map(); const not_exhaustive = !has_definite_elements(if_result) || !has_definite_elements(else_result); if (not_exhaustive) { @@ -675,17 +700,21 @@ function get_possible_last_child(block, adjacent_only) { } add_to_map(if_result, result); add_to_map(else_result, result); - } else if (block.type === 'AwaitBlock') { + } else if (relative_selector.type === 'AwaitBlock') { /** @type {NodeMap} */ - const pending_result = block.pending - ? loop_child(block.pending.nodes, adjacent_only) + const pending_result = relative_selector.pending + ? loop_child(relative_selector.pending.nodes, adjacent_only) : new Map(); /** @type {NodeMap} */ - const then_result = block.then ? loop_child(block.then.nodes, adjacent_only) : new Map(); + const then_result = relative_selector.then + ? loop_child(relative_selector.then.nodes, adjacent_only) + : new Map(); /** @type {NodeMap} */ - const catch_result = block.catch ? loop_child(block.catch.nodes, adjacent_only) : new Map(); + const catch_result = relative_selector.catch + ? loop_child(relative_selector.catch.nodes, adjacent_only) + : new Map(); const not_exhaustive = !has_definite_elements(pending_result) || !has_definite_elements(then_result) || @@ -774,7 +803,18 @@ function loop_child(children, adjacent_only) { return result; } -class Block { +/** + * Represents a compound selector (aka an array of simple selectors) plus + * a preceding combinator (if not the first in the list). Given this... + * + * ```css + * .a + .b.c {...} + * ``` + * + * ...both `.a` and `+ .b.c` are relative selectors. + * Combined, they are a complex selector. + */ +class RelativeSelector { /** @type {boolean} */ host; @@ -837,16 +877,16 @@ class Block { /** @param {import('#compiler').Css.ComplexSelector} selector */ function group_selectors(selector) { - let block = new Block(null); - const blocks = [block]; + let relative_selector = new RelativeSelector(null); + const relative_selectors = [relative_selector]; selector.children.forEach((child) => { if (child.type === 'Combinator') { - block = new Block(child); - blocks.push(block); + relative_selector = new RelativeSelector(child); + relative_selectors.push(relative_selector); } else { - block.add(child); + relative_selector.add(child); } }); - return blocks; + return relative_selectors; } From efd201d40843d1b09447844831702d6495966b4a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:37:04 -0500 Subject: [PATCH 6/9] tidy --- packages/svelte/src/compiler/css/Selector.js | 40 +++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/packages/svelte/src/compiler/css/Selector.js b/packages/svelte/src/compiler/css/Selector.js index 1d1990840797..8f1bef724c0f 100644 --- a/packages/svelte/src/compiler/css/Selector.js +++ b/packages/svelte/src/compiler/css/Selector.js @@ -220,7 +220,7 @@ function apply_selector(relative_selectors, node, stylesheet) { return ( (relative_selector.global && relative_selectors.every((relative_selector) => relative_selector.global)) || - (relative_selector.host && relative_selectors.length === 0) + (relative_selector.is_host && relative_selectors.length === 0) ); } const applies = block_might_apply_to_node(relative_selector, node); @@ -254,7 +254,7 @@ function apply_selector(relative_selectors, node, stylesheet) { if (ancestor_block.global) { continue; } - if (ancestor_block.host) { + if (ancestor_block.is_host) { return mark(relative_selector, node); } /** @type {import('#compiler').RegularElement | import('#compiler').SvelteElement | null} */ @@ -328,7 +328,7 @@ const regex_backslash_and_following_character = /\\(.)/g; * @returns {NO_MATCH | POSSIBLE_MATCH | UNKNOWN_SELECTOR} */ function block_might_apply_to_node(relative_selector, node) { - if (relative_selector.host || relative_selector.root) return NO_MATCH; + if (relative_selector.is_host || relative_selector.is_root) return NO_MATCH; let i = relative_selector.selectors.length; while (i--) { @@ -815,51 +815,37 @@ function loop_child(children, adjacent_only) { * Combined, they are a complex selector. */ class RelativeSelector { - /** @type {boolean} */ - host; - - /** @type {boolean} */ - root; - /** @type {import('#compiler').Css.Combinator | null} */ combinator; /** @type {import('#compiler').Css.SimpleSelector[]} */ - selectors; - - /** @type {number} */ - start; - - /** @type {number} */ - end; + selectors = []; - /** @type {boolean} */ - should_encapsulate; + is_host = false; + is_root = false; + should_encapsulate = false; + start = -1; + end = -1; /** @param {import('#compiler').Css.Combinator | null} combinator */ constructor(combinator) { this.combinator = combinator; - this.host = false; - this.root = false; - this.selectors = []; - this.start = -1; - this.end = -1; - this.should_encapsulate = false; } /** @param {import('#compiler').Css.SimpleSelector} selector */ add(selector) { if (this.selectors.length === 0) { this.start = selector.start; - this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host'; + this.is_host = selector.type === 'PseudoClassSelector' && selector.name === 'host'; } - this.root = this.root || (selector.type === 'PseudoClassSelector' && selector.name === 'root'); + this.is_root = + this.is_root || (selector.type === 'PseudoClassSelector' && selector.name === 'root'); this.selectors.push(selector); this.end = selector.end; } can_ignore() { - return this.global || this.host || this.root; + return this.global || this.is_host || this.is_root; } get global() { From 389aff956bcf098f56273e84554ef4bd07f7bd56 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:37:38 -0500 Subject: [PATCH 7/9] tidy --- packages/svelte/src/compiler/css/Selector.js | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/svelte/src/compiler/css/Selector.js b/packages/svelte/src/compiler/css/Selector.js index 8f1bef724c0f..fded4f17bc88 100644 --- a/packages/svelte/src/compiler/css/Selector.js +++ b/packages/svelte/src/compiler/css/Selector.js @@ -113,7 +113,7 @@ export class ComplexSelector { let first = true; for (const relative_selector of this.relative_selectors) { - if (relative_selector.global) { + if (relative_selector.is_global) { remove_global_pseudo_class(relative_selector.selectors[0]); } @@ -139,13 +139,13 @@ export class ComplexSelector { let start = 0; let end = this.relative_selectors.length; for (; start < end; start += 1) { - if (!this.relative_selectors[start].global) break; + if (!this.relative_selectors[start].is_global) break; } for (; end > start; end -= 1) { - if (!this.relative_selectors[end - 1].global) break; + if (!this.relative_selectors[end - 1].is_global) break; } for (let i = start; i < end; i += 1) { - if (this.relative_selectors[i].global) { + if (this.relative_selectors[i].is_global) { error(this.relative_selectors[i].selectors[0], 'invalid-css-global-placement'); } } @@ -218,8 +218,8 @@ function apply_selector(relative_selectors, node, stylesheet) { if (!relative_selector) return false; if (!node) { return ( - (relative_selector.global && - relative_selectors.every((relative_selector) => relative_selector.global)) || + (relative_selector.is_global && + relative_selectors.every((relative_selector) => relative_selector.is_global)) || (relative_selector.is_host && relative_selectors.length === 0) ); } @@ -251,7 +251,7 @@ function apply_selector(relative_selectors, node, stylesheet) { relative_selector.combinator.name === ' ' ) { for (const ancestor_block of relative_selectors) { - if (ancestor_block.global) { + if (ancestor_block.is_global) { continue; } if (ancestor_block.is_host) { @@ -270,13 +270,13 @@ function apply_selector(relative_selectors, node, stylesheet) { return mark(relative_selector, node); } } - if (relative_selectors.every((relative_selector) => relative_selector.global)) { + if (relative_selectors.every((relative_selector) => relative_selector.is_global)) { return mark(relative_selector, node); } return false; } else if (relative_selector.combinator.name === '>') { const has_global_parent = relative_selectors.every( - (relative_selector) => relative_selector.global + (relative_selector) => relative_selector.is_global ); if ( has_global_parent || @@ -297,7 +297,9 @@ function apply_selector(relative_selectors, node, stylesheet) { // NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the // css-tree limitation that does not parse the inner selector of :global // so unless we are sure there will be no sibling to match, we will consider it as matched - const has_global = relative_selectors.some((relative_selector) => relative_selector.global); + const has_global = relative_selectors.some( + (relative_selector) => relative_selector.is_global + ); if (has_global) { if (siblings.size === 0 && get_element_parent(node) !== null) { return false; @@ -845,10 +847,10 @@ class RelativeSelector { } can_ignore() { - return this.global || this.is_host || this.is_root; + return this.is_global || this.is_host || this.is_root; } - get global() { + get is_global() { return ( this.selectors.length >= 1 && this.selectors[0].type === 'PseudoClassSelector' && From 3391312cfd1605f2025947774f6abcfe10817253 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:40:09 -0500 Subject: [PATCH 8/9] unused file --- .../src/compiler/phases/2-analyze/utils/hash.js | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 packages/svelte/src/compiler/phases/2-analyze/utils/hash.js diff --git a/packages/svelte/src/compiler/phases/2-analyze/utils/hash.js b/packages/svelte/src/compiler/phases/2-analyze/utils/hash.js deleted file mode 100644 index d7a0270334ae..000000000000 --- a/packages/svelte/src/compiler/phases/2-analyze/utils/hash.js +++ /dev/null @@ -1,14 +0,0 @@ -const regex_return_characters = /\r/g; - -/** - * @param {string} str - * @returns {string} - */ -export default function hash(str) { - str = str.replace(regex_return_characters, ''); - let hash = 5381; - let i = str.length; - - while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); - return (hash >>> 0).toString(36); -} From 39fa1e537d4fb99f773882ce3c15a97f06985afe Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Feb 2024 11:42:22 -0500 Subject: [PATCH 9/9] tweak --- packages/svelte/src/compiler/css/Selector.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/svelte/src/compiler/css/Selector.js b/packages/svelte/src/compiler/css/Selector.js index fded4f17bc88..dc5360c893eb 100644 --- a/packages/svelte/src/compiler/css/Selector.js +++ b/packages/svelte/src/compiler/css/Selector.js @@ -50,9 +50,7 @@ export class ComplexSelector { this.relative_selectors = group_selectors(node); // take trailing :global(...) selectors out of consideration - const i = this.relative_selectors.findLastIndex( - (relative_selector) => !relative_selector.can_ignore() - ); + const i = this.relative_selectors.findLastIndex((s) => !s.can_ignore()); this.local_relative_selectors = this.relative_selectors.slice(0, i + 1); // if we have a `:root {...}` or `:global(...) {...}` selector, we need to mark