diff --git a/src/generators/extractSelectors.ts b/src/generators/extractSelectors.ts index df539a45b2..782d752671 100644 --- a/src/generators/extractSelectors.ts +++ b/src/generators/extractSelectors.ts @@ -12,12 +12,9 @@ export default function extractSelectors(css: Node) :Node[] { rule.block.children.forEach(processRule); return; } - console.log(rule); - throw new Error('nope'); - } - if (rule.type !== 'Rule') { - // TODO @media etc - throw new Error(`not supported: ${rule.type}`); + + // TODO + throw new Error(`Not implemented: @${rule.name}. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!`); } const selectors = rule.selector.children; @@ -110,12 +107,13 @@ function selectorAppliesTo(parts: Node[], node: Node, stack: Node[]) { return selectorAppliesTo(parts.slice(0, i), stack.pop(), stack); } - console.log(part); + // TODO other combinators return true; } else { - throw new Error(`TODO ${part.type}`); + // bail. TODO figure out what these could be + return true; } } diff --git a/src/generators/shared/processCss.ts b/src/generators/shared/processCss.ts index b7281e8c8a..00e564862a 100644 --- a/src/generators/shared/processCss.ts +++ b/src/generators/shared/processCss.ts @@ -1,4 +1,5 @@ import MagicString from 'magic-string'; +import { groupSelectors, isGlobalSelector } from '../../utils/css'; import { Parsed, Node } from '../../interfaces'; const commentsPattern = /\/\*[\s\S]*?\*\//g; @@ -37,23 +38,11 @@ export default function processCss( parsed.css.children.forEach(walkKeyframes); - function transformBlock(block: Node[]) { + function encapsulateBlock(block: Node[]) { let i = block.length; while (i--) { const child = block[i]; - - if (child.type === 'PseudoElementSelector') continue; - - if (child.type === 'PseudoClassSelector') { - if (child.name === 'global') { - const first = child.children[0]; - const last = child.children[child.children.length - 1]; - code.remove(child.start, first.start).remove(last.end, child.end); - return; - } else { - continue; - } - } + if (child.type === 'PseudoElementSelector' || child.type === 'PseudoClassSelector') continue; code.appendLeft(child.end, attr); return; @@ -91,19 +80,20 @@ export default function processCss( // separate .foo > .bar > .baz into three separate blocks, so // that we can transform only the first and last let block: Node[] = []; - const blocks: Node[][] = [block]; + const blocks: Node[][] = groupSelectors(selector); - selector.children.forEach((child: Node) => { - if (child.type === 'WhiteSpace' || child.type === 'Combinator') { - block = []; - blocks.push(block); - } else { - block.push(child); + blocks.forEach((block: Node[], i) => { + if (i === 0 || i === blocks.length - 1) { + encapsulateBlock(blocks[i]); } - }); - transformBlock(blocks[0]); - if (blocks.length > 1) transformBlock(blocks[blocks.length - 1]); + if (isGlobalSelector(block)) { + const selector = block[0]; + const first = selector.children[0]; + const last = selector.children[selector.children.length - 1]; + code.remove(selector.start, first.start).remove(last.end, selector.end); + } + }); } }); diff --git a/src/utils/css.ts b/src/utils/css.ts new file mode 100644 index 0000000000..31a6a26876 --- /dev/null +++ b/src/utils/css.ts @@ -0,0 +1,21 @@ +import { Node } from '../interfaces'; + +export function isGlobalSelector(block: Node[]) { + return block.length === 1 && block[0].type === 'PseudoClassSelector' && block[0].name === 'global'; +} + +export function groupSelectors(selector: Node) { + let block: Node[] = []; + const blocks: Node[][] = [block]; + + selector.children.forEach((child: Node) => { + if (child.type === 'WhiteSpace' || child.type === 'Combinator') { + block = []; + blocks.push(block); + } else { + block.push(child); + } + }); + + return blocks; +} \ No newline at end of file diff --git a/src/validate/css/index.ts b/src/validate/css/index.ts new file mode 100644 index 0000000000..b2f26e78d0 --- /dev/null +++ b/src/validate/css/index.ts @@ -0,0 +1,58 @@ +import { groupSelectors, isGlobalSelector } from '../../utils/css'; +import { Validator } from '../index'; +import { Node } from '../../interfaces'; + +export default function validateCss(validator: Validator, css: Node) { + function validateRule(rule: Node) { + if (rule.type === 'Atrule') { + if (rule.name === 'keyframes') return; + if (rule.name == 'media') { + rule.block.children.forEach(validateRule); + return; + } + + // TODO + throw new Error(`Not implemented: @${rule.name}. Please raise an issue at https://github.com/sveltejs/svelte/issues — thanks!`); + } + + const selectors = rule.selector.children; + selectors.forEach(validateSelector); + } + + function validateSelector(selector: Node) { + const blocks: Node[][] = groupSelectors(selector); + + blocks.forEach((block, i) => { + if (block.find((part: Node) => part.type === 'PseudoClassSelector' && part.name === 'global')) { + // check that :global(...) is by itself + if (block.length !== 1) { + validator.error(`:global(...) cannot be mixed with non-global selectors`, block[0].start); + } + + // check that :global(...) isn't sandwiched by other selectors + // if (i > 0 && i < blocks.length - 1) { + // validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, block[0].start); + // } + } + }); + + let start = 0; + let end = blocks.length; + + for (; start < end; start += 1) { + if (!isGlobalSelector(blocks[start])) break; + } + + for (; end > start; end -= 1) { + if (!isGlobalSelector(blocks[end - 1])) break; + } + + for (let i = start; i < end; i += 1) { + if (isGlobalSelector(blocks[i])) { + validator.error(`:global(...) can be at the start or end of a selector sequence, but not in the middle`, blocks[i][0].start); + } + } + } + + css.children.forEach(validateRule); +} \ No newline at end of file diff --git a/src/validate/index.ts b/src/validate/index.ts index a395eb83d8..32a6a82c92 100644 --- a/src/validate/index.ts +++ b/src/validate/index.ts @@ -1,4 +1,5 @@ import validateJs from './js/index'; +import validateCss from './css/index'; import validateHtml from './html/index'; import { getLocator, Location } from 'locate-character'; import getCodeFrame from '../utils/getCodeFrame'; @@ -101,6 +102,10 @@ export default function validate( validateJs(validator, parsed.js); } + if (parsed.css) { + validateCss(validator, parsed.css); + } + if (parsed.html) { validateHtml(validator, parsed.html); } diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/_config.js similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/_config.js rename to test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/_config.js diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/expected.css new file mode 100644 index 0000000000..d09033651f --- /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 0000000000..db13732174 --- /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 0000000000..85baf01a2d --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-inner-multiple/input.html @@ -0,0 +1,9 @@ +
+ +
+ + \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/_config.js b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/_config.js new file mode 100644 index 0000000000..0371e65c7e --- /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 0000000000..9137c5bd8d --- /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 0000000000..35cfecee4f --- /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 0000000000..57e227284c --- /dev/null +++ b/test/css/samples/omit-scoping-attribute-descendant-global-outer-multiple/input.html @@ -0,0 +1,7 @@ +

this may or may not be styled

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

raw

' + } +}; \ No newline at end of file diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/expected.css b/test/css/samples/omit-scoping-attribute-global/expected.css similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/expected.css rename to test/css/samples/omit-scoping-attribute-global/expected.css diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/expected.html b/test/css/samples/omit-scoping-attribute-global/expected.html similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/expected.html rename to test/css/samples/omit-scoping-attribute-global/expected.html diff --git a/test/css/samples/omit-scoping-attribute-descendant-global/input.html b/test/css/samples/omit-scoping-attribute-global/input.html similarity index 100% rename from test/css/samples/omit-scoping-attribute-descendant-global/input.html rename to test/css/samples/omit-scoping-attribute-global/input.html diff --git a/test/validator/samples/css-invalid-global-placement/errors.json b/test/validator/samples/css-invalid-global-placement/errors.json new file mode 100644 index 0000000000..a2b8994175 --- /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 0000000000..33da3499b2 --- /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 0000000000..8aa138e3f2 --- /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 0000000000..5d325a4cd1 --- /dev/null +++ b/test/validator/samples/css-invalid-global/input.html @@ -0,0 +1,5 @@ + \ No newline at end of file