Skip to content

Commit 7784231

Browse files
committedSep 4, 2024·
Add support for loud comments
1 parent 21819c3 commit 7784231

13 files changed

+588
-9
lines changed
 

‎lib/src/parse/sass.dart

-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,6 @@ class SassParser extends StylesheetParser {
268268

269269
_readIndentation();
270270
}
271-
if (!buffer.trailingString.trimRight().endsWith("*/")) buffer.write(" */");
272271

273272
return LoudComment(buffer.interpolation(scanner.spanFrom(start)));
274273
}

‎lib/src/visitor/async_evaluate.dart

+4-2
Original file line numberDiff line numberDiff line change
@@ -1911,8 +1911,10 @@ final class _EvaluateVisitor
19111911
_endOfImports++;
19121912
}
19131913

1914-
_parent.addChild(ModifiableCssComment(
1915-
await _performInterpolation(node.text), node.span));
1914+
var text = await _performInterpolation(node.text);
1915+
// Indented syntax doesn't require */
1916+
if (!text.endsWith("*/")) text += " */";
1917+
_parent.addChild(ModifiableCssComment(text, node.span));
19161918
return null;
19171919
}
19181920

‎lib/src/visitor/evaluate.dart

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// DO NOT EDIT. This file was generated from async_evaluate.dart.
66
// See tool/grind/synchronize.dart for details.
77
//
8-
// Checksum: ebf292c26dcfdd7f61fd70ce3dc9e0be2b6708b3
8+
// Checksum: 2ab69d23a3b34cb54ddd74e2e854614dda582174
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1903,8 +1903,10 @@ final class _EvaluateVisitor
19031903
_endOfImports++;
19041904
}
19051905

1906-
_parent.addChild(
1907-
ModifiableCssComment(_performInterpolation(node.text), node.span));
1906+
var text = _performInterpolation(node.text);
1907+
// Indented syntax doesn't require */
1908+
if (!text.endsWith("*/")) text += " */";
1909+
_parent.addChild(ModifiableCssComment(text, node.span));
19081910
return null;
19091911
}
19101912

‎pkg/sass-parser/jest.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const config = {
33
roots: ['lib'],
44
testEnvironment: 'node',
55
setupFilesAfterEnv: ['jest-extended/all', '<rootDir>/test/setup.ts'],
6+
verbose: false,
67
};
78

89
export default config;

‎pkg/sass-parser/lib/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export {
3333
InterpolationRaws,
3434
NewNodeForInterpolation,
3535
} from './src/interpolation';
36+
export {
37+
CssComment,
38+
CssCommentProps,
39+
CssCommentRaws,
40+
} from './src/statement/css-comment';
3641
export {
3742
DebugRule,
3843
DebugRuleProps,

‎pkg/sass-parser/lib/src/sass-internal.ts

+6
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ declare namespace SassInternal {
101101
readonly isExclusive: boolean;
102102
}
103103

104+
class LoudComment extends Statement {
105+
readonly text: Interpolation;
106+
}
107+
104108
class Stylesheet extends ParentStatement<Statement[]> {}
105109

106110
class StyleRule extends ParentStatement<Statement[]> {
@@ -143,6 +147,7 @@ export type EachRule = SassInternal.EachRule;
143147
export type ErrorRule = SassInternal.ErrorRule;
144148
export type ExtendRule = SassInternal.ExtendRule;
145149
export type ForRule = SassInternal.ForRule;
150+
export type LoudComment = SassInternal.LoudComment;
146151
export type Stylesheet = SassInternal.Stylesheet;
147152
export type StyleRule = SassInternal.StyleRule;
148153
export type Interpolation = SassInternal.Interpolation;
@@ -158,6 +163,7 @@ export interface StatementVisitorObject<T> {
158163
visitErrorRule(node: ErrorRule): T;
159164
visitExtendRule(node: ExtendRule): T;
160165
visitForRule(node: ForRule): T;
166+
visitLoudComment(node: LoudComment): T;
161167
visitStyleRule(node: StyleRule): T;
162168
}
163169

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`a CSS-style comment toJSON 1`] = `
4+
{
5+
"inputs": [
6+
{
7+
"css": "/* foo */",
8+
"hasBOM": false,
9+
"id": "<input css _____>",
10+
},
11+
],
12+
"raws": {
13+
"closed": true,
14+
"left": " ",
15+
"right": " ",
16+
},
17+
"sassType": "comment",
18+
"source": <1:1-1:10 in 0>,
19+
"text": "foo",
20+
"textInterpolation": <foo>,
21+
"type": "comment",
22+
}
23+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import * as postcss from 'postcss';
6+
7+
import {Root} from './root';
8+
import {ChildNode, NewNode} from '.';
9+
10+
/**
11+
* A fake intermediate class to convince TypeScript to use Sass types for
12+
* various upstream methods.
13+
*
14+
* @hidden
15+
*/
16+
export class _Comment<Props> extends postcss.Comment {
17+
// Override the PostCSS types to constrain them to Sass types only.
18+
// Unfortunately, there's no way to abstract this out, because anything
19+
// mixin-like returns an intersection type which doesn't actually override
20+
// parent methods. See microsoft/TypeScript#59394.
21+
22+
after(newNode: NewNode): this;
23+
assign(overrides: Partial<Props>): this;
24+
before(newNode: NewNode): this;
25+
cloneAfter(overrides?: Partial<Props>): this;
26+
cloneBefore(overrides?: Partial<Props>): this;
27+
next(): ChildNode | undefined;
28+
prev(): ChildNode | undefined;
29+
replaceWith(...nodes: NewNode[]): this;
30+
root(): Root;
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
exports._Comment = require('postcss').Comment;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import {CssComment, Interpolation, Root, css, sass, scss} from '../..';
6+
import * as utils from '../../../test/utils';
7+
8+
describe('a CSS-style comment', () => {
9+
let node: CssComment;
10+
function describeNode(description: string, create: () => CssComment): void {
11+
describe(description, () => {
12+
beforeEach(() => void (node = create()));
13+
14+
it('has type comment', () => expect(node.type).toBe('comment'));
15+
16+
it('has sassType comment', () => expect(node.sassType).toBe('comment'));
17+
18+
it('has matching textInterpolation', () =>
19+
expect(node).toHaveInterpolation('textInterpolation', 'foo'));
20+
21+
it('has matching text', () => expect(node.text).toBe('foo'));
22+
});
23+
}
24+
25+
describeNode(
26+
'parsed as SCSS',
27+
() => scss.parse('/* foo */').nodes[0] as CssComment
28+
);
29+
30+
describeNode(
31+
'parsed as CSS',
32+
() => css.parse('/* foo */').nodes[0] as CssComment
33+
);
34+
35+
describeNode(
36+
'parsed as Sass',
37+
() => sass.parse('/* foo').nodes[0] as CssComment
38+
);
39+
40+
describe('constructed manually', () => {
41+
describeNode(
42+
'with an interpolation',
43+
() =>
44+
new CssComment({
45+
textInterpolation: new Interpolation({nodes: ['foo']}),
46+
})
47+
);
48+
49+
describeNode('with a text string', () => new CssComment({text: 'foo'}));
50+
});
51+
52+
describe('constructed from ChildProps', () => {
53+
describeNode('with an interpolation', () =>
54+
utils.fromChildProps({
55+
textInterpolation: new Interpolation({nodes: ['foo']}),
56+
})
57+
);
58+
59+
describeNode('with a text string', () =>
60+
utils.fromChildProps({text: 'foo'})
61+
);
62+
});
63+
64+
describe('parses raws', () => {
65+
describe('in SCSS', () => {
66+
it('with whitespace before and after text', () =>
67+
expect((scss.parse('/* foo */').nodes[0] as CssComment).raws).toEqual({
68+
left: ' ',
69+
right: ' ',
70+
closed: true,
71+
}));
72+
73+
it('with whitespace before and after interpolation', () =>
74+
expect(
75+
(scss.parse('/* #{foo} */').nodes[0] as CssComment).raws
76+
).toEqual({left: ' ', right: ' ', closed: true}));
77+
78+
it('without whitespace before and after text', () =>
79+
expect((scss.parse('/*foo*/').nodes[0] as CssComment).raws).toEqual({
80+
left: '',
81+
right: '',
82+
closed: true,
83+
}));
84+
85+
it('without whitespace before and after interpolation', () =>
86+
expect((scss.parse('/*#{foo}*/').nodes[0] as CssComment).raws).toEqual({
87+
left: '',
88+
right: '',
89+
closed: true,
90+
}));
91+
92+
it('with whitespace and no text', () =>
93+
expect((scss.parse('/* */').nodes[0] as CssComment).raws).toEqual({
94+
left: ' ',
95+
right: '',
96+
closed: true,
97+
}));
98+
99+
it('with no whitespace and no text', () =>
100+
expect((scss.parse('/**/').nodes[0] as CssComment).raws).toEqual({
101+
left: '',
102+
right: '',
103+
closed: true,
104+
}));
105+
});
106+
107+
describe('in Sass', () => {
108+
// TODO: Test explicit whitespace after text and interpolation once we
109+
// properly parse raws from somewhere other than the original text.
110+
111+
it('with whitespace before text', () =>
112+
expect((sass.parse('/* foo').nodes[0] as CssComment).raws).toEqual({
113+
left: ' ',
114+
right: '',
115+
closed: false,
116+
}));
117+
118+
it('with whitespace before interpolation', () =>
119+
expect((sass.parse('/* #{foo}').nodes[0] as CssComment).raws).toEqual({
120+
left: ' ',
121+
right: '',
122+
closed: false,
123+
}));
124+
125+
it('without whitespace before and after text', () =>
126+
expect((sass.parse('/*foo').nodes[0] as CssComment).raws).toEqual({
127+
left: '',
128+
right: '',
129+
closed: false,
130+
}));
131+
132+
it('without whitespace before and after interpolation', () =>
133+
expect((sass.parse('/*#{foo}').nodes[0] as CssComment).raws).toEqual({
134+
left: '',
135+
right: '',
136+
closed: false,
137+
}));
138+
139+
it('with no whitespace and no text', () =>
140+
expect((sass.parse('/*').nodes[0] as CssComment).raws).toEqual({
141+
left: '',
142+
right: '',
143+
closed: false,
144+
}));
145+
146+
it('with a trailing */', () =>
147+
expect((sass.parse('/* foo */').nodes[0] as CssComment).raws).toEqual({
148+
left: ' ',
149+
right: ' ',
150+
closed: true,
151+
}));
152+
});
153+
});
154+
155+
describe('stringifies', () => {
156+
describe('to SCSS', () => {
157+
it('with default raws', () =>
158+
expect(new CssComment({text: 'foo'}).toString()).toBe('/* foo */'));
159+
160+
it('with left', () =>
161+
expect(
162+
new CssComment({
163+
text: 'foo',
164+
raws: {left: '\n'},
165+
}).toString()
166+
).toBe('/*\nfoo */'));
167+
168+
it('with right', () =>
169+
expect(
170+
new CssComment({
171+
text: 'foo',
172+
raws: {right: '\n'},
173+
}).toString()
174+
).toBe('/* foo\n*/'));
175+
176+
it('with before', () =>
177+
expect(
178+
new Root({
179+
nodes: [new CssComment({text: 'foo', raws: {before: '/**/'}})],
180+
}).toString()
181+
).toBe('/**//* foo */'));
182+
});
183+
});
184+
185+
describe('assigned new text', () => {
186+
beforeEach(() => {
187+
node = scss.parse('/* foo */').nodes[0] as CssComment;
188+
});
189+
190+
it("removes the old text's parent", () => {
191+
const oldText = node.textInterpolation!;
192+
node.textInterpolation = 'bar';
193+
expect(oldText.parent).toBeUndefined();
194+
});
195+
196+
it("assigns the new interpolation's parent", () => {
197+
const interpolation = new Interpolation({nodes: ['bar']});
198+
node.textInterpolation = interpolation;
199+
expect(interpolation.parent).toBe(node);
200+
});
201+
202+
it('assigns the interpolation explicitly', () => {
203+
const interpolation = new Interpolation({nodes: ['bar']});
204+
node.textInterpolation = interpolation;
205+
expect(node.textInterpolation).toBe(interpolation);
206+
});
207+
208+
it('assigns the interpolation as a string', () => {
209+
node.textInterpolation = 'bar';
210+
expect(node).toHaveInterpolation('textInterpolation', 'bar');
211+
});
212+
213+
it('assigns the interpolation as text', () => {
214+
node.text = 'bar';
215+
expect(node).toHaveInterpolation('textInterpolation', 'bar');
216+
});
217+
});
218+
219+
describe('clone', () => {
220+
let original: CssComment;
221+
beforeEach(
222+
() => void (original = scss.parse('/* foo */').nodes[0] as CssComment)
223+
);
224+
225+
describe('with no overrides', () => {
226+
let clone: CssComment;
227+
beforeEach(() => {
228+
clone = original.clone();
229+
});
230+
231+
describe('has the same properties:', () => {
232+
it('textInterpolation', () =>
233+
expect(clone).toHaveInterpolation('textInterpolation', 'foo'));
234+
235+
it('text', () => expect(clone.text).toBe('foo'));
236+
237+
it('raws', () =>
238+
expect(clone.raws).toEqual({left: ' ', right: ' ', closed: true}));
239+
240+
it('source', () => expect(clone.source).toBe(original.source));
241+
});
242+
243+
describe('creates a new', () => {
244+
it('self', () => expect(clone).not.toBe(original));
245+
246+
for (const attr of ['textInterpolation', 'raws'] as const) {
247+
it(attr, () => expect(clone[attr]).not.toBe(original[attr]));
248+
}
249+
});
250+
});
251+
252+
describe('overrides', () => {
253+
describe('text', () => {
254+
describe('defined', () => {
255+
let clone: CssComment;
256+
beforeEach(() => {
257+
clone = original.clone({text: 'bar'});
258+
});
259+
260+
it('changes text', () => expect(clone.text).toBe('bar'));
261+
262+
it('changes textInterpolation', () =>
263+
expect(clone).toHaveInterpolation('textInterpolation', 'bar'));
264+
});
265+
266+
describe('undefined', () => {
267+
let clone: CssComment;
268+
beforeEach(() => {
269+
clone = original.clone({text: undefined});
270+
});
271+
272+
it('preserves text', () => expect(clone.text).toBe('foo'));
273+
274+
it('preserves textInterpolation', () =>
275+
expect(clone).toHaveInterpolation('textInterpolation', 'foo'));
276+
});
277+
});
278+
279+
describe('textInterpolation', () => {
280+
describe('defined', () => {
281+
let clone: CssComment;
282+
beforeEach(() => {
283+
clone = original.clone({
284+
textInterpolation: new Interpolation({nodes: ['baz']}),
285+
});
286+
});
287+
288+
it('changes text', () => expect(clone.text).toBe('baz'));
289+
290+
it('changes textInterpolation', () =>
291+
expect(clone).toHaveInterpolation('textInterpolation', 'baz'));
292+
});
293+
294+
describe('undefined', () => {
295+
let clone: CssComment;
296+
beforeEach(() => {
297+
clone = original.clone({textInterpolation: undefined});
298+
});
299+
300+
it('preserves text', () => expect(clone.text).toBe('foo'));
301+
302+
it('preserves textInterpolation', () =>
303+
expect(clone).toHaveInterpolation('textInterpolation', 'foo'));
304+
});
305+
});
306+
307+
describe('raws', () => {
308+
it('defined', () =>
309+
expect(original.clone({raws: {right: ' '}}).raws).toEqual({
310+
right: ' ',
311+
}));
312+
313+
it('undefined', () =>
314+
expect(original.clone({raws: undefined}).raws).toEqual({
315+
left: ' ',
316+
right: ' ',
317+
closed: true,
318+
}));
319+
});
320+
});
321+
});
322+
323+
it('toJSON', () =>
324+
expect(scss.parse('/* foo */').nodes[0]).toMatchSnapshot());
325+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright 2024 Google Inc. Use of this source code is governed by an
2+
// MIT-style license that can be found in the LICENSE file or at
3+
// https://opensource.org/licenses/MIT.
4+
5+
import * as postcss from 'postcss';
6+
import type {CommentRaws} from 'postcss/lib/comment';
7+
8+
import {convertExpression} from '../expression/convert';
9+
import {LazySource} from '../lazy-source';
10+
import type * as sassInternal from '../sass-internal';
11+
import {Interpolation} from '../interpolation';
12+
import * as utils from '../utils';
13+
import {ContainerProps, Statement, StatementWithChildren} from '.';
14+
import {_Comment} from './comment-internal';
15+
import {interceptIsClean} from './intercept-is-clean';
16+
import * as sassParser from '../..';
17+
18+
/**
19+
* The set of raws supported by {@link CssComment}.
20+
*
21+
* @category Statement
22+
*/
23+
export interface CssCommentRaws extends CommentRaws {
24+
/**
25+
* In the indented syntax, this indicates whether a comment is explicitly
26+
* closed with a `*\/`. It's ignored in other syntaxes.
27+
*
28+
* It defaults to false.
29+
*/
30+
closed?: boolean;
31+
}
32+
33+
/**
34+
* The initializer properties for {@link CssComment}.
35+
*
36+
* @category Statement
37+
*/
38+
export type CssCommentProps = ContainerProps & {
39+
raws?: CssCommentRaws;
40+
} & ({text: string} | {textInterpolation: Interpolation | string});
41+
42+
/**
43+
* A CSS-style "loud" comment. Extends [`postcss.Comment`].
44+
*
45+
* [`postcss.Comment`]: https://postcss.org/api/#comment
46+
*
47+
* @category Statement
48+
*/
49+
export class CssComment
50+
extends _Comment<Partial<CssCommentProps>>
51+
implements Statement
52+
{
53+
readonly sassType = 'comment' as const;
54+
declare parent: StatementWithChildren | undefined;
55+
declare raws: CssCommentRaws;
56+
57+
get text(): string {
58+
return this.textInterpolation.toString();
59+
}
60+
set text(value: string) {
61+
this.textInterpolation = value;
62+
}
63+
64+
/** The interpolation that represents this selector's contents. */
65+
get textInterpolation(): Interpolation {
66+
return this._textInterpolation!;
67+
}
68+
set textInterpolation(textInterpolation: Interpolation | string) {
69+
// TODO - postcss/postcss#1957: Mark this as dirty
70+
if (this._textInterpolation) {
71+
this._textInterpolation.parent = undefined;
72+
}
73+
if (typeof textInterpolation === 'string') {
74+
textInterpolation = new Interpolation({
75+
nodes: [textInterpolation],
76+
});
77+
}
78+
textInterpolation.parent = this;
79+
this._textInterpolation = textInterpolation;
80+
}
81+
private _textInterpolation?: Interpolation;
82+
83+
constructor(defaults: CssCommentProps);
84+
/** @hidden */
85+
constructor(_: undefined, inner: sassInternal.LoudComment);
86+
constructor(defaults?: CssCommentProps, inner?: sassInternal.LoudComment) {
87+
super(defaults as unknown as postcss.CommentProps);
88+
89+
if (inner) {
90+
this.source = new LazySource(inner);
91+
const nodes = [...inner.text.contents];
92+
93+
// The interpolation's contents are guaranteed to begin with a string,
94+
// because Sass includes the `/*`.
95+
let first = nodes[0] as string;
96+
const firstMatch = first.match(/^\/\*([ \t\n\r\f]*)/)!;
97+
this.raws.left ??= firstMatch[1];
98+
first = first.substring(firstMatch[0].length);
99+
if (first.length === 0) {
100+
nodes.shift();
101+
} else {
102+
nodes[0] = first;
103+
}
104+
105+
// The interpolation will end with `*/` in SCSS, but not necessarily in
106+
// the indented syntax.
107+
let last = nodes.at(-1);
108+
if (typeof last === 'string') {
109+
const lastMatch = last.match(/([ \t\n\r\f]*)\*\/$/);
110+
this.raws.right ??= lastMatch?.[1] ?? '';
111+
this.raws.closed = !!lastMatch;
112+
if (lastMatch) {
113+
last = last.substring(0, last.length - lastMatch[0].length);
114+
if (last.length === 0) {
115+
nodes.pop();
116+
} else {
117+
nodes[0] = last;
118+
}
119+
}
120+
} else {
121+
this.raws.right ??= '';
122+
this.raws.closed = false;
123+
}
124+
125+
this.textInterpolation = new Interpolation();
126+
for (const child of nodes) {
127+
this.textInterpolation.append(
128+
typeof child === 'string' ? child : convertExpression(child)
129+
);
130+
}
131+
}
132+
}
133+
134+
clone(overrides?: Partial<CssCommentProps>): this {
135+
return utils.cloneNode(
136+
this,
137+
overrides,
138+
['raws', 'textInterpolation'],
139+
['text']
140+
);
141+
}
142+
143+
toJSON(): object;
144+
/** @hidden */
145+
toJSON(_: string, inputs: Map<postcss.Input, number>): object;
146+
toJSON(_?: string, inputs?: Map<postcss.Input, number>): object {
147+
return utils.toJSON(this, ['text', 'textInterpolation'], inputs);
148+
}
149+
150+
/** @hidden */
151+
toString(
152+
stringifier: postcss.Stringifier | postcss.Syntax = sassParser.scss
153+
.stringify
154+
): string {
155+
return super.toString(stringifier);
156+
}
157+
158+
/** @hidden */
159+
get nonStatementChildren(): ReadonlyArray<Interpolation> {
160+
return [this.textInterpolation];
161+
}
162+
}
163+
164+
interceptIsClean(CssComment);

‎pkg/sass-parser/lib/src/statement/index.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {Interpolation} from '../interpolation';
88
import {LazySource} from '../lazy-source';
99
import {Node, NodeProps} from '../node';
1010
import * as sassInternal from '../sass-internal';
11+
import {CssComment, CssCommentProps} from './css-comment';
1112
import {GenericAtRule, GenericAtRuleProps} from './generic-at-rule';
1213
import {DebugRule, DebugRuleProps} from './debug-rule';
1314
import {EachRule, EachRuleProps} from './each-rule';
@@ -18,14 +19,14 @@ import {Rule, RuleProps} from './rule';
1819

1920
// TODO: Replace this with the corresponding Sass types once they're
2021
// implemented.
21-
export {Comment, Declaration} from 'postcss';
22+
export {Declaration} from 'postcss';
2223

2324
/**
2425
* The union type of all Sass statements.
2526
*
2627
* @category Statement
2728
*/
28-
export type AnyStatement = Root | Rule | GenericAtRule;
29+
export type AnyStatement = Comment | Root | Rule | GenericAtRule;
2930

3031
/**
3132
* Sass statement types.
@@ -40,6 +41,7 @@ export type StatementType =
4041
| 'root'
4142
| 'rule'
4243
| 'atrule'
44+
| 'comment'
4345
| 'debug-rule'
4446
| 'each-rule'
4547
| 'for-rule'
@@ -52,14 +54,21 @@ export type StatementType =
5254
*/
5355
export type AtRule = DebugRule | EachRule | ErrorRule | ForRule | GenericAtRule;
5456

57+
/**
58+
* All Sass statements that are comments.
59+
*
60+
* @category Statement
61+
*/
62+
export type Comment = CssComment;
63+
5564
/**
5665
* All Sass statements that are valid children of other statements.
5766
*
5867
* The Sass equivalent of PostCSS's `ChildNode`.
5968
*
6069
* @category Statement
6170
*/
62-
export type ChildNode = Rule | AtRule;
71+
export type ChildNode = Rule | AtRule | Comment;
6372

6473
/**
6574
* The properties that can be used to construct {@link ChildNode}s.
@@ -70,6 +79,7 @@ export type ChildNode = Rule | AtRule;
7079
*/
7180
export type ChildProps =
7281
| postcss.ChildProps
82+
| CssCommentProps
7383
| DebugRuleProps
7484
| EachRuleProps
7585
| ErrorRuleProps
@@ -138,6 +148,7 @@ const visitor = sassInternal.createStatementVisitor<Statement>({
138148
source: new LazySource(inner),
139149
});
140150
},
151+
visitLoudComment: inner => new CssComment(undefined, inner),
141152
visitStyleRule: inner => new Rule(undefined, inner),
142153
});
143154

@@ -249,6 +260,8 @@ export function normalize(
249260
result.push(new ForRule(node));
250261
} else if ('errorExpression' in node) {
251262
result.push(new ErrorRule(node));
263+
} else if ('text' in node || 'textInterpolation' in node) {
264+
result.push(new CssComment(node as CssCommentProps));
252265
} else {
253266
result.push(...postcssNormalizeAndConvertToSass(self, node, sample));
254267
}

‎pkg/sass_api/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
* Remove the `CallableDeclaration()` constructor.
44

5+
* Loud comments in the Sass syntax no longer automatically inject ` */` to the
6+
end when parsed.
7+
58
## 10.4.8
69

710
* No user-visible changes.

0 commit comments

Comments
 (0)
Please sign in to comment.