Browse Source

Allow commas in rule values

master
Dylan Baker 2 years ago
parent
commit
8958d4e0cc
4 changed files with 62 additions and 31 deletions
  1. 10
    6
      src/ast/rule.ts
  2. 10
    1
      src/parser.ts
  3. 2
    2
      src/tests/compiler.test.ts
  4. 40
    22
      src/tests/parser.test.ts

+ 10
- 6
src/ast/rule.ts View File

@@ -3,14 +3,14 @@ import Env, { EnvError } from '../env';
3 3
 
4 4
 export class Rule {
5 5
   public property: AST.Property;
6
-  public value: AST.Value;
6
+  public values: AST.Value[];
7 7
 
8 8
   public constructor(
9 9
     property: AST.Property,
10
-    value: AST.Value
10
+    values: AST.Value[]
11 11
   ) {
12 12
     this.property = property;
13
-    this.value = value;
13
+    this.values = values;
14 14
   }
15 15
 
16 16
   public compile(env: Env, opts: AST.Opts): string | EnvError {
@@ -18,8 +18,12 @@ export class Rule {
18 18
     const indentSpacer = opts.prettyPrint ? '  ' + Array(opts.depth).fill(' ').join('') : '';
19 19
     const property = this.property.compile(env, opts);
20 20
     if (property instanceof EnvError) return property;
21
-    const value = this.value.compile(env, opts);
22
-    if (value instanceof EnvError) return value;
23
-    return `${indentSpacer}${property}:${wordSpacer}${value};`;
21
+    const values: string[] = [];
22
+    for (const value of this.values) {
23
+      const compiledValue = value.compile(env, opts);
24
+      if (compiledValue instanceof EnvError) return compiledValue;
25
+      values.push(compiledValue);
26
+    }
27
+    return `${indentSpacer}${property}:${wordSpacer}${values.join(',')};`;
24 28
   }
25 29
 }

+ 10
- 1
src/parser.ts View File

@@ -311,10 +311,19 @@ export default class Parser {
311 311
       const property = this.property();
312 312
       if (property instanceof ParserError) return property;
313 313
 
314
+      const values = [];
314 315
       const value = this.value();
315 316
       if (value instanceof ParserError) return value;
317
+      values.push(value);
316 318
 
317
-      return new AST.Rule(property, value);
319
+      while (this.currentToken().type === TokenTypes.COMMA) {
320
+        this.eat(TokenTypes.COMMA);
321
+        const value = this.value();
322
+        if (value instanceof ParserError) return value;
323
+        values.push(value);
324
+      }
325
+
326
+      return new AST.Rule(property, values);
318 327
     } else if (this.currentToken().type === TokenTypes.LPAREN) {
319 328
       const next = this.nextToken();
320 329
       if (next.type === TokenTypes.LITERAL) {

+ 2
- 2
src/tests/compiler.test.ts View File

@@ -160,11 +160,11 @@ test('compiles mixins', (t) => {
160 160
     [
161 161
       new AST.Rule(
162 162
         new AST.Property(new Token(TokenTypes.PROPERTY, 'max-width', 1)),
163
-        new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1)),
163
+        [new AST.Identifier(new Token(TokenTypes.IDENTIFIER, 'width', 1))],
164 164
       ),
165 165
       new AST.Rule(
166 166
         new AST.Property(new Token(TokenTypes.PROPERTY, 'margin', 1)),
167
-        new AST.Literal(new Token(TokenTypes.LITERAL, 'auto', 1)),
167
+        [new AST.Literal(new Token(TokenTypes.LITERAL, 'auto', 1))],
168 168
       )
169 169
     ]
170 170
   ));

+ 40
- 22
src/tests/parser.test.ts View File

@@ -71,7 +71,7 @@ test('parses an expression', (t) => {
71 71
     t.deepEqual(result.tree, [
72 72
       new AST.RuleSet(
73 73
         [new AST.Selector(literalToken('.para'))],
74
-        [new AST.Rule(property('color'), literalNode('black'))]
74
+        [new AST.Rule(property('color'), [literalNode('black')])]
75 75
       ),
76 76
     ]);
77 77
   }
@@ -88,7 +88,7 @@ test('parses an expression with multiple selectors', (t) => {
88 88
           new AST.Selector(literalToken('.header')),
89 89
           new AST.Selector(literalToken('.footer')),
90 90
         ],
91
-        [new AST.Rule(property('color'), literalNode('black'))]
91
+        [new AST.Rule(property('color'), [literalNode('black')])]
92 92
       ),
93 93
     ]);
94 94
   }
@@ -105,7 +105,7 @@ test('parses an expression with children', (t) => {
105 105
       new AST.RuleSet(
106 106
         [new AST.Selector(literalToken('body'))],
107 107
         [
108
-          new AST.Rule(property('color'), literalNode('black')),
108
+          new AST.Rule(property('color'), [literalNode('black')]),
109 109
           new AST.RuleSet(
110 110
             [
111 111
               new AST.Selector(literalToken('div'), [
@@ -113,7 +113,7 @@ test('parses an expression with children', (t) => {
113 113
               ]),
114 114
             ],
115 115
             [
116
-              new AST.Rule(property('color'), literalNode('blue')),
116
+              new AST.Rule(property('color'), [literalNode('blue')]),
117 117
               new AST.RuleSet(
118 118
                 [
119 119
                   new AST.Selector(literalToken('span'), [
@@ -122,7 +122,7 @@ test('parses an expression with children', (t) => {
122 122
                     ]),
123 123
                   ]),
124 124
                 ],
125
-                [new AST.Rule(property('color'), literalNode('red'))]
125
+                [new AST.Rule(property('color'), [literalNode('red')])]
126 126
               ),
127 127
             ]
128 128
           ),
@@ -166,8 +166,8 @@ test('parses a `let` call with body', (t) => {
166 166
           new AST.RuleSet(
167 167
             [new AST.Selector(literalToken('div'))],
168 168
             [
169
-              new AST.Rule(property('background'), identifierNode('blue')),
170
-              new AST.Rule(property('color'), identifierNode('red')),
169
+              new AST.Rule(property('background'), [identifierNode('blue')]),
170
+              new AST.Rule(property('color'), [identifierNode('red')]),
171 171
             ]
172 172
           ),
173 173
         ]
@@ -192,11 +192,11 @@ test('parses multiple rulesets in a let block', (t) => {
192 192
         [
193 193
           new AST.RuleSet(
194 194
             [new AST.Selector(literalToken('div'))],
195
-            [new AST.Rule(property('background'), identifierNode('blue'))]
195
+            [new AST.Rule(property('background'), [identifierNode('blue')])]
196 196
           ),
197 197
           new AST.RuleSet(
198 198
             [new AST.Selector(literalToken('span'))],
199
-            [new AST.Rule(property('color'), identifierNode('red'))]
199
+            [new AST.Rule(property('color'), [identifierNode('red')])]
200 200
           ),
201 201
         ]
202 202
       ),
@@ -218,7 +218,7 @@ test('parses media query', (t) => {
218 218
         [
219 219
           new AST.RuleSet(
220 220
             [new AST.Selector(literalToken('div'))],
221
-            [new AST.Rule(property('flex-direction'), literalNode('row'))]
221
+            [new AST.Rule(property('flex-direction'), [literalNode('row')])]
222 222
           ),
223 223
         ]
224 224
       ),
@@ -235,11 +235,11 @@ test('parses keyframes', (t) => {
235 235
       new AST.Keyframes(literalNode('fade'), [
236 236
         new AST.RuleSet(
237 237
           [new AST.Selector(literalToken('0%'))],
238
-          [new AST.Rule(property('opacity'), literalNode('1'))]
238
+          [new AST.Rule(property('opacity'), [literalNode('1')])]
239 239
         ),
240 240
         new AST.RuleSet(
241 241
           [new AST.Selector(literalToken('100%'))],
242
-          [new AST.Rule(property('opacity'), literalNode('0'))]
242
+          [new AST.Rule(property('opacity'), [literalNode('0')])]
243 243
         ),
244 244
       ]),
245 245
     ]);
@@ -276,11 +276,11 @@ test('parses mixin', (t) => {
276 276
         [
277 277
           new AST.Rule(
278 278
             new AST.Property(propertyToken('max-width')),
279
-            identifierNode('width')
279
+            [identifierNode('width')]
280 280
           ),
281 281
           new AST.Rule(
282 282
             new AST.Property(propertyToken('margin')),
283
-            literalNode('auto')
283
+            [literalNode('auto')]
284 284
           ),
285 285
         ]
286 286
       ),
@@ -304,9 +304,9 @@ test('allow rules and children in any order', (t) => {
304 304
                 new AST.Selector(literalToken('div')),
305 305
               ]),
306 306
             ],
307
-            [new AST.Rule(property('color'), literalNode('blue'))]
307
+            [new AST.Rule(property('color'), [literalNode('blue')])]
308 308
           ),
309
-          new AST.Rule(property('color'), literalNode('green')),
309
+          new AST.Rule(property('color'), [literalNode('green')]),
310 310
         ]
311 311
       )
312 312
     );
@@ -323,14 +323,14 @@ test('combine rules and children', (t) => {
323 323
       new AST.RuleSet(
324 324
         [new AST.Selector(literalToken('div'))],
325 325
         [
326
-          new AST.Rule(property('color'), literalNode('green')),
326
+          new AST.Rule(property('color'), [literalNode('green')]),
327 327
           new AST.RuleSet(
328 328
             [
329 329
               new AST.Selector(literalToken('span'), [
330 330
                 new AST.Selector(literalToken('div')),
331 331
               ]),
332 332
             ],
333
-            [new AST.Rule(property('color'), literalNode('blue'))]
333
+            [new AST.Rule(property('color'), [literalNode('blue')])]
334 334
           ),
335 335
         ]
336 336
       )
@@ -366,7 +366,7 @@ test('parse mixin with children', (t) => {
366 366
         [
367 367
           new AST.RuleSet(
368 368
             [new AST.Selector(literalToken('div'))],
369
-            [new AST.Rule(property('color'), literalNode('blue'))]
369
+            [new AST.Rule(property('color'), [literalNode('blue')])]
370 370
           ),
371 371
         ]
372 372
       )
@@ -392,7 +392,7 @@ test('media query as child', (t) => {
392 392
             [
393 393
               new AST.RuleSet(
394 394
                 [new AST.Selector(literalToken('&'))],
395
-                [new AST.Rule(property('color'), literalNode('blue'))]
395
+                [new AST.Rule(property('color'), [literalNode('blue')])]
396 396
               ),
397 397
             ]
398 398
           ),
@@ -418,7 +418,7 @@ test('mixin as child', (t) => {
418 418
             [
419 419
               new AST.RuleSet(
420 420
                 [new AST.Selector(literalToken('div'))],
421
-                [new AST.Rule(property('color'), literalNode('blue'))]
421
+                [new AST.Rule(property('color'), [literalNode('blue')])]
422 422
               ),
423 423
             ]
424 424
           )
@@ -445,7 +445,7 @@ test('define a mixin inside another mixin', (t) => {
445 445
             [
446 446
               new AST.RuleSet(
447 447
                 [new AST.Selector(literalToken('div'))],
448
-                [new AST.Rule(property('color'), identifierNode('y'))]
448
+                [new AST.Rule(property('color'), [identifierNode('y')])]
449 449
               ),
450 450
             ]
451 451
           ),
@@ -454,3 +454,21 @@ test('define a mixin inside another mixin', (t) => {
454 454
     );
455 455
   }
456 456
 });
457
+
458
+test('allow multiple values for a rule', (t) => {
459
+  t.plan(2);
460
+  const result = parse('(p :font-family Times, serif)');
461
+  t.false(result instanceof ParserError);
462
+  if (!(result instanceof LexerError) && !(result instanceof ParserError)) {
463
+    t.deepEqual(
464
+      result.tree[0],
465
+      new AST.RuleSet(
466
+        [new AST.Selector(literalToken('p'))],
467
+        [new AST.Rule(
468
+          property('font-family'),
469
+          [literalNode('Times'), literalNode('serif')]
470
+        )]
471
+      )
472
+    )
473
+  }
474
+});

Loading…
Cancel
Save