A stylesheet language written in TypeScript that compiles to CSS
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

compiler.test.ts 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import * as test from 'tape';
  2. import { AST } from '../ast';
  3. import Compiler, { CompilerOpts } from '../compiler';
  4. import Env, { EnvError } from '../env';
  5. import Lexer, { LexerError, LexerResult } from '../lexer';
  6. import Parser, { ParserError, ParserResult } from '../parser';
  7. import Token, { TokenTypes } from '../token';
  8. const defaultOpts = {
  9. prettyPrint: false,
  10. env: new Env(),
  11. };
  12. const compile = (source: string, opts: CompilerOpts = defaultOpts)=> {
  13. const { prettyPrint, env } = opts;
  14. const lexerResult: LexerResult = new Lexer(source).scan();
  15. if (!(lexerResult instanceof LexerError)) {
  16. const parserResult: ParserResult = new Parser(lexerResult).parse();
  17. if (!(parserResult instanceof ParserError)) {
  18. return new Compiler(parserResult, { prettyPrint, env }).compile();
  19. }
  20. return parserResult;
  21. }
  22. return lexerResult;
  23. };
  24. test('compiles literal', (t) => {
  25. t.plan(1);
  26. const result = compile('#FF0000');
  27. t.equal(result, '#FF0000');
  28. });
  29. test('compiles expression', (t) => {
  30. t.plan(1);
  31. const result = compile('(div :color blue)');
  32. t.equal(result, 'div{color:blue;}');
  33. });
  34. test('compiles expression with pretty printing', (t) => {
  35. t.plan(1);
  36. const result = compile('(div :color blue)', { prettyPrint: true });
  37. t.equal(result, 'div {\n color: blue;\n}');
  38. });
  39. test('compiles nested expression', (t) => {
  40. t.plan(1);
  41. const result = compile('(div :color blue (span :color red))');
  42. t.equal(result, 'div{color:blue;}div span{color:red;}');
  43. });
  44. test('omits empty sets', (t) => {
  45. t.plan(1);
  46. const result = compile('(div (span :color blue))');
  47. t.equal(result, 'div span{color:blue;}');
  48. });
  49. test('pretty printing separates multiple selectors', (t) => {
  50. t.plan(1);
  51. const result = compile('(div, p :color blue)', { prettyPrint: true });
  52. t.equal(result, 'div, p {\n color: blue;\n}');
  53. });
  54. test('compile identifier', (t) => {
  55. t.plan(1);
  56. const result = compile('(@let $red #FF0000 (div :color $red))');
  57. t.equal(result, 'div{color:#FF0000;}');
  58. });
  59. test('undefined variable throws an error', (t) => {
  60. t.plan(1);
  61. const result = compile('(@let $red #FF0000)(div :color $blue)');
  62. t.true(result instanceof EnvError);
  63. });
  64. test('pretty printing does not return an extra newline at the end', (t) => {
  65. t.plan(1);
  66. const result = compile('(div :color blue)', { prettyPrint: true });
  67. t.equal(result, 'div {\n color: blue;\n}');
  68. });
  69. test('compiles media query', (t) => {
  70. t.plan(1);
  71. const result = compile(
  72. '(@media :min-width 1000px (div :flex-direction row))'
  73. );
  74. t.equal(result, '@media(min-width:1000px){div{flex-direction:row;}}');
  75. });
  76. test('compiles pretty media query', (t) => {
  77. t.plan(1);
  78. const result = compile(
  79. '(@media :min-width 1000px (div :flex-direction row))',
  80. { prettyPrint: true }
  81. );
  82. t.equal(
  83. result,
  84. '@media(min-width: 1000px) {\n div {\n flex-direction: row;\n }\n}'
  85. );
  86. });
  87. test('compiles keyframe animation', (t) => {
  88. t.plan(1);
  89. const result = compile('(@keyframes fade (0% :opacity 1) (100% :opacity 2))');
  90. t.equal(result, '@keyframes fade{0%{opacity:1;}100%{opacity:2;}}');
  91. });
  92. test('compiles pretty keyframe animation', (t) => {
  93. t.plan(1);
  94. const result = compile(
  95. '(@keyframes fade (0% :opacity 1) (100% :opacity 2))',
  96. { prettyPrint: true }
  97. );
  98. t.equal(
  99. result,
  100. '@keyframes fade {\n 0% {\n opacity: 1;\n }\n 100% {\n opacity: 2;\n }\n}'
  101. );
  102. });
  103. test('does not append extra semicolons', (t) => {
  104. t.plan(1);
  105. const result = compile('(div :color blue :font-size 16px :display flex)');
  106. t.equal(result, 'div{color:blue;font-size:16px;display:flex;}');
  107. });
  108. test('deeply nested rules', (t) => {
  109. t.plan(1);
  110. const result = compile('(body (main (div (ul (li (a :color pink))))))');
  111. t.equal(result, 'body main div ul li a{color:pink;}');
  112. });
  113. test('compile function applications', (t) => {
  114. t.plan(2);
  115. t.equal(compile('(@rgba 1 2 3 4)'), 'rgba(1,2,3,4)');
  116. t.equal(compile('(div :color (@rgba 1 2 3 4))'), 'div{color:rgba(1,2,3,4);}');
  117. });
  118. test('identifiers in function applications', (t) => {
  119. t.plan(1);
  120. t.equal(compile('(@let $red 30 $green 40 $blue 50 (div :color (@rgb $red $green $blue)))'), 'div{color:rgb(30,40,50);}');
  121. });
  122. test('ampersand refers to parent selector', (t) => {
  123. t.plan(1);
  124. t.equal(compile('(div :color green (&:hover :color blue))'), 'div{color:green;}div:hover{color:blue;}');
  125. });
  126. test('compiles mixins', (t) => {
  127. t.plan(2);
  128. const env = new Env();
  129. const result = compile('(@mixin centered ($width) :max-width $width :margin auto)', {
  130. env: env,
  131. });
  132. t.equal(result, '');
  133. t.deepEqual(env.get(new Token(TokenTypes.LITERAL, 'centered', 1)), new AST.Mixin(
  134. new Token(TokenTypes.LITERAL, 'centered', 1),
  135. [new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1))],
  136. [
  137. new AST.Rule(
  138. new AST.Property(new Token(TokenTypes.PROPERTY, 'max-width', 1)),
  139. new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1)),
  140. ),
  141. new AST.Rule(
  142. new AST.Property(new Token(TokenTypes.PROPERTY, 'margin', 1)),
  143. new AST.Literal(new Token(TokenTypes.LITERAL, 'auto', 1)),
  144. )
  145. ]
  146. ));
  147. });