ether-code 0.1.6 → 0.1.8

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.
Files changed (45) hide show
  1. package/cli/compiler.js +9 -53
  2. package/cli/ether.js +1 -1
  3. package/generators/css-generator.js +42 -55
  4. package/generators/graphql-generator.js +19 -22
  5. package/generators/html-generator.js +51 -220
  6. package/generators/js-generator.js +76 -157
  7. package/generators/node-generator.js +49 -93
  8. package/generators/php-generator.js +46 -68
  9. package/generators/python-generator.js +35 -54
  10. package/generators/react-generator.js +37 -47
  11. package/generators/ruby-generator.js +59 -119
  12. package/generators/sql-generator.js +42 -63
  13. package/generators/ts-generator.js +59 -133
  14. package/i18n/i18n-css.json +147 -147
  15. package/i18n/i18n-graphql.json +6 -6
  16. package/i18n/i18n-html.json +135 -135
  17. package/i18n/i18n-js.json +107 -107
  18. package/i18n/i18n-node.json +14 -14
  19. package/i18n/i18n-php.json +177 -177
  20. package/i18n/i18n-python.json +16 -16
  21. package/i18n/i18n-react.json +97 -97
  22. package/i18n/i18n-ruby.json +22 -22
  23. package/i18n/i18n-sql.json +153 -153
  24. package/i18n/i18n-ts.json +10 -10
  25. package/lexer/ether-lexer.js +175 -34
  26. package/lexer/tokens.js +6 -6
  27. package/package.json +1 -1
  28. package/parsers/ast-css.js +0 -545
  29. package/parsers/ast-graphql.js +0 -424
  30. package/parsers/ast-html.js +0 -886
  31. package/parsers/ast-js.js +0 -750
  32. package/parsers/ast-node.js +0 -2440
  33. package/parsers/ast-php.js +0 -957
  34. package/parsers/ast-react.js +0 -580
  35. package/parsers/ast-ruby.js +0 -895
  36. package/parsers/ast-ts.js +0 -1352
  37. package/parsers/css-parser.js +0 -1981
  38. package/parsers/graphql-parser.js +0 -2011
  39. package/parsers/html-parser.js +0 -1182
  40. package/parsers/js-parser.js +0 -2564
  41. package/parsers/node-parser.js +0 -2644
  42. package/parsers/php-parser.js +0 -3037
  43. package/parsers/react-parser.js +0 -1035
  44. package/parsers/ruby-parser.js +0 -2680
  45. package/parsers/ts-parser.js +0 -3881
@@ -1,1981 +0,0 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const AST = require('./ast-css')
4
-
5
- class CSSLexer {
6
- constructor(source, i18n) {
7
- this.source = source
8
- this.i18n = i18n
9
- this.pos = 0
10
- this.line = 1
11
- this.column = 1
12
- this.tokens = []
13
- this.indentStack = [0]
14
- }
15
-
16
- peek(offset = 0) {
17
- return this.source[this.pos + offset] || ''
18
- }
19
-
20
- advance() {
21
- const char = this.source[this.pos]
22
- this.pos++
23
- if (char === '\n') {
24
- this.line++
25
- this.column = 1
26
- } else {
27
- this.column++
28
- }
29
- return char
30
- }
31
-
32
- skipWhitespace() {
33
- while (this.pos < this.source.length) {
34
- const char = this.peek()
35
- if (char === ' ' || char === '\t' || char === '\r') {
36
- this.advance()
37
- } else if (char === '-' && this.peek(1) === '-' && this.peek(2) !== '-') {
38
- this.skipLineComment()
39
- } else if (char === '{' && this.peek(1) === '-') {
40
- this.skipBlockComment()
41
- } else {
42
- break
43
- }
44
- }
45
- }
46
-
47
- skipLineComment() {
48
- while (this.pos < this.source.length && this.peek() !== '\n') {
49
- this.advance()
50
- }
51
- }
52
-
53
- skipBlockComment() {
54
- this.advance()
55
- this.advance()
56
- while (this.pos < this.source.length) {
57
- if (this.peek() === '-' && this.peek(1) === '}') {
58
- this.advance()
59
- this.advance()
60
- break
61
- }
62
- this.advance()
63
- }
64
- }
65
-
66
- readIndent() {
67
- let indent = 0
68
- while (this.peek() === ' ') {
69
- indent++
70
- this.advance()
71
- }
72
- while (this.peek() === '\t') {
73
- indent += 4
74
- this.advance()
75
- }
76
- return indent
77
- }
78
-
79
- readString(quote) {
80
- let value = ''
81
- this.advance()
82
- while (this.pos < this.source.length) {
83
- const char = this.peek()
84
- if (char === quote) {
85
- this.advance()
86
- break
87
- }
88
- if (char === '\\') {
89
- this.advance()
90
- value += this.advance()
91
- } else {
92
- value += this.advance()
93
- }
94
- }
95
- return value
96
- }
97
-
98
- readNumber() {
99
- let value = ''
100
- if (this.peek() === '-' || this.peek() === '+') {
101
- value += this.advance()
102
- }
103
- while (/[0-9]/.test(this.peek())) {
104
- value += this.advance()
105
- }
106
- if (this.peek() === '.') {
107
- value += this.advance()
108
- while (/[0-9]/.test(this.peek())) {
109
- value += this.advance()
110
- }
111
- }
112
- if (this.peek() === 'e' || this.peek() === 'E') {
113
- value += this.advance()
114
- if (this.peek() === '-' || this.peek() === '+') {
115
- value += this.advance()
116
- }
117
- while (/[0-9]/.test(this.peek())) {
118
- value += this.advance()
119
- }
120
- }
121
- return parseFloat(value)
122
- }
123
-
124
- readIdentifier() {
125
- let value = ''
126
- while (this.pos < this.source.length) {
127
- const char = this.peek()
128
- if (/[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯0-9_-]/.test(char)) {
129
- value += this.advance()
130
- } else {
131
- break
132
- }
133
- }
134
- return value
135
- }
136
-
137
- readWord() {
138
- let words = []
139
- let currentWord = ''
140
- const startPos = this.pos
141
-
142
- while (this.pos < this.source.length) {
143
- const char = this.peek()
144
- if (/[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯0-9_-]/.test(char)) {
145
- currentWord += this.advance()
146
- } else if (char === ' ' && currentWord) {
147
- const nextChar = this.source[this.pos + 1]
148
- if (nextChar && /[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯]/.test(nextChar)) {
149
- const testWord = currentWord + ' ' + this.peekWord(this.pos + 1)
150
- if (this.isMultiWordKeyword(testWord)) {
151
- words.push(currentWord)
152
- currentWord = ''
153
- this.advance()
154
- } else {
155
- break
156
- }
157
- } else {
158
- break
159
- }
160
- } else {
161
- break
162
- }
163
- }
164
-
165
- if (currentWord) {
166
- words.push(currentWord)
167
- }
168
-
169
- return words.join(' ')
170
- }
171
-
172
- peekWord(startPos) {
173
- let word = ''
174
- let pos = startPos
175
- while (pos < this.source.length) {
176
- const char = this.source[pos]
177
- if (/[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯0-9_-]/.test(char)) {
178
- word += char
179
- pos++
180
- } else {
181
- break
182
- }
183
- }
184
- return word
185
- }
186
-
187
- isMultiWordKeyword(text) {
188
- const multiWordPatterns = [
189
- 'marge autour', 'marge dedans', 'margin top', 'margin bottom',
190
- 'margen alrededor', 'margen adentro', 'внешний отступ', 'внутренний отступ',
191
- '外边距', '内边距', '外側余白', '内側余白',
192
- 'tres clair', 'très clair', 'tres fonce', 'très foncé', 'tres grand', 'très grand', 'tres petit', 'très petit',
193
- 'very light', 'very dark', 'very large', 'very small',
194
- 'muy claro', 'muy oscuro', 'muy grande', 'muy pequeño',
195
- 'очень светлый', 'очень темный', 'очень большой', 'очень маленький',
196
- '非常浅', '非常深', '非常大', '非常小',
197
- 'とても明るい', 'とても暗い', 'とても大きい', 'とても小さい',
198
- 'bleu clair', 'vert fonce', 'vert foncé', 'rouge vif',
199
- 'light blue', 'dark green', 'bright red',
200
- 'azul claro', 'verde oscuro', 'rojo vivo',
201
- 'голубой', 'темно-зеленый', 'ярко-красный',
202
- '浅蓝', '深绿', '亮红',
203
- '水色', '深緑', '鮮やかな赤',
204
- 'police generique', 'police générique', 'border radius', 'box shadow',
205
- 'generic font', 'fuente generica', 'fuente genérica',
206
- 'общий шрифт', '通用字体', '汎用フォント',
207
- 'text align', 'font size', 'font weight', 'line height',
208
- 'letter spacing', 'word spacing', 'text decoration',
209
- 'text transform', 'white space', 'overflow wrap',
210
- 'au survol', 'au clic', 'au focus', 'en survol',
211
- 'on hover', 'on click', 'on focus',
212
- 'al pasar', 'al clicar', 'al enfocar',
213
- 'при наведении', 'при клике', 'при фокусе',
214
- '悬停时', '点击时', '聚焦时',
215
- 'ホバー時', 'クリック時', 'フォーカス時',
216
- 'quand survole', 'quand survolé', 'quand clique', 'quand cliqué', 'quand focalise', 'quand focalisé',
217
- 'when hovered', 'when clicked', 'when focused',
218
- 'cuando pasado', 'cuando clicado', 'cuando enfocado',
219
- 'когда наведен', 'когда кликнут', 'когда сфокусирован',
220
- '当悬停', '当点击', '当聚焦',
221
- 'ホバーされた時', 'クリックされた時', 'フォーカスされた時',
222
- 'plus grand que', 'plus petit que', 'egal a', 'égal à',
223
- 'greater than', 'less than', 'equal to',
224
- 'mayor que', 'menor que', 'igual a',
225
- 'больше чем', 'меньше чем', 'равно',
226
- '大于', '小于', '等于',
227
- 'より大きい', 'より小さい', '等しい',
228
- 'au moins', 'au plus', 'tous les', 'safe area',
229
- 'at least', 'at most', 'all the',
230
- 'al menos', 'como maximo', 'como máximo', 'todos los',
231
- 'как минимум', 'как максимум', 'все',
232
- '至少', '最多', '所有',
233
- '少なくとも', '多くとも', 'すべての',
234
- 'largeur min', 'largeur max', 'hauteur min', 'hauteur max',
235
- 'min width', 'max width', 'min height', 'max height',
236
- 'ancho minimo', 'ancho mínimo', 'ancho maximo', 'ancho máximo',
237
- 'alto minimo', 'alto mínimo', 'alto maximo', 'alto máximo',
238
- 'мин ширина', 'макс ширина', 'мин высота', 'макс высота',
239
- '最小宽度', '最大宽度', '最小高度', '最大高度',
240
- '最小幅', '最大幅', '最小高さ', '最大高さ',
241
- 'ratio aspect', 'aspect ratio', 'relacion aspecto', 'relación aspecto',
242
- 'соотношение сторон', '宽高比', 'アスペクト比',
243
- 'degrade lineaire', 'dégradé linéaire', 'degrade radial', 'dégradé radial', 'degrade conique', 'dégradé conique',
244
- 'linear gradient', 'radial gradient', 'conic gradient',
245
- 'degradado lineal', 'degradado radial', 'degradado conico', 'degradado cónico',
246
- 'линейный градиент', 'радиальный градиент', 'конический градиент',
247
- '线性渐变', '径向渐变', '锥形渐变',
248
- '線形グラデーション', '放射グラデーション', '円錐グラデーション',
249
- 'melange couleur', 'mélange couleur', 'color mix',
250
- 'mezcla color', 'смешение цветов', '颜色混合', '色の混合',
251
- 'clair sombre', 'light dark', 'claro oscuro', 'светлый темный', '明暗', '明暗',
252
- 'ombre portee', 'ombre portée', 'drop shadow', 'sombra proyectada', 'тень', '投影', 'ドロップシャドウ',
253
- 'niveaux gris', 'grayscale', 'escala grises', 'оттенки серого', '灰度', 'グレースケール',
254
- 'teinte rotation', 'hue rotate', 'rotacion tono', 'rotación tono', 'вращение оттенка', '色相旋转', '色相回転',
255
- 'courbe bezier', 'courbe bézier', 'cubic bezier', 'curva bezier', 'curva bézier', 'кривая безье', '贝塞尔曲线', 'ベジェ曲線',
256
- 'fondu croise', 'fondu croisé', 'cross fade', 'fundido cruzado', 'перекрестное затухание', '交叉淡入淡出', 'クロスフェード',
257
- 'ensemble images', 'image set', 'conjunto imagenes', 'conjunto imágenes', 'набор изображений', '图像集', '画像セット',
258
- 'ajuster contenu', 'fit content', 'ajustar contenido', 'подогнать содержимое', '适应内容', 'コンテンツに合わせる'
259
- ]
260
- const lowerText = text.toLowerCase()
261
- return multiWordPatterns.some(p => lowerText.startsWith(p.toLowerCase()))
262
- }
263
-
264
- tokenize() {
265
- while (this.pos < this.source.length) {
266
- const loc = { line: this.line, column: this.column }
267
-
268
- if (this.peek() === '\n') {
269
- this.advance()
270
- if (this.pos < this.source.length && this.peek() !== '\n') {
271
- const indent = this.readIndent()
272
- this.tokens.push({ type: 'NEWLINE', loc })
273
- this.tokens.push({ type: 'INDENT', value: indent, loc: { line: this.line, column: 1 } })
274
- }
275
- continue
276
- }
277
-
278
- this.skipWhitespace()
279
- if (this.pos >= this.source.length) break
280
-
281
- const char = this.peek()
282
- const newLoc = { line: this.line, column: this.column }
283
-
284
- if (char === '"' || char === "'") {
285
- const value = this.readString(char)
286
- this.tokens.push({ type: 'STRING', value, quote: char, loc: newLoc })
287
- } else if (/[0-9]/.test(char) || (char === '-' && /[0-9]/.test(this.peek(1))) || (char === '.' && /[0-9]/.test(this.peek(1)))) {
288
- const value = this.readNumber()
289
- const unit = this.readUnit()
290
- this.tokens.push({ type: 'NUMBER', value, unit, loc: newLoc })
291
- } else if (char === '#') {
292
- this.advance()
293
- const hex = this.readIdentifier()
294
- this.tokens.push({ type: 'HEX_COLOR', value: hex, loc: newLoc })
295
- } else if (char === '.') {
296
- this.advance()
297
- const className = this.readIdentifier()
298
- this.tokens.push({ type: 'CLASS_SELECTOR', value: className, loc: newLoc })
299
- } else if (char === '@') {
300
- this.advance()
301
- const atKeyword = this.readWord()
302
- this.tokens.push({ type: 'AT_KEYWORD', value: atKeyword, loc: newLoc })
303
- } else if (char === ':') {
304
- this.advance()
305
- if (this.peek() === ':') {
306
- this.advance()
307
- const pseudoElement = this.readIdentifier()
308
- this.tokens.push({ type: 'PSEUDO_ELEMENT', value: pseudoElement, loc: newLoc })
309
- } else if (/[a-zA-Z]/.test(this.peek())) {
310
- const pseudoClass = this.readIdentifier()
311
- this.tokens.push({ type: 'PSEUDO_CLASS', value: pseudoClass, loc: newLoc })
312
- } else {
313
- this.tokens.push({ type: 'COLON', loc: newLoc })
314
- }
315
- } else if (char === '=') {
316
- this.advance()
317
- this.tokens.push({ type: 'EQUALS', loc: newLoc })
318
- } else if (char === ',') {
319
- this.advance()
320
- this.tokens.push({ type: 'COMMA', loc: newLoc })
321
- } else if (char === '(') {
322
- this.advance()
323
- this.tokens.push({ type: 'LPAREN', loc: newLoc })
324
- } else if (char === ')') {
325
- this.advance()
326
- this.tokens.push({ type: 'RPAREN', loc: newLoc })
327
- } else if (char === '[') {
328
- this.advance()
329
- this.tokens.push({ type: 'LBRACKET', loc: newLoc })
330
- } else if (char === ']') {
331
- this.advance()
332
- this.tokens.push({ type: 'RBRACKET', loc: newLoc })
333
- } else if (char === '{') {
334
- this.advance()
335
- this.tokens.push({ type: 'LBRACE', loc: newLoc })
336
- } else if (char === '}') {
337
- this.advance()
338
- this.tokens.push({ type: 'RBRACE', loc: newLoc })
339
- } else if (char === '>') {
340
- this.advance()
341
- if (this.peek() === '=') {
342
- this.advance()
343
- this.tokens.push({ type: 'GTE', loc: newLoc })
344
- } else {
345
- this.tokens.push({ type: 'CHILD_COMBINATOR', loc: newLoc })
346
- }
347
- } else if (char === '<') {
348
- this.advance()
349
- if (this.peek() === '=') {
350
- this.advance()
351
- this.tokens.push({ type: 'LTE', loc: newLoc })
352
- } else {
353
- this.tokens.push({ type: 'LT', loc: newLoc })
354
- }
355
- } else if (char === '+') {
356
- this.advance()
357
- this.tokens.push({ type: 'ADJACENT_COMBINATOR', loc: newLoc })
358
- } else if (char === '~') {
359
- this.advance()
360
- this.tokens.push({ type: 'SIBLING_COMBINATOR', loc: newLoc })
361
- } else if (char === '*') {
362
- this.advance()
363
- this.tokens.push({ type: 'UNIVERSAL_SELECTOR', loc: newLoc })
364
- } else if (char === '|') {
365
- this.advance()
366
- if (this.peek() === '|') {
367
- this.advance()
368
- this.tokens.push({ type: 'COLUMN_COMBINATOR', loc: newLoc })
369
- } else {
370
- this.tokens.push({ type: 'NAMESPACE', loc: newLoc })
371
- }
372
- } else if (char === '!') {
373
- this.advance()
374
- const word = this.readIdentifier()
375
- if (word.toLowerCase() === 'important') {
376
- this.tokens.push({ type: 'IMPORTANT', loc: newLoc })
377
- }
378
- } else if (char === '/') {
379
- this.advance()
380
- this.tokens.push({ type: 'SLASH', loc: newLoc })
381
- } else if (char === '%') {
382
- this.advance()
383
- this.tokens.push({ type: 'PERCENT', loc: newLoc })
384
- } else if (/[a-zA-ZÀ-ÿА-яぁ-ゟァ-ヿ一-龯_]/.test(char)) {
385
- const word = this.readWord()
386
- const tokenType = this.classifyWord(word)
387
- this.tokens.push({ type: tokenType, value: word, loc: newLoc })
388
- } else {
389
- this.advance()
390
- }
391
- }
392
-
393
- this.tokens.push({ type: 'EOF', loc: { line: this.line, column: this.column } })
394
- return this.tokens
395
- }
396
-
397
- readUnit() {
398
- const units = [
399
- 'px', 'em', 'rem', '%', 'vh', 'vw', 'vmin', 'vmax',
400
- 'cm', 'mm', 'in', 'pt', 'pc', 'ch', 'ex',
401
- 'deg', 'rad', 'grad', 'turn',
402
- 's', 'ms', 'fr', 'dpi', 'dpcm', 'dppx',
403
- 'cqw', 'cqh', 'cqi', 'cqb', 'cqmin', 'cqmax',
404
- 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh'
405
- ]
406
-
407
- for (const unit of units) {
408
- if (this.source.substring(this.pos, this.pos + unit.length).toLowerCase() === unit) {
409
- for (let i = 0; i < unit.length; i++) {
410
- this.advance()
411
- }
412
- return unit
413
- }
414
- }
415
- return null
416
- }
417
-
418
- classifyWord(word) {
419
- const lowerWord = word.toLowerCase()
420
-
421
- if (this.isEtherKeyword(lowerWord)) return 'ETHER_KEYWORD'
422
- if (this.isProperty(lowerWord)) return 'PROPERTY'
423
- if (this.isColorName(lowerWord)) return 'COLOR_NAME'
424
- if (this.isFunction(lowerWord)) return 'FUNCTION'
425
- if (this.isValue(lowerWord)) return 'VALUE_KEYWORD'
426
-
427
- return 'IDENTIFIER'
428
- }
429
-
430
- isEtherKeyword(word) {
431
- const keywords = [
432
- 'si', 'if', 'sinon', 'else', 'au survol', 'hover',
433
- 'au clic', 'active', 'au focus', 'focus',
434
- 'quand survole', 'quand survolé', 'quand clique', 'quand cliqué', 'quand focalise', 'quand focalisé',
435
- 'tous les', 'all', 'chaque', 'each', 'premier', 'first',
436
- 'dernier', 'last', 'impairs', 'odd', 'pairs', 'even',
437
- 'et', 'and', 'ou', 'or', 'pas', 'not',
438
- 'centre', 'centré', 'centered', 'invisible', 'cache', 'caché', 'hidden',
439
- 'cliquable', 'clickable', 'selectionnable', 'sélectionnable', 'selectable',
440
- 'cuando', 'si', 'sino', 'al pasar', 'al clicar', 'al enfocar',
441
- 'cuando pasado', 'cuando clicado', 'cuando enfocado',
442
- 'todos', 'cada', 'primero', 'ultimo', 'último', 'impares', 'pares',
443
- 'y', 'o', 'no', 'centrado', 'oculto', 'seleccionable',
444
- 'если', 'иначе', 'при наведении', 'при клике', 'при фокусе',
445
- 'когда наведен', 'когда кликнут', 'когда сфокусирован',
446
- 'все', 'каждый', 'первый', 'последний', 'нечетные', 'четные',
447
- 'и', 'или', 'не', 'центрированный', 'скрытый', 'выбираемый',
448
- '如果', '否则', '悬停时', '点击时', '聚焦时',
449
- '当悬停', '当点击', '当聚焦',
450
- '所有', '每个', '第一个', '最后一个', '奇数', '偶数',
451
- '和', '或', '非', '居中', '隐藏', '可选择',
452
- 'もし', 'そうでなければ', 'ホバー時', 'クリック時', 'フォーカス時',
453
- 'ホバーされた時', 'クリックされた時', 'フォーカスされた時',
454
- 'すべて', '各', '最初', '最後', '奇数', '偶数',
455
- 'かつ', 'または', 'ではない', '中央', '非表示', '選択可能'
456
- ]
457
- return keywords.includes(word.toLowerCase())
458
- }
459
-
460
- isProperty(word) {
461
- for (const category of Object.values(this.i18n)) {
462
- if (typeof category === 'object' && category !== null) {
463
- for (const item of Object.values(category)) {
464
- if (typeof item === 'object' && item !== null) {
465
- for (const translation of Object.values(item)) {
466
- if (typeof translation === 'string' && translation.toLowerCase() === word.toLowerCase()) {
467
- return true
468
- }
469
- }
470
- }
471
- }
472
- }
473
- }
474
- return false
475
- }
476
-
477
- isColorName(word) {
478
- const colors = this.i18n.colors || {}
479
- for (const color of Object.values(colors)) {
480
- if (typeof color === 'object') {
481
- for (const translation of Object.values(color)) {
482
- if (typeof translation === 'string' && translation.toLowerCase() === word.toLowerCase()) {
483
- return true
484
- }
485
- }
486
- }
487
- }
488
- return false
489
- }
490
-
491
- isFunction(word) {
492
- const functions = [
493
- 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch',
494
- 'url', 'calc', 'min', 'max', 'clamp', 'var',
495
- 'linear-gradient', 'radial-gradient', 'conic-gradient',
496
- 'repeat', 'minmax', 'fit-content',
497
- 'translate', 'translateX', 'translateY', 'translateZ',
498
- 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'rotate3d',
499
- 'scale', 'scaleX', 'scaleY', 'scaleZ', 'scale3d',
500
- 'skew', 'skewX', 'skewY', 'matrix', 'matrix3d', 'perspective',
501
- 'blur', 'brightness', 'contrast', 'drop-shadow', 'grayscale',
502
- 'hue-rotate', 'invert', 'opacity', 'saturate', 'sepia',
503
- 'circle', 'ellipse', 'polygon', 'path', 'inset',
504
- 'cubic-bezier', 'steps', 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out',
505
- 'attr', 'counter', 'counters', 'env', 'image-set', 'cross-fade',
506
- 'color-mix', 'light-dark'
507
- ]
508
- return functions.includes(word.toLowerCase())
509
- }
510
-
511
- isValue(word) {
512
- const values = [
513
- 'auto', 'none', 'inherit', 'initial', 'unset', 'revert', 'revert-layer',
514
- 'block', 'inline', 'flex', 'grid', 'contents', 'flow-root',
515
- 'absolute', 'relative', 'fixed', 'sticky', 'static',
516
- 'visible', 'hidden', 'scroll', 'clip',
517
- 'solid', 'dashed', 'dotted', 'double', 'groove', 'ridge', 'inset', 'outset',
518
- 'normal', 'bold', 'bolder', 'lighter',
519
- 'italic', 'oblique',
520
- 'left', 'right', 'center', 'justify', 'start', 'end',
521
- 'top', 'bottom', 'middle', 'baseline',
522
- 'wrap', 'nowrap', 'pre', 'pre-wrap', 'pre-line', 'break-spaces',
523
- 'pointer', 'default', 'text', 'wait', 'help', 'not-allowed', 'grab', 'grabbing',
524
- 'cover', 'contain', 'fill', 'fit', 'scale-down',
525
- 'repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'space', 'round',
526
- 'row', 'column', 'row-reverse', 'column-reverse',
527
- 'stretch', 'flex-start', 'flex-end', 'space-between', 'space-around', 'space-evenly',
528
- 'uppercase', 'lowercase', 'capitalize',
529
- 'underline', 'overline', 'line-through', 'wavy',
530
- 'forwards', 'backwards', 'both', 'alternate', 'alternate-reverse',
531
- 'running', 'paused', 'infinite'
532
- ]
533
- return values.includes(word.toLowerCase())
534
- }
535
- }
536
-
537
- class CSSParser {
538
- constructor(i18nPath = null) {
539
- this.i18n = {}
540
- this.tokens = []
541
- this.pos = 0
542
- this.currentIndent = 0
543
-
544
- if (i18nPath) {
545
- this.loadI18n(i18nPath)
546
- }
547
- }
548
-
549
- loadI18n(filePath) {
550
- try {
551
- const content = fs.readFileSync(filePath, 'utf-8')
552
- this.i18n = JSON.parse(content)
553
- } catch (e) {
554
- console.error(`Erreur chargement i18n: ${e.message}`)
555
- this.i18n = {}
556
- }
557
- }
558
-
559
- setI18n(i18nData) {
560
- this.i18n = i18nData
561
- }
562
-
563
- parse(source) {
564
- const lexer = new CSSLexer(source, this.i18n)
565
- this.tokens = lexer.tokenize()
566
- this.pos = 0
567
- return this.parseStyleSheet()
568
- }
569
-
570
- peek(offset = 0) {
571
- const idx = this.pos + offset
572
- return idx < this.tokens.length ? this.tokens[idx] : { type: 'EOF' }
573
- }
574
-
575
- advance() {
576
- const token = this.tokens[this.pos]
577
- this.pos++
578
- return token
579
- }
580
-
581
- expect(type) {
582
- const token = this.peek()
583
- if (token.type !== type) {
584
- throw new Error(`Attendu ${type}, reçu ${token.type} à ligne ${token.loc?.line}`)
585
- }
586
- return this.advance()
587
- }
588
-
589
- match(...types) {
590
- return types.includes(this.peek().type)
591
- }
592
-
593
- skipNewlines() {
594
- while (this.match('NEWLINE', 'INDENT')) {
595
- if (this.match('INDENT')) {
596
- this.currentIndent = this.peek().value
597
- }
598
- this.advance()
599
- }
600
- }
601
-
602
- parseStyleSheet() {
603
- const rules = []
604
-
605
- this.skipNewlines()
606
-
607
- while (!this.match('EOF')) {
608
- const rule = this.parseRule()
609
- if (rule) {
610
- rules.push(rule)
611
- }
612
- this.skipNewlines()
613
- }
614
-
615
- return new AST.StyleSheet(rules)
616
- }
617
-
618
- parseRule() {
619
- const token = this.peek()
620
-
621
- if (token.type === 'AT_KEYWORD') {
622
- return this.parseAtRule()
623
- }
624
-
625
- if (token.type === 'ETHER_KEYWORD') {
626
- return this.parseEtherConstruct()
627
- }
628
-
629
- return this.parseStyleRule()
630
- }
631
-
632
- parseAtRule() {
633
- const atToken = this.advance()
634
- const name = atToken.value.toLowerCase()
635
-
636
- switch (name) {
637
- case 'si':
638
- case 'if':
639
- case 'media':
640
- return this.parseMediaQuery(atToken.loc)
641
- case 'keyframes':
642
- case 'animation':
643
- return this.parseKeyframes(atToken.loc)
644
- case 'font-face':
645
- case 'police':
646
- return this.parseFontFace(atToken.loc)
647
- case 'import':
648
- case 'importer':
649
- return this.parseImport(atToken.loc)
650
- case 'supports':
651
- case 'supporte':
652
- return this.parseSupports(atToken.loc)
653
- case 'layer':
654
- case 'couche':
655
- return this.parseLayer(atToken.loc)
656
- case 'container':
657
- case 'conteneur':
658
- return this.parseContainer(atToken.loc)
659
- case 'scope':
660
- case 'portée':
661
- return this.parseScope(atToken.loc)
662
- case 'page':
663
- return this.parsePage(atToken.loc)
664
- case 'property':
665
- case 'propriété':
666
- return this.parsePropertyRule(atToken.loc)
667
- case 'counter-style':
668
- case 'style compteur':
669
- return this.parseCounterStyle(atToken.loc)
670
- case 'starting-style':
671
- case 'style initial':
672
- return this.parseStartingStyle(atToken.loc)
673
- default:
674
- return this.parseGenericAtRule(name, atToken.loc)
675
- }
676
- }
677
-
678
- parseMediaQuery(loc) {
679
- const conditions = []
680
- let mediaType = null
681
-
682
- this.skipNewlines()
683
-
684
- while (!this.match('NEWLINE', 'EOF', 'LBRACE')) {
685
- const token = this.peek()
686
-
687
- if (token.type === 'IDENTIFIER' || token.type === 'ETHER_KEYWORD') {
688
- const word = this.advance().value.toLowerCase()
689
- if (['screen', 'print', 'all', 'écran', 'impression', 'tout'].includes(word)) {
690
- mediaType = this.translateMediaType(word)
691
- } else if (['and', 'et', 'or', 'ou', 'not', 'pas', 'only', 'seulement'].includes(word)) {
692
- conditions.push({ type: 'operator', value: word })
693
- } else {
694
- conditions.push(this.parseMediaFeature(word))
695
- }
696
- } else if (token.type === 'LPAREN') {
697
- this.advance()
698
- const feature = this.parseMediaFeature()
699
- this.expect('RPAREN')
700
- conditions.push(feature)
701
- } else if (token.type === 'NUMBER') {
702
- const num = this.advance()
703
- conditions.push({ type: 'value', value: num.value, unit: num.unit })
704
- } else if (token.type === 'GT' || token.type === 'LT' || token.type === 'GTE' || token.type === 'LTE') {
705
- conditions.push({ type: 'operator', value: this.advance().type })
706
- } else {
707
- this.advance()
708
- }
709
- }
710
-
711
- const rules = this.parseBlock()
712
-
713
- return new AST.MediaQuery(mediaType, conditions, rules, loc)
714
- }
715
-
716
- parseMediaFeature(name = null) {
717
- if (!name && this.match('IDENTIFIER', 'PROPERTY')) {
718
- name = this.advance().value
719
- }
720
-
721
- let value = null
722
- let operator = null
723
-
724
- if (this.match('COLON')) {
725
- this.advance()
726
- value = this.parseValue()
727
- } else if (this.match('GTE', 'LTE', 'GT', 'LT')) {
728
- operator = this.advance().type
729
- value = this.parseValue()
730
- }
731
-
732
- const cssFeature = this.translateMediaFeature(name)
733
-
734
- return new AST.MediaCondition(cssFeature, value, operator)
735
- }
736
-
737
- translateMediaType(type) {
738
- const map = {
739
- 'ecran': 'screen', 'écran': 'screen', 'screen': 'screen', 'pantalla': 'screen', 'экран': 'screen', '屏幕': 'screen', '画面': 'screen',
740
- 'impression': 'print', 'print': 'print', 'impresion': 'print', 'impresión': 'print', 'печать': 'print', '打印': 'print', '印刷': 'print',
741
- 'tout': 'all', 'all': 'all', 'todo': 'all', 'все': 'all', '全部': 'all', 'すべて': 'all'
742
- }
743
- return map[type.toLowerCase()] || type
744
- }
745
-
746
- translateMediaFeature(feature) {
747
- const map = {
748
- 'largeur': 'width', 'width': 'width', 'ancho': 'width', 'ширина': 'width', '宽度': 'width', '幅': 'width',
749
- 'hauteur': 'height', 'height': 'height', 'alto': 'height', 'altura': 'height', 'высота': 'height', '高度': 'height', '高さ': 'height',
750
- 'largeur min': 'min-width', 'min-width': 'min-width', 'ancho minimo': 'min-width', 'ancho mínimo': 'min-width', 'мин ширина': 'min-width', '最小宽度': 'min-width', '最小幅': 'min-width',
751
- 'largeur max': 'max-width', 'max-width': 'max-width', 'ancho maximo': 'max-width', 'ancho máximo': 'max-width', 'макс ширина': 'max-width', '最大宽度': 'max-width', '最大幅': 'max-width',
752
- 'hauteur min': 'min-height', 'min-height': 'min-height', 'alto minimo': 'min-height', 'alto mínimo': 'min-height', 'мин высота': 'min-height', '最小高度': 'min-height', '最小高さ': 'min-height',
753
- 'hauteur max': 'max-height', 'max-height': 'max-height', 'alto maximo': 'max-height', 'alto máximo': 'max-height', 'макс высота': 'max-height', '最大高度': 'max-height', '最大高さ': 'max-height',
754
- 'orientation': 'orientation', 'orientacion': 'orientation', 'orientación': 'orientation', 'ориентация': 'orientation', '方向': 'orientation', '向き': 'orientation',
755
- 'paysage': 'landscape', 'landscape': 'landscape', 'paisaje': 'landscape', 'альбомная': 'landscape', '横向': 'landscape', '横': 'landscape',
756
- 'portrait': 'portrait', 'retrato': 'portrait', 'книжная': 'portrait', '纵向': 'portrait', '縦': 'portrait',
757
- 'resolution': 'resolution', 'résolution': 'resolution', 'resolución': 'resolution', 'resolucion': 'resolution', 'разрешение': 'resolution', '分辨率': 'resolution', '解像度': 'resolution',
758
- 'couleur': 'color', 'color': 'color', 'цвет': 'color', '颜色': 'color', '色': 'color',
759
- 'ratio aspect': 'aspect-ratio', 'aspect-ratio': 'aspect-ratio', 'relacion aspecto': 'aspect-ratio', 'relación aspecto': 'aspect-ratio', 'соотношение сторон': 'aspect-ratio', '宽高比': 'aspect-ratio', 'アスペクト比': 'aspect-ratio',
760
- 'preference couleur': 'prefers-color-scheme', 'préférence couleur': 'prefers-color-scheme', 'prefers-color-scheme': 'prefers-color-scheme', 'preferencia color': 'prefers-color-scheme', 'предпочтение цвета': 'prefers-color-scheme', '颜色偏好': 'prefers-color-scheme', '色設定': 'prefers-color-scheme',
761
- 'mouvement reduit': 'prefers-reduced-motion', 'mouvement réduit': 'prefers-reduced-motion', 'prefers-reduced-motion': 'prefers-reduced-motion', 'movimiento reducido': 'prefers-reduced-motion', 'уменьшенное движение': 'prefers-reduced-motion', '减少动画': 'prefers-reduced-motion', '動作軽減': 'prefers-reduced-motion',
762
- 'contraste eleve': 'prefers-contrast', 'contraste élevé': 'prefers-contrast', 'prefers-contrast': 'prefers-contrast', 'contraste alto': 'prefers-contrast', 'высокий контраст': 'prefers-contrast', '高对比度': 'prefers-contrast', 'ハイコントラスト': 'prefers-contrast'
763
- }
764
- return map[feature?.toLowerCase()] || feature
765
- }
766
-
767
- parseKeyframes(loc) {
768
- this.skipNewlines()
769
- let name = ''
770
- if (this.match('IDENTIFIER', 'STRING')) {
771
- name = this.advance().value
772
- }
773
-
774
- const keyframes = []
775
- this.skipNewlines()
776
-
777
- if (this.match('LBRACE')) {
778
- this.advance()
779
- this.skipNewlines()
780
-
781
- while (!this.match('RBRACE', 'EOF')) {
782
- const keyframe = this.parseKeyframe()
783
- if (keyframe) keyframes.push(keyframe)
784
- this.skipNewlines()
785
- }
786
-
787
- if (this.match('RBRACE')) this.advance()
788
- } else {
789
- const baseIndent = this.currentIndent
790
- while (!this.match('EOF')) {
791
- this.skipNewlines()
792
- if (this.currentIndent <= baseIndent && !this.match('NUMBER', 'IDENTIFIER')) {
793
- break
794
- }
795
- const keyframe = this.parseKeyframe()
796
- if (keyframe) keyframes.push(keyframe)
797
- }
798
- }
799
-
800
- return new AST.KeyframesRule(name, keyframes, loc)
801
- }
802
-
803
- parseKeyframe() {
804
- let selector = null
805
-
806
- if (this.match('NUMBER')) {
807
- const num = this.advance()
808
- selector = num.value + (num.unit || '%')
809
- } else if (this.match('IDENTIFIER')) {
810
- const word = this.advance().value.toLowerCase()
811
- if (['from', 'début', 'inicio', 'начало', '开始', '開始'].includes(word)) {
812
- selector = 'from'
813
- } else if (['to', 'fin', 'final', 'конец', '结束', '終了'].includes(word)) {
814
- selector = 'to'
815
- } else {
816
- selector = word
817
- }
818
- }
819
-
820
- const declarations = this.parseDeclarations()
821
-
822
- return new AST.Keyframe(selector, declarations)
823
- }
824
-
825
- parseFontFace(loc) {
826
- const declarations = this.parseDeclarations()
827
- return new AST.FontFaceRule(declarations, loc)
828
- }
829
-
830
- parseImport(loc) {
831
- this.skipNewlines()
832
- let url = ''
833
-
834
- if (this.match('STRING')) {
835
- url = this.advance().value
836
- } else if (this.match('FUNCTION') && this.peek().value.toLowerCase() === 'url') {
837
- this.advance()
838
- if (this.match('LPAREN')) {
839
- this.advance()
840
- if (this.match('STRING')) {
841
- url = this.advance().value
842
- }
843
- this.expect('RPAREN')
844
- }
845
- }
846
-
847
- let mediaQueries = null
848
- let layer = null
849
- let supports = null
850
-
851
- while (!this.match('NEWLINE', 'EOF')) {
852
- const token = this.peek()
853
- if (token.type === 'IDENTIFIER') {
854
- const word = token.value.toLowerCase()
855
- if (['layer', 'couche'].includes(word)) {
856
- this.advance()
857
- if (this.match('LPAREN')) {
858
- this.advance()
859
- layer = this.match('IDENTIFIER') ? this.advance().value : null
860
- this.expect('RPAREN')
861
- } else {
862
- layer = true
863
- }
864
- } else if (['supports', 'supporte'].includes(word)) {
865
- this.advance()
866
- if (this.match('LPAREN')) {
867
- this.advance()
868
- supports = this.parseSupportsCondition()
869
- this.expect('RPAREN')
870
- }
871
- } else {
872
- break
873
- }
874
- } else {
875
- break
876
- }
877
- }
878
-
879
- return new AST.ImportRule(url, mediaQueries, layer, supports, loc)
880
- }
881
-
882
- parseSupports(loc) {
883
- const condition = this.parseSupportsCondition()
884
- const rules = this.parseBlock()
885
- return new AST.SupportsRule(condition, rules, loc)
886
- }
887
-
888
- parseSupportsCondition() {
889
- const conditions = []
890
-
891
- while (!this.match('NEWLINE', 'EOF', 'LBRACE', 'RPAREN')) {
892
- if (this.match('LPAREN')) {
893
- this.advance()
894
- const property = this.match('PROPERTY', 'IDENTIFIER') ? this.advance().value : null
895
- let value = null
896
- if (this.match('COLON')) {
897
- this.advance()
898
- value = this.parseValue()
899
- }
900
- this.expect('RPAREN')
901
- conditions.push({ property, value })
902
- } else if (this.match('IDENTIFIER', 'ETHER_KEYWORD')) {
903
- const word = this.advance().value.toLowerCase()
904
- if (['and', 'et', 'or', 'ou', 'not', 'pas'].includes(word)) {
905
- conditions.push({ operator: word })
906
- }
907
- } else {
908
- this.advance()
909
- }
910
- }
911
-
912
- return conditions
913
- }
914
-
915
- parseLayer(loc) {
916
- this.skipNewlines()
917
- let name = null
918
-
919
- if (this.match('IDENTIFIER')) {
920
- name = this.advance().value
921
- }
922
-
923
- if (this.match('LBRACE') || (this.match('NEWLINE') && this.tokens[this.pos + 1]?.type === 'INDENT')) {
924
- const rules = this.parseBlock()
925
- return new AST.LayerRule(name, rules, loc)
926
- }
927
-
928
- return new AST.LayerRule(name, null, loc)
929
- }
930
-
931
- parseContainer(loc) {
932
- this.skipNewlines()
933
- let name = null
934
- const conditions = []
935
-
936
- if (this.match('IDENTIFIER')) {
937
- const word = this.peek().value.toLowerCase()
938
- if (!['size', 'inline-size', 'block-size', 'taille'].includes(word)) {
939
- name = this.advance().value
940
- }
941
- }
942
-
943
- while (!this.match('NEWLINE', 'EOF', 'LBRACE')) {
944
- if (this.match('LPAREN')) {
945
- this.advance()
946
- const feature = this.parseMediaFeature()
947
- this.expect('RPAREN')
948
- conditions.push(feature)
949
- } else {
950
- this.advance()
951
- }
952
- }
953
-
954
- const rules = this.parseBlock()
955
-
956
- return new AST.ContainerQuery(name, conditions, rules, loc)
957
- }
958
-
959
- parseScope(loc) {
960
- let start = null
961
- let end = null
962
-
963
- if (this.match('LPAREN')) {
964
- this.advance()
965
- start = this.parseSelector()
966
- this.expect('RPAREN')
967
- }
968
-
969
- if (this.match('IDENTIFIER') && this.peek().value.toLowerCase() === 'to') {
970
- this.advance()
971
- if (this.match('LPAREN')) {
972
- this.advance()
973
- end = this.parseSelector()
974
- this.expect('RPAREN')
975
- }
976
- }
977
-
978
- const rules = this.parseBlock()
979
-
980
- return new AST.ScopeRule(start, end, rules, loc)
981
- }
982
-
983
- parsePage(loc) {
984
- let selector = null
985
-
986
- if (this.match('COLON')) {
987
- this.advance()
988
- selector = this.match('IDENTIFIER') ? this.advance().value : null
989
- }
990
-
991
- const declarations = this.parseDeclarations()
992
-
993
- return new AST.PageRule(selector, declarations, loc)
994
- }
995
-
996
- parsePropertyRule(loc) {
997
- this.skipNewlines()
998
- let name = ''
999
-
1000
- if (this.match('IDENTIFIER')) {
1001
- name = '--' + this.advance().value
1002
- }
1003
-
1004
- const declarations = this.parseDeclarations()
1005
-
1006
- let syntax = '*'
1007
- let inherits = false
1008
- let initialValue = null
1009
-
1010
- for (const decl of declarations) {
1011
- if (decl.property.cssName === 'syntax') {
1012
- syntax = decl.value.value
1013
- } else if (decl.property.cssName === 'inherits') {
1014
- inherits = decl.value.value === 'true'
1015
- } else if (decl.property.cssName === 'initial-value') {
1016
- initialValue = decl.value
1017
- }
1018
- }
1019
-
1020
- return new AST.PropertyRule(name, syntax, inherits, initialValue, loc)
1021
- }
1022
-
1023
- parseCounterStyle(loc) {
1024
- this.skipNewlines()
1025
- let name = ''
1026
-
1027
- if (this.match('IDENTIFIER')) {
1028
- name = this.advance().value
1029
- }
1030
-
1031
- const declarations = this.parseDeclarations()
1032
-
1033
- return new AST.CounterStyleRule(name, declarations, loc)
1034
- }
1035
-
1036
- parseStartingStyle(loc) {
1037
- const rules = this.parseBlock()
1038
- return new AST.StartingStyleRule(rules, loc)
1039
- }
1040
-
1041
- parseGenericAtRule(name, loc) {
1042
- while (!this.match('NEWLINE', 'EOF', 'LBRACE')) {
1043
- this.advance()
1044
- }
1045
-
1046
- if (this.match('LBRACE')) {
1047
- const rules = this.parseBlock()
1048
- return { type: 'AtRule', name, rules, loc }
1049
- }
1050
-
1051
- return { type: 'AtRule', name, loc }
1052
- }
1053
-
1054
- parseEtherConstruct() {
1055
- const token = this.advance()
1056
- const keyword = token.value.toLowerCase()
1057
-
1058
- if (['au survol', 'hover', 'quand survolé', 'en survol'].includes(keyword)) {
1059
- return this.parseEtherState(':hover', token.loc)
1060
- }
1061
- if (['au clic', 'active', 'quand cliqué', 'en clic'].includes(keyword)) {
1062
- return this.parseEtherState(':active', token.loc)
1063
- }
1064
- if (['au focus', 'focus', 'quand focalisé', 'en focus'].includes(keyword)) {
1065
- return this.parseEtherState(':focus', token.loc)
1066
- }
1067
- if (['si', 'if'].includes(keyword)) {
1068
- return this.parseEtherCondition(token.loc)
1069
- }
1070
- if (['centré', 'centered'].includes(keyword)) {
1071
- return this.parseEtherAlias('centré', token.loc)
1072
- }
1073
- if (['invisible'].includes(keyword)) {
1074
- return this.parseEtherAlias('invisible', token.loc)
1075
- }
1076
- if (['caché', 'hidden'].includes(keyword)) {
1077
- return this.parseEtherAlias('caché', token.loc)
1078
- }
1079
-
1080
- return null
1081
- }
1082
-
1083
- parseEtherState(cssState, loc) {
1084
- const declarations = this.parseDeclarations()
1085
- return new AST.EtherState(cssState, declarations, loc)
1086
- }
1087
-
1088
- parseEtherCondition(loc) {
1089
- const condition = []
1090
-
1091
- while (!this.match('NEWLINE', 'EOF')) {
1092
- condition.push(this.advance())
1093
- }
1094
-
1095
- const rules = this.parseBlock()
1096
- let elseRules = null
1097
-
1098
- this.skipNewlines()
1099
- if (this.match('ETHER_KEYWORD')) {
1100
- const word = this.peek().value.toLowerCase()
1101
- if (['sinon', 'else'].includes(word)) {
1102
- this.advance()
1103
- elseRules = this.parseBlock()
1104
- }
1105
- }
1106
-
1107
- return new AST.EtherCondition(condition, rules, elseRules, loc)
1108
- }
1109
-
1110
- parseEtherAlias(alias, loc) {
1111
- const aliasMap = {
1112
- 'centré': [
1113
- new AST.Declaration(
1114
- new AST.Property('alignement texte', 'text-align'),
1115
- new AST.Value('centre', 'center')
1116
- ),
1117
- new AST.Declaration(
1118
- new AST.Property('marge', 'margin'),
1119
- new AST.Value('auto', 'auto')
1120
- )
1121
- ],
1122
- 'invisible': [
1123
- new AST.Declaration(
1124
- new AST.Property('affichage', 'display'),
1125
- new AST.Value('aucun', 'none')
1126
- )
1127
- ],
1128
- 'caché': [
1129
- new AST.Declaration(
1130
- new AST.Property('visibilité', 'visibility'),
1131
- new AST.Value('caché', 'hidden')
1132
- )
1133
- ]
1134
- }
1135
-
1136
- return new AST.EtherAlias(alias, aliasMap[alias] || [], loc)
1137
- }
1138
-
1139
- parseStyleRule() {
1140
- const selectors = this.parseSelectorList()
1141
- const declarations = this.parseDeclarations()
1142
- const nestedRules = []
1143
-
1144
- return new AST.Rule(selectors, declarations, this.peek().loc)
1145
- }
1146
-
1147
- parseSelectorList() {
1148
- const selectors = []
1149
-
1150
- selectors.push(this.parseSelector())
1151
-
1152
- while (this.match('COMMA')) {
1153
- this.advance()
1154
- this.skipNewlines()
1155
- selectors.push(this.parseSelector())
1156
- }
1157
-
1158
- return new AST.SelectorList(selectors)
1159
- }
1160
-
1161
- parseSelector() {
1162
- let left = this.parseCompoundSelector()
1163
-
1164
- while (this.match('CHILD_COMBINATOR', 'ADJACENT_COMBINATOR', 'SIBLING_COMBINATOR', 'COLUMN_COMBINATOR')) {
1165
- const combinator = this.advance().type
1166
- const right = this.parseCompoundSelector()
1167
- left = new AST.ComplexSelector(left, this.getCombinatorSymbol(combinator), right)
1168
- }
1169
-
1170
- if (this.match('IDENTIFIER', 'CLASS_SELECTOR', 'UNIVERSAL_SELECTOR', 'LBRACKET', 'PSEUDO_CLASS', 'PSEUDO_ELEMENT')) {
1171
- const right = this.parseCompoundSelector()
1172
- left = new AST.ComplexSelector(left, ' ', right)
1173
- }
1174
-
1175
- return left
1176
- }
1177
-
1178
- getCombinatorSymbol(type) {
1179
- const map = {
1180
- 'CHILD_COMBINATOR': '>',
1181
- 'ADJACENT_COMBINATOR': '+',
1182
- 'SIBLING_COMBINATOR': '~',
1183
- 'COLUMN_COMBINATOR': '||'
1184
- }
1185
- return map[type] || ' '
1186
- }
1187
-
1188
- parseCompoundSelector() {
1189
- const selectors = []
1190
-
1191
- while (true) {
1192
- if (this.match('UNIVERSAL_SELECTOR')) {
1193
- this.advance()
1194
- selectors.push(new AST.SimpleSelector('universal', '*'))
1195
- } else if (this.match('IDENTIFIER')) {
1196
- const tag = this.advance().value
1197
- selectors.push(new AST.SimpleSelector('type', tag))
1198
- } else if (this.match('CLASS_SELECTOR')) {
1199
- const className = this.advance().value
1200
- selectors.push(new AST.SimpleSelector('class', className))
1201
- } else if (this.match('HEX_COLOR') && selectors.length === 0) {
1202
- const id = this.advance().value
1203
- selectors.push(new AST.SimpleSelector('id', id))
1204
- } else if (this.match('LBRACKET')) {
1205
- selectors.push(this.parseAttributeSelector())
1206
- } else if (this.match('PSEUDO_CLASS')) {
1207
- selectors.push(this.parsePseudoClassSelector())
1208
- } else if (this.match('PSEUDO_ELEMENT')) {
1209
- selectors.push(this.parsePseudoElementSelector())
1210
- } else {
1211
- break
1212
- }
1213
- }
1214
-
1215
- if (selectors.length === 0) {
1216
- return null
1217
- }
1218
-
1219
- if (selectors.length === 1) {
1220
- return selectors[0]
1221
- }
1222
-
1223
- return new AST.CompoundSelector(selectors)
1224
- }
1225
-
1226
- parseAttributeSelector() {
1227
- this.expect('LBRACKET')
1228
-
1229
- const attribute = this.match('IDENTIFIER') ? this.advance().value : ''
1230
- let operator = null
1231
- let value = null
1232
- let flags = null
1233
-
1234
- if (this.match('EQUALS')) {
1235
- this.advance()
1236
- operator = '='
1237
- } else if (this.peek().type === 'IDENTIFIER') {
1238
- const op = this.peek().value
1239
- if (['~=', '|=', '^=', '$=', '*='].includes(op)) {
1240
- operator = this.advance().value
1241
- }
1242
- }
1243
-
1244
- if (operator && this.match('STRING', 'IDENTIFIER')) {
1245
- value = this.advance().value
1246
- }
1247
-
1248
- if (this.match('IDENTIFIER')) {
1249
- const flag = this.peek().value.toLowerCase()
1250
- if (['i', 's'].includes(flag)) {
1251
- flags = this.advance().value
1252
- }
1253
- }
1254
-
1255
- this.expect('RBRACKET')
1256
-
1257
- return new AST.AttributeSelector(attribute, operator, value, flags)
1258
- }
1259
-
1260
- parsePseudoClassSelector() {
1261
- const name = this.advance().value
1262
- let argument = null
1263
-
1264
- if (this.match('LPAREN')) {
1265
- this.advance()
1266
- const args = []
1267
- while (!this.match('RPAREN', 'EOF')) {
1268
- args.push(this.advance())
1269
- }
1270
- argument = args.map(t => t.value).join('')
1271
- this.expect('RPAREN')
1272
- }
1273
-
1274
- return new AST.PseudoClassSelector(name, argument)
1275
- }
1276
-
1277
- parsePseudoElementSelector() {
1278
- const name = this.advance().value
1279
- return new AST.PseudoElementSelector(name)
1280
- }
1281
-
1282
- parseDeclarations() {
1283
- const declarations = []
1284
-
1285
- this.skipNewlines()
1286
-
1287
- if (this.match('LBRACE')) {
1288
- this.advance()
1289
- this.skipNewlines()
1290
-
1291
- while (!this.match('RBRACE', 'EOF')) {
1292
- const decl = this.parseDeclaration()
1293
- if (decl) declarations.push(decl)
1294
- this.skipNewlines()
1295
- }
1296
-
1297
- if (this.match('RBRACE')) this.advance()
1298
- } else {
1299
- const baseIndent = this.currentIndent
1300
-
1301
- while (!this.match('EOF')) {
1302
- this.skipNewlines()
1303
-
1304
- if (this.currentIndent <= baseIndent && declarations.length > 0) {
1305
- break
1306
- }
1307
-
1308
- if (this.match('PROPERTY', 'IDENTIFIER')) {
1309
- const decl = this.parseDeclaration()
1310
- if (decl) declarations.push(decl)
1311
- } else {
1312
- break
1313
- }
1314
- }
1315
- }
1316
-
1317
- return declarations
1318
- }
1319
-
1320
- parseBlock() {
1321
- const rules = []
1322
-
1323
- this.skipNewlines()
1324
-
1325
- if (this.match('LBRACE')) {
1326
- this.advance()
1327
- this.skipNewlines()
1328
-
1329
- while (!this.match('RBRACE', 'EOF')) {
1330
- const rule = this.parseRule()
1331
- if (rule) rules.push(rule)
1332
- this.skipNewlines()
1333
- }
1334
-
1335
- if (this.match('RBRACE')) this.advance()
1336
- } else {
1337
- const baseIndent = this.currentIndent
1338
-
1339
- while (!this.match('EOF')) {
1340
- this.skipNewlines()
1341
-
1342
- if (this.currentIndent <= baseIndent && rules.length > 0) {
1343
- break
1344
- }
1345
-
1346
- const rule = this.parseRule()
1347
- if (rule) {
1348
- rules.push(rule)
1349
- } else {
1350
- break
1351
- }
1352
- }
1353
- }
1354
-
1355
- return rules
1356
- }
1357
-
1358
- parseDeclaration() {
1359
- const propToken = this.peek()
1360
-
1361
- if (!this.match('PROPERTY', 'IDENTIFIER')) {
1362
- return null
1363
- }
1364
-
1365
- const propertyName = this.advance().value
1366
- const cssProperty = this.translateProperty(propertyName)
1367
-
1368
- if (!this.match('EQUALS', 'COLON')) {
1369
- return null
1370
- }
1371
- this.advance()
1372
-
1373
- const value = this.parseValue()
1374
-
1375
- let important = false
1376
- if (this.match('IMPORTANT')) {
1377
- this.advance()
1378
- important = true
1379
- }
1380
-
1381
- return new AST.Declaration(
1382
- new AST.Property(propertyName, cssProperty, propToken.loc),
1383
- value,
1384
- important,
1385
- propToken.loc
1386
- )
1387
- }
1388
-
1389
- translateProperty(name) {
1390
- const lowerName = name.toLowerCase()
1391
-
1392
- for (const category of Object.values(this.i18n)) {
1393
- if (typeof category === 'object' && category !== null) {
1394
- for (const [key, item] of Object.entries(category)) {
1395
- if (typeof item === 'object' && item !== null && item.css) {
1396
- for (const [lang, translation] of Object.entries(item)) {
1397
- if (lang !== 'css' && typeof translation === 'string' && translation.toLowerCase() === lowerName) {
1398
- return item.css
1399
- }
1400
- }
1401
- }
1402
- }
1403
- }
1404
- }
1405
-
1406
- const directMap = {
1407
- 'couleur': 'color', 'color': 'color', 'цвет': 'color', '颜色': 'color', '色': 'color',
1408
- 'fond': 'background', 'background': 'background', 'fondo': 'background', 'фон': 'background', '背景': 'background',
1409
- 'largeur': 'width', 'width': 'width', 'ancho': 'width', 'ширина': 'width', '宽度': 'width', '幅': 'width',
1410
- 'hauteur': 'height', 'height': 'height', 'alto': 'height', 'altura': 'height', 'высота': 'height', '高度': 'height', '高さ': 'height',
1411
- 'marge autour': 'margin', 'margin': 'margin', 'margen': 'margin', 'внешний отступ': 'margin', '外边距': 'margin', '外側余白': 'margin',
1412
- 'marge dedans': 'padding', 'padding': 'padding', 'relleno': 'padding', 'внутренний отступ': 'padding', '内边距': 'padding', '内側余白': 'padding',
1413
- 'bordure': 'border', 'border': 'border', 'borde': 'border', 'граница': 'border', '边框': 'border', 'ボーダー': 'border',
1414
- 'arrondi': 'border-radius', 'border-radius': 'border-radius', 'radio borde': 'border-radius', 'скругление': 'border-radius', '圆角': 'border-radius', '角丸': 'border-radius',
1415
- 'ombre': 'box-shadow', 'box-shadow': 'box-shadow', 'sombra': 'box-shadow', 'тень': 'box-shadow', '阴影': 'box-shadow', '影': 'box-shadow',
1416
- 'police': 'font-family', 'font-family': 'font-family', 'fuente': 'font-family', 'familia fuente': 'font-family', 'шрифт': 'font-family', '字体': 'font-family', 'フォント': 'font-family',
1417
- 'taille': 'font-size', 'font-size': 'font-size', 'tamano': 'font-size', 'tamaño': 'font-size', 'размер шрифта': 'font-size', '字号': 'font-size', '文字サイズ': 'font-size',
1418
- 'poids': 'font-weight', 'font-weight': 'font-weight', 'peso': 'font-weight', 'насыщенность': 'font-weight', '字重': 'font-weight', 'フォントウェイト': 'font-weight',
1419
- 'style': 'font-style', 'font-style': 'font-style', 'estilo': 'font-style', 'estilo fuente': 'font-style', 'начертание': 'font-style', '字体样式': 'font-style', 'フォントスタイル': 'font-style',
1420
- 'alignement': 'text-align', 'text-align': 'text-align', 'alineacion': 'text-align', 'alineación': 'text-align', 'выравнивание': 'text-align', '文本对齐': 'text-align', 'テキスト配置': 'text-align',
1421
- 'affichage': 'display', 'display': 'display', 'mostrar': 'display', 'отображение': 'display', '显示': 'display', '表示': 'display',
1422
- 'position': 'position', 'posicion': 'position', 'posición': 'position', 'позиция': 'position', '位置': 'position',
1423
- 'haut': 'top', 'top': 'top', 'arriba': 'top', 'верх': 'top', '顶部': 'top', '上': 'top',
1424
- 'bas': 'bottom', 'bottom': 'bottom', 'abajo': 'bottom', 'низ': 'bottom', '底部': 'bottom', '下': 'bottom',
1425
- 'gauche': 'left', 'left': 'left', 'izquierda': 'left', 'лево': 'left', '左': 'left',
1426
- 'droite': 'right', 'right': 'right', 'derecha': 'right', 'право': 'right', '右': 'right',
1427
- 'opacite': 'opacity', 'opacité': 'opacity', 'opacity': 'opacity', 'opacidad': 'opacity', 'прозрачность': 'opacity', '不透明度': 'opacity', '透明度': 'opacity',
1428
- 'transition': 'transition', 'transicion': 'transition', 'transición': 'transition', 'переход': 'transition', '过渡': 'transition', 'トランジション': 'transition',
1429
- 'animation': 'animation', 'animacion': 'animation', 'animación': 'animation', 'анимация': 'animation', '动画': 'animation', 'アニメーション': 'animation',
1430
- 'transformation': 'transform', 'transform': 'transform', 'transformacion': 'transform', 'transformación': 'transform', 'трансформация': 'transform', '变换': 'transform', '変形': 'transform',
1431
- 'filtre': 'filter', 'filter': 'filter', 'filtro': 'filter', 'фильтр': 'filter', '滤镜': 'filter', 'フィルター': 'filter',
1432
- 'grille': 'grid', 'grid': 'grid', 'rejilla': 'grid', 'cuadricula': 'grid', 'cuadrícula': 'grid', 'сетка': 'grid', '网格': 'grid', 'グリッド': 'grid',
1433
- 'flex': 'flex', 'флекс': 'flex', '弹性': 'flex', 'フレックス': 'flex',
1434
- 'curseur': 'cursor', 'cursor': 'cursor', 'курсор': 'cursor', '光标': 'cursor', 'カーソル': 'cursor',
1435
- 'debordement': 'overflow', 'débordement': 'overflow', 'overflow': 'overflow', 'desbordamiento': 'overflow', 'переполнение': 'overflow', '溢出': 'overflow', 'オーバーフロー': 'overflow',
1436
- 'visibilite': 'visibility', 'visibilité': 'visibility', 'visibility': 'visibility', 'visibilidad': 'visibility', 'видимость': 'visibility', '可见性': 'visibility', '表示状態': 'visibility',
1437
- 'index z': 'z-index', 'z-index': 'z-index', 'indice z': 'z-index', 'índice z': 'z-index', 'индекс z': 'z-index', 'z索引': 'z-index', 'zインデックス': 'z-index',
1438
- 'espacement lettres': 'letter-spacing', 'letter-spacing': 'letter-spacing', 'espaciado letras': 'letter-spacing', 'межбуквенный интервал': 'letter-spacing', '字母间距': 'letter-spacing', '文字間隔': 'letter-spacing',
1439
- 'espacement mots': 'word-spacing', 'word-spacing': 'word-spacing', 'espaciado palabras': 'word-spacing', 'межсловный интервал': 'word-spacing', '单词间距': 'word-spacing', '単語間隔': 'word-spacing',
1440
- 'hauteur ligne': 'line-height', 'line-height': 'line-height', 'altura linea': 'line-height', 'altura línea': 'line-height', 'высота строки': 'line-height', '行高': 'line-height', '行の高さ': 'line-height',
1441
- 'decoration texte': 'text-decoration', 'décoration texte': 'text-decoration', 'text-decoration': 'text-decoration', 'decoracion texto': 'text-decoration', 'decoración texto': 'text-decoration', 'оформление текста': 'text-decoration', '文本装饰': 'text-decoration', 'テキスト装飾': 'text-decoration',
1442
- 'transformation texte': 'text-transform', 'text-transform': 'text-transform', 'transformacion texto': 'text-transform', 'transformación texto': 'text-transform', 'преобразование текста': 'text-transform', '文本转换': 'text-transform', 'テキスト変換': 'text-transform',
1443
- 'espace blanc': 'white-space', 'white-space': 'white-space', 'espacio blanco': 'white-space', 'пробелы': 'white-space', '空白处理': 'white-space', '空白': 'white-space',
1444
- 'retour ligne': 'word-wrap', 'word-wrap': 'word-wrap', 'ajuste palabra': 'word-wrap', 'перенос слов': 'word-wrap', '单词换行': 'word-wrap', '単語折り返し': 'word-wrap',
1445
- 'contenu': 'content', 'content': 'content', 'contenido': 'content', 'содержимое': 'content', '内容': 'content', 'コンテンツ': 'content',
1446
- 'compteur': 'counter-reset', 'counter-reset': 'counter-reset', 'contador': 'counter-reset', 'счетчик': 'counter-reset', '计数器': 'counter-reset', 'カウンター': 'counter-reset',
1447
- 'liste style': 'list-style', 'list-style': 'list-style', 'estilo lista': 'list-style', 'стиль списка': 'list-style', '列表样式': 'list-style', 'リストスタイル': 'list-style',
1448
- 'tableau': 'table-layout', 'table-layout': 'table-layout', 'disposicion tabla': 'table-layout', 'disposición tabla': 'table-layout', 'макет таблицы': 'table-layout', '表格布局': 'table-layout', 'テーブルレイアウト': 'table-layout',
1449
- 'bordure collapse': 'border-collapse', 'border-collapse': 'border-collapse', 'colapsar bordes': 'border-collapse', 'схлопывание границ': 'border-collapse', '边框折叠': 'border-collapse', 'ボーダー折りたたみ': 'border-collapse',
1450
- 'outline': 'outline', 'contorno': 'outline', 'контур': 'outline', '轮廓': 'outline', 'アウトライン': 'outline',
1451
- 'selection utilisateur': 'user-select', 'sélection utilisateur': 'user-select', 'user-select': 'user-select', 'seleccion usuario': 'user-select', 'selección usuario': 'user-select', 'выделение пользователем': 'user-select', '用户选择': 'user-select', 'ユーザー選択': 'user-select',
1452
- 'evenements pointeur': 'pointer-events', 'événements pointeur': 'pointer-events', 'pointer-events': 'pointer-events', 'eventos puntero': 'pointer-events', 'события указателя': 'pointer-events', '指针事件': 'pointer-events', 'ポインターイベント': 'pointer-events',
1453
- 'redimensionner': 'resize', 'resize': 'resize', 'redimensionar': 'resize', 'изменение размера': 'resize', '调整大小': 'resize', 'リサイズ': 'resize',
1454
- 'apparence': 'appearance', 'appearance': 'appearance', 'apariencia': 'appearance', 'внешний вид': 'appearance', '外观': 'appearance', '外観': 'appearance',
1455
- 'ajustement objet': 'object-fit', 'object-fit': 'object-fit', 'ajuste objeto': 'object-fit', 'подгонка объекта': 'object-fit', '对象适应': 'object-fit', 'オブジェクトフィット': 'object-fit',
1456
- 'position objet': 'object-position', 'object-position': 'object-position', 'posicion objeto': 'object-position', 'posición objeto': 'object-position', 'позиция объекта': 'object-position', '对象位置': 'object-position', 'オブジェクト位置': 'object-position',
1457
- 'accent couleur': 'accent-color', 'accent-color': 'accent-color', 'color acento': 'accent-color', 'цвет акцента': 'accent-color', '强调色': 'accent-color', 'アクセントカラー': 'accent-color',
1458
- 'schema couleur': 'color-scheme', 'schéma couleur': 'color-scheme', 'color-scheme': 'color-scheme', 'esquema color': 'color-scheme', 'цветовая схема': 'color-scheme', '配色方案': 'color-scheme', 'カラースキーム': 'color-scheme',
1459
- 'ratio aspect': 'aspect-ratio', 'aspect-ratio': 'aspect-ratio', 'relacion aspecto': 'aspect-ratio', 'relación aspecto': 'aspect-ratio', 'соотношение сторон': 'aspect-ratio', '宽高比': 'aspect-ratio', 'アスペクト比': 'aspect-ratio',
1460
- 'ecart': 'gap', 'écart': 'gap', 'gap': 'gap', 'espacio': 'gap', 'brecha': 'gap', 'промежуток': 'gap', '间距': 'gap', 'ギャップ': 'gap',
1461
- 'place contenu': 'place-content', 'place-content': 'place-content', 'colocar contenido': 'place-content', 'размещение содержимого': 'place-content', '放置内容': 'place-content', 'コンテンツ配置': 'place-content',
1462
- 'place elements': 'place-items', 'place éléments': 'place-items', 'place-items': 'place-items', 'colocar elementos': 'place-items', 'размещение элементов': 'place-items', '放置项目': 'place-items', 'アイテム配置': 'place-items'
1463
- }
1464
-
1465
- return directMap[lowerName] || name
1466
- }
1467
-
1468
- parseValue() {
1469
- const values = []
1470
-
1471
- while (!this.match('NEWLINE', 'EOF', 'IMPORTANT', 'RBRACE', 'RPAREN')) {
1472
- if (this.match('COMMA')) {
1473
- break
1474
- }
1475
-
1476
- const val = this.parseSingleValue()
1477
- if (val) {
1478
- values.push(val)
1479
- } else {
1480
- break
1481
- }
1482
- }
1483
-
1484
- if (values.length === 0) {
1485
- return new AST.Value('', '')
1486
- }
1487
-
1488
- if (values.length === 1) {
1489
- return values[0]
1490
- }
1491
-
1492
- return new AST.ValueList(values)
1493
- }
1494
-
1495
- parseSingleValue() {
1496
- const token = this.peek()
1497
-
1498
- if (this.match('NUMBER')) {
1499
- const num = this.advance()
1500
- if (num.unit === '%') {
1501
- return new AST.PercentageValue(num.value, token.loc)
1502
- }
1503
- return new AST.NumberValue(num.value, num.unit, token.loc)
1504
- }
1505
-
1506
- if (this.match('HEX_COLOR')) {
1507
- const hex = this.advance().value
1508
- return new AST.ColorValue('hex', hex, '#' + hex, token.loc)
1509
- }
1510
-
1511
- if (this.match('COLOR_NAME')) {
1512
- const colorName = this.advance().value
1513
- const cssColor = this.translateColor(colorName)
1514
- return new AST.ColorValue('named', colorName, cssColor, token.loc)
1515
- }
1516
-
1517
- if (this.match('STRING')) {
1518
- const str = this.advance()
1519
- return new AST.StringValue(str.value, str.quote, token.loc)
1520
- }
1521
-
1522
- if (this.match('FUNCTION')) {
1523
- return this.parseFunctionCall()
1524
- }
1525
-
1526
- if (this.match('IDENTIFIER', 'VALUE_KEYWORD', 'PROPERTY')) {
1527
- const word = this.advance()
1528
-
1529
- if (this.match('COLON')) {
1530
- this.advance()
1531
- return this.parseFunctionWithColon(word.value, token.loc)
1532
- }
1533
-
1534
- const cssValue = this.translateValue(word.value)
1535
- return new AST.Identifier(word.value, cssValue, token.loc)
1536
- }
1537
-
1538
- if (this.match('SLASH')) {
1539
- this.advance()
1540
- return new AST.Identifier('/', '/', token.loc)
1541
- }
1542
-
1543
- if (this.match('COMMA')) {
1544
- this.advance()
1545
- return null
1546
- }
1547
-
1548
- return null
1549
- }
1550
-
1551
- parseFunctionCall() {
1552
- const funcToken = this.advance()
1553
- const funcName = funcToken.value
1554
- const cssFuncName = this.translateFunction(funcName)
1555
- const args = []
1556
-
1557
- if (this.match('LPAREN')) {
1558
- this.advance()
1559
-
1560
- while (!this.match('RPAREN', 'EOF')) {
1561
- const arg = this.parseSingleValue()
1562
- if (arg) args.push(arg)
1563
-
1564
- if (this.match('COMMA')) {
1565
- this.advance()
1566
- }
1567
- }
1568
-
1569
- this.expect('RPAREN')
1570
- } else if (this.match('COLON')) {
1571
- this.advance()
1572
- return this.parseFunctionWithColon(funcName, funcToken.loc)
1573
- }
1574
-
1575
- return new AST.FunctionCall(funcName, cssFuncName, args, funcToken.loc)
1576
- }
1577
-
1578
- parseFunctionWithColon(funcName, loc) {
1579
- const args = []
1580
-
1581
- while (!this.match('NEWLINE', 'EOF', 'IMPORTANT', 'RBRACE')) {
1582
- if (this.match('COMMA')) {
1583
- this.advance()
1584
- continue
1585
- }
1586
-
1587
- const arg = this.parseSingleValue()
1588
- if (arg) {
1589
- args.push(arg)
1590
- } else {
1591
- break
1592
- }
1593
- }
1594
-
1595
- const cssFuncName = this.translateFunction(funcName)
1596
- return new AST.FunctionCall(funcName, cssFuncName, args, loc)
1597
- }
1598
-
1599
- translateColor(colorName) {
1600
- const colors = this.i18n.colors || {}
1601
- const lowerName = colorName.toLowerCase()
1602
-
1603
- for (const [key, color] of Object.entries(colors)) {
1604
- if (typeof color === 'object') {
1605
- for (const [lang, translation] of Object.entries(color)) {
1606
- if (typeof translation === 'string' && translation.toLowerCase() === lowerName) {
1607
- return color.en || key
1608
- }
1609
- }
1610
- }
1611
- }
1612
-
1613
- const shades = this.i18n.colorShades || {}
1614
- for (const [key, shade] of Object.entries(shades)) {
1615
- if (typeof shade === 'object') {
1616
- for (const translation of Object.values(shade)) {
1617
- if (typeof translation === 'string' && lowerName.includes(translation.toLowerCase())) {
1618
- return colorName
1619
- }
1620
- }
1621
- }
1622
- }
1623
-
1624
- return colorName
1625
- }
1626
-
1627
- translateValue(value) {
1628
- const lowerValue = value.toLowerCase()
1629
-
1630
- for (const category of Object.values(this.i18n)) {
1631
- if (typeof category === 'object' && category !== null) {
1632
- for (const item of Object.values(category)) {
1633
- if (typeof item === 'object' && item !== null && item.css) {
1634
- for (const [lang, translation] of Object.entries(item)) {
1635
- if (lang !== 'css' && typeof translation === 'string' && translation.toLowerCase() === lowerValue) {
1636
- return item.css
1637
- }
1638
- }
1639
- }
1640
- }
1641
- }
1642
- }
1643
-
1644
- return value
1645
- }
1646
-
1647
- translateFunction(funcName) {
1648
- const map = {
1649
- 'degrade lineaire': 'linear-gradient', 'dégradé linéaire': 'linear-gradient', 'linear-gradient': 'linear-gradient',
1650
- 'degradado lineal': 'linear-gradient', 'линейный градиент': 'linear-gradient', '线性渐变': 'linear-gradient', '線形グラデーション': 'linear-gradient',
1651
- 'degrade radial': 'radial-gradient', 'dégradé radial': 'radial-gradient', 'radial-gradient': 'radial-gradient',
1652
- 'degradado radial': 'radial-gradient', 'радиальный градиент': 'radial-gradient', '径向渐变': 'radial-gradient', '放射グラデーション': 'radial-gradient',
1653
- 'degrade conique': 'conic-gradient', 'dégradé conique': 'conic-gradient', 'conic-gradient': 'conic-gradient',
1654
- 'degradado conico': 'conic-gradient', 'degradado cónico': 'conic-gradient', 'конический градиент': 'conic-gradient', '锥形渐变': 'conic-gradient', '円錐グラデーション': 'conic-gradient',
1655
- 'repeter': 'repeat', 'répéter': 'repeat', 'repeat': 'repeat', 'repetir': 'repeat', 'повторить': 'repeat', '重复': 'repeat', '繰り返し': 'repeat',
1656
- 'minmax': 'minmax', 'мин макс': 'minmax', '最小最大': 'minmax', '最小最大': 'minmax',
1657
- 'ajuster contenu': 'fit-content', 'fit-content': 'fit-content', 'ajustar contenido': 'fit-content', 'подогнать содержимое': 'fit-content', '适应内容': 'fit-content', 'コンテンツに合わせる': 'fit-content',
1658
- 'calculer': 'calc', 'calc': 'calc', 'calcular': 'calc', 'вычислить': 'calc', '计算': 'calc', '計算': 'calc',
1659
- 'minimum': 'min', 'min': 'min', 'mínimo': 'min', 'minimo': 'min', 'минимум': 'min', '最小': 'min',
1660
- 'maximum': 'max', 'max': 'max', 'máximo': 'max', 'maximo': 'max', 'максимум': 'max', '最大': 'max',
1661
- 'borner': 'clamp', 'clamp': 'clamp', 'limitar': 'clamp', 'ограничить': 'clamp', '限制': 'clamp', 'クランプ': 'clamp',
1662
- 'variable': 'var', 'var': 'var', 'переменная': 'var', '变量': 'var', '変数': 'var',
1663
- 'url': 'url', 'enlace': 'url', 'ссылка': 'url', '链接': 'url', 'URL': 'url',
1664
- 'rgb': 'rgb', 'rgba': 'rgba',
1665
- 'hsl': 'hsl', 'hsla': 'hsla',
1666
- 'hwb': 'hwb', 'lab': 'lab', 'lch': 'lch',
1667
- 'oklab': 'oklab', 'oklch': 'oklch',
1668
- 'melange couleur': 'color-mix', 'mélange couleur': 'color-mix', 'color-mix': 'color-mix',
1669
- 'mezcla color': 'color-mix', 'смешение цветов': 'color-mix', '颜色混合': 'color-mix', '色の混合': 'color-mix',
1670
- 'clair sombre': 'light-dark', 'light-dark': 'light-dark',
1671
- 'claro oscuro': 'light-dark', 'светлый темный': 'light-dark', '明暗': 'light-dark',
1672
- 'translation': 'translate', 'translate': 'translate', 'traduccion': 'translate', 'traducción': 'translate', 'перемещение': 'translate', '平移': 'translate', '移動': 'translate',
1673
- 'translation x': 'translateX', 'translateX': 'translateX', 'traduccion x': 'translateX', 'перемещение x': 'translateX', 'x平移': 'translateX', 'x移動': 'translateX',
1674
- 'translation y': 'translateY', 'translateY': 'translateY', 'traduccion y': 'translateY', 'перемещение y': 'translateY', 'y平移': 'translateY', 'y移動': 'translateY',
1675
- 'translation z': 'translateZ', 'translateZ': 'translateZ', 'traduccion z': 'translateZ', 'перемещение z': 'translateZ', 'z平移': 'translateZ', 'z移動': 'translateZ',
1676
- 'rotation': 'rotate', 'rotate': 'rotate', 'rotacion': 'rotate', 'rotación': 'rotate', 'вращение': 'rotate', '旋转': 'rotate', '回転': 'rotate',
1677
- 'echelle': 'scale', 'échelle': 'scale', 'scale': 'scale', 'escala': 'scale', 'масштаб': 'scale', '缩放': 'scale', 'スケール': 'scale',
1678
- 'inclinaison': 'skew', 'skew': 'skew', 'inclinacion': 'skew', 'inclinación': 'skew', 'наклон': 'skew', '倾斜': 'skew', '傾斜': 'skew',
1679
- 'flou': 'blur', 'blur': 'blur', 'desenfoque': 'blur', 'размытие': 'blur', '模糊': 'blur', 'ぼかし': 'blur',
1680
- 'luminosite': 'brightness', 'luminosité': 'brightness', 'brightness': 'brightness', 'brillo': 'brightness', 'яркость': 'brightness', '亮度': 'brightness', '明るさ': 'brightness',
1681
- 'contraste': 'contrast', 'contrast': 'contrast', 'контраст': 'contrast', '对比度': 'contrast', 'コントラスト': 'contrast',
1682
- 'niveaux gris': 'grayscale', 'grayscale': 'grayscale', 'escala grises': 'grayscale', 'оттенки серого': 'grayscale', '灰度': 'grayscale', 'グレースケール': 'grayscale',
1683
- 'inverser': 'invert', 'invert': 'invert', 'invertir': 'invert', 'инвертировать': 'invert', '反转': 'invert', '反転': 'invert',
1684
- 'saturation': 'saturate', 'saturate': 'saturate', 'saturacion': 'saturate', 'saturación': 'saturate', 'насыщенность': 'saturate', '饱和度': 'saturate', '彩度': 'saturate',
1685
- 'sepia': 'sepia', 'sépia': 'sepia', 'сепия': 'sepia', '深褐色': 'sepia', 'セピア': 'sepia',
1686
- 'teinte rotation': 'hue-rotate', 'hue-rotate': 'hue-rotate', 'rotacion tono': 'hue-rotate', 'rotación tono': 'hue-rotate', 'вращение оттенка': 'hue-rotate', '色相旋转': 'hue-rotate', '色相回転': 'hue-rotate',
1687
- 'ombre portee': 'drop-shadow', 'ombre portée': 'drop-shadow', 'drop-shadow': 'drop-shadow', 'sombra proyectada': 'drop-shadow', 'тень': 'drop-shadow', '投影': 'drop-shadow', 'ドロップシャドウ': 'drop-shadow',
1688
- 'cercle': 'circle', 'circle': 'circle', 'circulo': 'circle', 'círculo': 'circle', 'круг': 'circle', '圆形': 'circle', '円': 'circle',
1689
- 'ellipse': 'ellipse', 'elipse': 'ellipse', 'эллипс': 'ellipse', '椭圆': 'ellipse', '楕円': 'ellipse',
1690
- 'polygone': 'polygon', 'polygon': 'polygon', 'poligono': 'polygon', 'polígono': 'polygon', 'многоугольник': 'polygon', '多边形': 'polygon', '多角形': 'polygon',
1691
- 'encart': 'inset', 'inset': 'inset', 'inserto': 'inset', 'вставка': 'inset', '内嵌': 'inset', 'インセット': 'inset',
1692
- 'chemin': 'path', 'path': 'path', 'camino': 'path', 'путь': 'path', '路径': 'path', 'パス': 'path',
1693
- 'courbe bezier': 'cubic-bezier', 'courbe bézier': 'cubic-bezier', 'cubic-bezier': 'cubic-bezier',
1694
- 'curva bezier': 'cubic-bezier', 'curva bézier': 'cubic-bezier', 'кривая безье': 'cubic-bezier', '贝塞尔曲线': 'cubic-bezier', 'ベジェ曲線': 'cubic-bezier',
1695
- 'etapes': 'steps', 'étapes': 'steps', 'steps': 'steps', 'pasos': 'steps', 'шаги': 'steps', '步骤': 'steps', 'ステップ': 'steps',
1696
- 'lineaire': 'linear', 'linéaire': 'linear', 'linear': 'linear', 'lineal': 'linear', 'линейный': 'linear', '线性': 'linear', 'リニア': 'linear',
1697
- 'attribut': 'attr', 'attr': 'attr', 'atributo': 'attr', 'атрибут': 'attr', '属性': 'attr', '属性': 'attr',
1698
- 'compteur': 'counter', 'counter': 'counter', 'contador': 'counter', 'счетчик': 'counter', '计数器': 'counter', 'カウンター': 'counter',
1699
- 'env': 'env', 'entorno': 'env', 'окружение': 'env', '环境': 'env', '環境': 'env',
1700
- 'ensemble images': 'image-set', 'image-set': 'image-set', 'conjunto imagenes': 'image-set', 'conjunto imágenes': 'image-set', 'набор изображений': 'image-set', '图像集': 'image-set', '画像セット': 'image-set',
1701
- 'fondu croise': 'cross-fade', 'fondu croisé': 'cross-fade', 'cross-fade': 'cross-fade', 'fundido cruzado': 'cross-fade', 'перекрестное затухание': 'cross-fade', '交叉淡入淡出': 'cross-fade', 'クロスフェード': 'cross-fade',
1702
- 'facile': 'ease', 'ease': 'ease', 'suave': 'ease', 'плавно': 'ease', '缓动': 'ease', 'イーズ': 'ease',
1703
- 'facile entree': 'ease-in', 'facile entrée': 'ease-in', 'ease-in': 'ease-in', 'suave entrada': 'ease-in', 'плавно вначале': 'ease-in', '缓入': 'ease-in', 'イーズイン': 'ease-in',
1704
- 'facile sortie': 'ease-out', 'ease-out': 'ease-out', 'suave salida': 'ease-out', 'плавно вконце': 'ease-out', '缓出': 'ease-out', 'イーズアウト': 'ease-out',
1705
- 'facile entree sortie': 'ease-in-out', 'facile entrée sortie': 'ease-in-out', 'ease-in-out': 'ease-in-out', 'suave entrada salida': 'ease-in-out', 'плавно вначале вконце': 'ease-in-out', '缓入缓出': 'ease-in-out', 'イーズインアウト': 'ease-in-out'
1706
- }
1707
-
1708
- return map[funcName.toLowerCase()] || funcName
1709
- }
1710
- }
1711
-
1712
- class CSSCodeGenerator {
1713
- constructor() {
1714
- this.indent = 0
1715
- this.indentStr = ' '
1716
- }
1717
-
1718
- generate(ast) {
1719
- if (!ast) return ''
1720
-
1721
- switch (ast.type) {
1722
- case 'StyleSheet':
1723
- return this.generateStyleSheet(ast)
1724
- case 'Rule':
1725
- return this.generateRule(ast)
1726
- case 'MediaQuery':
1727
- return this.generateMediaQuery(ast)
1728
- case 'KeyframesRule':
1729
- return this.generateKeyframes(ast)
1730
- case 'FontFaceRule':
1731
- return this.generateFontFace(ast)
1732
- case 'ImportRule':
1733
- return this.generateImport(ast)
1734
- case 'SupportsRule':
1735
- return this.generateSupports(ast)
1736
- case 'LayerRule':
1737
- return this.generateLayer(ast)
1738
- case 'ContainerQuery':
1739
- return this.generateContainer(ast)
1740
- case 'ScopeRule':
1741
- return this.generateScope(ast)
1742
- case 'PageRule':
1743
- return this.generatePage(ast)
1744
- case 'PropertyRule':
1745
- return this.generatePropertyRule(ast)
1746
- case 'CounterStyleRule':
1747
- return this.generateCounterStyle(ast)
1748
- case 'StartingStyleRule':
1749
- return this.generateStartingStyle(ast)
1750
- case 'EtherAlias':
1751
- return this.generateEtherAlias(ast)
1752
- case 'EtherState':
1753
- return this.generateEtherState(ast)
1754
- default:
1755
- return ''
1756
- }
1757
- }
1758
-
1759
- generateStyleSheet(ast) {
1760
- return ast.rules.map(rule => this.generate(rule)).filter(Boolean).join('\n\n')
1761
- }
1762
-
1763
- generateRule(ast) {
1764
- const selectors = this.generateSelectorList(ast.selectors)
1765
- const declarations = this.generateDeclarations(ast.declarations)
1766
- return `${selectors} {\n${declarations}\n}`
1767
- }
1768
-
1769
- generateSelectorList(ast) {
1770
- if (!ast) return ''
1771
- if (ast.type === 'SelectorList') {
1772
- return ast.selectors.map(s => this.generateSelector(s)).join(', ')
1773
- }
1774
- return this.generateSelector(ast)
1775
- }
1776
-
1777
- generateSelector(ast) {
1778
- if (!ast) return ''
1779
-
1780
- switch (ast.type) {
1781
- case 'SimpleSelector':
1782
- if (ast.selectorType === 'class') return '.' + ast.value
1783
- if (ast.selectorType === 'id') return '#' + ast.value
1784
- if (ast.selectorType === 'universal') return '*'
1785
- return ast.value
1786
- case 'CompoundSelector':
1787
- return ast.selectors.map(s => this.generateSelector(s)).join('')
1788
- case 'ComplexSelector':
1789
- const left = this.generateSelector(ast.left)
1790
- const right = this.generateSelector(ast.right)
1791
- if (ast.combinator === ' ') return `${left} ${right}`
1792
- return `${left} ${ast.combinator} ${right}`
1793
- case 'PseudoClassSelector':
1794
- return ast.argument ? `:${ast.name}(${ast.argument})` : `:${ast.name}`
1795
- case 'PseudoElementSelector':
1796
- return `::${ast.name}`
1797
- case 'AttributeSelector':
1798
- let attr = `[${ast.attribute}`
1799
- if (ast.operator) {
1800
- attr += `${ast.operator}"${ast.value}"`
1801
- if (ast.flags) attr += ` ${ast.flags}`
1802
- }
1803
- return attr + ']'
1804
- default:
1805
- return ''
1806
- }
1807
- }
1808
-
1809
- generateDeclarations(declarations) {
1810
- return declarations.map(d => this.generateDeclaration(d)).join('\n')
1811
- }
1812
-
1813
- generateDeclaration(ast) {
1814
- const prop = ast.property.cssName || ast.property.name
1815
- const val = this.generateValue(ast.value)
1816
- const important = ast.important ? ' !important' : ''
1817
- return `${this.indentStr}${prop}: ${val}${important};`
1818
- }
1819
-
1820
- generateValue(ast) {
1821
- if (!ast) return ''
1822
-
1823
- switch (ast.type) {
1824
- case 'Value':
1825
- return ast.cssValue || ast.value
1826
- case 'ValueList':
1827
- return ast.values.map(v => this.generateValue(v)).join(ast.separator)
1828
- case 'NumberValue':
1829
- return ast.unit ? `${ast.value}${ast.unit}` : String(ast.value)
1830
- case 'PercentageValue':
1831
- return `${ast.value}%`
1832
- case 'ColorValue':
1833
- return ast.cssValue
1834
- case 'StringValue':
1835
- return `${ast.quote}${ast.value}${ast.quote}`
1836
- case 'Identifier':
1837
- return ast.cssName || ast.name
1838
- case 'FunctionCall':
1839
- const args = ast.args.map(a => this.generateValue(a)).join(', ')
1840
- return `${ast.cssName}(${args})`
1841
- case 'UrlValue':
1842
- return `url(${ast.url})`
1843
- case 'Variable':
1844
- return `var(--${ast.name})`
1845
- default:
1846
- return ''
1847
- }
1848
- }
1849
-
1850
- generateMediaQuery(ast) {
1851
- let query = '@media'
1852
- if (ast.mediaType) query += ` ${ast.mediaType}`
1853
- if (ast.conditions.length > 0) {
1854
- const conds = ast.conditions.map(c => {
1855
- if (c.type === 'operator') return c.value
1856
- if (c.type === 'MediaCondition') {
1857
- if (c.value) {
1858
- return `(${c.feature}: ${this.generateValue(c.value)})`
1859
- }
1860
- return `(${c.feature})`
1861
- }
1862
- return ''
1863
- }).join(' ')
1864
- if (ast.mediaType) query += ' and '
1865
- query += conds
1866
- }
1867
-
1868
- this.indent++
1869
- const rules = ast.rules.map(r => this.generate(r)).join('\n\n')
1870
- this.indent--
1871
-
1872
- return `${query} {\n${rules}\n}`
1873
- }
1874
-
1875
- generateKeyframes(ast) {
1876
- const keyframes = ast.keyframes.map(kf => {
1877
- const decls = this.generateDeclarations(kf.declarations)
1878
- return ` ${kf.selector} {\n${decls.split('\n').map(d => ' ' + d).join('\n')}\n }`
1879
- }).join('\n')
1880
-
1881
- return `@keyframes ${ast.name} {\n${keyframes}\n}`
1882
- }
1883
-
1884
- generateFontFace(ast) {
1885
- const decls = this.generateDeclarations(ast.declarations)
1886
- return `@font-face {\n${decls}\n}`
1887
- }
1888
-
1889
- generateImport(ast) {
1890
- let rule = `@import url("${ast.url}")`
1891
- if (ast.layer) rule += ` layer(${ast.layer === true ? '' : ast.layer})`
1892
- return rule + ';'
1893
- }
1894
-
1895
- generateSupports(ast) {
1896
- const cond = ast.condition.map(c => {
1897
- if (c.operator) return c.operator
1898
- if (c.property) {
1899
- return `(${c.property}: ${this.generateValue(c.value)})`
1900
- }
1901
- return ''
1902
- }).join(' ')
1903
-
1904
- const rules = ast.rules.map(r => this.generate(r)).join('\n\n')
1905
- return `@supports ${cond} {\n${rules}\n}`
1906
- }
1907
-
1908
- generateLayer(ast) {
1909
- if (ast.rules === null) {
1910
- return `@layer ${ast.name};`
1911
- }
1912
- const rules = ast.rules.map(r => this.generate(r)).join('\n\n')
1913
- return `@layer ${ast.name} {\n${rules}\n}`
1914
- }
1915
-
1916
- generateContainer(ast) {
1917
- let query = '@container'
1918
- if (ast.name) query += ` ${ast.name}`
1919
- if (ast.conditions.length > 0) {
1920
- const conds = ast.conditions.map(c => {
1921
- if (c.value) return `(${c.feature}: ${this.generateValue(c.value)})`
1922
- return `(${c.feature})`
1923
- }).join(' and ')
1924
- query += ` ${conds}`
1925
- }
1926
-
1927
- const rules = ast.rules.map(r => this.generate(r)).join('\n\n')
1928
- return `${query} {\n${rules}\n}`
1929
- }
1930
-
1931
- generateScope(ast) {
1932
- let rule = '@scope'
1933
- if (ast.start) rule += ` (${this.generateSelector(ast.start)})`
1934
- if (ast.end) rule += ` to (${this.generateSelector(ast.end)})`
1935
-
1936
- const rules = ast.rules.map(r => this.generate(r)).join('\n\n')
1937
- return `${rule} {\n${rules}\n}`
1938
- }
1939
-
1940
- generatePage(ast) {
1941
- let rule = '@page'
1942
- if (ast.selector) rule += ` :${ast.selector}`
1943
- const decls = this.generateDeclarations(ast.declarations)
1944
- return `${rule} {\n${decls}\n}`
1945
- }
1946
-
1947
- generatePropertyRule(ast) {
1948
- const decls = [
1949
- ` syntax: '${ast.syntax}';`,
1950
- ` inherits: ${ast.inherits};`
1951
- ]
1952
- if (ast.initialValue) {
1953
- decls.push(` initial-value: ${this.generateValue(ast.initialValue)};`)
1954
- }
1955
- return `@property ${ast.name} {\n${decls.join('\n')}\n}`
1956
- }
1957
-
1958
- generateCounterStyle(ast) {
1959
- const decls = this.generateDeclarations(ast.declarations)
1960
- return `@counter-style ${ast.name} {\n${decls}\n}`
1961
- }
1962
-
1963
- generateStartingStyle(ast) {
1964
- const rules = ast.rules.map(r => this.generate(r)).join('\n\n')
1965
- return `@starting-style {\n${rules}\n}`
1966
- }
1967
-
1968
- generateEtherAlias(ast) {
1969
- return ast.expandedDeclarations.map(d => this.generateDeclaration(d)).join('\n')
1970
- }
1971
-
1972
- generateEtherState(ast) {
1973
- return ast.declarations.map(d => this.generateDeclaration(d)).join('\n')
1974
- }
1975
- }
1976
-
1977
- module.exports = {
1978
- CSSLexer,
1979
- CSSParser,
1980
- CSSCodeGenerator
1981
- }