@tbela99/css-parser 0.0.1-rc3 → 0.0.1-rc4

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.
@@ -5,6 +5,7 @@ import { minify, combinators } from '../ast/minify.js';
5
5
  import { tokenize } from './tokenize.js';
6
6
 
7
7
  const urlTokenMatcher = /^(["']?)[a-zA-Z0-9_/.-][a-zA-Z0-9_/:.#?-]+(\1)$/;
8
+ const trimWhiteSpace = ['Gt', 'Gte', 'Lt', 'Lte'];
8
9
  const funcLike = ['Start-parens', 'Func', 'UrlFunc', 'Pseudo-class-func'];
9
10
  /**
10
11
  *
@@ -51,6 +52,10 @@ async function parse(iterator, opt = {}) {
51
52
  let tokens = results.map(mapToken);
52
53
  let i;
53
54
  let loc;
55
+ // if ((<Token>tokens.at(-1))?.typ == 'EOF') {
56
+ //
57
+ // tokens.pop();
58
+ // }
54
59
  for (i = 0; i < tokens.length; i++) {
55
60
  if (tokens[i].typ == 'Comment') {
56
61
  // @ts-ignore
@@ -169,7 +174,7 @@ async function parse(iterator, opt = {}) {
169
174
  // https://www.w3.org/TR/css-nesting-1/#conditionals
170
175
  // allowed nesting at-rules
171
176
  // there must be a top level rule in the stack
172
- const raw = tokens.reduce((acc, curr) => {
177
+ const raw = parseTokens(tokens, { minify: options.minify }).reduce((acc, curr) => {
173
178
  acc.push(renderToken(curr, { removeComments: true }));
174
179
  return acc;
175
180
  }, []);
@@ -200,8 +205,8 @@ async function parse(iterator, opt = {}) {
200
205
  const uniq = new Map;
201
206
  parseTokens(tokens, { minify: options.minify }).reduce((acc, curr, index, array) => {
202
207
  if (curr.typ == 'Whitespace') {
203
- if (array[index - 1]?.typ == 'Gt' ||
204
- array[index + 1]?.typ == 'Gt' ||
208
+ if (trimWhiteSpace.includes(array[index - 1]?.typ) ||
209
+ trimWhiteSpace.includes(array[index + 1]?.typ) ||
205
210
  combinators.includes(array[index - 1]?.val) ||
206
211
  combinators.includes(array[index + 1]?.val)) {
207
212
  return acc;
@@ -405,7 +410,7 @@ function getTokenType(val, hint) {
405
410
  return ([
406
411
  'Whitespace', 'Semi-colon', 'Colon', 'Block-start',
407
412
  'Block-start', 'Attr-start', 'Attr-end', 'Start-parens', 'End-parens',
408
- 'Comma', 'Gt', 'Lt'
413
+ 'Comma', 'Gt', 'Lt', 'Gte', 'Lte', 'EOF'
409
414
  ].includes(hint) ? { typ: hint } : { typ: hint, val });
410
415
  }
411
416
  if (val == ' ') {
@@ -533,11 +538,12 @@ function parseTokens(tokens, options = {}) {
533
538
  const t = tokens[i];
534
539
  if (t.typ == 'Whitespace' && ((i == 0 ||
535
540
  i + 1 == tokens.length ||
536
- ['Comma'].includes(tokens[i + 1].typ) ||
541
+ ['Comma', 'Gte', 'Lte'].includes(tokens[i + 1].typ)) ||
537
542
  (i > 0 &&
538
- tokens[i + 1]?.typ != 'Literal' &&
539
- funcLike.includes(tokens[i - 1].typ) &&
540
- !['var', 'calc'].includes(tokens[i - 1].val))))) {
543
+ // tokens[i + 1]?.typ != 'Literal' ||
544
+ // funcLike.includes(tokens[i - 1].typ) &&
545
+ // !['var', 'calc'].includes((<FunctionToken>tokens[i - 1]).val)))) &&
546
+ trimWhiteSpace.includes(tokens[i - 1].typ)))) {
541
547
  tokens.splice(i--, 1);
542
548
  continue;
543
549
  }
@@ -654,7 +660,7 @@ function parseTokens(tokens, options = {}) {
654
660
  let m = t.chi.length;
655
661
  while (m-- > 0) {
656
662
  // @ts-ignore
657
- if (t.chi[m].typ == 'Literal') {
663
+ if (['Literal'].concat(trimWhiteSpace).includes(t.chi[m].typ)) {
658
664
  // @ts-ignore
659
665
  if (t.chi[m + 1]?.typ == 'Whitespace') {
660
666
  // @ts-ignore
@@ -36,11 +36,6 @@ function* tokenize(iterator) {
36
36
  }
37
37
  buffer += quoteStr;
38
38
  while (value = peek()) {
39
- // if (ind >= iterator.length) {
40
- //
41
- // yield pushToken(buffer, hasNewLine ? 'Bad-string' : 'Unclosed-string');
42
- // break;
43
- // }
44
39
  if (value == '\\') {
45
40
  const sequence = peek(6);
46
41
  let escapeSequence = '';
@@ -60,9 +55,23 @@ function* tokenize(iterator) {
60
55
  }
61
56
  break;
62
57
  }
58
+ // @ts-ignore
59
+ if (isNewLine(codepoint)) {
60
+ if (i == 1) {
61
+ buffer += value + escapeSequence.slice(0, i);
62
+ next(i + 1);
63
+ continue;
64
+ }
65
+ // else {
66
+ yield pushToken(buffer + value + escapeSequence.slice(0, i), 'Bad-string');
67
+ buffer = '';
68
+ // }
69
+ next(i + 1);
70
+ break;
71
+ }
63
72
  // not hex or new line
64
73
  // @ts-ignore
65
- if (i == 1 && !isNewLine(codepoint)) {
74
+ else if (i == 1) {
66
75
  buffer += value + sequence[i];
67
76
  next(2);
68
77
  continue;
@@ -82,13 +91,6 @@ function* tokenize(iterator) {
82
91
  next(escapeSequence.length + 1);
83
92
  continue;
84
93
  }
85
- // buffer += value;
86
- // if (ind >= iterator.length) {
87
- //
88
- // // drop '\\' at the end
89
- // yield pushToken(buffer);
90
- // break;
91
- // }
92
94
  buffer += next(2);
93
95
  continue;
94
96
  }
@@ -98,20 +100,28 @@ function* tokenize(iterator) {
98
100
  next();
99
101
  // i += value.length;
100
102
  buffer = '';
101
- break;
103
+ return;
102
104
  }
103
105
  if (isNewLine(value.charCodeAt(0))) {
104
106
  hasNewLine = true;
105
107
  }
106
108
  if (hasNewLine && value == ';') {
107
- yield pushToken(buffer, 'Bad-string');
109
+ yield pushToken(buffer + value, 'Bad-string');
108
110
  buffer = '';
111
+ next();
109
112
  break;
110
113
  }
111
114
  buffer += value;
112
- // i += value.length;
113
115
  next();
114
116
  }
117
+ if (hasNewLine) {
118
+ yield pushToken(buffer, 'Bad-string');
119
+ }
120
+ else {
121
+ // EOF - 'Unclosed-string' fixed
122
+ yield pushToken(buffer + quote, 'String');
123
+ }
124
+ buffer = '';
115
125
  }
116
126
  function peek(count = 1) {
117
127
  if (count == 1) {
@@ -225,6 +235,11 @@ function* tokenize(iterator) {
225
235
  yield pushToken(buffer);
226
236
  buffer = '';
227
237
  }
238
+ if (peek() == '=') {
239
+ yield pushToken('', 'Lte');
240
+ next();
241
+ break;
242
+ }
228
243
  buffer += value;
229
244
  value = next();
230
245
  if (ind >= iterator.length) {
@@ -293,7 +308,13 @@ function* tokenize(iterator) {
293
308
  yield pushToken(buffer);
294
309
  buffer = '';
295
310
  }
296
- yield pushToken('', 'Gt');
311
+ if (peek() == '=') {
312
+ yield pushToken('', 'Gte');
313
+ next();
314
+ }
315
+ else {
316
+ yield pushToken('', 'Gt');
317
+ }
297
318
  consumeWhiteSpace();
298
319
  break;
299
320
  case '.':
@@ -335,7 +356,7 @@ function* tokenize(iterator) {
335
356
  break;
336
357
  case '(':
337
358
  if (buffer.length == 0) {
338
- yield pushToken('', 'Start-parens');
359
+ yield pushToken(value);
339
360
  break;
340
361
  }
341
362
  buffer += value;
@@ -449,6 +470,7 @@ function* tokenize(iterator) {
449
470
  if (buffer.length > 0) {
450
471
  yield pushToken(buffer);
451
472
  }
473
+ // yield pushToken('', 'EOF');
452
474
  }
453
475
 
454
476
  export { tokenize };
@@ -159,37 +159,30 @@ function isNumber(name) {
159
159
  return true;
160
160
  }
161
161
  function isDimension(name) {
162
- let index = 0;
163
- while (index++ < name.length) {
164
- if (isDigit(name.charCodeAt(name.length - index))) {
165
- index--;
166
- break;
167
- }
168
- if (index == 3) {
169
- break;
162
+ let index = name.length;
163
+ while (index--) {
164
+ if (isLetter(name.charCodeAt(index))) {
165
+ continue;
170
166
  }
167
+ index++;
168
+ break;
171
169
  }
172
- if (index == 0 || index > 3) {
173
- return false;
174
- }
175
- const number = name.slice(0, -index);
176
- return number.length > 0 && isIdentStart(name.charCodeAt(name.length - index)) && isNumber(number);
170
+ const number = name.slice(0, index);
171
+ return number.length > 0 && isIdentStart(name.charCodeAt(index)) && isNumber(number);
177
172
  }
178
173
  function isPercentage(name) {
179
174
  return name.endsWith('%') && isNumber(name.slice(0, -1));
180
175
  }
181
176
  function parseDimension(name) {
182
- let index = 0;
183
- while (index++ < name.length) {
184
- if (isDigit(name.charCodeAt(name.length - index))) {
185
- index--;
186
- break;
187
- }
188
- if (index == 3) {
189
- break;
177
+ let index = name.length;
178
+ while (index--) {
179
+ if (isLetter(name.charCodeAt(index))) {
180
+ continue;
190
181
  }
182
+ index++;
183
+ break;
191
184
  }
192
- const dimension = { typ: 'Dimension', val: name.slice(0, -index), unit: name.slice(-index) };
185
+ const dimension = { typ: 'Dimension', val: name.slice(0, index), unit: name.slice(index) };
193
186
  if (isAngle(dimension)) {
194
187
  // @ts-ignore
195
188
  dimension.typ = 'Angle';
@@ -1,5 +1,22 @@
1
- import { COLORS_NAMES, rgb2Hex, hsl2Hex, hwb2hex, cmyk2hex, NAMES_COLORS } from './utils/color.js';
1
+ import { getAngle, COLORS_NAMES, rgb2Hex, hsl2Hex, hwb2hex, cmyk2hex, NAMES_COLORS } from './utils/color.js';
2
2
 
3
+ function reduceNumber(val) {
4
+ val = (+val).toString();
5
+ if (val === '0') {
6
+ return '0';
7
+ }
8
+ const chr = val.charAt(0);
9
+ if (chr == '-') {
10
+ const slice = val.slice(0, 2);
11
+ if (slice == '-0') {
12
+ return val.length == 2 ? '0' : '-' + val.slice(2);
13
+ }
14
+ }
15
+ if (chr == '0') {
16
+ return val.slice(1);
17
+ }
18
+ return val;
19
+ }
3
20
  function render(data, opt = {}) {
4
21
  const startTime = performance.now();
5
22
  const options = Object.assign(opt.minify ?? true ? {
@@ -12,17 +29,19 @@ function render(data, opt = {}) {
12
29
  compress: false,
13
30
  removeComments: false,
14
31
  }, { colorConvert: true, preserveLicense: false }, opt);
15
- function reducer(acc, curr, index, original) {
16
- if (curr.typ == 'Comment' && options.removeComments) {
17
- if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
18
- return acc;
32
+ return {
33
+ code: doRender(data, options, function reducer(acc, curr) {
34
+ if (curr.typ == 'Comment' && options.removeComments) {
35
+ if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
36
+ return acc;
37
+ }
38
+ return acc + curr.val;
19
39
  }
20
- }
21
- return acc + renderToken(curr, options);
22
- }
23
- return { code: doRender(data, options, reducer, 0), stats: {
40
+ return acc + renderToken(curr, options, reducer);
41
+ }, 0), stats: {
24
42
  total: `${(performance.now() - startTime).toFixed(2)}ms`
25
- } };
43
+ }
44
+ };
26
45
  }
27
46
  // @ts-ignore
28
47
  function doRender(data, options, reducer, level = 0, indents = []) {
@@ -36,9 +55,9 @@ function doRender(data, options, reducer, level = 0, indents = []) {
36
55
  const indentSub = indents[level + 1];
37
56
  switch (data.typ) {
38
57
  case 'Declaration':
39
- return `${data.nam}:${options.indent}${data.val.reduce((acc, curr) => acc + renderToken(curr), '')}`;
58
+ return `${data.nam}:${options.indent}${data.val.reduce(reducer, '')}`;
40
59
  case 'Comment':
41
- return options.removeComments ? '' : data.val;
60
+ return !options.removeComments || (options.preserveLicense && data.val.startsWith('/*!')) ? data.val : '';
42
61
  case 'StyleSheet':
43
62
  return data.chi.reduce((css, node) => {
44
63
  const str = doRender(node, options, reducer, level, indents);
@@ -59,7 +78,7 @@ function doRender(data, options, reducer, level = 0, indents = []) {
59
78
  let children = data.chi.reduce((css, node) => {
60
79
  let str;
61
80
  if (node.typ == 'Comment') {
62
- str = options.removeComments ? '' : node.val;
81
+ str = options.removeComments && (!options.preserveLicense || !node.val.startsWith('/*!')) ? '' : node.val;
63
82
  }
64
83
  else if (node.typ == 'Declaration') {
65
84
  if (node.val.length == 0) {
@@ -92,7 +111,18 @@ function doRender(data, options, reducer, level = 0, indents = []) {
92
111
  }
93
112
  return '';
94
113
  }
95
- function renderToken(token, options = {}) {
114
+ function renderToken(token, options = {}, reducer) {
115
+ if (reducer == null) {
116
+ reducer = function (acc, curr) {
117
+ if (curr.typ == 'Comment' && options.removeComments) {
118
+ if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
119
+ return acc;
120
+ }
121
+ return acc + curr.val;
122
+ }
123
+ return acc + renderToken(curr, options, reducer);
124
+ };
125
+ }
96
126
  switch (token.typ) {
97
127
  case 'Color':
98
128
  if (options.minify || options.colorConvert) {
@@ -143,22 +173,19 @@ function renderToken(token, options = {}) {
143
173
  case 'UrlFunc':
144
174
  case 'Pseudo-class-func':
145
175
  // @ts-ignore
146
- return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce((acc, curr) => {
147
- if (options.removeComments && curr.typ == 'Comment') {
148
- if (!options.preserveLicense || !curr.val.startsWith('/*!')) {
149
- return acc;
150
- }
151
- }
152
- return acc + renderToken(curr, options);
153
- }, '') + ')';
176
+ return ( /* options.minify && 'Pseudo-class-func' == token.typ && token.val.slice(0, 2) == '::' ? token.val.slice(1) :*/token.val ?? '') + '(' + token.chi.reduce(reducer, '') + ')';
154
177
  case 'Includes':
155
178
  return '~=';
156
179
  case 'Dash-match':
157
180
  return '|=';
158
181
  case 'Lt':
159
182
  return '<';
183
+ case 'Lte':
184
+ return '<=';
160
185
  case 'Gt':
161
186
  return '>';
187
+ case 'Gte':
188
+ return '>=';
162
189
  case 'End-parens':
163
190
  return ')';
164
191
  case 'Attr-start':
@@ -176,37 +203,73 @@ function renderToken(token, options = {}) {
176
203
  case 'Important':
177
204
  return '!important';
178
205
  case 'Attr':
179
- return '[' + token.chi.reduce((acc, curr) => acc + renderToken(curr, options), '') + ']';
206
+ return '[' + token.chi.reduce(reducer, '') + ']';
180
207
  case 'Time':
181
- case 'Frequency':
182
208
  case 'Angle':
183
209
  case 'Length':
184
210
  case 'Dimension':
185
- const val = (+token.val).toString();
211
+ case 'Frequency':
212
+ case 'Resolution':
213
+ let val = reduceNumber(token.val);
214
+ let unit = token.unit;
215
+ if (token.typ == 'Angle') {
216
+ const angle = getAngle(token);
217
+ let v;
218
+ let value = val + unit;
219
+ for (const u of ['turn', 'deg', 'rad', 'grad']) {
220
+ if (token.unit == u) {
221
+ continue;
222
+ }
223
+ switch (u) {
224
+ case 'turn':
225
+ v = reduceNumber(angle);
226
+ if (v.length + 4 < value.length) {
227
+ val = v;
228
+ unit = u;
229
+ value = v + u;
230
+ }
231
+ break;
232
+ case 'deg':
233
+ v = reduceNumber(angle * 360);
234
+ if (v.length + 3 < value.length) {
235
+ val = v;
236
+ unit = u;
237
+ value = v + u;
238
+ }
239
+ break;
240
+ case 'rad':
241
+ v = reduceNumber(angle * (2 * Math.PI));
242
+ if (v.length + 3 < value.length) {
243
+ val = v;
244
+ unit = u;
245
+ value = v + u;
246
+ }
247
+ break;
248
+ case 'grad':
249
+ v = reduceNumber(angle * 400);
250
+ if (v.length + 4 < value.length) {
251
+ val = v;
252
+ unit = u;
253
+ value = v + u;
254
+ }
255
+ break;
256
+ }
257
+ }
258
+ }
186
259
  if (val === '0') {
187
- if (token.typ == 'Time') {
260
+ if (unit == 'Time') {
188
261
  return '0s';
189
262
  }
190
- if (token.typ == 'Frequency') {
263
+ if (unit == 'Frequency') {
191
264
  return '0Hz';
192
265
  }
193
266
  // @ts-ignore
194
- if (token.typ == 'Resolution') {
267
+ if (unit == 'Resolution') {
195
268
  return '0x';
196
269
  }
197
270
  return '0';
198
271
  }
199
- const chr = val.charAt(0);
200
- if (chr == '-') {
201
- const slice = val.slice(0, 2);
202
- if (slice == '-0') {
203
- return (val.length == 2 ? '0' : '-' + val.slice(2)) + token.unit;
204
- }
205
- }
206
- else if (chr == '0') {
207
- return val.slice(1) + token.unit;
208
- }
209
- return val + token.unit;
272
+ return val + unit;
210
273
  case 'Perc':
211
274
  return token.val + '%';
212
275
  case 'Number':
@@ -223,7 +286,7 @@ function renderToken(token, options = {}) {
223
286
  }
224
287
  return num;
225
288
  case 'Comment':
226
- if (options.removeComments) {
289
+ if (options.removeComments && (!options.preserveLicense || !token.val.startsWith('/*!'))) {
227
290
  return '';
228
291
  }
229
292
  case 'Url-token':
@@ -418,7 +418,7 @@ function cmyk2hex(token) {
418
418
  return `#${rgb.reduce((acc, curr) => acc + curr.toString(16).padStart(2, '0'), '')}`;
419
419
  }
420
420
  function getAngle(token) {
421
- if (token.typ == 'Dimension') {
421
+ if (token.typ == 'Angle') {
422
422
  switch (token.unit) {
423
423
  case 'deg':
424
424
  // @ts-ignore
@@ -491,4 +491,4 @@ function hsl2rgb(h, s, l, a = null) {
491
491
  return values;
492
492
  }
493
493
 
494
- export { COLORS_NAMES, NAMES_COLORS, cmyk2hex, hsl2Hex, hwb2hex, rgb2Hex };
494
+ export { COLORS_NAMES, NAMES_COLORS, cmyk2hex, getAngle, hsl2Hex, hwb2hex, rgb2Hex };
@@ -1,7 +1,13 @@
1
1
  import { parse as parse$1 } from '../lib/parser/parse.js';
2
2
  export { parseString, urlTokenMatcher } from '../lib/parser/parse.js';
3
- import '../lib/renderer/utils/color.js';
3
+ export { tokenize } from '../lib/parser/tokenize.js';
4
+ export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from '../lib/parser/utils/syntax.js';
5
+ export { getConfig } from '../lib/parser/utils/config.js';
6
+ export { matchType } from '../lib/parser/utils/type.js';
7
+ export { render, renderToken } from '../lib/renderer/render.js';
4
8
  import { transform as transform$1 } from '../lib/transform.js';
9
+ export { combinators, hasDeclaration, minify, minifyRule, reduceSelector } from '../lib/ast/minify.js';
10
+ export { walk } from '../lib/ast/walk.js';
5
11
  import { load } from './load.js';
6
12
  import { resolve } from '../lib/fs/resolve.js';
7
13
  export { dirname, matchUrl } from '../lib/fs/resolve.js';
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@tbela99/css-parser",
3
3
  "description": "CSS parser for node and the browser",
4
- "version": "0.0.1-rc3",
4
+ "version": "0.0.1-rc4",
5
5
  "exports": {
6
- ".": "./dist/index.js",
6
+ ".": "./dist/node/index.js",
7
7
  "./umd": "./dist/index-umd-web.js",
8
8
  "./web": "./dist/web/index.js",
9
9
  "./cjs": "./dist/index.cjs"
@@ -24,14 +24,16 @@
24
24
  "keywords": [
25
25
  "parser",
26
26
  "css",
27
- "css parser",
28
27
  "css-parser",
29
28
  "node",
30
29
  "ast",
30
+ "nesting",
31
+ "nested",
32
+ "compiler",
31
33
  "browser",
32
- "css nesting",
33
- "css compiler",
34
- "nested css"
34
+ "css-nesting",
35
+ "css-compiler",
36
+ "nested-css"
35
37
  ],
36
38
  "author": "Thierry Bela",
37
39
  "license": "MIT OR LGPL-3.0",
@@ -44,7 +46,6 @@
44
46
  "@rollup/plugin-commonjs": "^25.0.4",
45
47
  "@rollup/plugin-json": "^6.0.0",
46
48
  "@rollup/plugin-node-resolve": "^15.1.0",
47
- "@rollup/plugin-terser": "^0.4.3",
48
49
  "@rollup/plugin-typescript": "^11.1.2",
49
50
  "@types/chai": "^4.3.5",
50
51
  "@types/mocha": "^10.0.1",
package/dist/index.js DELETED
@@ -1,11 +0,0 @@
1
- export { parse, transform } from './node/index.js';
2
- export { parseString, urlTokenMatcher } from './lib/parser/parse.js';
3
- export { tokenize } from './lib/parser/tokenize.js';
4
- export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from './lib/parser/utils/syntax.js';
5
- export { getConfig } from './lib/parser/utils/config.js';
6
- export { matchType } from './lib/parser/utils/type.js';
7
- export { render, renderToken } from './lib/renderer/render.js';
8
- export { combinators, hasDeclaration, minify, minifyRule, reduceSelector } from './lib/ast/minify.js';
9
- export { walk } from './lib/ast/walk.js';
10
- export { load } from './node/load.js';
11
- export { dirname, matchUrl, resolve } from './lib/fs/resolve.js';