Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BooleanExpression and NumberExpression to sass-parser #2376

Merged
merged 12 commits into from
Oct 18, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.80.4

* No user-visible changes.

## 1.80.3

* Fix a bug where `@import url("...")` would crash in plain CSS files.
Expand Down
4 changes: 4 additions & 0 deletions pkg/sass-parser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.4.1

* Add `BooleanExpression` and `NumberExpression`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can update this to 0.2.7 if this version has already come out

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I just cut a release for it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updating all three in tandem can be confusing 😅 - I wonder if we could find a way to more easily update these


## 0.4.0

* **Breaking change:** Warnings are no longer emitted during parsing, so the
Expand Down
14 changes: 14 additions & 0 deletions pkg/sass-parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,17 @@ There are a few cases where an operation that's valid in PostCSS won't work with

* Trying to add child nodes to a Sass statement that doesn't support children
like `@use` or `@error` is not supported.

## Contributing

Before sending out a pull request, please run the following commands from the
`pkg/sass-parser` directory:

* `npm run check` - Runs `eslint`, and then tries to compile the package with
`tsc`.

* `npm run test` - Runs all the tests in the package.

Note: You should run `dart run grinder before-test` from the `dart-sass`
directory beforehand to ensure you're running `sass-parser` against the latest
version of `dart-sass` JavaScript API.
11 changes: 10 additions & 1 deletion pkg/sass-parser/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// https://opensource.org/licenses/MIT.

import * as postcss from 'postcss';
import * as sassApi from 'sass';

import {Root} from './src/statement/root';
import * as sassInternal from './src/sass-internal';
Expand All @@ -27,6 +26,16 @@ export {
StringExpressionProps,
StringExpressionRaws,
} from './src/expression/string';
export {
BooleanExpression,
BooleanExpressionProps,
BooleanExpressionRaws,
} from './src/expression/boolean';
export {
NumberExpression,
NumberExpressionProps,
NumberExpressionRaws,
} from './src/expression/number';
export {
Interpolation,
InterpolationProps,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a boolean expression toJSON 1`] = `
{
"inputs": [
{
"css": "@#{true}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"raws": {},
"sassType": "boolean",
"source": <1:4-1:8 in 0>,
"value": true,
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a number expression toJSON 1`] = `
{
"inputs": [
{
"css": "@#{123%}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"raws": {},
"sassType": "number",
"source": <1:4-1:8 in 0>,
"unit": "%",
"value": 123,
}
`;
122 changes: 122 additions & 0 deletions pkg/sass-parser/lib/src/expression/boolean.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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 {BooleanExpression} from '../..';
import * as utils from '../../../test/utils';

describe('a boolean expression', () => {
let node: BooleanExpression;

describe('true', () => {
function describeNode(
description: string,
create: () => BooleanExpression
): void {
describe(description, () => {
beforeEach(() => void (node = create()));

it('has sassType boolean', () => expect(node.sassType).toBe('boolean'));

it('is true', () => expect(node.value).toBe(true));
});
}

describeNode('parsed', () => utils.parseExpression('true'));

describeNode(
'constructed manually',
() => new BooleanExpression({value: true})
);

describeNode('constructed from ExpressionProps', () =>
utils.fromExpressionProps({value: true})
);
});

describe('false', () => {
function describeNode(
description: string,
create: () => BooleanExpression
): void {
describe(description, () => {
beforeEach(() => void (node = create()));

it('has sassType boolean', () => expect(node.sassType).toBe('boolean'));

it('is false', () => expect(node.value).toBe(false));
});
}

describeNode('parsed', () => utils.parseExpression('false'));

describeNode(
'constructed manually',
() => new BooleanExpression({value: false})
);

describeNode('constructed from ExpressionProps', () =>
utils.fromExpressionProps({value: false})
);
});

it('assigned new value', () => {
node = utils.parseExpression('true');
node.value = false;
expect(node.value).toBe(false);
});

describe('stringifies', () => {
it('true', () => {
expect(utils.parseExpression('true').toString()).toBe('true');
});

it('false', () => {
expect(utils.parseExpression('false').toString()).toBe('false');
});
});

describe('clone', () => {
let original: BooleanExpression;

beforeEach(() => {
original = utils.parseExpression('true');
});

describe('with no overrides', () => {
let clone: BooleanExpression;

beforeEach(() => void (clone = original.clone()));

describe('has the same properties:', () => {
it('value', () => expect(clone.value).toBe(true));

it('raws', () => expect(clone.raws).toEqual({}));

it('source', () => expect(clone.source).toBe(original.source));
});

it('creates a new self', () => expect(clone).not.toBe(original));
});

describe('overrides', () => {
describe('value', () => {
it('defined', () =>
expect(original.clone({value: false}).value).toBe(false));

it('undefined', () =>
expect(original.clone({value: undefined}).value).toBe(true));
});

describe('raws', () => {
it('defined', () =>
expect(original.clone({raws: {}}).raws).toEqual({}));

it('undefined', () =>
expect(original.clone({raws: undefined}).raws).toEqual({}));
});
});
});

it('toJSON', () => expect(utils.parseExpression('true')).toMatchSnapshot());
});
82 changes: 82 additions & 0 deletions pkg/sass-parser/lib/src/expression/boolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// 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 {LazySource} from '../lazy-source';
import type * as sassInternal from '../sass-internal';
import * as utils from '../utils';
import {Expression} from '.';

/**
* The initializer properties for {@link BooleanExpression}.
*
* @category Expression
*/
export interface BooleanExpressionProps {
value: boolean;
raws?: BooleanExpressionRaws;
}

/**
* Raws indicating how to precisely serialize a {@link BooleanExpression}.
*
* @category Expression
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface -- No raws for a boolean expression yet.
export interface BooleanExpressionRaws {}

/**
* An expression representing a boolean literal in Sass.
*
* @category Expression
*/
export class BooleanExpression extends Expression {
readonly sassType = 'boolean' as const;
declare raws: BooleanExpressionRaws;

/** The boolean value of this expression. */
get value(): boolean {
return this._value;
}
set value(value: boolean) {
// TODO - postcss/postcss#1957: Mark this as dirty
this._value = value;
}
private _value!: boolean;

constructor(defaults: BooleanExpressionProps);
/** @hidden */
constructor(_: undefined, inner: sassInternal.BooleanExpression);
constructor(defaults?: object, inner?: sassInternal.BooleanExpression) {
super(defaults);
if (inner) {
this.source = new LazySource(inner);
this.value = inner.value;
} else {
this.value ??= false;
}
}

clone(overrides?: Partial<BooleanExpressionProps>): this {
return utils.cloneNode(this, overrides, ['raws', 'value']);
}

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

/** @hidden */
toString(): string {
return this.value ? 'true' : 'false';
}

/** @hidden */
get nonStatementChildren(): ReadonlyArray<Expression> {
return [];
}
}
4 changes: 4 additions & 0 deletions pkg/sass-parser/lib/src/expression/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import * as sassInternal from '../sass-internal';
import {BinaryOperationExpression} from './binary-operation';
import {StringExpression} from './string';
import {Expression} from '.';
import {BooleanExpression} from './boolean';
import {NumberExpression} from './number';

/** The visitor to use to convert internal Sass nodes to JS. */
const visitor = sassInternal.createExpressionVisitor<Expression>({
visitBinaryOperationExpression: inner =>
new BinaryOperationExpression(undefined, inner),
visitStringExpression: inner => new StringExpression(undefined, inner),
visitBooleanExpression: inner => new BooleanExpression(undefined, inner),
visitNumberExpression: inner => new NumberExpression(undefined, inner),
});

/** Converts an internal expression AST node into an external one. */
Expand Down
7 changes: 7 additions & 0 deletions pkg/sass-parser/lib/src/expression/from-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,17 @@
import {BinaryOperationExpression} from './binary-operation';
import {Expression, ExpressionProps} from '.';
import {StringExpression} from './string';
import {BooleanExpression} from './boolean';
import {NumberExpression} from './number';

/** Constructs an expression from {@link ExpressionProps}. */
export function fromProps(props: ExpressionProps): Expression {
if ('text' in props) return new StringExpression(props);
if ('left' in props) return new BinaryOperationExpression(props);
if ('value' in props) {
if (typeof props.value === 'boolean') return new BooleanExpression(props);
if (typeof props.value === 'number') return new NumberExpression(props);
}

throw new Error(`Unknown node type: ${props}`);
}
18 changes: 15 additions & 3 deletions pkg/sass-parser/lib/src/expression/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,31 @@ import type {
BinaryOperationExpression,
BinaryOperationExpressionProps,
} from './binary-operation';
import {BooleanExpression, BooleanExpressionProps} from './boolean';
import {NumberExpression, NumberExpressionProps} from './number';
import type {StringExpression, StringExpressionProps} from './string';

/**
* The union type of all Sass expressions.
*
* @category Expression
*/
export type AnyExpression = BinaryOperationExpression | StringExpression;
export type AnyExpression =
| BinaryOperationExpression
| StringExpression
| BooleanExpression
| NumberExpression;

/**
* Sass expression types.
*
* @category Expression
*/
export type ExpressionType = 'binary-operation' | 'string';
export type ExpressionType =
| 'binary-operation'
| 'string'
| 'boolean'
| 'number';

/**
* The union type of all properties that can be used to construct Sass
Expand All @@ -31,7 +41,9 @@ export type ExpressionType = 'binary-operation' | 'string';
*/
export type ExpressionProps =
| BinaryOperationExpressionProps
| StringExpressionProps;
| StringExpressionProps
| BooleanExpressionProps
| NumberExpressionProps;

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