postcss 8.3.11 → 8.4.3

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.

@@ -181,10 +181,10 @@ export default abstract class Container<
181
181
  */
182
182
  walkRules(
183
183
  selectorFilter: string | RegExp,
184
- callback: (atRule: Rule, index: number) => false | void
184
+ callback: (rule: Rule, index: number) => false | void
185
185
  ): false | undefined
186
186
  walkRules(
187
- callback: (atRule: Rule, index: number) => false | void
187
+ callback: (rule: Rule, index: number) => false | void
188
188
  ): false | undefined
189
189
 
190
190
  /**
package/lib/container.js CHANGED
@@ -316,7 +316,7 @@ class Container extends Node {
316
316
  }
317
317
 
318
318
  let processed = nodes.map(i => {
319
- // istanbul ignore next
319
+ /* c8 ignore next */
320
320
  if (!i[my]) Container.rebuild(i)
321
321
  i = i.proxyOf
322
322
  if (i.parent) i.parent.removeChild(i)
@@ -410,7 +410,7 @@ Container.registerAtRule = dependant => {
410
410
  module.exports = Container
411
411
  Container.default = Container
412
412
 
413
- // istanbul ignore next
413
+ /* c8 ignore start */
414
414
  Container.rebuild = node => {
415
415
  if (node.type === 'atrule') {
416
416
  Object.setPrototypeOf(node, AtRule.prototype)
@@ -430,3 +430,4 @@ Container.rebuild = node => {
430
430
  })
431
431
  }
432
432
  }
433
+ /* c8 ignore stop */
@@ -1,5 +1,20 @@
1
1
  import { FilePosition } from './input.js'
2
2
 
3
+ /**
4
+ * A position that is part of a range.
5
+ */
6
+ export interface RangePosition {
7
+ /**
8
+ * The line number in the input.
9
+ */
10
+ line: number
11
+
12
+ /**
13
+ * The column number in the input.
14
+ */
15
+ column: number
16
+ }
17
+
3
18
  /**
4
19
  * The CSS parser throws this error for broken CSS.
5
20
  *
@@ -31,17 +46,21 @@ import { FilePosition } from './input.js'
31
46
  */
32
47
  export default class CssSyntaxError {
33
48
  /**
34
- * @param message Error message.
35
- * @param line Source line of the error.
36
- * @param column Source column of the error.
37
- * @param source Source code of the broken file.
38
- * @param file Absolute path to the broken file.
39
- * @param plugin PostCSS plugin name, if error came from plugin.
49
+ * Instantiates a CSS syntax error. Can be instantiated for a single position
50
+ * or for a range.
51
+ * @param message Error message.
52
+ * @param lineOrStartPos If for a single position, the line number, or if for
53
+ * a range, the inclusive start position of the error.
54
+ * @param columnOrEndPos If for a single position, the column number, or if for
55
+ * a range, the exclusive end position of the error.
56
+ * @param source Source code of the broken file.
57
+ * @param file Absolute path to the broken file.
58
+ * @param plugin PostCSS plugin name, if error came from plugin.
40
59
  */
41
60
  constructor(
42
61
  message: string,
43
- line?: number,
44
- column?: number,
62
+ lineOrStartPos?: number | RangePosition,
63
+ columnOrEndPos?: number | RangePosition,
45
64
  source?: string,
46
65
  file?: string,
47
66
  plugin?: string
@@ -121,6 +140,34 @@ export default class CssSyntaxError {
121
140
  */
122
141
  column?: number
123
142
 
143
+ /**
144
+ * Source line of the error's end, exclusive. Provided if the error pertains
145
+ * to a range.
146
+ *
147
+ * ```js
148
+ * error.endLine //=> 3
149
+ * error.input.endLine //=> 4
150
+ * ```
151
+ *
152
+ * PostCSS will use the input source map to detect the original location.
153
+ * If you need the position in the PostCSS input, use `error.input.endLine`.
154
+ */
155
+ endLine?: number
156
+
157
+ /**
158
+ * Source column of the error's end, exclusive. Provided if the error pertains
159
+ * to a range.
160
+ *
161
+ * ```js
162
+ * error.endColumn //=> 1
163
+ * error.input.endColumn //=> 4
164
+ * ```
165
+ *
166
+ * PostCSS will use the input source map to detect the original location.
167
+ * If you need the position in the PostCSS input, use `error.input.endColumn`.
168
+ */
169
+ endColumn?: number
170
+
124
171
  /**
125
172
  * Source code of the broken file.
126
173
  *
@@ -20,8 +20,15 @@ class CssSyntaxError extends Error {
20
20
  this.plugin = plugin
21
21
  }
22
22
  if (typeof line !== 'undefined' && typeof column !== 'undefined') {
23
- this.line = line
24
- this.column = column
23
+ if (typeof line === 'number') {
24
+ this.line = line
25
+ this.column = column
26
+ } else {
27
+ this.line = line.line
28
+ this.column = line.column
29
+ this.endLine = column.line
30
+ this.endColumn = column.column
31
+ }
25
32
  }
26
33
 
27
34
  this.setMessage()
package/lib/input.d.ts CHANGED
@@ -13,15 +13,25 @@ export interface FilePosition {
13
13
  file?: string
14
14
 
15
15
  /**
16
- * Line in source file.
16
+ * Line of inclusive start position in source file.
17
17
  */
18
18
  line: number
19
19
 
20
20
  /**
21
- * Column in source file.
21
+ * Column of inclusive start position in source file.
22
22
  */
23
23
  column: number
24
24
 
25
+ /**
26
+ * Line of exclusive end position in source file.
27
+ */
28
+ endLine?: number
29
+
30
+ /**
31
+ * Column of exclusive end position in source file.
32
+ */
33
+ endColumn?: number
34
+
25
35
  /**
26
36
  * Source code.
27
37
  */
@@ -108,18 +118,28 @@ export default class Input {
108
118
  /**
109
119
  * Reads the input source map and returns a symbol position
110
120
  * in the input source (e.g., in a Sass file that was compiled
111
- * to CSS before being passed to PostCSS).
121
+ * to CSS before being passed to PostCSS). Optionally takes an
122
+ * end position, exclusive.
112
123
  *
113
124
  * ```js
114
125
  * root.source.input.origin(1, 1) //=> { file: 'a.css', line: 3, column: 1 }
126
+ * root.source.input.origin(1, 1, 1, 4)
127
+ * //=> { file: 'a.css', line: 3, column: 1, endLine: 3, endColumn: 4 }
115
128
  * ```
116
129
  *
117
- * @param line Line in input CSS.
118
- * @param column Column in input CSS.
130
+ * @param line Line for inclusive start position in input CSS.
131
+ * @param column Column for inclusive start position in input CSS.
132
+ * @param endLine Line for exclusive end position in input CSS.
133
+ * @param endColumn Column for exclusive end position in input CSS.
119
134
  *
120
135
  * @return Position in input source.
121
136
  */
122
- origin(line: number, column: number): FilePosition | false
137
+ origin(
138
+ line: number,
139
+ column: number,
140
+ endLine?: number,
141
+ endColumn?: number
142
+ ): FilePosition | false
123
143
 
124
144
  /**
125
145
  * Converts source offset to line and column.
package/lib/input.js CHANGED
@@ -103,18 +103,43 @@ class Input {
103
103
  }
104
104
 
105
105
  error(message, line, column, opts = {}) {
106
- let result
107
- if (!column) {
106
+ let result, endLine, endColumn
107
+
108
+ if (line && typeof line === 'object') {
109
+ let start = line
110
+ let end = column
111
+ if (typeof line.offset === 'number') {
112
+ let pos = this.fromOffset(start.offset)
113
+ line = pos.line
114
+ column = pos.col
115
+ } else {
116
+ line = start.line
117
+ column = start.column
118
+ }
119
+ if (typeof end.offset === 'number') {
120
+ let pos = this.fromOffset(end.offset)
121
+ endLine = pos.line
122
+ endColumn = pos.col
123
+ } else {
124
+ endLine = end.line
125
+ endColumn = end.column
126
+ }
127
+ } else if (!column) {
108
128
  let pos = this.fromOffset(line)
109
129
  line = pos.line
110
130
  column = pos.col
111
131
  }
112
- let origin = this.origin(line, column)
132
+
133
+ let origin = this.origin(line, column, endLine, endColumn)
113
134
  if (origin) {
114
135
  result = new CssSyntaxError(
115
136
  message,
116
- origin.line,
117
- origin.column,
137
+ origin.endLine === undefined
138
+ ? origin.line
139
+ : { line: origin.line, column: origin.column },
140
+ origin.endLine === undefined
141
+ ? origin.column
142
+ : { line: origin.endLine, column: origin.endColumn },
118
143
  origin.source,
119
144
  origin.file,
120
145
  opts.plugin
@@ -122,15 +147,15 @@ class Input {
122
147
  } else {
123
148
  result = new CssSyntaxError(
124
149
  message,
125
- line,
126
- column,
150
+ endLine === undefined ? line : { line, column },
151
+ endLine === undefined ? column : { line: endLine, column: endColumn },
127
152
  this.css,
128
153
  this.file,
129
154
  opts.plugin
130
155
  )
131
156
  }
132
157
 
133
- result.input = { line, column, source: this.css }
158
+ result.input = { line, column, endLine, endColumn, source: this.css }
134
159
  if (this.file) {
135
160
  if (pathToFileURL) {
136
161
  result.input.url = pathToFileURL(this.file).toString()
@@ -141,13 +166,18 @@ class Input {
141
166
  return result
142
167
  }
143
168
 
144
- origin(line, column) {
169
+ origin(line, column, endLine, endColumn) {
145
170
  if (!this.map) return false
146
171
  let consumer = this.map.consumer()
147
172
 
148
173
  let from = consumer.originalPositionFor({ line, column })
149
174
  if (!from.source) return false
150
175
 
176
+ let to
177
+ if (typeof endLine === 'number') {
178
+ to = consumer.originalPositionFor({ line: endLine, column: endColumn })
179
+ }
180
+
151
181
  let fromUrl
152
182
 
153
183
  if (isAbsolute(from.source)) {
@@ -162,14 +192,16 @@ class Input {
162
192
  let result = {
163
193
  url: fromUrl.toString(),
164
194
  line: from.line,
165
- column: from.column
195
+ column: from.column,
196
+ endLine: to && to.line,
197
+ endColumn: to && to.column
166
198
  }
167
199
 
168
200
  if (fromUrl.protocol === 'file:') {
169
201
  if (fileURLToPath) {
170
202
  result.file = fileURLToPath(fromUrl)
171
203
  } else {
172
- // istanbul ignore next
204
+ /* c8 ignore next 2 */
173
205
  throw new Error(`file: protocol is not available in this PostCSS build`)
174
206
  }
175
207
  }
@@ -89,8 +89,9 @@ export default class LazyResult implements PromiseLike<Result> {
89
89
  *
90
90
  * This property will only work with synchronous plugins.
91
91
  * If the processor contains any asynchronous plugins
92
- * it will throw an error. This is why this method is only
93
- * for debug purpose, you should always use `LazyResult#then`.
92
+ * it will throw an error.
93
+ *
94
+ * PostCSS runners should always use `LazyResult#then`.
94
95
  */
95
96
  get css(): string
96
97
 
@@ -100,8 +101,9 @@ export default class LazyResult implements PromiseLike<Result> {
100
101
  *
101
102
  * This property will only work with synchronous plugins.
102
103
  * If the processor contains any asynchronous plugins
103
- * it will throw an error. This is why this method is only
104
- * for debug purpose, you should always use `LazyResult#then`.
104
+ * it will throw an error.
105
+ *
106
+ * PostCSS runners should always use `LazyResult#then`.
105
107
  */
106
108
  get content(): string
107
109
 
@@ -111,8 +113,9 @@ export default class LazyResult implements PromiseLike<Result> {
111
113
  *
112
114
  * This property will only work with synchronous plugins.
113
115
  * If the processor contains any asynchronous plugins
114
- * it will throw an error. This is why this method is only
115
- * for debug purpose, you should always use `LazyResult#then`.
116
+ * it will throw an error.
117
+ *
118
+ * PostCSS runners should always use `LazyResult#then`.
116
119
  */
117
120
  get map(): SourceMap
118
121
 
@@ -123,8 +126,7 @@ export default class LazyResult implements PromiseLike<Result> {
123
126
  * This property will only work with synchronous plugins. If the processor
124
127
  * contains any asynchronous plugins it will throw an error.
125
128
  *
126
- * This is why this method is only for debug purpose,
127
- * you should always use `LazyResult#then`.
129
+ * PostCSS runners should always use `LazyResult#then`.
128
130
  */
129
131
  get root(): Root
130
132
 
@@ -135,8 +137,7 @@ export default class LazyResult implements PromiseLike<Result> {
135
137
  * This property will only work with synchronous plugins. If the processor
136
138
  * contains any asynchronous plugins it will throw an error.
137
139
  *
138
- * This is why this method is only for debug purpose,
139
- * you should always use `LazyResult#then`.
140
+ * PostCSS runners should always use `LazyResult#then`.
140
141
  */
141
142
  get messages(): Message[]
142
143
 
@@ -137,7 +137,7 @@ class LazyResult {
137
137
  }
138
138
 
139
139
  if (root && !root[my]) {
140
- // istanbul ignore next
140
+ /* c8 ignore next 2 */
141
141
  Container.rebuild(root)
142
142
  }
143
143
  }
@@ -364,6 +364,7 @@ class LazyResult {
364
364
  let b = runtimeVer.split('.')
365
365
 
366
366
  if (a[0] !== b[0] || parseInt(a[1]) > parseInt(b[1])) {
367
+ // eslint-disable-next-line no-console
367
368
  console.error(
368
369
  'Unknown error from PostCSS plugin. Your current PostCSS ' +
369
370
  'version is ' +
@@ -378,7 +379,8 @@ class LazyResult {
378
379
  }
379
380
  }
380
381
  } catch (err) {
381
- // istanbul ignore next
382
+ /* c8 ignore next 3 */
383
+ // eslint-disable-next-line no-console
382
384
  if (console && console.error) console.error(err)
383
385
  }
384
386
  return error
@@ -4,15 +4,18 @@ let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
4
4
  let { dirname, resolve, relative, sep } = require('path')
5
5
  let { pathToFileURL } = require('url')
6
6
 
7
+ let Input = require('./input')
8
+
7
9
  let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator)
8
10
  let pathAvailable = Boolean(dirname && resolve && relative && sep)
9
11
 
10
12
  class MapGenerator {
11
- constructor(stringify, root, opts) {
13
+ constructor(stringify, root, opts, cssString) {
12
14
  this.stringify = stringify
13
15
  this.mapOpts = opts.map || {}
14
16
  this.root = root
15
17
  this.opts = opts
18
+ this.css = cssString
16
19
  }
17
20
 
18
21
  isMap() {
@@ -25,14 +28,19 @@ class MapGenerator {
25
28
  previous() {
26
29
  if (!this.previousMaps) {
27
30
  this.previousMaps = []
28
- this.root.walk(node => {
29
- if (node.source && node.source.input.map) {
30
- let map = node.source.input.map
31
- if (!this.previousMaps.includes(map)) {
32
- this.previousMaps.push(map)
31
+ if (this.root) {
32
+ this.root.walk(node => {
33
+ if (node.source && node.source.input.map) {
34
+ let map = node.source.input.map
35
+ if (!this.previousMaps.includes(map)) {
36
+ this.previousMaps.push(map)
37
+ }
33
38
  }
34
- }
35
- })
39
+ })
40
+ } else {
41
+ let input = new Input(this.css, this.opts)
42
+ if (input.map) this.previousMaps.push(input.map)
43
+ }
36
44
  }
37
45
 
38
46
  return this.previousMaps
@@ -67,30 +75,41 @@ class MapGenerator {
67
75
  clearAnnotation() {
68
76
  if (this.mapOpts.annotation === false) return
69
77
 
70
- let node
71
- for (let i = this.root.nodes.length - 1; i >= 0; i--) {
72
- node = this.root.nodes[i]
73
- if (node.type !== 'comment') continue
74
- if (node.text.indexOf('# sourceMappingURL=') === 0) {
75
- this.root.removeChild(i)
78
+ if (this.root) {
79
+ let node
80
+ for (let i = this.root.nodes.length - 1; i >= 0; i--) {
81
+ node = this.root.nodes[i]
82
+ if (node.type !== 'comment') continue
83
+ if (node.text.indexOf('# sourceMappingURL=') === 0) {
84
+ this.root.removeChild(i)
85
+ }
76
86
  }
87
+ } else if (this.css) {
88
+ this.css = this.css.replace(/(\n)?\/\*#[\S\s]*?\*\/$/gm, '')
77
89
  }
78
90
  }
79
91
 
80
92
  setSourcesContent() {
81
93
  let already = {}
82
- this.root.walk(node => {
83
- if (node.source) {
84
- let from = node.source.input.from
85
- if (from && !already[from]) {
86
- already[from] = true
87
- this.map.setSourceContent(
88
- this.toUrl(this.path(from)),
89
- node.source.input.css
90
- )
94
+ if (this.root) {
95
+ this.root.walk(node => {
96
+ if (node.source) {
97
+ let from = node.source.input.from
98
+ if (from && !already[from]) {
99
+ already[from] = true
100
+ this.map.setSourceContent(
101
+ this.toUrl(this.path(from)),
102
+ node.source.input.css
103
+ )
104
+ }
91
105
  }
92
- }
93
- })
106
+ })
107
+ } else if (this.css) {
108
+ let from = this.opts.from
109
+ ? this.toUrl(this.path(this.opts.from))
110
+ : '<no source>'
111
+ this.map.setSourceContent(from, this.css)
112
+ }
94
113
  }
95
114
 
96
115
  applyPrevMaps() {
@@ -129,7 +148,6 @@ class MapGenerator {
129
148
  if (Buffer) {
130
149
  return Buffer.from(str).toString('base64')
131
150
  } else {
132
- // istanbul ignore next
133
151
  return window.btoa(unescape(encodeURIComponent(str)))
134
152
  }
135
153
  }
@@ -147,7 +165,6 @@ class MapGenerator {
147
165
  } else {
148
166
  content = this.outputFile() + '.map'
149
167
  }
150
-
151
168
  let eol = '\n'
152
169
  if (this.css.includes('\r\n')) eol = '\r\n'
153
170
 
@@ -157,23 +174,38 @@ class MapGenerator {
157
174
  outputFile() {
158
175
  if (this.opts.to) {
159
176
  return this.path(this.opts.to)
160
- }
161
- if (this.opts.from) {
177
+ } else if (this.opts.from) {
162
178
  return this.path(this.opts.from)
179
+ } else {
180
+ return 'to.css'
163
181
  }
164
- return 'to.css'
165
182
  }
166
183
 
167
184
  generateMap() {
168
- this.generateString()
185
+ if (this.root) {
186
+ this.generateString()
187
+ } else if (this.previous().length === 1) {
188
+ let prev = this.previous()[0].consumer()
189
+ prev.file = this.outputFile()
190
+ this.map = SourceMapGenerator.fromSourceMap(prev)
191
+ } else {
192
+ this.map = new SourceMapGenerator({ file: this.outputFile() })
193
+ this.map.addMapping({
194
+ source: this.opts.from ? this.toUrl(this.opts.from) : '<no source>',
195
+ generated: { line: 1, column: 0 },
196
+ original: { line: 1, column: 0 }
197
+ })
198
+ }
199
+
169
200
  if (this.isSourcesContent()) this.setSourcesContent()
170
- if (this.previous().length > 0) this.applyPrevMaps()
201
+ if (this.root && this.previous().length > 0) this.applyPrevMaps()
171
202
  if (this.isAnnotation()) this.addAnnotation()
172
203
 
173
204
  if (this.isInline()) {
174
205
  return [this.css]
206
+ } else {
207
+ return [this.css, this.map]
175
208
  }
176
- return [this.css, this.map]
177
209
  }
178
210
 
179
211
  path(file) {
@@ -193,7 +225,6 @@ class MapGenerator {
193
225
 
194
226
  toUrl(path) {
195
227
  if (sep === '\\') {
196
- // istanbul ignore next
197
228
  path = path.replace(/\\/g, '/')
198
229
  }
199
230
  return encodeURI(path).replace(/[#?]/g, encodeURIComponent)
@@ -206,7 +237,6 @@ class MapGenerator {
206
237
  if (pathToFileURL) {
207
238
  return pathToFileURL(node.source.input.from).toString()
208
239
  } else {
209
- // istanbul ignore next
210
240
  throw new Error(
211
241
  '`map.absolute` option is not available in this PostCSS build'
212
242
  )
@@ -284,16 +314,15 @@ class MapGenerator {
284
314
 
285
315
  generate() {
286
316
  this.clearAnnotation()
287
-
288
317
  if (pathAvailable && sourceMapAvailable && this.isMap()) {
289
318
  return this.generateMap()
319
+ } else {
320
+ let result = ''
321
+ this.stringify(this.root, i => {
322
+ result += i
323
+ })
324
+ return [result]
290
325
  }
291
-
292
- let result = ''
293
- this.stringify(this.root, i => {
294
- result += i
295
- })
296
- return [result]
297
326
  }
298
327
  }
299
328
 
@@ -0,0 +1,37 @@
1
+ import Result, { Message, ResultOptions } from './result.js'
2
+ import { SourceMap } from './postcss.js'
3
+ import Processor from './processor.js'
4
+ import Warning from './warning.js'
5
+ import Root from './root.js'
6
+ import LazyResult from './lazy-result.js'
7
+
8
+ /**
9
+ * A Promise proxy for the result of PostCSS transformations.
10
+ * This lazy result instance doesn't parse css unless `NoWorkResult#root` or `Result#root`
11
+ * are accessed. See the example below for details.
12
+ * A `NoWork` instance is returned by `Processor#process` ONLY when no plugins defined.
13
+ *
14
+ * ```js
15
+ * const noWorkResult = postcss().process(css) // No plugins are defined.
16
+ * // CSS is not parsed
17
+ * let root = noWorkResult.root // now css is parsed because we accessed the root
18
+ * ```
19
+ */
20
+ export default class NoWorkResult implements LazyResult {
21
+ then: Promise<Result>['then']
22
+ catch: Promise<Result>['catch']
23
+ finally: Promise<Result>['finally']
24
+ constructor(processor: Processor, css: string, opts: ResultOptions)
25
+ get [Symbol.toStringTag](): string
26
+ get processor(): Processor
27
+ get opts(): ResultOptions
28
+ get css(): string
29
+ get content(): string
30
+ get map(): SourceMap
31
+ get root(): Root
32
+ get messages(): Message[]
33
+ warnings(): Warning[]
34
+ toString(): string
35
+ sync(): Result
36
+ async(): Promise<Result>
37
+ }