@tbela99/css-parser 0.0.1 → 0.2.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,238 +1,243 @@
1
1
  import { isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHexColor, isHash, isIdentStart, isColor } from './utils/syntax.js';
2
+ import { EnumToken } from '../ast/types.js';
3
+ import { minify, combinators } from '../ast/minify.js';
4
+ import { walkValues, walk } from '../ast/walk.js';
5
+ import { expand } from '../ast/expand.js';
2
6
  import { renderToken } from '../renderer/render.js';
3
7
  import { COLORS_NAMES } from '../renderer/utils/color.js';
4
- import { minify, combinators } from '../ast/minify.js';
5
8
  import { tokenize } from './tokenize.js';
6
9
 
7
10
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
8
- const trimWhiteSpace = ['Gt', 'Gte', 'Lt', 'Lte'];
9
- const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
10
- /**
11
- *
12
- * @param iterator
13
- * @param opt
14
- */
15
- async function parse(iterator, opt = {}) {
16
- const startTime = performance.now();
17
- const errors = [];
18
- const options = {
19
- src: '',
20
- sourcemap: false,
21
- minify: true,
22
- nestingRules: false,
23
- resolveImport: false,
24
- resolveUrls: false,
25
- removeEmpty: true,
26
- ...opt
27
- };
28
- if (options.resolveImport) {
29
- options.resolveUrls = true;
30
- }
31
- const src = options.src;
32
- const stack = [];
33
- const ast = {
34
- typ: "StyleSheet",
35
- chi: []
36
- };
37
- let tokens = [];
38
- let map = new Map;
39
- let bytesIn = 0;
40
- let context = ast;
41
- if (options.sourcemap) {
42
- ast.loc = {
43
- sta: {
44
- ind: 0,
45
- lin: 1,
46
- col: 1
47
- },
48
- src: ''
49
- };
50
- }
51
- async function parseNode(results) {
52
- let tokens = results.map(mapToken);
53
- let i;
54
- let loc;
55
- for (i = 0; i < tokens.length; i++) {
56
- if (tokens[i].typ == 'Comment' || tokens[i].typ == 'CDOCOMM') {
57
- const position = map.get(tokens[i]);
58
- if (tokens[i].typ == 'CDOCOMM' && context.typ != 'StyleSheet') {
59
- errors.push({ action: 'drop', message: `CDOCOMM not allowed here ${JSON.stringify(tokens[i], null, 1)}`, location: { src, ...position } });
60
- continue;
61
- }
62
- loc = {
63
- sta: position,
64
- src
65
- };
66
- // @ts-ignore
67
- context.chi.push(tokens[i]);
68
- if (options.sourcemap) {
69
- tokens[i].loc = loc;
70
- }
71
- }
72
- else if (tokens[i].typ != 'Whitespace') {
73
- break;
74
- }
75
- }
76
- tokens = tokens.slice(i);
77
- if (tokens.length == 0) {
78
- return null;
79
- }
80
- let delim = tokens.at(-1);
81
- if (delim.typ == 'Semi-colon' || delim.typ == 'Block-start' || delim.typ == 'Block-end') {
82
- tokens.pop();
11
+ const trimWhiteSpace = [EnumToken.CommentTokenType, EnumToken.GtTokenType, EnumToken.GteTokenType, EnumToken.LtTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType];
12
+ const funcLike = [
13
+ EnumToken.ParensTokenType,
14
+ EnumToken.FunctionTokenType,
15
+ EnumToken.UrlFunctionTokenType,
16
+ EnumToken.StartParensTokenType,
17
+ EnumToken.ImageFunctionTokenType,
18
+ EnumToken.PseudoClassFuncTokenType,
19
+ EnumToken.TimingFunctionTokenType,
20
+ EnumToken.TimingFunctionTokenType
21
+ ];
22
+ const BadTokensTypes = [
23
+ EnumToken.BadCommentTokenType,
24
+ EnumToken.BadCdoTokenType,
25
+ EnumToken.BadUrlTokenType,
26
+ EnumToken.BadStringTokenType
27
+ ];
28
+ const webkitPseudoAliasMap = {
29
+ '-webkit-autofill': 'autofill'
30
+ };
31
+ async function doParse(iterator, options = {}) {
32
+ return new Promise(async (resolve, reject) => {
33
+ if (options.signal != null) {
34
+ options.signal.addEventListener('abort', reject);
83
35
  }
84
- else {
85
- delim = { typ: 'Semi-colon' };
36
+ options = {
37
+ src: '',
38
+ sourcemap: false,
39
+ minify: true,
40
+ nestingRules: false,
41
+ resolveImport: false,
42
+ resolveUrls: false,
43
+ removeCharset: false,
44
+ removeEmpty: true,
45
+ removeDuplicateDeclarations: true,
46
+ computeShorthand: true,
47
+ computeCalcExpression: true,
48
+ inlineCssVariables: false,
49
+ ...options
50
+ };
51
+ if (options.expandNestingRules) {
52
+ options.nestingRules = false;
86
53
  }
87
- // @ts-ignore
88
- while (['Whitespace', 'Bad-string', 'Bad-comment'].includes(tokens.at(-1)?.typ)) {
89
- tokens.pop();
54
+ if (options.resolveImport) {
55
+ options.resolveUrls = true;
90
56
  }
91
- if (tokens.length == 0) {
92
- return null;
57
+ const startTime = performance.now();
58
+ const errors = [];
59
+ const src = options.src;
60
+ const stack = [];
61
+ let ast = {
62
+ typ: EnumToken.StyleSheetNodeType,
63
+ chi: []
64
+ };
65
+ let tokens = [];
66
+ let map = new Map;
67
+ let bytesIn = 0;
68
+ let context = ast;
69
+ if (options.sourcemap) {
70
+ ast.loc = {
71
+ sta: {
72
+ ind: 0,
73
+ lin: 1,
74
+ col: 1
75
+ },
76
+ src: ''
77
+ };
93
78
  }
94
- if (tokens[0]?.typ == 'At-rule') {
95
- const atRule = tokens.shift();
96
- const position = map.get(atRule);
97
- if (atRule.val == 'charset' && position.ind > 0) {
98
- errors.push({ action: 'drop', message: 'parse: invalid @charset', location: { src, ...position } });
79
+ async function parseNode(results) {
80
+ let tokens = results.map(mapToken);
81
+ let i;
82
+ let loc;
83
+ for (i = 0; i < tokens.length; i++) {
84
+ if (tokens[i].typ == EnumToken.CommentTokenType || tokens[i].typ == EnumToken.CDOCOMMTokenType) {
85
+ const position = map.get(tokens[i]);
86
+ if (tokens[i].typ == EnumToken.CDOCOMMTokenType && context.typ != EnumToken.StyleSheetNodeType) {
87
+ errors.push({
88
+ action: 'drop',
89
+ message: `CDOCOMM not allowed here ${JSON.stringify(tokens[i], null, 1)}`,
90
+ location: { src, ...position }
91
+ });
92
+ continue;
93
+ }
94
+ loc = {
95
+ sta: position,
96
+ src
97
+ };
98
+ // @ts-ignore
99
+ context.chi.push(tokens[i]);
100
+ if (options.sourcemap) {
101
+ tokens[i].loc = loc;
102
+ }
103
+ }
104
+ else if (tokens[i].typ != EnumToken.WhitespaceTokenType) {
105
+ break;
106
+ }
107
+ }
108
+ tokens = tokens.slice(i);
109
+ if (tokens.length == 0) {
99
110
  return null;
100
111
  }
112
+ let delim = tokens.at(-1);
113
+ if (delim.typ == EnumToken.SemiColonTokenType || delim.typ == EnumToken.BlockStartTokenType || delim.typ == EnumToken.BlockEndTokenType) {
114
+ tokens.pop();
115
+ }
116
+ else {
117
+ delim = { typ: EnumToken.SemiColonTokenType };
118
+ }
101
119
  // @ts-ignore
102
- while (['Whitespace'].includes(tokens[0]?.typ)) {
103
- tokens.shift();
120
+ while ([EnumToken.WhitespaceTokenType, EnumToken.BadStringTokenType, EnumToken.BadCommentTokenType].includes(tokens.at(-1)?.typ)) {
121
+ tokens.pop();
104
122
  }
105
- if (atRule.val == 'import') {
106
- // only @charset and @layer are accepted before @import
107
- if (context.chi.length > 0) {
108
- let i = context.chi.length;
109
- while (i--) {
110
- const type = context.chi[i].typ;
111
- if (type == 'Comment') {
112
- continue;
113
- }
114
- if (type != 'AtRule') {
115
- errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
116
- return null;
117
- }
118
- const name = context.chi[i].nam;
119
- if (name != 'charset' && name != 'import' && name != 'layer') {
120
- errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
121
- return null;
122
- }
123
- break;
123
+ if (tokens.length == 0) {
124
+ return null;
125
+ }
126
+ if (tokens[0]?.typ == EnumToken.AtRuleTokenType) {
127
+ const atRule = tokens.shift();
128
+ const position = map.get(atRule);
129
+ if (atRule.val == 'charset') {
130
+ if (position.ind > 0) {
131
+ errors.push({
132
+ action: 'drop',
133
+ message: 'doParse: invalid @charset',
134
+ location: { src, ...position }
135
+ });
136
+ return null;
137
+ }
138
+ if (options.removeCharset) {
139
+ return null;
124
140
  }
125
141
  }
126
142
  // @ts-ignore
127
- if (tokens[0]?.typ != 'String' && tokens[0]?.typ != 'UrlFunc') {
128
- errors.push({ action: 'drop', message: 'parse: invalid @import', location: { src, ...position } });
129
- return null;
130
- }
131
- // @ts-ignore
132
- if (tokens[0].typ == 'UrlFunc' && tokens[1]?.typ != 'Url-token' && tokens[1]?.typ != 'String') {
133
- errors.push({ action: 'drop', message: 'parse: invalid @import', location: { src, ...position } });
134
- return null;
135
- }
136
- }
137
- if (atRule.val == 'import') {
138
- // @ts-ignore
139
- if (tokens[0].typ == 'UrlFunc' && tokens[1].typ == 'Url-token') {
143
+ while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) {
140
144
  tokens.shift();
141
- // @ts-ignore
142
- tokens[0].typ = 'String';
143
- // @ts-ignore
144
- tokens[0].val = `"${tokens[0].val}"`;
145
145
  }
146
- // @ts-ignore
147
- if (tokens[0].typ == 'String') {
148
- if (options.resolveImport) {
149
- const url = tokens[0].val.slice(1, -1);
150
- try {
151
- // @ts-ignore
152
- const root = await options.load(url, options.src).then((src) => {
153
- return parse(src, Object.assign({}, options, {
154
- minify: false,
155
- // @ts-ignore
156
- src: options.resolve(url, options.src).absolute
157
- }));
158
- });
159
- bytesIn += root.stats.bytesIn;
160
- if (root.ast.chi.length > 0) {
161
- context.chi.push(...root.ast.chi);
146
+ if (atRule.val == 'import') {
147
+ // only @charset and @layer are accepted before @import
148
+ if (context.chi.length > 0) {
149
+ let i = context.chi.length;
150
+ while (i--) {
151
+ const type = context.chi[i].typ;
152
+ if (type == EnumToken.CommentNodeType) {
153
+ continue;
162
154
  }
163
- if (root.errors.length > 0) {
164
- errors.push(...root.errors);
155
+ if (type != EnumToken.AtRuleNodeType) {
156
+ errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
157
+ return null;
165
158
  }
166
- return null;
167
- }
168
- catch (error) {
169
- // @ts-ignore
170
- errors.push({ action: 'ignore', message: 'parse: ' + error.message, error });
159
+ const name = context.chi[i].nam;
160
+ if (name != 'charset' && name != 'import' && name != 'layer') {
161
+ errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
162
+ return null;
163
+ }
164
+ break;
171
165
  }
172
166
  }
173
- }
174
- }
175
- // https://www.w3.org/TR/css-nesting-1/#conditionals
176
- // allowed nesting at-rules
177
- // there must be a top level rule in the stack
178
- const raw = parseTokens(tokens, { minify: options.minify }).reduce((acc, curr) => {
179
- acc.push(renderToken(curr, { removeComments: true }));
180
- return acc;
181
- }, []);
182
- const node = {
183
- typ: 'AtRule',
184
- nam: renderToken(atRule, { removeComments: true }),
185
- val: raw.join('')
186
- };
187
- Object.defineProperty(node, 'raw', { enumerable: false, configurable: true, writable: true, value: raw });
188
- if (delim.typ == 'Block-start') {
189
- node.chi = [];
190
- }
191
- loc = {
192
- sta: position,
193
- src
194
- };
195
- if (options.sourcemap) {
196
- node.loc = loc;
197
- }
198
- // @ts-ignore
199
- context.chi.push(node);
200
- return delim.typ == 'Block-start' ? node : null;
201
- }
202
- else {
203
- // rule
204
- if (delim.typ == 'Block-start') {
205
- const position = map.get(tokens[0]);
206
- const uniq = new Map;
207
- parseTokens(tokens, { minify: true }).reduce((acc, curr, index, array) => {
208
- if (curr.typ == 'Whitespace') {
209
- if (trimWhiteSpace.includes(array[index - 1]?.typ) ||
210
- trimWhiteSpace.includes(array[index + 1]?.typ) ||
211
- combinators.includes(array[index - 1]?.val) ||
212
- combinators.includes(array[index + 1]?.val)) {
213
- return acc;
214
- }
167
+ // @ts-ignore
168
+ if (tokens[0]?.typ != EnumToken.StringTokenType && tokens[0]?.typ != EnumToken.UrlFunctionTokenType) {
169
+ errors.push({
170
+ action: 'drop',
171
+ message: 'doParse: invalid @import',
172
+ location: { src, ...position }
173
+ });
174
+ return null;
215
175
  }
216
- let t = renderToken(curr, { minify: false });
217
- if (t == ',') {
218
- acc.push([]);
176
+ // @ts-ignore
177
+ if (tokens[0].typ == EnumToken.UrlFunctionTokenType && tokens[1]?.typ != EnumToken.UrlTokenTokenType && tokens[1]?.typ != EnumToken.StringTokenType) {
178
+ errors.push({
179
+ action: 'drop',
180
+ message: 'doParse: invalid @import',
181
+ location: { src, ...position }
182
+ });
183
+ return null;
219
184
  }
220
- else {
221
- acc[acc.length - 1].push(t);
185
+ }
186
+ if (atRule.val == 'import') {
187
+ // @ts-ignore
188
+ if (tokens[0].typ == EnumToken.UrlFunctionTokenType && tokens[1].typ == EnumToken.UrlTokenTokenType) {
189
+ tokens.shift();
190
+ // @ts-ignore
191
+ tokens[0].typ = EnumToken.StringTokenType;
192
+ // @ts-ignore
193
+ tokens[0].val = `"${tokens[0].val}"`;
222
194
  }
195
+ // @ts-ignore
196
+ if (tokens[0].typ == EnumToken.StringTokenType) {
197
+ if (options.resolveImport) {
198
+ const url = tokens[0].val.slice(1, -1);
199
+ try {
200
+ // @ts-ignore
201
+ const root = await options.load(url, options.src).then((src) => {
202
+ return doParse(src, Object.assign({}, options, {
203
+ minify: false,
204
+ // @ts-ignore
205
+ src: options.resolve(url, options.src).absolute
206
+ }));
207
+ });
208
+ bytesIn += root.stats.bytesIn;
209
+ if (root.ast.chi.length > 0) {
210
+ // @todo - filter charset, layer and scope
211
+ context.chi.push(...root.ast.chi);
212
+ }
213
+ if (root.errors.length > 0) {
214
+ errors.push(...root.errors);
215
+ }
216
+ return null;
217
+ }
218
+ catch (error) {
219
+ // @ts-ignore
220
+ errors.push({ action: 'ignore', message: 'doParse: ' + error.message, error });
221
+ }
222
+ }
223
+ }
224
+ }
225
+ // https://www.w3.org/TR/css-nesting-1/#conditionals
226
+ // allowed nesting at-rules
227
+ // there must be a top level rule in the stack
228
+ const raw = parseTokens(tokens, { minify: options.minify }).reduce((acc, curr) => {
229
+ acc.push(renderToken(curr, { removeComments: true }));
223
230
  return acc;
224
- }, [[]]).reduce((acc, curr) => {
225
- acc.set(curr.join(''), curr);
226
- return acc;
227
- }, uniq);
231
+ }, []);
228
232
  const node = {
229
- typ: 'Rule',
230
- // @ts-ignore
231
- sel: [...uniq.keys()].join(','),
232
- chi: []
233
+ typ: EnumToken.AtRuleNodeType,
234
+ nam: renderToken(atRule, { removeComments: true }),
235
+ val: raw.join('')
233
236
  };
234
- let raw = [...uniq.values()];
235
237
  Object.defineProperty(node, 'raw', { enumerable: false, configurable: true, writable: true, value: raw });
238
+ if (delim.typ == EnumToken.BlockStartTokenType) {
239
+ node.chi = [];
240
+ }
236
241
  loc = {
237
242
  sta: position,
238
243
  src
@@ -242,171 +247,255 @@ async function parse(iterator, opt = {}) {
242
247
  }
243
248
  // @ts-ignore
244
249
  context.chi.push(node);
245
- return node;
250
+ return delim.typ == EnumToken.BlockStartTokenType ? node : null;
246
251
  }
247
252
  else {
248
- // declaration
249
- // @ts-ignore
250
- let name = null;
251
- // @ts-ignore
252
- let value = null;
253
- for (let i = 0; i < tokens.length; i++) {
254
- if (tokens[i].typ == 'Comment') {
255
- continue;
256
- }
257
- if (tokens[i].typ == 'Colon') {
258
- name = tokens.slice(0, i);
259
- value = parseTokens(tokens.slice(i + 1), {
260
- parseColor: true,
261
- src: options.src,
262
- resolveUrls: options.resolveUrls,
263
- resolve: options.resolve,
264
- cwd: options.cwd
265
- });
253
+ // rule
254
+ if (delim.typ == EnumToken.BlockStartTokenType) {
255
+ const position = map.get(tokens[0]);
256
+ const uniq = new Map;
257
+ parseTokens(tokens, { minify: true }).reduce((acc, curr, index, array) => {
258
+ if (curr.typ == EnumToken.WhitespaceTokenType) {
259
+ if (trimWhiteSpace.includes(array[index - 1]?.typ) ||
260
+ trimWhiteSpace.includes(array[index + 1]?.typ) ||
261
+ combinators.includes(array[index - 1]?.val) ||
262
+ combinators.includes(array[index + 1]?.val)) {
263
+ return acc;
264
+ }
265
+ }
266
+ let t = renderToken(curr, { minify: false });
267
+ if (t == ',') {
268
+ acc.push([]);
269
+ }
270
+ else {
271
+ acc[acc.length - 1].push(t);
272
+ }
273
+ return acc;
274
+ }, [[]]).reduce((acc, curr) => {
275
+ acc.set(curr.join(''), curr);
276
+ return acc;
277
+ }, uniq);
278
+ const node = {
279
+ typ: EnumToken.RuleNodeType,
280
+ // @ts-ignore
281
+ sel: [...uniq.keys()].join(','),
282
+ chi: []
283
+ };
284
+ let raw = [...uniq.values()];
285
+ Object.defineProperty(node, 'raw', {
286
+ enumerable: false,
287
+ configurable: true,
288
+ writable: true,
289
+ value: raw
290
+ });
291
+ loc = {
292
+ sta: position,
293
+ src
294
+ };
295
+ if (options.sourcemap) {
296
+ node.loc = loc;
266
297
  }
298
+ // @ts-ignore
299
+ context.chi.push(node);
300
+ return node;
267
301
  }
268
- if (name == null) {
269
- name = tokens;
270
- }
271
- const position = map.get(name[0]);
272
- if (name.length > 0) {
273
- for (let i = 1; i < name.length; i++) {
274
- if (name[i].typ != 'Whitespace' && name[i].typ != 'Comment') {
275
- errors.push({
276
- action: 'drop',
277
- message: 'parse: invalid declaration',
278
- location: { src, ...position }
302
+ else {
303
+ // declaration
304
+ // @ts-ignore
305
+ let name = null;
306
+ // @ts-ignore
307
+ let value = null;
308
+ for (let i = 0; i < tokens.length; i++) {
309
+ if (tokens[i].typ == EnumToken.CommentTokenType) {
310
+ continue;
311
+ }
312
+ if (tokens[i].typ == EnumToken.ColonTokenType) {
313
+ name = tokens.slice(0, i);
314
+ value = parseTokens(tokens.slice(i + 1), {
315
+ parseColor: true,
316
+ src: options.src,
317
+ resolveUrls: options.resolveUrls,
318
+ resolve: options.resolve,
319
+ cwd: options.cwd
279
320
  });
280
- return null;
281
321
  }
282
322
  }
283
- }
284
- if (value == null) {
285
- errors.push({
286
- action: 'drop',
287
- message: 'parse: invalid declaration',
288
- location: { src, ...position }
289
- });
290
- return null;
291
- }
292
- if (value.length == 0) {
293
- errors.push({
294
- action: 'drop',
295
- message: 'parse: invalid declaration',
296
- location: { src, ...position }
297
- });
298
- return null;
299
- }
300
- const node = {
301
- typ: 'Declaration',
302
- // @ts-ignore
303
- nam: renderToken(name.shift(), { removeComments: true }),
323
+ if (name == null) {
324
+ name = tokens;
325
+ }
326
+ const position = map.get(name[0]);
327
+ if (name.length > 0) {
328
+ for (let i = 1; i < name.length; i++) {
329
+ if (name[i].typ != EnumToken.WhitespaceTokenType && name[i].typ != EnumToken.CommentTokenType) {
330
+ errors.push({
331
+ action: 'drop',
332
+ message: 'doParse: invalid declaration',
333
+ location: { src, ...position }
334
+ });
335
+ return null;
336
+ }
337
+ }
338
+ }
339
+ if (value == null || value.length == 0) {
340
+ errors.push({
341
+ action: 'drop',
342
+ message: 'doParse: invalid declaration',
343
+ location: { src, ...position }
344
+ });
345
+ return null;
346
+ }
347
+ const node = {
348
+ typ: EnumToken.DeclarationNodeType,
349
+ // @ts-ignore
350
+ nam: renderToken(name.shift(), { removeComments: true }),
351
+ // @ts-ignore
352
+ val: value
353
+ };
354
+ while (node.val[0]?.typ == EnumToken.WhitespaceTokenType) {
355
+ node.val.shift();
356
+ }
357
+ if (node.val.length == 0) {
358
+ errors.push({
359
+ action: 'drop',
360
+ message: 'doParse: invalid declaration',
361
+ location: { src, ...position }
362
+ });
363
+ return null;
364
+ }
304
365
  // @ts-ignore
305
- val: value
306
- };
307
- while (node.val[0]?.typ == 'Whitespace') {
308
- node.val.shift();
309
- }
310
- if (node.val.length == 0) {
311
- errors.push({
312
- action: 'drop',
313
- message: 'parse: invalid declaration',
314
- location: { src, ...position }
315
- });
366
+ context.chi.push(node);
316
367
  return null;
317
368
  }
318
- // @ts-ignore
319
- context.chi.push(node);
320
- return null;
321
369
  }
322
370
  }
323
- }
324
- function mapToken(token) {
325
- const node = getTokenType(token.token, token.hint);
326
- map.set(node, token.position);
327
- return node;
328
- }
329
- const iter = tokenize(iterator);
330
- let item;
331
- while (item = iter.next().value) {
332
- bytesIn = item.bytesIn;
333
- // parse error
334
- if (item.hint != null && item.hint.startsWith('Bad-')) {
335
- // bad token
336
- continue;
371
+ function mapToken(token) {
372
+ const node = getTokenType(token.token, token.hint);
373
+ map.set(node, token.position);
374
+ return node;
337
375
  }
338
- tokens.push(item);
339
- if (item.token == ';' || item.token == '{') {
340
- let node = await parseNode(tokens);
341
- if (node != null) {
342
- stack.push(node);
343
- // @ts-ignore
344
- context = node;
376
+ const iter = tokenize(iterator);
377
+ let item;
378
+ while (item = iter.next().value) {
379
+ bytesIn = item.bytesIn;
380
+ // doParse error
381
+ if (item.hint != null && BadTokensTypes.includes(item.hint)) {
382
+ // bad token
383
+ continue;
345
384
  }
346
- else if (item.token == '{') {
347
- // node == null
348
- // consume and throw away until the closing '}' or EOF
349
- let inBlock = 1;
350
- do {
351
- item = iter.next().value;
352
- if (item == null) {
353
- break;
354
- }
355
- if (item.token == '{') {
356
- inBlock++;
357
- }
358
- else if (item.token == '}') {
359
- inBlock--;
360
- }
361
- } while (inBlock != 0);
385
+ tokens.push(item);
386
+ if (item.token == ';' || item.token == '{') {
387
+ let node = await parseNode(tokens);
388
+ if (node != null) {
389
+ stack.push(node);
390
+ // @ts-ignore
391
+ context = node;
392
+ }
393
+ else if (item.token == '{') {
394
+ // node == null
395
+ // consume and throw away until the closing '}' or EOF
396
+ let inBlock = 1;
397
+ do {
398
+ item = iter.next().value;
399
+ if (item == null) {
400
+ break;
401
+ }
402
+ if (item.token == '{') {
403
+ inBlock++;
404
+ }
405
+ else if (item.token == '}') {
406
+ inBlock--;
407
+ }
408
+ } while (inBlock != 0);
409
+ }
410
+ tokens = [];
411
+ map = new Map;
412
+ }
413
+ else if (item.token == '}') {
414
+ await parseNode(tokens);
415
+ const previousNode = stack.pop();
416
+ // @ts-ignore
417
+ context = stack[stack.length - 1] || ast;
418
+ // @ts-ignore
419
+ if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
420
+ context.chi.pop();
421
+ }
422
+ tokens = [];
423
+ map = new Map;
362
424
  }
363
- tokens = [];
364
- map = new Map;
365
425
  }
366
- else if (item.token == '}') {
426
+ if (tokens.length > 0) {
367
427
  await parseNode(tokens);
428
+ }
429
+ while (stack.length > 0 && context != ast) {
368
430
  const previousNode = stack.pop();
369
431
  // @ts-ignore
370
432
  context = stack[stack.length - 1] || ast;
371
433
  // @ts-ignore
372
434
  if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
373
435
  context.chi.pop();
436
+ continue;
374
437
  }
375
- tokens = [];
376
- map = new Map;
438
+ break;
377
439
  }
378
- }
379
- if (tokens.length > 0) {
380
- await parseNode(tokens);
381
- }
382
- while (stack.length > 0 && context != ast) {
383
- const previousNode = stack.pop();
384
- // @ts-ignore
385
- context = stack[stack.length - 1] || ast;
386
- // @ts-ignore
387
- if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
388
- context.chi.pop();
389
- continue;
440
+ const endParseTime = performance.now();
441
+ if (options.expandNestingRules) {
442
+ ast = expand(ast);
390
443
  }
391
- break;
392
- }
393
- const endParseTime = performance.now();
394
- if (options.minify) {
395
- if (ast.chi.length > 0) {
396
- minify(ast, options, true, errors);
444
+ if (options.visitor != null) {
445
+ for (const result of walk(ast)) {
446
+ if (result.node.typ == EnumToken.DeclarationNodeType &&
447
+ // @ts-ignore
448
+ (typeof options.visitor.Declaration == 'function' || options.visitor.Declaration?.[result.node.nam] != null)) {
449
+ const callable = typeof options.visitor.Declaration == 'function' ? options.visitor.Declaration : options.visitor.Declaration[result.node.nam];
450
+ const results = callable(result.node);
451
+ if (results == null || (Array.isArray(results) && results.length == 0)) {
452
+ continue;
453
+ }
454
+ // @ts-ignore
455
+ result.parent.chi.splice(result.parent.chi.indexOf(result.node), 1, ...(Array.isArray(results) ? results : [results]));
456
+ }
457
+ else if (options.visitor.Rule != null && result.node.typ == EnumToken.RuleNodeType) {
458
+ const results = options.visitor.Rule(result.node);
459
+ if (results == null || (Array.isArray(results) && results.length == 0)) {
460
+ continue;
461
+ }
462
+ // @ts-ignore
463
+ result.parent.chi.splice(result.parent.chi.indexOf(result.node), 1, ...(Array.isArray(results) ? results : [results]));
464
+ }
465
+ else if (options.visitor.AtRule != null &&
466
+ result.node.typ == EnumToken.AtRuleNodeType &&
467
+ // @ts-ignore
468
+ (typeof options.visitor.AtRule == 'function' || options.visitor.AtRule?.[result.node.nam] != null)) {
469
+ const callable = typeof options.visitor.AtRule == 'function' ? options.visitor.AtRule : options.visitor.AtRule[result.node.nam];
470
+ const results = callable(result.node);
471
+ if (results == null || (Array.isArray(results) && results.length == 0)) {
472
+ continue;
473
+ }
474
+ // @ts-ignore
475
+ result.parent.chi.splice(result.parent.chi.indexOf(result.node), 1, ...(Array.isArray(results) ? results : [results]));
476
+ }
477
+ }
397
478
  }
398
- }
399
- const endTime = performance.now();
400
- return {
401
- ast,
402
- errors,
403
- stats: {
404
- bytesIn,
405
- parse: `${(endParseTime - startTime).toFixed(2)}ms`,
406
- minify: `${(endTime - endParseTime).toFixed(2)}ms`,
407
- total: `${(endTime - startTime).toFixed(2)}ms`
479
+ if (options.minify) {
480
+ if (ast.chi.length > 0) {
481
+ minify(ast, options, true, errors, false);
482
+ }
408
483
  }
409
- };
484
+ const endTime = performance.now();
485
+ if (options.signal != null) {
486
+ options.signal.removeEventListener('abort', reject);
487
+ }
488
+ resolve({
489
+ ast,
490
+ errors,
491
+ stats: {
492
+ bytesIn,
493
+ parse: `${(endParseTime - startTime).toFixed(2)}ms`,
494
+ minify: `${(endTime - endParseTime).toFixed(2)}ms`,
495
+ total: `${(endTime - startTime).toFixed(2)}ms`
496
+ }
497
+ });
498
+ });
410
499
  }
411
500
  function parseString(src, options = { location: false }) {
412
501
  return parseTokens([...tokenize(src)].map(t => {
@@ -423,81 +512,111 @@ function getTokenType(val, hint) {
423
512
  }
424
513
  if (hint != null) {
425
514
  return ([
426
- 'Whitespace', 'Semi-colon', 'Colon', 'Block-start',
427
- 'Block-start', 'Attr-start', 'Attr-end', 'Start-parens', 'End-parens',
428
- 'Comma', 'Gt', 'Lt', 'Gte', 'Lte', 'EOF'
515
+ EnumToken.WhitespaceTokenType, EnumToken.SemiColonTokenType, EnumToken.ColonTokenType, EnumToken.BlockStartTokenType,
516
+ EnumToken.BlockStartTokenType, EnumToken.AttrStartTokenType, EnumToken.AttrEndTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType,
517
+ EnumToken.CommaTokenType, EnumToken.GtTokenType, EnumToken.LtTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.CommaTokenType,
518
+ EnumToken.StartMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType, EnumToken.DashMatchTokenType, EnumToken.ContainMatchTokenType,
519
+ EnumToken.EOFTokenType
429
520
  ].includes(hint) ? { typ: hint } : { typ: hint, val });
430
521
  }
431
522
  if (val == ' ') {
432
- return { typ: 'Whitespace' };
523
+ return { typ: EnumToken.WhitespaceTokenType };
433
524
  }
434
525
  if (val == ';') {
435
- return { typ: 'Semi-colon' };
526
+ return { typ: EnumToken.SemiColonTokenType };
436
527
  }
437
528
  if (val == '{') {
438
- return { typ: 'Block-start' };
529
+ return { typ: EnumToken.BlockStartTokenType };
439
530
  }
440
531
  if (val == '}') {
441
- return { typ: 'Block-end' };
532
+ return { typ: EnumToken.BlockEndTokenType };
442
533
  }
443
534
  if (val == '[') {
444
- return { typ: 'Attr-start' };
535
+ return { typ: EnumToken.AttrStartTokenType };
445
536
  }
446
537
  if (val == ']') {
447
- return { typ: 'Attr-end' };
538
+ return { typ: EnumToken.AttrEndTokenType };
448
539
  }
449
540
  if (val == ':') {
450
- return { typ: 'Colon' };
541
+ return { typ: EnumToken.ColonTokenType };
451
542
  }
452
543
  if (val == ')') {
453
- return { typ: 'End-parens' };
544
+ return { typ: EnumToken.EndParensTokenType };
454
545
  }
455
546
  if (val == '(') {
456
- return { typ: 'Start-parens' };
547
+ return { typ: EnumToken.StartParensTokenType };
457
548
  }
458
549
  if (val == '=') {
459
- return { typ: 'Delim', val };
550
+ return { typ: EnumToken.DelimTokenType, val };
460
551
  }
461
552
  if (val == ';') {
462
- return { typ: 'Semi-colon' };
553
+ return { typ: EnumToken.SemiColonTokenType };
463
554
  }
464
555
  if (val == ',') {
465
- return { typ: 'Comma' };
556
+ return { typ: EnumToken.CommaTokenType };
466
557
  }
467
558
  if (val == '<') {
468
- return { typ: 'Lt' };
559
+ return { typ: EnumToken.LtTokenType };
469
560
  }
470
561
  if (val == '>') {
471
- return { typ: 'Gt' };
562
+ return { typ: EnumToken.GtTokenType };
472
563
  }
473
564
  if (isPseudo(val)) {
474
565
  return val.endsWith('(') ? {
475
- typ: 'Pseudo-class-func',
566
+ typ: EnumToken.PseudoClassFuncTokenType,
476
567
  val: val.slice(0, -1),
477
568
  chi: []
478
569
  }
479
570
  : {
480
- typ: 'Pseudo-class',
571
+ typ: EnumToken.PseudoClassTokenType,
481
572
  val
482
573
  };
483
574
  }
484
575
  if (isAtKeyword(val)) {
485
576
  return {
486
- typ: 'At-rule',
577
+ typ: EnumToken.AtRuleTokenType,
487
578
  val: val.slice(1)
488
579
  };
489
580
  }
490
581
  if (isFunction(val)) {
491
582
  val = val.slice(0, -1);
583
+ if (val == 'url') {
584
+ return {
585
+ typ: EnumToken.UrlFunctionTokenType,
586
+ val,
587
+ chi: []
588
+ };
589
+ }
590
+ if (['linear-gradient', 'radial-gradient', 'repeating-linear-gradient', 'repeating-radial-gradient', 'conic-gradient', 'image', 'image-set', 'element', 'cross-fade'].includes(val)) {
591
+ return {
592
+ typ: EnumToken.ImageFunctionTokenType,
593
+ val,
594
+ chi: []
595
+ };
596
+ }
597
+ if (['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear', 'step-start', 'step-end', 'steps', 'cubic-bezier'].includes(val)) {
598
+ return {
599
+ typ: EnumToken.TimingFunctionTokenType,
600
+ val,
601
+ chi: []
602
+ };
603
+ }
604
+ if (['view', 'scroll'].includes(val)) {
605
+ return {
606
+ typ: EnumToken.TimelineFunctionTokenType,
607
+ val,
608
+ chi: []
609
+ };
610
+ }
492
611
  return {
493
- typ: val == 'url' ? 'UrlFunc' : 'Func',
612
+ typ: EnumToken.FunctionTokenType,
494
613
  val,
495
614
  chi: []
496
615
  };
497
616
  }
498
617
  if (isNumber(val)) {
499
618
  return {
500
- typ: 'Number',
619
+ typ: EnumToken.NumberTokenType,
501
620
  val
502
621
  };
503
622
  }
@@ -506,114 +625,210 @@ function getTokenType(val, hint) {
506
625
  }
507
626
  if (isPercentage(val)) {
508
627
  return {
509
- typ: 'Perc',
628
+ typ: EnumToken.PercentageTokenType,
510
629
  val: val.slice(0, -1)
511
630
  };
512
631
  }
513
632
  const v = val.toLowerCase();
514
633
  if (v == 'currentcolor' || val == 'transparent' || v in COLORS_NAMES) {
515
634
  return {
516
- typ: 'Color',
635
+ typ: EnumToken.ColorTokenType,
517
636
  val,
518
637
  kin: 'lit'
519
638
  };
520
639
  }
521
640
  if (isIdent(val)) {
522
641
  return {
523
- typ: 'Iden',
642
+ typ: val.startsWith('--') ? EnumToken.DashedIdenTokenType : EnumToken.IdenTokenType,
524
643
  val
525
644
  };
526
645
  }
527
646
  if (val.charAt(0) == '#' && isHexColor(val)) {
528
647
  return {
529
- typ: 'Color',
648
+ typ: EnumToken.ColorTokenType,
530
649
  val,
531
650
  kin: 'hex'
532
651
  };
533
652
  }
534
653
  if (val.charAt(0) == '#' && isHash(val)) {
535
654
  return {
536
- typ: 'Hash',
655
+ typ: EnumToken.HashTokenType,
537
656
  val
538
657
  };
539
658
  }
540
659
  if ('"\''.includes(val.charAt(0))) {
541
660
  return {
542
- typ: 'Unclosed-string',
661
+ typ: EnumToken.UnclosedStringTokenType,
543
662
  val
544
663
  };
545
664
  }
546
665
  return {
547
- typ: 'Literal',
666
+ typ: EnumToken.LiteralTokenType,
548
667
  val
549
668
  };
550
669
  }
551
670
  function parseTokens(tokens, options = {}) {
552
671
  for (let i = 0; i < tokens.length; i++) {
553
672
  const t = tokens[i];
554
- if (t.typ == 'Whitespace' && ((i == 0 ||
673
+ if (t.typ == EnumToken.WhitespaceTokenType && ((i == 0 ||
555
674
  i + 1 == tokens.length ||
556
- ['Comma', 'Gte', 'Lte'].includes(tokens[i + 1].typ)) ||
675
+ [EnumToken.CommaTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType].includes(tokens[i + 1].typ)) ||
557
676
  (i > 0 &&
558
- // tokens[i + 1]?.typ != 'Literal' ||
677
+ // tokens[i + 1]?.typ != Literal ||
559
678
  // funcLike.includes(tokens[i - 1].typ) &&
560
679
  // !['var', 'calc'].includes((<FunctionToken>tokens[i - 1]).val)))) &&
561
680
  trimWhiteSpace.includes(tokens[i - 1].typ)))) {
562
681
  tokens.splice(i--, 1);
563
682
  continue;
564
683
  }
565
- if (t.typ == 'Colon') {
684
+ if (t.typ == EnumToken.ColonTokenType) {
566
685
  const typ = tokens[i + 1]?.typ;
567
686
  if (typ != null) {
568
- if (typ == 'Func') {
687
+ if (typ == EnumToken.FunctionTokenType) {
569
688
  tokens[i + 1].val = ':' + tokens[i + 1].val;
570
- tokens[i + 1].typ = 'Pseudo-class-func';
689
+ tokens[i + 1].typ = EnumToken.PseudoClassFuncTokenType;
571
690
  }
572
- else if (typ == 'Iden') {
691
+ else if (typ == EnumToken.IdenTokenType) {
692
+ if (tokens[i + 1].val in webkitPseudoAliasMap) {
693
+ tokens[i + 1].val = webkitPseudoAliasMap[tokens[i + 1].val];
694
+ }
573
695
  tokens[i + 1].val = ':' + tokens[i + 1].val;
574
- tokens[i + 1].typ = 'Pseudo-class';
696
+ tokens[i + 1].typ = EnumToken.PseudoClassTokenType;
575
697
  }
576
- if (typ == 'Func' || typ == 'Iden') {
698
+ if (typ == EnumToken.FunctionTokenType || typ == EnumToken.IdenTokenType) {
577
699
  tokens.splice(i, 1);
578
700
  i--;
579
- continue;
580
701
  }
581
702
  }
703
+ continue;
582
704
  }
583
- if (t.typ == 'Attr-start') {
705
+ if (t.typ == EnumToken.AttrStartTokenType) {
584
706
  let k = i;
585
707
  let inAttr = 1;
586
708
  while (++k < tokens.length) {
587
- if (tokens[k].typ == 'Attr-end') {
709
+ if (tokens[k].typ == EnumToken.AttrEndTokenType) {
588
710
  inAttr--;
589
711
  }
590
- else if (tokens[k].typ == 'Attr-start') {
712
+ else if (tokens[k].typ == EnumToken.AttrStartTokenType) {
591
713
  inAttr++;
592
714
  }
593
715
  if (inAttr == 0) {
594
716
  break;
595
717
  }
596
718
  }
597
- Object.assign(t, { typ: 'Attr', chi: tokens.splice(i + 1, k - i) });
719
+ Object.assign(t, { typ: EnumToken.AttrTokenType, chi: tokens.splice(i + 1, k - i) });
598
720
  // @ts-ignore
599
- if (t.chi.at(-1).typ == 'Attr-end') {
721
+ if (t.chi.at(-1).typ == EnumToken.AttrEndTokenType) {
600
722
  // @ts-ignore
601
723
  t.chi.pop();
724
+ }
725
+ // @ts-ignore
726
+ if (t.chi.length > 1) {
727
+ /*(<AttrToken>t).chi =*/
602
728
  // @ts-ignore
603
- if (t.chi.length > 1) {
604
- /*(<AttrToken>t).chi =*/
729
+ parseTokens(t.chi, t.typ);
730
+ }
731
+ // @ts-ignore
732
+ // t.chi.forEach(val => {
733
+ // if (val.typ == EnumToken.StringTokenType) {
734
+ // const slice = val.val.slice(1, -1);
735
+ // if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) {
736
+ // Object.assign(val, {typ: EnumToken.IdenTokenType, val: slice});
737
+ // }
738
+ // }
739
+ // });
740
+ let m = t.chi.length;
741
+ let val;
742
+ for (m = 0; m < t.chi.length; m++) {
743
+ val = t.chi[m];
744
+ if (val.typ == EnumToken.StringTokenType) {
745
+ const slice = val.val.slice(1, -1);
746
+ if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) {
747
+ Object.assign(val, { typ: EnumToken.IdenTokenType, val: slice });
748
+ }
749
+ }
750
+ else if (val.typ == EnumToken.LiteralTokenType && val.val == '|') {
751
+ let upper = m;
752
+ let lower = m;
753
+ while (++upper < t.chi.length) {
754
+ if (t.chi[upper].typ == EnumToken.CommentTokenType) {
755
+ continue;
756
+ }
757
+ break;
758
+ }
759
+ while (lower-- > 0) {
760
+ if (t.chi[lower].typ == EnumToken.CommentTokenType) {
761
+ continue;
762
+ }
763
+ break;
764
+ }
605
765
  // @ts-ignore
606
- parseTokens(t.chi, t.typ);
766
+ t.chi[m] = {
767
+ typ: EnumToken.NameSpaceAttributeTokenType,
768
+ l: t.chi[lower],
769
+ r: t.chi[upper]
770
+ };
771
+ t.chi.splice(upper, 1);
772
+ if (lower >= 0) {
773
+ t.chi.splice(lower, 1);
774
+ m--;
775
+ }
607
776
  }
608
- // @ts-ignore
609
- t.chi.forEach(val => {
610
- if (val.typ == 'String') {
777
+ else if ([
778
+ EnumToken.DashMatchTokenType, EnumToken.StartMatchTokenType, EnumToken.ContainMatchTokenType, EnumToken.EndMatchTokenType, EnumToken.IncludeMatchTokenType
779
+ ].includes(t.chi[m].typ)) {
780
+ let upper = m;
781
+ let lower = m;
782
+ while (++upper < t.chi.length) {
783
+ if (t.chi[upper].typ == EnumToken.CommentTokenType) {
784
+ continue;
785
+ }
786
+ break;
787
+ }
788
+ while (lower-- > 0) {
789
+ if (t.chi[lower].typ == EnumToken.CommentTokenType) {
790
+ continue;
791
+ }
792
+ break;
793
+ }
794
+ val = t.chi[lower];
795
+ if (val.typ == EnumToken.StringTokenType) {
611
796
  const slice = val.val.slice(1, -1);
612
797
  if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) {
613
- Object.assign(val, { typ: 'Iden', val: slice });
798
+ Object.assign(val, { typ: EnumToken.IdenTokenType, val: slice });
614
799
  }
615
800
  }
616
- });
801
+ val = t.chi[upper];
802
+ if (val.typ == EnumToken.StringTokenType) {
803
+ const slice = val.val.slice(1, -1);
804
+ if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) {
805
+ Object.assign(val, { typ: EnumToken.IdenTokenType, val: slice });
806
+ }
807
+ }
808
+ t.chi[m] = {
809
+ typ: EnumToken.MatchExpressionTokenType,
810
+ op: t.chi[m].typ,
811
+ l: t.chi[lower],
812
+ r: t.chi[upper]
813
+ };
814
+ t.chi.splice(upper, 1);
815
+ t.chi.splice(lower, 1);
816
+ upper = m;
817
+ m--;
818
+ while (upper < t.chi.length && t.chi[upper].typ == EnumToken.WhitespaceTokenType) {
819
+ upper++;
820
+ }
821
+ if (upper < t.chi.length &&
822
+ t.chi[upper].typ == EnumToken.Iden &&
823
+ ['i', 's'].includes(t.chi[upper].val.toLowerCase())) {
824
+ t.chi[m].attr = t.chi[upper].val;
825
+ t.chi.splice(upper, 1);
826
+ }
827
+ }
828
+ }
829
+ m = t.chi.length;
830
+ while (t.chi.at(-1)?.typ == EnumToken.WhitespaceTokenType) {
831
+ t.chi.pop();
617
832
  }
618
833
  continue;
619
834
  }
@@ -621,18 +836,18 @@ function parseTokens(tokens, options = {}) {
621
836
  let parens = 1;
622
837
  let k = i;
623
838
  while (++k < tokens.length) {
624
- if (tokens[k].typ == 'Colon') {
839
+ if (tokens[k].typ == EnumToken.ColonTokenType) {
625
840
  const typ = tokens[k + 1]?.typ;
626
841
  if (typ != null) {
627
- if (typ == 'Iden') {
628
- tokens[k + 1].typ = 'Pseudo-class';
842
+ if (typ == EnumToken.IdenTokenType) {
843
+ tokens[k + 1].typ = EnumToken.PseudoClassTokenType;
629
844
  tokens[k + 1].val = ':' + tokens[k + 1].val;
630
845
  }
631
- else if (typ == 'Func') {
632
- tokens[k + 1].typ = 'Pseudo-class-func';
846
+ else if (typ == EnumToken.FunctionTokenType) {
847
+ tokens[k + 1].typ = EnumToken.PseudoClassFuncTokenType;
633
848
  tokens[k + 1].val = ':' + tokens[k + 1].val;
634
849
  }
635
- if (typ == 'Func' || typ == 'Iden') {
850
+ if (typ == EnumToken.FunctionTokenType || typ == EnumToken.IdenTokenType) {
636
851
  tokens.splice(k, 1);
637
852
  k--;
638
853
  continue;
@@ -642,7 +857,7 @@ function parseTokens(tokens, options = {}) {
642
857
  if (funcLike.includes(tokens[k].typ)) {
643
858
  parens++;
644
859
  }
645
- else if (tokens[k].typ == 'End-parens') {
860
+ else if (tokens[k].typ == EnumToken.EndParensTokenType) {
646
861
  parens--;
647
862
  }
648
863
  if (parens == 0) {
@@ -652,29 +867,55 @@ function parseTokens(tokens, options = {}) {
652
867
  // @ts-ignore
653
868
  t.chi = tokens.splice(i + 1, k - i);
654
869
  // @ts-ignore
655
- if (t.chi.at(-1)?.typ == 'End-parens') {
870
+ if (t.chi.at(-1)?.typ == EnumToken.EndParensTokenType) {
656
871
  // @ts-ignore
657
872
  t.chi.pop();
658
873
  }
874
+ if (t.typ == EnumToken.FunctionTokenType && t.val == 'calc') {
875
+ for (const { value, parent } of walkValues(t.chi)) {
876
+ if (value.typ == EnumToken.WhitespaceTokenType) {
877
+ const p = (parent ?? t);
878
+ for (let i = 0; i < (p).chi.length; i++) {
879
+ // @ts-ignore
880
+ if (p.chi[i] == value) {
881
+ // @ts-ignore
882
+ (p).chi.splice(i, 1);
883
+ i--;
884
+ break;
885
+ }
886
+ }
887
+ }
888
+ else if (value.typ == EnumToken.LiteralTokenType && ['+', '-', '/', '*'].includes(value.val)) {
889
+ // @ts-ignore
890
+ value.typ = value.val == '+' ? EnumToken.Add : (value.val == '-' ? EnumToken.Sub : (value.val == '*' ? EnumToken.Mul : EnumToken.Div));
891
+ // @ts-ignore
892
+ delete value.val;
893
+ }
894
+ }
895
+ }
896
+ else if (t.typ == EnumToken.StartParensTokenType) {
897
+ // @ts-ignore
898
+ t.typ = EnumToken.ParensTokenType;
899
+ }
659
900
  // @ts-ignore
660
- if (options.parseColor && t.typ == 'Func' && isColor(t)) {
901
+ if (options.parseColor && t.typ == EnumToken.FunctionTokenType && isColor(t)) {
661
902
  // if (isColor) {
662
903
  // @ts-ignore
663
- t.typ = 'Color';
904
+ t.typ = EnumToken.ColorTokenType;
664
905
  // @ts-ignore
665
906
  t.kin = t.val;
666
907
  // @ts-ignore
667
908
  let m = t.chi.length;
668
909
  while (m-- > 0) {
669
910
  // @ts-ignore
670
- if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) {
911
+ if ([EnumToken.LiteralTokenType].concat(trimWhiteSpace).includes(t.chi[m].typ)) {
671
912
  // @ts-ignore
672
- if (t.chi[m + 1]?.typ == 'Whitespace') {
913
+ if (t.chi[m + 1]?.typ == EnumToken.WhitespaceTokenType) {
673
914
  // @ts-ignore
674
915
  t.chi.splice(m + 1, 1);
675
916
  }
676
917
  // @ts-ignore
677
- if (t.chi[m - 1]?.typ == 'Whitespace') {
918
+ if (t.chi[m - 1]?.typ == EnumToken.WhitespaceTokenType) {
678
919
  // @ts-ignore
679
920
  t.chi.splice(m - 1, 1);
680
921
  m--;
@@ -682,22 +923,21 @@ function parseTokens(tokens, options = {}) {
682
923
  }
683
924
  }
684
925
  continue;
685
- // }
686
926
  }
687
- if (t.typ == 'UrlFunc') {
927
+ if (t.typ == EnumToken.UrlFunctionTokenType) {
688
928
  // @ts-ignore
689
- if (t.chi[0]?.typ == 'String') {
929
+ if (t.chi[0]?.typ == EnumToken.StringTokenType) {
690
930
  // @ts-ignore
691
931
  const value = t.chi[0].val.slice(1, -1);
692
932
  // @ts-ignore
693
933
  if (t.chi[0].val.slice(1, 5) != 'data:' && urlTokenMatcher.test(value)) {
694
934
  // @ts-ignore
695
- t.chi[0].typ = 'Url-token';
935
+ t.chi[0].typ = EnumToken.UrlTokenTokenType;
696
936
  // @ts-ignore
697
937
  t.chi[0].val = options.src !== '' && options.resolveUrls ? options.resolve(value, options.src).absolute : value;
698
938
  }
699
939
  }
700
- if (t.chi[0]?.typ == 'Url-token') {
940
+ if (t.chi[0]?.typ == EnumToken.UrlTokenTokenType) {
701
941
  if (options.src !== '' && options.resolveUrls) {
702
942
  // @ts-ignore
703
943
  t.chi[0].val = options.resolve(t.chi[0].val, options.src, options.cwd).relative;
@@ -708,13 +948,13 @@ function parseTokens(tokens, options = {}) {
708
948
  if (t.chi.length > 0) {
709
949
  // @ts-ignore
710
950
  parseTokens(t.chi, options);
711
- if (t.typ == 'Pseudo-class-func' && t.val == ':is' && options.minify) {
951
+ if (t.typ == EnumToken.PseudoClassFuncTokenType && t.val == ':is' && options.minify) {
712
952
  //
713
- const count = t.chi.filter(t => t.typ != 'Comment').length;
953
+ const count = t.chi.filter(t => t.typ != EnumToken.CommentTokenType).length;
714
954
  if (count == 1 ||
715
955
  (i == 0 &&
716
- (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1)) ||
717
- (tokens[i - 1]?.typ == 'Comma' && (tokens[i + 1]?.typ == 'Comma' || tokens.length == i + 1))) {
956
+ (tokens[i + 1]?.typ == EnumToken.CommaTokenType || tokens.length == i + 1)) ||
957
+ (tokens[i - 1]?.typ == EnumToken.CommaTokenType && (tokens[i + 1]?.typ == EnumToken.CommaTokenType || tokens.length == i + 1))) {
718
958
  tokens.splice(i, 1, ...t.chi);
719
959
  i = Math.max(0, i - t.chi.length);
720
960
  }
@@ -723,22 +963,22 @@ function parseTokens(tokens, options = {}) {
723
963
  continue;
724
964
  }
725
965
  if (options.parseColor) {
726
- if (t.typ == 'Iden') {
966
+ if (t.typ == EnumToken.IdenTokenType) {
727
967
  // named color
728
968
  const value = t.val.toLowerCase();
729
969
  if (value in COLORS_NAMES) {
730
970
  Object.assign(t, {
731
- typ: 'Color',
971
+ typ: EnumToken.ColorTokenType,
732
972
  val: COLORS_NAMES[value].length < value.length ? COLORS_NAMES[value] : value,
733
973
  kin: 'hex'
734
974
  });
735
975
  }
736
976
  continue;
737
977
  }
738
- if (t.typ == 'Hash' && isHexColor(t.val)) {
978
+ if (t.typ == EnumToken.HashTokenType && isHexColor(t.val)) {
739
979
  // hex color
740
980
  // @ts-ignore
741
- t.typ = 'Color';
981
+ t.typ = EnumToken.ColorTokenType;
742
982
  // @ts-ignore
743
983
  t.kin = 'hex';
744
984
  }
@@ -747,4 +987,4 @@ function parseTokens(tokens, options = {}) {
747
987
  return tokens;
748
988
  }
749
989
 
750
- export { parse, parseString, urlTokenMatcher };
990
+ export { doParse, parseString, parseTokens, urlTokenMatcher };