123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- import * as test from 'tape';
-
- import { 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.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'))]
- ),
- ]
- )
- );
- }
- });
|