Skip to content

Commit fd5cff0

Browse files
committed
Add sass-parser support for the @warn rule
1 parent 251c757 commit fd5cff0

File tree

8 files changed

+378
-3
lines changed

8 files changed

+378
-3
lines changed

pkg/sass-parser/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
* Add support for parsing variable declarations.
44

5+
* Add support for parsing the `@warn` rule.
6+
57
## 0.4.1
68

79
* Add `BooleanExpression` and `NumberExpression`.

pkg/sass-parser/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export {
101101
VariableDeclarationProps,
102102
VariableDeclarationRaws,
103103
} from './src/statement/variable-declaration';
104+
export {WarnRule, WarnRuleProps, WarnRuleRaws} from './src/statement/warn-rule';
104105

105106
/** Options that can be passed to the Sass parsers to control their behavior. */
106107
export type SassParserOptions = Pick<postcss.ProcessOptions, 'from' | 'map'>;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,10 @@ declare namespace SassInternal {
188188
readonly isGlobal: boolean;
189189
}
190190

191+
class WarnRule extends Statement {
192+
readonly expression: Expression;
193+
}
194+
191195
class ConfiguredVariable extends SassNode {
192196
readonly name: string;
193197
readonly expression: Expression;
@@ -247,6 +251,7 @@ export type StyleRule = SassInternal.StyleRule;
247251
export type SupportsRule = SassInternal.SupportsRule;
248252
export type UseRule = SassInternal.UseRule;
249253
export type VariableDeclaration = SassInternal.VariableDeclaration;
254+
export type WarnRule = SassInternal.WarnRule;
250255
export type ConfiguredVariable = SassInternal.ConfiguredVariable;
251256
export type Interpolation = SassInternal.Interpolation;
252257
export type Expression = SassInternal.Expression;
@@ -270,6 +275,7 @@ export interface StatementVisitorObject<T> {
270275
visitSupportsRule(node: SupportsRule): T;
271276
visitUseRule(node: UseRule): T;
272277
visitVariableDeclaration(node: VariableDeclaration): T;
278+
visitWarnRule(node: WarnRule): T;
273279
}
274280

275281
export interface ExpressionVisitorObject<T> {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`a @warn rule toJSON 1`] = `
4+
{
5+
"inputs": [
6+
{
7+
"css": "@warn foo",
8+
"hasBOM": false,
9+
"id": "<input css _____>",
10+
},
11+
],
12+
"name": "warn",
13+
"params": "foo",
14+
"raws": {},
15+
"sassType": "warn-rule",
16+
"source": <1:1-1:10 in 0>,
17+
"type": "atrule",
18+
"warnExpression": <foo>,
19+
}
20+
`;

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
VariableDeclaration,
2323
VariableDeclarationProps,
2424
} from './variable-declaration';
25+
import {WarnRule, WarnRuleProps} from './warn-rule';
2526

2627
// TODO: Replace this with the corresponding Sass types once they're
2728
// implemented.
@@ -54,7 +55,8 @@ export type StatementType =
5455
| 'error-rule'
5556
| 'use-rule'
5657
| 'sass-comment'
57-
| 'variable-declaration';
58+
| 'variable-declaration'
59+
| 'warn-rule';
5860

5961
/**
6062
* All Sass statements that are also at-rules.
@@ -67,7 +69,8 @@ export type AtRule =
6769
| ErrorRule
6870
| ForRule
6971
| GenericAtRule
70-
| UseRule;
72+
| UseRule
73+
| WarnRule;
7174

7275
/**
7376
* All Sass statements that are comments.
@@ -103,7 +106,8 @@ export type ChildProps =
103106
| RuleProps
104107
| SassCommentChildProps
105108
| UseRuleProps
106-
| VariableDeclarationProps;
109+
| VariableDeclarationProps
110+
| WarnRuleProps;
107111

108112
/**
109113
* The Sass eqivalent of PostCSS's `ContainerProps`.
@@ -192,6 +196,7 @@ const visitor = sassInternal.createStatementVisitor<Statement>({
192196
},
193197
visitUseRule: inner => new UseRule(undefined, inner),
194198
visitVariableDeclaration: inner => new VariableDeclaration(undefined, inner),
199+
visitWarnRule: inner => new WarnRule(undefined, inner),
195200
});
196201

197202
/** Appends parsed versions of `internal`'s children to `container`. */
@@ -310,6 +315,8 @@ export function normalize(
310315
result.push(new UseRule(node));
311316
} else if ('variableName' in node) {
312317
result.push(new VariableDeclaration(node));
318+
} else if ('warnExpression' in node) {
319+
result.push(new WarnRule(node));
313320
} else {
314321
result.push(...postcssNormalizeAndConvertToSass(self, node, sample));
315322
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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 {StringExpression, WarnRule, sass, scss} from '../..';
6+
import * as utils from '../../../test/utils';
7+
8+
describe('a @warn rule', () => {
9+
let node: WarnRule;
10+
function describeNode(description: string, create: () => WarnRule): void {
11+
describe(description, () => {
12+
beforeEach(() => void (node = create()));
13+
14+
it('has a name', () => expect(node.name.toString()).toBe('warn'));
15+
16+
it('has an expression', () =>
17+
expect(node).toHaveStringExpression('warnExpression', 'foo'));
18+
19+
it('has matching params', () => expect(node.params).toBe('foo'));
20+
21+
it('has undefined nodes', () => expect(node.nodes).toBeUndefined());
22+
});
23+
}
24+
25+
describeNode(
26+
'parsed as SCSS',
27+
() => scss.parse('@warn foo').nodes[0] as WarnRule
28+
);
29+
30+
describeNode(
31+
'parsed as Sass',
32+
() => sass.parse('@warn foo').nodes[0] as WarnRule
33+
);
34+
35+
describeNode(
36+
'constructed manually',
37+
() =>
38+
new WarnRule({
39+
warnExpression: {text: 'foo'},
40+
})
41+
);
42+
43+
describeNode('constructed from ChildProps', () =>
44+
utils.fromChildProps({
45+
warnExpression: {text: 'foo'},
46+
})
47+
);
48+
49+
it('throws an error when assigned a new name', () =>
50+
expect(
51+
() =>
52+
(new WarnRule({
53+
warnExpression: {text: 'foo'},
54+
}).name = 'bar')
55+
).toThrow());
56+
57+
describe('assigned a new expression', () => {
58+
beforeEach(() => {
59+
node = scss.parse('@warn foo').nodes[0] as WarnRule;
60+
});
61+
62+
it('sets an empty string expression as undefined params', () => {
63+
node.params = undefined;
64+
expect(node.params).toBe('');
65+
expect(node).toHaveStringExpression('warnExpression', '');
66+
});
67+
68+
it('sets an empty string expression as empty string params', () => {
69+
node.params = '';
70+
expect(node.params).toBe('');
71+
expect(node).toHaveStringExpression('warnExpression', '');
72+
});
73+
74+
it("removes the old expression's parent", () => {
75+
const oldExpression = node.warnExpression;
76+
node.warnExpression = {text: 'bar'};
77+
expect(oldExpression.parent).toBeUndefined();
78+
});
79+
80+
it("assigns the new expression's parent", () => {
81+
const expression = new StringExpression({text: 'bar'});
82+
node.warnExpression = expression;
83+
expect(expression.parent).toBe(node);
84+
});
85+
86+
it('assigns the expression explicitly', () => {
87+
const expression = new StringExpression({text: 'bar'});
88+
node.warnExpression = expression;
89+
expect(node.warnExpression).toBe(expression);
90+
});
91+
92+
it('assigns the expression as ExpressionProps', () => {
93+
node.warnExpression = {text: 'bar'};
94+
expect(node).toHaveStringExpression('warnExpression', 'bar');
95+
});
96+
97+
it('assigns the expression as params', () => {
98+
node.params = 'bar';
99+
expect(node).toHaveStringExpression('warnExpression', 'bar');
100+
});
101+
});
102+
103+
describe('stringifies', () => {
104+
describe('to SCSS', () => {
105+
it('with default raws', () =>
106+
expect(
107+
new WarnRule({
108+
warnExpression: {text: 'foo'},
109+
}).toString()
110+
).toBe('@warn foo;'));
111+
112+
it('with afterName', () =>
113+
expect(
114+
new WarnRule({
115+
warnExpression: {text: 'foo'},
116+
raws: {afterName: '/**/'},
117+
}).toString()
118+
).toBe('@warn/**/foo;'));
119+
120+
it('with between', () =>
121+
expect(
122+
new WarnRule({
123+
warnExpression: {text: 'foo'},
124+
raws: {between: '/**/'},
125+
}).toString()
126+
).toBe('@warn foo/**/;'));
127+
});
128+
});
129+
130+
describe('clone', () => {
131+
let original: WarnRule;
132+
beforeEach(() => {
133+
original = scss.parse('@warn foo').nodes[0] as WarnRule;
134+
// TODO: remove this once raws are properly parsed
135+
original.raws.between = ' ';
136+
});
137+
138+
describe('with no overrides', () => {
139+
let clone: WarnRule;
140+
beforeEach(() => void (clone = original.clone()));
141+
142+
describe('has the same properties:', () => {
143+
it('params', () => expect(clone.params).toBe('foo'));
144+
145+
it('warnExpression', () =>
146+
expect(clone).toHaveStringExpression('warnExpression', 'foo'));
147+
148+
it('raws', () => expect(clone.raws).toEqual({between: ' '}));
149+
150+
it('source', () => expect(clone.source).toBe(original.source));
151+
});
152+
153+
describe('creates a new', () => {
154+
it('self', () => expect(clone).not.toBe(original));
155+
156+
for (const attr of ['warnExpression', 'raws'] as const) {
157+
it(attr, () => expect(clone[attr]).not.toBe(original[attr]));
158+
}
159+
});
160+
});
161+
162+
describe('overrides', () => {
163+
describe('raws', () => {
164+
it('defined', () =>
165+
expect(original.clone({raws: {afterName: ' '}}).raws).toEqual({
166+
afterName: ' ',
167+
}));
168+
169+
it('undefined', () =>
170+
expect(original.clone({raws: undefined}).raws).toEqual({
171+
between: ' ',
172+
}));
173+
});
174+
175+
describe('warnExpression', () => {
176+
describe('defined', () => {
177+
let clone: WarnRule;
178+
beforeEach(() => {
179+
clone = original.clone({warnExpression: {text: 'bar'}});
180+
});
181+
182+
it('changes params', () => expect(clone.params).toBe('bar'));
183+
184+
it('changes warnExpression', () =>
185+
expect(clone).toHaveStringExpression('warnExpression', 'bar'));
186+
});
187+
188+
describe('undefined', () => {
189+
let clone: WarnRule;
190+
beforeEach(() => {
191+
clone = original.clone({warnExpression: undefined});
192+
});
193+
194+
it('preserves params', () => expect(clone.params).toBe('foo'));
195+
196+
it('preserves warnExpression', () =>
197+
expect(clone).toHaveStringExpression('warnExpression', 'foo'));
198+
});
199+
});
200+
});
201+
});
202+
203+
it('toJSON', () =>
204+
expect(scss.parse('@warn foo').nodes[0]).toMatchSnapshot());
205+
});

0 commit comments

Comments
 (0)