@tbela99/css-parser 1.1.0 → 1.1.1-alpha4
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.
- package/CHANGELOG.md +4 -0
- package/dist/index-umd-web.js +296 -169
- package/dist/index.cjs +296 -169
- package/dist/index.d.ts +24 -7
- package/dist/lib/ast/types.js +9 -0
- package/dist/lib/ast/utils/utils.js +104 -0
- package/dist/lib/parser/parse.js +24 -41
- package/dist/lib/syntax/syntax.js +4 -0
- package/dist/lib/validation/config.json.js +63 -63
- package/dist/lib/validation/parser/parse.js +14 -31
- package/dist/lib/validation/syntax.js +192 -36
- package/package.json +5 -5
|
@@ -36,12 +36,25 @@ function createContext(input) {
|
|
|
36
36
|
update(context) {
|
|
37
37
|
// @ts-ignore
|
|
38
38
|
const newIndex = result.indexOf(context.current());
|
|
39
|
-
if (newIndex
|
|
40
|
-
// console.error({newIndex, v: result[newIndex]});
|
|
41
|
-
// console.error(new Error('update'))
|
|
39
|
+
if (newIndex > this.index) {
|
|
42
40
|
this.index = newIndex;
|
|
43
41
|
}
|
|
44
42
|
},
|
|
43
|
+
consume(token, howMany) {
|
|
44
|
+
let newIndex = result.indexOf(token, this.index + 1);
|
|
45
|
+
if (newIndex == -1 || newIndex < this.index) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
howMany ??= 0;
|
|
49
|
+
let splice = 1;
|
|
50
|
+
if (result[newIndex - 1]?.typ == EnumToken.WhitespaceTokenType) {
|
|
51
|
+
splice++;
|
|
52
|
+
newIndex--;
|
|
53
|
+
}
|
|
54
|
+
result.splice(this.index + 1, 0, ...result.splice(newIndex, splice + howMany));
|
|
55
|
+
this.index += howMany + splice;
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
45
58
|
done() {
|
|
46
59
|
return this.index + 1 >= result.length;
|
|
47
60
|
},
|
|
@@ -66,13 +79,21 @@ function createContext(input) {
|
|
|
66
79
|
return result.slice(this.index + 1);
|
|
67
80
|
},
|
|
68
81
|
clone() {
|
|
69
|
-
const context = createContext(
|
|
82
|
+
const context = createContext(result.slice());
|
|
70
83
|
context.index = this.index;
|
|
71
84
|
return context;
|
|
85
|
+
},
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
toJSON() {
|
|
88
|
+
return {
|
|
89
|
+
index: this.index,
|
|
90
|
+
slice: this.slice(),
|
|
91
|
+
tokens: this.tokens()
|
|
92
|
+
};
|
|
72
93
|
}
|
|
73
94
|
};
|
|
74
95
|
}
|
|
75
|
-
function evaluateSyntax(node, options
|
|
96
|
+
function evaluateSyntax(node, options) {
|
|
76
97
|
let ast;
|
|
77
98
|
let result;
|
|
78
99
|
switch (node.typ) {
|
|
@@ -81,6 +102,7 @@ function evaluateSyntax(node, options, parent) {
|
|
|
81
102
|
break;
|
|
82
103
|
}
|
|
83
104
|
ast = getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, node.nam);
|
|
105
|
+
// console.error({ast: ast.reduce((acc, curr) => acc + renderSyntax(curr), '')});
|
|
84
106
|
if (ast != null) {
|
|
85
107
|
let token = null;
|
|
86
108
|
const values = node.val.slice();
|
|
@@ -100,6 +122,7 @@ function evaluateSyntax(node, options, parent) {
|
|
|
100
122
|
}
|
|
101
123
|
}
|
|
102
124
|
result = doEvaluateSyntax(ast, createContext(values), { ...options, visited: new WeakMap() });
|
|
125
|
+
// console.error(JSON.stringify({ast, values, result}, null, 1));
|
|
103
126
|
if (result.valid == SyntaxValidationResult.Valid && !result.context.done()) {
|
|
104
127
|
let token = null;
|
|
105
128
|
while ((token = result.context.next()) != null) {
|
|
@@ -158,6 +181,7 @@ function doEvaluateSyntax(syntaxes, context, options) {
|
|
|
158
181
|
let i = 0;
|
|
159
182
|
let result;
|
|
160
183
|
let token = null;
|
|
184
|
+
// console.error(`>> doEvaluateSyntax: ${syntaxes.reduce((acc, curr) => acc + renderSyntax(curr), '')}\n>> ${JSON.stringify({syntaxes}, null, 1)}>> context: ${context.slice<Token>().reduce((acc, curr) => acc + renderToken(curr), '')}`);
|
|
161
185
|
for (; i < syntaxes.length; i++) {
|
|
162
186
|
syntax = syntaxes[i];
|
|
163
187
|
if (context.done()) {
|
|
@@ -187,6 +211,7 @@ function doEvaluateSyntax(syntaxes, context, options) {
|
|
|
187
211
|
}
|
|
188
212
|
else {
|
|
189
213
|
if (isVisited(token, syntax, 'doEvaluateSyntax', options)) {
|
|
214
|
+
// console.error(`cyclic dependency: ${renderSyntax(syntax)}`);
|
|
190
215
|
return {
|
|
191
216
|
valid: SyntaxValidationResult.Drop,
|
|
192
217
|
node: token,
|
|
@@ -208,7 +233,8 @@ function doEvaluateSyntax(syntaxes, context, options) {
|
|
|
208
233
|
}
|
|
209
234
|
context.update(result.context);
|
|
210
235
|
}
|
|
211
|
-
|
|
236
|
+
// @ts-ignore
|
|
237
|
+
return result ?? {
|
|
212
238
|
valid: SyntaxValidationResult.Valid,
|
|
213
239
|
node: null,
|
|
214
240
|
syntax: syntaxes[i - 1],
|
|
@@ -333,6 +359,7 @@ function matchOccurence(syntax, context, options) {
|
|
|
333
359
|
};
|
|
334
360
|
}
|
|
335
361
|
function match(syntax, context, options) {
|
|
362
|
+
// console.error(`>> match(): ${renderSyntax(syntax)}\n>> ${JSON.stringify({syntax}, null, 1)}>> context: ${context.slice<Token>().reduce((acc, curr) => acc + renderToken(curr), '')}`);
|
|
336
363
|
let success = false;
|
|
337
364
|
let result;
|
|
338
365
|
let token = context.peek();
|
|
@@ -350,7 +377,7 @@ function match(syntax, context, options) {
|
|
|
350
377
|
}
|
|
351
378
|
return {
|
|
352
379
|
valid: SyntaxValidationResult.Drop,
|
|
353
|
-
node: context.
|
|
380
|
+
node: context.current(),
|
|
354
381
|
syntax,
|
|
355
382
|
error: `expected '${ValidationTokenEnum[syntax.typ].toLowerCase()}', got '${context.done() ? null : renderToken(context.peek())}'`,
|
|
356
383
|
context
|
|
@@ -385,7 +412,7 @@ function match(syntax, context, options) {
|
|
|
385
412
|
}
|
|
386
413
|
switch (syntax.typ) {
|
|
387
414
|
case ValidationTokenEnum.Keyword:
|
|
388
|
-
success = (token.typ == EnumToken.IdenTokenType || token.typ == EnumToken.DashedIdenTokenType) &&
|
|
415
|
+
success = (token.typ == EnumToken.IdenTokenType || token.typ == EnumToken.DashedIdenTokenType || isIdentColor(token)) &&
|
|
389
416
|
(token.val == syntax.val ||
|
|
390
417
|
syntax.val.localeCompare(token.val, undefined, { sensitivity: 'base' }) == 0 ||
|
|
391
418
|
// config.declarations.all
|
|
@@ -414,20 +441,17 @@ function match(syntax, context, options) {
|
|
|
414
441
|
context
|
|
415
442
|
};
|
|
416
443
|
}
|
|
417
|
-
{
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
context.next();
|
|
427
|
-
result.context = context;
|
|
428
|
-
return result;
|
|
429
|
-
}
|
|
444
|
+
// {
|
|
445
|
+
// console.error(JSON.stringify({funcDef: (getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, (syntax as ValidationFunctionDefinitionToken).val + '()')?.[0] as ValidationFunctionToken).chi}, null, 1))
|
|
446
|
+
//
|
|
447
|
+
// const child = getParsedSyntax(ValidationSyntaxGroupEnum.Syntaxes, (syntax as ValidationFunctionDefinitionToken).val + '()')?.[0] as ValidationFunctionToken;
|
|
448
|
+
result = match(getParsedSyntax("syntaxes" /* ValidationSyntaxGroupEnum.Syntaxes */, syntax.val + '()')?.[0], context, options);
|
|
449
|
+
if (result.valid == SyntaxValidationResult.Valid) {
|
|
450
|
+
context.next();
|
|
451
|
+
result.context = context;
|
|
452
|
+
return result;
|
|
430
453
|
}
|
|
454
|
+
// }
|
|
431
455
|
break;
|
|
432
456
|
case ValidationTokenEnum.DeclarationType:
|
|
433
457
|
return doEvaluateSyntax(getParsedSyntax("declarations" /* ValidationSyntaxGroupEnum.Declarations */, syntax.val), context, {
|
|
@@ -524,6 +548,7 @@ function match(syntax, context, options) {
|
|
|
524
548
|
}
|
|
525
549
|
function matchPropertyType(syntax, context, options) {
|
|
526
550
|
if (![
|
|
551
|
+
'bg-position',
|
|
527
552
|
'length-percentage', 'flex', 'calc-sum', 'color', 'color-base', 'system-color', 'deprecated-system-color',
|
|
528
553
|
'pseudo-class-selector', 'pseudo-element-selector'
|
|
529
554
|
].includes(syntax.val)) {
|
|
@@ -553,6 +578,96 @@ function matchPropertyType(syntax, context, options) {
|
|
|
553
578
|
return { ...result, context };
|
|
554
579
|
}
|
|
555
580
|
switch (syntax.val) {
|
|
581
|
+
case 'bg-position': {
|
|
582
|
+
let val;
|
|
583
|
+
let keyworkMatchCount = 0;
|
|
584
|
+
let lengthMatchCount = 0;
|
|
585
|
+
let functionMatchCount = 0;
|
|
586
|
+
let isBGX = false;
|
|
587
|
+
let isBGY = false;
|
|
588
|
+
while (token != null && keyworkMatchCount + lengthMatchCount + functionMatchCount < 3) {
|
|
589
|
+
// match one value: keyword or length
|
|
590
|
+
success = (token.typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(token.val));
|
|
591
|
+
if (success) {
|
|
592
|
+
functionMatchCount++;
|
|
593
|
+
context.next();
|
|
594
|
+
token = context.peek();
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (token.typ == EnumToken.WhitespaceTokenType) {
|
|
598
|
+
context.next();
|
|
599
|
+
token = context.peek();
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (token.typ == EnumToken.IdenTokenType) {
|
|
603
|
+
val = token.val.toLowerCase();
|
|
604
|
+
success = ['left', 'center', 'right', 'top', 'center', 'bottom'].includes(val);
|
|
605
|
+
if (!success) {
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
keyworkMatchCount++;
|
|
609
|
+
if (keyworkMatchCount > 2) {
|
|
610
|
+
return {
|
|
611
|
+
valid: SyntaxValidationResult.Drop,
|
|
612
|
+
node: token,
|
|
613
|
+
syntax,
|
|
614
|
+
error: `expected <length>`,
|
|
615
|
+
context
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
if (val == 'left' || val == 'right') {
|
|
619
|
+
if (isBGX) {
|
|
620
|
+
return {
|
|
621
|
+
valid: SyntaxValidationResult.Drop,
|
|
622
|
+
node: token,
|
|
623
|
+
syntax,
|
|
624
|
+
error: `top | bottom | <length-percentage>`,
|
|
625
|
+
context
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
isBGX = true;
|
|
629
|
+
}
|
|
630
|
+
if (val == 'top' || val == 'bottom') {
|
|
631
|
+
if (isBGY) {
|
|
632
|
+
return {
|
|
633
|
+
valid: SyntaxValidationResult.Drop,
|
|
634
|
+
node: token,
|
|
635
|
+
syntax,
|
|
636
|
+
error: `expected left | right | <length-percentage>`,
|
|
637
|
+
context
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
isBGY = true;
|
|
641
|
+
}
|
|
642
|
+
context.next();
|
|
643
|
+
token = context.peek();
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
success = token.typ == EnumToken.LengthTokenType || token.typ == EnumToken.PercentageTokenType || (token.typ == EnumToken.NumberTokenType && token.val == '0');
|
|
647
|
+
if (!success) {
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
lengthMatchCount++;
|
|
651
|
+
context.next();
|
|
652
|
+
token = context.peek();
|
|
653
|
+
}
|
|
654
|
+
if (keyworkMatchCount + lengthMatchCount + functionMatchCount == 0) {
|
|
655
|
+
return {
|
|
656
|
+
valid: SyntaxValidationResult.Drop,
|
|
657
|
+
node: token,
|
|
658
|
+
syntax,
|
|
659
|
+
error: `expected <bg-position>`,
|
|
660
|
+
context
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
valid: SyntaxValidationResult.Valid,
|
|
665
|
+
node: token,
|
|
666
|
+
syntax,
|
|
667
|
+
error: '',
|
|
668
|
+
context
|
|
669
|
+
};
|
|
670
|
+
}
|
|
556
671
|
case 'calc-sum':
|
|
557
672
|
success = (token.typ == EnumToken.FunctionTokenType && mathFuncs.includes(token.val)) ||
|
|
558
673
|
// @ts-ignore
|
|
@@ -621,8 +736,8 @@ function matchPropertyType(syntax, context, options) {
|
|
|
621
736
|
case 'number':
|
|
622
737
|
case 'number-token':
|
|
623
738
|
success = token.typ == EnumToken.NumberTokenType;
|
|
624
|
-
if ('range' in syntax) {
|
|
625
|
-
success =
|
|
739
|
+
if (success && 'range' in syntax) {
|
|
740
|
+
success = +token.val >= +syntax.range[0] && (syntax.range[1] == null || +token.val <= +syntax.range[1]);
|
|
626
741
|
}
|
|
627
742
|
break;
|
|
628
743
|
case 'angle':
|
|
@@ -760,24 +875,28 @@ function anyOf(syntaxes, context, options) {
|
|
|
760
875
|
function allOf(syntax, context, options) {
|
|
761
876
|
let result;
|
|
762
877
|
let i;
|
|
763
|
-
|
|
764
|
-
// 1px var(...) 2px => 1px 2px var(...)
|
|
765
|
-
const slice = context.slice();
|
|
878
|
+
let slice = context.slice();
|
|
766
879
|
const vars = [];
|
|
767
880
|
const tokens = [];
|
|
881
|
+
const repeatable = [];
|
|
882
|
+
// match optional syntax first
|
|
883
|
+
// <length>{2,3}&&<color>? => <color>?&&<length>{2,3}
|
|
884
|
+
for (i = 0; i < syntax.length; i++) {
|
|
885
|
+
if (syntax[i].length == 1 && syntax[i][0].occurence != null) {
|
|
886
|
+
repeatable.push(syntax[i]);
|
|
887
|
+
syntax.splice(i--, 1);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (repeatable.length > 0) {
|
|
891
|
+
syntax.push(...repeatable);
|
|
892
|
+
}
|
|
893
|
+
// sort tokens -> wildCard -> last
|
|
894
|
+
// 1px var(...) 2px => 1px 2px var(...)
|
|
768
895
|
for (i = 0; i < slice.length; i++) {
|
|
769
896
|
if (slice[i].typ == EnumToken.FunctionTokenType && wildCardFuncs.includes(slice[i].val.toLowerCase())) {
|
|
770
897
|
vars.push(slice[i]);
|
|
771
|
-
slice
|
|
772
|
-
|
|
773
|
-
vars.push(slice[i]);
|
|
774
|
-
slice.splice(i, 1);
|
|
775
|
-
if (i > 0) {
|
|
776
|
-
i--;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
if (i > 0) {
|
|
780
|
-
i--;
|
|
898
|
+
if (slice[i + 1]?.typ == EnumToken.WhitespaceTokenType) {
|
|
899
|
+
vars.push(slice[++i]);
|
|
781
900
|
}
|
|
782
901
|
continue;
|
|
783
902
|
}
|
|
@@ -791,7 +910,44 @@ function allOf(syntax, context, options) {
|
|
|
791
910
|
tokens.push(...vars);
|
|
792
911
|
}
|
|
793
912
|
const con = createContext(tokens);
|
|
913
|
+
let cp;
|
|
914
|
+
let j;
|
|
794
915
|
for (i = 0; i < syntax.length; i++) {
|
|
916
|
+
if (syntax[i].length == 1 && syntax[i][0].isOptional) {
|
|
917
|
+
syntax[i][0].isOptional = false;
|
|
918
|
+
j = 0;
|
|
919
|
+
cp = con.clone();
|
|
920
|
+
slice = cp.slice();
|
|
921
|
+
if (cp.done()) {
|
|
922
|
+
syntax[i][0].isOptional = true;
|
|
923
|
+
syntax.splice(i, 1);
|
|
924
|
+
i = -1;
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
while (!cp.done()) {
|
|
928
|
+
result = doEvaluateSyntax(syntax[i], cp.clone(), options);
|
|
929
|
+
if (result.valid == SyntaxValidationResult.Valid) {
|
|
930
|
+
let end = slice.indexOf(cp.current());
|
|
931
|
+
if (end == -1) {
|
|
932
|
+
end = 0;
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
end -= j - 1;
|
|
936
|
+
}
|
|
937
|
+
con.consume(slice[j], end < 0 ? 0 : end);
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
cp.next();
|
|
941
|
+
j++;
|
|
942
|
+
}
|
|
943
|
+
syntax[i][0].isOptional = true;
|
|
944
|
+
// @ts-ignore
|
|
945
|
+
if (result?.valid == SyntaxValidationResult.Valid) {
|
|
946
|
+
syntax.splice(i, 1);
|
|
947
|
+
i = -1;
|
|
948
|
+
}
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
795
951
|
result = doEvaluateSyntax(syntax[i], con.clone(), options);
|
|
796
952
|
if (result.valid == SyntaxValidationResult.Valid) {
|
|
797
953
|
con.update(result.context);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tbela99/css-parser",
|
|
3
3
|
"description": "CSS parser for node and the browser",
|
|
4
|
-
"version": "v1.1.
|
|
4
|
+
"version": "v1.1.1-alpha4",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/node/index.js",
|
|
7
7
|
"./node": "./dist/node/index.js",
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "rollup -c;./build.sh dist/index.d.ts 'declare interface' 'declare type'",
|
|
19
|
-
"test": "web-test-runner \"test/**/web.spec.js\" --node-resolve --playwright --browsers chromium firefox webkit --root-dir=.; mocha --reporter-options='maxDiffSize=1801920' \"test/**/node.spec.js\"",
|
|
19
|
+
"test": "web-test-runner \"test/**/web.spec.js\" --node-resolve --playwright --browsers chromium firefox webkit --root-dir=.; mocha --reporter-options='maxDiffSize=1801920' --timeout=10000 \"test/**/node.spec.js\"",
|
|
20
20
|
"test:web": "web-test-runner \"test/**/web.spec.js\" --node-resolve --playwright --browsers chromium firefox webkit --root-dir=.",
|
|
21
21
|
"test:node": "mocha --reporter-options='maxDiffSize=1801920' \"test/**/node.spec.js\"",
|
|
22
|
-
"test:cov": "c8 -x 'test/specs/**/*.js' -x dist/lib/validation/syntax.js -x 'dist/lib/validation/parser/*.js' --reporter=html --reporter=text --reporter=json-summary mocha --reporter-options='maxDiffSize=1801920' \"test/**/node.spec.js\"",
|
|
22
|
+
"test:cov": "c8 -x 'test/specs/**/*.js' -x dist/lib/validation/syntax.js -x 'dist/lib/validation/parser/*.js' --reporter=html --reporter=text --reporter=json-summary mocha --reporter-options='maxDiffSize=1801920' --timeout=10000 \"test/**/node.spec.js\"",
|
|
23
23
|
"test:web-cov": "web-test-runner -x 'test/specs/**/*.js' -x dist/lib/validation/syntax.js,dist/lib/validation/parser \"test/**/web.spec.js\" --node-resolve --playwright --browsers chromium firefox webkit --root-dir=. --coverage",
|
|
24
24
|
"profile": "node --enable-source-maps --inspect-brk test/inspect.js",
|
|
25
25
|
"syntax-update": "esno tools/validation.ts",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"c8": "^10.1.3",
|
|
66
66
|
"esno": "^4.8.0",
|
|
67
67
|
"mocha": "^11.7.1",
|
|
68
|
-
"playwright": "^1.
|
|
69
|
-
"rollup": "^4.
|
|
68
|
+
"playwright": "^1.54.1",
|
|
69
|
+
"rollup": "^4.45.1",
|
|
70
70
|
"rollup-plugin-dts": "^6.2.1",
|
|
71
71
|
"tslib": "^2.8.1"
|
|
72
72
|
}
|