fix: preserve CSS comments in the AST printer (#18637)

Adds a new `comments` array to the stylesheet node which CSS comments
are added to. Is subsequently used in `print` to see them in the output.

Alternative to #18475
pull/18628/head
Simon H 2 months ago committed by GitHub
parent 3ed9db4ba7
commit a1d5035d17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: preserve CSS comments in the AST printer

@ -141,7 +141,8 @@ export function parseCss(source) {
type: 'StyleSheetFile', type: 'StyleSheetFile',
start: 0, start: 0,
end: source.length, end: source.length,
children children,
comments: parser.css_comments
}; };
} }

@ -50,6 +50,9 @@ export class Parser {
/** */ /** */
index = 0; index = 0;
/** @type {AST.CSS.CSSComment[]} */
css_comments = [];
/** /**
* Creates a minimal parser instance for CSS-only parsing. * Creates a minimal parser instance for CSS-only parsing.
* Skips Svelte component parsing setup. * Skips Svelte component parsing setup.
@ -61,6 +64,7 @@ export class Parser {
parser.template = source; parser.template = source;
parser.index = 0; parser.index = 0;
parser.loose = false; parser.loose = false;
parser.css_comments = [];
return parser; return parser;
} }

@ -24,6 +24,7 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/;
*/ */
export default function read_style(parser, start, attributes) { export default function read_style(parser, start, attributes) {
const content_start = parser.index; const content_start = parser.index;
parser.css_comments = [];
const children = read_body(parser, (p) => p.match('</style') || p.index >= p.template.length); const children = read_body(parser, (p) => p.match('</style') || p.index >= p.template.length);
const content_end = parser.index; const content_end = parser.index;
@ -36,6 +37,7 @@ export default function read_style(parser, start, attributes) {
end: parser.index, end: parser.index,
attributes, attributes,
children, children,
comments: parser.css_comments,
content: { content: {
start: content_start, start: content_start,
end: content_end, end: content_end,
@ -229,18 +231,22 @@ function read_selector(parser, inside_pseudo_class = false) {
end: parser.index end: parser.index
}); });
} else if (parser.eat('::')) { } else if (parser.eat('::')) {
const name = read_identifier(parser);
/** @type {AST.CSS.SelectorList | null} */
let args = null;
if (parser.eat('(')) {
args = read_selector_list(parser, true);
parser.eat(')', true);
}
relative_selector.selectors.push({ relative_selector.selectors.push({
type: 'PseudoElementSelector', type: 'PseudoElementSelector',
name: read_identifier(parser), name,
start, start,
end: parser.index end: parser.index,
...(args && { args })
}); });
// We read the inner selectors of a pseudo element to ensure it parses correctly,
// but we don't do anything with the result.
if (parser.eat('(')) {
read_selector_list(parser, true);
parser.eat(')', true);
}
} else if (parser.eat(':')) { } else if (parser.eat(':')) {
const name = read_identifier(parser); const name = read_identifier(parser);
@ -323,7 +329,7 @@ function read_selector(parser, inside_pseudo_class = false) {
} }
const index = parser.index; const index = parser.index;
allow_comment_or_whitespace(parser); allow_comment_or_whitespace(parser, false);
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) { if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
// rewind, so we know whether to continue building the selector list // rewind, so we know whether to continue building the selector list
@ -449,7 +455,7 @@ function read_block_item(parser) {
// read ahead to understand whether we're dealing with a declaration or a nested rule. // read ahead to understand whether we're dealing with a declaration or a nested rule.
// this involves some duplicated work, but avoids a try-catch that would disguise errors // this involves some duplicated work, but avoids a try-catch that would disguise errors
const start = parser.index; const start = parser.index;
read_value(parser); read_value(parser, false);
const char = parser.template[parser.index]; const char = parser.template[parser.index];
parser.index = start; parser.index = start;
@ -492,10 +498,13 @@ function read_declaration(parser) {
/** /**
* @param {Parser} parser * @param {Parser} parser
* @param {boolean} [capture_comments]
* @returns {string} * @returns {string}
*/ */
function read_value(parser) { function read_value(parser, capture_comments = true) {
let value = ''; let value = '';
/** @type {AST.CSS.CSSComment[]} */
const value_comments = [];
let escaped = false; let escaped = false;
let in_url = false; let in_url = false;
@ -523,6 +532,13 @@ function read_value(parser) {
} else if (char === '(' && value.slice(-3) === 'url') { } else if (char === '(' && value.slice(-3) === 'url') {
in_url = true; in_url = true;
} else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) { } else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) {
const leading_whitespace = value.length - value.trimStart().length;
for (const comment of value_comments) {
comment.position = Math.max(
0,
/** @type {number} */ (comment.position) - leading_whitespace
);
}
return value.trim(); return value.trim();
} else if ( } else if (
char === '/' && char === '/' &&
@ -530,13 +546,11 @@ function read_value(parser) {
!quote_mark && !quote_mark &&
parser.template[parser.index + 1] === '*' parser.template[parser.index + 1] === '*'
) { ) {
parser.index += 2; const comment = read_comment(parser);
while (parser.index < parser.template.length) { if (capture_comments) {
if (parser.template[parser.index] === '*' && parser.template[parser.index + 1] === '/') { comment.position = value.length;
parser.index += 2; parser.css_comments.push(comment);
break; value_comments.push(comment);
}
parser.index++;
} }
continue; continue;
} }
@ -624,13 +638,16 @@ function read_identifier(parser) {
return identifier; return identifier;
} }
/** @param {Parser} parser */ /**
function allow_comment_or_whitespace(parser) { * @param {Parser} parser
* @param {boolean} [capture_comments]
*/
function allow_comment_or_whitespace(parser, capture_comments = true) {
parser.allow_whitespace(); parser.allow_whitespace();
while (parser.match('/*') || parser.match('<!--')) { while (parser.match('/*') || parser.match('<!--')) {
if (parser.eat('/*')) { if (parser.match('/*')) {
parser.read_until(REGEX_COMMENT_CLOSE); const comment = read_comment(parser);
parser.eat('*/', true); if (capture_comments) parser.css_comments.push(comment);
} }
if (parser.eat('<!--')) { if (parser.eat('<!--')) {
@ -642,6 +659,25 @@ function allow_comment_or_whitespace(parser) {
} }
} }
/**
* @param {Parser} parser
* @returns {AST.CSS.CSSComment}
*/
function read_comment(parser) {
const start = parser.index;
parser.eat('/*', true);
const value = parser.read_until(REGEX_COMMENT_CLOSE);
parser.eat('*/', true);
const end = parser.index;
return {
type: 'CSSComment',
value,
start,
end
};
}
/** /**
* Parse standalone CSS content (not wrapped in `<style>`). * Parse standalone CSS content (not wrapped in `<style>`).
* @param {Parser} parser * @param {Parser} parser

@ -312,6 +312,9 @@ const css_visitors = {
} }
context.next(); context.next();
},
PseudoElementSelector() {
// Don't analyse these
} }
}; };

@ -23,6 +23,9 @@ const visitors = {
context.next(); context.next();
} }
}, },
PseudoElementSelector() {
// Don't analyse these
},
ComplexSelector(node, context) { ComplexSelector(node, context) {
if ( if (
!node.metadata.used && !node.metadata.used &&

@ -374,6 +374,9 @@ const visitors = {
if (node.name === 'is' || node.name === 'where' || node.name === 'has' || node.name === 'not') { if (node.name === 'is' || node.name === 'where' || node.name === 'has' || node.name === 'not') {
context.next(); context.next();
} }
},
PseudoElementSelector() {
// Functional pseudo-element arguments are not scoped as selectors.
} }
}; };

@ -19,6 +19,9 @@ const LINE_BREAK_THRESHOLD = 50;
*/ */
export function print(ast, options = undefined) { export function print(ast, options = undefined) {
const comments = (ast.type === 'Root' && ast.comments) || []; const comments = (ast.type === 'Root' && ast.comments) || [];
const css_comments =
(ast.type === 'Root' ? ast.css?.comments : ast.type === 'StyleSheet' ? ast.comments : null) ||
[];
return esrap.print( return esrap.print(
ast, ast,
@ -29,7 +32,7 @@ export function print(ast, options = undefined) {
getTrailingComments: options?.getTrailingComments getTrailingComments: options?.getTrailingComments
}), }),
...svelte_visitors(comments), ...svelte_visitors(comments),
...css_visitors ...css_visitors(css_comments, comments)
}), }),
{ {
indent: options?.indent indent: options?.indent
@ -177,161 +180,263 @@ function base_element(node, context, comments) {
context.append(child_context); context.append(child_context);
} }
/** @type {Visitors<AST.SvelteNode>} */ /**
const css_visitors = { * @param {AST.CSS.CSSComment[]} comments
Atrule(node, context) { * @param {AST.JSComment[]} js_comments
context.write(`@${node.name}`); * @returns {Visitors<AST.SvelteNode>}
if (node.prelude) context.write(` ${node.prelude}`); */
function css_visitors(comments, js_comments) {
if (node.block) { let comment_index = 0;
context.write(' ');
context.visit(node.block); /** @param {number} end */
} else { const has_comment_before = (end) => comments[comment_index]?.start < end;
context.write(';');
} /**
}, * @param {Context} context
* @param {AST.CSS.CSSComment} comment
*/
function write_comment(context, comment) {
context.write(`/*${comment.value}*/`);
}
AttributeSelector(node, context) { /**
context.write(`[${node.name}`); * @param {Context} context
if (node.matcher) { * @param {number} end
context.write(node.matcher); */
context.write(`"${node.value}"`); function write_inline_comments(context, end) {
if (node.flags) { let written = false;
context.write(` ${node.flags}`);
} while (has_comment_before(end)) {
if (written) context.write(' ');
write_comment(context, comments[comment_index++]);
written = true;
} }
context.write(']');
},
Block(node, context) { return written;
context.write('{'); }
if (node.children.length > 0) { /**
context.indent(); * @param {Context} context
context.newline(); * @param {string} value
* @param {number} end
*/
function write_value(context, value, end) {
let offset = 0;
while (has_comment_before(end)) {
const comment = comments[comment_index++];
const position = Math.max(offset, Math.min(comment.position ?? 0, value.length));
context.write(value.slice(offset, position));
write_comment(context, comment);
offset = position;
}
let started = false; context.write(value.slice(offset));
}
for (const child of node.children) { /**
if (started) { * @param {Context} context
context.newline(); * @param {Array<AST.CSS.Rule | AST.CSS.Atrule | AST.CSS.Declaration>} children
} * @param {number} end
* @param {boolean} margins
*/
function print_children(context, children, end, margins) {
let started = false;
context.visit(child); const separate = () => {
if (!started) return;
if (margins) context.margin();
context.newline();
};
for (const child of children) {
while (has_comment_before(child.start)) {
separate();
write_comment(context, comments[comment_index++]);
started = true; started = true;
} }
context.dedent(); separate();
context.newline(); context.visit(child);
started = true;
} }
context.write('}'); while (has_comment_before(end)) {
}, separate();
write_comment(context, comments[comment_index++]);
started = true;
}
}
ClassSelector(node, context) { /**
context.write(`.${node.name}`); * @param {AST.CSS.SelectorList} node
}, * @param {Context} context
* @param {boolean} multiline
*/
function print_selector_list(node, context, multiline) {
let needs_separator = false;
let remaining_selectors = node.children.length;
ComplexSelector(node, context) {
for (const selector of node.children) { for (const selector of node.children) {
while (has_comment_before(selector.start)) {
if (needs_separator) context.write(' ');
write_comment(context, comments[comment_index++]);
needs_separator = true;
}
if (needs_separator) {
if (multiline) context.newline();
else context.write(' ');
}
context.visit(selector); context.visit(selector);
needs_separator = true;
remaining_selectors -= 1;
if (remaining_selectors > 0) context.write(',');
} }
}, }
Declaration(node, context) { return {
context.write(`${node.property}: ${node.value};`); Atrule(node, context) {
}, context.write(`@${node.name}`);
IdSelector(node, context) { const prelude_end = node.block?.start ?? node.end;
context.write(`#${node.name}`); if (node.prelude || has_comment_before(prelude_end)) {
}, context.write(' ');
write_value(context, node.prelude, prelude_end);
}
NestingSelector(node, context) { if (node.block) {
context.write('&'); context.write(' ');
}, context.visit(node.block);
} else {
context.write(';');
}
},
AttributeSelector(node, context) {
context.write(`[${node.name}`);
if (node.matcher) {
context.write(node.matcher);
context.write(`"${node.value}"`);
if (node.flags) context.write(` ${node.flags}`);
}
context.write(']');
},
Nth(node, context) { Block(node, context) {
context.write(node.value); context.write('{');
},
Percentage(node, context) { if (node.children.length > 0 || has_comment_before(node.end)) {
context.write(node.value); context.indent();
}, context.newline();
print_children(context, node.children, node.end, false);
context.dedent();
context.newline();
}
PseudoClassSelector(node, context) { context.write('}');
context.write(`:${node.name}`); },
if (node.args) { ClassSelector(node, context) {
context.write('('); context.write(`.${node.name}`);
},
let started = false; ComplexSelector(node, context) {
for (const selector of node.children) context.visit(selector);
},
for (const arg of node.args.children) { Declaration(node, context) {
if (started) { context.write(`${node.property}: `);
context.write(', '); write_value(context, node.value, node.end);
} context.write(';');
},
context.visit(arg); IdSelector(node, context) {
context.write(`#${node.name}`);
},
started = true; NestingSelector(node, context) {
} context.write('&');
},
context.write(')'); Nth(node, context) {
} context.write(node.value);
}, },
PseudoElementSelector(node, context) { Percentage(node, context) {
context.write(`::${node.name}`); context.write(node.value);
}, },
RelativeSelector(node, context) { PseudoClassSelector(node, context) {
if (node.combinator) { context.write(`:${node.name}`);
if (node.combinator.name === ' ') {
context.write(' '); if (node.args) {
} else { context.write('(');
context.write(` ${node.combinator.name} `); context.visit(node.args);
if (has_comment_before(node.end)) {
context.write(' ');
write_inline_comments(context, node.end);
}
context.write(')');
} }
} },
PseudoElementSelector(node, context) {
context.write(`::${node.name}`);
if (node.args) {
context.write('(');
context.visit(node.args);
if (has_comment_before(node.end)) {
context.write(' ');
write_inline_comments(context, node.end);
}
context.write(')');
}
},
for (const selector of node.selectors) { RelativeSelector(node, context) {
context.visit(selector); if (node.combinator) {
} if (node.combinator.name === ' ') context.write(' ');
}, else context.write(` ${node.combinator.name} `);
}
Rule(node, context) { for (const selector of node.selectors) context.visit(selector);
let started = false; },
for (const selector of node.prelude.children) { Rule(node, context) {
if (started) { print_selector_list(node.prelude, context, true);
context.write(','); context.write(' ');
context.newline(); if (write_inline_comments(context, node.block.start)) context.write(' ');
} context.visit(node.block);
},
context.visit(selector); SelectorList(node, context) {
started = true; print_selector_list(node, context, false);
} },
context.write(' '); StyleSheet(node, context) {
context.visit(node.block); context.write('<style');
}, attributes(node, node.attributes, context, js_comments);
context.write('>');
SelectorList(node, context) { if (node.children.length > 0 || node.comments.length > 0) {
let started = false; context.indent();
for (const selector of node.children) { context.newline();
if (started) { print_children(context, node.children, node.content.end, true);
context.write(', '); context.dedent();
context.newline();
} }
context.visit(selector); context.write('</style>');
started = true; },
}
},
TypeSelector(node, context) { TypeSelector(node, context) {
context.write(node.name); context.write(node.name);
} }
}; };
}
/** /**
* @param {AST.JSComment[]} comments * @param {AST.JSComment[]} comments
@ -845,34 +950,6 @@ const svelte_visitors = (comments) => ({
} }
}, },
StyleSheet(node, context) {
context.write('<style');
attributes(node, node.attributes, context, comments);
context.write('>');
if (node.children.length > 0) {
context.indent();
context.newline();
let started = false;
for (const child of node.children) {
if (started) {
context.margin();
context.newline();
}
context.visit(child);
started = true;
}
context.dedent();
context.newline();
}
context.write('</style>');
},
SvelteBody(node, context) { SvelteBody(node, context) {
base_element(node, context, comments); base_element(node, context, comments);
}, },

@ -8,6 +8,15 @@ export namespace _CSS {
export interface StyleSheetBase extends BaseNode { export interface StyleSheetBase extends BaseNode {
children: Array<Atrule | Rule>; children: Array<Atrule | Rule>;
/** CSS comments in source order */
comments: CSSComment[];
}
export interface CSSComment extends BaseNode {
type: 'CSSComment';
value: string;
/** Character offset in a containing declaration value or at-rule prelude */
position?: number;
} }
export interface StyleSheetFile extends StyleSheetBase { export interface StyleSheetFile extends StyleSheetBase {
@ -135,6 +144,7 @@ export namespace _CSS {
export interface PseudoElementSelector extends BaseNode { export interface PseudoElementSelector extends BaseNode {
type: 'PseudoElementSelector'; type: 'PseudoElementSelector';
name: string; name: string;
args?: SelectorList;
} }
export interface PseudoClassSelector extends BaseNode { export interface PseudoClassSelector extends BaseNode {

@ -69,9 +69,21 @@ describe('parseCss', () => {
}); });
it('parses comments', () => { it('parses comments', () => {
const ast = parseCss('/* comment */ div { color: red; }'); const ast = parseCss(
'/* top */ div, /* selector */ span { /* block */ color: /* value */ red; }'
);
assert.equal(ast.children.length, 1); assert.equal(ast.children.length, 1);
assert.equal(ast.children[0].type, 'Rule'); assert.equal(ast.children[0].type, 'Rule');
assert.deepEqual(
ast.comments.map((comment) => comment.value),
[' top ', ' selector ', ' block ', ' value ']
);
assert.deepEqual(ast.comments[0], {
type: 'CSSComment',
value: ' top ',
start: 0,
end: 9
});
}); });
it('parses complex selectors', () => { it('parses complex selectors', () => {

@ -33,6 +33,7 @@
"type": "Style", "type": "Style",
"start": 16, "start": 16,
"end": 56, "end": 56,
"comments": [],
"attributes": [], "attributes": [],
"children": [ "children": [
{ {

@ -33,6 +33,7 @@
"type": "Style", "type": "Style",
"start": 16, "start": 16,
"end": 66, "end": 66,
"comments": [],
"attributes": [], "attributes": [],
"children": [ "children": [
{ {

@ -1071,6 +1071,14 @@
"end": 797 "end": 797
} }
], ],
"comments": [
{
"type": "CSSComment",
"value": " test that all these are parsed correctly ",
"start": 12,
"end": 58
}
],
"content": { "content": {
"start": 7, "start": 7,
"end": 798, "end": 798,
@ -1125,5 +1133,6 @@
} }
] ]
}, },
"options": null "options": null,
"comments": []
} }

@ -25,7 +25,35 @@
"type": "PseudoElementSelector", "type": "PseudoElementSelector",
"name": "view-transition-old", "name": "view-transition-old",
"start": 60, "start": 60,
"end": 81 "end": 86,
"args": {
"type": "SelectorList",
"start": 82,
"end": 85,
"children": [
{
"type": "ComplexSelector",
"start": 82,
"end": 85,
"children": [
{
"type": "RelativeSelector",
"combinator": null,
"selectors": [
{
"type": "TypeSelector",
"name": "x-y",
"start": 82,
"end": 85
}
],
"start": 82,
"end": 85
}
]
}
]
}
} }
], ],
"start": 60, "start": 60,
@ -89,7 +117,35 @@
"type": "PseudoElementSelector", "type": "PseudoElementSelector",
"name": "view-transition-old", "name": "view-transition-old",
"start": 119, "start": 119,
"end": 140 "end": 145,
"args": {
"type": "SelectorList",
"start": 141,
"end": 144,
"children": [
{
"type": "ComplexSelector",
"start": 141,
"end": 144,
"children": [
{
"type": "RelativeSelector",
"combinator": null,
"selectors": [
{
"type": "TypeSelector",
"name": "x-y",
"start": 141,
"end": 144
}
],
"start": 141,
"end": 144
}
]
}
]
}
} }
], ],
"start": 119, "start": 119,
@ -147,7 +203,35 @@
"type": "PseudoElementSelector", "type": "PseudoElementSelector",
"name": "highlight", "name": "highlight",
"start": 171, "start": 171,
"end": 182 "end": 199,
"args": {
"type": "SelectorList",
"start": 183,
"end": 198,
"children": [
{
"type": "ComplexSelector",
"start": 183,
"end": 198,
"children": [
{
"type": "RelativeSelector",
"combinator": null,
"selectors": [
{
"type": "TypeSelector",
"name": "rainbow-color-1",
"start": 183,
"end": 198
}
],
"start": 183,
"end": 198
}
]
}
]
}
} }
], ],
"start": 171, "start": 171,
@ -200,7 +284,35 @@
"type": "PseudoElementSelector", "type": "PseudoElementSelector",
"name": "part", "name": "part",
"start": 234, "start": 234,
"end": 240 "end": 245,
"args": {
"type": "SelectorList",
"start": 241,
"end": 244,
"children": [
{
"type": "ComplexSelector",
"start": 241,
"end": 244,
"children": [
{
"type": "RelativeSelector",
"combinator": null,
"selectors": [
{
"type": "TypeSelector",
"name": "foo",
"start": 241,
"end": 244
}
],
"start": 241,
"end": 244
}
]
}
]
}
} }
], ],
"start": 220, "start": 220,
@ -247,7 +359,35 @@
"type": "PseudoElementSelector", "type": "PseudoElementSelector",
"name": "slotted", "name": "slotted",
"start": 266, "start": 266,
"end": 275 "end": 285,
"args": {
"type": "SelectorList",
"start": 276,
"end": 284,
"children": [
{
"type": "ComplexSelector",
"start": 276,
"end": 284,
"children": [
{
"type": "RelativeSelector",
"combinator": null,
"selectors": [
{
"type": "ClassSelector",
"name": "content",
"start": 276,
"end": 284
}
],
"start": 276,
"end": 284
}
]
}
]
}
} }
], ],
"start": 266, "start": 266,
@ -390,6 +530,26 @@
"end": 377 "end": 377
} }
], ],
"comments": [
{
"type": "CSSComment",
"value": " test that all these are parsed correctly ",
"start": 12,
"end": 58
},
{
"type": "CSSComment",
"value": "button",
"start": 311,
"end": 321
},
{
"type": "CSSComment",
"value": "p after h1",
"start": 332,
"end": 346
}
],
"content": { "content": {
"start": 7, "start": 7,
"end": 378, "end": 378,
@ -405,5 +565,6 @@
"type": "Fragment", "type": "Fragment",
"nodes": [] "nodes": []
}, },
"options": null "options": null,
"comments": []
} }

@ -53,6 +53,7 @@
"end": 82 "end": 82
} }
], ],
"comments": [],
"content": { "content": {
"start": 61, "start": 61,
"end": 83, "end": 83,
@ -77,6 +78,24 @@
] ]
}, },
"options": null, "options": null,
"comments": [
{
"type": "Line",
"value": " script and style but no markup",
"start": 10,
"end": 43,
"loc": {
"start": {
"line": 2,
"column": 1
},
"end": {
"line": 2,
"column": 34
}
}
}
],
"instance": { "instance": {
"type": "Script", "type": "Script",
"start": 0, "start": 0,

@ -68,6 +68,7 @@
"end": 196 "end": 196
} }
], ],
"comments": [],
"content": { "content": {
"start": 43, "start": 43,
"end": 197, "end": 197,
@ -122,5 +123,6 @@
} }
] ]
}, },
"options": null "options": null,
"comments": []
} }

@ -0,0 +1,26 @@
<style>
/* first */
/* second */
.foo, /* between */
.bar /* trailing */ {
/* block */
color: r /* value */ ed;
}
@media screen/* media */and (width > 0) {
/* nested */
.baz {
display: block;
}
}
.empty {
/* only */
}
.part::part(/* argument */ label) {
display: block;
}
/* last */
</style>

@ -0,0 +1,28 @@
<style>
/* first */
/* second */
.foo, /* between */
.bar /* trailing */ {
/* block */
color: r /* value */ ed;
}
@media screen/* media */and (width > 0) {
/* nested */
.baz {
display: block;
}
}
.empty {
/* only */
}
.part::part(/* argument */ label) {
display: block;
}
/* last */
</style>

@ -1694,6 +1694,15 @@ declare module 'svelte/compiler' {
export interface StyleSheetBase extends BaseNode { export interface StyleSheetBase extends BaseNode {
children: Array<Atrule | Rule>; children: Array<Atrule | Rule>;
/** CSS comments in source order */
comments: CSSComment[];
}
export interface CSSComment extends BaseNode {
type: 'CSSComment';
value: string;
/** Character offset in a containing declaration value or at-rule prelude */
position?: number;
} }
export interface StyleSheetFile extends StyleSheetBase { export interface StyleSheetFile extends StyleSheetBase {
@ -1788,6 +1797,7 @@ declare module 'svelte/compiler' {
export interface PseudoElementSelector extends BaseNode { export interface PseudoElementSelector extends BaseNode {
type: 'PseudoElementSelector'; type: 'PseudoElementSelector';
name: string; name: string;
args?: SelectorList;
} }
export interface PseudoClassSelector extends BaseNode { export interface PseudoClassSelector extends BaseNode {

Loading…
Cancel
Save