From 0cbd20120023f6b53065dd05f56d8f6d46388ffc Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 26 Jun 2017 08:09:41 -0400 Subject: [PATCH 01/21] set up tests for omitted scoping attributes --- test/css/index.js | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/test/css/index.js b/test/css/index.js index 51cf1b4aba99..9566667c65af 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -1,6 +1,6 @@ import assert from "assert"; import * as fs from "fs"; -import { svelte } from "../helpers.js"; +import { env, svelte } from "../helpers.js"; function tryRequire(file) { try { @@ -23,19 +23,45 @@ describe("css", () => { } (solo ? it.only : it)(dir, () => { - const config = tryRequire(`./samples/${dir}/_config.js`) || {}; + const config = Object.assign(tryRequire(`./samples/${dir}/_config.js`) || {}, { + format: 'iife', + name: 'SvelteComponent' + }); const input = fs .readFileSync(`test/css/samples/${dir}/input.html`, "utf-8") .replace(/\s+$/, ""); - const actual = svelte.compile(input, config).css; - fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, actual); - const expected = fs.readFileSync( - `test/css/samples/${dir}/expected.css`, - "utf-8" - ); + const actual = svelte.compile(input, config); + fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, actual.css); + const expected = { + html: read(`test/css/samples/${dir}/expected.html`), + css: read(`test/css/samples/${dir}/expected.css`) + }; - assert.equal(actual.trim(), expected.trim()); + assert.equal(actual.css.trim(), expected.css.trim()); + + // verify that the right elements have scoping selectors + if (expected.html !== null) { + return env().then(window => { + const Component = eval(`(function () { ${actual.code}; return SvelteComponent; }())`); + const target = window.document.querySelector("main"); + + new Component({ target }); + const html = target.innerHTML; + + fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); + + assert.equal(html.trim(), expected.html.trim()); + }); + } }); }); }); + +function read(file) { + try { + return fs.readFileSync(file, 'utf-8'); + } catch(err) { + return null; + } +} \ No newline at end of file From fae9036ce63b107fab81e4987089a21695d832d1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 26 Jun 2017 10:36:13 -0400 Subject: [PATCH 02/21] only apply svelte-123xyz attributes where necessary (WIP) --- src/generators/Generator.ts | 15 +++++ src/generators/dom/index.ts | 2 +- src/generators/dom/visit.ts | 8 ++- .../dom/visitors/Component/Component.ts | 5 +- src/generators/dom/visitors/EachBlock.ts | 7 +- .../dom/visitors/Element/Element.ts | 9 +-- src/generators/dom/visitors/IfBlock.ts | 19 +++--- src/generators/extractSelectors.ts | 64 +++++++++++++++++++ .../_config.js | 3 + .../expected.css | 4 ++ .../expected.html | 2 + .../input.html | 18 ++++++ .../_config.js | 3 + .../expected.css | 4 ++ .../expected.html | 2 + .../input.html | 8 +++ .../_config.js | 3 + .../expected.css | 4 ++ .../expected.html | 1 + .../input.html | 9 +++ .../samples/omit-scoping-attribute/_config.js | 3 + .../omit-scoping-attribute/expected.css | 4 ++ .../omit-scoping-attribute/expected.html | 1 + .../samples/omit-scoping-attribute/input.html | 9 +++ 24 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 src/generators/extractSelectors.ts create mode 100644 test/css/samples/omit-scoping-attribute-class-dynamic/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-class-dynamic/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-class-dynamic/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-class-dynamic/input.html create mode 100644 test/css/samples/omit-scoping-attribute-class-static/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-class-static/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-class-static/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-class-static/input.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/input.html create mode 100644 test/css/samples/omit-scoping-attribute/_config.js create mode 100644 test/css/samples/omit-scoping-attribute/expected.css create mode 100644 test/css/samples/omit-scoping-attribute/expected.html create mode 100644 test/css/samples/omit-scoping-attribute/input.html diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 1a1528d0c42d..c33f440ebc2e 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -13,6 +13,7 @@ import annotateWithScopes from '../utils/annotateWithScopes'; import clone from '../utils/clone'; import DomBlock from './dom/Block'; import SsrBlock from './server-side-rendering/Block'; +import extractSelectors from './extractSelectors'; import { Node, Parsed, CompileOptions } from '../interfaces'; const test = typeof global !== 'undefined' && global.__svelte_test; @@ -40,6 +41,8 @@ export default class Generator { cssId: string; usesRefs: boolean; + selectors: any[]; // TODO how to indicate it takes `(Node[]) => boolean` functions? + importedNames: Set; aliases: Map; usedNames: Set; @@ -76,6 +79,8 @@ export default class Generator { this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.usesRefs = false; + this.selectors = extractSelectors(parsed.css); + // allow compiler to deconflict user's `import { get } from 'whatever'` and // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; this.importedNames = new Set(); @@ -211,6 +216,16 @@ export default class Generator { }; } + cssAppliesTo(node: Node, stack: Node[]) { + for (let i = 0; i < this.selectors.length; i += 1) { + const selector = this.selectors[i]; + if (selector.appliesTo(node, stack)) { + selector.used = true; + return true; + } + } + } + findDependencies( contextDependencies: Map, indexes: Map, diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 8921c6cabefc..65d697fa0b60 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -73,7 +73,7 @@ export default function dom( const { block, state } = preprocess(generator, namespace, parsed.html); parsed.html.children.forEach((node: Node) => { - visit(generator, block, state, node); + visit(generator, block, state, node, []); }); const builders = { diff --git a/src/generators/dom/visit.ts b/src/generators/dom/visit.ts index 91601fa8fd91..82fc23c03b17 100644 --- a/src/generators/dom/visit.ts +++ b/src/generators/dom/visit.ts @@ -2,13 +2,15 @@ import visitors from './visitors/index'; import { DomGenerator } from './index'; import Block from './Block'; import { Node } from '../../interfaces'; +import { State } from './interfaces'; export default function visit( generator: DomGenerator, block: Block, - state, - node: Node + state: State, + node: Node, + elementStack: Node[] ) { const visitor = visitors[node.type]; - visitor(generator, block, state, node); + visitor(generator, block, state, node, elementStack); } diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 96fdd4e31e46..d126f09dc277 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -40,7 +40,8 @@ export default function visitComponent( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const hasChildren = node.children.length > 0; const name = block.getUniqueName( @@ -123,7 +124,7 @@ export default function visitComponent( const childBlock = node._block; node.children.forEach((child: Node) => { - visit(generator, childBlock, childState, child); + visit(generator, childBlock, childState, child, elementStack); }); const yieldFragment = block.getUniqueName(`${name}_yield_fragment`); diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 4be9059e523e..dc98ad1f3763 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -9,7 +9,8 @@ export default function visitEachBlock( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const each_block = generator.getUniqueName(`each_block`); const create_each_block = node._block.name; @@ -118,12 +119,12 @@ export default function visitEachBlock( } node.children.forEach((child: Node) => { - visit(generator, node._block, node._state, child); + visit(generator, node._block, node._state, child, elementStack); }); if (node.else) { node.else.children.forEach((child: Node) => { - visit(generator, node.else._block, node.else._state, child); + visit(generator, node.else._block, node.else._state, child, elementStack); }); } } diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 5e16abb3eb85..bc2e811d3bbe 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -35,14 +35,15 @@ export default function visitElement( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { if (node.name in meta) { return meta[node.name](generator, block, node); } if (generator.components.has(node.name) || node.name === ':Self') { - return visitComponent(generator, block, state, node); + return visitComponent(generator, block, state, node, elementStack); } const childState = node._state; @@ -67,7 +68,7 @@ export default function visitElement( } // add CSS encapsulation attribute - if (generator.cssId && (!generator.cascade || state.isTopLevel)) { + if (generator.cssId && (generator.cascade ? state.isTopLevel : generator.cssAppliesTo(node, elementStack))) { block.builders.hydrate.addLine( `${generator.helper( 'setAttribute' @@ -172,7 +173,7 @@ export default function visitElement( } node.children.forEach((child: Node) => { - visit(generator, block, childState, child); + visit(generator, block, childState, child, elementStack.concat(node)); }); if (node.lateUpdate) { diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index fc2c54ca9738..deb625e8080d 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -19,7 +19,8 @@ function getBranches( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const branches = [ { @@ -31,11 +32,11 @@ function getBranches( }, ]; - visitChildren(generator, block, state, node); + visitChildren(generator, block, state, node, elementStack); if (isElseIf(node.else)) { branches.push( - ...getBranches(generator, block, state, node.else.children[0]) + ...getBranches(generator, block, state, node.else.children[0], elementStack) ); } else { branches.push({ @@ -47,7 +48,7 @@ function getBranches( }); if (node.else) { - visitChildren(generator, block, state, node.else); + visitChildren(generator, block, state, node.else, elementStack); } } @@ -58,10 +59,11 @@ function visitChildren( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { node.children.forEach((child: Node) => { - visit(generator, node._block, node._state, child); + visit(generator, node._block, node._state, child, elementStack); }); } @@ -69,7 +71,8 @@ export default function visitIfBlock( generator: DomGenerator, block: Block, state: State, - node: Node + node: Node, + elementStack: Node[] ) { const name = generator.getUniqueName(`if_block`); const anchor = node.needsAnchor @@ -77,7 +80,7 @@ export default function visitIfBlock( : (node.next && node.next._state.name) || 'null'; const params = block.params.join(', '); - const branches = getBranches(generator, block, state, node); + const branches = getBranches(generator, block, state, node, elementStack); const hasElse = isElseBranch(branches[branches.length - 1]); const if_name = hasElse ? '' : `if ( ${name} ) `; diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts new file mode 100644 index 000000000000..bcb6bf031b06 --- /dev/null +++ b/src/generators/extractSelectors.ts @@ -0,0 +1,64 @@ +import { Node } from '../interfaces'; + +export default function extractSelectors(css: Node) :Node[] { + if (!css) return []; + + const selectors = []; + + function processRule(rule: Node) { + if (rule.type !== 'Rule') { + // TODO @media etc + throw new Error(`not supported: ${rule.type}`); + } + + const selectors = rule.selector.children; + selectors.forEach(processSelector); + } + + function processSelector(selector: Node) { + selectors.push({ + used: false, + appliesTo: (node: Node, stack: Node[]) => { + let i = selector.children.length; + let j = stack.length; + + while (i--) { + if (!node) return false; + + const part = selector.children[i]; + + if (part.type === 'ClassSelector') { + if (!classMatches(node, part.name)) return false; + } + + else if (part.type === 'TypeSelector') { + if (node.name !== part.name) return false; + } + + else if (part.type === 'WhiteSpace') { + node = stack[--j]; + } + + else { + throw new Error(`TODO ${part.type}`); + } + } + + return true; + } + }); + } + + css.children.forEach(processRule); + + return selectors; +} + +function classMatches(node: Node, className: string) { + const attr = node.attributes.find((attr: Node) => attr.name === 'class'); + if (!attr) return false; + if (attr.value.length > 1) return true; + if (attr.value[0].type !== 'Text') return true; + + return attr.value[0].data.split(' ').indexOf(className) !== -1; +} \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/_config.js b/test/css/samples/omit-scoping-attribute-class-dynamic/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css new file mode 100644 index 000000000000..45b763d4de37 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css @@ -0,0 +1,4 @@ + + .foo[svelte-2643270928] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html new file mode 100644 index 000000000000..a51f22d968a2 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/input.html b/test/css/samples/omit-scoping-attribute-class-dynamic/input.html new file mode 100644 index 000000000000..1ef2a1796aa7 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/input.html @@ -0,0 +1,18 @@ +

this is styled

+

this is unstyled

+ + + + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-static/_config.js b/test/css/samples/omit-scoping-attribute-class-static/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-static/expected.css b/test/css/samples/omit-scoping-attribute-class-static/expected.css new file mode 100644 index 000000000000..bf4e3cb73c45 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/expected.css @@ -0,0 +1,4 @@ + + .foo[svelte-633959357] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-class-static/expected.html b/test/css/samples/omit-scoping-attribute-class-static/expected.html new file mode 100644 index 000000000000..b972aa4cb195 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-static/input.html b/test/css/samples/omit-scoping-attribute-class-static/input.html new file mode 100644 index 000000000000..6b877fa13e18 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/input.html @@ -0,0 +1,8 @@ +

this is styled

+

this is unstyled

+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/_config.js b/test/css/samples/omit-scoping-attribute-descendant.solo/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css new file mode 100644 index 000000000000..b596c30221f8 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css @@ -0,0 +1,4 @@ + + div[svelte-1941127328] p[svelte-1941127328] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html new file mode 100644 index 000000000000..650f7868f803 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html @@ -0,0 +1 @@ +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/input.html b/test/css/samples/omit-scoping-attribute-descendant.solo/input.html new file mode 100644 index 000000000000..c9de6011040f --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant.solo/input.html @@ -0,0 +1,9 @@ +
+

this is styled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute/_config.js b/test/css/samples/omit-scoping-attribute/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute/expected.css b/test/css/samples/omit-scoping-attribute/expected.css new file mode 100644 index 000000000000..f8adbd21a705 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/expected.css @@ -0,0 +1,4 @@ + + p[svelte-1997768937] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute/expected.html b/test/css/samples/omit-scoping-attribute/expected.html new file mode 100644 index 000000000000..580168ced2e7 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/expected.html @@ -0,0 +1 @@ +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute/input.html b/test/css/samples/omit-scoping-attribute/input.html new file mode 100644 index 000000000000..69251dc236fb --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/input.html @@ -0,0 +1,9 @@ +
+

this is styled

+
+ + \ No newline at end of file From ab4000740637c436b072bfa74c643f5f47220d02 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 27 Jun 2017 10:45:10 -0400 Subject: [PATCH 03/21] move CSS analysis into preprocess --- src/generators/Generator.ts | 14 +-- src/generators/dom/preprocess.ts | 31 +++++-- .../dom/visitors/Element/Element.ts | 3 +- src/generators/extractSelectors.ts | 88 ++++++++++++++----- .../expected.css | 4 - .../expected.html | 1 - .../_config.js | 0 .../expected.css | 4 + .../expected.html | 1 + .../input.html | 11 +++ .../_config.js | 3 + .../expected.css | 4 + .../expected.html | 1 + .../input.html | 4 +- 14 files changed, 127 insertions(+), 42 deletions(-) delete mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/expected.css delete mode 100644 test/css/samples/omit-scoping-attribute-descendant.solo/expected.html rename test/css/samples/{omit-scoping-attribute-descendant.solo => omit-scoping-attribute-descendant}/_config.js (100%) create mode 100644 test/css/samples/omit-scoping-attribute-descendant/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-descendant/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant/input.html create mode 100644 test/css/samples/omit-scoping-attribute-whitespace/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-whitespace/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-whitespace/expected.html rename test/css/samples/{omit-scoping-attribute-descendant.solo => omit-scoping-attribute-whitespace}/input.html (54%) diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index c33f440ebc2e..aa3771fea516 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -216,13 +216,17 @@ export default class Generator { }; } - cssAppliesTo(node: Node, stack: Node[]) { + applyCss(node: Node, stack: Node[]) { + if (!this.cssId) return; + + if (this.cascade) { + if (stack.length === 0) node._needsCssAttribute = true; + return; + } + for (let i = 0; i < this.selectors.length; i += 1) { const selector = this.selectors[i]; - if (selector.appliesTo(node, stack)) { - selector.used = true; - return true; - } + selector.apply(node, stack); } } diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 55a057b9e444..e4ea6f8f9fb6 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -40,6 +40,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean ) => { const dependencies = block.findDependencies(node.expression); @@ -55,6 +56,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean ) => { const dependencies = block.findDependencies(node.expression); @@ -66,7 +68,14 @@ const preprocessors = { node._state = getChildState(state, { basename, name }); }, - Text: (generator: DomGenerator, block: Block, state: State, node: Node, stripWhitespace: boolean) => { + Text: ( + generator: DomGenerator, + block: Block, + state: State, + node: Node, + elementStack: Node[], + stripWhitespace: boolean + ) => { node._state = getChildState(state); if (!/\S/.test(node.data)) { @@ -83,6 +92,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) => { @@ -102,7 +112,7 @@ const preprocessors = { node._state = getChildState(state); blocks.push(node._block); - preprocessChildren(generator, node._block, node._state, node, stripWhitespace, node); + preprocessChildren(generator, node._block, node._state, node, elementStack, stripWhitespace, nextSibling); if (node._block.dependencies.size > 0) { dynamic = true; @@ -127,6 +137,7 @@ const preprocessors = { node.else._block, node.else._state, node.else, + elementStack, stripWhitespace, nextSibling ); @@ -154,6 +165,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) => { @@ -202,7 +214,7 @@ const preprocessors = { }); generator.blocks.push(node._block); - preprocessChildren(generator, node._block, node._state, node, stripWhitespace, nextSibling); + preprocessChildren(generator, node._block, node._state, node, elementStack, stripWhitespace, nextSibling); block.addDependencies(node._block.dependencies); node._block.hasUpdateMethod = node._block.dependencies.size > 0; @@ -219,6 +231,7 @@ const preprocessors = { node.else._block, node.else._state, node.else, + elementStack, stripWhitespace, nextSibling ); @@ -231,6 +244,7 @@ const preprocessors = { block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) => { @@ -315,6 +329,8 @@ const preprocessors = { : state.namespace, allUsedContexts: [], }); + + generator.applyCss(node, elementStack); } if (node.children.length) { @@ -328,12 +344,12 @@ const preprocessors = { }); generator.blocks.push(node._block); - preprocessChildren(generator, node._block, node._state, node, stripWhitespace, nextSibling); + preprocessChildren(generator, node._block, node._state, node, elementStack, stripWhitespace, nextSibling); block.addDependencies(node._block.dependencies); node._block.hasUpdateMethod = node._block.dependencies.size > 0; } else { if (node.name === 'pre' || node.name === 'textarea') stripWhitespace = false; - preprocessChildren(generator, block, node._state, node, stripWhitespace, nextSibling); + preprocessChildren(generator, block, node._state, node, elementStack.concat(node), stripWhitespace, nextSibling); } } }, @@ -344,6 +360,7 @@ function preprocessChildren( block: Block, state: State, node: Node, + elementStack: Node[], stripWhitespace: boolean, nextSibling: Node ) { @@ -373,7 +390,7 @@ function preprocessChildren( cleaned.forEach((child: Node, i: number) => { const preprocessor = preprocessors[child.type]; - if (preprocessor) preprocessor(generator, block, state, child, stripWhitespace, cleaned[i + 1] || nextSibling); + if (preprocessor) preprocessor(generator, block, state, child, elementStack, stripWhitespace, cleaned[i + 1] || nextSibling); if (lastChild) { lastChild.next = child; @@ -432,7 +449,7 @@ export default function preprocess( }; generator.blocks.push(block); - preprocessChildren(generator, block, state, node, true, null); + preprocessChildren(generator, block, state, node, [], true, null); block.hasUpdateMethod = block.dependencies.size > 0; return { block, state }; diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 2eb31dc2d07e..bb7de853bb56 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -81,7 +81,8 @@ export default function visitElement( } // add CSS encapsulation attribute - if (generator.cssId && (generator.cascade ? state.isTopLevel : generator.cssAppliesTo(node, elementStack))) { + // TODO add a helper for this, rather than repeating it + if (node._needsCssAttribute) { block.builders.hydrate.addLine( `@setAttribute( ${name}, '${generator.cssId}', '' );` ); diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index bcb6bf031b06..50a021ea6b63 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -6,6 +6,15 @@ export default function extractSelectors(css: Node) :Node[] { const selectors = []; function processRule(rule: Node) { + if (rule.type === 'Atrule') { + if (rule.name === 'keyframes') return; + if (rule.name == 'media') { + rule.block.children.forEach(processRule); + return; + } + console.log(rule); + throw new Error('nope'); + } if (rule.type !== 'Rule') { // TODO @media etc throw new Error(`not supported: ${rule.type}`); @@ -18,40 +27,73 @@ export default function extractSelectors(css: Node) :Node[] { function processSelector(selector: Node) { selectors.push({ used: false, - appliesTo: (node: Node, stack: Node[]) => { - let i = selector.children.length; - let j = stack.length; + apply: (node: Node, stack: Node[]) => { + const applies = selectorAppliesTo(selector.children, node, stack.slice()); - while (i--) { - if (!node) return false; + if (applies) { + node._needsCssAttribute = true; + if (selector.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; + } + } + }); + } - const part = selector.children[i]; + css.children.forEach(processRule); - if (part.type === 'ClassSelector') { - if (!classMatches(node, part.name)) return false; - } + return selectors; +} + +function isDescendantSelector(selector: Node) { + return selector.type === 'WhiteSpace'; // TODO or '>' +} + +function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]) { + let i = parts.length; + let j = stack.length; - else if (part.type === 'TypeSelector') { - if (node.name !== part.name) return false; - } + while (i--) { + if (!node) return; + const part = parts[i]; - else if (part.type === 'WhiteSpace') { - node = stack[--j]; - } + if (part.type === 'PseudoClassSelector' || part.type === 'PseudoElementSelector') { + continue; + } + + if (part.type === 'ClassSelector') { + if (!classMatches(node, part.name)) return false; + } - else { - throw new Error(`TODO ${part.type}`); - } + else if (part.type === 'TypeSelector') { + if (node.name !== part.name) return false; + } + + else if (part.type === 'WhiteSpace') { + parts = parts.slice(0, i); + + while (stack.length) { + if (selectorAppliesTo(parts, stack.pop(), stack)) { + return true; } + } - return true; + return false; + } + + else if (part.type === 'Combinator') { + if (part.name === '>') { + return selectorAppliesTo(parts.slice(0, i), stack.pop(), stack); } - }); - } - css.children.forEach(processRule); + console.log(part); + return true; + } - return selectors; + else { + throw new Error(`TODO ${part.type}`); + } + } + + return true; } function classMatches(node: Node, className: string) { diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css deleted file mode 100644 index b596c30221f8..000000000000 --- a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.css +++ /dev/null @@ -1,4 +0,0 @@ - - div[svelte-1941127328] p[svelte-1941127328] { - color: red; - } diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html b/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html deleted file mode 100644 index 650f7868f803..000000000000 --- a/test/css/samples/omit-scoping-attribute-descendant.solo/expected.html +++ /dev/null @@ -1 +0,0 @@ -

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/_config.js b/test/css/samples/omit-scoping-attribute-descendant/_config.js similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant.solo/_config.js rename to test/css/samples/omit-scoping-attribute-descendant/_config.js diff --git a/test/css/samples/omit-scoping-attribute-descendant/expected.css b/test/css/samples/omit-scoping-attribute-descendant/expected.css new file mode 100644 index 000000000000..548ef810ef97 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant/expected.css @@ -0,0 +1,4 @@ + + div[svelte-2557028325] > p[svelte-2557028325] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant/expected.html b/test/css/samples/omit-scoping-attribute-descendant/expected.html new file mode 100644 index 000000000000..c3cd49d71e6b --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant/expected.html @@ -0,0 +1 @@ +

this is not styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant/input.html b/test/css/samples/omit-scoping-attribute-descendant/input.html new file mode 100644 index 000000000000..8af2cc4dd117 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant/input.html @@ -0,0 +1,11 @@ +
+
+

this is not styled

+
+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace/_config.js b/test/css/samples/omit-scoping-attribute-whitespace/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace/expected.css b/test/css/samples/omit-scoping-attribute-whitespace/expected.css new file mode 100644 index 000000000000..fedf7985e148 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/expected.css @@ -0,0 +1,4 @@ + + div[svelte-4240344240] p[svelte-4240344240] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-whitespace/expected.html b/test/css/samples/omit-scoping-attribute-whitespace/expected.html new file mode 100644 index 000000000000..06092e315c54 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/expected.html @@ -0,0 +1 @@ +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant.solo/input.html b/test/css/samples/omit-scoping-attribute-whitespace/input.html similarity index 54% rename from test/css/samples/omit-scoping-attribute-descendant.solo/input.html rename to test/css/samples/omit-scoping-attribute-whitespace/input.html index c9de6011040f..6d00e912676c 100644 --- a/test/css/samples/omit-scoping-attribute-descendant.solo/input.html +++ b/test/css/samples/omit-scoping-attribute-whitespace/input.html @@ -1,5 +1,7 @@
-

this is styled

+
+

this is styled

+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-outer/_config.js new file mode 100644 index 000000000000..0371e65c7e35 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer/_config.js @@ -0,0 +1,7 @@ +export default { + cascade: false, + + data: { + raw: '

raw

' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css new file mode 100644 index 000000000000..46c00c646dfd --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css @@ -0,0 +1,4 @@ + + div > p[svelte-794545435] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.html new file mode 100644 index 000000000000..5c3c80ea93b8 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.html @@ -0,0 +1 @@ +

this may or may not be styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer/input.html b/test/css/samples/omit-scoping-attribute-descendant-global-outer/input.html new file mode 100644 index 000000000000..e5fb3ba144f9 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer/input.html @@ -0,0 +1,7 @@ +

this may or may not be styled

+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace-multiple/_config.js b/test/css/samples/omit-scoping-attribute-whitespace-multiple/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace-multiple/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css new file mode 100644 index 000000000000..590f54700608 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css @@ -0,0 +1,4 @@ + + div[svelte-4032668709] section p[svelte-4032668709] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.html b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.html new file mode 100644 index 000000000000..7a3dea14316b --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.html @@ -0,0 +1 @@ +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace-multiple/input.html b/test/css/samples/omit-scoping-attribute-whitespace-multiple/input.html new file mode 100644 index 000000000000..a3e4b4136a88 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace-multiple/input.html @@ -0,0 +1,11 @@ +
+
+

this is styled

+
+
+ + \ No newline at end of file From d9aa3ec5ae86c6736304b542a7d03a82ebdd081d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 00:49:18 -0400 Subject: [PATCH 05/21] more :global(...) handling --- src/generators/extractSelectors.ts | 30 +++++++++++++++---- .../_config.js | 7 +++++ .../expected.css | 4 +++ .../expected.html | 1 + .../input.html | 7 +++++ 5 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index b0a1aee417f9..df539a45b2b0 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -25,10 +25,25 @@ export default function extractSelectors(css: Node) :Node[] { } function processSelector(selector: Node) { + // take trailing :global(...) selectors out of consideration + let i = selector.children.length; + while (i > 2) { + const last = selector.children[i-1]; + const penultimate = selector.children[i-2]; + + if (last.type === 'PseudoClassSelector' && last.name === 'global') { + i -= 2; + } else { + break; + } + } + + const parts = selector.children.slice(0, i); + selectors.push({ used: false, // TODO use this! warn on unused selectors apply: (node: Node, stack: Node[]) => { - const applies = selectorAppliesTo(selector.children, node, stack.slice()); + const applies = selectorAppliesTo(parts, node, stack.slice()); if (applies) { node._needsCssAttribute = true; @@ -52,15 +67,20 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]) { let j = stack.length; while (i--) { + if (!node) { + return parts.every((part: Node) => { + return part.type === 'Combinator' || (part.type === 'PseudoClassSelector' && part.name === 'global'); + }); + } + const part = parts[i]; if (part.type === 'PseudoClassSelector' && part.name === 'global') { - // bail - return true; + // TODO shouldn't see this here... maybe we should enforce that :global(...) + // cannot be sandwiched between non-global selectors? + return false; } - if (!node) return false; - if (part.type === 'PseudoClassSelector' || part.type === 'PseudoElementSelector') { continue; } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global/_config.js new file mode 100644 index 000000000000..0371e65c7e35 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global/_config.js @@ -0,0 +1,7 @@ +export default { + cascade: false, + + data: { + raw: '

raw

' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global/expected.css new file mode 100644 index 000000000000..be3405979388 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global/expected.css @@ -0,0 +1,4 @@ + + div { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global/expected.html new file mode 100644 index 000000000000..281c6866c375 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/input.html b/test/css/samples/omit-scoping-attribute-descendant-global/input.html new file mode 100644 index 000000000000..a85bfa0bb334 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global/input.html @@ -0,0 +1,7 @@ +
+ + \ No newline at end of file From 5499327a70ebc7b243d0110abbeecd65ef67207c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 12:14:18 -0400 Subject: [PATCH 06/21] refactoring, and more :global(...) fixes --- src/generators/extractSelectors.ts | 14 ++--- src/generators/shared/processCss.ts | 38 +++++------- src/utils/css.ts | 21 +++++++ src/validate/css/index.ts | 58 +++++++++++++++++++ src/validate/index.ts | 5 ++ .../_config.js | 0 .../expected.css | 4 ++ .../expected.html | 1 + .../input.html | 9 +++ .../_config.js | 7 +++ .../expected.css | 4 ++ .../expected.html | 1 + .../input.html | 7 +++ .../omit-scoping-attribute-global/_config.js | 7 +++ .../expected.css | 0 .../expected.html | 0 .../input.html | 0 .../css-invalid-global-placement/errors.json | 8 +++ .../css-invalid-global-placement/input.html | 5 ++ .../samples/css-invalid-global/errors.json | 8 +++ .../samples/css-invalid-global/input.html | 5 ++ 21 files changed, 170 insertions(+), 32 deletions(-) create mode 100644 src/utils/css.ts create mode 100644 src/validate/css/index.ts rename test/css/samples/{omit-scoping-attribute-descendant-global => omit-scoping-attribute-descendant-global-inner-multiple}/_config.js (100%) create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/input.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/input.html create mode 100644 test/css/samples/omit-scoping-attribute-global/_config.js rename test/css/samples/{omit-scoping-attribute-descendant-global => omit-scoping-attribute-global}/expected.css (100%) rename test/css/samples/{omit-scoping-attribute-descendant-global => omit-scoping-attribute-global}/expected.html (100%) rename test/css/samples/{omit-scoping-attribute-descendant-global => omit-scoping-attribute-global}/input.html (100%) create mode 100644 test/validator/samples/css-invalid-global-placement/errors.json create mode 100644 test/validator/samples/css-invalid-global-placement/input.html create mode 100644 test/validator/samples/css-invalid-global/errors.json create mode 100644 test/validator/samples/css-invalid-global/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index df539a45b2b0..782d75267121 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -12,12 +12,9 @@ export default function extractSelectors(css: Node) :Node[] { rule.block.children.forEach(processRule); return; } - console.log(rule); - throw new Error('nope'); - } - if (rule.type !== 'Rule') { - // TODO @media etc - throw new Error(`not supported: ${rule.type}`); + + // TODO + throw new Error(`Not implemented: @${rule.name}. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!`); } const selectors = rule.selector.children; @@ -110,12 +107,13 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]) { return selectorAppliesTo(parts.slice(0, i), stack.pop(), stack); } - console.log(part); + // TODO other combinators return true; } else { - throw new Error(`TODO ${part.type}`); + // bail. TODO figure out what these could be + return true; } } diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index b7281e8c8abd..00e564862af0 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -1,4 +1,5 @@ import MagicString from 'magic-string'; +import { groupSelectors, isGlobalSelector } from '../../utils/css'; import { Parsed, Node } from '../../interfaces'; const commentsPattern = /\/\*[\s\S]*?\*\//g; @@ -37,23 +38,11 @@ export default function processCss( parsed.css.children.forEach(walkKeyframes); - function transformBlock(block: Node[]) { + function encapsulateBlock(block: Node[]) { let i = block.length; while (i--) { const child = block[i]; - - if (child.type === 'PseudoElementSelector') continue; - - if (child.type === 'PseudoClassSelector') { - if (child.name === 'global') { - const first = child.children[0]; - const last = child.children[child.children.length - 1]; - code.remove(child.start, first.start).remove(last.end, child.end); - return; - } else { - continue; - } - } + if (child.type === 'PseudoElementSelector' || child.type === 'PseudoClassSelector') continue; code.appendLeft(child.end, attr); return; @@ -91,19 +80,20 @@ export default function processCss( // separate .foo > .bar > .baz into three separate blocks, so // that we can transform only the first and last let block: Node[] = []; - const blocks: Node[][] = [block]; + const blocks: Node[][] = groupSelectors(selector); - selector.children.forEach((child: Node) => { - if (child.type === 'WhiteSpace' || child.type === 'Combinator') { - block = []; - blocks.push(block); - } else { - block.push(child); + blocks.forEach((block: Node[], i) => { + if (i === 0 || i === blocks.length - 1) { + encapsulateBlock(blocks[i]); } - }); - transformBlock(blocks[0]); - if (blocks.length > 1) transformBlock(blocks[blocks.length - 1]); + if (isGlobalSelector(block)) { + const selector = block[0]; + const first = selector.children[0]; + const last = selector.children[selector.children.length - 1]; + code.remove(selector.start, first.start).remove(last.end, selector.end); + } + }); } }); diff --git a/src/utils/css.ts b/src/utils/css.ts new file mode 100644 index 000000000000..31a6a26876df --- /dev/null +++ b/src/utils/css.ts @@ -0,0 +1,21 @@ +import { Node } from '../interfaces'; + +export function isGlobalSelector(block: Node[]) { + return block.length === 1 && block[0].type === 'PseudoClassSelector' && block[0].name === 'global'; +} + +export function groupSelectors(selector: Node) { + let block: Node[] = []; + const blocks: Node[][] = [block]; + + selector.children.forEach((child: Node) => { + if (child.type === 'WhiteSpace' || child.type === 'Combinator') { + block = []; + blocks.push(block); + } else { + block.push(child); + } + }); + + return blocks; +} \ No newline at end of file diff --git a/src/validate/css/index.ts b/src/validate/css/index.ts new file mode 100644 index 000000000000..b2f26e78d037 --- /dev/null +++ b/src/validate/css/index.ts @@ -0,0 +1,58 @@ +import { groupSelectors, isGlobalSelector } from '../../utils/css'; +import { Validator } from '../index'; +import { Node } from '../../interfaces'; + +export default function validateCss(validator: Validator, css: Node) { + function validateRule(rule: Node) { + if (rule.type === 'Atrule') { + if (rule.name === 'keyframes') return; + if (rule.name == 'media') { + rule.block.children.forEach(validateRule); + return; + } + + // TODO + throw new Error(`Not implemented: @${rule.name}. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!`); + } + + const selectors = rule.selector.children; + selectors.forEach(validateSelector); + } + + function validateSelector(selector: Node) { + const blocks: Node[][] = groupSelectors(selector); + + blocks.forEach((block, i) => { + if (block.find((part: Node) => part.type === 'PseudoClassSelector' && part.name === 'global')) { + // check that :global(...) is by itself + if (block.length !== 1) { + validator.error(`:global(...) cannot be mixed with non-global selectors`, block[0].start); + } + + // check that :global(...) isn't sandwiched by other selectors + // if (i > 0 && i < blocks.length - 1) { + // validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, block[0].start); + // } + } + }); + + let start = 0; + let end = blocks.length; + + for (; start < end; start += 1) { + if (!isGlobalSelector(blocks[start])) break; + } + + for (; end > start; end -= 1) { + if (!isGlobalSelector(blocks[end - 1])) break; + } + + for (let i = start; i < end; i += 1) { + if (isGlobalSelector(blocks[i])) { + validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, blocks[i][0].start); + } + } + } + + css.children.forEach(validateRule); +} \ No newline at end of file diff --git a/src/validate/index.ts b/src/validate/index.ts index a395eb83d856..32a6a82c9268 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -1,4 +1,5 @@ import validateJs from './js/index'; +import validateCss from './css/index'; import validateHtml from './html/index'; import { getLocator, Location } from 'locate-character'; import getCodeFrame from '../utils/getCodeFrame'; @@ -101,6 +102,10 @@ export default function validate( validateJs(validator, parsed.js); } + if (parsed.css) { + validateCss(validator, parsed.css); + } + if (parsed.html) { validateHtml(validator, parsed.html); } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/_config.js similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/_config.js rename to test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/_config.js diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css new file mode 100644 index 000000000000..d09033651f46 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css @@ -0,0 +1,4 @@ + + div[svelte-3386191472] > p > em { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html new file mode 100644 index 000000000000..db137321742e --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/input.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/input.html new file mode 100644 index 000000000000..85baf01a2dc6 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/input.html @@ -0,0 +1,9 @@ +
+ +
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/_config.js new file mode 100644 index 000000000000..0371e65c7e35 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/_config.js @@ -0,0 +1,7 @@ +export default { + cascade: false, + + data: { + raw: '

raw

' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css new file mode 100644 index 000000000000..9137c5bd8d00 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css @@ -0,0 +1,4 @@ + + div > section > p[svelte-3390623146] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html new file mode 100644 index 000000000000..35cfecee4fb5 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html @@ -0,0 +1 @@ +

this may or may not be styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/input.html b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/input.html new file mode 100644 index 000000000000..57e227284c6a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/input.html @@ -0,0 +1,7 @@ +

this may or may not be styled

+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-global/_config.js b/test/css/samples/omit-scoping-attribute-global/_config.js new file mode 100644 index 000000000000..0371e65c7e35 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global/_config.js @@ -0,0 +1,7 @@ +export default { + cascade: false, + + data: { + raw: '

raw

' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/expected.css b/test/css/samples/omit-scoping-attribute-global/expected.css similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/expected.css rename to test/css/samples/omit-scoping-attribute-global/expected.css diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/expected.html b/test/css/samples/omit-scoping-attribute-global/expected.html similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/expected.html rename to test/css/samples/omit-scoping-attribute-global/expected.html diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/input.html b/test/css/samples/omit-scoping-attribute-global/input.html similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/input.html rename to test/css/samples/omit-scoping-attribute-global/input.html diff --git a/test/validator/samples/css-invalid-global-placement/errors.json b/test/validator/samples/css-invalid-global-placement/errors.json new file mode 100644 index 000000000000..a2b899417510 --- /dev/null +++ b/test/validator/samples/css-invalid-global-placement/errors.json @@ -0,0 +1,8 @@ +[{ + "message": ":global(...) can be at the start or end of a selector sequence, but not in the middle", + "loc": { + "line": 2, + "column": 6 + }, + "pos": 14 +}] \ No newline at end of file diff --git a/test/validator/samples/css-invalid-global-placement/input.html b/test/validator/samples/css-invalid-global-placement/input.html new file mode 100644 index 000000000000..33da3499b26f --- /dev/null +++ b/test/validator/samples/css-invalid-global-placement/input.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/validator/samples/css-invalid-global/errors.json b/test/validator/samples/css-invalid-global/errors.json new file mode 100644 index 000000000000..8aa138e3f2f2 --- /dev/null +++ b/test/validator/samples/css-invalid-global/errors.json @@ -0,0 +1,8 @@ +[{ + "message": ":global(...) cannot be mixed with non-global selectors", + "loc": { + "line": 2, + "column": 1 + }, + "pos": 9 +}] \ No newline at end of file diff --git a/test/validator/samples/css-invalid-global/input.html b/test/validator/samples/css-invalid-global/input.html new file mode 100644 index 000000000000..5d325a4cd18f --- /dev/null +++ b/test/validator/samples/css-invalid-global/input.html @@ -0,0 +1,5 @@ + \ No newline at end of file From f485c2562050ee95f6ceca5365256b23009d3b32 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 13:00:47 -0400 Subject: [PATCH 07/21] create reusable walkRules helper --- src/generators/extractSelectors.ts | 23 +++++------------------ src/generators/shared/processCss.ts | 19 ++----------------- src/utils/css.ts | 12 ++++++++++++ src/validate/css/index.ts | 22 ++++------------------ 4 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index 782d75267121..86222438b319 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -1,3 +1,4 @@ +import { walkRules } from '../utils/css'; import { Node } from '../interfaces'; export default function extractSelectors(css: Node) :Node[] { @@ -5,21 +6,9 @@ export default function extractSelectors(css: Node) :Node[] { const selectors = []; - function processRule(rule: Node) { - if (rule.type === 'Atrule') { - if (rule.name === 'keyframes') return; - if (rule.name == 'media') { - rule.block.children.forEach(processRule); - return; - } - - // TODO - throw new Error(`Not implemented: @${rule.name}. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!`); - } - - const selectors = rule.selector.children; - selectors.forEach(processSelector); - } + walkRules(css.children, node => { + node.selector.children.forEach(processSelector); + }); function processSelector(selector: Node) { // take trailing :global(...) selectors out of consideration @@ -50,8 +39,6 @@ export default function extractSelectors(css: Node) :Node[] { }); } - css.children.forEach(processRule); - return selectors; } @@ -59,7 +46,7 @@ function isDescendantSelector(selector: Node) { return selector.type === 'WhiteSpace'; // TODO or '>' } -function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]) { +function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { let i = parts.length; let j = stack.length; diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index 00e564862af0..77c1d4a420d5 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -1,5 +1,5 @@ import MagicString from 'magic-string'; -import { groupSelectors, isGlobalSelector } from '../../utils/css'; +import { groupSelectors, isGlobalSelector, walkRules } from '../../utils/css'; import { Parsed, Node } from '../../interfaces'; const commentsPattern = /\/\*[\s\S]*?\*\//g; @@ -114,22 +114,7 @@ export default function processCss( }); } - function walk(node: Node) { - if (node.type === 'Rule') { - transform(node); - } else if ( - node.type === 'Atrule' && - node.name.toLowerCase() === 'keyframes' - ) { - // these have already been processed - } else if (node.children) { - node.children.forEach(walk); - } else if (node.block) { - walk(node.block); - } - } - - parsed.css.children.forEach(walk); + walkRules(parsed.css.children, transform); // remove comments. TODO would be nice if this was exposed in css-tree let match; diff --git a/src/utils/css.ts b/src/utils/css.ts index 31a6a26876df..ac78bdd96a0e 100644 --- a/src/utils/css.ts +++ b/src/utils/css.ts @@ -18,4 +18,16 @@ export function groupSelectors(selector: Node) { }); return blocks; +} + +export function walkRules(nodes: Node[], callback: (node: Node) => void) { + nodes.forEach((node: Node) => { + if (node.type === 'Rule') { + callback(node); + } else if (node.type === 'Atrule') { + if (node.name === 'media' || node.name === 'supports' || node.name === 'document') { + walkRules(node.block.children, callback); + } + } + }); } \ No newline at end of file diff --git a/src/validate/css/index.ts b/src/validate/css/index.ts index b2f26e78d037..6251f59fb0a3 100644 --- a/src/validate/css/index.ts +++ b/src/validate/css/index.ts @@ -1,23 +1,11 @@ -import { groupSelectors, isGlobalSelector } from '../../utils/css'; +import { groupSelectors, isGlobalSelector, walkRules } from '../../utils/css'; import { Validator } from '../index'; import { Node } from '../../interfaces'; export default function validateCss(validator: Validator, css: Node) { - function validateRule(rule: Node) { - if (rule.type === 'Atrule') { - if (rule.name === 'keyframes') return; - if (rule.name == 'media') { - rule.block.children.forEach(validateRule); - return; - } - - // TODO - throw new Error(`Not implemented: @${rule.name}. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!`); - } - - const selectors = rule.selector.children; - selectors.forEach(validateSelector); - } + walkRules(css.children, rule => { + rule.selector.children.forEach(validateSelector); + }); function validateSelector(selector: Node) { const blocks: Node[][] = groupSelectors(selector); @@ -53,6 +41,4 @@ export default function validateCss(validator: Validator, css: Node) { } } } - - css.children.forEach(validateRule); } \ No newline at end of file From b72684e99c062279846282d7a3dc8faa21100bc6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 13:57:54 -0400 Subject: [PATCH 08/21] handle universal selectors --- src/generators/extractSelectors.ts | 1 + src/generators/shared/processCss.ts | 9 +++++++-- .../samples/cascade-false-universal-selector/_config.js | 3 +++ .../cascade-false-universal-selector/expected.css | 4 ++++ .../cascade-false-universal-selector/expected.html | 1 + .../samples/cascade-false-universal-selector/input.html | 7 +++++++ test/css/samples/universal-selector/expected.css | 4 ++++ test/css/samples/universal-selector/input.html | 7 +++++++ 8 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 test/css/samples/cascade-false-universal-selector/_config.js create mode 100644 test/css/samples/cascade-false-universal-selector/expected.css create mode 100644 test/css/samples/cascade-false-universal-selector/expected.html create mode 100644 test/css/samples/cascade-false-universal-selector/input.html create mode 100644 test/css/samples/universal-selector/expected.css create mode 100644 test/css/samples/universal-selector/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index 86222438b319..7dbf7b27ca7d 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -74,6 +74,7 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { } else if (part.type === 'TypeSelector') { + if (part.name === '*') return true; if (node.name !== part.name) return false; } diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index 77c1d4a420d5..15719e7b8f72 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -44,7 +44,12 @@ export default function processCss( const child = block[i]; if (child.type === 'PseudoElementSelector' || child.type === 'PseudoClassSelector') continue; - code.appendLeft(child.end, attr); + if (child.type === 'TypeSelector' && child.name === '*') { + code.overwrite(child.start, child.end, attr); + } else { + code.appendLeft(child.end, attr); + } + return; } } @@ -64,7 +69,7 @@ export default function processCss( if (firstToken.type === 'TypeSelector') { const insert = firstToken.end - offset; - const head = css.slice(start, insert); + const head = firstToken.name === '*' ? css.slice(firstToken.end - offset, insert) : css.slice(start, insert); const tail = css.slice(insert, end); transformed = `${head}${attr}${tail}, ${attr} ${selectorString}`; diff --git a/test/css/samples/cascade-false-universal-selector/_config.js b/test/css/samples/cascade-false-universal-selector/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/cascade-false-universal-selector/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/cascade-false-universal-selector/expected.css b/test/css/samples/cascade-false-universal-selector/expected.css new file mode 100644 index 000000000000..bb9155feb061 --- /dev/null +++ b/test/css/samples/cascade-false-universal-selector/expected.css @@ -0,0 +1,4 @@ + + [svelte-2950902288] { + color: red; + } diff --git a/test/css/samples/cascade-false-universal-selector/expected.html b/test/css/samples/cascade-false-universal-selector/expected.html new file mode 100644 index 000000000000..732e063d51d3 --- /dev/null +++ b/test/css/samples/cascade-false-universal-selector/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/cascade-false-universal-selector/input.html b/test/css/samples/cascade-false-universal-selector/input.html new file mode 100644 index 000000000000..36a65e23e6fa --- /dev/null +++ b/test/css/samples/cascade-false-universal-selector/input.html @@ -0,0 +1,7 @@ +
+ + \ No newline at end of file diff --git a/test/css/samples/universal-selector/expected.css b/test/css/samples/universal-selector/expected.css new file mode 100644 index 000000000000..53f57d90839c --- /dev/null +++ b/test/css/samples/universal-selector/expected.css @@ -0,0 +1,4 @@ + + [svelte-2950902288], [svelte-2950902288] * { + color: red; + } diff --git a/test/css/samples/universal-selector/input.html b/test/css/samples/universal-selector/input.html new file mode 100644 index 000000000000..36a65e23e6fa --- /dev/null +++ b/test/css/samples/universal-selector/input.html @@ -0,0 +1,7 @@ +
+ + \ No newline at end of file From d2f5296bd9d05c88f162ccc7843d2470a8954576 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 15:17:39 -0400 Subject: [PATCH 09/21] handle attribute selectors with = operator --- src/generators/extractSelectors.ts | 36 +++++++++++++++++-- .../_config.js | 6 ++++ .../expected.css | 4 +++ .../expected.html | 2 ++ .../input.html | 10 ++++++ .../_config.js | 3 ++ .../expected.css | 4 +++ .../expected.html | 2 ++ .../input.html | 10 ++++++ 9 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/input.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index 7dbf7b27ca7d..6fdd40933c3d 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -73,6 +73,10 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { if (!classMatches(node, part.name)) return false; } + else if (part.type === 'AttributeSelector') { + if (!attributeMatches(node, part)) return false; + } + else if (part.type === 'TypeSelector') { if (part.name === '*') return true; if (node.name !== part.name) return false; @@ -111,8 +115,34 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { function classMatches(node: Node, className: string) { const attr = node.attributes.find((attr: Node) => attr.name === 'class'); if (!attr) return false; - if (attr.value.length > 1) return true; - if (attr.value[0].type !== 'Text') return true; + if (isDynamic(attr)) return true; + + const value = attr.value[0].data; + + return value.split(' ').indexOf(className) !== -1; +} + +function attributeMatches(node: Node, selector: Node) { + const attr = node.attributes.find((attr: Node) => attr.name === selector.name.name); + if (!attr) return false; + if (isDynamic(attr)) return true; + + const expectedValue = unquote(selector.value.value); + const actualValue = attr.value[0].data; - return attr.value[0].data.split(' ').indexOf(className) !== -1; + if (selector.operator === '=') { + return actualValue === expectedValue; + } + + return true; +} + +function isDynamic(attr: Node) { + return attr.value.length > 1 || attr.value[0].type !== 'Text'; +} + +function unquote(str: string) { + if (str[0] === str[str.length - 1] && str[0] === "'" || str[0] === '"') { + return str.slice(1, str.length - 1); + } } \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/_config.js new file mode 100644 index 000000000000..32cdf8cb7919 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/_config.js @@ -0,0 +1,6 @@ +export default { + cascade: false, + data: { + dynamic: 'whatever' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css new file mode 100644 index 000000000000..26fdc211a078 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css @@ -0,0 +1,4 @@ + + [data-foo='bar'][svelte-1339732946] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html new file mode 100644 index 000000000000..e27985b20d1c --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/input.html new file mode 100644 index 000000000000..7ddd680b27c3 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css new file mode 100644 index 000000000000..4a2cb3428e7b --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css @@ -0,0 +1,4 @@ + + [data-foo='bar'][svelte-2543760126] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html new file mode 100644 index 000000000000..6dcb8cfaa365 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/input.html new file mode 100644 index 000000000000..28e31604f8a3 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file From 74d15ea877af6302afe7b89483d0030d152b7537 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 15:25:55 -0400 Subject: [PATCH 10/21] handle empty attributes --- src/generators/extractSelectors.ts | 10 +++++----- .../_config.js | 3 +++ .../expected.css | 4 ++++ .../expected.html | 2 ++ .../input.html | 10 ++++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index 6fdd40933c3d..25e0fa8a2c79 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -114,8 +114,8 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { function classMatches(node: Node, className: string) { const attr = node.attributes.find((attr: Node) => attr.name === 'class'); - if (!attr) return false; - if (isDynamic(attr)) return true; + if (!attr || attr.value === true) return false; + if (isDynamic(attr.value)) return true; const value = attr.value[0].data; @@ -125,7 +125,7 @@ function classMatches(node: Node, className: string) { function attributeMatches(node: Node, selector: Node) { const attr = node.attributes.find((attr: Node) => attr.name === selector.name.name); if (!attr) return false; - if (isDynamic(attr)) return true; + if (attr.value === true || isDynamic(attr.value)) return true; const expectedValue = unquote(selector.value.value); const actualValue = attr.value[0].data; @@ -137,8 +137,8 @@ function attributeMatches(node: Node, selector: Node) { return true; } -function isDynamic(attr: Node) { - return attr.value.length > 1 || attr.value[0].type !== 'Text'; +function isDynamic(value: Node) { + return value.length > 1 || value[0].type !== 'Text'; } function unquote(str: string) { diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css new file mode 100644 index 000000000000..c57bf43bd42d --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css @@ -0,0 +1,4 @@ + + [data-foo][svelte-2966013849] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html new file mode 100644 index 000000000000..77ed0898cff7 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector/input.html new file mode 100644 index 000000000000..eba59af19978 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file From 3dfe92b54fcf528bbe6c6e3c0f1041e41158b83d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 15:42:52 -0400 Subject: [PATCH 11/21] handle ~= attribute selector operator --- src/generators/extractSelectors.ts | 4 ++++ .../_config.js | 6 ++++++ .../expected.css | 4 ++++ .../expected.html | 2 ++ .../input.html | 10 ++++++++++ .../_config.js | 3 +++ .../expected.css | 4 ++++ .../expected.html | 2 ++ .../input.html | 10 ++++++++++ 9 files changed, 45 insertions(+) create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index 25e0fa8a2c79..d83829655109 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -134,6 +134,10 @@ function attributeMatches(node: Node, selector: Node) { return actualValue === expectedValue; } + if(selector.operator === '~=') { + return actualValue.split(/\s/).indexOf(expectedValue) !== -1; + } + return true; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js new file mode 100644 index 000000000000..32cdf8cb7919 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js @@ -0,0 +1,6 @@ +export default { + cascade: false, + data: { + dynamic: 'whatever' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css new file mode 100644 index 000000000000..ca5ed2b2ba2c --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css @@ -0,0 +1,4 @@ + + [data-foo~='bar'][svelte-25731322] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html new file mode 100644 index 000000000000..4908f4998562 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html new file mode 100644 index 000000000000..e8e95d9eaceb --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css new file mode 100644 index 000000000000..20cf2fd9e380 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css @@ -0,0 +1,4 @@ + + [data-foo~='bar'][svelte-1786044856] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html new file mode 100644 index 000000000000..aef480d8ef24 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/input.html new file mode 100644 index 000000000000..026d7d6ed25b --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file From 45dd99d6dbfd8413ed513bf66e80cc4f55ba9b52 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 16:11:38 -0400 Subject: [PATCH 12/21] implement all attribute selector operators --- src/generators/extractSelectors.ts | 20 ++++++++++--------- .../_config.js | 3 +++ .../expected.css | 4 ++++ .../expected.html | 2 ++ .../input.html | 10 ++++++++++ .../_config.js | 3 +++ .../expected.css | 4 ++++ .../expected.html | 2 ++ .../input.html | 10 ++++++++++ .../_config.js | 3 +++ .../expected.css | 4 ++++ .../expected.html | 3 +++ .../input.html | 11 ++++++++++ .../_config.js | 3 +++ .../expected.css | 4 ++++ .../expected.html | 2 ++ .../input.html | 10 ++++++++++ .../_config.js | 3 +++ .../expected.css | 4 ++++ .../expected.html | 2 ++ .../input.html | 10 ++++++++++ .../_config.js | 6 ------ .../expected.css | 4 ---- .../expected.html | 2 -- .../input.html | 10 ---------- 25 files changed, 108 insertions(+), 31 deletions(-) create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-contains/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-contains/input.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/input.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/input.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-prefix/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-prefix/input.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-suffix/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-suffix/input.html delete mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js delete mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css delete mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html delete mode 100644 test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index d83829655109..5f7d3452b63a 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -122,6 +122,15 @@ function classMatches(node: Node, className: string) { return value.split(' ').indexOf(className) !== -1; } +const operators = { + '=' : (value: string, flags: string) => new RegExp(`^${value}$`, flags), + '~=': (value: string, flags: string) => new RegExp(`\\b${value}\\b`, flags), + '|=': (value: string, flags: string) => new RegExp(`^${value}(-.+)?$`, flags), + '^=': (value: string, flags: string) => new RegExp(`^${value}`, flags), + '$=': (value: string, flags: string) => new RegExp(`${value}$`, flags), + '*=': (value: string, flags: string) => new RegExp(value, flags) +}; + function attributeMatches(node: Node, selector: Node) { const attr = node.attributes.find((attr: Node) => attr.name === selector.name.name); if (!attr) return false; @@ -130,15 +139,8 @@ function attributeMatches(node: Node, selector: Node) { const expectedValue = unquote(selector.value.value); const actualValue = attr.value[0].data; - if (selector.operator === '=') { - return actualValue === expectedValue; - } - - if(selector.operator === '~=') { - return actualValue.split(/\s/).indexOf(expectedValue) !== -1; - } - - return true; + const pattern = operators[selector.operator](expectedValue, selector.flags || ''); + return pattern.test(actualValue); } function isDynamic(value: Node) { diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css new file mode 100644 index 000000000000..b48dfbe85a86 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css @@ -0,0 +1,4 @@ + + [data-foo*='bar'][svelte-4224841812] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html new file mode 100644 index 000000000000..22a45b853e2b --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/input.html new file mode 100644 index 000000000000..ea0d54fe6754 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css new file mode 100644 index 000000000000..d20a6b98bf10 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css @@ -0,0 +1,4 @@ + + [data-foo='bar' i][svelte-4191913977] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html new file mode 100644 index 000000000000..1d65033d3e0a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/input.html new file mode 100644 index 000000000000..e80da7b707e1 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css new file mode 100644 index 000000000000..8b57a24cd9d3 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css @@ -0,0 +1,4 @@ + + [data-foo|='bar'][svelte-1225676040] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html new file mode 100644 index 000000000000..1ba93cd6e50b --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html @@ -0,0 +1,3 @@ +

this is styled

+

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/input.html new file mode 100644 index 000000000000..c7a8b598ecea --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/input.html @@ -0,0 +1,11 @@ +
+

this is styled

+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css new file mode 100644 index 000000000000..232c462c4b04 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css @@ -0,0 +1,4 @@ + + [data-foo^='bar'][svelte-3106767242] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html new file mode 100644 index 000000000000..267528a5168d --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html @@ -0,0 +1,2 @@ +

this is styled

+

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/input.html new file mode 100644 index 000000000000..91daf582fd94 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/input.html @@ -0,0 +1,10 @@ +
+

this is styled

+

this is unstyled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css new file mode 100644 index 000000000000..832f50cf3be7 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css @@ -0,0 +1,4 @@ + + [data-foo$='bar'][svelte-207782622] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html new file mode 100644 index 000000000000..00bf78a6ca82 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html @@ -0,0 +1,2 @@ +

this is unstyled

+

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/input.html new file mode 100644 index 000000000000..641d776c6c38 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/input.html @@ -0,0 +1,10 @@ +
+

this is unstyled

+

this is styled

+
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js deleted file mode 100644 index 32cdf8cb7919..000000000000 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/_config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - cascade: false, - data: { - dynamic: 'whatever' - } -}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css deleted file mode 100644 index ca5ed2b2ba2c..000000000000 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.css +++ /dev/null @@ -1,4 +0,0 @@ - - [data-foo~='bar'][svelte-25731322] { - color: red; - } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html deleted file mode 100644 index 4908f4998562..000000000000 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/expected.html +++ /dev/null @@ -1,2 +0,0 @@ -

this is styled

-

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html deleted file mode 100644 index e8e95d9eaceb..000000000000 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals-dynamic/input.html +++ /dev/null @@ -1,10 +0,0 @@ -
-

this is styled

-

this is unstyled

-
- - \ No newline at end of file From 7b289e95f1203d5ead889199b1bd583a316db1d0 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 16:33:11 -0400 Subject: [PATCH 13/21] implement ID selectors, refactor --- src/generators/extractSelectors.ts | 28 ++++++++----------- .../omit-scoping-attribute-id/_config.js | 3 ++ .../omit-scoping-attribute-id/expected.css | 4 +++ .../omit-scoping-attribute-id/expected.html | 2 ++ .../omit-scoping-attribute-id/input.html | 8 ++++++ 5 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 test/css/samples/omit-scoping-attribute-id/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-id/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-id/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-id/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index 5f7d3452b63a..c715b7744fd0 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -70,11 +70,15 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { } if (part.type === 'ClassSelector') { - if (!classMatches(node, part.name)) return false; + if (!attributeMatches(node, 'class', part.name, '~=', false)) return false; + } + + else if (part.type === 'IdSelector') { + if (!attributeMatches(node, 'id', part.name, '=', false)) return false; } else if (part.type === 'AttributeSelector') { - if (!attributeMatches(node, part)) return false; + if (!attributeMatches(node, part.name.name, part.value && unquote(part.value.value), part.operator, part.flags)) return false; } else if (part.type === 'TypeSelector') { @@ -112,16 +116,6 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { return true; } -function classMatches(node: Node, className: string) { - const attr = node.attributes.find((attr: Node) => attr.name === 'class'); - if (!attr || attr.value === true) return false; - if (isDynamic(attr.value)) return true; - - const value = attr.value[0].data; - - return value.split(' ').indexOf(className) !== -1; -} - const operators = { '=' : (value: string, flags: string) => new RegExp(`^${value}$`, flags), '~=': (value: string, flags: string) => new RegExp(`\\b${value}\\b`, flags), @@ -131,15 +125,15 @@ const operators = { '*=': (value: string, flags: string) => new RegExp(value, flags) }; -function attributeMatches(node: Node, selector: Node) { - const attr = node.attributes.find((attr: Node) => attr.name === selector.name.name); +function attributeMatches(node: Node, name: string, expectedValue: string, operator: string, caseInsensitive: boolean) { + const attr = node.attributes.find((attr: Node) => attr.name === name); if (!attr) return false; - if (attr.value === true || isDynamic(attr.value)) return true; + if (attr.value === true) return operator === null; + if (isDynamic(attr.value)) return true; - const expectedValue = unquote(selector.value.value); const actualValue = attr.value[0].data; - const pattern = operators[selector.operator](expectedValue, selector.flags || ''); + const pattern = operators[operator](expectedValue, caseInsensitive ? 'i' : ''); return pattern.test(actualValue); } diff --git a/test/css/samples/omit-scoping-attribute-id/_config.js b/test/css/samples/omit-scoping-attribute-id/_config.js new file mode 100644 index 000000000000..b37866f9b61a --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-id/_config.js @@ -0,0 +1,3 @@ +export default { + cascade: false +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-id/expected.css b/test/css/samples/omit-scoping-attribute-id/expected.css new file mode 100644 index 000000000000..d62cda1725ce --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-id/expected.css @@ -0,0 +1,4 @@ + + #foo[svelte-2727164615] { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-id/expected.html b/test/css/samples/omit-scoping-attribute-id/expected.html new file mode 100644 index 000000000000..f9e629de5673 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-id/expected.html @@ -0,0 +1,2 @@ +
+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-id/input.html b/test/css/samples/omit-scoping-attribute-id/input.html new file mode 100644 index 000000000000..9db4995a028b --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-id/input.html @@ -0,0 +1,8 @@ +
+
+ + \ No newline at end of file From 2ec0a850fc943db56fd44cbdbd4483f8d30d3842 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 1 Jul 2017 23:27:02 -0400 Subject: [PATCH 14/21] apply css optimisations to SSR --- src/generators/server-side-rendering/index.ts | 3 + .../server-side-rendering/preprocess.ts | 90 +++++++++++++++++++ .../server-side-rendering/visitors/Element.ts | 2 +- test/css/index.js | 41 ++++++--- .../expected.css | 2 +- .../expected.html | 4 +- .../input.html | 6 +- test/helpers.js | 41 ++++++--- 8 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 src/generators/server-side-rendering/preprocess.ts diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 29cfccd67a54..92c3599aeb50 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -1,6 +1,7 @@ import deindent from '../../utils/deindent'; import Generator from '../Generator'; import Block from './Block'; +import preprocess from './preprocess'; import visit from './visit'; import { removeNode, removeObjectKey } from '../../utils/removeNode'; import { Parsed, Node, CompileOptions } from '../../interfaces'; @@ -24,6 +25,8 @@ export class SsrGenerator extends Generator { // in an SSR context, we don't need to include events, methods, oncreate or ondestroy const { templateProperties, defaultExport } = this; + preprocess(this, parsed.html); + if (templateProperties.oncreate) removeNode( this.code, diff --git a/src/generators/server-side-rendering/preprocess.ts b/src/generators/server-side-rendering/preprocess.ts new file mode 100644 index 000000000000..c82fda9e9bc7 --- /dev/null +++ b/src/generators/server-side-rendering/preprocess.ts @@ -0,0 +1,90 @@ +import { SsrGenerator } from './index'; +import { Node } from '../../interfaces'; + +function noop () {} + +function isElseIf(node: Node) { + return ( + node && node.children.length === 1 && node.children[0].type === 'IfBlock' + ); +} + +const preprocessors = { + MustacheTag: noop, + RawMustacheTag: noop, + Text: noop, + + IfBlock: ( + generator: SsrGenerator, + node: Node, + elementStack: Node[] + ) => { + function attachBlocks(node: Node) { + preprocessChildren(generator, node, elementStack); + + if (isElseIf(node.else)) { + attachBlocks(node.else.children[0]); + } else if (node.else) { + preprocessChildren( + generator, + node.else, + elementStack + ); + } + } + + attachBlocks(node); + }, + + EachBlock: ( + generator: SsrGenerator, + node: Node, + elementStack: Node[] + ) => { + preprocessChildren(generator, node, elementStack); + + if (node.else) { + preprocessChildren( + generator, + node.else, + elementStack + ); + } + }, + + Element: ( + generator: SsrGenerator, + node: Node, + elementStack: Node[] + ) => { + const isComponent = + generator.components.has(node.name) || node.name === ':Self'; + + if (!isComponent) { + generator.applyCss(node, elementStack); + } + + if (node.children.length) { + if (isComponent) { + preprocessChildren(generator, node, elementStack); + } else { + preprocessChildren(generator, node, elementStack.concat(node)); + } + } + }, +}; + +function preprocessChildren( + generator: SsrGenerator, + node: Node, + elementStack: Node[] +) { + node.children.forEach((child: Node, i: number) => { + const preprocessor = preprocessors[child.type]; + if (preprocessor) preprocessor(generator, child, elementStack); + }); +} + +export default function preprocess(generator: SsrGenerator, html: Node) { + preprocessChildren(generator, html, []); +} \ No newline at end of file diff --git a/src/generators/server-side-rendering/visitors/Element.ts b/src/generators/server-side-rendering/visitors/Element.ts index 3540bd12f70b..765e16ec8a7f 100644 --- a/src/generators/server-side-rendering/visitors/Element.ts +++ b/src/generators/server-side-rendering/visitors/Element.ts @@ -56,7 +56,7 @@ export default function visitElement( } }); - if (generator.cssId && (!generator.cascade || generator.elementDepth === 0)) { + if (node._needsCssAttribute) { openingTag += ` ${generator.cssId}`; } diff --git a/test/css/index.js b/test/css/index.js index 3c4e42774685..e4f31f00a301 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -1,6 +1,6 @@ import assert from "assert"; import * as fs from "fs"; -import { env, svelte } from "../helpers.js"; +import { env, normalizeHtml, svelte } from "../helpers.js"; function tryRequire(file) { try { @@ -23,27 +23,36 @@ describe("css", () => { } (solo ? it.only : it)(dir, () => { - const config = Object.assign(tryRequire(`./samples/${dir}/_config.js`) || {}, { - format: 'iife', - name: 'SvelteComponent' - }); + const config = tryRequire(`./samples/${dir}/_config.js`) || {}; const input = fs .readFileSync(`test/css/samples/${dir}/input.html`, "utf-8") .replace(/\s+$/, ""); - const actual = svelte.compile(input, config); - fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, actual.css); + const dom = svelte.compile(input, Object.assign(config, { + format: 'iife', + name: 'SvelteComponent' + })); + + const ssr = svelte.compile(input, Object.assign(config, { + format: 'iife', + generate: 'ssr', + name: 'SvelteComponent' + })); + + assert.equal(dom.css, ssr.css); + + fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css); const expected = { html: read(`test/css/samples/${dir}/expected.html`), css: read(`test/css/samples/${dir}/expected.css`) }; - assert.equal(actual.css.trim(), expected.css.trim()); + assert.equal(dom.css.trim(), expected.css.trim()); // verify that the right elements have scoping selectors if (expected.html !== null) { return env().then(window => { - const Component = eval(`(function () { ${actual.code}; return SvelteComponent; }())`); + const Component = eval(`(function () { ${dom.code}; return SvelteComponent; }())`); const target = window.document.querySelector("main"); new Component({ target, data: config.data }); @@ -51,7 +60,19 @@ describe("css", () => { fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); - assert.equal(html.trim(), expected.html.trim()); + // dom + assert.equal( + normalizeHtml(window, html), + normalizeHtml(window, expected.html) + ); + + // ssr + const component = eval(`(function () { ${ssr.code}; return SvelteComponent; }())`); + + assert.equal( + normalizeHtml(window, component.render(config.data)), + normalizeHtml(window, expected.html) + ); }); } }); diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css index c57bf43bd42d..662e453d8382 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css @@ -1,4 +1,4 @@ - [data-foo][svelte-2966013849] { + [autoplay][svelte-240005720] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html index 77ed0898cff7..254afb2a46ed 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html @@ -1,2 +1,2 @@ -

this is styled

-

this is unstyled

\ No newline at end of file +
+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/input.html b/test/css/samples/omit-scoping-attribute-attribute-selector/input.html index eba59af19978..6f4549ead8d8 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector/input.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/input.html @@ -1,10 +1,10 @@
-

this is styled

-

this is unstyled

+ +
\ No newline at end of file diff --git a/test/helpers.js b/test/helpers.js index 8d9cd3a660e7..8087b173e4ef 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -69,6 +69,20 @@ export function env() { function cleanChildren(node) { let previous = null; + // sort attributes + const attributes = Array.from(node.attributes).sort((a, b) => { + return a.name < b.name ? -1 : 1; + }); + + attributes.forEach(attr => { + node.removeAttribute(attr.name); + }); + + attributes.forEach(attr => { + node.setAttribute(attr.name, attr.value); + }); + + // recurse [...node.childNodes].forEach(child => { if (child.nodeType === 8) { // comment @@ -114,22 +128,23 @@ function cleanChildren(node) { } } +export function normalizeHtml(window, html) { + const node = window.document.createElement('div'); + node.innerHTML = html + .replace(/>[\s\r\n]+<') + .trim(); + cleanChildren(node, ''); + return node.innerHTML; +} + export function setupHtmlEqual() { return env().then(window => { assert.htmlEqual = (actual, expected, message) => { - window.document.body.innerHTML = actual - .replace(/>[\s\r\n]+<') - .trim(); - cleanChildren(window.document.body, ''); - actual = window.document.body.innerHTML; - - window.document.body.innerHTML = expected - .replace(/>[\s\r\n]+<') - .trim(); - cleanChildren(window.document.body, ''); - expected = window.document.body.innerHTML; - - assert.deepEqual(actual, expected, message); + assert.deepEqual( + normalizeHtml(window, actual), + normalizeHtml(window, expected), + message + ); }; }); } From f79e90142959c3a3e4645a47af82aa879fb81463 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 2 Jul 2017 09:22:36 -0400 Subject: [PATCH 15/21] bug fix --- src/generators/extractSelectors.ts | 6 ++++-- .../_config.js | 7 +++++++ .../expected.css | 4 ++++ .../expected.html | 1 + .../input.html | 9 +++++++++ 5 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-inner-class/_config.js create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html create mode 100644 test/css/samples/omit-scoping-attribute-descendant-global-inner-class/input.html diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index c715b7744fd0..024542ddcc71 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -32,8 +32,10 @@ export default function extractSelectors(css: Node) :Node[] { const applies = selectorAppliesTo(parts, node, stack.slice()); if (applies) { + // add svelte-123xyz attribute to outermost and innermost + // elements — no need to add it to intermediate elements node._needsCssAttribute = true; - if (selector.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; + if (stack[0] && selector.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; } } }); @@ -43,7 +45,7 @@ export default function extractSelectors(css: Node) :Node[] { } function isDescendantSelector(selector: Node) { - return selector.type === 'WhiteSpace'; // TODO or '>' + return selector.type === 'WhiteSpace' || selector.type === 'Combinator'; } function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/_config.js new file mode 100644 index 000000000000..0371e65c7e35 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/_config.js @@ -0,0 +1,7 @@ +export default { + cascade: false, + + data: { + raw: '

raw

' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css new file mode 100644 index 000000000000..5b0fe980e66e --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css @@ -0,0 +1,4 @@ + + .foo[svelte-2122726581] .bar { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html new file mode 100644 index 000000000000..008c01737416 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/input.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/input.html new file mode 100644 index 000000000000..c2f6057255d0 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/input.html @@ -0,0 +1,9 @@ +
+ +
+ + \ No newline at end of file From 6f01d5f62f7988dc2b0764a1398d86d7c1dd367a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Jul 2017 11:42:36 -0400 Subject: [PATCH 16/21] refactor a bit, fix typing error --- src/generators/Generator.ts | 18 +++++-- .../{extractSelectors.ts => Selector.ts} | 47 +++++++++---------- 2 files changed, 35 insertions(+), 30 deletions(-) rename src/generators/{extractSelectors.ts => Selector.ts} (78%) diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index aa3771fea516..595cc12edb9b 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -13,7 +13,8 @@ import annotateWithScopes from '../utils/annotateWithScopes'; import clone from '../utils/clone'; import DomBlock from './dom/Block'; import SsrBlock from './server-side-rendering/Block'; -import extractSelectors from './extractSelectors'; +import { walkRules } from '../utils/css'; +import Selector from './Selector'; import { Node, Parsed, CompileOptions } from '../interfaces'; const test = typeof global !== 'undefined' && global.__svelte_test; @@ -41,7 +42,7 @@ export default class Generator { cssId: string; usesRefs: boolean; - selectors: any[]; // TODO how to indicate it takes `(Node[]) => boolean` functions? + selectors: Selector[]; importedNames: Set; aliases: Map; @@ -74,12 +75,21 @@ export default class Generator { this.expectedProperties = new Set(); this.code = new MagicString(source); + this.usesRefs = false; + + // styles this.cascade = options.cascade !== false; // TODO remove this option in v2 this.css = parsed.css ? processCss(parsed, this.code, this.cascade) : null; this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; - this.usesRefs = false; + this.selectors = []; - this.selectors = extractSelectors(parsed.css); + if (parsed.css) { + walkRules(parsed.css.children, node => { + node.selector.children.forEach((child: Node) => { + this.selectors.push(new Selector(child)); + }); + }); + } // allow compiler to deconflict user's `import { get } from 'whatever'` and // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; diff --git a/src/generators/extractSelectors.ts b/src/generators/Selector.ts similarity index 78% rename from src/generators/extractSelectors.ts rename to src/generators/Selector.ts index 024542ddcc71..4a1c566b69d3 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/Selector.ts @@ -1,21 +1,19 @@ import { walkRules } from '../utils/css'; import { Node } from '../interfaces'; -export default function extractSelectors(css: Node) :Node[] { - if (!css) return []; +export default class Selector { + node: Node; + parts: Node[]; + used: boolean; - const selectors = []; + constructor(node: Node) { + this.node = node; - walkRules(css.children, node => { - node.selector.children.forEach(processSelector); - }); - - function processSelector(selector: Node) { // take trailing :global(...) selectors out of consideration - let i = selector.children.length; + let i = node.children.length; while (i > 2) { - const last = selector.children[i-1]; - const penultimate = selector.children[i-2]; + const last = node.children[i-1]; + const penultimate = node.children[i-2]; if (last.type === 'PseudoClassSelector' && last.name === 'global') { i -= 2; @@ -24,24 +22,21 @@ export default function extractSelectors(css: Node) :Node[] { } } - const parts = selector.children.slice(0, i); - - selectors.push({ - used: false, // TODO use this! warn on unused selectors - apply: (node: Node, stack: Node[]) => { - const applies = selectorAppliesTo(parts, node, stack.slice()); + this.parts = node.children.slice(0, i); - if (applies) { - // add svelte-123xyz attribute to outermost and innermost - // elements — no need to add it to intermediate elements - node._needsCssAttribute = true; - if (stack[0] && selector.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; - } - } - }); + this.used = false; // TODO use this! warn on unused selectors } - return selectors; + apply(node: Node, stack: Node[]) { + const applies = selectorAppliesTo(this.parts, node, stack.slice()); + + if (applies) { + // add svelte-123xyz attribute to outermost and innermost + // elements — no need to add it to intermediate elements + node._needsCssAttribute = true; + if (stack[0] && this.node.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; + } + } } function isDescendantSelector(selector: Node) { From 205bcfac7e6dadc754971bb28db9994993a7c99f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Jul 2017 16:38:36 -0400 Subject: [PATCH 17/21] warn on unused selectors --- src/generators/Generator.ts | 29 +++++++++++++++++++ src/generators/Selector.ts | 9 ++++-- src/generators/dom/index.ts | 2 ++ src/generators/server-side-rendering/index.ts | 2 ++ src/interfaces.ts | 4 ++- src/validate/index.ts | 4 +-- test/css/index.js | 27 +++++++++++++++-- .../_config.js | 18 +++++++++++- test/css/samples/unused-selector/_config.js | 20 +++++++++++++ test/css/samples/unused-selector/expected.css | 8 +++++ .../css/samples/unused-selector/expected.html | 1 + test/css/samples/unused-selector/input.html | 11 +++++++ .../css/samples/unused-selector/warnings.json | 10 +++++++ 13 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 test/css/samples/unused-selector/_config.js create mode 100644 test/css/samples/unused-selector/expected.css create mode 100644 test/css/samples/unused-selector/expected.html create mode 100644 test/css/samples/unused-selector/input.html create mode 100644 test/css/samples/unused-selector/warnings.json diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 595cc12edb9b..6eff2f2a3d7e 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -1,5 +1,7 @@ import MagicString, { Bundle } from 'magic-string'; import { walk } from 'estree-walker'; +import { getLocator } from 'locate-character'; +import getCodeFrame from '../utils/getCodeFrame'; import isReference from '../utils/isReference'; import flattenReference from '../utils/flattenReference'; import globalWhitelist from '../utils/globalWhitelist'; @@ -619,4 +621,31 @@ export default class Generator { this.namespace = namespace; this.templateProperties = templateProperties; } + + warnOnUnusedSelectors() { + if (this.cascade) return; + + let locator; + + this.selectors.forEach((selector: Selector) => { + if (!selector.used) { + const pos = selector.node.start; + + if (!locator) locator = getLocator(this.source); + const { line, column } = locator(pos); + + const frame = getCodeFrame(this.source, line, column); + const message = `Unused CSS selector`; + + this.options.onwarn({ + message, + frame, + loc: { line: line + 1, column }, + pos, + filename: this.options.filename, + toString: () => `${message} (${line + 1}:${column})\n${frame}`, + }); + } + }); + } } diff --git a/src/generators/Selector.ts b/src/generators/Selector.ts index 4a1c566b69d3..862bbea650a4 100644 --- a/src/generators/Selector.ts +++ b/src/generators/Selector.ts @@ -1,14 +1,17 @@ -import { walkRules } from '../utils/css'; +import { groupSelectors, isGlobalSelector, walkRules } from '../utils/css'; import { Node } from '../interfaces'; export default class Selector { node: Node; + blocks: Node[][]; parts: Node[]; used: boolean; constructor(node: Node) { this.node = node; + this.blocks = groupSelectors(this.node); + // take trailing :global(...) selectors out of consideration let i = node.children.length; while (i > 2) { @@ -24,13 +27,15 @@ export default class Selector { this.parts = node.children.slice(0, i); - this.used = false; // TODO use this! warn on unused selectors + this.used = isGlobalSelector(this.blocks[0]); } apply(node: Node, stack: Node[]) { const applies = selectorAppliesTo(this.parts, node, stack.slice()); if (applies) { + this.used = true; + // add svelte-123xyz attribute to outermost and innermost // elements — no need to add it to intermediate elements node._needsCssAttribute = true; diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 1035f83643d5..2810f2cbd143 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -61,6 +61,8 @@ export default function dom( const { block, state } = preprocess(generator, namespace, parsed.html); + generator.warnOnUnusedSelectors(); + parsed.html.children.forEach((node: Node) => { visit(generator, block, state, node, []); }); diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 92c3599aeb50..ff18b0687888 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -27,6 +27,8 @@ export class SsrGenerator extends Generator { preprocess(this, parsed.html); + this.warnOnUnusedSelectors(); + if (templateProperties.oncreate) removeNode( this.code, diff --git a/src/interfaces.ts b/src/interfaces.ts index fbb745b7b8e1..78fc8730b985 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -26,9 +26,11 @@ export interface Parsed { } export interface Warning { - loc?: { line: number; column: number; pos: number }; + loc?: { line: number; column: number; pos?: number }; + pos?: number; message: string; filename?: string; + frame?: string; toString: () => string; } diff --git a/src/validate/index.ts b/src/validate/index.ts index 32a6a82c9268..62cb9e614d11 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -35,9 +35,9 @@ export class Validator { constructor(parsed: Parsed, source: string, options: CompileOptions) { this.source = source; - this.filename = options !== undefined ? options.filename : undefined; + this.filename = options.filename; - this.onwarn = options !== undefined ? options.onwarn : undefined; + this.onwarn = options.onwarn; this.namespace = null; this.defaultExport = null; diff --git a/test/css/index.js b/test/css/index.js index e4f31f00a301..221f67973908 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -4,13 +4,21 @@ import { env, normalizeHtml, svelte } from "../helpers.js"; function tryRequire(file) { try { - return require(file).default; + const mod = require(file); + return mod.default || mod; } catch (err) { if (err.code !== "MODULE_NOT_FOUND") throw err; return null; } } +function normalizeWarning(warning) { + warning.frame = warning.frame.replace(/^\n/, '').replace(/^\t+/gm, ''); + delete warning.filename; + delete warning.toString; + return warning; +} + describe("css", () => { fs.readdirSync("test/css/samples").forEach(dir => { if (dir[0] === ".") return; @@ -28,19 +36,32 @@ describe("css", () => { .readFileSync(`test/css/samples/${dir}/input.html`, "utf-8") .replace(/\s+$/, ""); + const expectedWarnings = (config.warnings || []).map(normalizeWarning); + const domWarnings = []; + const ssrWarnings = []; + const dom = svelte.compile(input, Object.assign(config, { format: 'iife', - name: 'SvelteComponent' + name: 'SvelteComponent', + onwarn: warning => { + domWarnings.push(warning); + } })); const ssr = svelte.compile(input, Object.assign(config, { format: 'iife', generate: 'ssr', - name: 'SvelteComponent' + name: 'SvelteComponent', + onwarn: warning => { + ssrWarnings.push(warning); + } })); assert.equal(dom.css, ssr.css); + assert.deepEqual(domWarnings.map(normalizeWarning), ssrWarnings.map(normalizeWarning)); + assert.deepEqual(domWarnings.map(normalizeWarning), expectedWarnings); + fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css); const expected = { html: read(`test/css/samples/${dir}/expected.html`), diff --git a/test/css/samples/omit-scoping-attribute-descendant/_config.js b/test/css/samples/omit-scoping-attribute-descendant/_config.js index b37866f9b61a..0d56c7ba8c09 100644 --- a/test/css/samples/omit-scoping-attribute-descendant/_config.js +++ b/test/css/samples/omit-scoping-attribute-descendant/_config.js @@ -1,3 +1,19 @@ export default { - cascade: false + cascade: false, + + warnings: [{ + message: 'Unused CSS selector', + loc: { + line: 8, + column: 1 + }, + pos: 74, + frame: ` + 6: + 7: \ No newline at end of file diff --git a/test/css/samples/unused-selector/warnings.json b/test/css/samples/unused-selector/warnings.json new file mode 100644 index 000000000000..6d6c3f5deba2 --- /dev/null +++ b/test/css/samples/unused-selector/warnings.json @@ -0,0 +1,10 @@ +[{ + "filename": "SvelteComponent.html", + "message": "Unused CSS selector", + "loc": { + "line": 8, + "column": 1 + }, + "pos": 61, + "frame": " 6: }\n 7: \n 8: .bar {\n ^\n 9: color: blue;\n10: }" +}] \ No newline at end of file From 2c9fb31a9e1a98ecf9a9d412b210f49f79f5e280 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Jul 2017 17:34:26 -0400 Subject: [PATCH 18/21] simplify tests --- test/css/index.js | 6 +++--- test/css/samples/basic/expected.css | 2 +- .../samples/cascade-false-global-keyframes/expected.css | 4 ++-- test/css/samples/cascade-false-global/input.html | 2 +- test/css/samples/cascade-false-keyframes/expected.css | 8 ++++---- .../css/samples/cascade-false-pseudo-element/expected.css | 6 +++--- .../samples/cascade-false-universal-selector/expected.css | 2 +- .../cascade-false-universal-selector/expected.html | 2 +- test/css/samples/cascade-false/expected.css | 6 +++--- test/css/samples/keyframes/expected.css | 6 +++--- test/css/samples/media-query/expected.css | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 4 ++-- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../omit-scoping-attribute-class-dynamic/expected.css | 2 +- .../omit-scoping-attribute-class-dynamic/expected.html | 2 +- .../omit-scoping-attribute-class-static/expected.css | 2 +- .../omit-scoping-attribute-class-static/expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../omit-scoping-attribute-descendant/expected.css | 2 +- test/css/samples/omit-scoping-attribute-id/expected.css | 2 +- test/css/samples/omit-scoping-attribute-id/expected.html | 2 +- .../expected.css | 2 +- .../expected.html | 2 +- .../omit-scoping-attribute-whitespace/expected.css | 2 +- .../omit-scoping-attribute-whitespace/expected.html | 2 +- test/css/samples/omit-scoping-attribute/expected.css | 2 +- test/css/samples/omit-scoping-attribute/expected.html | 2 +- test/css/samples/universal-selector/expected.css | 2 +- test/validator/samples/css-invalid-global/errors.json | 6 +++--- test/validator/samples/css-invalid-global/input.html | 2 +- 55 files changed, 70 insertions(+), 70 deletions(-) diff --git a/test/css/index.js b/test/css/index.js index e4f31f00a301..4e1cf23b5caa 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -47,7 +47,7 @@ describe("css", () => { css: read(`test/css/samples/${dir}/expected.css`) }; - assert.equal(dom.css.trim(), expected.css.trim()); + assert.equal(dom.css.replace(/svelte-\d+/g, 'svelte-xyz').trim(), expected.css.trim()); // verify that the right elements have scoping selectors if (expected.html !== null) { @@ -62,7 +62,7 @@ describe("css", () => { // dom assert.equal( - normalizeHtml(window, html), + normalizeHtml(window, html).replace(/svelte-\d+/g, 'svelte-xyz'), normalizeHtml(window, expected.html) ); @@ -70,7 +70,7 @@ describe("css", () => { const component = eval(`(function () { ${ssr.code}; return SvelteComponent; }())`); assert.equal( - normalizeHtml(window, component.render(config.data)), + normalizeHtml(window, component.render(config.data)).replace(/svelte-\d+/g, 'svelte-xyz'), normalizeHtml(window, expected.html) ); }); diff --git a/test/css/samples/basic/expected.css b/test/css/samples/basic/expected.css index 0d52d6d95f08..1ece91b2e62f 100644 --- a/test/css/samples/basic/expected.css +++ b/test/css/samples/basic/expected.css @@ -1,4 +1,4 @@ - div[svelte-2278551596], [svelte-2278551596] div { + div[svelte-xyz], [svelte-xyz] div { color: red; } diff --git a/test/css/samples/cascade-false-global-keyframes/expected.css b/test/css/samples/cascade-false-global-keyframes/expected.css index 042db534263b..a0741615b0c1 100644 --- a/test/css/samples/cascade-false-global-keyframes/expected.css +++ b/test/css/samples/cascade-false-global-keyframes/expected.css @@ -4,10 +4,10 @@ 100% { color: blue; } } - .animated[svelte-90785995] { + .animated[svelte-xyz] { animation: why 2s; } - .also-animated[svelte-90785995] { + .also-animated[svelte-xyz] { animation: not-defined-here 2s; } diff --git a/test/css/samples/cascade-false-global/input.html b/test/css/samples/cascade-false-global/input.html index 947edb6ab57d..0350c826f523 100644 --- a/test/css/samples/cascade-false-global/input.html +++ b/test/css/samples/cascade-false-global/input.html @@ -6,7 +6,7 @@ color: red; } - :global(div.foo) { + :global(div).foo { color: blue; } diff --git a/test/css/samples/cascade-false-keyframes/expected.css b/test/css/samples/cascade-false-keyframes/expected.css index 5377c86da7c2..583014ae4154 100644 --- a/test/css/samples/cascade-false-keyframes/expected.css +++ b/test/css/samples/cascade-false-keyframes/expected.css @@ -1,13 +1,13 @@ - @keyframes svelte-1647166666-why { + @keyframes svelte-xyz-why { 0% { color: red; } 100% { color: blue; } } - .animated[svelte-1647166666] { - animation: svelte-1647166666-why 2s; + .animated[svelte-xyz] { + animation: svelte-xyz-why 2s; } - .also-animated[svelte-1647166666] { + .also-animated[svelte-xyz] { animation: not-defined-here 2s; } diff --git a/test/css/samples/cascade-false-pseudo-element/expected.css b/test/css/samples/cascade-false-pseudo-element/expected.css index 3488839c46aa..5ee54847cb1c 100644 --- a/test/css/samples/cascade-false-pseudo-element/expected.css +++ b/test/css/samples/cascade-false-pseudo-element/expected.css @@ -1,12 +1,12 @@ - span[svelte-2146001331]::after { + span[svelte-xyz]::after { content: 'i am a pseudo-element'; } - span[svelte-2146001331]:first-child { + span[svelte-xyz]:first-child { color: red; } - span[svelte-2146001331]:last-child::after { + span[svelte-xyz]:last-child::after { color: blue; } diff --git a/test/css/samples/cascade-false-universal-selector/expected.css b/test/css/samples/cascade-false-universal-selector/expected.css index bb9155feb061..ca0a6de74250 100644 --- a/test/css/samples/cascade-false-universal-selector/expected.css +++ b/test/css/samples/cascade-false-universal-selector/expected.css @@ -1,4 +1,4 @@ - [svelte-2950902288] { + [svelte-xyz] { color: red; } diff --git a/test/css/samples/cascade-false-universal-selector/expected.html b/test/css/samples/cascade-false-universal-selector/expected.html index 732e063d51d3..e274b3e5098d 100644 --- a/test/css/samples/cascade-false-universal-selector/expected.html +++ b/test/css/samples/cascade-false-universal-selector/expected.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/test/css/samples/cascade-false/expected.css b/test/css/samples/cascade-false/expected.css index 373c5aefed6a..39677a203a71 100644 --- a/test/css/samples/cascade-false/expected.css +++ b/test/css/samples/cascade-false/expected.css @@ -1,12 +1,12 @@ - div[svelte-781920915] { + div[svelte-xyz] { color: red; } - div.foo[svelte-781920915] { + div.foo[svelte-xyz] { color: blue; } - .foo[svelte-781920915] { + .foo[svelte-xyz] { font-weight: bold; } diff --git a/test/css/samples/keyframes/expected.css b/test/css/samples/keyframes/expected.css index c7d8540bdd69..53d24fc691a2 100644 --- a/test/css/samples/keyframes/expected.css +++ b/test/css/samples/keyframes/expected.css @@ -1,9 +1,9 @@ - @keyframes svelte-2931302006-why { + @keyframes svelte-xyz-why { 0% { color: red; } 100% { color: blue; } } - [svelte-2931302006].animated, [svelte-2931302006] .animated { - animation: svelte-2931302006-why 2s; + [svelte-xyz].animated, [svelte-xyz] .animated { + animation: svelte-xyz-why 2s; } diff --git a/test/css/samples/media-query/expected.css b/test/css/samples/media-query/expected.css index cecc50d8609b..5ea1a960592a 100644 --- a/test/css/samples/media-query/expected.css +++ b/test/css/samples/media-query/expected.css @@ -1,6 +1,6 @@ @media (min-width: 400px) { - [svelte-411199634].large-screen, [svelte-411199634] .large-screen { + [svelte-xyz].large-screen, [svelte-xyz] .large-screen { display: block; } } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css index b48dfbe85a86..957354dc6a51 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css @@ -1,4 +1,4 @@ - [data-foo*='bar'][svelte-4224841812] { + [data-foo*='bar'][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html index 22a45b853e2b..c69d6b03c98e 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css index d20a6b98bf10..218ef835be84 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.css @@ -1,4 +1,4 @@ - [data-foo='bar' i][svelte-4191913977] { + [data-foo='bar' i][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html index 1d65033d3e0a..4da16a0c2ff6 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-case-insensitive/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css index 26fdc211a078..636d4206b95e 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css @@ -1,4 +1,4 @@ - [data-foo='bar'][svelte-1339732946] { + [data-foo='bar'][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html index e27985b20d1c..f9cc8d8b5fa8 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css index 4a2cb3428e7b..636d4206b95e 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css @@ -1,4 +1,4 @@ - [data-foo='bar'][svelte-2543760126] { + [data-foo='bar'][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html index 6dcb8cfaa365..4579673c467f 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css index 8b57a24cd9d3..590f946bfbe4 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css @@ -1,4 +1,4 @@ - [data-foo|='bar'][svelte-1225676040] { + [data-foo|='bar'][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html index 1ba93cd6e50b..37880da66775 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.html @@ -1,3 +1,3 @@ -

this is styled

-

this is styled

+

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css index 232c462c4b04..8bcb51086730 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css @@ -1,4 +1,4 @@ - [data-foo^='bar'][svelte-3106767242] { + [data-foo^='bar'][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html index 267528a5168d..a929494570e0 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css index 832f50cf3be7..2d209085d0f5 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css @@ -1,4 +1,4 @@ - [data-foo$='bar'][svelte-207782622] { + [data-foo$='bar'][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html index 00bf78a6ca82..71abcb7fa58b 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.html @@ -1,2 +1,2 @@

this is unstyled

-

this is styled

\ No newline at end of file +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css index 20cf2fd9e380..fcacb8704f28 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css @@ -1,4 +1,4 @@ - [data-foo~='bar'][svelte-1786044856] { + [data-foo~='bar'][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html index aef480d8ef24..64244097a249 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css index 662e453d8382..283a92edca12 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css @@ -1,4 +1,4 @@ - [autoplay][svelte-240005720] { + [autoplay][svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html index 254afb2a46ed..6a5b1043889c 100644 --- a/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html @@ -1,2 +1,2 @@ -
+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css index 45b763d4de37..ee6e96c597cc 100644 --- a/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css @@ -1,4 +1,4 @@ - .foo[svelte-2643270928] { + .foo[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html index a51f22d968a2..c45e8d88a614 100644 --- a/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-class-static/expected.css b/test/css/samples/omit-scoping-attribute-class-static/expected.css index bf4e3cb73c45..ee6e96c597cc 100644 --- a/test/css/samples/omit-scoping-attribute-class-static/expected.css +++ b/test/css/samples/omit-scoping-attribute-class-static/expected.css @@ -1,4 +1,4 @@ - .foo[svelte-633959357] { + .foo[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-class-static/expected.html b/test/css/samples/omit-scoping-attribute-class-static/expected.html index b972aa4cb195..24687444c480 100644 --- a/test/css/samples/omit-scoping-attribute-class-static/expected.html +++ b/test/css/samples/omit-scoping-attribute-class-static/expected.html @@ -1,2 +1,2 @@ -

this is styled

+

this is styled

this is unstyled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css index 5b0fe980e66e..e8a40fc90830 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css @@ -1,4 +1,4 @@ - .foo[svelte-2122726581] .bar { + .foo[svelte-xyz] .bar { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html index 008c01737416..bbe7db594631 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css index d09033651f46..93ab1b04e595 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css @@ -1,4 +1,4 @@ - div[svelte-3386191472] > p > em { + div[svelte-xyz] > p > em { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html index db137321742e..e274b3e5098d 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.css index 67a7fbed8682..92d8f497d7ab 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.css +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.css @@ -1,4 +1,4 @@ - div[svelte-3744486606] > p { + div[svelte-xyz] > p { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.html index 4511b7222133..e274b3e5098d 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.html +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css index 9137c5bd8d00..fa651566fb5e 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css @@ -1,4 +1,4 @@ - div > section > p[svelte-3390623146] { + div > section > p[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html index 35cfecee4fb5..c3b07834462a 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.html @@ -1 +1 @@ -

this may or may not be styled

\ No newline at end of file +

this may or may not be styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css index 46c00c646dfd..f62e2305201e 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css @@ -1,4 +1,4 @@ - div > p[svelte-794545435] { + div > p[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.html b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.html index 5c3c80ea93b8..c3b07834462a 100644 --- a/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.html +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.html @@ -1 +1 @@ -

this may or may not be styled

\ No newline at end of file +

this may or may not be styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant/expected.css b/test/css/samples/omit-scoping-attribute-descendant/expected.css index 548ef810ef97..12d8add9303e 100644 --- a/test/css/samples/omit-scoping-attribute-descendant/expected.css +++ b/test/css/samples/omit-scoping-attribute-descendant/expected.css @@ -1,4 +1,4 @@ - div[svelte-2557028325] > p[svelte-2557028325] { + div[svelte-xyz] > p[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-id/expected.css b/test/css/samples/omit-scoping-attribute-id/expected.css index d62cda1725ce..09133ba7ffba 100644 --- a/test/css/samples/omit-scoping-attribute-id/expected.css +++ b/test/css/samples/omit-scoping-attribute-id/expected.css @@ -1,4 +1,4 @@ - #foo[svelte-2727164615] { + #foo[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-id/expected.html b/test/css/samples/omit-scoping-attribute-id/expected.html index f9e629de5673..cb6d2f229267 100644 --- a/test/css/samples/omit-scoping-attribute-id/expected.html +++ b/test/css/samples/omit-scoping-attribute-id/expected.html @@ -1,2 +1,2 @@ -
+
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css index 590f54700608..b9da332c6fbf 100644 --- a/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css +++ b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css @@ -1,4 +1,4 @@ - div[svelte-4032668709] section p[svelte-4032668709] { + div[svelte-xyz] section p[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.html b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.html index 7a3dea14316b..874ad340127b 100644 --- a/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.html +++ b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.html @@ -1 +1 @@ -

this is styled

\ No newline at end of file +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-whitespace/expected.css b/test/css/samples/omit-scoping-attribute-whitespace/expected.css index fedf7985e148..cf24b2c3cc12 100644 --- a/test/css/samples/omit-scoping-attribute-whitespace/expected.css +++ b/test/css/samples/omit-scoping-attribute-whitespace/expected.css @@ -1,4 +1,4 @@ - div[svelte-4240344240] p[svelte-4240344240] { + div[svelte-xyz] p[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute-whitespace/expected.html b/test/css/samples/omit-scoping-attribute-whitespace/expected.html index 06092e315c54..874ad340127b 100644 --- a/test/css/samples/omit-scoping-attribute-whitespace/expected.html +++ b/test/css/samples/omit-scoping-attribute-whitespace/expected.html @@ -1 +1 @@ -

this is styled

\ No newline at end of file +

this is styled

\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute/expected.css b/test/css/samples/omit-scoping-attribute/expected.css index f8adbd21a705..4bd7298c3da6 100644 --- a/test/css/samples/omit-scoping-attribute/expected.css +++ b/test/css/samples/omit-scoping-attribute/expected.css @@ -1,4 +1,4 @@ - p[svelte-1997768937] { + p[svelte-xyz] { color: red; } diff --git a/test/css/samples/omit-scoping-attribute/expected.html b/test/css/samples/omit-scoping-attribute/expected.html index 580168ced2e7..60c8d1ce9c55 100644 --- a/test/css/samples/omit-scoping-attribute/expected.html +++ b/test/css/samples/omit-scoping-attribute/expected.html @@ -1 +1 @@ -

this is styled

\ No newline at end of file +

this is styled

\ No newline at end of file diff --git a/test/css/samples/universal-selector/expected.css b/test/css/samples/universal-selector/expected.css index 53f57d90839c..ad9785f26416 100644 --- a/test/css/samples/universal-selector/expected.css +++ b/test/css/samples/universal-selector/expected.css @@ -1,4 +1,4 @@ - [svelte-2950902288], [svelte-2950902288] * { + [svelte-xyz], [svelte-xyz] * { color: red; } diff --git a/test/validator/samples/css-invalid-global/errors.json b/test/validator/samples/css-invalid-global/errors.json index 8aa138e3f2f2..71cb1f12610e 100644 --- a/test/validator/samples/css-invalid-global/errors.json +++ b/test/validator/samples/css-invalid-global/errors.json @@ -1,8 +1,8 @@ [{ - "message": ":global(...) cannot be mixed with non-global selectors", + "message": ":global(...) must be the first element in a compound selector", "loc": { "line": 2, - "column": 1 + "column": 5 }, - "pos": 9 + "pos": 13 }] \ No newline at end of file diff --git a/test/validator/samples/css-invalid-global/input.html b/test/validator/samples/css-invalid-global/input.html index 5d325a4cd18f..518674424a00 100644 --- a/test/validator/samples/css-invalid-global/input.html +++ b/test/validator/samples/css-invalid-global/input.html @@ -1,5 +1,5 @@ \ No newline at end of file From 5f6db885387990d51fa8eb541e9b39127fdabfa5 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Jul 2017 17:35:39 -0400 Subject: [PATCH 19/21] allow :global(...) selectors to have trailing modifiers --- src/utils/css.ts | 2 +- src/validate/css/index.ts | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/utils/css.ts b/src/utils/css.ts index ac78bdd96a0e..ec547bd784a3 100644 --- a/src/utils/css.ts +++ b/src/utils/css.ts @@ -1,7 +1,7 @@ import { Node } from '../interfaces'; export function isGlobalSelector(block: Node[]) { - return block.length === 1 && block[0].type === 'PseudoClassSelector' && block[0].name === 'global'; + return block[0].type === 'PseudoClassSelector' && block[0].name === 'global'; } export function groupSelectors(selector: Node) { diff --git a/src/validate/css/index.ts b/src/validate/css/index.ts index 6251f59fb0a3..8b90eff09c3a 100644 --- a/src/validate/css/index.ts +++ b/src/validate/css/index.ts @@ -10,17 +10,13 @@ export default function validateCss(validator: Validator, css: Node) { function validateSelector(selector: Node) { const blocks: Node[][] = groupSelectors(selector); - blocks.forEach((block, i) => { - if (block.find((part: Node) => part.type === 'PseudoClassSelector' && part.name === 'global')) { - // check that :global(...) is by itself - if (block.length !== 1) { - validator.error(`:global(...) cannot be mixed with non-global selectors`, block[0].start); + blocks.forEach((block) => { + let i = block.length; + while (i-- > 1) { + const part = block[i]; + if (part.type === 'PseudoClassSelector' && part.name === 'global') { + validator.error(`:global(...) must be the first element in a compound selector`, part.start); } - - // check that :global(...) isn't sandwiched by other selectors - // if (i > 0 && i < blocks.length - 1) { - // validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, block[0].start); - // } } }); From 0c33eb4b1b8fb3c2cabb206056de91e733f7e968 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 3 Jul 2017 18:06:11 -0400 Subject: [PATCH 20/21] fix tests --- test/css/samples/cascade-false-global/input.html | 2 +- test/css/samples/unused-selector/expected.css | 4 ++-- test/css/samples/unused-selector/expected.html | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/css/samples/cascade-false-global/input.html b/test/css/samples/cascade-false-global/input.html index 0350c826f523..947edb6ab57d 100644 --- a/test/css/samples/cascade-false-global/input.html +++ b/test/css/samples/cascade-false-global/input.html @@ -6,7 +6,7 @@ color: red; } - :global(div).foo { + :global(div.foo) { color: blue; } diff --git a/test/css/samples/unused-selector/expected.css b/test/css/samples/unused-selector/expected.css index 76559bfb61af..c9e2ae86c0f5 100644 --- a/test/css/samples/unused-selector/expected.css +++ b/test/css/samples/unused-selector/expected.css @@ -1,8 +1,8 @@ - .foo[svelte-698347462] { + .foo[svelte-xyz] { color: red; } - .bar[svelte-698347462] { + .bar[svelte-xyz] { color: blue; } diff --git a/test/css/samples/unused-selector/expected.html b/test/css/samples/unused-selector/expected.html index 083a321ea7ad..bbe7db594631 100644 --- a/test/css/samples/unused-selector/expected.html +++ b/test/css/samples/unused-selector/expected.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file From 7a752df55d9a1150a30740425d2436e3322b3d01 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 5 Jul 2017 11:13:12 -0400 Subject: [PATCH 21/21] fix handling of modified :global(...) selectors --- src/generators/Generator.ts | 5 +- src/generators/Selector.ts | 34 +++++++++- src/generators/shared/processCss.ts | 62 +++++-------------- src/utils/css.ts | 22 +++++-- src/validate/css/index.ts | 14 ++--- .../samples/cascade-false-global/input.html | 2 +- 6 files changed, 77 insertions(+), 62 deletions(-) diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 6eff2f2a3d7e..d1cee03e1aa0 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -81,7 +81,6 @@ export default class Generator { // styles this.cascade = options.cascade !== false; // TODO remove this option in v2 - this.css = parsed.css ? processCss(parsed, this.code, this.cascade) : null; this.cssId = parsed.css ? `svelte-${parsed.hash}` : ''; this.selectors = []; @@ -91,6 +90,10 @@ export default class Generator { this.selectors.push(new Selector(child)); }); }); + + this.css = processCss(this, this.code, this.cascade); + } else { + this.css = null; } // allow compiler to deconflict user's `import { get } from 'whatever'` and diff --git a/src/generators/Selector.ts b/src/generators/Selector.ts index 862bbea650a4..e7a23489c131 100644 --- a/src/generators/Selector.ts +++ b/src/generators/Selector.ts @@ -1,9 +1,10 @@ +import MagicString from 'magic-string'; import { groupSelectors, isGlobalSelector, walkRules } from '../utils/css'; import { Node } from '../interfaces'; export default class Selector { node: Node; - blocks: Node[][]; + blocks: any; // TODO parts: Node[]; used: boolean; @@ -27,7 +28,7 @@ export default class Selector { this.parts = node.children.slice(0, i); - this.used = isGlobalSelector(this.blocks[0]); + this.used = this.blocks[0].global; } apply(node: Node, stack: Node[]) { @@ -42,6 +43,35 @@ export default class Selector { if (stack[0] && this.node.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; } } + + transform(code: MagicString, attr: string) { + function encapsulateBlock(block) { + let i = block.selectors.length; + while (i--) { + const selector = block.selectors[i]; + if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') continue; + + if (selector.type === 'TypeSelector' && selector.name === '*') { + code.overwrite(selector.start, selector.end, attr); + } else { + code.appendLeft(selector.end, attr); + } + + return; + } + } + + this.blocks.forEach((block, i) => { + if (block.global) { + const selector = block.selectors[0]; + const first = selector.children[0]; + const last = selector.children[selector.children.length - 1]; + code.remove(selector.start, first.start).remove(last.end, selector.end); + } else if (i === 0 || i === this.blocks.length - 1) { + encapsulateBlock(block); + } + }); + } } function isDescendantSelector(selector: Node) { diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index 15719e7b8f72..5caec5523946 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -1,18 +1,19 @@ import MagicString from 'magic-string'; import { groupSelectors, isGlobalSelector, walkRules } from '../../utils/css'; -import { Parsed, Node } from '../../interfaces'; +import Generator from '../Generator'; +import { Node } from '../../interfaces'; const commentsPattern = /\/\*[\s\S]*?\*\//g; export default function processCss( - parsed: Parsed, + generator: Generator, code: MagicString, cascade: boolean ) { - const css = parsed.css.content.styles; - const offset = parsed.css.content.start; + const css = generator.parsed.css.content.styles; + const offset = generator.parsed.css.content.start; - const attr = `[svelte-${parsed.hash}]`; + const attr = `[svelte-${generator.parsed.hash}]`; const keyframes = new Map(); @@ -23,7 +24,7 @@ export default function processCss( if (expression.name.startsWith('-global-')) { code.remove(expression.start, expression.start + 8); } else { - const newName = `svelte-${parsed.hash}-${expression.name}`; + const newName = `svelte-${generator.parsed.hash}-${expression.name}`; code.overwrite(expression.start, expression.end, newName); keyframes.set(expression.name, newName); } @@ -36,23 +37,7 @@ export default function processCss( } } - parsed.css.children.forEach(walkKeyframes); - - function encapsulateBlock(block: Node[]) { - let i = block.length; - while (i--) { - const child = block[i]; - if (child.type === 'PseudoElementSelector' || child.type === 'PseudoClassSelector') continue; - - if (child.type === 'TypeSelector' && child.name === '*') { - code.overwrite(child.start, child.end, attr); - } else { - code.appendLeft(child.end, attr); - } - - return; - } - } + generator.parsed.css.children.forEach(walkKeyframes); function transform(rule: Node) { rule.selector.children.forEach((selector: Node) => { @@ -78,27 +63,6 @@ export default function processCss( } code.overwrite(selector.start, selector.end, transformed); - } else { - let shouldTransform = true; - let c = selector.start; - - // separate .foo > .bar > .baz into three separate blocks, so - // that we can transform only the first and last - let block: Node[] = []; - const blocks: Node[][] = groupSelectors(selector); - - blocks.forEach((block: Node[], i) => { - if (i === 0 || i === blocks.length - 1) { - encapsulateBlock(blocks[i]); - } - - if (isGlobalSelector(block)) { - const selector = block[0]; - const first = selector.children[0]; - const last = selector.children[selector.children.length - 1]; - code.remove(selector.start, first.start).remove(last.end, selector.end); - } - }); } }); @@ -119,7 +83,13 @@ export default function processCss( }); } - walkRules(parsed.css.children, transform); + walkRules(generator.parsed.css.children, transform); + + if (!cascade) { + generator.selectors.forEach(selector => { + selector.transform(code, attr); + }); + } // remove comments. TODO would be nice if this was exposed in css-tree let match; @@ -130,5 +100,5 @@ export default function processCss( code.remove(start, end); } - return code.slice(parsed.css.content.start, parsed.css.content.end); + return code.slice(generator.parsed.css.content.start, generator.parsed.css.content.end); } diff --git a/src/utils/css.ts b/src/utils/css.ts index ec547bd784a3..1e00d657e485 100644 --- a/src/utils/css.ts +++ b/src/utils/css.ts @@ -5,15 +5,27 @@ export function isGlobalSelector(block: Node[]) { } export function groupSelectors(selector: Node) { - let block: Node[] = []; - const blocks: Node[][] = [block]; + let block = { + global: selector.children[0].type === 'PseudoClassSelector' && selector.children[0].name === 'global', + selectors: [], + combinator: null + }; - selector.children.forEach((child: Node) => { + const blocks = [block]; + + selector.children.forEach((child: Node, i: number) => { if (child.type === 'WhiteSpace' || child.type === 'Combinator') { - block = []; + const next = selector.children[i + 1]; + + block = { + global: next.type === 'PseudoClassSelector' && next.name === 'global', + selectors: [], + combinator: child + }; + blocks.push(block); } else { - block.push(child); + block.selectors.push(child); } }); diff --git a/src/validate/css/index.ts b/src/validate/css/index.ts index 8b90eff09c3a..57c13a7716bd 100644 --- a/src/validate/css/index.ts +++ b/src/validate/css/index.ts @@ -8,12 +8,12 @@ export default function validateCss(validator: Validator, css: Node) { }); function validateSelector(selector: Node) { - const blocks: Node[][] = groupSelectors(selector); + const blocks = groupSelectors(selector); blocks.forEach((block) => { - let i = block.length; + let i = block.selectors.length; while (i-- > 1) { - const part = block[i]; + const part = block.selectors[i]; if (part.type === 'PseudoClassSelector' && part.name === 'global') { validator.error(`:global(...) must be the first element in a compound selector`, part.start); } @@ -24,16 +24,16 @@ export default function validateCss(validator: Validator, css: Node) { let end = blocks.length; for (; start < end; start += 1) { - if (!isGlobalSelector(blocks[start])) break; + if (!blocks[start].global) break; } for (; end > start; end -= 1) { - if (!isGlobalSelector(blocks[end - 1])) break; + if (!blocks[end - 1].global) break; } for (let i = start; i < end; i += 1) { - if (isGlobalSelector(blocks[i])) { - validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, blocks[i][0].start); + if (blocks[i].global) { + validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, blocks[i].selectors[0].start); } } } diff --git a/test/css/samples/cascade-false-global/input.html b/test/css/samples/cascade-false-global/input.html index 947edb6ab57d..0350c826f523 100644 --- a/test/css/samples/cascade-false-global/input.html +++ b/test/css/samples/cascade-false-global/input.html @@ -6,7 +6,7 @@ color: red; } - :global(div.foo) { + :global(div).foo { color: blue; }