diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 1a1528d0c42d..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'; @@ -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,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 = []; + + 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'`; @@ -211,6 +228,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 +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 new file mode 100644 index 000000000000..862bbea650a4 --- /dev/null +++ b/src/generators/Selector.ts @@ -0,0 +1,150 @@ +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) { + 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 = 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; + if (stack[0] && this.node.children.find(isDescendantSelector)) stack[0]._needsCssAttribute = true; + } + } +} + +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 82a117eaae82..db26f10475a8 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; @@ -117,12 +118,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..15719e7b8f72 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, walkRules } from '../../utils/css'; import { Parsed, Node } from '../../interfaces'; const commentsPattern = /\/\*[\s\S]*?\*\//g; @@ -37,6 +38,22 @@ 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; + } + } + function transform(rule: Node) { rule.selector.children.forEach((selector: Node) => { if (cascade) { @@ -52,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}`; @@ -65,37 +82,23 @@ export default function processCss( 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; - } + // 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); - 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; + blocks.forEach((block: Node[], i) => { + if (i === 0 || i === blocks.length - 1) { + encapsulateBlock(blocks[i]); } - c = child.end; + 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); + } }); - - if (shouldTransform) { - code.appendLeft(c, attr); - } } }); @@ -116,22 +119,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/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..ac78bdd96a0e --- /dev/null +++ b/src/utils/css.ts @@ -0,0 +1,33 @@ +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; +} + +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..6251f59fb0a3 --- /dev/null +++ b/src/validate/css/index.ts @@ -0,0 +1,44 @@ +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: 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); + } + } + } +} \ 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..221f67973908 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.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), + 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) + ); + }); + } }); }); }); + +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/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/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-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 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/_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 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..662e453d8382 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-attribute-selector/expected.css @@ -0,0 +1,4 @@ + + [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 new file mode 100644 index 000000000000..254afb2a46ed --- /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..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-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 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..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-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..67a7fbed8682 --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner/expected.css @@ -0,0 +1,4 @@ + + div[svelte-3744486606] > 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..4511b7222133 --- /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..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-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-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..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 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 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-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..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 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 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..76559bfb61af --- /dev/null +++ b/test/css/samples/unused-selector/expected.css @@ -0,0 +1,8 @@ + + .foo[svelte-698347462] { + color: red; + } + + .bar[svelte-698347462] { + 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..083a321ea7ad --- /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..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