A stylesheet language written in TypeScript that compiles to CSS
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. });