123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- import * as test from 'tape';
-
- import * as AST from '../ast';
- import Lexer, { LexerError, LexerResult } from '../lexer';
- import Parser, { ParserError, ParserResult } from '../parser';
- import Token, { TokenTypes } from '../token';
-
- const parse = (source: string) => {
- const lexerResult: LexerResult = new Lexer(source).scan();
- if (!(lexerResult instanceof LexerError)) {
- const parserResult: ParserResult = new Parser(lexerResult).parse();
- return parserResult;
- }
-
- return lexerResult;
- };
-
- const literalToken = (value: string): Token => {
- return new Token(TokenTypes.LITERAL, value, 1);
- };
-
- const propertyToken = (value: string): Token => {
- return new Token(TokenTypes.PROPERTY, value, 1);
- };
-
- const identifierToken = (value: string): Token => {
- return new Token(TokenTypes.IDENTIFIER, value, 1);
- };
-
- const literalNode = (value: string): AST.Literal => {
- return new AST.Literal(literalToken(value));
- };
-
- const identifierNode = (value: string): AST.Identifier => {
- return new AST.Identifier(identifierToken(value));
- };
-
- const property = (value: string): AST.Property => {
- const token = new Token(TokenTypes.PROPERTY, value, 1);
- return new AST.Property(token);
- };
-
- const functionName = (value: string): AST.Literal => {
- const token = new Token(TokenTypes.FUNCTION_NAME, value, 1);
- return new AST.Literal(token);
- };
-
- test('parses a literal', (t) => {
- t.plan(2);
- const result = parse('#FF0000');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [literalNode('#FF0000')]);
- }
- });
-
- test('parses a identifier', (t) => {
- t.plan(2);
- const result = parse('$blue');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [identifierNode('blue')]);
- }
- });
-
- test('parses an expression', (t) => {
- t.plan(2);
- const result = parse('(.para :color black)');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.RuleSet(
- [new AST.Selector(literalToken('.para'))],
- [new AST.Rule(property('color'), [literalNode('black')])]
- ),
- ]);
- }
- });
-
- test('parses an expression with multiple selectors', (t) => {
- t.plan(2);
- const result = parse('(.header, .footer :color black)');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.RuleSet(
- [
- new AST.Selector(literalToken('.header')),
- new AST.Selector(literalToken('.footer')),
- ],
- [new AST.Rule(property('color'), [literalNode('black')])]
- ),
- ]);
- }
- });
-
- test('parses an expression with children', (t) => {
- t.plan(2);
- const result = parse(
- '(body :color black (div :color blue (span :color red)))'
- );
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.RuleSet(
- [new AST.Selector(literalToken('body'))],
- [
- new AST.Rule(property('color'), [literalNode('black')]),
- new AST.RuleSet(
- [
- new AST.Selector(literalToken('div'), [
- new AST.Selector(literalToken('body')),
- ]),
- ],
- [
- new AST.Rule(property('color'), [literalNode('blue')]),
- new AST.RuleSet(
- [
- new AST.Selector(literalToken('span'), [
- new AST.Selector(literalToken('div'), [
- new AST.Selector(literalToken('body')),
- ]),
- ]),
- ],
- [new AST.Rule(property('color'), [literalNode('red')])]
- ),
- ]
- ),
- ]
- ),
- ]);
- }
- });
-
- test('parses a `let` call', (t) => {
- t.plan(2);
- const result = parse('(@let $blue #0000FF $red #FF0000)');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.Let(
- [
- new AST.Binding(identifierNode('blue'), literalNode('#0000FF')),
- new AST.Binding(identifierNode('red'), literalNode('#FF0000')),
- ],
- []
- ),
- ]);
- }
- });
-
- test('parses a `let` call with body', (t) => {
- t.plan(2);
- const result = parse(
- '(@let $blue #0000FF $red #FF0000 (div :background $blue :color $red))'
- );
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.Let(
- [
- new AST.Binding(identifierNode('blue'), literalNode('#0000FF')),
- new AST.Binding(identifierNode('red'), literalNode('#FF0000')),
- ],
- [
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [
- new AST.Rule(property('background'), [identifierNode('blue')]),
- new AST.Rule(property('color'), [identifierNode('red')]),
- ]
- ),
- ]
- ),
- ]);
- }
- });
-
- test('parses multiple rulesets in a let block', (t) => {
- t.plan(2);
- const result = parse(
- '(@let $blue #0000FF $red #FF0000 (div :background $blue) (span :color $red))'
- );
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.Let(
- [
- new AST.Binding(identifierNode('blue'), literalNode('#0000FF')),
- new AST.Binding(identifierNode('red'), literalNode('#FF0000')),
- ],
- [
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [new AST.Rule(property('background'), [identifierNode('blue')])]
- ),
- new AST.RuleSet(
- [new AST.Selector(literalToken('span'))],
- [new AST.Rule(property('color'), [identifierNode('red')])]
- ),
- ]
- ),
- ]);
- }
- });
-
- test('parses media query', (t) => {
- t.plan(2);
- const result = parse('(@media :min-width 1000px (div :flex-direction row))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.MediaQuery(
- new AST.MediaQueryPredicate(
- property('min-width'),
- literalNode('1000px')
- ),
- [
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [new AST.Rule(property('flex-direction'), [literalNode('row')])]
- ),
- ]
- ),
- ]);
- }
- });
-
- test('parses keyframes', (t) => {
- t.plan(2);
- const result = parse('(@keyframes fade (0% :opacity 1) (100% :opacity 0))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.Keyframes(literalNode('fade'), [
- new AST.RuleSet(
- [new AST.Selector(literalToken('0%'))],
- [new AST.Rule(property('opacity'), [literalNode('1')])]
- ),
- new AST.RuleSet(
- [new AST.Selector(literalToken('100%'))],
- [new AST.Rule(property('opacity'), [literalNode('0')])]
- ),
- ]),
- ]);
- }
- });
-
- test('parses function call', (t) => {
- t.plan(2);
- const result = parse('(@rgba 255 0 0 0)');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.Application(functionName('rgba'), [
- literalNode('255'),
- literalNode('0'),
- literalNode('0'),
- literalNode('0'),
- ]),
- ]);
- }
- });
-
- test('parses mixin', (t) => {
- t.plan(2);
- const result = parse(
- '(@mixin centered ($width) :max-width $width :margin auto)'
- );
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(result.tree, [
- new AST.Mixin(
- literalToken('centered'),
- [identifierNode('width')],
- [
- new AST.Rule(
- new AST.Property(propertyToken('max-width')),
- [identifierNode('width')]
- ),
- new AST.Rule(
- new AST.Property(propertyToken('margin')),
- [literalNode('auto')]
- ),
- ]
- ),
- ]);
- }
- });
-
- test('allow rules and children in any order', (t) => {
- t.plan(2);
- const result = parse('(div (span :color blue) :color green)');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [
- new AST.RuleSet(
- [
- new AST.Selector(literalToken('span'), [
- new AST.Selector(literalToken('div')),
- ]),
- ],
- [new AST.Rule(property('color'), [literalNode('blue')])]
- ),
- new AST.Rule(property('color'), [literalNode('green')]),
- ]
- )
- );
- }
- });
-
- test('combine rules and children', (t) => {
- t.plan(2);
- const result = parse('(div :color green (span :color blue))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [
- new AST.Rule(property('color'), [literalNode('green')]),
- new AST.RuleSet(
- [
- new AST.Selector(literalToken('span'), [
- new AST.Selector(literalToken('div')),
- ]),
- ],
- [new AST.Rule(property('color'), [literalNode('blue')])]
- ),
- ]
- )
- );
- }
- });
-
- test('parse mixin application', (t) => {
- t.plan(2);
- const result = parse('(div (@border #000000))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [new AST.Application(functionName('border'), [literalNode('#000000')])]
- )
- );
- }
- });
-
- test('parse mixin with children', (t) => {
- t.plan(2);
- const result = parse('(@mixin m () (div :color blue))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.Mixin(
- literalToken('m'),
- [],
- [
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [new AST.Rule(property('color'), [literalNode('blue')])]
- ),
- ]
- )
- );
- }
- });
-
- test('media query as child', (t) => {
- t.plan(2);
- const result = parse('(div (@media :min-width 1000px (& :color blue)))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [
- new AST.MediaQuery(
- new AST.MediaQueryPredicate(
- property('min-width'),
- literalNode('1000px')
- ),
- [
- new AST.RuleSet(
- [new AST.Selector(literalToken('&'))],
- [new AST.Rule(property('color'), [literalNode('blue')])]
- ),
- ]
- ),
- ]
- )
- );
- }
- });
-
- test('mixin as child', (t) => {
- t.plan(2);
- const result = parse('(div (@mixin m () (div :color blue)))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [
- new AST.Mixin(
- literalToken('m'),
- [],
- [
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [new AST.Rule(property('color'), [literalNode('blue')])]
- ),
- ]
- )
- ]
- )
- );
- }
- });
-
- test('define a mixin inside another mixin', (t) => {
- t.plan(2);
- const result = parse('(@mixin outer ($x) (@mixin inner ($y) (div :color $y)))');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.Mixin(
- literalToken('outer'),
- [identifierNode('x')],
- [
- new AST.Mixin(
- literalToken('inner'),
- [identifierNode('y')],
- [
- new AST.RuleSet(
- [new AST.Selector(literalToken('div'))],
- [new AST.Rule(property('color'), [identifierNode('y')])]
- ),
- ]
- ),
- ]
- )
- );
- }
- });
-
- test('allow multiple values for a rule', (t) => {
- t.plan(2);
- const result = parse('(p :font-family Times, serif)');
- t.false(result instanceof ParserError);
- if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
- t.deepEqual(
- result.tree[0],
- new AST.RuleSet(
- [new AST.Selector(literalToken('p'))],
- [new AST.Rule(
- property('font-family'),
- [literalNode('Times'), literalNode('serif')]
- )]
- )
- )
- }
- });
|