diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 6c1c365f2e..9ce1f8b39e 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -50,6 +50,7 @@ export default class Generator { parsed: Parsed, source: string, name: string, + stylesheet: Stylesheet, options: CompileOptions ) { this.ast = clone(parsed); @@ -76,7 +77,7 @@ export default class Generator { this.usesRefs = false; // styles - this.stylesheet = new Stylesheet(source, parsed, options.filename, options.cascade !== false); + this.stylesheet = stylesheet; // TODO this is legacy — just to get the tests to pass during the transition this.css = this.stylesheet.render(options.cssOutputFilename).css; diff --git a/src/generators/Selector.ts b/src/generators/Selector.ts index b5e2941d1c..ee95236a39 100644 --- a/src/generators/Selector.ts +++ b/src/generators/Selector.ts @@ -1,5 +1,6 @@ import MagicString from 'magic-string'; -import { groupSelectors, isGlobalSelector } from '../utils/css'; +import { groupSelectors } from '../utils/css'; +import { Validator } from '../validate/index'; import { Node } from '../interfaces'; export default class Selector { @@ -72,6 +73,35 @@ export default class Selector { } }); } + + validate(validator: Validator) { + this.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 = this.blocks.length; + + for (; start < end; start += 1) { + if (!this.blocks[start].global) break; + } + + for (; end > start; end -= 1) { + if (!this.blocks[end - 1].global) break; + } + + for (let i = start; i < end; i += 1) { + if (this.blocks[i].global) { + validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, this.blocks[i].selectors[0].start); + } + } + } } function isDescendantSelector(selector: Node) { diff --git a/src/generators/Stylesheet.ts b/src/generators/Stylesheet.ts index 69159e1509..fab27aa051 100644 --- a/src/generators/Stylesheet.ts +++ b/src/generators/Stylesheet.ts @@ -3,6 +3,7 @@ import { walk } from 'estree-walker'; import { getLocator } from 'locate-character'; import Selector from './Selector'; import getCodeFrame from '../utils/getCodeFrame'; +import { Validator } from '../validate/index'; import { Node, Parsed } from '../interfaces'; class Rule { @@ -193,6 +194,14 @@ export default class Stylesheet { }; } + validate(validator: Validator) { + this.rules.forEach(rule => { + rule.selectors.forEach(selector => { + selector.validate(validator); + }); + }); + } + warnOnUnusedSelectors(onwarn) { if (this.cascade) return; diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 63fefb6687..01cd6cc568 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -9,6 +9,7 @@ import CodeBuilder from '../../utils/CodeBuilder'; import visit from './visit'; import shared from './shared'; import Generator from '../Generator'; +import Stylesheet from '../Stylesheet'; import preprocess from './preprocess'; import Block from './Block'; import { Parsed, CompileOptions, Node } from '../../interfaces'; @@ -28,9 +29,10 @@ export class DomGenerator extends Generator { parsed: Parsed, source: string, name: string, + stylesheet: Stylesheet, options: CompileOptions ) { - super(parsed, source, name, options); + super(parsed, source, name, stylesheet, options); this.blocks = []; this.readonly = new Set(); @@ -45,11 +47,12 @@ export class DomGenerator extends Generator { export default function dom( parsed: Parsed, source: string, + stylesheet: Stylesheet, options: CompileOptions ) { const format = options.format || 'es'; - const generator = new DomGenerator(parsed, source, options.name || 'SvelteComponent', options); + const generator = new DomGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options); const { computations, diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index c755b63adc..9bd68dcbf2 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -1,5 +1,6 @@ import deindent from '../../utils/deindent'; import Generator from '../Generator'; +import Stylesheet from '../Stylesheet'; import Block from './Block'; import preprocess from './preprocess'; import visit from './visit'; @@ -15,9 +16,10 @@ export class SsrGenerator extends Generator { parsed: Parsed, source: string, name: string, + stylesheet: Stylesheet, options: CompileOptions ) { - super(parsed, source, name, options); + super(parsed, source, name, stylesheet, options); this.bindings = []; this.renderCode = ''; this.elementDepth = 0; @@ -63,11 +65,12 @@ export class SsrGenerator extends Generator { export default function ssr( parsed: Parsed, source: string, + stylesheet: Stylesheet, options: CompileOptions ) { const format = options.format || 'cjs'; - const generator = new SsrGenerator(parsed, source, options.name || 'SvelteComponent', options); + const generator = new SsrGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options); const { computations, name, hasJs, templateProperties } = generator; diff --git a/src/index.ts b/src/index.ts index 5c9d98992f..78696bc3a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import generate from './generators/dom/index'; import generateSSR from './generators/server-side-rendering/index'; import { assign } from './shared/index.js'; import { version } from '../package.json'; +import Stylesheet from './generators/Stylesheet'; import { Parsed, CompileOptions, Warning } from './interfaces'; function normalizeOptions(options: CompileOptions): CompileOptions { @@ -44,11 +45,13 @@ export function compile(source: string, _options: CompileOptions) { return; } - validate(parsed, source, options); + const stylesheet = new Stylesheet(source, parsed, options.filename, options.cascade !== false); + + validate(parsed, source, stylesheet, options); const compiler = options.generate === 'ssr' ? generateSSR : generate; - return compiler(parsed, source, options); + return compiler(parsed, source, stylesheet, options); } export function create(source: string, _options: CompileOptions = {}) { diff --git a/src/utils/css.ts b/src/utils/css.ts index 1e00d657e4..b398956760 100644 --- a/src/utils/css.ts +++ b/src/utils/css.ts @@ -1,9 +1,5 @@ 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', diff --git a/src/validate/css/index.ts b/src/validate/css/index.ts index 57c13a7716..658b3631bb 100644 --- a/src/validate/css/index.ts +++ b/src/validate/css/index.ts @@ -1,4 +1,4 @@ -import { groupSelectors, isGlobalSelector, walkRules } from '../../utils/css'; +import { groupSelectors, walkRules } from '../../utils/css'; import { Validator } from '../index'; import { Node } from '../../interfaces'; diff --git a/src/validate/index.ts b/src/validate/index.ts index 62cb9e614d..4e5fe1caa4 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -4,6 +4,7 @@ import validateHtml from './html/index'; import { getLocator, Location } from 'locate-character'; import getCodeFrame from '../utils/getCodeFrame'; import CompileError from '../utils/CompileError'; +import Stylesheet from '../generators/Stylesheet'; import { Node, Parsed, CompileOptions, Warning } from '../interfaces'; class ValidationError extends CompileError { @@ -73,6 +74,7 @@ export class Validator { export default function validate( parsed: Parsed, source: string, + stylesheet: Stylesheet, options: CompileOptions ) { const { onwarn, onerror, name, filename } = options; @@ -103,7 +105,7 @@ export default function validate( } if (parsed.css) { - validateCss(validator, parsed.css); + stylesheet.validate(validator); } if (parsed.html) { diff --git a/test/validator/index.js b/test/validator/index.js index 5a63958c9c..e41e18a1da 100644 --- a/test/validator/index.js +++ b/test/validator/index.js @@ -18,12 +18,10 @@ describe("validate", () => { const input = fs.readFileSync(filename, "utf-8").replace(/\s+$/, ""); try { - const parsed = svelte.parse(input); - const errors = []; const warnings = []; - svelte.validate(parsed, input, { + svelte.compile(input, { onerror(error) { errors.push({ message: error.message,