postcss 8.4.24 → 8.4.26

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.

Potentially problematic release.


This version of postcss might be problematic. Click here for more details.

package/lib/parser.js CHANGED
@@ -31,49 +31,140 @@ class Parser {
31
31
  this.customProperty = false
32
32
 
33
33
  this.createTokenizer()
34
- this.root.source = { input, start: { offset: 0, line: 1, column: 1 } }
34
+ this.root.source = { input, start: { column: 1, line: 1, offset: 0 } }
35
35
  }
36
36
 
37
- createTokenizer() {
38
- this.tokenizer = tokenizer(this.input)
39
- }
37
+ atrule(token) {
38
+ let node = new AtRule()
39
+ node.name = token[1].slice(1)
40
+ if (node.name === '') {
41
+ this.unnamedAtrule(node, token)
42
+ }
43
+ this.init(node, token[2])
44
+
45
+ let type
46
+ let prev
47
+ let shift
48
+ let last = false
49
+ let open = false
50
+ let params = []
51
+ let brackets = []
40
52
 
41
- parse() {
42
- let token
43
53
  while (!this.tokenizer.endOfFile()) {
44
54
  token = this.tokenizer.nextToken()
55
+ type = token[0]
45
56
 
46
- switch (token[0]) {
47
- case 'space':
48
- this.spaces += token[1]
49
- break
57
+ if (type === '(' || type === '[') {
58
+ brackets.push(type === '(' ? ')' : ']')
59
+ } else if (type === '{' && brackets.length > 0) {
60
+ brackets.push('}')
61
+ } else if (type === brackets[brackets.length - 1]) {
62
+ brackets.pop()
63
+ }
50
64
 
51
- case ';':
52
- this.freeSemicolon(token)
65
+ if (brackets.length === 0) {
66
+ if (type === ';') {
67
+ node.source.end = this.getPosition(token[2])
68
+ this.semicolon = true
53
69
  break
54
-
55
- case '}':
70
+ } else if (type === '{') {
71
+ open = true
72
+ break
73
+ } else if (type === '}') {
74
+ if (params.length > 0) {
75
+ shift = params.length - 1
76
+ prev = params[shift]
77
+ while (prev && prev[0] === 'space') {
78
+ prev = params[--shift]
79
+ }
80
+ if (prev) {
81
+ node.source.end = this.getPosition(prev[3] || prev[2])
82
+ }
83
+ }
56
84
  this.end(token)
57
85
  break
86
+ } else {
87
+ params.push(token)
88
+ }
89
+ } else {
90
+ params.push(token)
91
+ }
58
92
 
59
- case 'comment':
60
- this.comment(token)
61
- break
93
+ if (this.tokenizer.endOfFile()) {
94
+ last = true
95
+ break
96
+ }
97
+ }
62
98
 
63
- case 'at-word':
64
- this.atrule(token)
65
- break
99
+ node.raws.between = this.spacesAndCommentsFromEnd(params)
100
+ if (params.length) {
101
+ node.raws.afterName = this.spacesAndCommentsFromStart(params)
102
+ this.raw(node, 'params', params)
103
+ if (last) {
104
+ token = params[params.length - 1]
105
+ node.source.end = this.getPosition(token[3] || token[2])
106
+ this.spaces = node.raws.between
107
+ node.raws.between = ''
108
+ }
109
+ } else {
110
+ node.raws.afterName = ''
111
+ node.params = ''
112
+ }
66
113
 
67
- case '{':
68
- this.emptyRule(token)
69
- break
114
+ if (open) {
115
+ node.nodes = []
116
+ this.current = node
117
+ }
118
+ }
70
119
 
71
- default:
72
- this.other(token)
73
- break
120
+ checkMissedSemicolon(tokens) {
121
+ let colon = this.colon(tokens)
122
+ if (colon === false) return
123
+
124
+ let founded = 0
125
+ let token
126
+ for (let j = colon - 1; j >= 0; j--) {
127
+ token = tokens[j]
128
+ if (token[0] !== 'space') {
129
+ founded += 1
130
+ if (founded === 2) break
74
131
  }
75
132
  }
76
- this.endFile()
133
+ // If the token is a word, e.g. `!important`, `red` or any other valid property's value.
134
+ // Then we need to return the colon after that word token. [3] is the "end" colon of that word.
135
+ // And because we need it after that one we do +1 to get the next one.
136
+ throw this.input.error(
137
+ 'Missed semicolon',
138
+ token[0] === 'word' ? token[3] + 1 : token[2]
139
+ )
140
+ }
141
+
142
+ colon(tokens) {
143
+ let brackets = 0
144
+ let token, type, prev
145
+ for (let [i, element] of tokens.entries()) {
146
+ token = element
147
+ type = token[0]
148
+
149
+ if (type === '(') {
150
+ brackets += 1
151
+ }
152
+ if (type === ')') {
153
+ brackets -= 1
154
+ }
155
+ if (brackets === 0 && type === ':') {
156
+ if (!prev) {
157
+ this.doubleColon(token)
158
+ } else if (prev[0] === 'word' && prev[1] === 'progid') {
159
+ continue
160
+ } else {
161
+ return i
162
+ }
163
+ }
164
+
165
+ prev = token
166
+ }
167
+ return false
77
168
  }
78
169
 
79
170
  comment(token) {
@@ -94,86 +185,8 @@ class Parser {
94
185
  }
95
186
  }
96
187
 
97
- emptyRule(token) {
98
- let node = new Rule()
99
- this.init(node, token[2])
100
- node.selector = ''
101
- node.raws.between = ''
102
- this.current = node
103
- }
104
-
105
- other(start) {
106
- let end = false
107
- let type = null
108
- let colon = false
109
- let bracket = null
110
- let brackets = []
111
- let customProperty = start[1].startsWith('--')
112
-
113
- let tokens = []
114
- let token = start
115
- while (token) {
116
- type = token[0]
117
- tokens.push(token)
118
-
119
- if (type === '(' || type === '[') {
120
- if (!bracket) bracket = token
121
- brackets.push(type === '(' ? ')' : ']')
122
- } else if (customProperty && colon && type === '{') {
123
- if (!bracket) bracket = token
124
- brackets.push('}')
125
- } else if (brackets.length === 0) {
126
- if (type === ';') {
127
- if (colon) {
128
- this.decl(tokens, customProperty)
129
- return
130
- } else {
131
- break
132
- }
133
- } else if (type === '{') {
134
- this.rule(tokens)
135
- return
136
- } else if (type === '}') {
137
- this.tokenizer.back(tokens.pop())
138
- end = true
139
- break
140
- } else if (type === ':') {
141
- colon = true
142
- }
143
- } else if (type === brackets[brackets.length - 1]) {
144
- brackets.pop()
145
- if (brackets.length === 0) bracket = null
146
- }
147
-
148
- token = this.tokenizer.nextToken()
149
- }
150
-
151
- if (this.tokenizer.endOfFile()) end = true
152
- if (brackets.length > 0) this.unclosedBracket(bracket)
153
-
154
- if (end && colon) {
155
- if (!customProperty) {
156
- while (tokens.length) {
157
- token = tokens[tokens.length - 1][0]
158
- if (token !== 'space' && token !== 'comment') break
159
- this.tokenizer.back(tokens.pop())
160
- }
161
- }
162
- this.decl(tokens, customProperty)
163
- } else {
164
- this.unknownWord(tokens)
165
- }
166
- }
167
-
168
- rule(tokens) {
169
- tokens.pop()
170
-
171
- let node = new Rule()
172
- this.init(node, tokens[0][2])
173
-
174
- node.raws.between = this.spacesAndCommentsFromEnd(tokens)
175
- this.raw(node, 'selector', tokens)
176
- this.current = node
188
+ createTokenizer() {
189
+ this.tokenizer = tokenizer(this.input)
177
190
  }
178
191
 
179
192
  decl(tokens, customProperty) {
@@ -247,122 +260,55 @@ class Parser {
247
260
  break
248
261
  } else if (token[1].toLowerCase() === 'important') {
249
262
  let cache = tokens.slice(0)
250
- let str = ''
251
- for (let j = i; j > 0; j--) {
252
- let type = cache[j][0]
253
- if (str.trim().indexOf('!') === 0 && type !== 'space') {
254
- break
255
- }
256
- str = cache.pop()[1] + str
257
- }
258
- if (str.trim().indexOf('!') === 0) {
259
- node.important = true
260
- node.raws.important = str
261
- tokens = cache
262
- }
263
- }
264
-
265
- if (token[0] !== 'space' && token[0] !== 'comment') {
266
- break
267
- }
268
- }
269
-
270
- let hasWord = tokens.some(i => i[0] !== 'space' && i[0] !== 'comment')
271
-
272
- if (hasWord) {
273
- node.raws.between += firstSpaces.map(i => i[1]).join('')
274
- firstSpaces = []
275
- }
276
- this.raw(node, 'value', firstSpaces.concat(tokens), customProperty)
277
-
278
- if (node.value.includes(':') && !customProperty) {
279
- this.checkMissedSemicolon(tokens)
280
- }
281
- }
282
-
283
- atrule(token) {
284
- let node = new AtRule()
285
- node.name = token[1].slice(1)
286
- if (node.name === '') {
287
- this.unnamedAtrule(node, token)
288
- }
289
- this.init(node, token[2])
290
-
291
- let type
292
- let prev
293
- let shift
294
- let last = false
295
- let open = false
296
- let params = []
297
- let brackets = []
298
-
299
- while (!this.tokenizer.endOfFile()) {
300
- token = this.tokenizer.nextToken()
301
- type = token[0]
302
-
303
- if (type === '(' || type === '[') {
304
- brackets.push(type === '(' ? ')' : ']')
305
- } else if (type === '{' && brackets.length > 0) {
306
- brackets.push('}')
307
- } else if (type === brackets[brackets.length - 1]) {
308
- brackets.pop()
309
- }
310
-
311
- if (brackets.length === 0) {
312
- if (type === ';') {
313
- node.source.end = this.getPosition(token[2])
314
- this.semicolon = true
315
- break
316
- } else if (type === '{') {
317
- open = true
318
- break
319
- } else if (type === '}') {
320
- if (params.length > 0) {
321
- shift = params.length - 1
322
- prev = params[shift]
323
- while (prev && prev[0] === 'space') {
324
- prev = params[--shift]
325
- }
326
- if (prev) {
327
- node.source.end = this.getPosition(prev[3] || prev[2])
328
- }
263
+ let str = ''
264
+ for (let j = i; j > 0; j--) {
265
+ let type = cache[j][0]
266
+ if (str.trim().indexOf('!') === 0 && type !== 'space') {
267
+ break
329
268
  }
330
- this.end(token)
331
- break
332
- } else {
333
- params.push(token)
269
+ str = cache.pop()[1] + str
270
+ }
271
+ if (str.trim().indexOf('!') === 0) {
272
+ node.important = true
273
+ node.raws.important = str
274
+ tokens = cache
334
275
  }
335
- } else {
336
- params.push(token)
337
276
  }
338
277
 
339
- if (this.tokenizer.endOfFile()) {
340
- last = true
278
+ if (token[0] !== 'space' && token[0] !== 'comment') {
341
279
  break
342
280
  }
343
281
  }
344
282
 
345
- node.raws.between = this.spacesAndCommentsFromEnd(params)
346
- if (params.length) {
347
- node.raws.afterName = this.spacesAndCommentsFromStart(params)
348
- this.raw(node, 'params', params)
349
- if (last) {
350
- token = params[params.length - 1]
351
- node.source.end = this.getPosition(token[3] || token[2])
352
- this.spaces = node.raws.between
353
- node.raws.between = ''
354
- }
355
- } else {
356
- node.raws.afterName = ''
357
- node.params = ''
283
+ let hasWord = tokens.some(i => i[0] !== 'space' && i[0] !== 'comment')
284
+
285
+ if (hasWord) {
286
+ node.raws.between += firstSpaces.map(i => i[1]).join('')
287
+ firstSpaces = []
358
288
  }
289
+ this.raw(node, 'value', firstSpaces.concat(tokens), customProperty)
359
290
 
360
- if (open) {
361
- node.nodes = []
362
- this.current = node
291
+ if (node.value.includes(':') && !customProperty) {
292
+ this.checkMissedSemicolon(tokens)
363
293
  }
364
294
  }
365
295
 
296
+ doubleColon(token) {
297
+ throw this.input.error(
298
+ 'Double colon',
299
+ { offset: token[2] },
300
+ { offset: token[2] + token[1].length }
301
+ )
302
+ }
303
+
304
+ emptyRule(token) {
305
+ let node = new Rule()
306
+ this.init(node, token[2])
307
+ node.selector = ''
308
+ node.raws.between = ''
309
+ this.current = node
310
+ }
311
+
366
312
  end(token) {
367
313
  if (this.current.nodes && this.current.nodes.length) {
368
314
  this.current.raws.semicolon = this.semicolon
@@ -404,23 +350,128 @@ class Parser {
404
350
  getPosition(offset) {
405
351
  let pos = this.input.fromOffset(offset)
406
352
  return {
407
- offset,
353
+ column: pos.col,
408
354
  line: pos.line,
409
- column: pos.col
355
+ offset
410
356
  }
411
357
  }
412
358
 
413
359
  init(node, offset) {
414
360
  this.current.push(node)
415
361
  node.source = {
416
- start: this.getPosition(offset),
417
- input: this.input
362
+ input: this.input,
363
+ start: this.getPosition(offset)
418
364
  }
419
365
  node.raws.before = this.spaces
420
366
  this.spaces = ''
421
367
  if (node.type !== 'comment') this.semicolon = false
422
368
  }
423
369
 
370
+ other(start) {
371
+ let end = false
372
+ let type = null
373
+ let colon = false
374
+ let bracket = null
375
+ let brackets = []
376
+ let customProperty = start[1].startsWith('--')
377
+
378
+ let tokens = []
379
+ let token = start
380
+ while (token) {
381
+ type = token[0]
382
+ tokens.push(token)
383
+
384
+ if (type === '(' || type === '[') {
385
+ if (!bracket) bracket = token
386
+ brackets.push(type === '(' ? ')' : ']')
387
+ } else if (customProperty && colon && type === '{') {
388
+ if (!bracket) bracket = token
389
+ brackets.push('}')
390
+ } else if (brackets.length === 0) {
391
+ if (type === ';') {
392
+ if (colon) {
393
+ this.decl(tokens, customProperty)
394
+ return
395
+ } else {
396
+ break
397
+ }
398
+ } else if (type === '{') {
399
+ this.rule(tokens)
400
+ return
401
+ } else if (type === '}') {
402
+ this.tokenizer.back(tokens.pop())
403
+ end = true
404
+ break
405
+ } else if (type === ':') {
406
+ colon = true
407
+ }
408
+ } else if (type === brackets[brackets.length - 1]) {
409
+ brackets.pop()
410
+ if (brackets.length === 0) bracket = null
411
+ }
412
+
413
+ token = this.tokenizer.nextToken()
414
+ }
415
+
416
+ if (this.tokenizer.endOfFile()) end = true
417
+ if (brackets.length > 0) this.unclosedBracket(bracket)
418
+
419
+ if (end && colon) {
420
+ if (!customProperty) {
421
+ while (tokens.length) {
422
+ token = tokens[tokens.length - 1][0]
423
+ if (token !== 'space' && token !== 'comment') break
424
+ this.tokenizer.back(tokens.pop())
425
+ }
426
+ }
427
+ this.decl(tokens, customProperty)
428
+ } else {
429
+ this.unknownWord(tokens)
430
+ }
431
+ }
432
+
433
+ parse() {
434
+ let token
435
+ while (!this.tokenizer.endOfFile()) {
436
+ token = this.tokenizer.nextToken()
437
+
438
+ switch (token[0]) {
439
+ case 'space':
440
+ this.spaces += token[1]
441
+ break
442
+
443
+ case ';':
444
+ this.freeSemicolon(token)
445
+ break
446
+
447
+ case '}':
448
+ this.end(token)
449
+ break
450
+
451
+ case 'comment':
452
+ this.comment(token)
453
+ break
454
+
455
+ case 'at-word':
456
+ this.atrule(token)
457
+ break
458
+
459
+ case '{':
460
+ this.emptyRule(token)
461
+ break
462
+
463
+ default:
464
+ this.other(token)
465
+ break
466
+ }
467
+ }
468
+ this.endFile()
469
+ }
470
+
471
+ precheckMissedSemicolon(/* tokens */) {
472
+ // Hook for Safe Parser
473
+ }
474
+
424
475
  raw(node, prop, tokens, customProperty) {
425
476
  let token, type
426
477
  let length = tokens.length
@@ -451,11 +502,22 @@ class Parser {
451
502
  }
452
503
  if (!clean) {
453
504
  let raw = tokens.reduce((all, i) => all + i[1], '')
454
- node.raws[prop] = { value, raw }
505
+ node.raws[prop] = { raw, value }
455
506
  }
456
507
  node[prop] = value
457
508
  }
458
509
 
510
+ rule(tokens) {
511
+ tokens.pop()
512
+
513
+ let node = new Rule()
514
+ this.init(node, tokens[0][2])
515
+
516
+ node.raws.between = this.spacesAndCommentsFromEnd(tokens)
517
+ this.raw(node, 'selector', tokens)
518
+ this.current = node
519
+ }
520
+
459
521
  spacesAndCommentsFromEnd(tokens) {
460
522
  let lastTokenType
461
523
  let spaces = ''
@@ -467,6 +529,8 @@ class Parser {
467
529
  return spaces
468
530
  }
469
531
 
532
+ // Errors
533
+
470
534
  spacesAndCommentsFromStart(tokens) {
471
535
  let next
472
536
  let spaces = ''
@@ -498,36 +562,11 @@ class Parser {
498
562
  return result
499
563
  }
500
564
 
501
- colon(tokens) {
502
- let brackets = 0
503
- let token, type, prev
504
- for (let [i, element] of tokens.entries()) {
505
- token = element
506
- type = token[0]
507
-
508
- if (type === '(') {
509
- brackets += 1
510
- }
511
- if (type === ')') {
512
- brackets -= 1
513
- }
514
- if (brackets === 0 && type === ':') {
515
- if (!prev) {
516
- this.doubleColon(token)
517
- } else if (prev[0] === 'word' && prev[1] === 'progid') {
518
- continue
519
- } else {
520
- return i
521
- }
522
- }
523
-
524
- prev = token
525
- }
526
- return false
565
+ unclosedBlock() {
566
+ let pos = this.current.source.start
567
+ throw this.input.error('Unclosed block', pos.line, pos.column)
527
568
  }
528
569
 
529
- // Errors
530
-
531
570
  unclosedBracket(bracket) {
532
571
  throw this.input.error(
533
572
  'Unclosed bracket',
@@ -536,14 +575,6 @@ class Parser {
536
575
  )
537
576
  }
538
577
 
539
- unknownWord(tokens) {
540
- throw this.input.error(
541
- 'Unknown word',
542
- { offset: tokens[0][2] },
543
- { offset: tokens[0][2] + tokens[0][1].length }
544
- )
545
- }
546
-
547
578
  unexpectedClose(token) {
548
579
  throw this.input.error(
549
580
  'Unexpected }',
@@ -552,16 +583,11 @@ class Parser {
552
583
  )
553
584
  }
554
585
 
555
- unclosedBlock() {
556
- let pos = this.current.source.start
557
- throw this.input.error('Unclosed block', pos.line, pos.column)
558
- }
559
-
560
- doubleColon(token) {
586
+ unknownWord(tokens) {
561
587
  throw this.input.error(
562
- 'Double colon',
563
- { offset: token[2] },
564
- { offset: token[2] + token[1].length }
588
+ 'Unknown word',
589
+ { offset: tokens[0][2] },
590
+ { offset: tokens[0][2] + tokens[0][1].length }
565
591
  )
566
592
  }
567
593
 
@@ -572,32 +598,6 @@ class Parser {
572
598
  { offset: token[2] + token[1].length }
573
599
  )
574
600
  }
575
-
576
- precheckMissedSemicolon(/* tokens */) {
577
- // Hook for Safe Parser
578
- }
579
-
580
- checkMissedSemicolon(tokens) {
581
- let colon = this.colon(tokens)
582
- if (colon === false) return
583
-
584
- let founded = 0
585
- let token
586
- for (let j = colon - 1; j >= 0; j--) {
587
- token = tokens[j]
588
- if (token[0] !== 'space') {
589
- founded += 1
590
- if (founded === 2) break
591
- }
592
- }
593
- // If the token is a word, e.g. `!important`, `red` or any other valid property's value.
594
- // Then we need to return the colon after that word token. [3] is the "end" colon of that word.
595
- // And because we need it after that one we do +1 to get the next one.
596
- throw this.input.error(
597
- 'Missed semicolon',
598
- token[0] === 'word' ? token[3] + 1 : token[2]
599
- )
600
- }
601
601
  }
602
602
 
603
603
  module.exports = Parser