Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 1 | 'use strict' |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 2 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 3 | let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js') |
| 4 | let { fileURLToPath, pathToFileURL } = require('url') |
| 5 | let { resolve, isAbsolute } = require('path') |
| 6 | let { nanoid } = require('nanoid/non-secure') |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 7 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 8 | let terminalHighlight = require('./terminal-highlight') |
| 9 | let CssSyntaxError = require('./css-syntax-error') |
| 10 | let PreviousMap = require('./previous-map') |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 11 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 12 | let fromOffsetCache = Symbol('fromOffsetCache') |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 13 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 14 | let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator) |
| 15 | let pathAvailable = Boolean(resolve && isAbsolute) |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 16 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 17 | class Input { |
| 18 | constructor(css, opts = {}) { |
| 19 | if ( |
| 20 | css === null || |
| 21 | typeof css === 'undefined' || |
| 22 | (typeof css === 'object' && !css.toString) |
| 23 | ) { |
| 24 | throw new Error(`PostCSS received ${css} instead of CSS string`) |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 25 | } |
| 26 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 27 | this.css = css.toString() |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 28 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 29 | if (this.css[0] === '\uFEFF' || this.css[0] === '\uFFFE') { |
| 30 | this.hasBOM = true |
| 31 | this.css = this.css.slice(1) |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 32 | } else { |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 33 | this.hasBOM = false |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 34 | } |
| 35 | |
| 36 | if (opts.from) { |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 37 | if ( |
| 38 | !pathAvailable || |
| 39 | /^\w+:\/\//.test(opts.from) || |
| 40 | isAbsolute(opts.from) |
| 41 | ) { |
| 42 | this.file = opts.from |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 43 | } else { |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 44 | this.file = resolve(opts.from) |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 45 | } |
| 46 | } |
| 47 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 48 | if (pathAvailable && sourceMapAvailable) { |
| 49 | let map = new PreviousMap(this.css, opts) |
| 50 | if (map.text) { |
| 51 | this.map = map |
| 52 | let file = map.consumer().file |
| 53 | if (!this.file && file) this.file = this.mapResolve(file) |
| 54 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 55 | } |
| 56 | |
| 57 | if (!this.file) { |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 58 | this.id = '<input css ' + nanoid(6) + '>' |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 59 | } |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 60 | if (this.map) this.map.file = this.from |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 61 | } |
| 62 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 63 | fromOffset(offset) { |
| 64 | let lastLine, lineToIndex |
| 65 | if (!this[fromOffsetCache]) { |
| 66 | let lines = this.css.split('\n') |
| 67 | lineToIndex = new Array(lines.length) |
| 68 | let prevIndex = 0 |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 69 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 70 | for (let i = 0, l = lines.length; i < l; i++) { |
| 71 | lineToIndex[i] = prevIndex |
| 72 | prevIndex += lines[i].length + 1 |
| 73 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 74 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 75 | this[fromOffsetCache] = lineToIndex |
Tim van der Lippe | f2ea2c9 | 2021-11-08 10:55:56 +0000 | [diff] [blame] | 76 | } else { |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 77 | lineToIndex = this[fromOffsetCache] |
| 78 | } |
| 79 | lastLine = lineToIndex[lineToIndex.length - 1] |
| 80 | |
| 81 | let min = 0 |
| 82 | if (offset >= lastLine) { |
| 83 | min = lineToIndex.length - 1 |
| 84 | } else { |
| 85 | let max = lineToIndex.length - 2 |
| 86 | let mid |
| 87 | while (min < max) { |
| 88 | mid = min + ((max - min) >> 1) |
| 89 | if (offset < lineToIndex[mid]) { |
| 90 | max = mid - 1 |
| 91 | } else if (offset >= lineToIndex[mid + 1]) { |
| 92 | min = mid + 1 |
| 93 | } else { |
| 94 | min = mid |
| 95 | break |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | return { |
| 100 | line: min + 1, |
| 101 | col: offset - lineToIndex[min] + 1 |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | error(message, line, column, opts = {}) { |
| 106 | let result |
| 107 | if (!column) { |
| 108 | let pos = this.fromOffset(line) |
| 109 | line = pos.line |
| 110 | column = pos.col |
| 111 | } |
| 112 | let origin = this.origin(line, column) |
| 113 | if (origin) { |
| 114 | result = new CssSyntaxError( |
| 115 | message, |
| 116 | origin.line, |
| 117 | origin.column, |
| 118 | origin.source, |
| 119 | origin.file, |
| 120 | opts.plugin |
| 121 | ) |
| 122 | } else { |
| 123 | result = new CssSyntaxError( |
| 124 | message, |
| 125 | line, |
| 126 | column, |
| 127 | this.css, |
| 128 | this.file, |
| 129 | opts.plugin |
| 130 | ) |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 131 | } |
| 132 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 133 | result.input = { line, column, source: this.css } |
| 134 | if (this.file) { |
| 135 | if (pathToFileURL) { |
| 136 | result.input.url = pathToFileURL(this.file).toString() |
| 137 | } |
| 138 | result.input.file = this.file |
| 139 | } |
Tim van der Lippe | f2ea2c9 | 2021-11-08 10:55:56 +0000 | [diff] [blame] | 140 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 141 | return result |
| 142 | } |
| 143 | |
| 144 | origin(line, column) { |
| 145 | if (!this.map) return false |
| 146 | let consumer = this.map.consumer() |
| 147 | |
| 148 | let from = consumer.originalPositionFor({ line, column }) |
| 149 | if (!from.source) return false |
| 150 | |
| 151 | let fromUrl |
| 152 | |
| 153 | if (isAbsolute(from.source)) { |
| 154 | fromUrl = pathToFileURL(from.source) |
| 155 | } else { |
| 156 | fromUrl = new URL( |
| 157 | from.source, |
| 158 | this.map.consumer().sourceRoot || pathToFileURL(this.map.mapFile) |
| 159 | ) |
| 160 | } |
| 161 | |
| 162 | let result = { |
| 163 | url: fromUrl.toString(), |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 164 | line: from.line, |
| 165 | column: from.column |
Tim van der Lippe | 1e08ee8 | 2021-11-04 13:26:10 +0000 | [diff] [blame] | 166 | } |
Mathias Bynens | 79e2cf0 | 2020-05-29 16:46:17 +0200 | [diff] [blame] | 167 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 168 | if (fromUrl.protocol === 'file:') { |
| 169 | if (fileURLToPath) { |
| 170 | result.file = fileURLToPath(fromUrl) |
| 171 | } else { |
| 172 | // istanbul ignore next |
| 173 | throw new Error(`file: protocol is not available in this PostCSS build`) |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | let source = consumer.sourceContentFor(from.source) |
| 178 | if (source) result.source = source |
| 179 | |
| 180 | return result |
Tim van der Lippe | f2ea2c9 | 2021-11-08 10:55:56 +0000 | [diff] [blame] | 181 | } |
Tim van der Lippe | 1e08ee8 | 2021-11-04 13:26:10 +0000 | [diff] [blame] | 182 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 183 | mapResolve(file) { |
| 184 | if (/^\w+:\/\//.test(file)) { |
| 185 | return file |
Tim van der Lippe | f2ea2c9 | 2021-11-08 10:55:56 +0000 | [diff] [blame] | 186 | } |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 187 | return resolve(this.map.consumer().sourceRoot || this.map.root || '.', file) |
| 188 | } |
Alex Rudenko | 6c0f161 | 2021-11-05 06:28:44 +0000 | [diff] [blame] | 189 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 190 | get from() { |
| 191 | return this.file || this.id |
| 192 | } |
Tim van der Lippe | f2ea2c9 | 2021-11-08 10:55:56 +0000 | [diff] [blame] | 193 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 194 | toJSON() { |
| 195 | let json = {} |
| 196 | for (let name of ['hasBOM', 'css', 'file', 'id']) { |
| 197 | if (this[name] != null) { |
| 198 | json[name] = this[name] |
| 199 | } |
| 200 | } |
| 201 | if (this.map) { |
| 202 | json.map = { ...this.map } |
| 203 | if (json.map.consumerCache) { |
| 204 | json.map.consumerCache = undefined |
| 205 | } |
| 206 | } |
| 207 | return json |
| 208 | } |
| 209 | } |
Tim van der Lippe | 2b4a9df | 2021-11-08 12:58:12 +0000 | [diff] [blame] | 210 | |
Tim van der Lippe | 16b8228 | 2021-11-08 13:50:26 +0000 | [diff] [blame] | 211 | module.exports = Input |
| 212 | Input.default = Input |
| 213 | |
| 214 | if (terminalHighlight && terminalHighlight.registerInput) { |
| 215 | terminalHighlight.registerInput(Input) |
| 216 | } |