Skip to content

Commit

Permalink
Explicitly annotate GenericAtRule as having optional nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Dec 12, 2024
1 parent f38dbb0 commit bf50fad
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 172 deletions.
364 changes: 194 additions & 170 deletions pkg/sass-parser/lib/src/statement/container.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,183 +6,207 @@ import * as postcss from 'postcss';

import {GenericAtRule, Root, Rule} from '../..';

let root: Root;
describe('a container node', () => {
beforeEach(() => {
root = new Root();
});

describe('can add', () => {
it('a single Sass node', () => {
const rule = new Rule({selector: '.foo'});
root.append(rule);
expect(root.nodes).toEqual([rule]);
expect(rule.parent).toBe(root);
});

it('a list of Sass nodes', () => {
const rule1 = new Rule({selector: '.foo'});
const rule2 = new Rule({selector: '.bar'});
root.append([rule1, rule2]);
expect(root.nodes).toEqual([rule1, rule2]);
expect(rule1.parent).toBe(root);
expect(rule2.parent).toBe(root);
});

it('a Sass root node', () => {
const rule1 = new Rule({selector: '.foo'});
const rule2 = new Rule({selector: '.bar'});
const otherRoot = new Root({nodes: [rule1, rule2]});
root.append(otherRoot);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
expect(rule1.parent).toBeUndefined();
expect(rule2.parent).toBeUndefined();
describe('with nodes', () => {
let root: Root;
beforeEach(() => {
root = new Root();
});

it('a PostCSS rule node', () => {
const node = postcss.parse('.foo {}').nodes[0];
root.append(node);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[0].source).toBe(node.source);
expect(node.parent).toBeUndefined();
});

it('a PostCSS at-rule node', () => {
const node = postcss.parse('@foo bar').nodes[0];
root.append(node);
expect(root.nodes[0]).toBeInstanceOf(GenericAtRule);
expect(root.nodes[0]).toHaveInterpolation('nameInterpolation', 'foo');
expect(root.nodes[0]).toHaveInterpolation('paramsInterpolation', 'bar');
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[0].source).toBe(node.source);
expect(node.parent).toBeUndefined();
});

it('a list of PostCSS nodes', () => {
const rule1 = new postcss.Rule({selector: '.foo'});
const rule2 = new postcss.Rule({selector: '.bar'});
root.append([rule1, rule2]);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
expect(rule1.parent).toBeUndefined();
expect(rule2.parent).toBeUndefined();
});

it('a PostCSS root node', () => {
const rule1 = new postcss.Rule({selector: '.foo'});
const rule2 = new postcss.Rule({selector: '.bar'});
const otherRoot = new postcss.Root({nodes: [rule1, rule2]});
root.append(otherRoot);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
expect(rule1.parent).toBeUndefined();
expect(rule2.parent).toBeUndefined();
});

it("a single Sass node's properties", () => {
root.append({selectorInterpolation: '.foo'});
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
});

it("a single PostCSS node's properties", () => {
root.append({selector: '.foo'});
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
});

it('a list of properties', () => {
root.append(
{selectorInterpolation: '.foo'},
{selectorInterpolation: '.bar'},
);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
});

it('a plain CSS string', () => {
root.append('.foo {}');
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
describe('can add', () => {
it('a single Sass node', () => {
const rule = new Rule({selector: '.foo'});
root.append(rule);
expect(root.nodes).toEqual([rule]);
expect(rule.parent).toBe(root);
});

it('a list of Sass nodes', () => {
const rule1 = new Rule({selector: '.foo'});
const rule2 = new Rule({selector: '.bar'});
root.append([rule1, rule2]);
expect(root.nodes).toEqual([rule1, rule2]);
expect(rule1.parent).toBe(root);
expect(rule2.parent).toBe(root);
});

it('a Sass root node', () => {
const rule1 = new Rule({selector: '.foo'});
const rule2 = new Rule({selector: '.bar'});
const otherRoot = new Root({nodes: [rule1, rule2]});
root.append(otherRoot);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
expect(rule1.parent).toBeUndefined();
expect(rule2.parent).toBeUndefined();
});

it('a PostCSS rule node', () => {
const node = postcss.parse('.foo {}').nodes[0];
root.append(node);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[0].source).toBe(node.source);
expect(node.parent).toBeUndefined();
});

it('a PostCSS at-rule node', () => {
const node = postcss.parse('@foo bar').nodes[0];
root.append(node);
expect(root.nodes[0]).toBeInstanceOf(GenericAtRule);
expect(root.nodes[0]).toHaveInterpolation('nameInterpolation', 'foo');
expect(root.nodes[0]).toHaveInterpolation('paramsInterpolation', 'bar');
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[0].source).toBe(node.source);
expect(node.parent).toBeUndefined();
});

it('a list of PostCSS nodes', () => {
const rule1 = new postcss.Rule({selector: '.foo'});
const rule2 = new postcss.Rule({selector: '.bar'});
root.append([rule1, rule2]);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
expect(rule1.parent).toBeUndefined();
expect(rule2.parent).toBeUndefined();
});

it('a PostCSS root node', () => {
const rule1 = new postcss.Rule({selector: '.foo'});
const rule2 = new postcss.Rule({selector: '.bar'});
const otherRoot = new postcss.Root({nodes: [rule1, rule2]});
root.append(otherRoot);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
expect(rule1.parent).toBeUndefined();
expect(rule2.parent).toBeUndefined();
});

it("a single Sass node's properties", () => {
root.append({selectorInterpolation: '.foo'});
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
});

it("a single PostCSS node's properties", () => {
root.append({selector: '.foo'});
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
});

it('a list of properties', () => {
root.append(
{selectorInterpolation: '.foo'},
{selectorInterpolation: '.bar'},
);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
});

it('a plain CSS string', () => {
root.append('.foo {}');
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[0].parent).toBe(root);
});

it('a list of plain CSS strings', () => {
root.append(['.foo {}', '.bar {}']);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
});

it('undefined', () => {
root.append(undefined);
expect(root.nodes).toHaveLength(0);
});
});
});

it('a list of plain CSS strings', () => {
root.append(['.foo {}', '.bar {}']);
expect(root.nodes[0]).toBeInstanceOf(Rule);
expect(root.nodes[0]).toHaveInterpolation(
'selectorInterpolation',
'.foo',
);
expect(root.nodes[1]).toBeInstanceOf(Rule);
expect(root.nodes[1]).toHaveInterpolation(
'selectorInterpolation',
'.bar',
);
expect(root.nodes[0].parent).toBe(root);
expect(root.nodes[1].parent).toBe(root);
describe('without nodes', () => {
let rule: GenericAtRule;
beforeEach(() => {
rule = new GenericAtRule({name: 'foo'});
});

it('undefined', () => {
root.append(undefined);
expect(root.nodes).toHaveLength(0);
describe('can add', () => {
it('a node', () => {
rule.append('@bar');
expect(rule.nodes).not.toBeUndefined();
expect(rule.nodes![0]).toBeInstanceOf(GenericAtRule);
expect(rule.nodes![0]).toHaveInterpolation('nameInterpolation', 'bar');
});

it('undefined', () => {
rule.append(undefined);
expect(rule.nodes).not.toBeUndefined();
expect(rule.nodes).toHaveLength(0);
});
});
});
});
4 changes: 2 additions & 2 deletions pkg/sass-parser/lib/src/statement/generic-at-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class GenericAtRule
readonly sassType = 'atrule' as const;
declare parent: StatementWithChildren | undefined;
declare raws: GenericAtRuleRaws;
declare nodes: ChildNode[];
declare nodes: ChildNode[] | undefined;

get name(): string {
return this.nameInterpolation.toString();
Expand Down Expand Up @@ -207,7 +207,7 @@ export class GenericAtRule

/** @hidden */
normalize(node: NewNode, sample?: postcss.Node): ChildNode[] {
return normalize(this, node, sample);
return normalize(this as StatementWithChildren, node, sample);
}
}

Expand Down
Loading

0 comments on commit bf50fad

Please sign in to comment.