@tbela99/css-parser 0.1.0 → 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,258 +1,243 @@
1
1
  import { isPseudo, isAtKeyword, isFunction, isNumber, isDimension, parseDimension, isPercentage, isIdent, isHexColor, isHash, isIdentStart, isColor } from './utils/syntax.js';
2
2
  import { EnumToken } from '../ast/types.js';
3
3
  import { minify, combinators } from '../ast/minify.js';
4
- import { walkValues } from '../ast/walk.js';
4
+ import { walkValues, walk } from '../ast/walk.js';
5
5
  import { expand } from '../ast/expand.js';
6
6
  import { renderToken } from '../renderer/render.js';
7
7
  import { COLORS_NAMES } from '../renderer/utils/color.js';
8
8
  import { tokenize } from './tokenize.js';
9
9
 
10
10
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
11
- const trimWhiteSpace = [EnumToken.GtTokenType, EnumToken.GteTokenType, EnumToken.LtTokenType, EnumToken.LteTokenType];
12
- const funcLike = [EnumToken.ParensTokenType, EnumToken.StartParensTokenType, EnumToken.FunctionTokenType, EnumToken.UrlFunctionTokenType, EnumToken.PseudoClassFuncTokenType];
13
- const BadTokensTypes = [EnumToken.BadCommentTokenType,
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,
14
24
  EnumToken.BadCdoTokenType,
15
25
  EnumToken.BadUrlTokenType,
16
- EnumToken.BadStringTokenType];
26
+ EnumToken.BadStringTokenType
27
+ ];
28
+ const webkitPseudoAliasMap = {
29
+ '-webkit-autofill': 'autofill'
30
+ };
17
31
  async function doParse(iterator, options = {}) {
18
- options = {
19
- src: '',
20
- sourcemap: false,
21
- minify: true,
22
- nestingRules: false,
23
- resolveImport: false,
24
- resolveUrls: false,
25
- removeCharset: false,
26
- removeEmpty: true,
27
- removeDuplicateDeclarations: true,
28
- computeShorthand: true,
29
- computeCalcExpression: true,
30
- inlineCssVariables: false,
31
- ...options
32
- };
33
- if (options.expandNestingRules) {
34
- options.nestingRules = false;
35
- }
36
- if (options.resolveImport) {
37
- options.resolveUrls = true;
38
- }
39
- const startTime = performance.now();
40
- const errors = [];
41
- const src = options.src;
42
- const stack = [];
43
- let ast = {
44
- typ: 2 /* NodeType.StyleSheetNodeType */,
45
- chi: []
46
- };
47
- let tokens = [];
48
- let map = new Map;
49
- let bytesIn = 0;
50
- let context = ast;
51
- if (options.sourcemap) {
52
- ast.loc = {
53
- sta: {
54
- ind: 0,
55
- lin: 1,
56
- col: 1
57
- },
58
- src: ''
59
- };
60
- }
61
- async function parseNode(results) {
62
- let tokens = results.map(mapToken);
63
- let i;
64
- let loc;
65
- for (i = 0; i < tokens.length; i++) {
66
- if (tokens[i].typ == EnumToken.CommentTokenType || tokens[i].typ == EnumToken.CDOCOMMTokenType) {
67
- const position = map.get(tokens[i]);
68
- if (tokens[i].typ == EnumToken.CDOCOMMTokenType && context.typ != 2 /* NodeType.StyleSheetNodeType */) {
69
- errors.push({
70
- action: 'drop',
71
- message: `CDOCOMM not allowed here ${JSON.stringify(tokens[i], null, 1)}`,
72
- location: { src, ...position }
73
- });
74
- continue;
75
- }
76
- loc = {
77
- sta: position,
78
- src
79
- };
80
- // @ts-ignore
81
- context.chi.push(tokens[i]);
82
- if (options.sourcemap) {
83
- tokens[i].loc = loc;
84
- }
85
- }
86
- else if (tokens[i].typ != EnumToken.WhitespaceTokenType) {
87
- break;
88
- }
89
- }
90
- tokens = tokens.slice(i);
91
- if (tokens.length == 0) {
92
- return null;
32
+ return new Promise(async (resolve, reject) => {
33
+ if (options.signal != null) {
34
+ options.signal.addEventListener('abort', reject);
93
35
  }
94
- let delim = tokens.at(-1);
95
- if (delim.typ == EnumToken.SemiColonTokenType || delim.typ == EnumToken.BlockStartTokenType || delim.typ == EnumToken.BlockEndTokenType) {
96
- tokens.pop();
97
- }
98
- else {
99
- delim = { typ: EnumToken.SemiColonTokenType };
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;
100
53
  }
101
- // @ts-ignore
102
- while ([EnumToken.WhitespaceTokenType, EnumToken.BadStringTokenType, EnumToken.BadCommentTokenType].includes(tokens.at(-1)?.typ)) {
103
- tokens.pop();
54
+ if (options.resolveImport) {
55
+ options.resolveUrls = true;
104
56
  }
105
- if (tokens.length == 0) {
106
- 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
+ };
107
78
  }
108
- if (tokens[0]?.typ == EnumToken.AtRuleTokenType) {
109
- const atRule = tokens.shift();
110
- const position = map.get(atRule);
111
- if (atRule.val == 'charset') {
112
- if (position.ind > 0) {
113
- errors.push({ action: 'drop', message: 'doParse: invalid @charset', location: { src, ...position } });
114
- return null;
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
+ }
115
103
  }
116
- if (options.removeCharset) {
117
- return null;
104
+ else if (tokens[i].typ != EnumToken.WhitespaceTokenType) {
105
+ break;
118
106
  }
119
107
  }
108
+ tokens = tokens.slice(i);
109
+ if (tokens.length == 0) {
110
+ return null;
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
+ }
120
119
  // @ts-ignore
121
- while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) {
122
- tokens.shift();
120
+ while ([EnumToken.WhitespaceTokenType, EnumToken.BadStringTokenType, EnumToken.BadCommentTokenType].includes(tokens.at(-1)?.typ)) {
121
+ tokens.pop();
123
122
  }
124
- if (atRule.val == 'import') {
125
- // only @charset and @layer are accepted before @import
126
- if (context.chi.length > 0) {
127
- let i = context.chi.length;
128
- while (i--) {
129
- const type = context.chi[i].typ;
130
- if (type == 0 /* NodeType.CommentNodeType */) {
131
- continue;
132
- }
133
- if (type != 3 /* NodeType.AtRuleNodeType */) {
134
- errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
135
- return null;
136
- }
137
- const name = context.chi[i].nam;
138
- if (name != 'charset' && name != 'import' && name != 'layer') {
139
- errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
140
- return null;
141
- }
142
- 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;
143
140
  }
144
141
  }
145
142
  // @ts-ignore
146
- if (tokens[0]?.typ != EnumToken.StringTokenType && tokens[0]?.typ != EnumToken.UrlFunctionTokenType) {
147
- errors.push({ action: 'drop', message: 'doParse: invalid @import', location: { src, ...position } });
148
- return null;
149
- }
150
- // @ts-ignore
151
- if (tokens[0].typ == EnumToken.UrlFunctionTokenType && tokens[1]?.typ != EnumToken.UrlTokenTokenType && tokens[1]?.typ != EnumToken.StringTokenType) {
152
- errors.push({ action: 'drop', message: 'doParse: invalid @import', location: { src, ...position } });
153
- return null;
154
- }
155
- }
156
- if (atRule.val == 'import') {
157
- // @ts-ignore
158
- if (tokens[0].typ == EnumToken.UrlFunctionTokenType && tokens[1].typ == EnumToken.UrlTokenTokenType) {
143
+ while ([EnumToken.WhitespaceTokenType].includes(tokens[0]?.typ)) {
159
144
  tokens.shift();
160
- // @ts-ignore
161
- tokens[0].typ = EnumToken.StringTokenType;
162
- // @ts-ignore
163
- tokens[0].val = `"${tokens[0].val}"`;
164
145
  }
165
- // @ts-ignore
166
- if (tokens[0].typ == EnumToken.StringTokenType) {
167
- if (options.resolveImport) {
168
- const url = tokens[0].val.slice(1, -1);
169
- try {
170
- // @ts-ignore
171
- const root = await options.load(url, options.src).then((src) => {
172
- return doParse(src, Object.assign({}, options, {
173
- minify: false,
174
- // @ts-ignore
175
- src: options.resolve(url, options.src).absolute
176
- }));
177
- });
178
- bytesIn += root.stats.bytesIn;
179
- if (root.ast.chi.length > 0) {
180
- // @todo - filter charset, layer and scope
181
- 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;
182
154
  }
183
- if (root.errors.length > 0) {
184
- errors.push(...root.errors);
155
+ if (type != EnumToken.AtRuleNodeType) {
156
+ errors.push({ action: 'drop', message: 'invalid @import', location: { src, ...position } });
157
+ return null;
185
158
  }
186
- return null;
187
- }
188
- catch (error) {
189
- // @ts-ignore
190
- errors.push({ action: 'ignore', message: 'doParse: ' + 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;
191
165
  }
192
166
  }
193
- }
194
- }
195
- // https://www.w3.org/TR/css-nesting-1/#conditionals
196
- // allowed nesting at-rules
197
- // there must be a top level rule in the stack
198
- const raw = parseTokens(tokens, { minify: options.minify }).reduce((acc, curr) => {
199
- acc.push(renderToken(curr, { removeComments: true }));
200
- return acc;
201
- }, []);
202
- const node = {
203
- typ: 3 /* NodeType.AtRuleNodeType */,
204
- nam: renderToken(atRule, { removeComments: true }),
205
- val: raw.join('')
206
- };
207
- Object.defineProperty(node, 'raw', { enumerable: false, configurable: true, writable: true, value: raw });
208
- if (delim.typ == EnumToken.BlockStartTokenType) {
209
- node.chi = [];
210
- }
211
- loc = {
212
- sta: position,
213
- src
214
- };
215
- if (options.sourcemap) {
216
- node.loc = loc;
217
- }
218
- // @ts-ignore
219
- context.chi.push(node);
220
- return delim.typ == EnumToken.BlockStartTokenType ? node : null;
221
- }
222
- else {
223
- // rule
224
- if (delim.typ == EnumToken.BlockStartTokenType) {
225
- const position = map.get(tokens[0]);
226
- const uniq = new Map;
227
- parseTokens(tokens, { minify: true }).reduce((acc, curr, index, array) => {
228
- if (curr.typ == EnumToken.WhitespaceTokenType) {
229
- if (trimWhiteSpace.includes(array[index - 1]?.typ) ||
230
- trimWhiteSpace.includes(array[index + 1]?.typ) ||
231
- combinators.includes(array[index - 1]?.val) ||
232
- combinators.includes(array[index + 1]?.val)) {
233
- return acc;
234
- }
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;
235
175
  }
236
- let t = renderToken(curr, { minify: false });
237
- if (t == ',') {
238
- 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;
239
184
  }
240
- else {
241
- 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}"`;
242
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 }));
243
230
  return acc;
244
- }, [[]]).reduce((acc, curr) => {
245
- acc.set(curr.join(''), curr);
246
- return acc;
247
- }, uniq);
231
+ }, []);
248
232
  const node = {
249
- typ: 4 /* NodeType.RuleNodeType */,
250
- // @ts-ignore
251
- sel: [...uniq.keys()].join(','),
252
- chi: []
233
+ typ: EnumToken.AtRuleNodeType,
234
+ nam: renderToken(atRule, { removeComments: true }),
235
+ val: raw.join('')
253
236
  };
254
- let raw = [...uniq.values()];
255
237
  Object.defineProperty(node, 'raw', { enumerable: false, configurable: true, writable: true, value: raw });
238
+ if (delim.typ == EnumToken.BlockStartTokenType) {
239
+ node.chi = [];
240
+ }
256
241
  loc = {
257
242
  sta: position,
258
243
  src
@@ -262,166 +247,255 @@ async function doParse(iterator, options = {}) {
262
247
  }
263
248
  // @ts-ignore
264
249
  context.chi.push(node);
265
- return node;
250
+ return delim.typ == EnumToken.BlockStartTokenType ? node : null;
266
251
  }
267
252
  else {
268
- // declaration
269
- // @ts-ignore
270
- let name = null;
271
- // @ts-ignore
272
- let value = null;
273
- for (let i = 0; i < tokens.length; i++) {
274
- if (tokens[i].typ == EnumToken.CommentTokenType) {
275
- continue;
276
- }
277
- if (tokens[i].typ == EnumToken.ColonTokenType) {
278
- name = tokens.slice(0, i);
279
- value = parseTokens(tokens.slice(i + 1), {
280
- parseColor: true,
281
- src: options.src,
282
- resolveUrls: options.resolveUrls,
283
- resolve: options.resolve,
284
- cwd: options.cwd
285
- });
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;
286
297
  }
298
+ // @ts-ignore
299
+ context.chi.push(node);
300
+ return node;
287
301
  }
288
- if (name == null) {
289
- name = tokens;
290
- }
291
- const position = map.get(name[0]);
292
- if (name.length > 0) {
293
- for (let i = 1; i < name.length; i++) {
294
- if (name[i].typ != EnumToken.WhitespaceTokenType && name[i].typ != EnumToken.CommentTokenType) {
295
- errors.push({
296
- action: 'drop',
297
- message: 'doParse: invalid declaration',
298
- 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
299
320
  });
300
- return null;
301
321
  }
302
322
  }
303
- }
304
- if (value == null || value.length == 0) {
305
- errors.push({
306
- action: 'drop',
307
- message: 'doParse: invalid declaration',
308
- location: { src, ...position }
309
- });
310
- return null;
311
- }
312
- const node = {
313
- typ: 5 /* NodeType.DeclarationNodeType */,
314
- // @ts-ignore
315
- 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
+ }
316
365
  // @ts-ignore
317
- val: value
318
- };
319
- while (node.val[0]?.typ == EnumToken.WhitespaceTokenType) {
320
- node.val.shift();
321
- }
322
- if (node.val.length == 0) {
323
- errors.push({
324
- action: 'drop',
325
- message: 'doParse: invalid declaration',
326
- location: { src, ...position }
327
- });
366
+ context.chi.push(node);
328
367
  return null;
329
368
  }
330
- // @ts-ignore
331
- context.chi.push(node);
332
- return null;
333
369
  }
334
370
  }
335
- }
336
- function mapToken(token) {
337
- const node = getTokenType(token.token, token.hint);
338
- map.set(node, token.position);
339
- return node;
340
- }
341
- const iter = tokenize(iterator);
342
- let item;
343
- while (item = iter.next().value) {
344
- bytesIn = item.bytesIn;
345
- // doParse error
346
- if (item.hint != null && BadTokensTypes.includes(item.hint)) {
347
- // bad token
348
- continue;
371
+ function mapToken(token) {
372
+ const node = getTokenType(token.token, token.hint);
373
+ map.set(node, token.position);
374
+ return node;
349
375
  }
350
- tokens.push(item);
351
- if (item.token == ';' || item.token == '{') {
352
- let node = await parseNode(tokens);
353
- if (node != null) {
354
- stack.push(node);
355
- // @ts-ignore
356
- 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;
357
384
  }
358
- else if (item.token == '{') {
359
- // node == null
360
- // consume and throw away until the closing '}' or EOF
361
- let inBlock = 1;
362
- do {
363
- item = iter.next().value;
364
- if (item == null) {
365
- break;
366
- }
367
- if (item.token == '{') {
368
- inBlock++;
369
- }
370
- else if (item.token == '}') {
371
- inBlock--;
372
- }
373
- } 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;
374
424
  }
375
- tokens = [];
376
- map = new Map;
377
425
  }
378
- else if (item.token == '}') {
426
+ if (tokens.length > 0) {
379
427
  await parseNode(tokens);
428
+ }
429
+ while (stack.length > 0 && context != ast) {
380
430
  const previousNode = stack.pop();
381
431
  // @ts-ignore
382
432
  context = stack[stack.length - 1] || ast;
383
433
  // @ts-ignore
384
434
  if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
385
435
  context.chi.pop();
436
+ continue;
386
437
  }
387
- tokens = [];
388
- map = new Map;
438
+ break;
389
439
  }
390
- }
391
- if (tokens.length > 0) {
392
- await parseNode(tokens);
393
- }
394
- while (stack.length > 0 && context != ast) {
395
- const previousNode = stack.pop();
396
- // @ts-ignore
397
- context = stack[stack.length - 1] || ast;
398
- // @ts-ignore
399
- if (options.removeEmpty && previousNode != null && previousNode.chi.length == 0 && context.chi[context.chi.length - 1] == previousNode) {
400
- context.chi.pop();
401
- continue;
440
+ const endParseTime = performance.now();
441
+ if (options.expandNestingRules) {
442
+ ast = expand(ast);
402
443
  }
403
- break;
404
- }
405
- const endParseTime = performance.now();
406
- if (options.expandNestingRules) {
407
- ast = expand(ast);
408
- }
409
- if (options.minify) {
410
- if (ast.chi.length > 0) {
411
- minify(ast, options, true, errors, false);
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
+ }
412
478
  }
413
- }
414
- const endTime = performance.now();
415
- return {
416
- ast,
417
- errors,
418
- stats: {
419
- bytesIn,
420
- parse: `${(endParseTime - startTime).toFixed(2)}ms`,
421
- minify: `${(endTime - endParseTime).toFixed(2)}ms`,
422
- total: `${(endTime - startTime).toFixed(2)}ms`
479
+ if (options.minify) {
480
+ if (ast.chi.length > 0) {
481
+ minify(ast, options, true, errors, false);
482
+ }
423
483
  }
424
- };
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
+ });
425
499
  }
426
500
  function parseString(src, options = { location: false }) {
427
501
  return parseTokens([...tokenize(src)].map(t => {
@@ -440,7 +514,9 @@ function getTokenType(val, hint) {
440
514
  return ([
441
515
  EnumToken.WhitespaceTokenType, EnumToken.SemiColonTokenType, EnumToken.ColonTokenType, EnumToken.BlockStartTokenType,
442
516
  EnumToken.BlockStartTokenType, EnumToken.AttrStartTokenType, EnumToken.AttrEndTokenType, EnumToken.StartParensTokenType, EnumToken.EndParensTokenType,
443
- EnumToken.CommaTokenType, EnumToken.GtTokenType, EnumToken.LtTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.EOFTokenType
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
444
520
  ].includes(hint) ? { typ: hint } : { typ: hint, val });
445
521
  }
446
522
  if (val == ' ') {
@@ -504,8 +580,36 @@ function getTokenType(val, hint) {
504
580
  }
505
581
  if (isFunction(val)) {
506
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
+ }
507
611
  return {
508
- typ: val == 'url' ? EnumToken.UrlFunctionTokenType : EnumToken.FunctionTokenType,
612
+ typ: EnumToken.FunctionTokenType,
509
613
  val,
510
614
  chi: []
511
615
  };
@@ -535,7 +639,7 @@ function getTokenType(val, hint) {
535
639
  }
536
640
  if (isIdent(val)) {
537
641
  return {
538
- typ: EnumToken.IdenTokenType,
642
+ typ: val.startsWith('--') ? EnumToken.DashedIdenTokenType : EnumToken.IdenTokenType,
539
643
  val
540
644
  };
541
645
  }
@@ -568,7 +672,7 @@ function parseTokens(tokens, options = {}) {
568
672
  const t = tokens[i];
569
673
  if (t.typ == EnumToken.WhitespaceTokenType && ((i == 0 ||
570
674
  i + 1 == tokens.length ||
571
- [EnumToken.CommaTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType].includes(tokens[i + 1].typ)) ||
675
+ [EnumToken.CommaTokenType, EnumToken.GteTokenType, EnumToken.LteTokenType, EnumToken.ColumnCombinatorTokenType].includes(tokens[i + 1].typ)) ||
572
676
  (i > 0 &&
573
677
  // tokens[i + 1]?.typ != Literal ||
574
678
  // funcLike.includes(tokens[i - 1].typ) &&
@@ -585,15 +689,18 @@ function parseTokens(tokens, options = {}) {
585
689
  tokens[i + 1].typ = EnumToken.PseudoClassFuncTokenType;
586
690
  }
587
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
+ }
588
695
  tokens[i + 1].val = ':' + tokens[i + 1].val;
589
696
  tokens[i + 1].typ = EnumToken.PseudoClassTokenType;
590
697
  }
591
698
  if (typ == EnumToken.FunctionTokenType || typ == EnumToken.IdenTokenType) {
592
699
  tokens.splice(i, 1);
593
700
  i--;
594
- continue;
595
701
  }
596
702
  }
703
+ continue;
597
704
  }
598
705
  if (t.typ == EnumToken.AttrStartTokenType) {
599
706
  let k = i;
@@ -614,21 +721,114 @@ function parseTokens(tokens, options = {}) {
614
721
  if (t.chi.at(-1).typ == EnumToken.AttrEndTokenType) {
615
722
  // @ts-ignore
616
723
  t.chi.pop();
724
+ }
725
+ // @ts-ignore
726
+ if (t.chi.length > 1) {
727
+ /*(<AttrToken>t).chi =*/
617
728
  // @ts-ignore
618
- if (t.chi.length > 1) {
619
- /*(<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
+ }
620
765
  // @ts-ignore
621
- 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
+ }
622
776
  }
623
- // @ts-ignore
624
- t.chi.forEach(val => {
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];
625
795
  if (val.typ == EnumToken.StringTokenType) {
626
796
  const slice = val.val.slice(1, -1);
627
797
  if ((slice.charAt(0) != '-' || (slice.charAt(0) == '-' && isIdentStart(slice.charCodeAt(1)))) && isIdent(slice)) {
628
798
  Object.assign(val, { typ: EnumToken.IdenTokenType, val: slice });
629
799
  }
630
800
  }
631
- });
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();
632
832
  }
633
833
  continue;
634
834
  }