Skip to content

Commit

Permalink
Add support for parsing variable expressions (#2541)
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 authored Mar 10, 2025
1 parent 2680d5f commit e6589fe
Show file tree
Hide file tree
Showing 13 changed files with 472 additions and 19 deletions.
2 changes: 2 additions & 0 deletions pkg/sass-parser/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

* Add support for parsing unary operation expressions.

* Add support for parsing variable expressions.

## 0.4.15

* Add support for parsing list expressions.
Expand Down
5 changes: 5 additions & 0 deletions pkg/sass-parser/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@ export {
UnaryOperationExpressionProps,
UnaryOperationExpressionRaws,
} from './src/expression/unary-operation';
export {
VariableExpression,
VariableExpressionProps,
VariableExpressionRaws,
} from './src/expression/variable';

/** Options that can be passed to the Sass parsers to control their behavior. */
export type SassParserOptions = Pick<postcss.ProcessOptions, 'from' | 'map'>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a variable expression toJSON with a namespace 1`] = `
{
"inputs": [
{
"css": "@#{bar.$foo}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"namespace": "bar",
"raws": {},
"sassType": "variable",
"source": <1:4-1:12 in 0>,
"variableName": "foo",
}
`;

exports[`a variable expression toJSON without a namespace 1`] = `
{
"inputs": [
{
"css": "@#{$foo}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"raws": {},
"sassType": "variable",
"source": <1:4-1:8 in 0>,
"variableName": "foo",
}
`;
2 changes: 2 additions & 0 deletions pkg/sass-parser/lib/src/expression/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {ParenthesizedExpression} from './parenthesized';
import {SelectorExpression} from './selector';
import {StringExpression} from './string';
import {UnaryOperationExpression} from './unary-operation';
import {VariableExpression} from './variable';

/** The visitor to use to convert internal Sass nodes to JS. */
const visitor = sassInternal.createExpressionVisitor<AnyExpression>({
Expand Down Expand Up @@ -55,6 +56,7 @@ const visitor = sassInternal.createExpressionVisitor<AnyExpression>({
}),
visitUnaryOperationExpression: inner =>
new UnaryOperationExpression(undefined, inner),
visitVariableExpression: inner => new VariableExpression(undefined, inner),
});

/** Converts an internal expression AST node into an external one. */
Expand Down
2 changes: 2 additions & 0 deletions pkg/sass-parser/lib/src/expression/from-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {NumberExpression} from './number';
import {ParenthesizedExpression} from './parenthesized';
import {StringExpression} from './string';
import {UnaryOperationExpression} from './unary-operation';
import {VariableExpression} from './variable';

/** Constructs an expression from {@link ExpressionProps}. */
export function fromProps(props: ExpressionProps): AnyExpression {
Expand All @@ -25,6 +26,7 @@ export function fromProps(props: ExpressionProps): AnyExpression {
if ('separator' in props) return new ListExpression(props);
if ('nodes' in props) return new MapExpression(props);
if ('inParens' in props) return new ParenthesizedExpression(props);
if ('variableName' in props) return new VariableExpression(props);
if ('name' in props) {
if (typeof props.name === 'string') {
return new FunctionExpression(props as FunctionExpressionProps);
Expand Down
35 changes: 27 additions & 8 deletions pkg/sass-parser/lib/src/expression/function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,35 @@ describe('a function expression', () => {
}).toString(),
).toBe('foo(bar)'));

it('with a namespace', () =>
expect(
new FunctionExpression({
namespace: 'baz',
name: 'foo',
arguments: [{text: 'bar'}],
}).toString(),
).toBe('baz.foo(bar)'));
describe('with a namespace', () => {
it("that's an identifier", () =>
expect(
new FunctionExpression({
namespace: 'baz',
name: 'foo',
arguments: [{text: 'bar'}],
}).toString(),
).toBe('baz.foo(bar)'));

it("that's not an identifier", () =>
expect(
new FunctionExpression({
namespace: 'b z',
name: 'foo',
arguments: [{text: 'bar'}],
}).toString(),
).toBe('b\\20z.foo(bar)'));
});
});

it("with a name that's not an identifier", () =>
expect(
new FunctionExpression({
name: 'f o',
arguments: [{text: 'bar'}],
}).toString(),
).toBe('f\\20o(bar)'));

it('with matching namespace', () =>
expect(
new FunctionExpression({
Expand Down
14 changes: 8 additions & 6 deletions pkg/sass-parser/lib/src/expression/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {ArgumentList, ArgumentListProps} from '../argument-list';
import {LazySource} from '../lazy-source';
import {NodeProps} from '../node';
import {RawWithValue} from '../raw-with-value';
import type * as sassInternal from '../sass-internal';
import * as sassInternal from '../sass-internal';
import * as utils from '../utils';
import {Expression} from '.';

Expand All @@ -34,7 +34,7 @@ export interface FunctionExpressionRaws {
* The function's namespace.
*
* This may be different than {@link FunctionExpression.namespace} if the
* namespace contains escape codes or underscores.
* namespace contains escape codes.
*/
namespace?: RawWithValue<string>;

Expand All @@ -59,8 +59,8 @@ export class FunctionExpression extends Expression {
/**
* This function's namespace.
*
* This is the parsed and normalized value, with underscores converted to
* hyphens and escapes resolved to the characters they represent.
* This is the parsed and normalized value, with escapes resolved to the
* characters they represent.
*/
get namespace(): string | undefined {
return this._namespace;
Expand Down Expand Up @@ -136,9 +136,11 @@ export class FunctionExpression extends Expression {
(this.namespace
? (this.raws.namespace?.value === this.namespace
? this.raws.namespace.raw
: this.namespace) + '.'
: sassInternal.toCssIdentifier(this.namespace)) + '.'
: '') +
(this.raws.name?.value === this.name ? this.raws.name.raw : this.name) +
(this.raws.name?.value === this.name
? this.raws.name.raw
: sassInternal.toCssIdentifier(this.name)) +
this.arguments
);
}
Expand Down
10 changes: 7 additions & 3 deletions pkg/sass-parser/lib/src/expression/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
UnaryOperationExpression,
UnaryOperationExpressionProps,
} from './unary-operation';
import type {VariableExpression, VariableExpressionProps} from './variable';

/**
* The union type of all Sass expressions.
Expand All @@ -47,7 +48,8 @@ export type AnyExpression =
| ParenthesizedExpression
| SelectorExpression
| StringExpression
| UnaryOperationExpression;
| UnaryOperationExpression
| VariableExpression;

/**
* Sass expression types.
Expand All @@ -67,7 +69,8 @@ export type ExpressionType =
| 'parenthesized'
| 'selector-expr'
| 'string'
| 'unary-operation';
| 'unary-operation'
| 'variable';

/**
* The union type of all properties that can be used to construct Sass
Expand All @@ -87,7 +90,8 @@ export type ExpressionProps =
| NumberExpressionProps
| ParenthesizedExpressionProps
| StringExpressionProps
| UnaryOperationExpressionProps;
| UnaryOperationExpressionProps
| VariableExpressionProps;

/**
* The superclass of Sass expression nodes.
Expand Down
Loading

0 comments on commit e6589fe

Please sign in to comment.