A stylesheet language written in TypeScript that compiles to CSS
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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