postcss 8.5.28 → 8.5.30

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.
package/lib/list.js CHANGED
@@ -20,8 +20,21 @@ let list = {
20
20
  let inQuote = false
21
21
  let prevQuote = ''
22
22
  let escape = false
23
+ let inComment = false
24
+
25
+ for (let i = 0; i < string.length; i++) {
26
+ let letter = string[i]
27
+
28
+ if (inComment) {
29
+ current += letter
30
+ if (letter === '*' && string[i + 1] === '/') {
31
+ current += '/'
32
+ i += 1
33
+ inComment = false
34
+ }
35
+ continue
36
+ }
23
37
 
24
- for (let letter of string) {
25
38
  if (escape) {
26
39
  escape = false
27
40
  } else if (letter === '\\') {
@@ -33,6 +46,11 @@ let list = {
33
46
  } else if (letter === '"' || letter === "'") {
34
47
  inQuote = true
35
48
  prevQuote = letter
49
+ } else if (letter === '/' && string[i + 1] === '*') {
50
+ current += '/*'
51
+ i += 1
52
+ inComment = true
53
+ continue
36
54
  } else if (letter === '(') {
37
55
  func += 1
38
56
  } else if (letter === ')') {
@@ -75,16 +75,22 @@ class MapGenerator {
75
75
  }
76
76
  }
77
77
  } else if (this.css) {
78
+ // Single forward pass. Removing every annotation from the whole
79
+ // string separately would be O(n^2) on many annotations.
78
80
  let annotation = '/*# sourceMappingURL='
81
+ let css = ''
82
+ let cursor = 0
79
83
  let startIndex
80
- while ((startIndex = this.css.lastIndexOf(annotation)) !== -1) {
84
+ while ((startIndex = this.css.indexOf(annotation, cursor)) !== -1) {
81
85
  let endIndex = this.css.indexOf('*/', startIndex + annotation.length)
82
86
  if (endIndex === -1) break
83
- while (startIndex > 0 && this.css[startIndex - 1] === '\n') {
87
+ while (startIndex > cursor && this.css[startIndex - 1] === '\n') {
84
88
  startIndex--
85
89
  }
86
- this.css = this.css.slice(0, startIndex) + this.css.slice(endIndex + 2)
90
+ css += this.css.slice(cursor, startIndex)
91
+ cursor = endIndex + 2
87
92
  }
93
+ if (cursor > 0) this.css = css + this.css.slice(cursor)
88
94
  }
89
95
  }
90
96
 
package/lib/parser.js CHANGED
@@ -494,6 +494,9 @@ class Parser {
494
494
  let token, type
495
495
  let length = tokens.length
496
496
  let value = ''
497
+ // Last character of `value`. Reading it from `value` would flatten
498
+ // the string concatenation on every comment and make parsing O(n^2).
499
+ let last = ''
497
500
  let clean = true
498
501
  let next, prev
499
502
 
@@ -506,16 +509,18 @@ class Parser {
506
509
  prev = tokens[i - 1] ? tokens[i - 1][0] : 'empty'
507
510
  next = tokens[i + 1] ? tokens[i + 1][0] : 'empty'
508
511
  if (!SAFE_COMMENT_NEIGHBOR[prev] && !SAFE_COMMENT_NEIGHBOR[next]) {
509
- if (value.slice(-1) === ',') {
512
+ if (last === ',') {
510
513
  clean = false
511
514
  } else {
512
515
  value += token[1]
516
+ last = token[1].slice(-1)
513
517
  }
514
518
  } else {
515
519
  clean = false
516
520
  }
517
521
  } else {
518
522
  value += token[1]
523
+ last = token[1].slice(-1)
519
524
  }
520
525
  }
521
526
  if (!clean) {
package/lib/processor.js CHANGED
@@ -7,7 +7,7 @@ let Root = require('./root')
7
7
 
8
8
  class Processor {
9
9
  constructor(plugins = []) {
10
- this.version = '8.5.28'
10
+ this.version = '8.5.30'
11
11
  this.plugins = this.normalize(plugins)
12
12
  }
13
13
 
@@ -1,21 +1,9 @@
1
1
  'use strict'
2
2
 
3
- // Escapes sequences that could break out of an HTML <style> context.
4
- // Uses CSS unicode escaping (\3c = '<') which is valid CSS and parsed
5
- // correctly by all compliant CSS consumers.
6
- const STYLE_TAG = /(<)(\/?style\b)/gi
7
- const COMMENT_OPEN = /(<)(!--)/g
8
-
9
3
  // Characters that end an at-rule name, mirroring RE_AT_END in the tokenizer.
10
4
  // Params starting with anything else need a space to stay separate tokens.
11
5
  const AT_NAME_END = /[\t\n\f\r "#'()/;[\\\]{}]/
12
6
 
13
- function escapeHTMLInCSS(str) {
14
- if (typeof str !== 'string') return str
15
- if (!str.includes('<')) return str
16
- return str.replace(STYLE_TAG, '\\3c $2').replace(COMMENT_OPEN, '\\3c $2')
17
- }
18
-
19
7
  const DEFAULT_RAW = {
20
8
  after: '\n',
21
9
  beforeClose: '\n',
@@ -50,11 +38,12 @@ function atruleStart(str, node) {
50
38
  }
51
39
 
52
40
  // `*--x` is not a custom property: the parser checks the first token, and the
53
- // `*`/`_` hack prefix moves from `prop` into `before` only after that.
41
+ // `*`/`_` hack prefix moves from `prop` into `before` only after that. A free
42
+ // semicolon in front of the declaration also ends up in `before`.
54
43
  function isCustomProperty(node) {
55
44
  if (!node.prop.startsWith('--')) return false
56
45
  let before = node.raws.before
57
- return typeof before === 'undefined' || !/\S$/.test(before)
46
+ return typeof before === 'undefined' || !/[^\s;]$/.test(before)
58
47
  }
59
48
 
60
49
  function pushBody(str, stack, node) {
@@ -92,17 +81,17 @@ function pushBody(str, stack, node) {
92
81
 
93
82
  function pushBlock(str, stack, node, start) {
94
83
  let between = str.raw(node, 'between', 'beforeOpen')
95
- str.builder(escapeHTMLInCSS(start + between) + '{', node, 'start')
84
+ str.builder(start + between + '{', node, 'start')
96
85
 
97
86
  let hasNodes = node.nodes && node.nodes.length
98
87
  let close = () => {
99
88
  let after = hasNodes
100
89
  ? str.raw(node, 'after')
101
90
  : str.raw(node, 'after', 'emptyBody')
102
- if (after) str.builder(escapeHTMLInCSS(after))
91
+ if (after) str.builder(after)
103
92
  str.builder('}', node, 'end')
104
93
  if (node.type === 'rule' && node.raws.ownSemicolon) {
105
- str.builder(escapeHTMLInCSS(node.raws.ownSemicolon), node, 'end')
94
+ str.builder(node.raws.ownSemicolon, node, 'end')
106
95
  }
107
96
  }
108
97
 
@@ -125,7 +114,7 @@ class Stringifier {
125
114
  this.block(node, start)
126
115
  } else {
127
116
  let end = (node.raws.between || '') + (semicolon ? ';' : '')
128
- this.builder(escapeHTMLInCSS(start + end), node)
117
+ this.builder(start + end, node)
129
118
  }
130
119
  }
131
120
 
@@ -160,7 +149,7 @@ class Stringifier {
160
149
 
161
150
  block(node, start) {
162
151
  let between = this.raw(node, 'between', 'beforeOpen')
163
- this.builder(escapeHTMLInCSS(start + between) + '{', node, 'start')
152
+ this.builder(start + between + '{', node, 'start')
164
153
 
165
154
  let after
166
155
  if (node.nodes && node.nodes.length) {
@@ -170,7 +159,7 @@ class Stringifier {
170
159
  after = this.raw(node, 'after', 'emptyBody')
171
160
  }
172
161
 
173
- if (after) this.builder(escapeHTMLInCSS(after))
162
+ if (after) this.builder(after)
174
163
  this.builder('}', node, 'end')
175
164
  }
176
165
 
@@ -197,7 +186,7 @@ class Stringifier {
197
186
  let child = entry.node
198
187
  let before = this.raw(child, 'before')
199
188
  if (before) {
200
- this.builder(entry.document ? before : escapeHTMLInCSS(before))
189
+ this.builder(before)
201
190
  }
202
191
 
203
192
  if (expandable && child.type === 'rule') {
@@ -213,7 +202,7 @@ class Stringifier {
213
202
  comment(node) {
214
203
  let left = this.raw(node, 'left', 'commentLeft')
215
204
  let right = this.raw(node, 'right', 'commentRight')
216
- this.builder(escapeHTMLInCSS('/*' + left + node.text + right + '*/'), node)
205
+ this.builder('/*' + left + node.text + right + '*/', node)
217
206
  }
218
207
 
219
208
  decl(node, semicolon) {
@@ -227,7 +216,7 @@ class Stringifier {
227
216
  }
228
217
 
229
218
  if (semicolon) string += ';'
230
- this.builder(escapeHTMLInCSS(string), node)
219
+ this.builder(string, node)
231
220
  }
232
221
 
233
222
  document(node) {
@@ -432,21 +421,20 @@ class Stringifier {
432
421
  }
433
422
 
434
423
  root(node) {
435
- if (node.source && node.source.input.hasBOM) {
424
+ let inDocument = node.parent && node.parent.type === 'document'
425
+ if (!inDocument && node.source && node.source.input.hasBOM) {
436
426
  this.builder('\uFEFF', node, 'start')
437
427
  }
438
428
  this.body(node)
439
429
  if (node.raws.after) {
440
- let after = node.raws.after
441
- let isDocument = node.parent && node.parent.type === 'document'
442
- this.builder(isDocument ? after : escapeHTMLInCSS(after))
430
+ this.builder(node.raws.after)
443
431
  }
444
432
  }
445
433
 
446
434
  rule(node) {
447
435
  this.block(node, this.rawValue(node, 'selector'))
448
436
  if (node.raws.ownSemicolon) {
449
- this.builder(escapeHTMLInCSS(node.raws.ownSemicolon), node, 'end')
437
+ this.builder(node.raws.ownSemicolon, node, 'end')
450
438
  }
451
439
  }
452
440
 
package/lib/stringify.js CHANGED
@@ -2,9 +2,69 @@
2
2
 
3
3
  let Stringifier = require('./stringifier')
4
4
 
5
+ const STYLE_TAG = /(<)(\/?style\b)/gi
6
+ const COMMENT_OPEN = /(<)(!--)/g
7
+
8
+ function escapeHTMLInCSS(str) {
9
+ if (!str.includes('<')) return str
10
+ return str.replace(STYLE_TAG, '\\3c $2').replace(COMMENT_OPEN, '\\3c $2')
11
+ }
12
+
13
+ class SafeStringifier extends Stringifier {
14
+ escapeHTML(node, print) {
15
+ let builder = this.builder
16
+ let chunks, css
17
+ this.builder = (str, child, type) => {
18
+ if (chunks) {
19
+ css += str
20
+ chunks.push(str, child, type)
21
+ } else if (str.includes('<')) {
22
+ // Dangerous sequences start with `<`, so everything before
23
+ // the first `<` is safe to pass through. The rest is buffered
24
+ // to escape it as a whole.
25
+ css = str
26
+ chunks = [str, child, type]
27
+ } else {
28
+ builder(str, child, type)
29
+ }
30
+ }
31
+ print()
32
+ this.builder = builder
33
+ if (!chunks) return
34
+
35
+ let escaped = escapeHTMLInCSS(css)
36
+ if (escaped === css) {
37
+ // Keep the chunks to get a precise source map
38
+ for (let i = 0; i < chunks.length; i += 3) {
39
+ builder(chunks[i], chunks[i + 1], chunks[i + 2])
40
+ }
41
+ } else {
42
+ // Escaping happens only on a hack attempt, source map is not important.
43
+ // Map only the node start: the end mapping expects the node’s last
44
+ // char at the end of the string, which is wrong for Root raws.after.
45
+ builder(escaped, node, 'start')
46
+ }
47
+ }
48
+
49
+ root(node) {
50
+ if (node.parent && node.parent.type === 'document') {
51
+ // Document raws around the Root hold markup and are kept as is since CSS in Document can be marked with <style>
52
+ this.escapeHTML(node, () => this.body(node))
53
+ if (node.raws.after) this.builder(node.raws.after)
54
+ } else {
55
+ this.escapeHTML(node, () => super.root(node))
56
+ }
57
+ }
58
+ }
59
+
5
60
  function stringify(node, builder) {
6
- let str = new Stringifier(builder)
7
- str.stringify(node)
61
+ let str = new SafeStringifier(builder)
62
+ // Root escapes its own output and keeps markup in Document raws
63
+ if (node.type === 'root' || node.type === 'document') {
64
+ str.stringify(node)
65
+ } else {
66
+ str.escapeHTML(node, () => str.stringify(node))
67
+ }
8
68
  }
9
69
 
10
70
  module.exports = stringify
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postcss",
3
- "version": "8.5.28",
3
+ "version": "8.5.30",
4
4
  "description": "Tool for transforming styles with JS plugins",
5
5
  "keywords": [
6
6
  "css",
@@ -78,9 +78,9 @@
78
78
  "./package.json": "./package.json"
79
79
  },
80
80
  "dependencies": {
81
- "nanoid": "^3.3.18",
81
+ "nanoid": "^3.3.20",
82
82
  "picocolors": "^1.1.1",
83
- "source-map-js": "^1.2.1"
83
+ "source-map-js": "^1.2.2"
84
84
  },
85
85
  "engines": {
86
86
  "node": "^10 || ^12 || >=14"