Skip to content

Commit

Permalink
Add support for @extend rules
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Sep 4, 2024
1 parent 3544cef commit b60577e
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 7 deletions.
5 changes: 2 additions & 3 deletions pkg/sass-parser/lib/src/interpolation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,8 @@ export class Interpolation extends Node {
*/
get asPlain(): string | null {
if (this.nodes.length === 0) return '';
if (this.nodes.length !== 1) return null;
if (typeof this.nodes[0] !== 'string') return null;
return this.nodes[0] as string;
if (this.nodes.some(node => typeof node !== 'string')) return null;
return this.nodes.join('');
}

/**
Expand Down
7 changes: 7 additions & 0 deletions pkg/sass-parser/lib/src/sass-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ declare namespace SassInternal {
readonly expression: Expression;
}

class ExtendRule extends Statement {
readonly selector: Interpolation;
readonly isOptional: boolean;
}

class Stylesheet extends ParentStatement<Statement[]> {}

class StyleRule extends ParentStatement<Statement[]> {
Expand Down Expand Up @@ -129,6 +134,7 @@ export type AtRule = SassInternal.AtRule;
export type DebugRule = SassInternal.DebugRule;
export type EachRule = SassInternal.EachRule;
export type ErrorRule = SassInternal.ErrorRule;
export type ExtendRule = SassInternal.ExtendRule;
export type Stylesheet = SassInternal.Stylesheet;
export type StyleRule = SassInternal.StyleRule;
export type Interpolation = SassInternal.Interpolation;
Expand All @@ -142,6 +148,7 @@ export interface StatementVisitorObject<T> {
visitDebugRule(node: DebugRule): T;
visitEachRule(node: EachRule): T;
visitErrorRule(node: ErrorRule): T;
visitExtendRule(node: ExtendRule): T;
visitStyleRule(node: StyleRule): T;
}

Expand Down
8 changes: 4 additions & 4 deletions pkg/sass-parser/lib/src/statement/at-root-rule.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('an @at-root rule', () => {
() => void (node = scss.parse('@at-root {}').nodes[0] as GenericAtRule)
);

it('has a name', () => expect(node.name.toString()).toBe('at-root'));
it('has a name', () => expect(node.name).toBe('at-root'));

it('has no paramsInterpolation', () =>
expect(node.paramsInterpolation).toBeUndefined());
Expand All @@ -27,7 +27,7 @@ describe('an @at-root rule', () => {
.nodes[0] as GenericAtRule)
);

it('has a name', () => expect(node.name.toString()).toBe('at-root'));
it('has a name', () => expect(node.name).toBe('at-root'));

it('has a paramsInterpolation', () =>
expect(node).toHaveInterpolation('paramsInterpolation', '(with: rule)'));
Expand All @@ -44,7 +44,7 @@ describe('an @at-root rule', () => {
.nodes[0] as GenericAtRule)
);

it('has a name', () => expect(node.name.toString()).toBe('at-root'));
it('has a name', () => expect(node.name).toBe('at-root'));

it('has a paramsInterpolation', () => {
const params = node.paramsInterpolation!;
Expand All @@ -63,7 +63,7 @@ describe('an @at-root rule', () => {
void (node = scss.parse('@at-root .foo {}').nodes[0] as GenericAtRule)
);

it('has a name', () => expect(node.name.toString()).toBe('at-root'));
it('has a name', () => expect(node.name).toBe('at-root'));

it('has no paramsInterpolation', () =>
expect(node.paramsInterpolation).toBeUndefined());
Expand Down
61 changes: 61 additions & 0 deletions pkg/sass-parser/lib/src/statement/extend-rule.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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 {GenericAtRule, Rule, scss} from '../..';

describe('an @extend rule', () => {
let node: GenericAtRule;

describe('with no interpolation', () => {
beforeEach(
() =>
void (node = (scss.parse('.foo {@extend .bar}').nodes[0] as Rule)
.nodes[0] as GenericAtRule)
);

it('has a name', () => expect(node.name).toBe('extend'));

it('has a paramsInterpolation', () =>
expect(node).toHaveInterpolation('paramsInterpolation', '.bar'));

it('has matching params', () => expect(node.params).toBe('.bar'));
});

describe('with interpolation', () => {
beforeEach(
() =>
void (node = (scss.parse('.foo {@extend .#{bar}}').nodes[0] as Rule)
.nodes[0] as GenericAtRule)
);

it('has a name', () => expect(node.name).toBe('extend'));

it('has a paramsInterpolation', () => {
const params = node.paramsInterpolation!;
expect(params.nodes[0]).toBe('.');
expect(params).toHaveStringExpression(1, 'bar');
});

it('has matching params', () => expect(node.params).toBe('.#{bar}'));
});

describe('with !optional', () => {
beforeEach(
() =>
void (node = (
scss.parse('.foo {@extend .bar !optional}').nodes[0] as Rule
).nodes[0] as GenericAtRule)
);

it('has a name', () => expect(node.name).toBe('extend'));

it('has a paramsInterpolation', () =>
expect(node).toHaveInterpolation(
'paramsInterpolation',
'.bar !optional'
));

it('has matching params', () => expect(node.params).toBe('.bar !optional'));
});
});
9 changes: 9 additions & 0 deletions pkg/sass-parser/lib/src/statement/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ const visitor = sassInternal.createStatementVisitor<Statement>({
visitDebugRule: inner => new DebugRule(undefined, inner),
visitErrorRule: inner => new ErrorRule(undefined, inner),
visitEachRule: inner => new EachRule(undefined, inner),
visitExtendRule: inner => {
const paramsInterpolation = new Interpolation(undefined, inner.selector);
if (inner.isOptional) paramsInterpolation.append('!optional');
return new GenericAtRule({
name: 'extend',
paramsInterpolation,
source: new LazySource(inner),
});
},
visitStyleRule: inner => new Rule(undefined, inner),
});

Expand Down

0 comments on commit b60577e

Please sign in to comment.