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.

parser.test.ts 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import * as test from 'tape';
  2. import { AST } from '../ast';
  3. import Lexer, { LexerError, LexerResult } from '../lexer';
  4. import Parser, { ParserError, ParserResult } from '../parser';
  5. import Token, { TokenTypes } from '../token';
  6. const parse = (source: string) => {
  7. const lexerResult: LexerResult = new Lexer(source).scan();
  8. if (!(lexerResult instanceof LexerError)) {
  9. const parserResult: ParserResult = new Parser(lexerResult).parse();
  10. return parserResult;
  11. }
  12. return lexerResult;
  13. };
  14. const literalToken = (value: string): Token => {
  15. return new Token(TokenTypes.LITERAL, value, 1);
  16. };
  17. const propertyToken = (value: string): Token => {
  18. return new Token(TokenTypes.PROPERTY, value, 1);
  19. };
  20. const identifierToken = (value: string): Token => {
  21. return new Token(TokenTypes.IDENTIFIER, value, 1);
  22. };
  23. const literalNode = (value: string): AST.Literal => {
  24. return new AST.Literal(literalToken(value));
  25. };
  26. const identifierNode = (value: string): AST.Identifier => {
  27. return new AST.Identifier(identifierToken(value));
  28. };
  29. const property = (value: string): AST.Property => {
  30. const token = new Token(TokenTypes.PROPERTY, value, 1);
  31. return new AST.Property(token);
  32. };
  33. const functionName = (value: string): AST.Literal => {
  34. const token = new Token(TokenTypes.FUNCTION_NAME, value, 1);
  35. return new AST.Literal(token);
  36. };
  37. test('parses a literal', (t) => {
  38. t.plan(2);
  39. const result = parse('#FF0000');
  40. t.false(result instanceof ParserError);
  41. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  42. t.deepEqual(result.tree, [literalNode('#FF0000')]);
  43. }
  44. });
  45. test('parses a identifier', (t) => {
  46. t.plan(2);
  47. const result = parse('$blue');
  48. t.false(result instanceof ParserError);
  49. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  50. t.deepEqual(result.tree, [identifierNode('blue')]);
  51. }
  52. });
  53. test('parses an expression', (t) => {
  54. t.plan(2);
  55. const result = parse('(.para :color black)');
  56. t.false(result instanceof ParserError);
  57. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  58. t.deepEqual(result.tree, [
  59. new AST.RuleSet(
  60. [new AST.Selector(literalToken('.para'))],
  61. [new AST.Rule(property('color'), literalNode('black'))]
  62. ),
  63. ]);
  64. }
  65. });
  66. test('parses an expression with multiple selectors', (t) => {
  67. t.plan(2);
  68. const result = parse('(.header, .footer :color black)');
  69. t.false(result instanceof ParserError);
  70. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  71. t.deepEqual(result.tree, [
  72. new AST.RuleSet(
  73. [
  74. new AST.Selector(literalToken('.header')),
  75. new AST.Selector(literalToken('.footer')),
  76. ],
  77. [new AST.Rule(property('color'), literalNode('black'))]
  78. ),
  79. ]);
  80. }
  81. });
  82. test('parses an expression with children', (t) => {
  83. t.plan(2);
  84. const result = parse(
  85. '(body :color black (div :color blue (span :color red)))'
  86. );
  87. t.false(result instanceof ParserError);
  88. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  89. t.deepEqual(result.tree, [
  90. new AST.RuleSet(
  91. [new AST.Selector(literalToken('body'))],
  92. [new AST.Rule(property('color'), literalNode('black'))],
  93. [
  94. new AST.RuleSet(
  95. [
  96. new AST.Selector(literalToken('div'), [
  97. new AST.Selector(literalToken('body')),
  98. ]),
  99. ],
  100. [new AST.Rule(property('color'), literalNode('blue'))],
  101. [
  102. new AST.RuleSet(
  103. [
  104. new AST.Selector(literalToken('span'), [
  105. new AST.Selector(literalToken('div'), [
  106. new AST.Selector(literalToken('body')),
  107. ]),
  108. ]),
  109. ],
  110. [new AST.Rule(property('color'), literalNode('red'))]
  111. ),
  112. ]
  113. ),
  114. ]
  115. ),
  116. ]);
  117. }
  118. });
  119. test('parses a `let` call', (t) => {
  120. t.plan(2);
  121. const result = parse('(@let $blue #0000FF $red #FF0000)');
  122. t.false(result instanceof ParserError);
  123. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  124. t.deepEqual(result.tree, [
  125. new AST.Let(
  126. [
  127. new AST.Binding(identifierNode('blue'), literalNode('#0000FF')),
  128. new AST.Binding(identifierNode('red'), literalNode('#FF0000')),
  129. ],
  130. []
  131. ),
  132. ]);
  133. }
  134. });
  135. test('parses a `let` call with body', (t) => {
  136. t.plan(2);
  137. const result = parse(
  138. '(@let $blue #0000FF $red #FF0000 (div :background $blue :color $red))'
  139. );
  140. t.false(result instanceof ParserError);
  141. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  142. t.deepEqual(result.tree, [
  143. new AST.Let(
  144. [
  145. new AST.Binding(identifierNode('blue'), literalNode('#0000FF')),
  146. new AST.Binding(identifierNode('red'), literalNode('#FF0000')),
  147. ],
  148. [
  149. new AST.RuleSet(
  150. [new AST.Selector(literalToken('div'))],
  151. [
  152. new AST.Rule(property('background'), identifierNode('blue')),
  153. new AST.Rule(property('color'), identifierNode('red')),
  154. ]
  155. ),
  156. ]
  157. ),
  158. ]);
  159. }
  160. });
  161. test('parses multiple rulesets in a let block', (t) => {
  162. t.plan(2);
  163. const result = parse(
  164. '(@let $blue #0000FF $red #FF0000 (div :background $blue) (span :color $red))'
  165. );
  166. t.false(result instanceof ParserError);
  167. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  168. t.deepEqual(result.tree, [
  169. new AST.Let(
  170. [
  171. new AST.Binding(identifierNode('blue'), literalNode('#0000FF')),
  172. new AST.Binding(identifierNode('red'), literalNode('#FF0000')),
  173. ],
  174. [
  175. new AST.RuleSet(
  176. [new AST.Selector(literalToken('div'))],
  177. [new AST.Rule(property('background'), identifierNode('blue'))]
  178. ),
  179. new AST.RuleSet(
  180. [new AST.Selector(literalToken('span'))],
  181. [new AST.Rule(property('color'), identifierNode('red'))]
  182. ),
  183. ]
  184. ),
  185. ]);
  186. }
  187. });
  188. test('parses media query', (t) => {
  189. t.plan(2);
  190. const result = parse('(@media :min-width 1000px (div :flex-direction row))');
  191. t.false(result instanceof ParserError);
  192. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  193. t.deepEqual(result.tree, [
  194. new AST.MediaQuery(
  195. new AST.MediaQueryPredicate(
  196. property('min-width'),
  197. literalNode('1000px')
  198. ),
  199. [
  200. new AST.RuleSet(
  201. [new AST.Selector(literalToken('div'))],
  202. [new AST.Rule(property('flex-direction'), literalNode('row'))]
  203. ),
  204. ]
  205. ),
  206. ]);
  207. }
  208. });
  209. test('parses keyframes', (t) => {
  210. t.plan(2);
  211. const result = parse('(@keyframes fade (0% :opacity 1) (100% :opacity 0))');
  212. t.false(result instanceof ParserError);
  213. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  214. t.deepEqual(result.tree, [
  215. new AST.Keyframes(literalNode('fade'), [
  216. new AST.RuleSet(
  217. [new AST.Selector(literalToken('0%'))],
  218. [new AST.Rule(property('opacity'), literalNode('1'))]
  219. ),
  220. new AST.RuleSet(
  221. [new AST.Selector(literalToken('100%'))],
  222. [new AST.Rule(property('opacity'), literalNode('0'))]
  223. ),
  224. ]),
  225. ]);
  226. }
  227. });
  228. test('parses function call', (t) => {
  229. t.plan(2);
  230. const result = parse('(@rgba 255 0 0 0)');
  231. t.false(result instanceof ParserError);
  232. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  233. t.deepEqual(result.tree, [
  234. new AST.Application(functionName('rgba'), [
  235. literalNode('255'),
  236. literalNode('0'),
  237. literalNode('0'),
  238. literalNode('0'),
  239. ]),
  240. ]);
  241. }
  242. });
  243. test('parses mixin', (t) => {
  244. t.plan(2);
  245. const result = parse('(@mixin centered ($width) :max-width $width :margin auto)');
  246. t.false(result instanceof ParserError);
  247. if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
  248. t.deepEqual(result.tree, [
  249. new AST.Mixin(
  250. literalToken('centered'),
  251. [
  252. identifierNode('width'),
  253. ],
  254. [
  255. new AST.Rule(
  256. new AST.Property(propertyToken('max-width')),
  257. identifierNode('width'),
  258. ),
  259. new AST.Rule(
  260. new AST.Property(propertyToken('margin')),
  261. literalNode('auto')
  262. ),
  263. ]
  264. ),
  265. ]);
  266. }
  267. });