Skip to content

Commit

Permalink
[WIP] Add sass-parser support for the @use rule
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Oct 10, 2024
1 parent 54dddf6 commit dccf012
Show file tree
Hide file tree
Showing 7 changed files with 496 additions and 4 deletions.
1 change: 1 addition & 0 deletions pkg/sass-parser/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as sassInternal from './src/sass-internal';
import {Stringifier} from './src/stringifier';

export {AnyNode, Node, NodeProps, NodeType} from './src/node';
export {RawWithValue} from './src/raw-with-value';
export {
AnyExpression,
Expression,
Expand Down
177 changes: 177 additions & 0 deletions pkg/sass-parser/lib/src/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// 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 type {AtRuleRaws} from 'postcss/lib/at-rule';

import {convertExpression} from '../expression/convert';
import {Expression, ExpressionProps} from '../expression';
import {fromProps} from '../expression/from-props';
import {LazySource} from '../lazy-source';
import {RawWithValue} from '../raw-with-value';
import type * as sassInternal from '../sass-internal';
import * as utils from '../utils';
import {
ChildNode,
ContainerProps,
NewNode,
Statement,
StatementWithChildren,
appendInternalChildren,
normalize,
} from '.';
import {_AtRule} from './at-rule-internal';
import {interceptIsClean} from './intercept-is-clean';
import * as sassParser from '../..';

/**
* The set of raws supported by {@link Configuration}.
*
* @category Statement
*/
export interface ConfigurationRaws {
/** The whitespace after the opening parenthesis. */
afterOpen?: string;

/**
* The space symbols and optionally comma between the last configured variable
* and the closing parenthesis.
*/
after?: string;
}

/**
* The initializer properties for {@link Configuration}.
*
* @category Statement
*/
export type ConfigurationProps {
raws?: ConfigurationRaws;
variables: Record<string, ConfiguredVariableValueProps> | Array<ConfiguredVariable|ConfiguredVariableProps>;
}

/**
* A configuration map for a `@use` or `@forward` rule.
*
* @category Statement
*/
export class Configuration extends Node {
readonly sassType = 'configuration' as const;
declare raws: ConfigurationRaws;

/** The underlying map from variable names to their values. */
private _variables: Map<string, ConfiguredVariable> = new Map();

/** The number of variables in this configuration. */
get size(): number {
return this._variables.size;
}

constructor(defaults?: ConfigurationProps);
/** @hidden */
constructor(_: undefined, inner: sassInternal.ConfiguredVariable[]);
constructor(defaults?: ConfigurationProps, inner?: sassInternal.ConfiguredVariable[]) {
this.raws = defaults?.raws ?? {};

if (defaults) {
for (const variable of Array.isArray(defaults.variables) ? defaults.variables : Object.entries(defaults.variables)) {
this.add(variable);
}
} else if (inner) {
this.source = new LazySource({
get span(): FileSpan {
// TODO: expand inner[0] and inner.at(-1) out through `(` and `)`
// respectively and then combine them.
}
});
for (const variable of inner) {
this.add(new ConfiguredVariable(undefined, variable));
}
}
}

/**
* Adds {@link variable} to this configuration.
*
* If there's already a variable with that name, it's removed first.
*/
add(variable: ConfiguredVariable|ConfiguredVariableProps): this {
if (!('sassType' in variable)) variable = new ConfiguredVariable(variable);
variable.parent = this;
this._variables.get(variable.name)?.parent = undefined;
this._variables.set(variable.name, variable);
return this;
}

/** Removes all variables from this configuration. */
clear(): void {
for (const variable of this._variables.values()) {
variable.parent = undefined;
}
this._variables.clear();
}

/** Removes the variable named {@link name} from this configuration. */
delete(key: string): boolean {
this._variables.get(name)?.parent = undefined;
return this._variables.delete(name);
}

/**
* Returns the variable named {@link name} from this configuration if it
* contains one.
*/
get(key: string): ConfiguredVariable|undefined {
return this._variables.get(key);
}

/**
* Returns whether this configuration has a variable named {@link name}.
*/
has(key: string): boolean {
return this._variables.has(key);
}

/**
* Sets the value for the variable named {@link key}. This fully overrides the
* previous value, so all previous raws and guarded state are discarded.
*/
set(key: string, value: ConfiguredVariableValueProps): this {
const variable = new ConfiguredVariable([key, value]);
variable.parent = this;
this._variables.get(key)?.parent = undefined;
this._variables.set(key, value);
return this;
}

/** Returns all the variables in this configuration. */
variables(): IterableIterator<ConfiguredVariable> {
return this._variables.values();
}

clone(overrides?: Partial<ConfigurationProps>): this {
// We can't use `utils.cloneNode` here because variables isn't a public
// field. Fortunately this class doesn't have any settable derived fields to
// make cloning more complicated.
return new Configuration({
raws: overrides?.raws ?? this.raws,
variables: overrides?.variables ?? [...this._variables.values()]
});
}

toJSON(): object;
/** @hidden */
toJSON(_: string, inputs: Map<postcss.Input, number>): object;
toJSON(_?: string, inputs?: Map<postcss.Input, number>): object {
return utils.toJSON(this, ['variables'], inputs);
}

toString(): string {
let result = `(${this.raws.afterOpen ?? ''}`;
for (const variable of this._variables.values()) {
result += variable.toString();
}
return result + `${this.raws.after ?? ''})`;
}
}
36 changes: 36 additions & 0 deletions pkg/sass-parser/lib/src/configured_variable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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';

Check warning on line 5 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'postcss' is defined but never used
import type {AtRuleRaws} from 'postcss/lib/at-rule';

Check warning on line 6 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'AtRuleRaws' is defined but never used

import {convertExpression} from '../expression/convert';

Check warning on line 8 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'convertExpression' is defined but never used
import {Expression, ExpressionProps} from '../expression';
import {fromProps} from '../expression/from-props';

Check warning on line 10 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'fromProps' is defined but never used
import {LazySource} from '../lazy-source';

Check warning on line 11 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'LazySource' is defined but never used
import {RawWithValue} from '../raw-with-value';

Check warning on line 12 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'RawWithValue' is defined but never used
import type * as sassInternal from '../sass-internal';

Check warning on line 13 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'sassInternal' is defined but never used
import * as utils from '../utils';

Check warning on line 14 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'utils' is defined but never used
import {
ChildNode,

Check warning on line 16 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'ChildNode' is defined but never used
ContainerProps,

Check warning on line 17 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

'ContainerProps' is defined but never used
NewNode,
Statement,
StatementWithChildren,
appendInternalChildren,
normalize,
} from '.';
import {_AtRule} from './at-rule-internal';
import {interceptIsClean} from './intercept-is-clean';
import * as sassParser from '../..';

export interface ConfiguredVariablePropsWithoutName {
raws?: ConfiguredVariableRaws;
value: Expression;
guarded?: boolean;
}

export type ConfiguredVariableValueProps = Expression|ExpressionProps|ConfiguredVariablePropsWithoutName;

Check failure on line 34 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

Replace `·Expression|ExpressionProps|` with `⏎··|·Expression⏎··|·ExpressionProps⏎··|·`

export type ConfiguredVariableProps = (ConfiguredVariablePropsWithoutName & {name?: string}) | [string, ConfiguredVariableValueProps];

Check failure on line 36 in pkg/sass-parser/lib/src/configured_variable.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

Replace `·(ConfiguredVariablePropsWithoutName·&·{name?:·string})` with `⏎··|·(ConfiguredVariablePropsWithoutName·&·{name?:·string})⏎·`
6 changes: 2 additions & 4 deletions pkg/sass-parser/lib/src/interpolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {fromProps} from './expression/from-props';
import {Expression, ExpressionProps} from './expression';
import {LazySource} from './lazy-source';
import {Node} from './node';
import {RawWithValue} from './src/raw-with-value';
import type * as sassInternal from './sass-internal';
import * as utils from './utils';

Expand Down Expand Up @@ -48,13 +49,10 @@ export interface InterpolationRaws {
* The text written in the stylesheet for the plain-text portions of the
* interpolation, without any interpretation of escape sequences.
*
* `raw` is the value of the raw itself, and `value` is the parsed value
* that's required to be in the interpolation in order for this raw to be used.
*
* Any indices for which {@link Interpolation.nodes} doesn't contain a string
* are ignored.
*/
text?: Array<{raw: string; value: string} | undefined>;
text?: Array<RawWithValue | undefined>;

/**
* The whitespace before and after each interpolated expression.
Expand Down
26 changes: 26 additions & 0 deletions pkg/sass-parser/lib/src/raw-with-value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.

/**
* An object describing how a value is represented in a stylesheet's source.
*
* This is used for values that can have multiple different representations that
* all produce the same value. The {@link raw} field indicates the textual
* representation in the stylesheet, while the {@link value} indicates the value
* it represents.
*
* When serializing, if {@link value} doesn't match the value in the AST node,
* this is ignored. This ensures that if a plugin overwrites the AST value
* and ignores the raws, its change is preserved in the serialized output.
*/
export interface RawWithValue<T> {
/** The textual representation of {@link value} in the stylesheet. */
raw: string;

/**
* The parsed value that {@link raw} represents. This is used to verify that
* this raw is still valid for the AST node that contains it.
*/
value: T;
}
15 changes: 15 additions & 0 deletions pkg/sass-parser/lib/src/sass-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ declare namespace SassInternal {
toInterpolation(): Interpolation;
}

class UseRule extends Statement {
readonly url: Object;
readonly namespace: string|null;

Check failure on line 177 in pkg/sass-parser/lib/src/sass-internal.ts

View workflow job for this annotation

GitHub Actions / test / sass-parser Static Analysis

Replace `|` with `·|·`
readonly configuration: ConfiguredValue[];
}

class ConfiguredVariable extends SassNode {
readonly name: string;
readonly expression: Expression;
readonly isGuarded: boolean;
}

class Expression extends SassNode {
accept<T>(visitor: ExpressionVisitor<T>): T;
}
Expand Down Expand Up @@ -214,6 +226,8 @@ export type SilentComment = SassInternal.SilentComment;
export type Stylesheet = SassInternal.Stylesheet;
export type StyleRule = SassInternal.StyleRule;
export type SupportsRule = SassInternal.SupportsRule;
export type UseRule = SassInternal.UseRule;
export type ConfiguredVariable = SassInternal.ConfiguredVariable;
export type Interpolation = SassInternal.Interpolation;
export type Expression = SassInternal.Expression;
export type BinaryOperationExpression = SassInternal.BinaryOperationExpression;
Expand All @@ -232,6 +246,7 @@ export interface StatementVisitorObject<T> {
visitSilentComment(node: SilentComment): T;
visitStyleRule(node: StyleRule): T;
visitSupportsRule(node: SupportsRule): T;
visitUseRule(node: UseRule): T;
}

export interface ExpressionVisitorObject<T> {
Expand Down
Loading

0 comments on commit dccf012

Please sign in to comment.