fix: preserve CSS comments when printing an AST

fix/preserve-css-comments-when-printing-an-ast
Manuel Serret 2 weeks ago
parent eae50dfd1c
commit 9d01b8bc8e

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: preserve CSS comments when printing an AST

@ -48,13 +48,17 @@ export default function read_style(parser, start, attributes) {
/**
* @param {Parser} parser
* @param {(parser: Parser) => boolean} finished
* @returns {Array<AST.CSS.Rule | AST.CSS.Atrule>}
* @returns {Array<AST.CSS.Rule | AST.CSS.Atrule | AST.CSS.CSSComment>}
*/
function read_body(parser, finished) {
/** @type {Array<AST.CSS.Rule | AST.CSS.Atrule>} */
/** @type {Array<AST.CSS.Rule | AST.CSS.Atrule | AST.CSS.CSSComment>} */
const children = [];
while ((allow_comment_or_whitespace(parser), !finished(parser))) {
while (parser.index < parser.template.length) {
children.push(...read_comments_and_whitespace(parser));
if (finished(parser)) break;
if (parser.match('@')) {
children.push(read_at_rule(parser));
} else {
@ -93,7 +97,8 @@ function read_at_rule(parser) {
start,
end: parser.index,
name,
prelude,
prelude: prelude.value,
...(prelude.raw && { raw: prelude.raw }),
block
};
}
@ -126,10 +131,10 @@ function read_rule(parser) {
* @returns {AST.CSS.SelectorList}
*/
function read_selector_list(parser, inside_pseudo_class = false) {
/** @type {AST.CSS.ComplexSelector[]} */
/** @type {Array<AST.CSS.ComplexSelector | AST.CSS.CSSComment>} */
const children = [];
allow_comment_or_whitespace(parser);
children.push(...read_comments_and_whitespace(parser));
const start = parser.index;
@ -138,7 +143,7 @@ function read_selector_list(parser, inside_pseudo_class = false) {
const end = parser.index;
allow_comment_or_whitespace(parser);
children.push(...read_comments_and_whitespace(parser));
if (inside_pseudo_class ? parser.match(')') : parser.match('{')) {
return {
@ -149,7 +154,7 @@ function read_selector_list(parser, inside_pseudo_class = false) {
};
} else {
parser.eat(',', true);
allow_comment_or_whitespace(parser);
children.push(...read_comments_and_whitespace(parser));
}
}
@ -323,7 +328,7 @@ function read_selector(parser, inside_pseudo_class = false) {
}
const index = parser.index;
allow_comment_or_whitespace(parser);
read_comments_and_whitespace(parser);
if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) {
// rewind, so we know whether to continue building the selector list
@ -412,11 +417,11 @@ function read_block(parser) {
parser.eat('{', true);
/** @type {Array<AST.CSS.Declaration | AST.CSS.Rule | AST.CSS.Atrule>} */
/** @type {Array<AST.CSS.Declaration | AST.CSS.Rule | AST.CSS.Atrule | AST.CSS.CSSComment>} */
const children = [];
while (parser.index < parser.template.length) {
allow_comment_or_whitespace(parser);
children.push(...read_comments_and_whitespace(parser));
if (parser.match('}')) {
break;
@ -471,7 +476,7 @@ function read_declaration(parser) {
const value = read_value(parser);
if (!value && !property.startsWith('--')) {
if (!value.value && !property.startsWith('--')) {
e.css_empty_declaration({ start, end: index });
}
@ -486,16 +491,19 @@ function read_declaration(parser) {
start,
end,
property,
value
value: value.value,
...(value.raw && { raw: value.raw })
};
}
/**
* @param {Parser} parser
* @returns {string}
* @returns {{ value: string; raw: string | null }}
*/
function read_value(parser) {
const start = parser.index;
let value = '';
let has_comment = false;
let escaped = false;
let in_url = false;
@ -523,13 +531,19 @@ function read_value(parser) {
} else if (char === '(' && value.slice(-3) === 'url') {
in_url = true;
} else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) {
return value.trim();
const normalized = value.trim();
return {
value: normalized,
raw: has_comment ? parser.template.slice(start, parser.index).trim() : null
};
} else if (
char === '/' &&
!in_url &&
!quote_mark &&
parser.template[parser.index + 1] === '*'
) {
has_comment = true;
parser.index += 2;
while (parser.index < parser.template.length) {
if (parser.template[parser.index] === '*' && parser.template[parser.index + 1] === '/') {
@ -624,13 +638,26 @@ function read_identifier(parser) {
return identifier;
}
/** @param {Parser} parser */
function allow_comment_or_whitespace(parser) {
/**
* @param {Parser} parser
* @returns {AST.CSS.CSSComment[]}
*/
function read_comments_and_whitespace(parser) {
/** @type {AST.CSS.CSSComment[]} */
const comments = [];
parser.allow_whitespace();
while (parser.match('/*') || parser.match('<!--')) {
if (parser.eat('/*')) {
parser.read_until(REGEX_COMMENT_CLOSE);
const start = parser.index - 2;
const data = parser.read_until(REGEX_COMMENT_CLOSE);
parser.eat('*/', true);
comments.push({
type: 'CSSComment',
start,
end: parser.index,
data
});
}
if (parser.eat('<!--')) {
@ -640,12 +667,14 @@ function allow_comment_or_whitespace(parser) {
parser.allow_whitespace();
}
return comments;
}
/**
* Parse standalone CSS content (not wrapped in `<style>`).
* @param {Parser} parser
* @returns {Array<AST.CSS.Rule | AST.CSS.Atrule>}
* @returns {Array<AST.CSS.Rule | AST.CSS.Atrule | AST.CSS.CSSComment>}
*/
export function parse_stylesheet(parser) {
return read_body(parser, (p) => p.index >= p.template.length);

@ -3,7 +3,7 @@
/** @import { Visitors } from 'zimmerframe' */
import { walk } from 'zimmerframe';
import * as e from '../../../errors.js';
import { is_keyframes_node } from '../../css.js';
import { is_keyframes_node, without_css_comments } from '../../css.js';
import { is_global, is_unscoped_pseudo_class } from './utils.js';
/**
@ -91,7 +91,8 @@ const css_visitors = {
const selector = relative_selector.selectors[i];
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
const child = selector.args?.children[0].children[0];
const child =
selector.args && without_css_comments(selector.args.children)[0]?.children[0];
// ensure `:global(element)` to be at the first position in a compound selector
if (child?.selectors[0].type === 'TypeSelector' && i !== 0) {
e.css_global_invalid_selector_list(selector);
@ -106,7 +107,7 @@ const css_visitors = {
// (standalone :global() with multiple selectors is OK)
if (
selector.args !== null &&
selector.args.children.length > 1 &&
without_css_comments(selector.args.children).length > 1 &&
(node.children.length > 1 || relative_selector.selectors.length > 1)
) {
e.css_global_invalid_selector(selector);
@ -130,9 +131,9 @@ const css_visitors = {
const first = node.children[0]?.selectors[1];
const no_nesting_scope =
first?.type !== 'PseudoClassSelector' || is_unscoped_pseudo_class(first);
const parent_is_global = node.metadata.rule.metadata.parent_rule.prelude.children.some(
(child) => child.children.length === 1 && child.children[0].metadata.is_global
);
const parent_is_global = without_css_comments(
node.metadata.rule.metadata.parent_rule.prelude.children
).some((child) => child.children.length === 1 && child.children[0].metadata.is_global);
// mark `&:hover` in `:global(.foo) { &:hover { color: green }}` as used
if (no_nesting_scope && parent_is_global) {
node.metadata.used = true;
@ -196,9 +197,10 @@ const css_visitors = {
},
Rule(node, context) {
node.metadata.parent_rule = context.state.rule;
const complex_selectors = without_css_comments(node.prelude.children);
// We gotta allow :global x, :global y because CSS preprocessors might generate that from :global { x, y {...} }
for (const complex_selector of node.prelude.children) {
for (const complex_selector of complex_selectors) {
let is_global_block = false;
for (let selector_idx = 0; selector_idx < complex_selector.children.length; selector_idx++) {
@ -238,7 +240,7 @@ const css_visitors = {
complex_selector.children.length === 1 &&
complex_selector.children[0].selectors.length === 1; // just `:global`, not e.g. `:global x`
if (is_lone_global && node.prelude.children.length > 1) {
if (is_lone_global && complex_selectors.length > 1) {
// `:global, :global x { z { ... } }` would become `x { z { ... } }` which means `z` is always
// constrained by `x`, which is not what the user intended
e.css_global_block_invalid_list(node.prelude);
@ -247,7 +249,7 @@ const css_visitors = {
if (
declaration &&
// :global { color: red; } is invalid, but foo :global { color: red; } is valid
node.prelude.children.length === 1 &&
complex_selectors.length === 1 &&
is_lone_global
) {
e.css_global_block_invalid_declaration(declaration);
@ -268,7 +270,7 @@ const css_visitors = {
// visit selector list first, to populate child selector metadata
context.visit(node.prelude, state);
for (const selector of node.prelude.children) {
for (const selector of complex_selectors) {
node.metadata.has_global_selectors ||= selector.metadata.is_global;
node.metadata.has_local_selectors ||= !selector.metadata.is_global;
}
@ -290,14 +292,15 @@ const css_visitors = {
if (!parent_rule) {
// https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#using_outside_nested_rule
const children = rule.prelude.children;
const children = without_css_comments(rule.prelude.children);
const selectors = children[0].children[0].selectors;
if (
children.length > 1 ||
selectors.length > 1 ||
selectors[0].type !== 'PseudoClassSelector' ||
selectors[0].name !== 'global' ||
selectors[0].args?.children[0]?.children[0].selectors[0] !== node
(selectors[0].args &&
without_css_comments(selectors[0].args.children)[0]?.children[0].selectors[0]) !== node
) {
e.css_nesting_selector_invalid_placement(node);
}
@ -305,8 +308,8 @@ const css_visitors = {
// :global { &.foo { ... } } is invalid
parent_rule.metadata.is_global_block &&
!parent_rule.metadata.parent_rule &&
parent_rule.prelude.children[0].children.length === 1 &&
parent_rule.prelude.children[0].children[0].selectors.length === 1
without_css_comments(parent_rule.prelude.children)[0].children.length === 1 &&
without_css_comments(parent_rule.prelude.children)[0].children[0].selectors.length === 1
) {
e.css_global_block_invalid_modifier_start(node);
}

@ -7,6 +7,7 @@ import {
is_unscoped_pseudo_class
} from './utils.js';
import { regex_ends_with_whitespace, regex_starts_with_whitespace } from '../../patterns.js';
import { without_css_comments } from '../../css.js';
import { get_attribute_chunks, is_text_attribute } from '../../../utils/ast.js';
/** @typedef {typeof NODE_PROBABLY_EXISTS | typeof NODE_DEFINITELY_EXISTS} NodeExistsValue */
@ -407,11 +408,13 @@ function is_global(selector, rule) {
selector_list = owner.prelude;
}
const has_global_selectors = !!selector_list?.children.some((complex_selector) => {
return complex_selector.children.every((relative_selector) =>
is_global(relative_selector, owner)
);
});
const has_global_selectors =
!!selector_list &&
without_css_comments(selector_list.children).some((complex_selector) => {
return complex_selector.children.every((relative_selector) =>
is_global(relative_selector, owner)
);
});
explicitly_global ||= has_global_selectors;
if (!has_global_selectors && !can_be_global) {
@ -448,9 +451,11 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
const rules = get_parent_rules(rule);
include_self =
rules.some((r) =>
r.prelude.children.some((c) => c.children.some((s) => is_global(s, r)))
without_css_comments(r.prelude.children).some((c) =>
c.children.some((s) => is_global(s, r))
)
) ||
rules[rules.length - 1].prelude.children.some((c) =>
without_css_comments(rules[rules.length - 1].prelude.children).some((c) =>
c.children.some((r) =>
r.selectors.some(
(s) =>
@ -464,8 +469,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
// :has(...) is special in that it means "look downwards in the CSS tree". Since our matching algorithm goes
// upwards and back-to-front, we need to first check the selectors inside :has(...), then check the rest of the
// selector in a way that is similar to ancestor matching. In a sense, we're treating `.x:has(.y)` as `.x .y`.
const complex_selectors = /** @type {Compiler.AST.CSS.SelectorList} */ (selector.args)
.children;
const complex_selectors = without_css_comments(selector.args.children);
let matched = false;
for (const complex_selector of complex_selectors) {
@ -522,7 +526,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
relative_selector.selectors.length === 1
) {
const args = selector.args;
const complex_selector = args.children[0];
const complex_selector = without_css_comments(args.children)[0];
return apply_selector(complex_selector.children, rule, element, BACKWARD);
}
@ -533,7 +537,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
// because they are then _more_ likely to bleed out of the component. The exception is complex selectors
// with descendants, in which case we scope them all.
if (name === 'not' && selector.args) {
for (const complex_selector of selector.args.children) {
for (const complex_selector of without_css_comments(selector.args.children)) {
walk(complex_selector, null, {
ComplexSelector(node, context) {
node.metadata.used = true;
@ -565,7 +569,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
if ((name === 'is' || name === 'where') && selector.args) {
let matched = false;
for (const complex_selector of selector.args.children) {
for (const complex_selector of without_css_comments(selector.args.children)) {
const relative = truncate(complex_selector);
const is_global = relative.length === 0;
@ -651,7 +655,7 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
const parent = /** @type {Compiler.AST.CSS.Rule} */ (rule.metadata.parent_rule);
for (const complex_selector of parent.prelude.children) {
for (const complex_selector of without_css_comments(parent.prelude.children)) {
if (
apply_selector(get_relative_selectors(complex_selector), parent, element, direction) ||
complex_selector.children.every((s) => is_global(s, parent))

@ -1,5 +1,7 @@
/** @import { AST } from '#compiler' */
/** @import { Node } from 'estree' */
import { without_css_comments } from '../../css.js';
const UNKNOWN = {};
/**
@ -147,10 +149,12 @@ export function is_unscoped_pseudo_class(selector) {
// one selector in the :not (.e.g :not(.x .y)), then .x and .y should be scoped
(selector.name !== 'not' ||
selector.args === null ||
selector.args.children.every((c) => c.children.length === 1))) ||
without_css_comments(selector.args.children).every((c) => c.children.length === 1))) ||
// selectors with has/is/where/not can also be global if all their children are global
selector.args === null ||
selector.args.children.every((c) => c.children.every((r) => is_global(r))))
without_css_comments(selector.args.children).every((c) =>
c.children.every((r) => is_global(r))
))
);
}

@ -3,7 +3,12 @@
/** @import { ComponentAnalysis } from '../../types.js' */
import MagicString from 'magic-string';
import { walk } from 'zimmerframe';
import { is_keyframes_node, regex_css_name_boundary, remove_css_prefix } from '../../css.js';
import {
is_keyframes_node,
regex_css_name_boundary,
remove_css_prefix,
without_css_comments
} from '../../css.js';
import { merge_with_preprocessor_map } from '../../../utils/mapped_code.js';
import { dev } from '../../../state.js';
@ -168,10 +173,11 @@ const visitors = {
}
if (node.metadata.is_global_block) {
const selector = node.prelude.children[0];
const selectors = without_css_comments(node.prelude.children);
const selector = selectors[0];
if (
node.prelude.children.length === 1 &&
selectors.length === 1 &&
selector.children.length === 1 &&
selector.children[0].selectors.length === 1
) {
@ -197,14 +203,15 @@ const visitors = {
},
SelectorList(node, { state, next, path }) {
const parent = path.at(-1);
const selectors = without_css_comments(node.children);
// Only add comments if we're not inside a complex selector that itself is unused or a global block
if (
(!is_in_global_block(path) ||
(node.children.length > 1 && parent?.type === 'Rule' && parent.metadata.is_global_block)) &&
(selectors.length > 1 && parent?.type === 'Rule' && parent.metadata.is_global_block)) &&
!path.find((n) => n.type === 'ComplexSelector' && !n.metadata.used)
) {
const children = node.children;
const children = selectors;
let pruning = false;
let prune_start = children[0].start;
let last = prune_start;
@ -298,10 +305,12 @@ const visitors = {
// In case of multiple :global selectors in a selector list we gotta delete the comma, too, but only if
// the next selector is used; if it's unused then the comma deletion happens as part of removal of that next selector
if (
parent_rule.prelude.children.length > 1 &&
without_css_comments(parent_rule.prelude.children).length > 1 &&
node.children.length === node.children.findIndex((s) => s === relative_selector) - 1
) {
const next_selector = parent_rule.prelude.children.find((s) => s.start > global.end);
const next_selector = without_css_comments(parent_rule.prelude.children).find(
(s) => s.start > global.end
);
if (next_selector && next_selector.metadata.used) {
context.state.code.update(global.end, next_selector.start, '');
}
@ -423,7 +432,7 @@ function remove_preceding_whitespace(end, state) {
*/
function is_empty(rule, is_in_global_block) {
if (rule.metadata.is_global_block) {
return rule.block.children.length === 0;
return rule.block.children.every((child) => child.type === 'CSSComment');
}
for (const child of rule.block.children) {
@ -438,7 +447,12 @@ function is_empty(rule, is_in_global_block) {
}
if (child.type === 'Atrule') {
if (child.block === null || child.block.children.length > 0) return false;
if (
child.block === null ||
child.block.children.some((child) => child.type !== 'CSSComment')
) {
return false;
}
}
}
@ -447,7 +461,7 @@ function is_empty(rule, is_in_global_block) {
/** @param {AST.CSS.Rule} rule */
function is_used(rule) {
return rule.prelude.children.some((selector) => selector.metadata.used);
return without_css_comments(rule.prelude.children).some((selector) => selector.metadata.used);
}
/**

@ -12,3 +12,7 @@ export function remove_css_prefix(name) {
/** @param {AST.CSS.Atrule} node */
export const is_keyframes_node = (node) => remove_css_prefix(node.name) === 'keyframes';
/** @param {AST.CSS.SelectorList['children']} children */
export const without_css_comments = (children) =>
children.filter((child) => child.type !== 'CSSComment');

@ -168,11 +168,49 @@ function base_element(node, context, comments) {
context.append(child_context);
}
/**
* @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.filter(
(child) => child.type === 'ComplexSelector'
).length;
for (let i = 0; i < node.children.length; i += 1) {
const child = node.children[i];
if (child.type === 'CSSComment') {
if (needs_separator) context.write(' ');
context.visit(child);
needs_separator = true;
continue;
}
if (needs_separator) {
if (multiline) context.newline();
else context.write(' ');
}
context.visit(child);
needs_separator = true;
remaining_selectors -= 1;
if (remaining_selectors > 0) {
context.write(',');
}
}
}
/** @type {Visitors<AST.SvelteNode>} */
const css_visitors = {
Atrule(node, context) {
context.write(`@${node.name}`);
if (node.prelude) context.write(` ${node.prelude}`);
if (node.prelude || node.raw) {
context.write(` ${node.raw ?? node.prelude}`);
}
if (node.block) {
context.write(' ');
@ -224,6 +262,10 @@ const css_visitors = {
context.write(`.${node.name}`);
},
CSSComment(node, context) {
context.write(`/*${node.data}*/`);
},
ComplexSelector(node, context) {
for (const selector of node.children) {
context.visit(selector);
@ -231,7 +273,7 @@ const css_visitors = {
},
Declaration(node, context) {
context.write(`${node.property}: ${node.value};`);
context.write(`${node.property}: ${node.raw ?? node.value};`);
},
IdSelector(node, context) {
@ -255,19 +297,7 @@ const css_visitors = {
if (node.args) {
context.write('(');
let started = false;
for (const arg of node.args.children) {
if (started) {
context.write(', ');
}
context.visit(arg);
started = true;
}
context.visit(node.args);
context.write(')');
}
},
@ -291,32 +321,13 @@ const css_visitors = {
},
Rule(node, context) {
let started = false;
for (const selector of node.prelude.children) {
if (started) {
context.write(',');
context.newline();
}
context.visit(selector);
started = true;
}
print_selector_list(node.prelude, context, true);
context.write(' ');
context.visit(node.block);
},
SelectorList(node, context) {
let started = false;
for (const selector of node.children) {
if (started) {
context.write(', ');
}
context.visit(selector);
started = true;
}
print_selector_list(node, context, false);
},
TypeSelector(node, context) {

@ -7,7 +7,7 @@ export namespace _CSS {
}
export interface StyleSheetBase extends BaseNode {
children: Array<Atrule | Rule>;
children: Array<Atrule | Rule | CSSComment>;
}
export interface StyleSheetFile extends StyleSheetBase {
@ -30,9 +30,15 @@ export namespace _CSS {
type: 'Atrule';
name: string;
prelude: string;
raw?: string;
block: Block | null;
}
export interface CSSComment extends BaseNode {
type: 'CSSComment';
data: string;
}
export interface Rule extends BaseNode {
type: 'Rule';
prelude: SelectorList;
@ -60,7 +66,7 @@ export namespace _CSS {
/**
* The `a`, `b` and `c` in `a, b, c {}`
*/
children: ComplexSelector[];
children: Array<ComplexSelector | CSSComment>;
}
/**
@ -176,18 +182,20 @@ export namespace _CSS {
export interface Block extends BaseNode {
type: 'Block';
children: Array<Declaration | Rule | Atrule>;
children: Array<Declaration | Rule | Atrule | CSSComment>;
}
export interface Declaration extends BaseNode {
type: 'Declaration';
property: string;
value: string;
raw?: string;
}
// for zimmerframe
export type Node =
| StyleSheet
| CSSComment
| Rule
| Atrule
| SelectorList

@ -70,8 +70,58 @@ describe('parseCss', () => {
it('parses comments', () => {
const ast = parseCss('/* comment */ div { color: red; }');
assert.equal(ast.children.length, 1);
assert.equal(ast.children[0].type, 'Rule');
assert.equal(ast.children.length, 2);
assert.equal(ast.children[0].type, 'CSSComment');
assert.equal(ast.children[1].type, 'Rule');
});
it('includes comments in the AST', () => {
const ast = parseCss(`/* top 1 *//* top 2 */
.foo, /* between */
.bar /* trailing */ {
/* block */
color: /* value */ red;
}
@media /* prelude */ screen {}`);
const [first, second, rule, atrule] = ast.children as any[];
assert.deepEqual(first, {
type: 'CSSComment',
start: 0,
end: 11,
data: ' top 1 '
});
assert.deepEqual(second, {
type: 'CSSComment',
start: 11,
end: 22,
data: ' top 2 '
});
assert.deepEqual(
rule.prelude.children.map((child: any) =>
child.type === 'CSSComment'
? { type: child.type, start: child.start, end: child.end, data: child.data }
: child.type
),
[
'ComplexSelector',
{ type: 'CSSComment', start: 29, end: 42, data: ' between ' },
'ComplexSelector',
{ type: 'CSSComment', start: 48, end: 62, data: ' trailing ' }
]
);
const [block_comment, declaration] = rule.block.children;
assert.deepEqual(block_comment, {
type: 'CSSComment',
start: 66,
end: 77,
data: ' block '
});
assert.equal(declaration.raw, '/* value */ red');
assert.equal(atrule.raw, '/* prelude */ screen');
});
it('parses complex selectors', () => {

@ -5,6 +5,12 @@
"end": 806,
"attributes": [],
"children": [
{
"type": "CSSComment",
"start": 12,
"end": 58,
"data": " test that all these are parsed correctly "
},
{
"type": "Rule",
"prelude": {

@ -5,6 +5,12 @@
"end": 386,
"attributes": [],
"children": [
{
"type": "CSSComment",
"start": 12,
"end": 58,
"data": " test that all these are parsed correctly "
},
{
"type": "Rule",
"prelude": {
@ -298,6 +304,12 @@
"start": 324,
"end": 355,
"children": [
{
"type": "CSSComment",
"start": 311,
"end": 321,
"data": "button"
},
{
"type": "ComplexSelector",
"start": 324,
@ -319,6 +331,12 @@
}
]
},
{
"type": "CSSComment",
"start": 332,
"end": 346,
"data": "p after h1"
},
{
"type": "ComplexSelector",
"start": 349,

@ -0,0 +1,16 @@
<style>
/* first */
/* second */
.foo, /* between */
.bar /* trailing */ {
/* block */
color: /* value */ red;
}
@media /* media */ screen {
/* nested */
.baz {
display: block;
}
}
</style>

@ -0,0 +1,18 @@
<style>
/* first */
/* second */
.foo, /* between */
.bar /* trailing */ {
/* block */
color: /* value */ red;
}
@media /* media */ screen {
/* nested */
.baz {
display: block;
}
}
</style>

@ -1693,7 +1693,7 @@ declare module 'svelte/compiler' {
}
export interface StyleSheetBase extends BaseNode {
children: Array<Atrule | Rule>;
children: Array<Atrule | Rule | CSSComment>;
}
export interface StyleSheetFile extends StyleSheetBase {
@ -1716,9 +1716,15 @@ declare module 'svelte/compiler' {
type: 'Atrule';
name: string;
prelude: string;
raw?: string;
block: Block | null;
}
export interface CSSComment extends BaseNode {
type: 'CSSComment';
data: string;
}
export interface Rule extends BaseNode {
type: 'Rule';
prelude: SelectorList;
@ -1733,7 +1739,7 @@ declare module 'svelte/compiler' {
/**
* The `a`, `b` and `c` in `a, b, c {}`
*/
children: ComplexSelector[];
children: Array<ComplexSelector | CSSComment>;
}
/**
@ -1829,18 +1835,20 @@ declare module 'svelte/compiler' {
export interface Block extends BaseNode {
type: 'Block';
children: Array<Declaration | Rule | Atrule>;
children: Array<Declaration | Rule | Atrule | CSSComment>;
}
export interface Declaration extends BaseNode {
type: 'Declaration';
property: string;
value: string;
raw?: string;
}
// for zimmerframe
export type Node =
| StyleSheet
| CSSComment
| Rule
| Atrule
| SelectorList

Loading…
Cancel
Save