@tbela99/css-parser 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
1
  import { isIdentStart, isIdent, isIdentColor, mathFuncs, isColor, parseColor, isPseudo, pseudoElements, isAtKeyword, isFunction, isNumber, isPercentage, parseDimension, isHexColor, isHash, mediaTypes } from '../syntax/syntax.js';
2
- import { EnumToken, ColorType, ValidationLevel, SyntaxValidationResult } from '../ast/types.js';
2
+ import { EnumToken, ColorType, ValidationLevel, ModuleCaseTransformEnum, ModuleScopeEnumOptions, SyntaxValidationResult } from '../ast/types.js';
3
3
  import { definedPropertySettings, minify, combinators } from '../ast/minify.js';
4
4
  import { walkValues, WalkerEvent, walk, WalkerOptionEnum } from '../ast/walk.js';
5
5
  import { expand } from '../ast/expand.js';
6
6
  import './utils/config.js';
7
7
  import { parseDeclarationNode } from './utils/declaration.js';
8
+ import { camelize, dasherize } from './utils/text.js';
8
9
  import { renderToken } from '../renderer/render.js';
9
10
  import '../renderer/sourcemap/lib/encode.js';
10
11
  import { funcLike, timingFunc, timelineFunc, COLORS_NAMES, systemColors, deprecatedSystemColors, colorsFunc } from '../syntax/color/utils/constants.js';
@@ -19,6 +20,7 @@ import '../validation/syntaxes/complex-selector.js';
19
20
  import { validateKeyframeSelector } from '../validation/syntaxes/keyframe-selector.js';
20
21
  import { isNodeAllowedInContext, evaluateSyntax } from '../validation/syntax.js';
21
22
  import { validateAtRuleKeyframes } from '../validation/at-rules/keyframes.js';
23
+ import { hashAlgorithms, hash } from './utils/hash.js';
22
24
 
23
25
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
24
26
  const trimWhiteSpace = [EnumToken.CommentTokenType, EnumToken.GtTokenType, EnumToken.GteTokenType, EnumToken.LtTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType];
@@ -38,9 +40,12 @@ const enumTokenHints = new Set([
38
40
  function reject(reason) {
39
41
  throw new Error(reason ?? 'Parsing aborted');
40
42
  }
41
- function normalizeVisitorKeyName(keyName) {
42
- return keyName.replace(/-([a-z])/g, (all, one) => one.toUpperCase());
43
- }
43
+ /**
44
+ * replace token in its parent node
45
+ * @param parent
46
+ * @param value
47
+ * @param replacement
48
+ */
44
49
  function replaceToken(parent, value, replacement) {
45
50
  for (const node of (Array.isArray(replacement) ? replacement : [replacement])) {
46
51
  if ('parent' in value && value.parent != node.parent) {
@@ -68,11 +73,129 @@ function replaceToken(parent, value, replacement) {
68
73
  target.splice(index, 1, ...(Array.isArray(replacement) ? replacement : [replacement]));
69
74
  }
70
75
  }
76
+ /**
77
+ * transform case of key name
78
+ * @param key
79
+ * @param how
80
+ *
81
+ * @throws Error
82
+ * @private
83
+ */
84
+ function getKeyName(key, how) {
85
+ switch (how) {
86
+ case ModuleCaseTransformEnum.CamelCase:
87
+ case ModuleCaseTransformEnum.CamelCaseOnly:
88
+ return camelize(key);
89
+ case ModuleCaseTransformEnum.DashCase:
90
+ case ModuleCaseTransformEnum.DashCaseOnly:
91
+ return dasherize(key);
92
+ }
93
+ return key;
94
+ }
95
+ /**
96
+ * generate scoped name
97
+ * @param localName
98
+ * @param filePath
99
+ * @param pattern
100
+ * @param hashLength
101
+ *
102
+ * @throws Error
103
+ * @private
104
+ */
105
+ async function generateScopedName(localName, filePath, pattern, hashLength = 5) {
106
+ if (localName.startsWith('--')) {
107
+ localName = localName.slice(2);
108
+ }
109
+ const matches = /.*?(([^/]+)\/)?([^/\\]*?)(\.([^?/]+))?([?].*)?$/.exec(filePath);
110
+ const folder = matches?.[2]?.replace?.(/[^A-Za-z0-9_-]/g, "_") ?? '';
111
+ const fileBase = matches?.[3] ?? '';
112
+ const ext = matches?.[5] ?? '';
113
+ const path = filePath.replace(/[^A-Za-z0-9_-]/g, "_");
114
+ // sanitize localName for safe char set (replace spaces/illegal chars)
115
+ const safeLocal = localName.replace(/[^A-Za-z0-9_-]/g, "_");
116
+ const hashString = `${localName}::${filePath}`;
117
+ let result = '';
118
+ let inParens = 0;
119
+ let key = '';
120
+ let position = 0;
121
+ // Compose final scoped name. Ensure the entire class doesn't start with digit:
122
+ for (const char of pattern) {
123
+ position += char.length;
124
+ if (char == '[') {
125
+ inParens++;
126
+ if (inParens != 1) {
127
+ throw new Error(`Unexpected character: '${char} at position ${position - 1}' in pattern '${pattern}'`);
128
+ }
129
+ continue;
130
+ }
131
+ if (char == ']') {
132
+ inParens--;
133
+ if (inParens != 0) {
134
+ throw new Error(`Unexpected character: '${char}:${position - 1}'`);
135
+ }
136
+ let hashAlgo = null;
137
+ let length = null;
138
+ if (key.includes(':')) {
139
+ const parts = key.split(':');
140
+ if (parts.length == 2) {
141
+ // @ts-ignore
142
+ [key, length] = parts;
143
+ // @ts-ignore
144
+ if (key == 'hash' && hashAlgorithms.includes(length)) {
145
+ // @ts-ignore
146
+ hashAlgo = length;
147
+ length = null;
148
+ }
149
+ }
150
+ if (parts.length == 3) {
151
+ // @ts-ignore
152
+ [key, hashAlgo, length] = parts;
153
+ }
154
+ if (length != null && !Number.isInteger(+length)) {
155
+ throw new Error(`Unsupported hash length: '${length}'. expecting format [hash:length] or [hash:hash-algo:length]`);
156
+ }
157
+ }
158
+ switch (key) {
159
+ case 'hash':
160
+ result += await hash(hashString, length ?? hashLength, hashAlgo);
161
+ break;
162
+ case 'name':
163
+ result += length != null ? fileBase.slice(0, +length) : fileBase;
164
+ break;
165
+ case 'local':
166
+ result += length != null ? safeLocal.slice(0, +length) : localName;
167
+ break;
168
+ case 'ext':
169
+ result += length != null ? ext.slice(0, +length) : ext;
170
+ break;
171
+ case 'path':
172
+ result += length != null ? path.slice(0, +length) : path;
173
+ break;
174
+ case 'folder':
175
+ result += length != null ? folder.slice(0, +length) : folder;
176
+ break;
177
+ default:
178
+ throw new Error(`Unsupported key: '${key}'`);
179
+ }
180
+ key = '';
181
+ continue;
182
+ }
183
+ if (inParens > 0) {
184
+ key += char;
185
+ }
186
+ else {
187
+ result += char;
188
+ }
189
+ }
190
+ // if leading char is digit, prefix underscore (very rare)
191
+ return (/^[0-9]/.test(result) ? '_' : '') + result;
192
+ }
71
193
  /**
72
194
  * parse css string
73
195
  * @param iter
74
196
  * @param options
75
197
  *
198
+ * @throws Error
76
199
  * @private
77
200
  */
78
201
  async function doParse(iter, options = {}) {
@@ -104,6 +227,9 @@ async function doParse(iter, options = {}) {
104
227
  if (typeof options.validation == 'boolean') {
105
228
  options.validation = options.validation ? ValidationLevel.All : ValidationLevel.None;
106
229
  }
230
+ if (options.module) {
231
+ options.expandNestingRules = true;
232
+ }
107
233
  if (options.expandNestingRules) {
108
234
  options.nestingRules = false;
109
235
  }
@@ -369,6 +495,7 @@ async function doParse(iter, options = {}) {
369
495
  const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream) : tokenize({
370
496
  stream,
371
497
  buffer: '',
498
+ offset: 0,
372
499
  position: { ind: 0, lin: 1, col: 1 },
373
500
  currentPosition: { ind: -1, lin: 1, col: 0 }
374
501
  }), Object.assign({}, options, {
@@ -428,7 +555,7 @@ async function doParse(iter, options = {}) {
428
555
  }
429
556
  let node = result.node;
430
557
  for (const handler of handlers) {
431
- callable = typeof handler == 'function' ? handler : handler[normalizeVisitorKeyName(node.typ == EnumToken.DeclarationNodeType || node.typ == EnumToken.AtRuleNodeType ? node.nam : node.val)];
558
+ callable = typeof handler == 'function' ? handler : handler[camelize(node.typ == EnumToken.DeclarationNodeType || node.typ == EnumToken.AtRuleNodeType ? node.nam : node.val)];
432
559
  if (callable == null) {
433
560
  continue;
434
561
  }
@@ -563,12 +690,9 @@ async function doParse(iter, options = {}) {
563
690
  }
564
691
  }
565
692
  }
566
- const endTime = performance.now();
567
- if (options.signal != null) {
568
- options.signal.removeEventListener('abort', reject);
569
- }
570
693
  stats.bytesIn += stats.importedBytesIn;
571
- return {
694
+ let endTime = performance.now();
695
+ const result = {
572
696
  ast,
573
697
  errors,
574
698
  stats: {
@@ -578,6 +702,503 @@ async function doParse(iter, options = {}) {
578
702
  total: `${(endTime - startTime).toFixed(2)}ms`
579
703
  }
580
704
  };
705
+ if (options.module) {
706
+ const moduleSettings = {
707
+ hashLength: 5,
708
+ filePath: '',
709
+ scoped: ModuleScopeEnumOptions.Local,
710
+ naming: ModuleCaseTransformEnum.IgnoreCase,
711
+ pattern: '',
712
+ generateScopedName,
713
+ ...(typeof options.module != 'object' ? {} : options.module)
714
+ };
715
+ const parseModuleTime = performance.now();
716
+ const namesMapping = {};
717
+ const global = new Set;
718
+ const processed = new Set;
719
+ const pattern = typeof options.module == 'boolean' ? null : moduleSettings.pattern;
720
+ const importMapping = {};
721
+ const cssVariablesMap = {};
722
+ const importedCssVariables = {};
723
+ let mapping = {};
724
+ let revMapping = {};
725
+ let filePath = typeof options.module == 'boolean' ? options.src : (moduleSettings.filePath ?? options.src);
726
+ filePath = filePath === '' ? options.src : options.resolve(filePath, options.dirname(options.src), options.cwd).relative;
727
+ if (typeof options.module == 'number') {
728
+ if (options.module & ModuleCaseTransformEnum.CamelCase) {
729
+ moduleSettings.naming = ModuleCaseTransformEnum.CamelCase;
730
+ }
731
+ else if (options.module & ModuleCaseTransformEnum.CamelCaseOnly) {
732
+ moduleSettings.naming = ModuleCaseTransformEnum.CamelCaseOnly;
733
+ }
734
+ else if (options.module & ModuleCaseTransformEnum.DashCase) {
735
+ moduleSettings.naming = ModuleCaseTransformEnum.DashCase;
736
+ }
737
+ else if (options.module & ModuleCaseTransformEnum.DashCaseOnly) {
738
+ moduleSettings.naming = ModuleCaseTransformEnum.DashCaseOnly;
739
+ }
740
+ if (options.module & ModuleScopeEnumOptions.Global) {
741
+ moduleSettings.scoped = ModuleScopeEnumOptions.Global;
742
+ }
743
+ if (options.module & ModuleScopeEnumOptions.Pure) {
744
+ // @ts-ignore
745
+ moduleSettings.scoped |= ModuleScopeEnumOptions.Pure;
746
+ }
747
+ if (options.module & ModuleScopeEnumOptions.ICSS) {
748
+ // @ts-ignore
749
+ moduleSettings.scoped |= ModuleScopeEnumOptions.ICSS;
750
+ }
751
+ }
752
+ if (typeof moduleSettings.scoped == 'boolean') {
753
+ moduleSettings.scoped = moduleSettings.scoped ? ModuleScopeEnumOptions.Local : ModuleScopeEnumOptions.Global;
754
+ }
755
+ moduleSettings.filePath = filePath;
756
+ moduleSettings.pattern = pattern != null && pattern !== '' ? pattern : (filePath === '' ? `[local]_[hash]` : `[local]_[hash]_[name]`);
757
+ for (const { node, parent } of walk(ast)) {
758
+ if (node.typ == EnumToken.CssVariableImportTokenType) {
759
+ const url = node.val.find(t => t.typ == EnumToken.StringTokenType).val.slice(1, -1);
760
+ const src = options.resolve(url, options.dirname(options.src), options.cwd);
761
+ const result = options.load(url, options.src);
762
+ const stream = result instanceof Promise || Object.getPrototypeOf(result).constructor.name == 'AsyncFunction' ? await result : result;
763
+ const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream) : tokenize({
764
+ stream,
765
+ buffer: '',
766
+ offset: 0,
767
+ position: { ind: 0, lin: 1, col: 1 },
768
+ currentPosition: { ind: -1, lin: 1, col: 0 }
769
+ }), Object.assign({}, options, {
770
+ minify: false,
771
+ setParent: false,
772
+ src: src.relative
773
+ }));
774
+ cssVariablesMap[node.nam] = root.cssModuleVariables;
775
+ parent.chi.splice(parent.chi.indexOf(node), 1);
776
+ continue;
777
+ }
778
+ if (node.typ == EnumToken.CssVariableDeclarationMapTokenType) {
779
+ const from = node.from.find(t => t.typ == EnumToken.IdenTokenType || isIdentColor(t));
780
+ if (!(from.val in cssVariablesMap)) {
781
+ errors.push({
782
+ node,
783
+ message: `could not resolve @value import from '${from.val}'`,
784
+ action: 'drop'
785
+ });
786
+ }
787
+ else {
788
+ for (const token of node.vars) {
789
+ if (token.typ == EnumToken.IdenTokenType || isIdentColor(token)) {
790
+ if (!(token.val in cssVariablesMap[from.val])) {
791
+ errors.push({
792
+ node,
793
+ message: `value '${token.val}' is not exported from '${from.val}'`,
794
+ action: 'drop'
795
+ });
796
+ continue;
797
+ }
798
+ result.cssModuleVariables ??= {};
799
+ result.cssModuleVariables[token.val] = importedCssVariables[token.val] = cssVariablesMap[from.val][token.val];
800
+ }
801
+ }
802
+ }
803
+ parent.chi.splice(parent.chi.indexOf(node), 1);
804
+ continue;
805
+ }
806
+ if (node.typ == EnumToken.CssVariableTokenType) {
807
+ if (parent?.typ == EnumToken.StyleSheetNodeType) {
808
+ if (result.cssModuleVariables == null) {
809
+ result.cssModuleVariables = {};
810
+ }
811
+ result.cssModuleVariables[node.nam] = node;
812
+ }
813
+ parent.chi.splice(parent.chi.indexOf(node), 1);
814
+ continue;
815
+ }
816
+ if (node.typ == EnumToken.DeclarationNodeType) {
817
+ if (node.nam.startsWith('--')) {
818
+ if (!(node.nam in namesMapping)) {
819
+ let result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? node.nam : moduleSettings.generateScopedName(node.nam, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
820
+ let value = result instanceof Promise ? await result : result;
821
+ mapping[node.nam] = '--' + (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly ? getKeyName(value, moduleSettings.naming) : value);
822
+ revMapping[node.nam] = node.nam;
823
+ }
824
+ node.nam = mapping[node.nam];
825
+ }
826
+ if ('composes' == node.nam.toLowerCase()) {
827
+ const tokens = [];
828
+ let isValid = true;
829
+ for (const token of node.val) {
830
+ if (token.typ == EnumToken.ComposesSelectorNodeType) {
831
+ if (!(token.r == null || token.r.typ == EnumToken.StringTokenType || token.r.typ == EnumToken.IdenTokenType)) {
832
+ errors.push({
833
+ action: 'drop',
834
+ message: `composes '${EnumToken[token.r.typ]}' is not supported`,
835
+ node
836
+ });
837
+ isValid = false;
838
+ break;
839
+ }
840
+ tokens.push(token);
841
+ }
842
+ }
843
+ // find parent rule
844
+ let parentRule = node.parent;
845
+ while (parentRule != null && parentRule.typ != EnumToken.RuleNodeType) {
846
+ parentRule = parentRule.parent;
847
+ }
848
+ if (!isValid || tokens.length == 0) {
849
+ if (tokens.length == 0) {
850
+ errors.push({
851
+ action: 'drop',
852
+ message: `composes is empty`,
853
+ node
854
+ });
855
+ }
856
+ parentRule.chi.splice(parentRule.chi.indexOf(node), 1);
857
+ continue;
858
+ }
859
+ for (const token of tokens) {
860
+ // composes: a b c;
861
+ if (token.r == null) {
862
+ for (const rule of token.l) {
863
+ if (rule.typ == EnumToken.WhitespaceTokenType || rule.typ == EnumToken.CommentTokenType) {
864
+ continue;
865
+ }
866
+ if (!(rule.val in mapping)) {
867
+ let result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? rule.val : moduleSettings.generateScopedName(rule.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
868
+ let value = result instanceof Promise ? await result : result;
869
+ mapping[rule.val] = (rule.typ == EnumToken.DashedIdenTokenType ? '--' : '') + (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly ? getKeyName(value, moduleSettings.naming) : value);
870
+ revMapping[mapping[rule.val]] = rule.val;
871
+ }
872
+ if (parentRule != null) {
873
+ for (const tk of parentRule.tokens) {
874
+ if (tk.typ == EnumToken.ClassSelectorTokenType) {
875
+ const val = tk.val.slice(1);
876
+ if (val in revMapping) {
877
+ const key = revMapping[val];
878
+ mapping[key] = [...new Set([...mapping[key].split(' '), mapping[rule.val]])].join(' ');
879
+ }
880
+ }
881
+ }
882
+ }
883
+ }
884
+ }
885
+ // composes: a b c from 'file.css';
886
+ else if (token.r.typ == EnumToken.String) {
887
+ const url = token.r.val.slice(1, -1);
888
+ const src = options.resolve(url, options.dirname(options.src), options.cwd);
889
+ const result = options.load(url, options.src);
890
+ const stream = result instanceof Promise || Object.getPrototypeOf(result).constructor.name == 'AsyncFunction' ? await result : result;
891
+ const root = await doParse(stream instanceof ReadableStream ? tokenizeStream(stream) : tokenize({
892
+ stream,
893
+ buffer: '',
894
+ offset: 0,
895
+ position: { ind: 0, lin: 1, col: 1 },
896
+ currentPosition: { ind: -1, lin: 1, col: 0 }
897
+ }), Object.assign({}, options, {
898
+ minify: false,
899
+ setParent: false,
900
+ src: src.relative
901
+ }));
902
+ const srcIndex = (src.relative.startsWith('/') || src.relative.startsWith('../') ? '' : './') + src.relative;
903
+ if (Object.keys(root.mapping).length > 0) {
904
+ importMapping[srcIndex] = {};
905
+ }
906
+ if (parentRule != null) {
907
+ for (const tk of parentRule.tokens) {
908
+ if (tk.typ == EnumToken.ClassSelectorTokenType) {
909
+ const val = tk.val.slice(1);
910
+ if (val in revMapping) {
911
+ const key = revMapping[val];
912
+ const values = [];
913
+ for (const iden of token.l) {
914
+ if (iden.typ != EnumToken.IdenTokenType && iden.typ != EnumToken.DashedIdenTokenType) {
915
+ continue;
916
+ }
917
+ if (!(iden.val in root.mapping)) {
918
+ const result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? iden.val : moduleSettings.generateScopedName(iden.val, srcIndex, moduleSettings.pattern, moduleSettings.hashLength);
919
+ let value = result instanceof Promise ? await result : result;
920
+ root.mapping[iden.val] = (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly ? getKeyName(value, moduleSettings.naming) : value);
921
+ root.revMapping[root.mapping[iden.val]] = iden.val;
922
+ }
923
+ importMapping[srcIndex][iden.val] = root.mapping[iden.val];
924
+ values.push(root.mapping[iden.val]);
925
+ }
926
+ mapping[key] = [...new Set([...mapping[key].split(' '), ...values])].join(' ');
927
+ }
928
+ }
929
+ }
930
+ }
931
+ }
932
+ // composes: a b c from global;
933
+ else if (token.r.typ == EnumToken.IdenTokenType) {
934
+ // global
935
+ if (parentRule != null) {
936
+ if ('global' == token.r.val.toLowerCase()) {
937
+ for (const tk of parentRule.tokens) {
938
+ if (tk.typ == EnumToken.ClassSelectorTokenType) {
939
+ const val = tk.val.slice(1);
940
+ if (val in revMapping) {
941
+ const key = revMapping[val];
942
+ mapping[key] = [...new Set([...mapping[key].split(' '), ...(token.l.reduce((acc, curr) => {
943
+ if (curr.typ == EnumToken.IdenTokenType) {
944
+ acc.push(curr.val);
945
+ }
946
+ return acc;
947
+ }, []))])].join(' ');
948
+ }
949
+ }
950
+ }
951
+ }
952
+ else {
953
+ errors.push({
954
+ action: 'drop',
955
+ message: `composes '${token.r.val}' is not supported`,
956
+ node
957
+ });
958
+ }
959
+ }
960
+ }
961
+ }
962
+ parentRule.chi.splice(parentRule.chi.indexOf(node), 1);
963
+ }
964
+ if (node.typ == EnumToken.DeclarationNodeType && ['grid-column', 'grid-column-start', 'grid-column-end', 'grid-row', 'grid-row-start', 'grid-row-end', 'grid-template', 'grid-template-columns', 'grid-template-rows'].includes(node.nam)) {
965
+ for (const { value } of walkValues(node.val, node)) {
966
+ if (value.typ != EnumToken.IdenTokenType) {
967
+ continue;
968
+ }
969
+ let idenToken = value.val;
970
+ let suffix = '';
971
+ if (idenToken.endsWith('-start')) {
972
+ suffix = '-start';
973
+ idenToken = idenToken.slice(0, -6);
974
+ }
975
+ else if (idenToken.endsWith('-end')) {
976
+ suffix = '-end';
977
+ idenToken = idenToken.slice(0, -4);
978
+ }
979
+ if (!(idenToken in mapping)) {
980
+ let result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? idenToken : moduleSettings.generateScopedName(idenToken, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
981
+ if (result instanceof Promise) {
982
+ result = await result;
983
+ }
984
+ mapping[idenToken] = result;
985
+ revMapping[result] = idenToken;
986
+ if (suffix !== '') {
987
+ idenToken += suffix;
988
+ if (!(idenToken in mapping)) {
989
+ mapping[idenToken] = result + suffix;
990
+ revMapping[result + suffix] = idenToken;
991
+ }
992
+ }
993
+ }
994
+ value.val = mapping[idenToken];
995
+ }
996
+ }
997
+ else if (node.nam == 'grid-template-areas' || node.nam == 'grid-template') {
998
+ for (let i = 0; i < node.val.length; i++) {
999
+ if (node.val[i].typ == EnumToken.String) {
1000
+ const tokens = parseString(node.val[i].val.slice(1, -1), { location: true });
1001
+ for (const { value } of walkValues(tokens)) {
1002
+ if (value.typ == EnumToken.IdenTokenType || value.typ == EnumToken.DashedIdenTokenType) {
1003
+ if (value.val in mapping) {
1004
+ value.val = mapping[value.val];
1005
+ }
1006
+ else {
1007
+ let result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? value.val : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
1008
+ if (result instanceof Promise) {
1009
+ result = await result;
1010
+ }
1011
+ mapping[value.val] = result;
1012
+ revMapping[result] = value.val;
1013
+ value.val = result;
1014
+ }
1015
+ }
1016
+ }
1017
+ node.val[i].val = node.val[i].val.charAt(0) + tokens.reduce((acc, curr) => acc + renderToken(curr), '') + node.val[i].val.charAt(node.val[i].val.length - 1);
1018
+ }
1019
+ }
1020
+ }
1021
+ else if (node.nam == 'animation' || node.nam == 'animation-name') {
1022
+ for (const { value } of walkValues(node.val, node)) {
1023
+ if (value.typ == EnumToken.IdenTokenType && ![
1024
+ 'none', 'infinite', 'normal', 'reverse', 'alternate',
1025
+ 'alternate-reverse', 'forwards', 'backwards', 'both',
1026
+ 'running', 'paused', 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out',
1027
+ 'step-start', 'step-end', 'jump-start', 'jump-end',
1028
+ 'jump-none', 'jump-both', 'start', 'end',
1029
+ 'inherit', 'initial', 'unset'
1030
+ ].includes(value.val)) {
1031
+ if (!(value.val in mapping)) {
1032
+ const result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? value.val : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
1033
+ mapping[value.val] = result instanceof Promise ? await result : result;
1034
+ revMapping[mapping[value.val]] = value.val;
1035
+ }
1036
+ value.val = mapping[value.val];
1037
+ }
1038
+ }
1039
+ }
1040
+ for (const { value, parent } of walkValues(node.val, node)) {
1041
+ if (value.typ == EnumToken.DashedIdenTokenType) {
1042
+ if (!(value.val in mapping)) {
1043
+ const result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? value.val : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
1044
+ let val = result instanceof Promise ? await result : result;
1045
+ mapping[value.val] = '--' + (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly ? getKeyName(val, moduleSettings.naming) : val);
1046
+ revMapping[mapping[value.val]] = value.val;
1047
+ }
1048
+ value.val = mapping[value.val];
1049
+ }
1050
+ else if ((value.typ == EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) {
1051
+ replaceToken(parent, value, importedCssVariables[value.val].val);
1052
+ }
1053
+ }
1054
+ }
1055
+ else if (node.typ == EnumToken.RuleNodeType) {
1056
+ if (node.tokens == null) {
1057
+ Object.defineProperty(node, 'tokens', {
1058
+ ...definedPropertySettings,
1059
+ value: parseSelector(parseString(node.sel, { location: true }))
1060
+ });
1061
+ }
1062
+ let hasIdOrClass = false;
1063
+ for (const { value } of walkValues(node.tokens, node,
1064
+ // @ts-ignore
1065
+ (value, parent) => {
1066
+ if (value.typ == EnumToken.PseudoClassTokenType) {
1067
+ const val = value.val.toLowerCase();
1068
+ switch (val) {
1069
+ case ':local':
1070
+ case ':global':
1071
+ {
1072
+ let index = parent.tokens.indexOf(value);
1073
+ parent.tokens.splice(index, 1);
1074
+ if (parent.tokens[index]?.typ == EnumToken.WhitespaceTokenType || parent.tokens[index]?.typ == EnumToken.DescendantCombinatorTokenType) {
1075
+ parent.tokens.splice(index, 1);
1076
+ }
1077
+ if (val == ':global') {
1078
+ for (; index < parent.tokens.length; index++) {
1079
+ if (parent.tokens[index].typ == EnumToken.CommaTokenType ||
1080
+ ([EnumToken.PseudoClassFuncTokenType, EnumToken.PseudoClassTokenType].includes(parent.tokens[index].typ) &&
1081
+ [':global', ':local'].includes(parent.tokens[index].val.toLowerCase()))) {
1082
+ break;
1083
+ }
1084
+ global.add(parent.tokens[index]);
1085
+ }
1086
+ }
1087
+ }
1088
+ break;
1089
+ }
1090
+ }
1091
+ else if (value.typ == EnumToken.PseudoClassFuncTokenType) {
1092
+ switch (value.val.toLowerCase()) {
1093
+ case ':global':
1094
+ for (const token of value.chi) {
1095
+ global.add(token);
1096
+ }
1097
+ parent.tokens.splice(parent.tokens.indexOf(value), 1, ...value.chi);
1098
+ break;
1099
+ case ':local':
1100
+ parent.tokens.splice(parent.tokens.indexOf(value), 1, ...value.chi);
1101
+ break;
1102
+ }
1103
+ }
1104
+ })) {
1105
+ if (value.typ == EnumToken.HashTokenType || value.typ == EnumToken.ClassSelectorTokenType) {
1106
+ hasIdOrClass = true;
1107
+ }
1108
+ if (processed.has(value)) {
1109
+ continue;
1110
+ }
1111
+ processed.add(value);
1112
+ if (value.typ == EnumToken.PseudoClassTokenType) ;
1113
+ else if (value.typ == EnumToken.PseudoClassFuncTokenType) ;
1114
+ else {
1115
+ if (global.has(value)) {
1116
+ continue;
1117
+ }
1118
+ if (value.typ == EnumToken.ClassSelectorTokenType) {
1119
+ const val = value.val.slice(1);
1120
+ if (!(val in mapping)) {
1121
+ const result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? val : moduleSettings.generateScopedName(val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
1122
+ let value = result instanceof Promise ? await result : result;
1123
+ mapping[val] = (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly ? getKeyName(value, moduleSettings.naming) : value);
1124
+ revMapping[mapping[val]] = val;
1125
+ }
1126
+ value.val = '.' + mapping[val];
1127
+ }
1128
+ }
1129
+ }
1130
+ if (moduleSettings.scoped & ModuleScopeEnumOptions.Pure) {
1131
+ if (!hasIdOrClass) {
1132
+ throw new Error(`pure module: No id or class found in selector '${node.sel}' at '${node.loc?.src ?? ''}':${node.loc?.sta?.lin ?? ''}:${node.loc?.sta?.col ?? ''}`);
1133
+ }
1134
+ }
1135
+ node.sel = '';
1136
+ for (const token of node.tokens) {
1137
+ node.sel += renderToken(token);
1138
+ }
1139
+ }
1140
+ else if (node.typ == EnumToken.AtRuleNodeType || node.typ == EnumToken.KeyframesAtRuleNodeType) {
1141
+ const val = node.nam.toLowerCase();
1142
+ if (node.tokens == null) {
1143
+ Object.defineProperty(node, 'tokens', {
1144
+ ...definedPropertySettings,
1145
+ // @ts-ignore
1146
+ value: parseAtRulePrelude(parseString(node.val), node)
1147
+ });
1148
+ }
1149
+ if (val == 'property' || val == 'keyframes') {
1150
+ const prefix = val == 'property' ? '--' : '';
1151
+ for (const value of node.tokens) {
1152
+ if ((prefix == '--' && value.typ == EnumToken.DashedIdenTokenType) || (prefix == '' && value.typ == EnumToken.IdenTokenType)) {
1153
+ if (!(value.val in mapping)) {
1154
+ const result = (moduleSettings.scoped & ModuleScopeEnumOptions.Global) ? value.val : moduleSettings.generateScopedName(value.val, moduleSettings.filePath, moduleSettings.pattern, moduleSettings.hashLength);
1155
+ let val = result instanceof Promise ? await result : result;
1156
+ mapping[value.val] = prefix + (moduleSettings.naming & ModuleCaseTransformEnum.DashCaseOnly || moduleSettings.naming & ModuleCaseTransformEnum.CamelCaseOnly ? getKeyName(val, moduleSettings.naming) : val);
1157
+ revMapping[mapping[value.val]] = value.val;
1158
+ }
1159
+ value.val = mapping[value.val];
1160
+ }
1161
+ }
1162
+ node.val = node.tokens.reduce((a, b) => a + renderToken(b), '');
1163
+ }
1164
+ else {
1165
+ let isReplaced = false;
1166
+ for (const { value, parent } of walkValues(node.tokens, node)) {
1167
+ if (EnumToken.MediaQueryConditionTokenType == parent.typ && value != parent.l) {
1168
+ if ((value.typ == EnumToken.IdenTokenType || isIdentColor(value)) && value.val in importedCssVariables) {
1169
+ isReplaced = true;
1170
+ parent.r.splice(parent.r.indexOf(value), 1, ...importedCssVariables[value.val].val);
1171
+ }
1172
+ }
1173
+ }
1174
+ if (isReplaced) {
1175
+ node.val = node.tokens.reduce((a, b) => a + renderToken(b), '');
1176
+ }
1177
+ }
1178
+ }
1179
+ }
1180
+ if (moduleSettings.naming != ModuleCaseTransformEnum.IgnoreCase) {
1181
+ revMapping = {};
1182
+ mapping = Object.entries(mapping).reduce((acc, [key, value]) => {
1183
+ const keyName = getKeyName(key, moduleSettings.naming);
1184
+ acc[keyName] = value;
1185
+ revMapping[value] = keyName;
1186
+ return acc;
1187
+ }, {});
1188
+ }
1189
+ result.mapping = mapping;
1190
+ result.revMapping = revMapping;
1191
+ if ((moduleSettings.scoped & ModuleScopeEnumOptions.ICSS) && Object.keys(importMapping).length > 0) {
1192
+ result.importMapping = importMapping;
1193
+ }
1194
+ endTime = performance.now();
1195
+ result.stats.module = `${(endTime - parseModuleTime).toFixed(2)}ms`;
1196
+ result.stats.total = `${(endTime - startTime).toFixed(2)}ms`;
1197
+ }
1198
+ if (options.signal != null) {
1199
+ options.signal.removeEventListener('abort', reject);
1200
+ }
1201
+ return result;
581
1202
  }
582
1203
  function getLastNode(context) {
583
1204
  let i = context.chi.length;
@@ -795,6 +1416,147 @@ function parseNode(results, context, options, errors, src, map, rawTokens, stats
795
1416
  isValid = false;
796
1417
  }
797
1418
  }
1419
+ if (node.nam == 'value') {
1420
+ let i = 0;
1421
+ while (i < tokens.length) {
1422
+ if (tokens[i].typ == EnumToken.WhitespaceTokenType || tokens[i].typ == EnumToken.CommentTokenType) {
1423
+ i++;
1424
+ continue;
1425
+ }
1426
+ break;
1427
+ }
1428
+ if (i < tokens.length) {
1429
+ if (tokens[i].typ == EnumToken.IdenTokenType || isIdentColor(tokens[i])) {
1430
+ let k = i + 1;
1431
+ while (k < tokens.length) {
1432
+ if (tokens[k].typ == EnumToken.WhitespaceTokenType || tokens[k].typ == EnumToken.CommentTokenType) {
1433
+ k++;
1434
+ continue;
1435
+ }
1436
+ // var or import
1437
+ if (tokens[k].typ == EnumToken.ColonTokenType) {
1438
+ let j = k;
1439
+ while (++j < tokens.length) {
1440
+ if (tokens[j].typ != EnumToken.WhitespaceTokenType && tokens[j].typ != EnumToken.CommentTokenType) {
1441
+ break;
1442
+ }
1443
+ }
1444
+ let offset = k + 1;
1445
+ while (offset < tokens.length && tokens[offset].typ == EnumToken.WhitespaceTokenType) {
1446
+ offset++;
1447
+ }
1448
+ if (tokens[j].typ == EnumToken.StringTokenType) {
1449
+ Object.assign(node, {
1450
+ typ: EnumToken.CssVariableImportTokenType,
1451
+ nam: tokens[i].val,
1452
+ val: tokens.slice(offset)
1453
+ });
1454
+ delete node.tokens;
1455
+ // @ts-ignore
1456
+ delete node.raw;
1457
+ context.chi.push(node);
1458
+ return null;
1459
+ }
1460
+ Object.assign(node, {
1461
+ typ: EnumToken.CssVariableTokenType,
1462
+ nam: tokens[i].val,
1463
+ val: tokens.slice(offset)
1464
+ });
1465
+ context.chi.push(node);
1466
+ return null;
1467
+ }
1468
+ if (tokens[k].typ == EnumToken.PseudoClassTokenType) {
1469
+ Object.assign(tokens[k], {
1470
+ typ: EnumToken.IdenTokenType,
1471
+ val: tokens[k].val.slice(1)
1472
+ });
1473
+ Object.assign(node, {
1474
+ typ: EnumToken.CssVariableTokenType,
1475
+ nam: tokens[i].val,
1476
+ val: tokens.slice(k)
1477
+ });
1478
+ context.chi.push(node);
1479
+ return null;
1480
+ }
1481
+ if (tokens[k].typ == EnumToken.CommaTokenType) {
1482
+ let j = i;
1483
+ while (++j < tokens.length) {
1484
+ if (tokens[j].typ == EnumToken.IdenTokenType && tokens[j].val.toLowerCase() == 'from') {
1485
+ const vars = tokens.slice(i, j);
1486
+ const from = tokens.slice(j + 1);
1487
+ let l = 0;
1488
+ let expect = EnumToken.IdenTokenType;
1489
+ for (; l < vars.length; l++) {
1490
+ if (vars[l].typ == EnumToken.WhitespaceTokenType || vars[l].typ == EnumToken.CommentTokenType) {
1491
+ continue;
1492
+ }
1493
+ if (expect == vars[l].typ || (expect == EnumToken.IdenTokenType && isIdentColor(vars[l]))) {
1494
+ expect = expect == EnumToken.CommaTokenType ? EnumToken.IdenTokenType : EnumToken.CommaTokenType;
1495
+ continue;
1496
+ }
1497
+ errors.push({
1498
+ action: 'drop',
1499
+ node: node,
1500
+ location: map.get(vars[l]) ?? location,
1501
+ message: `expecting '${EnumToken[expect]}' but found ${renderToken(vars[l])}`
1502
+ });
1503
+ return null;
1504
+ }
1505
+ l = 0;
1506
+ expect = EnumToken.IdenTokenType;
1507
+ for (; l < from.length; l++) {
1508
+ if (from[l].typ == EnumToken.WhitespaceTokenType || from[l].typ == EnumToken.CommentTokenType) {
1509
+ continue;
1510
+ }
1511
+ if (expect == from[l].typ || isIdentColor(from[l])) {
1512
+ while (++l < from.length) {
1513
+ if (from[l].typ == EnumToken.WhitespaceTokenType || from[l].typ == EnumToken.CommentTokenType) {
1514
+ continue;
1515
+ }
1516
+ errors.push({
1517
+ action: 'drop',
1518
+ node: node,
1519
+ location: map.get(from[l]) ?? location,
1520
+ message: `unexpected '${renderToken(from[l])}'`
1521
+ });
1522
+ return null;
1523
+ }
1524
+ break;
1525
+ }
1526
+ errors.push({
1527
+ action: 'drop',
1528
+ node: node,
1529
+ location: map.get(from[l]) ?? location,
1530
+ message: `expecting <string> but found ${renderToken(from[l])}`
1531
+ });
1532
+ return null;
1533
+ }
1534
+ // @ts-ignore
1535
+ delete node.nam;
1536
+ // @ts-ignore
1537
+ delete node.val;
1538
+ Object.assign(node, {
1539
+ typ: EnumToken.CssVariableDeclarationMapTokenType,
1540
+ vars,
1541
+ from
1542
+ });
1543
+ context.chi.push(node);
1544
+ return null;
1545
+ }
1546
+ }
1547
+ }
1548
+ k++;
1549
+ }
1550
+ Object.assign(node, {
1551
+ typ: EnumToken.CssVariableTokenType,
1552
+ nam: tokens[i].val,
1553
+ val: tokens.slice(k)
1554
+ });
1555
+ context.chi.push(node);
1556
+ return null;
1557
+ }
1558
+ }
1559
+ }
798
1560
  // @ts-ignore
799
1561
  const skipValidate = (options.validation & ValidationLevel.AtRule) == 0;
800
1562
  const isAllowed = skipValidate || isNodeAllowedInContext(node, context);
@@ -1333,6 +2095,7 @@ async function parseDeclarations(declaration) {
1333
2095
  return doParse(tokenize({
1334
2096
  stream: `.x{${declaration}}`,
1335
2097
  buffer: '',
2098
+ offset: 0,
1336
2099
  position: { ind: 0, lin: 1, col: 1 },
1337
2100
  currentPosition: { ind: -1, lin: 1, col: 0 }
1338
2101
  }), { setParent: false, minify: false, validation: false }).then(result => {
@@ -1453,6 +2216,7 @@ function parseString(src, options = { location: false }) {
1453
2216
  const parseInfo = {
1454
2217
  stream: src,
1455
2218
  buffer: '',
2219
+ offset: 0,
1456
2220
  position: { ind: 0, lin: 1, col: 1 },
1457
2221
  currentPosition: { ind: -1, lin: 1, col: 0 }
1458
2222
  };
@@ -1578,12 +2342,10 @@ function getTokenType(val, hint) {
1578
2342
  val: +val.slice(0, -1)
1579
2343
  };
1580
2344
  }
1581
- // if (isDimension(val)) {
1582
2345
  const dimension = parseDimension(val);
1583
2346
  if (dimension != null) {
1584
2347
  return dimension;
1585
2348
  }
1586
- // }
1587
2349
  const v = val.toLowerCase();
1588
2350
  if (v == 'currentcolor' || v == 'transparent' || v in COLORS_NAMES) {
1589
2351
  return {
@@ -1612,6 +2374,12 @@ function getTokenType(val, hint) {
1612
2374
  val
1613
2375
  };
1614
2376
  }
2377
+ if (val.charAt(0) == '.' && isIdent(val.slice(1))) {
2378
+ return {
2379
+ typ: EnumToken.ClassSelectorTokenType,
2380
+ val
2381
+ };
2382
+ }
1615
2383
  if (val.charAt(0) == '#' && isHexColor(val)) {
1616
2384
  return {
1617
2385
  typ: EnumToken.ColorTokenType,
@@ -1659,6 +2427,55 @@ function getTokenType(val, hint) {
1659
2427
  function parseTokens(tokens, options = {}) {
1660
2428
  for (let i = 0; i < tokens.length; i++) {
1661
2429
  const t = tokens[i];
2430
+ if (t.typ == EnumToken.IdenTokenType && t.val == 'from' && i > 0) {
2431
+ const left = [];
2432
+ const right = [];
2433
+ let foundLeft = 0;
2434
+ let foundRight = 0;
2435
+ let k = i;
2436
+ let l = i;
2437
+ while (k > 0) {
2438
+ if (tokens[k - 1].typ == EnumToken.CommentTokenType || tokens[k - 1].typ == EnumToken.WhitespaceTokenType) {
2439
+ left.push(tokens[--k]);
2440
+ continue;
2441
+ }
2442
+ if (tokens[k - 1].typ == EnumToken.IdenTokenType || tokens[k - 1].typ == EnumToken.DashedIdenTokenType) {
2443
+ foundLeft++;
2444
+ left.push(tokens[--k]);
2445
+ continue;
2446
+ }
2447
+ break;
2448
+ }
2449
+ while (++l < tokens.length) {
2450
+ if (tokens[l].typ == EnumToken.CommentTokenType || tokens[l].typ == EnumToken.WhitespaceTokenType) {
2451
+ right.push(tokens[l]);
2452
+ continue;
2453
+ }
2454
+ if (tokens[l].typ == EnumToken.IdenTokenType || tokens[l].typ == EnumToken.StringTokenType) {
2455
+ foundRight++;
2456
+ right.push(tokens[l]);
2457
+ continue;
2458
+ }
2459
+ break;
2460
+ }
2461
+ if (foundLeft > 0 && foundRight == 1) {
2462
+ while (left?.[0].typ == EnumToken.WhitespaceTokenType) {
2463
+ left.shift();
2464
+ }
2465
+ while (left.at(-1)?.typ == EnumToken.WhitespaceTokenType) {
2466
+ left.pop();
2467
+ }
2468
+ tokens.splice(k, l - k + 1, {
2469
+ typ: EnumToken.ComposesSelectorNodeType,
2470
+ l: left,
2471
+ r: right.reduce((a, b) => {
2472
+ return a == null ? b : b.typ == EnumToken.IdenTokenType || b.typ == EnumToken.StringTokenType ? b : a;
2473
+ }, null)
2474
+ });
2475
+ i = k;
2476
+ continue;
2477
+ }
2478
+ }
1662
2479
  if (t.typ == EnumToken.WhitespaceTokenType && ((i == 0 ||
1663
2480
  i + 1 == tokens.length ||
1664
2481
  [EnumToken.CommaTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType].includes(tokens[i + 1].typ)) ||
@@ -1942,4 +2759,4 @@ function parseTokens(tokens, options = {}) {
1942
2759
  return tokens;
1943
2760
  }
1944
2761
 
1945
- export { doParse, getTokenType, parseAtRulePrelude, parseDeclarations, parseSelector, parseString, parseTokens, replaceToken, urlTokenMatcher };
2762
+ export { doParse, generateScopedName, getKeyName, getTokenType, parseAtRulePrelude, parseDeclarations, parseSelector, parseString, parseTokens, replaceToken, urlTokenMatcher };