Skip to content

Commit 84e281e

Browse files
authored
Add BooleanExpression and NumberExpression to sass-parser (#2376)
* Add boolean expressions to the Sass parser * Add NumberExpression to sass-parser * Add "raw" and "value" to raws. The raws.value is compared with .value. If they are the same, then raws.raw is used when stringifying * Add contributing instructions for sass-parser
1 parent 76cfd6b commit 84e281e

18 files changed

+627
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.80.4
2+
3+
* No user-visible changes.
4+
15
## 1.80.3
26

37
* Fix a bug where `@import url("...")` would crash in plain CSS files.

pkg/sass-parser/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.4.1
2+
3+
* Add `BooleanExpression` and `NumberExpression`.
4+
15
## 0.4.0
26

37
* **Breaking change:** Warnings are no longer emitted during parsing, so the

pkg/sass-parser/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,17 @@ There are a few cases where an operation that's valid in PostCSS won't work with
257257

258258
* Trying to add child nodes to a Sass statement that doesn't support children
259259
like `@use` or `@error` is not supported.
260+
261+
## Contributing
262+
263+
Before sending out a pull request, please run the following commands from the
264+
`pkg/sass-parser` directory:
265+
266+
* `npm run check` - Runs `eslint`, and then tries to compile the package with
267+
`tsc`.
268+
269+
* `npm run test` - Runs all the tests in the package.
270+
271+
Note: You should run `dart run grinder before-test` from the `dart-sass`
272+
directory beforehand to ensure you're running `sass-parser` against the latest
273+
version of `dart-sass` JavaScript API.

pkg/sass-parser/lib/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// https://opensource.org/licenses/MIT.
44

55
import * as postcss from 'postcss';
6-
import * as sassApi from 'sass';
76

87
import {Root} from './src/statement/root';
98
import * as sassInternal from './src/sass-internal';
@@ -27,6 +26,16 @@ export {
2726
StringExpressionProps,
2827
StringExpressionRaws,
2928
} from './src/expression/string';
29+
export {
30+
BooleanExpression,
31+
BooleanExpressionProps,
32+
BooleanExpressionRaws,
33+
} from './src/expression/boolean';
34+
export {
35+
NumberExpression,
36+
NumberExpressionProps,
37+
NumberExpressionRaws,
38+
} from './src/expression/number';
3039
export {
3140
Interpolation,
3241
InterpolationProps,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`a boolean expression toJSON 1`] = `
4+
{
5+
"inputs": [
6+
{
7+
"css": "@#{true}",
8+
"hasBOM": false,
9+
"id": "<input css _____>",
10+
},
11+
],
12+
"raws": {},
13+
"sassType": "boolean",
14+
"source": <1:4-1:8 in 0>,
15+
"value": true,
16+
}
17+
`;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`a number expression toJSON 1`] = `
4+
{
5+
"inputs": [
6+
{
7+
"css": "@#{123%}",
8+
"hasBOM": false,
9+
"id": "<input css _____>",
10+
},
11+
],
12+
"raws": {},
13+
"sassType": "number",
14+
"source": <1:4-1:8 in 0>,
15+
"unit": "%",
16+
"value": 123,
17+
}
18+
`;
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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 {BooleanExpression} from '../..';
6+
import * as utils from '../../../test/utils';
7+
8+
describe('a boolean expression', () => {
9+
let node: BooleanExpression;
10+
11+
describe('true', () => {
12+
function describeNode(
13+
description: string,
14+
create: () => BooleanExpression
15+
): void {
16+
describe(description, () => {
17+
beforeEach(() => void (node = create()));
18+
19+
it('has sassType boolean', () => expect(node.sassType).toBe('boolean'));
20+
21+
it('is true', () => expect(node.value).toBe(true));
22+
});
23+
}
24+
25+
describeNode('parsed', () => utils.parseExpression('true'));
26+
27+
describeNode(
28+
'constructed manually',
29+
() => new BooleanExpression({value: true})
30+
);
31+
32+
describeNode('constructed from ExpressionProps', () =>
33+
utils.fromExpressionProps({value: true})
34+
);
35+
});
36+
37+
describe('false', () => {
38+
function describeNode(
39+
description: string,
40+
create: () => BooleanExpression
41+
): void {
42+
describe(description, () => {
43+
beforeEach(() => void (node = create()));
44+
45+
it('has sassType boolean', () => expect(node.sassType).toBe('boolean'));
46+
47+
it('is false', () => expect(node.value).toBe(false));
48+
});
49+
}
50+
51+
describeNode('parsed', () => utils.parseExpression('false'));
52+
53+
describeNode(
54+
'constructed manually',
55+
() => new BooleanExpression({value: false})
56+
);
57+
58+
describeNode('constructed from ExpressionProps', () =>
59+
utils.fromExpressionProps({value: false})
60+
);
61+
});
62+
63+
it('assigned new value', () => {
64+
node = utils.parseExpression('true');
65+
node.value = false;
66+
expect(node.value).toBe(false);
67+
});
68+
69+
describe('stringifies', () => {
70+
it('true', () => {
71+
expect(utils.parseExpression('true').toString()).toBe('true');
72+
});
73+
74+
it('false', () => {
75+
expect(utils.parseExpression('false').toString()).toBe('false');
76+
});
77+
});
78+
79+
describe('clone', () => {
80+
let original: BooleanExpression;
81+
82+
beforeEach(() => {
83+
original = utils.parseExpression('true');
84+
});
85+
86+
describe('with no overrides', () => {
87+
let clone: BooleanExpression;
88+
89+
beforeEach(() => void (clone = original.clone()));
90+
91+
describe('has the same properties:', () => {
92+
it('value', () => expect(clone.value).toBe(true));
93+
94+
it('raws', () => expect(clone.raws).toEqual({}));
95+
96+
it('source', () => expect(clone.source).toBe(original.source));
97+
});
98+
99+
it('creates a new self', () => expect(clone).not.toBe(original));
100+
});
101+
102+
describe('overrides', () => {
103+
describe('value', () => {
104+
it('defined', () =>
105+
expect(original.clone({value: false}).value).toBe(false));
106+
107+
it('undefined', () =>
108+
expect(original.clone({value: undefined}).value).toBe(true));
109+
});
110+
111+
describe('raws', () => {
112+
it('defined', () =>
113+
expect(original.clone({raws: {}}).raws).toEqual({}));
114+
115+
it('undefined', () =>
116+
expect(original.clone({raws: undefined}).raws).toEqual({}));
117+
});
118+
});
119+
});
120+
121+
it('toJSON', () => expect(utils.parseExpression('true')).toMatchSnapshot());
122+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 {LazySource} from '../lazy-source';
8+
import type * as sassInternal from '../sass-internal';
9+
import * as utils from '../utils';
10+
import {Expression} from '.';
11+
12+
/**
13+
* The initializer properties for {@link BooleanExpression}.
14+
*
15+
* @category Expression
16+
*/
17+
export interface BooleanExpressionProps {
18+
value: boolean;
19+
raws?: BooleanExpressionRaws;
20+
}
21+
22+
/**
23+
* Raws indicating how to precisely serialize a {@link BooleanExpression}.
24+
*
25+
* @category Expression
26+
*/
27+
// eslint-disable-next-line @typescript-eslint/no-empty-interface -- No raws for a boolean expression yet.
28+
export interface BooleanExpressionRaws {}
29+
30+
/**
31+
* An expression representing a boolean literal in Sass.
32+
*
33+
* @category Expression
34+
*/
35+
export class BooleanExpression extends Expression {
36+
readonly sassType = 'boolean' as const;
37+
declare raws: BooleanExpressionRaws;
38+
39+
/** The boolean value of this expression. */
40+
get value(): boolean {
41+
return this._value;
42+
}
43+
set value(value: boolean) {
44+
// TODO - postcss/postcss#1957: Mark this as dirty
45+
this._value = value;
46+
}
47+
private _value!: boolean;
48+
49+
constructor(defaults: BooleanExpressionProps);
50+
/** @hidden */
51+
constructor(_: undefined, inner: sassInternal.BooleanExpression);
52+
constructor(defaults?: object, inner?: sassInternal.BooleanExpression) {
53+
super(defaults);
54+
if (inner) {
55+
this.source = new LazySource(inner);
56+
this.value = inner.value;
57+
} else {
58+
this.value ??= false;
59+
}
60+
}
61+
62+
clone(overrides?: Partial<BooleanExpressionProps>): this {
63+
return utils.cloneNode(this, overrides, ['raws', 'value']);
64+
}
65+
66+
toJSON(): object;
67+
/** @hidden */
68+
toJSON(_: string, inputs: Map<postcss.Input, number>): object;
69+
toJSON(_?: string, inputs?: Map<postcss.Input, number>): object {
70+
return utils.toJSON(this, ['value'], inputs);
71+
}
72+
73+
/** @hidden */
74+
toString(): string {
75+
return this.value ? 'true' : 'false';
76+
}
77+
78+
/** @hidden */
79+
get nonStatementChildren(): ReadonlyArray<Expression> {
80+
return [];
81+
}
82+
}

pkg/sass-parser/lib/src/expression/convert.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ import * as sassInternal from '../sass-internal';
77
import {BinaryOperationExpression} from './binary-operation';
88
import {StringExpression} from './string';
99
import {Expression} from '.';
10+
import {BooleanExpression} from './boolean';
11+
import {NumberExpression} from './number';
1012

1113
/** The visitor to use to convert internal Sass nodes to JS. */
1214
const visitor = sassInternal.createExpressionVisitor<Expression>({
1315
visitBinaryOperationExpression: inner =>
1416
new BinaryOperationExpression(undefined, inner),
1517
visitStringExpression: inner => new StringExpression(undefined, inner),
18+
visitBooleanExpression: inner => new BooleanExpression(undefined, inner),
19+
visitNumberExpression: inner => new NumberExpression(undefined, inner),
1620
});
1721

1822
/** Converts an internal expression AST node into an external one. */

pkg/sass-parser/lib/src/expression/from-props.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55
import {BinaryOperationExpression} from './binary-operation';
66
import {Expression, ExpressionProps} from '.';
77
import {StringExpression} from './string';
8+
import {BooleanExpression} from './boolean';
9+
import {NumberExpression} from './number';
810

911
/** Constructs an expression from {@link ExpressionProps}. */
1012
export function fromProps(props: ExpressionProps): Expression {
1113
if ('text' in props) return new StringExpression(props);
1214
if ('left' in props) return new BinaryOperationExpression(props);
15+
if ('value' in props) {
16+
if (typeof props.value === 'boolean') return new BooleanExpression(props);
17+
if (typeof props.value === 'number') return new NumberExpression(props);
18+
}
19+
1320
throw new Error(`Unknown node type: ${props}`);
1421
}

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

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,31 @@ import type {
77
BinaryOperationExpression,
88
BinaryOperationExpressionProps,
99
} from './binary-operation';
10+
import {BooleanExpression, BooleanExpressionProps} from './boolean';
11+
import {NumberExpression, NumberExpressionProps} from './number';
1012
import type {StringExpression, StringExpressionProps} from './string';
1113

1214
/**
1315
* The union type of all Sass expressions.
1416
*
1517
* @category Expression
1618
*/
17-
export type AnyExpression = BinaryOperationExpression | StringExpression;
19+
export type AnyExpression =
20+
| BinaryOperationExpression
21+
| StringExpression
22+
| BooleanExpression
23+
| NumberExpression;
1824

1925
/**
2026
* Sass expression types.
2127
*
2228
* @category Expression
2329
*/
24-
export type ExpressionType = 'binary-operation' | 'string';
30+
export type ExpressionType =
31+
| 'binary-operation'
32+
| 'string'
33+
| 'boolean'
34+
| 'number';
2535

2636
/**
2737
* The union type of all properties that can be used to construct Sass
@@ -31,7 +41,9 @@ export type ExpressionType = 'binary-operation' | 'string';
3141
*/
3242
export type ExpressionProps =
3343
| BinaryOperationExpressionProps
34-
| StringExpressionProps;
44+
| StringExpressionProps
45+
| BooleanExpressionProps
46+
| NumberExpressionProps;
3547

3648
/**
3749
* The superclass of Sass expression nodes.

0 commit comments

Comments
 (0)