Skip to content

Commit

Permalink
Merge pull request #2403 from sass/parser-variable
Browse files Browse the repository at this point in the history
Add sass-parser support for variable declarations and `@warn` rules
  • Loading branch information
nex3 authored Oct 25, 2024
2 parents 20397ef + fd5cff0 commit 477b596
Show file tree
Hide file tree
Showing 22 changed files with 1,401 additions and 22 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.80.5-dev

* No user-visible changes.

## 1.80.4

* No user-visible changes.
Expand Down
10 changes: 9 additions & 1 deletion lib/src/ast/sass/statement/stylesheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ final class Stylesheet extends ParentStatement<List<Statement>> {
@internal
final List<ParseTimeWarning> parseTimeWarnings;

/// The set of (normalized) global variable names defined by this stylesheet
/// to the spans where they're defined.
@internal
final Map<String, FileSpan> globalVariables;

Stylesheet(Iterable<Statement> children, FileSpan span)
: this.internal(children, span, []);

Expand All @@ -62,8 +67,11 @@ final class Stylesheet extends ParentStatement<List<Statement>> {
@internal
Stylesheet.internal(Iterable<Statement> children, this.span,
List<ParseTimeWarning> parseTimeWarnings,
{this.plainCss = false})
{this.plainCss = false, Map<String, FileSpan>? globalVariables})
: parseTimeWarnings = UnmodifiableListView(parseTimeWarnings),
globalVariables = globalVariables == null
? const {}
: Map.unmodifiable(globalVariables),
super(List.unmodifiable(children)) {
loop:
for (var child in this.children) {
Expand Down
15 changes: 4 additions & 11 deletions lib/src/parse/stylesheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ abstract class StylesheetParser extends Parser {
var _inExpression = false;

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

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

/// Ensure that all global variable assignments produce a variable in this
/// stylesheet, even if they aren't evaluated. See sass/language#50.
statements.addAll(_globalVariables.values.map((declaration) =>
VariableDeclaration(declaration.name,
NullExpression(declaration.expression.span), declaration.span,
guarded: true)));

return Stylesheet.internal(statements, scanner.spanFrom(start), warnings,
plainCss: plainCss);
plainCss: plainCss, globalVariables: _globalVariables);
});
}

Expand Down Expand Up @@ -288,7 +281,7 @@ abstract class StylesheetParser extends Parser {
guarded: guarded,
global: global,
comment: precedingComment);
if (global) _globalVariables.putIfAbsent(name, () => declaration);
if (global) _globalVariables.putIfAbsent(name, () => declaration.span);
return declaration;
}

Expand Down
8 changes: 8 additions & 0 deletions lib/src/visitor/async_evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,14 @@ final class _EvaluateVisitor
for (var child in node.children) {
await child.accept(this);
}

// Make sure all global variables declared in a module always appear in the
// module's definition, even if their assignments aren't reached.
for (var (name, span) in node.globalVariables.pairs) {
visitVariableDeclaration(
VariableDeclaration(name, NullExpression(span), span, guarded: true));
}

return null;
}

Expand Down
10 changes: 9 additions & 1 deletion lib/src/visitor/evaluate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// DO NOT EDIT. This file was generated from async_evaluate.dart.
// See tool/grind/synchronize.dart for details.
//
// Checksum: e7260fedcd4f374ba517a93d038c3c53586c9622
// Checksum: 396c8f169d95c601598b8c3be1f4b948ca22effa
//
// ignore_for_file: unused_import

Expand Down Expand Up @@ -1005,6 +1005,14 @@ final class _EvaluateVisitor
for (var child in node.children) {
child.accept(this);
}

// Make sure all global variables declared in a module always appear in the
// module's definition, even if their assignments aren't reached.
for (var (name, span) in node.globalVariables.pairs) {
visitVariableDeclaration(
VariableDeclaration(name, NullExpression(span), span, guarded: true));
}

return null;
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/sass-parser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.4.2-dev

* Add support for parsing variable declarations.

* Add support for parsing the `@warn` rule.

## 0.4.1

* Add `BooleanExpression` and `NumberExpression`.
Expand Down
6 changes: 6 additions & 0 deletions pkg/sass-parser/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ export {
StatementType,
StatementWithChildren,
} from './src/statement';
export {
VariableDeclaration,
VariableDeclarationProps,
VariableDeclarationRaws,
} from './src/statement/variable-declaration';
export {WarnRule, WarnRuleProps, WarnRuleRaws} from './src/statement/warn-rule';

/** Options that can be passed to the Sass parsers to control their behavior. */
export type SassParserOptions = Pick<postcss.ProcessOptions, 'from' | 'map'>;
Expand Down
16 changes: 16 additions & 0 deletions pkg/sass-parser/lib/src/sass-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ declare namespace SassInternal {
readonly configuration: ConfiguredVariable[];
}

class VariableDeclaration extends Statement {
readonly namespace: string | null;
readonly name: string;
readonly expression: Expression;
readonly isGuarded: boolean;
readonly isGlobal: boolean;
}

class WarnRule extends Statement {
readonly expression: Expression;
}

class ConfiguredVariable extends SassNode {
readonly name: string;
readonly expression: Expression;
Expand Down Expand Up @@ -238,6 +250,8 @@ export type Stylesheet = SassInternal.Stylesheet;
export type StyleRule = SassInternal.StyleRule;
export type SupportsRule = SassInternal.SupportsRule;
export type UseRule = SassInternal.UseRule;
export type VariableDeclaration = SassInternal.VariableDeclaration;
export type WarnRule = SassInternal.WarnRule;
export type ConfiguredVariable = SassInternal.ConfiguredVariable;
export type Interpolation = SassInternal.Interpolation;
export type Expression = SassInternal.Expression;
Expand All @@ -260,6 +274,8 @@ export interface StatementVisitorObject<T> {
visitStyleRule(node: StyleRule): T;
visitSupportsRule(node: SupportsRule): T;
visitUseRule(node: UseRule): T;
visitVariableDeclaration(node: VariableDeclaration): T;
visitWarnRule(node: WarnRule): T;
}

export interface ExpressionVisitorObject<T> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a variable declaration toJSON 1`] = `
{
"expression": <"bar">,
"global": false,
"guarded": false,
"inputs": [
{
"css": "baz.$foo: "bar"",
"hasBOM": false,
"id": "<input css _____>",
},
],
"namespace": "baz",
"raws": {},
"sassType": "variable-declaration",
"source": <1:1-1:16 in 0>,
"type": "decl",
"variableName": "foo",
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a @warn rule toJSON 1`] = `
{
"inputs": [
{
"css": "@warn foo",
"hasBOM": false,
"id": "<input css _____>",
},
],
"name": "warn",
"params": "foo",
"raws": {},
"sassType": "warn-rule",
"source": <1:1-1:10 in 0>,
"type": "atrule",
"warnExpression": <foo>,
}
`;
77 changes: 77 additions & 0 deletions pkg/sass-parser/lib/src/statement/declaration-internal.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2024 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

import * as postcss from 'postcss';

import {Rule} from './rule';
import {Root} from './root';
import {AtRule, ChildNode, Comment, Declaration, NewNode} from '.';

/**
* A fake intermediate class to convince TypeScript to use Sass types for
* various upstream methods.
*
* @hidden
*/
export class _Declaration<Props> extends postcss.Declaration {
// Override the PostCSS container types to constrain them to Sass types only.
// Unfortunately, there's no way to abstract this out, because anything
// mixin-like returns an intersection type which doesn't actually override
// parent methods. See microsoft/TypeScript#59394.

after(newNode: NewNode): this;
append(...nodes: NewNode[]): this;
assign(overrides: Partial<Props>): this;
before(newNode: NewNode): this;
cloneAfter(overrides?: Partial<Props>): this;
cloneBefore(overrides?: Partial<Props>): this;
each(
callback: (node: ChildNode, index: number) => false | void
): false | undefined;
every(
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
): boolean;
insertAfter(oldNode: postcss.ChildNode | number, newNode: NewNode): this;
insertBefore(oldNode: postcss.ChildNode | number, newNode: NewNode): this;
next(): ChildNode | undefined;
prepend(...nodes: NewNode[]): this;
prev(): ChildNode | undefined;
replaceWith(...nodes: NewNode[]): this;
root(): Root;
some(
condition: (node: ChildNode, index: number, nodes: ChildNode[]) => boolean
): boolean;
walk(
callback: (node: ChildNode, index: number) => false | void
): false | undefined;
walkAtRules(
nameFilter: RegExp | string,
callback: (atRule: AtRule, index: number) => false | void
): false | undefined;
walkAtRules(
callback: (atRule: AtRule, index: number) => false | void
): false | undefined;
walkComments(
callback: (comment: Comment, indexed: number) => false | void
): false | undefined;
walkComments(
callback: (comment: Comment, indexed: number) => false | void
): false | undefined;
walkDecls(
propFilter: RegExp | string,
callback: (decl: Declaration, index: number) => false | void
): false | undefined;
walkDecls(
callback: (decl: Declaration, index: number) => false | void
): false | undefined;
walkRules(
selectorFilter: RegExp | string,
callback: (rule: Rule, index: number) => false | void
): false | undefined;
walkRules(
callback: (rule: Rule, index: number) => false | void
): false | undefined;
get first(): ChildNode | undefined;
get last(): ChildNode | undefined;
}
5 changes: 5 additions & 0 deletions pkg/sass-parser/lib/src/statement/declaration-internal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2024 Google Inc. Use of this source code is governed by an
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.

exports._Declaration = require('postcss').Declaration;
26 changes: 21 additions & 5 deletions pkg/sass-parser/lib/src/statement/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import {ForRule, ForRuleProps} from './for-rule';
import {Root} from './root';
import {Rule, RuleProps} from './rule';
import {UseRule, UseRuleProps} from './use-rule';
import {
VariableDeclaration,
VariableDeclarationProps,
} from './variable-declaration';
import {WarnRule, WarnRuleProps} from './warn-rule';

// TODO: Replace this with the corresponding Sass types once they're
// implemented.
Expand All @@ -28,7 +33,7 @@ export {Declaration} from 'postcss';
*
* @category Statement
*/
export type AnyStatement = Comment | Root | Rule | GenericAtRule;
export type AnyStatement = Comment | Root | Rule | AtRule | VariableDeclaration;

/**
* Sass statement types.
Expand All @@ -49,7 +54,9 @@ export type StatementType =
| 'for-rule'
| 'error-rule'
| 'use-rule'
| 'sass-comment';
| 'sass-comment'
| 'variable-declaration'
| 'warn-rule';

/**
* All Sass statements that are also at-rules.
Expand All @@ -62,7 +69,8 @@ export type AtRule =
| ErrorRule
| ForRule
| GenericAtRule
| UseRule;
| UseRule
| WarnRule;

/**
* All Sass statements that are comments.
Expand All @@ -78,7 +86,7 @@ export type Comment = CssComment | SassComment;
*
* @category Statement
*/
export type ChildNode = Rule | AtRule | Comment;
export type ChildNode = Rule | AtRule | Comment | VariableDeclaration;

/**
* The properties that can be used to construct {@link ChildNode}s.
Expand All @@ -97,7 +105,9 @@ export type ChildProps =
| GenericAtRuleProps
| RuleProps
| SassCommentChildProps
| UseRuleProps;
| UseRuleProps
| VariableDeclarationProps
| WarnRuleProps;

/**
* The Sass eqivalent of PostCSS's `ContainerProps`.
Expand Down Expand Up @@ -185,6 +195,8 @@ const visitor = sassInternal.createStatementVisitor<Statement>({
return rule;
},
visitUseRule: inner => new UseRule(undefined, inner),
visitVariableDeclaration: inner => new VariableDeclaration(undefined, inner),
visitWarnRule: inner => new WarnRule(undefined, inner),
});

/** Appends parsed versions of `internal`'s children to `container`. */
Expand Down Expand Up @@ -301,6 +313,10 @@ export function normalize(
result.push(new SassComment(node));
} else if ('useUrl' in node) {
result.push(new UseRule(node));
} else if ('variableName' in node) {
result.push(new VariableDeclaration(node));
} else if ('warnExpression' in node) {
result.push(new WarnRule(node));
} else {
result.push(...postcssNormalizeAndConvertToSass(self, node, sample));
}
Expand Down
Loading

0 comments on commit 477b596

Please sign in to comment.