Skip to content

Commit 251c757

Browse files
committed
Add sass-parser support for variable declarations
1 parent 20397ef commit 251c757

18 files changed

+1025
-21
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.80.5-dev
2+
3+
* No user-visible changes.
4+
15
## 1.80.4
26

37
* No user-visible changes.

lib/src/ast/sass/statement/stylesheet.dart

+9-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ final class Stylesheet extends ParentStatement<List<Statement>> {
5353
@internal
5454
final List<ParseTimeWarning> parseTimeWarnings;
5555

56+
/// The set of (normalized) global variable names defined by this stylesheet
57+
/// to the spans where they're defined.
58+
@internal
59+
final Map<String, FileSpan> globalVariables;
60+
5661
Stylesheet(Iterable<Statement> children, FileSpan span)
5762
: this.internal(children, span, []);
5863

@@ -62,8 +67,11 @@ final class Stylesheet extends ParentStatement<List<Statement>> {
6267
@internal
6368
Stylesheet.internal(Iterable<Statement> children, this.span,
6469
List<ParseTimeWarning> parseTimeWarnings,
65-
{this.plainCss = false})
70+
{this.plainCss = false, Map<String, FileSpan>? globalVariables})
6671
: parseTimeWarnings = UnmodifiableListView(parseTimeWarnings),
72+
globalVariables = globalVariables == null
73+
? const {}
74+
: Map.unmodifiable(globalVariables),
6775
super(List.unmodifiable(children)) {
6876
loop:
6977
for (var child in this.children) {

lib/src/parse/stylesheet.dart

+4-11
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ abstract class StylesheetParser extends Parser {
6161
var _inExpression = false;
6262

6363
/// A map from all variable names that are assigned with `!global` in the
64-
/// current stylesheet to the nodes where they're defined.
64+
/// current stylesheet to the spans where they're defined.
6565
///
6666
/// These are collected at parse time because they affect the variables
6767
/// exposed by the module generated for this stylesheet, *even if they aren't
6868
/// evaluated*. This allows us to ensure that the stylesheet always exposes
6969
/// the same set of variable names no matter how it's evaluated.
70-
final _globalVariables = <String, VariableDeclaration>{};
70+
final _globalVariables = <String, FileSpan>{};
7171

7272
/// Warnings discovered while parsing that should be emitted during
7373
/// evaluation once a proper logger is available.
@@ -100,15 +100,8 @@ abstract class StylesheetParser extends Parser {
100100
});
101101
scanner.expectDone();
102102

103-
/// Ensure that all global variable assignments produce a variable in this
104-
/// stylesheet, even if they aren't evaluated. See sass/language#50.
105-
statements.addAll(_globalVariables.values.map((declaration) =>
106-
VariableDeclaration(declaration.name,
107-
NullExpression(declaration.expression.span), declaration.span,
108-
guarded: true)));
109-
110103
return Stylesheet.internal(statements, scanner.spanFrom(start), warnings,
111-
plainCss: plainCss);
104+
plainCss: plainCss, globalVariables: _globalVariables);
112105
});
113106
}
114107

@@ -288,7 +281,7 @@ abstract class StylesheetParser extends Parser {
288281
guarded: guarded,
289282
global: global,
290283
comment: precedingComment);
291-
if (global) _globalVariables.putIfAbsent(name, () => declaration);
284+
if (global) _globalVariables.putIfAbsent(name, () => declaration.span);
292285
return declaration;
293286
}
294287

lib/src/visitor/async_evaluate.dart

+8
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,14 @@ final class _EvaluateVisitor
10051005
for (var child in node.children) {
10061006
await child.accept(this);
10071007
}
1008+
1009+
// Make sure all global variables declared in a module always appear in the
1010+
// module's definition, even if their assignments aren't reached.
1011+
for (var (name, span) in node.globalVariables.pairs) {
1012+
visitVariableDeclaration(
1013+
VariableDeclaration(name, NullExpression(span), span, guarded: true));
1014+
}
1015+
10081016
return null;
10091017
}
10101018

lib/src/visitor/evaluate.dart

+9-1
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: e7260fedcd4f374ba517a93d038c3c53586c9622
8+
// Checksum: 396c8f169d95c601598b8c3be1f4b948ca22effa
99
//
1010
// ignore_for_file: unused_import
1111

@@ -1005,6 +1005,14 @@ final class _EvaluateVisitor
10051005
for (var child in node.children) {
10061006
child.accept(this);
10071007
}
1008+
1009+
// Make sure all global variables declared in a module always appear in the
1010+
// module's definition, even if their assignments aren't reached.
1011+
for (var (name, span) in node.globalVariables.pairs) {
1012+
visitVariableDeclaration(
1013+
VariableDeclaration(name, NullExpression(span), span, guarded: true));
1014+
}
1015+
10081016
return null;
10091017
}
10101018

pkg/sass-parser/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.4.2-dev
2+
3+
* Add support for parsing variable declarations.
4+
15
## 0.4.1
26

37
* Add `BooleanExpression` and `NumberExpression`.

pkg/sass-parser/lib/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ export {
9696
StatementType,
9797
StatementWithChildren,
9898
} from './src/statement';
99+
export {
100+
VariableDeclaration,
101+
VariableDeclarationProps,
102+
VariableDeclarationRaws,
103+
} from './src/statement/variable-declaration';
99104

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

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

+10
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ declare namespace SassInternal {
180180
readonly configuration: ConfiguredVariable[];
181181
}
182182

183+
class VariableDeclaration extends Statement {
184+
readonly namespace: string | null;
185+
readonly name: string;
186+
readonly expression: Expression;
187+
readonly isGuarded: boolean;
188+
readonly isGlobal: boolean;
189+
}
190+
183191
class ConfiguredVariable extends SassNode {
184192
readonly name: string;
185193
readonly expression: Expression;
@@ -238,6 +246,7 @@ export type Stylesheet = SassInternal.Stylesheet;
238246
export type StyleRule = SassInternal.StyleRule;
239247
export type SupportsRule = SassInternal.SupportsRule;
240248
export type UseRule = SassInternal.UseRule;
249+
export type VariableDeclaration = SassInternal.VariableDeclaration;
241250
export type ConfiguredVariable = SassInternal.ConfiguredVariable;
242251
export type Interpolation = SassInternal.Interpolation;
243252
export type Expression = SassInternal.Expression;
@@ -260,6 +269,7 @@ export interface StatementVisitorObject<T> {
260269
visitStyleRule(node: StyleRule): T;
261270
visitSupportsRule(node: SupportsRule): T;
262271
visitUseRule(node: UseRule): T;
272+
visitVariableDeclaration(node: VariableDeclaration): T;
263273
}
264274

265275
export interface ExpressionVisitorObject<T> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`a variable declaration toJSON 1`] = `
4+
{
5+
"expression": <"bar">,
6+
"global": false,
7+
"guarded": false,
8+
"inputs": [
9+
{
10+
"css": "baz.$foo: "bar"",
11+
"hasBOM": false,
12+
"id": "<input css _____>",
13+
},
14+
],
15+
"namespace": "baz",
16+
"raws": {},
17+
"sassType": "variable-declaration",
18+
"source": <1:1-1:16 in 0>,
19+
"type": "decl",
20+
"variableName": "foo",
21+
}
22+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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 {Rule} from './rule';
8+
import {Root} from './root';
9+
import {AtRule, ChildNode, Comment, Declaration, NewNode} from '.';
10+
11+
/**
12+
* A fake intermediate class to convince TypeScript to use Sass types for
13+
* various upstream methods.
14+
*
15+
* @hidden
16+
*/
17+
export class _Declaration<Props> extends postcss.Declaration {
18+
// Override the PostCSS container types to constrain them to Sass types only.
19+
// Unfortunately, there's no way to abstract this out, because anything
20+
// mixin-like returns an intersection type which doesn't actually override
21+
// parent methods. See microsoft/TypeScript#59394.
22+
23+
after(newNode: NewNode): this;
24+
append(...nodes: NewNode[]): this;
25+
assign(overrides: Partial<Props>): this;
26+
before(newNode: NewNode): this;
27+
cloneAfter(overrides?: Partial<Props>): this;
28+
cloneBefore(overrides?: Partial<Props>): this;
29+
each(
30+
callback: (node: ChildNode, index: number) => false | void
31+
): false | undefined;
32+
every(
33+
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
34+
): boolean;
35+
insertAfter(oldNode: postcss.ChildNode | number, newNode: NewNode): this;
36+
insertBefore(oldNode: postcss.ChildNode | number, newNode: NewNode): this;
37+
next(): ChildNode | undefined;
38+
prepend(...nodes: NewNode[]): this;
39+
prev(): ChildNode | undefined;
40+
replaceWith(...nodes: NewNode[]): this;
41+
root(): Root;
42+
some(
43+
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
44+
): boolean;
45+
walk(
46+
callback: (node: ChildNode, index: number) => false | void
47+
): false | undefined;
48+
walkAtRules(
49+
nameFilter: RegExp | string,
50+
callback: (atRule: AtRule, index: number) => false | void
51+
): false | undefined;
52+
walkAtRules(
53+
callback: (atRule: AtRule, index: number) => false | void
54+
): false | undefined;
55+
walkComments(
56+
callback: (comment: Comment, indexed: number) => false | void
57+
): false | undefined;
58+
walkComments(
59+
callback: (comment: Comment, indexed: number) => false | void
60+
): false | undefined;
61+
walkDecls(
62+
propFilter: RegExp | string,
63+
callback: (decl: Declaration, index: number) => false | void
64+
): false | undefined;
65+
walkDecls(
66+
callback: (decl: Declaration, index: number) => false | void
67+
): false | undefined;
68+
walkRules(
69+
selectorFilter: RegExp | string,
70+
callback: (rule: Rule, index: number) => false | void
71+
): false | undefined;
72+
walkRules(
73+
callback: (rule: Rule, index: number) => false | void
74+
): false | undefined;
75+
get first(): ChildNode | undefined;
76+
get last(): ChildNode | undefined;
77+
}
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._Declaration = require('postcss').Declaration;

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

+13-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import {ForRule, ForRuleProps} from './for-rule';
1818
import {Root} from './root';
1919
import {Rule, RuleProps} from './rule';
2020
import {UseRule, UseRuleProps} from './use-rule';
21+
import {
22+
VariableDeclaration,
23+
VariableDeclarationProps,
24+
} from './variable-declaration';
2125

2226
// TODO: Replace this with the corresponding Sass types once they're
2327
// implemented.
@@ -28,7 +32,7 @@ export {Declaration} from 'postcss';
2832
*
2933
* @category Statement
3034
*/
31-
export type AnyStatement = Comment | Root | Rule | GenericAtRule;
35+
export type AnyStatement = Comment | Root | Rule | AtRule | VariableDeclaration;
3236

3337
/**
3438
* Sass statement types.
@@ -49,7 +53,8 @@ export type StatementType =
4953
| 'for-rule'
5054
| 'error-rule'
5155
| 'use-rule'
52-
| 'sass-comment';
56+
| 'sass-comment'
57+
| 'variable-declaration';
5358

5459
/**
5560
* All Sass statements that are also at-rules.
@@ -78,7 +83,7 @@ export type Comment = CssComment | SassComment;
7883
*
7984
* @category Statement
8085
*/
81-
export type ChildNode = Rule | AtRule | Comment;
86+
export type ChildNode = Rule | AtRule | Comment | VariableDeclaration;
8287

8388
/**
8489
* The properties that can be used to construct {@link ChildNode}s.
@@ -97,7 +102,8 @@ export type ChildProps =
97102
| GenericAtRuleProps
98103
| RuleProps
99104
| SassCommentChildProps
100-
| UseRuleProps;
105+
| UseRuleProps
106+
| VariableDeclarationProps;
101107

102108
/**
103109
* The Sass eqivalent of PostCSS's `ContainerProps`.
@@ -185,6 +191,7 @@ const visitor = sassInternal.createStatementVisitor<Statement>({
185191
return rule;
186192
},
187193
visitUseRule: inner => new UseRule(undefined, inner),
194+
visitVariableDeclaration: inner => new VariableDeclaration(undefined, inner),
188195
});
189196

190197
/** Appends parsed versions of `internal`'s children to `container`. */
@@ -301,6 +308,8 @@ export function normalize(
301308
result.push(new SassComment(node));
302309
} else if ('useUrl' in node) {
303310
result.push(new UseRule(node));
311+
} else if ('variableName' in node) {
312+
result.push(new VariableDeclaration(node));
304313
} else {
305314
result.push(...postcssNormalizeAndConvertToSass(self, node, sample));
306315
}

0 commit comments

Comments
 (0)