diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 1a1528d0c42d..d1cee03e1aa0 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'; @@ -13,6 +15,8 @@ import annotateWithScopes from '../utils/annotateWithScopes'; import clone from '../utils/clone'; import DomBlock from './dom/Block'; import SsrBlock from './server-side-rendering/Block'; +import { walkRules } from '../utils/css'; +import Selector from './Selector'; import { Node, Parsed, CompileOptions } from '../interfaces'; const test = typeof global !== 'undefined' && global.__svelte_test; @@ -40,6 +44,8 @@ export default class Generator { cssId: string; usesRefs: boolean; + selectors: Selector[]; + importedNames: Set; aliases: Map; usedNames: Set; @@ -71,10 +77,24 @@ 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 = []; + + if (parsed.css) { + walkRules(parsed.css.children, node => { + node.selector.children.forEach((child: Node) => { + 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 // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; @@ -211,6 +231,20 @@ export default class Generator { }; } + 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]; + selector.apply(node, stack); + } + } + findDependencies( contextDependencies: Map, indexes: Map, @@ -590,4 +624,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 new file mode 100644 index 000000000000..e7a23489c131 --- /dev/null +++ b/src/generators/Selector.ts @@ -0,0 +1,180 @@ +import MagicString from 'magic-string'; +import { groupSelectors, isGlobalSelector, walkRules } from '../utils/css'; +import { Node } from '../interfaces'; + +export default class Selector { + node: Node; + blocks: any; // TODO + 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) { + const last = node.children[i-1]; + const penultimate = node.children[i-2]; + + if (last.type === 'PseudoClassSelector' && last.name === 'global') { + i -= 2; + } else { + break; + } + } + + this.parts = node.children.slice(0, i); + + this.used = this.blocks[0].global; + } + + 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; + 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) { + return selector.type === 'WhiteSpace' || selector.type === 'Combinator'; +} + +function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]): boolean { + let i = parts.length; + 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') { + // TODO shouldn't see this here... maybe we should enforce that :global(...) + // cannot be sandwiched between non-global selectors? + return false; + } + + if (part.type === 'PseudoClassSelector' || part.type === 'PseudoElementSelector') { + continue; + } + + if (part.type === 'ClassSelector') { + 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.name.name, part.value && unquote(part.value.value), part.operator, part.flags)) return false; + } + + else if (part.type === 'TypeSelector') { + if (part.name === '*') return true; + 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 false; + } + + else if (part.type === 'Combinator') { + if (part.name === '>') { + return selectorAppliesTo(parts.slice(0, i), stack.pop(), stack); + } + + // TODO other combinators + return true; + } + + else { + // bail. TODO figure out what these could be + return true; + } + } + + return true; +} + +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, 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) return operator === null; + if (isDynamic(attr.value)) return true; + + const actualValue = attr.value[0].data; + + const pattern = operators[operator](expectedValue, caseInsensitive ? 'i' : ''); + return pattern.test(actualValue); +} + +function isDynamic(value: Node) { + return value.length > 1 || 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/src/generators/dom/index.ts b/src/generators/dom/index.ts index fe0b3857f243..2810f2cbd143 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -61,8 +61,10 @@ 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); + visit(generator, block, state, node, []); }); const builder = new CodeBuilder(); 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/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 557cc41a1c37..7145f5de36d7 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( @@ -121,7 +122,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 0e1e08c39439..a4baec17959a 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; @@ -125,12 +126,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 53b4df6fdd26..bb7de853bb56 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; @@ -80,7 +81,8 @@ export default function visitElement( } // add CSS encapsulation attribute - if (generator.cssId && (!generator.cascade || state.isTopLevel)) { + // TODO add a helper for this, rather than repeating it + if (node._needsCssAttribute) { block.builders.hydrate.addLine( `@setAttribute( ${name}, '${generator.cssId}', '' );` ); @@ -181,7 +183,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 ddac9800b53e..75b88e363fa9 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/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 29cfccd67a54..ff18b0687888 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,10 @@ 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); + + this.warnOnUnusedSelectors(); + 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/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index 8ea6021452ef..5caec5523946 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -1,17 +1,19 @@ import MagicString from 'magic-string'; -import { Parsed, Node } from '../../interfaces'; +import { groupSelectors, isGlobalSelector, walkRules } from '../../utils/css'; +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(); @@ -22,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); } @@ -35,7 +37,7 @@ export default function processCss( } } - parsed.css.children.forEach(walkKeyframes); + generator.parsed.css.children.forEach(walkKeyframes); function transform(rule: Node) { rule.selector.children.forEach((selector: Node) => { @@ -52,7 +54,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}`; @@ -61,41 +63,6 @@ export default function processCss( } code.overwrite(selector.start, selector.end, transformed); - } else { - let shouldTransform = true; - let c = selector.start; - - selector.children.forEach((child: Node) => { - if (child.type === 'WhiteSpace' || child.type === 'Combinator') { - code.appendLeft(c, attr); - shouldTransform = true; - return; - } - - if (!shouldTransform) return; - - if (child.type === 'PseudoClassSelector') { - // `:global(xyz)` > xyz - 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); - } else { - code.prependRight(c, attr); - } - - shouldTransform = false; - } else if (child.type === 'PseudoElementSelector') { - code.prependRight(c, attr); - shouldTransform = false; - } - - c = child.end; - }); - - if (shouldTransform) { - code.appendLeft(c, attr); - } } }); @@ -116,22 +83,13 @@ 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); - } - } + walkRules(generator.parsed.css.children, transform); - parsed.css.children.forEach(walk); + 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; @@ -142,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/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/utils/css.ts b/src/utils/css.ts new file mode 100644 index 000000000000..1e00d657e485 --- /dev/null +++ b/src/utils/css.ts @@ -0,0 +1,45 @@ +import { Node } from '../interfaces'; + +export function isGlobalSelector(block: Node[]) { + return block[0].type === 'PseudoClassSelector' && block[0].name === 'global'; +} + +export function groupSelectors(selector: Node) { + let block = { + global: selector.children[0].type === 'PseudoClassSelector' && selector.children[0].name === 'global', + selectors: [], + combinator: null + }; + + const blocks = [block]; + + selector.children.forEach((child: Node, i: number) => { + if (child.type === 'WhiteSpace' || child.type === 'Combinator') { + const next = selector.children[i + 1]; + + block = { + global: next.type === 'PseudoClassSelector' && next.name === 'global', + selectors: [], + combinator: child + }; + + blocks.push(block); + } else { + block.selectors.push(child); + } + }); + + 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 new file mode 100644 index 000000000000..57c13a7716bd --- /dev/null +++ b/src/validate/css/index.ts @@ -0,0 +1,40 @@ +import { groupSelectors, isGlobalSelector, walkRules } from '../../utils/css'; +import { Validator } from '../index'; +import { Node } from '../../interfaces'; + +export default function validateCss(validator: Validator, css: Node) { + walkRules(css.children, rule => { + rule.selector.children.forEach(validateSelector); + }); + + function validateSelector(selector: Node) { + const blocks = groupSelectors(selector); + + blocks.forEach((block) => { + let i = block.selectors.length; + while (i-- > 1) { + 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); + } + } + }); + + let start = 0; + let end = blocks.length; + + for (; start < end; start += 1) { + if (!blocks[start].global) break; + } + + for (; end > start; end -= 1) { + if (!blocks[end - 1].global) break; + } + + for (let i = start; i < end; i += 1) { + 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); + } + } + } +} \ No newline at end of file diff --git a/src/validate/index.ts b/src/validate/index.ts index a395eb83d856..62cb9e614d11 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'; @@ -34,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; @@ -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/index.js b/test/css/index.js index 51cf1b4aba99..6c56238ce2fe 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -1,16 +1,24 @@ import assert from "assert"; import * as fs from "fs"; -import { svelte } from "../helpers.js"; +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,14 +36,74 @@ describe("css", () => { .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 expectedWarnings = (config.warnings || []).map(normalizeWarning); + const domWarnings = []; + const ssrWarnings = []; + + const dom = svelte.compile(input, Object.assign(config, { + format: 'iife', + name: 'SvelteComponent', + onwarn: warning => { + domWarnings.push(warning); + } + })); + + const ssr = svelte.compile(input, Object.assign(config, { + format: 'iife', + generate: 'ssr', + 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`), + css: read(`test/css/samples/${dir}/expected.css`) + }; + + 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) { + return env().then(window => { + const Component = eval(`(function () { ${dom.code}; return SvelteComponent; }())`); + const target = window.document.querySelector("main"); - assert.equal(actual.trim(), expected.trim()); + new Component({ target, data: config.data }); + const html = target.innerHTML; + + fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); + + // dom + assert.equal( + normalizeHtml(window, html).replace(/svelte-\d+/g, 'svelte-xyz'), + normalizeHtml(window, expected.html) + ); + + // ssr + const component = eval(`(function () { ${ssr.code}; return SvelteComponent; }())`); + + assert.equal( + normalizeHtml(window, component.render(config.data)).replace(/svelte-\d+/g, 'svelte-xyz'), + normalizeHtml(window, expected.html) + ); + }); + } }); }); }); + +function read(file) { + try { + return fs.readFileSync(file, 'utf-8'); + } catch(err) { + return null; + } +} \ No newline at end of file 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/_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..ca0a6de74250 --- /dev/null +++ b/test/css/samples/cascade-false-universal-selector/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..e274b3e5098d --- /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/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/_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..957354dc6a51 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-contains/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..c69d6b03c98e --- /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..218ef835be84 --- /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-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 new file mode 100644 index 000000000000..4da16a0c2ff6 --- /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-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..636d4206b95e --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals-dynamic/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..f9cc8d8b5fa8 --- /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..636d4206b95e --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-equals/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..4579673c467f --- /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 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..590f946bfbe4 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-pipe-equals/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..37880da66775 --- /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..8bcb51086730 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-prefix/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..a929494570e0 --- /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..2d209085d0f5 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-suffix/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..71abcb7fa58b --- /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/_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..fcacb8704f28 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector-word-equals/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..64244097a249 --- /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 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..283a92edca12 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..6a5b1043889c --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.html @@ -0,0 +1,2 @@ +
+
\ 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..6f4549ead8d8 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/input.html @@ -0,0 +1,10 @@ +
+ + +
+ + \ 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..ee6e96c597cc --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-dynamic/expected.css @@ -0,0 +1,4 @@ + + .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 new file mode 100644 index 000000000000..c45e8d88a614 --- /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..ee6e96c597cc --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-class-static/expected.css @@ -0,0 +1,4 @@ + + .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 new file mode 100644 index 000000000000..24687444c480 --- /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-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..e8a40fc90830 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-class/expected.css @@ -0,0 +1,4 @@ + + .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 new file mode 100644 index 000000000000..bbe7db594631 --- /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 diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/_config.js new file mode 100644 index 000000000000..0371e65c7e35 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-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-inner-multiple/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css new file mode 100644 index 000000000000..93ab1b04e595 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 000000000000..e274b3e5098d --- /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-inner/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-inner/_config.js new file mode 100644 index 000000000000..0371e65c7e35 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner/_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/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.css new file mode 100644 index 000000000000..92d8f497d7ab --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 000000000000..e274b3e5098d --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner/input.html b/test/css/samples/omit-scoping-attribute-descendant-global-inner/input.html new file mode 100644 index 000000000000..bad6794f252f --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner/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..fa651566fb5e --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 000000000000..c3b07834462a --- /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-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..f62e2305201e --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 000000000000..c3b07834462a --- /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-descendant/_config.js b/test/css/samples/omit-scoping-attribute-descendant/_config.js new file mode 100644 index 000000000000..0d56c7ba8c09 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant/_config.js @@ -0,0 +1,19 @@ +export default { + 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/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-global/expected.css b/test/css/samples/omit-scoping-attribute-global/expected.css new file mode 100644 index 000000000000..be3405979388 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global/expected.css @@ -0,0 +1,4 @@ + + div { + color: red; + } diff --git a/test/css/samples/omit-scoping-attribute-global/expected.html b/test/css/samples/omit-scoping-attribute-global/expected.html new file mode 100644 index 000000000000..281c6866c375 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-global/input.html b/test/css/samples/omit-scoping-attribute-global/input.html new file mode 100644 index 000000000000..a85bfa0bb334 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-global/input.html @@ -0,0 +1,7 @@ +
+ + \ No newline at end of file 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..09133ba7ffba --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-id/expected.css @@ -0,0 +1,4 @@ + + #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 new file mode 100644 index 000000000000..cb6d2f229267 --- /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 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..b9da332c6fbf --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace-multiple/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 000000000000..874ad340127b --- /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 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..cf24b2c3cc12 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 000000000000..874ad340127b --- /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-whitespace/input.html b/test/css/samples/omit-scoping-attribute-whitespace/input.html new file mode 100644 index 000000000000..6d00e912676c --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-whitespace/input.html @@ -0,0 +1,11 @@ +
+
+

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..4bd7298c3da6 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute/expected.css @@ -0,0 +1,4 @@ + + 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 new file mode 100644 index 000000000000..60c8d1ce9c55 --- /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 diff --git a/test/css/samples/universal-selector/expected.css b/test/css/samples/universal-selector/expected.css new file mode 100644 index 000000000000..ad9785f26416 --- /dev/null +++ b/test/css/samples/universal-selector/expected.css @@ -0,0 +1,4 @@ + + [svelte-xyz], [svelte-xyz] * { + 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 diff --git a/test/css/samples/unused-selector/_config.js b/test/css/samples/unused-selector/_config.js new file mode 100644 index 000000000000..48504c996d72 --- /dev/null +++ b/test/css/samples/unused-selector/_config.js @@ -0,0 +1,20 @@ +export default { + cascade: false, + + warnings: [{ + filename: "SvelteComponent.html", + message: "Unused CSS selector", + loc: { + line: 8, + column: 1 + }, + pos: 60, + frame: ` + 6: } + 7: + 8: .bar { + ^ + 9: color: blue; + 10: }` + }] +}; \ No newline at end of file diff --git a/test/css/samples/unused-selector/expected.css b/test/css/samples/unused-selector/expected.css new file mode 100644 index 000000000000..c9e2ae86c0f5 --- /dev/null +++ b/test/css/samples/unused-selector/expected.css @@ -0,0 +1,8 @@ + + .foo[svelte-xyz] { + color: red; + } + + .bar[svelte-xyz] { + color: blue; + } diff --git a/test/css/samples/unused-selector/expected.html b/test/css/samples/unused-selector/expected.html new file mode 100644 index 000000000000..bbe7db594631 --- /dev/null +++ b/test/css/samples/unused-selector/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/unused-selector/input.html b/test/css/samples/unused-selector/input.html new file mode 100644 index 000000000000..1d62e3a67577 --- /dev/null +++ b/test/css/samples/unused-selector/input.html @@ -0,0 +1,11 @@ +
+ + \ 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 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 + ); }; }); } 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..71cb1f12610e --- /dev/null +++ b/test/validator/samples/css-invalid-global/errors.json @@ -0,0 +1,8 @@ +[{ + "message": ":global(...) must be the first element in a compound selector", + "loc": { + "line": 2, + "column": 5 + }, + "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 new file mode 100644 index 000000000000..518674424a00 --- /dev/null +++ b/test/validator/samples/css-invalid-global/input.html @@ -0,0 +1,5 @@ + \ No newline at end of file