ether-code 0.7.8 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,745 @@
1
+ const { EtherParserBase, TokenType } = require('./ether-parser-base')
2
+
3
+ class EtherParserCSS extends EtherParserBase {
4
+ constructor(options = {}) {
5
+ super(options)
6
+ this.loadI18n(['i18n-css.json'])
7
+ }
8
+
9
+ parse(source) {
10
+ this.tokenize(source)
11
+ return this.parseCSS()
12
+ }
13
+
14
+ parseCSS() {
15
+ const stylesheet = {
16
+ type: 'StyleSheet',
17
+ rules: []
18
+ }
19
+
20
+ this.skipNewlines()
21
+
22
+ while (!this.isAtEnd()) {
23
+ const startPos = this.pos
24
+ const rule = this.parseCSSRule()
25
+ if (rule) {
26
+ stylesheet.rules.push(rule)
27
+ }
28
+ this.skipNewlines()
29
+ if (this.pos === startPos && !this.isAtEnd()) {
30
+ this.advance()
31
+ }
32
+ }
33
+
34
+ return stylesheet
35
+ }
36
+
37
+ parseCSSRule() {
38
+ this.skipNewlines()
39
+ const token = this.current()
40
+ if (!token || token.type === TokenType.EOF) return null
41
+
42
+ if (token.type === TokenType.AT) {
43
+ return this.parseCSSAtRule()
44
+ }
45
+
46
+ const selector = this.parseCSSSelector()
47
+ if (!selector) return null
48
+
49
+ this.skipNewlines()
50
+ this.match(TokenType.INDENT)
51
+
52
+ const declarations = []
53
+ const nestedRules = []
54
+
55
+ while (!this.isAtEnd()) {
56
+ const current = this.current()
57
+
58
+ if (current && current.type === TokenType.DEDENT) {
59
+ this.advance()
60
+ break
61
+ }
62
+
63
+ if (current && current.type === TokenType.NEWLINE) {
64
+ this.advance()
65
+ continue
66
+ }
67
+
68
+ if (!current || current.type === TokenType.EOF) break
69
+
70
+ if (current.type === TokenType.IDENTIFIER) {
71
+ const value = current.value.toLowerCase()
72
+
73
+ if (value.startsWith('au ') || value === 'au survol' || value === 'au clic' ||
74
+ value === 'au focus' || value === 'actif' || value === 'visite' ||
75
+ value.startsWith('premier') || value.startsWith('dernier') ||
76
+ value.startsWith('avant') || value.startsWith('apres')) {
77
+ const pseudoRule = this.parseCSSPseudoBlock(selector)
78
+ if (pseudoRule) nestedRules.push(pseudoRule)
79
+ continue
80
+ }
81
+ }
82
+
83
+ const decl = this.parseCSSDeclaration()
84
+ if (decl) {
85
+ declarations.push(decl)
86
+ } else {
87
+ this.advance()
88
+ }
89
+ }
90
+
91
+ return {
92
+ type: 'Rule',
93
+ selector: selector,
94
+ declarations: declarations,
95
+ nestedRules: nestedRules
96
+ }
97
+ }
98
+
99
+ parseCSSSelector() {
100
+ const parts = []
101
+
102
+ while (!this.isAtEnd()) {
103
+ const token = this.current()
104
+
105
+ if (!token || token.type === TokenType.NEWLINE || token.type === TokenType.INDENT ||
106
+ token.type === TokenType.EOF) {
107
+ break
108
+ }
109
+
110
+ if (token.type === TokenType.DOUBLE_COLON) {
111
+ this.advance()
112
+ const pseudoName = this.current()
113
+ if (pseudoName && pseudoName.type === TokenType.IDENTIFIER) {
114
+ parts.push('::' + pseudoName.value)
115
+ this.advance()
116
+ }
117
+ } else if (token.type === TokenType.COLON) {
118
+ this.advance()
119
+ const next = this.current()
120
+ if (next && next.type === TokenType.COLON) {
121
+ this.advance()
122
+ const pseudoName = this.current()
123
+ if (pseudoName && pseudoName.type === TokenType.IDENTIFIER) {
124
+ parts.push('::' + pseudoName.value)
125
+ this.advance()
126
+ }
127
+ } else if (next && next.type === TokenType.IDENTIFIER) {
128
+ parts.push(':' + next.value)
129
+ this.advance()
130
+ } else {
131
+ break
132
+ }
133
+ } else if (token.type === TokenType.DOT) {
134
+ this.advance()
135
+ const name = this.current()
136
+ if (name && name.type === TokenType.IDENTIFIER) {
137
+ parts.push('.' + name.value)
138
+ this.advance()
139
+ }
140
+ } else if (token.type === TokenType.HASH) {
141
+ this.advance()
142
+ const name = this.current()
143
+ if (name && name.type === TokenType.IDENTIFIER) {
144
+ parts.push('#' + name.value)
145
+ this.advance()
146
+ }
147
+ } else if (token.type === TokenType.IDENTIFIER) {
148
+ parts.push(this.translateCSSSelector(token.value))
149
+ this.advance()
150
+ } else if (token.type === TokenType.STAR) {
151
+ parts.push('*')
152
+ this.advance()
153
+ } else if (token.type === TokenType.COMMA) {
154
+ parts.push(',')
155
+ this.advance()
156
+ } else if (token.type === TokenType.GT) {
157
+ parts.push('>')
158
+ this.advance()
159
+ } else if (token.type === TokenType.PLUS) {
160
+ parts.push('+')
161
+ this.advance()
162
+ } else if (token.type === TokenType.TILDE) {
163
+ parts.push('~')
164
+ this.advance()
165
+ } else if (token.type === TokenType.LBRACKET) {
166
+ let attrSelector = '['
167
+ this.advance()
168
+ while (!this.isAtEnd()) {
169
+ const attrToken = this.current()
170
+ if (!attrToken || attrToken.type === TokenType.RBRACKET) {
171
+ attrSelector += ']'
172
+ this.advance()
173
+ break
174
+ }
175
+ attrSelector += attrToken.value
176
+ this.advance()
177
+ }
178
+ parts.push(attrSelector)
179
+ } else if (token.type === TokenType.EQUALS) {
180
+ break
181
+ } else {
182
+ break
183
+ }
184
+ }
185
+
186
+ return parts.join(' ').replace(/\s+([,>+~])\s+/g, ' $1 ').replace(/\s+(::?)/g, '$1').trim()
187
+ }
188
+
189
+ translateCSSSelector(selector) {
190
+ const map = this.reverseMaps.css || {}
191
+ const lower = selector.toLowerCase()
192
+
193
+ const selectorTranslations = {
194
+ 'corps': 'body',
195
+ 'html': 'html',
196
+ 'document': 'html',
197
+ 'tete': 'head',
198
+ 'titre': 'title',
199
+ 'titre1': 'h1',
200
+ 'titre2': 'h2',
201
+ 'titre3': 'h3',
202
+ 'titre4': 'h4',
203
+ 'titre5': 'h5',
204
+ 'titre6': 'h6',
205
+ 'paragraphe': 'p',
206
+ 'lien': 'a',
207
+ 'ressource': 'link',
208
+ 'lien externe': 'link',
209
+ 'image': 'img',
210
+ 'bouton': 'button',
211
+ 'formulaire': 'form',
212
+ 'champ': 'input',
213
+ 'liste': 'ul',
214
+ 'liste ordonnee': 'ol',
215
+ 'element liste': 'li',
216
+ 'tableau': 'table',
217
+ 'ligne': 'tr',
218
+ 'cellule': 'td',
219
+ 'entete': 'header',
220
+ 'piedpage': 'footer',
221
+ 'navigation': 'nav',
222
+ 'section': 'section',
223
+ 'article': 'article',
224
+ 'cote': 'aside',
225
+ 'principal': 'main',
226
+ 'division': 'div',
227
+ 'portee': 'span'
228
+ }
229
+
230
+ return selectorTranslations[lower] || map[lower] || selector
231
+ }
232
+
233
+ parseCSSDeclaration() {
234
+ this.skipNewlines()
235
+ const token = this.current()
236
+ if (!token || token.type !== TokenType.IDENTIFIER) return null
237
+
238
+ const compoundProperties = {
239
+ 'marge': ['dedans', 'autour', 'haut', 'bas', 'gauche', 'droite'],
240
+ 'marge dedans': ['haut', 'bas', 'gauche', 'droite'],
241
+ 'marge autour': ['haut', 'bas', 'gauche', 'droite'],
242
+ 'remplissage': ['haut', 'bas', 'gauche', 'droite'],
243
+ 'taille': ['police'],
244
+ 'poids': ['police'],
245
+ 'style': ['police', 'liste'],
246
+ 'hauteur': ['ligne', 'min', 'max', 'minimum', 'maximum'],
247
+ 'largeur': ['min', 'max', 'minimum', 'maximum'],
248
+ 'alignement': ['texte'],
249
+ 'decoration': ['texte'],
250
+ 'transformation': ['texte'],
251
+ 'ombre': ['texte', 'boite', 'boîte'],
252
+ 'espacement': ['lettres'],
253
+ 'bordure': ['haut', 'bas', 'gauche', 'droite', 'couleur', 'style', 'largeur', 'arrondi'],
254
+ 'couleur': ['bordure', 'fond', 'texte', 'remplissage'],
255
+ 'arrondi': ['haut', 'bas'],
256
+ 'arrondi haut': ['gauche', 'droite'],
257
+ 'arrondi bas': ['gauche', 'droite'],
258
+ 'fond': ['couleur', 'image', 'position', 'taille', 'repetition', 'attachement'],
259
+ 'direction': ['flex'],
260
+ 'enveloppe': ['flex'],
261
+ 'justifier': ['contenu'],
262
+ 'aligner': ['elements', 'éléments', 'contenu'],
263
+ 'colonnes': ['grille'],
264
+ 'lignes': ['grille'],
265
+ 'espace': ['ligne', 'colonne', 'blanc'],
266
+ 'débordement': ['x', 'y'],
267
+ 'debordement': ['x', 'y'],
268
+ 'événements': ['pointeur'],
269
+ 'evenements': ['pointeur'],
270
+ 'modèle': ['boite', 'boîte'],
271
+ 'modele': ['boite', 'boîte'],
272
+ 'filtre': ['fond'],
273
+ 'decoupe': ['fond'],
274
+ 'découpe': ['fond'],
275
+ 'origine': ['transformation', 'perspective'],
276
+ 'liste': ['style', 'type', 'position', 'image'],
277
+ }
278
+
279
+ let property = token.value
280
+ this.advance()
281
+
282
+ let nextToken = this.current()
283
+ while (nextToken && nextToken.type === TokenType.IDENTIFIER) {
284
+ const propLower = property.toLowerCase()
285
+ if (compoundProperties[propLower] && compoundProperties[propLower].includes(nextToken.value.toLowerCase())) {
286
+ property = property + ' ' + nextToken.value
287
+ this.advance()
288
+ nextToken = this.current()
289
+ } else {
290
+ break
291
+ }
292
+ }
293
+
294
+ let hasColon = this.match(TokenType.COLON)
295
+ let hasEquals = !hasColon && this.match(TokenType.EQUALS)
296
+
297
+ if (!hasColon && !hasEquals) {
298
+ return null
299
+ }
300
+
301
+ const valueParts = []
302
+ let lastType = null
303
+
304
+ while (!this.isAtEnd()) {
305
+ const current = this.current()
306
+
307
+ if (!current || current.type === TokenType.NEWLINE ||
308
+ current.type === TokenType.DEDENT || current.type === TokenType.EOF) {
309
+ break
310
+ }
311
+
312
+ let needsSpace = valueParts.length > 0
313
+
314
+ if (current.type === TokenType.LPAREN || current.type === TokenType.COMMA ||
315
+ current.type === TokenType.RPAREN || current.type === TokenType.PERCENT ||
316
+ lastType === TokenType.LPAREN || lastType === TokenType.MINUS ||
317
+ lastType === TokenType.HASH || lastType === TokenType.COLON) {
318
+ needsSpace = false
319
+ }
320
+
321
+ if (lastType === TokenType.NUMBER || lastType === TokenType.INTEGER || lastType === TokenType.FLOAT) {
322
+ if (current.type === TokenType.IDENTIFIER) {
323
+ const units = ['px', 'em', 'rem', '%', 'vw', 'vh', 'vmin', 'vmax', 'ch', 'ex', 'cm', 'mm', 'in', 'pt', 'pc', 'deg', 'rad', 'turn', 'ms', 's', 'fr', 'cqw', 'cqh']
324
+ if (units.includes(current.value.toLowerCase())) {
325
+ needsSpace = false
326
+ }
327
+ }
328
+ }
329
+
330
+ if (current.type === TokenType.IDENTIFIER) {
331
+ if (needsSpace) valueParts.push(' ')
332
+ valueParts.push(current.value)
333
+ } else if (current.type === TokenType.NUMBER || current.type === TokenType.INTEGER ||
334
+ current.type === TokenType.FLOAT) {
335
+ if (needsSpace && lastType !== TokenType.HASH) valueParts.push(' ')
336
+ valueParts.push(current.value)
337
+ } else if (current.type === TokenType.STRING) {
338
+ if (needsSpace) valueParts.push(' ')
339
+ valueParts.push('"' + current.value + '"')
340
+ } else if (current.type === TokenType.HEX) {
341
+ if (needsSpace) valueParts.push(' ')
342
+ valueParts.push(current.value)
343
+ } else if (current.type === TokenType.HASH) {
344
+ if (needsSpace) valueParts.push(' ')
345
+ valueParts.push('#')
346
+ } else if (current.type === TokenType.COMMA) {
347
+ valueParts.push(',')
348
+ } else if (current.type === TokenType.LPAREN) {
349
+ valueParts.push('(')
350
+ } else if (current.type === TokenType.RPAREN) {
351
+ valueParts.push(')')
352
+ } else if (current.type === TokenType.PERCENT) {
353
+ valueParts.push('%')
354
+ } else if (current.type === TokenType.SLASH) {
355
+ if (needsSpace) valueParts.push(' ')
356
+ valueParts.push('/')
357
+ } else if (current.type === TokenType.MINUS) {
358
+ if (current.value === '--') {
359
+ valueParts.push('--')
360
+ } else if (lastType === TokenType.LPAREN) {
361
+ valueParts.push(current.value)
362
+ } else {
363
+ valueParts.push(' ' + current.value)
364
+ }
365
+ } else if (current.type === TokenType.PLUS) {
366
+ valueParts.push(' +')
367
+ } else if (current.type === TokenType.COLON) {
368
+ valueParts.push(':')
369
+ } else if (current.type === TokenType.DOT) {
370
+ valueParts.push('.')
371
+ } else {
372
+ break
373
+ }
374
+
375
+ lastType = current.type
376
+ this.advance()
377
+ }
378
+
379
+ return {
380
+ type: 'Declaration',
381
+ property: property,
382
+ value: valueParts.join('')
383
+ .replace(/,\s*/g, ', ')
384
+ .replace(/\s+/g, ' ')
385
+ .trim()
386
+ }
387
+ }
388
+
389
+ parseCSSPseudoBlock(parentSelector) {
390
+ const pseudoToken = this.current()
391
+ const pseudoClass = this.translatePseudoClass(pseudoToken.value)
392
+ this.advance()
393
+
394
+ this.skipNewlines()
395
+ this.match(TokenType.INDENT)
396
+
397
+ const declarations = []
398
+
399
+ while (!this.isAtEnd()) {
400
+ const current = this.current()
401
+
402
+ if (current && current.type === TokenType.DEDENT) {
403
+ this.advance()
404
+ break
405
+ }
406
+
407
+ if (current && current.type === TokenType.NEWLINE) {
408
+ this.advance()
409
+ continue
410
+ }
411
+
412
+ if (!current || current.type === TokenType.EOF) break
413
+
414
+ const decl = this.parseCSSDeclaration()
415
+ if (decl) {
416
+ declarations.push(decl)
417
+ } else {
418
+ this.advance()
419
+ }
420
+ }
421
+
422
+ return {
423
+ type: 'Rule',
424
+ selector: `${parentSelector}${pseudoClass}`,
425
+ declarations: declarations
426
+ }
427
+ }
428
+
429
+ translatePseudoClass(value) {
430
+ const map = {
431
+ 'au survol': ':hover',
432
+ 'au clic': ':active',
433
+ 'actif': ':active',
434
+ 'au focus': ':focus',
435
+ 'focus': ':focus',
436
+ 'visite': ':visited',
437
+ 'premier enfant': ':first-child',
438
+ 'dernier enfant': ':last-child',
439
+ 'premier de type': ':first-of-type',
440
+ 'dernier de type': ':last-of-type',
441
+ 'enfant unique': ':only-child',
442
+ 'unique de type': ':only-of-type',
443
+ 'vide': ':empty',
444
+ 'cible': ':target',
445
+ 'coche': ':checked',
446
+ 'desactive': ':disabled',
447
+ 'active': ':enabled',
448
+ 'requis': ':required',
449
+ 'optionnel': ':optional',
450
+ 'valide': ':valid',
451
+ 'invalide': ':invalid',
452
+ 'lecture seule': ':read-only',
453
+ 'lecture ecriture': ':read-write',
454
+ 'avant': '::before',
455
+ 'apres': '::after',
456
+ 'premiere lettre': '::first-letter',
457
+ 'premiere ligne': '::first-line',
458
+ 'selection': '::selection',
459
+ 'placeholder': '::placeholder',
460
+ 'marqueur': '::marker'
461
+ }
462
+
463
+ const lower = value.toLowerCase()
464
+ return map[lower] || ':' + lower.replace(/\s+/g, '-')
465
+ }
466
+
467
+ parseCSSAtRule() {
468
+ this.advance()
469
+ const nameToken = this.current()
470
+
471
+ if (!nameToken) return null
472
+
473
+ const name = nameToken.value.toLowerCase()
474
+ this.advance()
475
+
476
+ if (name === 'media' || name === 'requete media' || name === 'ecran' || name === 'si') {
477
+ return this.parseCSSMediaQuery()
478
+ }
479
+
480
+ if (name === 'keyframes' || name === 'images cles' || name === 'animation') {
481
+ return this.parseCSSKeyframes()
482
+ }
483
+
484
+ if (name === 'import' || name === 'importer') {
485
+ return this.parseCSSImport()
486
+ }
487
+
488
+ if (name === 'font-face' || name === 'police') {
489
+ return this.parseCSSFontFace()
490
+ }
491
+
492
+ if (name === 'container' || name === 'conteneur') {
493
+ return this.parseCSSContainerQuery()
494
+ }
495
+
496
+ return null
497
+ }
498
+
499
+ parseCSSMediaQuery() {
500
+ const queryParts = []
501
+
502
+ while (!this.isAtEnd()) {
503
+ const token = this.current()
504
+
505
+ if (!token || token.type === TokenType.NEWLINE || token.type === TokenType.INDENT) {
506
+ break
507
+ }
508
+
509
+ queryParts.push(token.value)
510
+ this.advance()
511
+ }
512
+
513
+ this.skipNewlines()
514
+ this.match(TokenType.INDENT)
515
+
516
+ const rules = []
517
+
518
+ while (!this.isAtEnd()) {
519
+ const current = this.current()
520
+
521
+ if (current && current.type === TokenType.DEDENT) {
522
+ this.advance()
523
+ break
524
+ }
525
+
526
+ const rule = this.parseCSSRule()
527
+ if (rule) {
528
+ rules.push(rule)
529
+ }
530
+
531
+ this.skipNewlines()
532
+ }
533
+
534
+ return {
535
+ type: 'MediaQuery',
536
+ query: this.translateMediaQuery(queryParts.join(' ')),
537
+ rules: rules
538
+ }
539
+ }
540
+
541
+ translateMediaQuery(query) {
542
+ let result = query
543
+
544
+ result = result.replace(/ecran\s+tres\s+petit/gi, 'screen and (max-width: 480px)')
545
+ result = result.replace(/ecran\s+petit/gi, 'screen and (max-width: 768px)')
546
+ result = result.replace(/ecran\s+moyen/gi, 'screen and (max-width: 1024px)')
547
+ result = result.replace(/ecran\s+large/gi, 'screen and (min-width: 1025px) and (max-width: 1280px)')
548
+ result = result.replace(/ecran\s+tres\s+large/gi, 'screen and (min-width: 1281px)')
549
+
550
+ result = result
551
+ .replace(/ecran/gi, 'screen')
552
+ .replace(/imprimante/gi, 'print')
553
+ .replace(/tous/gi, 'all')
554
+ .replace(/largeur\s+min/gi, 'min-width')
555
+ .replace(/largeur\s+max/gi, 'max-width')
556
+ .replace(/hauteur\s+min/gi, 'min-height')
557
+ .replace(/hauteur\s+max/gi, 'max-height')
558
+ .replace(/orientation/gi, 'orientation')
559
+ .replace(/paysage/gi, 'landscape')
560
+ .replace(/portrait/gi, 'portrait')
561
+ .replace(/\bet\b/gi, 'and')
562
+ .replace(/\bou\b/gi, 'or')
563
+ .replace(/\bpas\b/gi, 'not')
564
+
565
+ return result
566
+ }
567
+
568
+ parseCSSKeyframes() {
569
+ const nameToken = this.current()
570
+ const name = nameToken ? nameToken.value : 'animation'
571
+ if (nameToken) this.advance()
572
+
573
+ this.skipNewlines()
574
+ this.match(TokenType.INDENT)
575
+
576
+ const keyframes = []
577
+
578
+ while (!this.isAtEnd()) {
579
+ const current = this.current()
580
+
581
+ if (current && current.type === TokenType.DEDENT) {
582
+ this.advance()
583
+ break
584
+ }
585
+
586
+ if (current && current.type === TokenType.NEWLINE) {
587
+ this.advance()
588
+ continue
589
+ }
590
+
591
+ const keyframe = this.parseCSSKeyframe()
592
+ if (keyframe) {
593
+ keyframes.push(keyframe)
594
+ }
595
+ }
596
+
597
+ return {
598
+ type: 'Keyframes',
599
+ name: name,
600
+ keyframes: keyframes
601
+ }
602
+ }
603
+
604
+ parseCSSKeyframe() {
605
+ const token = this.current()
606
+ if (!token) return null
607
+
608
+ let selector = token.value
609
+
610
+ const selectorMap = {
611
+ 'debut': 'from',
612
+ 'fin': 'to',
613
+ 'de': 'from',
614
+ 'a': 'to',
615
+ 'vers': 'to'
616
+ }
617
+
618
+ selector = selectorMap[selector.toLowerCase()] || selector
619
+ this.advance()
620
+
621
+ this.skipNewlines()
622
+ this.match(TokenType.INDENT)
623
+
624
+ const declarations = []
625
+
626
+ while (!this.isAtEnd()) {
627
+ const current = this.current()
628
+
629
+ if (current && current.type === TokenType.DEDENT) {
630
+ this.advance()
631
+ break
632
+ }
633
+
634
+ if (current && current.type === TokenType.NEWLINE) {
635
+ this.advance()
636
+ continue
637
+ }
638
+
639
+ const decl = this.parseCSSDeclaration()
640
+ if (decl) {
641
+ declarations.push(decl)
642
+ }
643
+ }
644
+
645
+ return {
646
+ type: 'Keyframe',
647
+ selector: selector,
648
+ declarations: declarations
649
+ }
650
+ }
651
+
652
+ parseCSSImport() {
653
+ const urlToken = this.current()
654
+ const url = urlToken ? urlToken.value.replace(/['"]/g, '') : ''
655
+ if (urlToken) this.advance()
656
+
657
+ return {
658
+ type: 'Import',
659
+ url: url
660
+ }
661
+ }
662
+
663
+ parseCSSFontFace() {
664
+ this.skipNewlines()
665
+ this.match(TokenType.INDENT)
666
+
667
+ const declarations = []
668
+
669
+ while (!this.isAtEnd()) {
670
+ const current = this.current()
671
+
672
+ if (current && current.type === TokenType.DEDENT) {
673
+ this.advance()
674
+ break
675
+ }
676
+
677
+ if (current && current.type === TokenType.NEWLINE) {
678
+ this.advance()
679
+ continue
680
+ }
681
+
682
+ const decl = this.parseCSSDeclaration()
683
+ if (decl) {
684
+ declarations.push(decl)
685
+ }
686
+ }
687
+
688
+ return {
689
+ type: 'FontFace',
690
+ declarations: declarations
691
+ }
692
+ }
693
+
694
+ parseCSSContainerQuery() {
695
+ const queryParts = []
696
+
697
+ while (!this.isAtEnd()) {
698
+ const token = this.current()
699
+
700
+ if (!token || token.type === TokenType.NEWLINE || token.type === TokenType.INDENT) {
701
+ break
702
+ }
703
+
704
+ queryParts.push(token.value)
705
+ this.advance()
706
+ }
707
+
708
+ this.skipNewlines()
709
+ this.match(TokenType.INDENT)
710
+
711
+ const rules = []
712
+
713
+ while (!this.isAtEnd()) {
714
+ const current = this.current()
715
+
716
+ if (current && current.type === TokenType.DEDENT) {
717
+ this.advance()
718
+ break
719
+ }
720
+
721
+ const rule = this.parseCSSRule()
722
+ if (rule) {
723
+ rules.push(rule)
724
+ }
725
+
726
+ this.skipNewlines()
727
+ }
728
+
729
+ return {
730
+ type: 'ContainerQuery',
731
+ query: queryParts.join(' '),
732
+ rules: rules
733
+ }
734
+ }
735
+
736
+ parseStatement(lang) {
737
+ return null
738
+ }
739
+
740
+ parseExpression(lang) {
741
+ return null
742
+ }
743
+ }
744
+
745
+ module.exports = { EtherParserCSS }