Skip to content

Commit

Permalink
Merge pull request #2439 from sass/function
Browse files Browse the repository at this point in the history
Add sass-parser support for `@function`
  • Loading branch information
nex3 authored Dec 3, 2024
2 parents 6939faa + b10ca79 commit 02fff80
Show file tree
Hide file tree
Showing 25 changed files with 2,735 additions and 164 deletions.
6 changes: 6 additions & 0 deletions lib/src/js/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ void _updateAstPrototypes() {
'accept',
(Expression self, ExpressionVisitor<Object?> visitor) =>
self.accept(visitor));
var arguments = ArgumentDeclaration([], bogusSpan);
getJSClass(arguments)
.defineGetter('arguments', (ArgumentDeclaration self) => self.arguments);
var function = FunctionRule('a', arguments, [], bogusSpan);
getJSClass(function)
.defineGetter('arguments', (FunctionRule self) => self.arguments);

_addSupportsConditionToInterpolation();

Expand Down
20 changes: 20 additions & 0 deletions pkg/sass-parser/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
ConfiguredVariableProps,
ConfiguredVariableRaws,
} from './src/configured-variable';
export {Container} from './src/container';
export {AnyNode, Node, NodeProps, NodeType} from './src/node';
export {RawWithValue} from './src/raw-with-value';
export {
Expand Down Expand Up @@ -55,6 +56,20 @@ export {
InterpolationRaws,
NewNodeForInterpolation,
} from './src/interpolation';
export {
NewParameters,
ParameterListObjectProps,
ParameterListProps,
ParameterListRaws,
ParameterList,
} from './src/parameter-list';
export {
ParameterObjectProps,
ParameterRaws,
ParameterExpressionProps,
ParameterProps,
Parameter,
} from './src/parameter';
export {
CssComment,
CssCommentProps,
Expand All @@ -79,6 +94,11 @@ export {
ForwardRuleProps,
ForwardRuleRaws,
} from './src/statement/forward-rule';
export {
FunctionRuleRaws,
FunctionRuleProps,
FunctionRule,
} from './src/statement/function-rule';
export {
GenericAtRule,
GenericAtRuleProps,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ exports[`a configured variable toJSON 1`] = `
"id": "<input css _____>",
},
],
"name": "baz",
"raws": {},
"sassType": "configured-variable",
"source": <1:18-1:29 in 0>,
"variableName": "baz",
}
`;
20 changes: 20 additions & 0 deletions pkg/sass-parser/lib/src/__snapshots__/parameter-list.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a parameter list toJSON 1`] = `
{
"inputs": [
{
"css": "@function x($foo, $bar...) {}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"nodes": [
<$foo>,
],
"raws": {},
"restParameter": "bar",
"sassType": "parameter-list",
"source": <1:12-1:27 in 0>,
}
`;
34 changes: 34 additions & 0 deletions pkg/sass-parser/lib/src/__snapshots__/parameter.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`a parameter toJSON with a default 1`] = `
{
"defaultValue": <"qux">,
"inputs": [
{
"css": "@function x($baz: "qux") {}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"name": "baz",
"raws": {},
"sassType": "parameter",
"source": <1:13-1:24 in 0>,
}
`;

exports[`a parameter toJSON with no default 1`] = `
{
"inputs": [
{
"css": "@function x($baz) {}",
"hasBOM": false,
"id": "<input css _____>",
},
],
"name": "baz",
"raws": {},
"sassType": "parameter",
"source": <1:13-1:17 in 0>,
}
`;
56 changes: 27 additions & 29 deletions pkg/sass-parser/lib/src/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('a configuration map', () => {
it('contains the variable', () => {
expect(node.size).toBe(1);
const variable = [...node.variables()][0];
expect(variable.variableName).toEqual('bar');
expect(variable.name).toEqual('bar');
expect(variable).toHaveStringExpression('expression', 'baz');
});
});
Expand All @@ -101,9 +101,7 @@ describe('a configuration map', () => {
'variables array',
() =>
new Configuration({
variables: [
{variableName: 'bar', expression: {text: 'baz', quotes: true}},
],
variables: [{name: 'bar', expression: {text: 'baz', quotes: true}}],
}),
);

Expand All @@ -127,7 +125,7 @@ describe('a configuration map', () => {
describe('add()', () => {
test('with a ConfiguredVariable', () => {
const variable = new ConfiguredVariable({
variableName: 'foo',
name: 'foo',
expression: {text: 'bar', quotes: true},
});
expect(node.add(variable)).toBe(node);
Expand All @@ -137,29 +135,29 @@ describe('a configuration map', () => {
});

test('with a ConfiguredVariableProps', () => {
node.add({variableName: 'foo', expression: {text: 'bar', quotes: true}});
node.add({name: 'foo', expression: {text: 'bar', quotes: true}});
expect(node.size).toBe(1);
const variable = node.get('foo');
expect(variable?.variableName).toBe('foo');
expect(variable?.name).toBe('foo');
expect(variable).toHaveStringExpression('expression', 'bar');
expect(variable?.parent).toBe(node);
});

test('overwrites on old variable', () => {
node.add({variableName: 'foo', expression: {text: 'old', quotes: true}});
node.add({name: 'foo', expression: {text: 'old', quotes: true}});
const old = node.get('foo');
expect(old?.parent).toBe(node);

node.add({variableName: 'foo', expression: {text: 'new', quotes: true}});
node.add({name: 'foo', expression: {text: 'new', quotes: true}});
expect(node.size).toBe(1);
expect(old?.parent).toBeUndefined();
expect(node.get('foo')).toHaveStringExpression('expression', 'new');
});
});

test('clear() removes all variables', () => {
node.add({variableName: 'foo', expression: {text: 'bar', quotes: true}});
node.add({variableName: 'baz', expression: {text: 'bang', quotes: true}});
node.add({name: 'foo', expression: {text: 'bar', quotes: true}});
node.add({name: 'baz', expression: {text: 'bang', quotes: true}});
const foo = node.get('foo');
const bar = node.get('bar');
node.clear();
Expand All @@ -172,8 +170,8 @@ describe('a configuration map', () => {

describe('delete()', () => {
beforeEach(() => {
node.add({variableName: 'foo', expression: {text: 'bar', quotes: true}});
node.add({variableName: 'baz', expression: {text: 'bang', quotes: true}});
node.add({name: 'foo', expression: {text: 'bar', quotes: true}});
node.add({name: 'baz', expression: {text: 'bang', quotes: true}});
});

test('removes a matching variable', () => {
Expand All @@ -192,12 +190,12 @@ describe('a configuration map', () => {

describe('get()', () => {
beforeEach(() => {
node.add({variableName: 'foo', expression: {text: 'bar', quotes: true}});
node.add({name: 'foo', expression: {text: 'bar', quotes: true}});
});

test('returns a variable in the configuration', () => {
const variable = node.get('foo');
expect(variable?.variableName).toBe('foo');
expect(variable?.name).toBe('foo');
expect(variable).toHaveStringExpression('expression', 'bar');
});

Expand All @@ -208,7 +206,7 @@ describe('a configuration map', () => {

describe('has()', () => {
beforeEach(() => {
node.add({variableName: 'foo', expression: {text: 'bar', quotes: true}});
node.add({name: 'foo', expression: {text: 'bar', quotes: true}});
});

test('returns true for a variable in the configuration', () =>
Expand All @@ -220,7 +218,7 @@ describe('a configuration map', () => {

describe('set()', () => {
beforeEach(() => {
node.add({variableName: 'foo', expression: {text: 'bar', quotes: true}});
node.add({name: 'foo', expression: {text: 'bar', quotes: true}});
});

describe('adds a new variable', () => {
Expand All @@ -233,7 +231,7 @@ describe('a configuration map', () => {
expect(node.size).toBe(2);
const variable = node.get('baz');
expect(variable?.parent).toBe(node);
expect(variable?.variableName).toBe('baz');
expect(variable?.name).toBe('baz');
expect(variable).toHaveStringExpression('expression', 'bang');
});
}
Expand Down Expand Up @@ -285,15 +283,15 @@ describe('a configuration map', () => {
}).toString(),
).toBe('($foo: "bar", $baz: "bang",)'));

it('with comma: true and afterValue', () =>
it('with comma: true and after', () =>
expect(
new Configuration({
raws: {comma: true},
variables: {
foo: {text: 'bar', quotes: true},
baz: {
expression: {text: 'bang', quotes: true},
raws: {afterValue: '/**/'},
raws: {after: '/**/'},
},
},
}).toString(),
Expand All @@ -310,28 +308,28 @@ describe('a configuration map', () => {
}).toString(),
).toBe('($foo: "bar", $baz: "bang"/**/)'));

it('with after and afterValue', () =>
it('with after and after', () =>
expect(
new Configuration({
raws: {after: '/**/'},
variables: {
foo: {text: 'bar', quotes: true},
baz: {
expression: {text: 'bang', quotes: true},
raws: {afterValue: ' '},
raws: {after: ' '},
},
},
}).toString(),
).toBe('($foo: "bar", $baz: "bang" /**/)'));

it('with afterValue and a guard', () =>
it('with after and a guard', () =>
expect(
new Configuration({
variables: {
foo: {text: 'bar', quotes: true},
baz: {
expression: {text: 'bang', quotes: true},
raws: {afterValue: '/**/'},
raws: {after: '/**/'},
guarded: true,
},
},
Expand Down Expand Up @@ -359,10 +357,10 @@ describe('a configuration map', () => {
it('variables', () => {
expect(clone.size).toBe(2);
const variables = [...clone.variables()];
expect(variables[0]?.variableName).toBe('foo');
expect(variables[0]?.name).toBe('foo');
expect(variables[0]?.parent).toBe(clone);
expect(variables[0]).toHaveStringExpression('expression', 'bar');
expect(variables[1]?.variableName).toBe('baz');
expect(variables[1]?.name).toBe('baz');
expect(variables[1]?.parent).toBe(clone);
expect(variables[1]).toHaveStringExpression('expression', 'bang');
});
Expand Down Expand Up @@ -399,7 +397,7 @@ describe('a configuration map', () => {
});
expect(clone.size).toBe(1);
const variables = [...clone.variables()];
expect(variables[0]?.variableName).toBe('zip');
expect(variables[0]?.name).toBe('zip');
expect(variables[0]?.parent).toBe(clone);
expect(variables[0]).toHaveStringExpression('expression', 'zap');
});
Expand All @@ -408,10 +406,10 @@ describe('a configuration map', () => {
const clone = original.clone({variables: undefined});
expect(clone.size).toBe(2);
const variables = [...clone.variables()];
expect(variables[0]?.variableName).toBe('foo');
expect(variables[0]?.name).toBe('foo');
expect(variables[0]?.parent).toBe(clone);
expect(variables[0]).toHaveStringExpression('expression', 'bar');
expect(variables[1]?.variableName).toBe('baz');
expect(variables[1]?.name).toBe('baz');
expect(variables[1]?.parent).toBe(clone);
expect(variables[1]).toHaveStringExpression('expression', 'bang');
});
Expand Down
9 changes: 6 additions & 3 deletions pkg/sass-parser/lib/src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export interface ConfigurationProps {
| Array<ConfiguredVariable | ConfiguredVariableProps>;
}

// TODO: This should probably implement a similar interface to `ParameterList`
// as well as or instead of its current map-like interface.

/**
* A configuration map for a `@use` or `@forward` rule.
*
Expand Down Expand Up @@ -101,9 +104,9 @@ export class Configuration extends Node {
const realVariable =
'sassType' in variable ? variable : new ConfiguredVariable(variable);
realVariable.parent = this;
const old = this._variables.get(realVariable.variableName);
const old = this._variables.get(realVariable.name);
if (old) old.parent = undefined;
this._variables.set(realVariable.variableName, realVariable);
this._variables.set(realVariable.name, realVariable);
return this;
}

Expand Down Expand Up @@ -189,7 +192,7 @@ export class Configuration extends Node {
result += variable.raws.before ?? ' ';
}
result += variable.toString();
result += variable.raws.afterValue ?? '';
result += variable.raws.after ?? '';
}
return result + `${this.raws.comma ? ',' : ''}${this.raws.after ?? ''})`;
}
Expand Down
Loading

0 comments on commit 02fff80

Please sign in to comment.