8000 Add support for parsing null literals (#2525) · sass/dart-sass@37b66a1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 37b66a1

Browse files
authored
Add support for parsing null literals (#2525)
1 parent 87ef19a commit 37b66a1

File tree

14 files changed

+204
-4
lines changed

14 files changed

+204
-4
lines changed

CHANGELOG.md

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

37
* Fix a bug where global Sass functions whose names overlap with CSS math

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.16-dev
2+
3+
* Add support for parsing null literals.
4+
15
## 0.4.15
26

37
* Add support for parsing list expressions.

pkg/sass-parser/lib/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ export {
9898
MapExpressionRaws,
9999
NewNodeForMapExpression,
100100
} from './src/expression/map';
101+
export {
102+
NullExpression,
103+
NullExpressionProps,
104+
NullExpressionRaws,
105+
} from './src/expression/null';
101106
export {
102107
NumberExpression,
103108
NumberExpressionProps,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`a null expression toJSON 1`] = `
4+
{
5+
"inputs": [
6+
{
7+
"css": "@#{null}",
8+
"hasBOM": false,
9+
"id": "<input css _____>",
10+
},
11+
],
12+
"raws": {},
13+
"sassType": "null",
14+
"source": <1:4-1:8 in 0>,
15+
}
16+
`;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {FunctionExpression} from './function';
1313
import {InterpolatedFunctionExpression} from './interpolated-function';
1414
import {ListExpression} from './list';
1515
import {MapExpression} from './map';
16+
import {NullExpression} from './null';
1617
import {NumberExpression} from './number';
1718
import {StringExpression} from './string';
1819

@@ -33,6 +34,7 @@ const visitor = sassInternal.createExpressionVisitor<Expression>({
3334
new InterpolatedFunctionExpression(undefined, inner),
3435
visitListExpression: inner => new ListExpression(undefined, inner),
3536
visitMapExpression: inner => new MapExpression(undefined, inner),
37+
visitNullExpression: inner => new NullExpression(undefined, inner),
3638
visitNumberExpression: inner => new NumberExpression(undefined, inner),
3739
});
3840

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {FunctionExpression, FunctionExpressionProps} from './function';
1111
import {InterpolatedFunctionExpression} from './interpolated-function';
1212
import {ListExpression} from './list';
1313
import {MapExpression} from './map';
14+
import {NullExpression} from './null';
1415
import {NumberExpression} from './number';
1516
import {StringExpression} from './string';
1617

@@ -28,6 +29,7 @@ export function fromProps(props: ExpressionProps): Expression {
2829
}
2930
}
3031
if ('value' in props) {
32+
if (props.value === null) return new NullExpression();
3133
if (typeof props.value === 'boolean') return new BooleanExpression(props);
3234
if (typeof props.value === 'number') return new NumberExpression(props);
3335
if (props.value instanceof sass.SassColor) {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from './interpolated-function';
1717
import {ListExpression, ListExpressionProps} from './list';
1818
import {MapExpression, MapExpressionProps} from './map';
19+
import {NullExpression, NullExpressionProps} from './null';
1920
import {NumberExpression, NumberExpressionProps} from './number';
2021
import type {StringExpression, StringExpressionProps} from './string';
2122

@@ -32,6 +33,7 @@ export type AnyExpression =
3233
| InterpolatedFunctionExpression
3334
| ListExpression
3435
| MapExpression
36+
| NullExpression
3537
| NumberExpression
3638
| StringExpression;
3739

@@ -48,6 +50,7 @@ export type ExpressionType =
4850
| 'interpolated-function-call'
4951
| 'list'
5052
| 'map'
53+
| 'null'
5154
| 'number'
5255
| 'string';
5356

@@ -65,6 +68,7 @@ export type ExpressionProps =
6568
| InterpolatedFunctionExpressionProps
6669
| ListExpressionProps
6770
| MapExpressionProps
71+
| NullExpressionProps
6872
| NumberExpressionProps
6973
| StringExpressionProps;
7074

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 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 {NullExpression} from '../..';
6+
import * as utils from '../../../test/utils';
7+
8+
describe('a null expression', () => {
9+
let node: NullExpression;
10+
11+
function describeNode(
12+
description: string,
13+
create: () => NullExpression,
14+
): void {
15+
describe(description, () => {
16+
beforeEach(() => void (node = create()));
17+
18+
it('has sassType null', () => expect(node.sassType).toBe('null'));
19+
20+
it('has value null', () => expect(node.value).toBe(null));
21+
});
22+
}
23+
24+
describeNode('parsed', () => utils.parseExpression('null'));
25+
26+
describe('constructed manually', () => {
27+
describeNode('without props', () => new NullExpression());
28+
29+
describeNode('with empty props', () => new NullExpression({}));
30+
31+
describeNode('with value: null', () => new NullExpression({value: null}));
32+
});
33+
34+
describeNode('constructed from ExpressionProps', () =>
35+
utils.fromExpressionProps({value: null}),
36+
);
37+
38+
it('stringifies', () => expect(new NullExpression().toString()).toBe('null'));
39+
40+
describe('clone', () => {
41+
let original: NullExpression;
42+
43+
beforeEach(() => void (original = utils.parseExpression('null')));
44+
45+
describe('with no overrides', () => {
46+
let clone: NullExpression;
47+
48+
beforeEach(() => void (clone = original.clone()));
49+
50+
describe('has the same properties:', () => {
51+
it('value', () => expect(clone.value).toBe(null));
52+
53+
it('raws', () => expect(clone.raws).toEqual({}));
54+
55+
it('source', () => expect(clone.source).toBe(original.source));
56+
});
57+
58+
it('creates a new self', () => expect(clone).not.toBe(original));
59+
});
60+
61+
describe('overrides', () => {
62+
describe('raws', () => {
63+
it('defined', () =>
64+
expect(original.clone({raws: {}}).raws).toEqual({}));
65+
66+
it('undefined', () =>
67+
expect(original.clone({raws: undefined}).raws).toEqual({}));
68+
});
69+
});
70+
});
71+
72+
it('toJSON', () => expect(utils.parseExpression('null')).toMatchSnapshot());
73+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 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 {NodeProps} from '../node';
9+
import type * as sassInternal from '../sass-internal';
10+
import * as utils from '../utils';
11+
import {Expression} from '.';
12+
13+
/**
14+
* The initializer properties for {@link NullExpression}.
15+
*
16+
* @category Expression
17+
*/
18+
export interface NullExpressionProps extends NodeProps {
19+
value: null;
20+
raws?: NullExpressionRaws;
21+
}
22+
23+
/**
24+
* Raws indicating how to precisely serialize a {@link NullExpression}.
25+
*
26+
* @category Expression
27+
*/
28+
// eslint-disable-next-line @typescript-eslint/no-empty-interface -- No raws for a boolean expression yet.
29+
export interface NullExpressionRaws {}
30+
31+
/**
32+
* An expression representing a null literal in Sass.
33+
*
34+
* @category Expression
35+
*/
36+
export class NullExpression extends Expression {
37+
readonly sassType = 'null' as const;
38+
declare raws: NullExpressionRaws;
39+
40+
/**
41+
* The value of this expression. Always null.
42+
*
43+
* This is only present for consistency with other literal types.
44+
*/
45+
get value(): null {
46+
return null;
47+
}
48+
set value(value: null) {
49+
// Do nothing; value is already null. This is only necessary so that we can
50+
// have `value: null` in `NullExpressionProps` for consistency with other
51+
// literal types.
52+
}
53+
54+
constructor(defaults?: Partial<NullExpressionProps>);
55+
/** @hidden */
56+
constructor(_: undefined, inner: sassInternal.NullExpression);
57+
constructor(defaults?: object, inner?: sassInternal.NullExpression) {
58+
super(defaults);
59+
if (inner) this.source = new LazySource(inner);
60+
}
61+
62+
clone(overrides?: Partial<NullExpressionProps>): this {
63+
return utils.cloneNode(this, overrides, ['raws']);
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, [], inputs);
71+
}
72+
73+
/** @hidden */
74+
toString(): string {
75+
return 'null';
76+
}
77+
78+
/** @hidden */
79+
get nonStatementChildren(): ReadonlyArray<Expression> {
80+
return [];
81+
}
82+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ declare namespace SassInternal {
372372
readonly value: sass.SassColor;
373373
}
374374

375+
class NullExpression extends Expression {}
376+
375377
class NumberExpression extends Expression {
376378
readonly value: number;
377379
readonly unit: string;
@@ -434,6 +436,7 @@ export type MapExpression = SassInternal.MapExpression;
434436
export type StringExpression = SassInternal.StringExpression;
435437
export type BooleanExpression = SassInternal.BooleanExpression;
436438
export type ColorExpression = SassInternal.ColorExpression;
439+
export type NullExpression = SassInternal.NullExpression;
437440
export type NumberExpression = SassInternal.NumberExpression;
438441

439442
export interface StatementVisitorObject<T> {
@@ -474,6 +477,7 @@ export interface ExpressionVisitorObject<T> {
474477
visitInterpolatedFunctionExpression(node: InterpolatedFunctionExpression): T;
475478
visitListExpression(node: ListExpression): T;
476479
visitMapExpression(node: MapExpression): T;
480+
visitNullExpression(node: NullExpression): T;
477481
visitNumberExpression(node: NumberExpression): T;
478482
}
479483

0 commit comments

Comments
 (0)
0