Skip to content

Commit cec3540

Browse files
authored
chore: use zimmerframe for CSS analysis/transformation (#10482)
* fix type * parse selectors properly the first time * partial fix * fix * start moving CSS validation into analysis phase * finish moving validation * fix tests * regenerate types * start porting scoping logic etc * move Style to Css.StyleSheet * some encouraging progress * more progress * more progress * fix a bunch of cases * more fixes * tweak * almost there * keyframes * fix * all CSS tests passing * legacy stuff * all tests passing * delete old code * regenerate types * defer analysis, address TODO * unused * tidy up * rename stuff * read selectors before combinators --------- Co-authored-by: Rich Harris <[email protected]>
1 parent f8ff2b6 commit cec3540

File tree

31 files changed

+1543
-1389
lines changed

31 files changed

+1543
-1389
lines changed

packages/svelte/src/compiler/css/Stylesheet.js

Lines changed: 0 additions & 549 deletions
This file was deleted.

packages/svelte/src/compiler/legacy.js

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,7 @@ export function convert(source, ast) {
102102
},
103103
instance,
104104
module,
105-
css: ast.css
106-
? walk(ast.css, null, {
107-
_(node) {
108-
// @ts-ignore
109-
delete node.parent;
110-
}
111-
})
112-
: undefined
105+
css: ast.css ? visit(ast.css) : undefined
113106
};
114107
},
115108
AnimateDirective(node) {
@@ -192,6 +185,24 @@ export function convert(source, ast) {
192185
ClassDirective(node) {
193186
return { ...node, type: 'Class' };
194187
},
188+
ComplexSelector(node, { visit }) {
189+
const children = [];
190+
191+
for (const child of node.children) {
192+
if (child.combinator) {
193+
children.push(child.combinator);
194+
}
195+
196+
children.push(...child.selectors);
197+
}
198+
199+
return {
200+
type: 'Selector',
201+
start: node.start,
202+
end: node.end,
203+
children
204+
};
205+
},
195206
Component(node, { visit }) {
196207
return {
197208
type: 'InlineComponent',
@@ -389,6 +400,13 @@ export function convert(source, ast) {
389400
SpreadAttribute(node) {
390401
return { ...node, type: 'Spread' };
391402
},
403+
StyleSheet(node, context) {
404+
return {
405+
...node,
406+
...context.next(),
407+
type: 'Style'
408+
};
409+
},
392410
SvelteBody(node, { visit }) {
393411
return {
394412
type: 'Body',

packages/svelte/src/compiler/phases/1-parse/read/style.js

Lines changed: 109 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/;
1818
* @param {import('../index.js').Parser} parser
1919
* @param {number} start
2020
* @param {Array<import('#compiler').Attribute | import('#compiler').SpreadAttribute | import('#compiler').Directive>} attributes
21-
* @returns {import('#compiler').Style}
21+
* @returns {import('#compiler').Css.StyleSheet}
2222
*/
2323
export default function read_style(parser, start, attributes) {
2424
const content_start = parser.index;
@@ -28,7 +28,7 @@ export default function read_style(parser, start, attributes) {
2828
parser.read(/^<\/style\s*>/);
2929

3030
return {
31-
type: 'Style',
31+
type: 'StyleSheet',
3232
start,
3333
end: parser.index,
3434
attributes,
@@ -37,8 +37,7 @@ export default function read_style(parser, start, attributes) {
3737
start: content_start,
3838
end: content_end,
3939
styles: parser.template.slice(content_start, content_end)
40-
},
41-
parent: null
40+
}
4241
};
4342
}
4443

@@ -187,42 +186,66 @@ function read_selector_list(parser, inside_pseudo_class = false) {
187186
function read_selector(parser, inside_pseudo_class = false) {
188187
const list_start = parser.index;
189188

190-
/** @type {Array<import('#compiler').Css.SimpleSelector | import('#compiler').Css.Combinator>} */
189+
/** @type {import('#compiler').Css.RelativeSelector[]} */
191190
const children = [];
192191

192+
/**
193+
* @param {import('#compiler').Css.Combinator | null} combinator
194+
* @param {number} start
195+
* @returns {import('#compiler').Css.RelativeSelector}
196+
*/
197+
function create_selector(combinator, start) {
198+
return {
199+
type: 'RelativeSelector',
200+
combinator,
201+
selectors: [],
202+
start,
203+
end: -1,
204+
metadata: {
205+
is_global: false,
206+
is_host: false,
207+
is_root: false,
208+
scoped: false
209+
}
210+
};
211+
}
212+
213+
/** @type {import('#compiler').Css.RelativeSelector} */
214+
let relative_selector = create_selector(null, parser.index);
215+
193216
while (parser.index < parser.template.length) {
194-
const start = parser.index;
217+
let start = parser.index;
195218

196219
if (parser.eat('*')) {
197220
let name = '*';
198-
if (parser.match('|')) {
221+
222+
if (parser.eat('|')) {
199223
// * is the namespace (which we ignore)
200-
parser.index++;
201224
name = read_identifier(parser);
202225
}
203226

204-
children.push({
227+
relative_selector.selectors.push({
205228
type: 'TypeSelector',
206229
name,
207230
start,
208231
end: parser.index
209232
});
210233
} else if (parser.eat('#')) {
211-
children.push({
234+
relative_selector.selectors.push({
212235
type: 'IdSelector',
213236
name: read_identifier(parser),
214237
start,
215238
end: parser.index
216239
});
217240
} else if (parser.eat('.')) {
218-
children.push({
241+
relative_selector.selectors.push({
219242
type: 'ClassSelector',
220243
name: read_identifier(parser),
221244
start,
222245
end: parser.index
223246
});
224247
} else if (parser.eat('::')) {
225-
children.push({
248+
relative_selector.selectors.push({
226249
type: 'PseudoElementSelector',
227250
name: read_identifier(parser),
228251
start,
@@ -247,7 +270,7 @@ function read_selector(parser, inside_pseudo_class = false) {
247270
error(parser.index, 'invalid-css-global-selector');
248271
}
249272

250-
children.push({
273+
relative_selector.selectors.push({
251274
type: 'PseudoClassSelector',
252275
name,
253276
args,
@@ -276,7 +299,7 @@ function read_selector(parser, inside_pseudo_class = false) {
276299
parser.allow_whitespace();
277300
parser.eat(']', true);
278301

279-
children.push({
302+
relative_selector.selectors.push({
280303
type: 'AttributeSelector',
281304
start,
282305
end: parser.index,
@@ -288,37 +311,28 @@ function read_selector(parser, inside_pseudo_class = false) {
288311
} else if (inside_pseudo_class && parser.match_regex(REGEX_NTH_OF)) {
289312
// nth of matcher must come before combinator matcher to prevent collision else the '+' in '+2n-1' would be parsed as a combinator
290313

291-
children.push({
314+
relative_selector.selectors.push({
292315
type: 'Nth',
293316
value: /**@type {string} */ (parser.read(REGEX_NTH_OF)),
294317
start,
295318
end: parser.index
296319
});
297-
} else if (parser.match_regex(REGEX_COMBINATOR_WHITESPACE)) {
298-
parser.allow_whitespace();
299-
const start = parser.index;
300-
children.push({
301-
type: 'Combinator',
302-
name: /** @type {string} */ (parser.read(REGEX_COMBINATOR)),
303-
start,
304-
end: parser.index
305-
});
306-
parser.allow_whitespace();
307320
} else if (parser.match_regex(REGEX_PERCENTAGE)) {
308-
children.push({
321+
relative_selector.selectors.push({
309322
type: 'Percentage',
310323
value: /** @type {string} */ (parser.read(REGEX_PERCENTAGE)),
311324
start,
312325
end: parser.index
313326
});
314-
} else {
327+
} else if (!parser.match_regex(REGEX_COMBINATOR)) {
315328
let name = read_identifier(parser);
316-
if (parser.match('|')) {
329+
330+
if (parser.eat('|')) {
317331
// we ignore the namespace when trying to find matching element classes
318-
parser.index++;
319332
name = read_identifier(parser);
320333
}
321-
children.push({
334+
335+
relative_selector.selectors.push({
322336
type: 'TypeSelector',
323337
name,
324338
start,
@@ -330,29 +344,85 @@ function read_selector(parser, inside_pseudo_class = false) {
330344
parser.allow_whitespace();
331345

332346
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
347+
// rewind, so we know whether to continue building the selector list
333348
parser.index = index;
334349

350+
relative_selector.end = index;
351+
children.push(relative_selector);
352+
335353
return {
336-
type: 'Selector',
354+
type: 'ComplexSelector',
337355
start: list_start,
338356
end: index,
339-
children
357+
children,
358+
metadata: {
359+
used: false
360+
}
340361
};
341362
}
342363

343-
if (parser.index !== index && !parser.match_regex(REGEX_COMBINATOR)) {
344-
children.push({
345-
type: 'Combinator',
346-
name: ' ',
347-
start: index,
348-
end: parser.index
349-
});
364+
parser.index = index;
365+
const combinator = read_combinator(parser);
366+
367+
if (combinator) {
368+
if (relative_selector.selectors.length === 0) {
369+
if (!inside_pseudo_class) {
370+
error(start, 'invalid-css-selector');
371+
}
372+
} else {
373+
relative_selector.end = index;
374+
children.push(relative_selector);
375+
}
376+
377+
// ...and start a new one
378+
relative_selector = create_selector(combinator, combinator.start);
379+
380+
parser.allow_whitespace();
381+
382+
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
383+
error(parser.index, 'invalid-css-selector');
384+
}
350385
}
351386
}
352387

353388
error(parser.template.length, 'unexpected-eof');
354389
}
355390

391+
/**
392+
* @param {import('../index.js').Parser} parser
393+
* @returns {import('#compiler').Css.Combinator | null}
394+
*/
395+
function read_combinator(parser) {
396+
const start = parser.index;
397+
parser.allow_whitespace();
398+
399+
const index = parser.index;
400+
const name = parser.read(REGEX_COMBINATOR);
401+
402+
if (name) {
403+
const end = parser.index;
404+
parser.allow_whitespace();
405+
406+
return {
407+
type: 'Combinator',
408+
name,
409+
start: index,
410+
end
411+
};
412+
}
413+
414+
if (parser.index !== start) {
415+
return {
416+
type: 'Combinator',
417+
name: ' ',
418+
start,
419+
end: parser.index
420+
};
421+
}
422+
423+
return null;
424+
}
425+
356426
/**
357427
* @param {import('../index.js').Parser} parser
358428
* @returns {import('#compiler').Css.Block}

0 commit comments

Comments
 (0)