package.lib.input.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of postcss Show documentation
Show all versions of postcss Show documentation
Tool for transforming styles with JS plugins
'use strict'
let { nanoid } = require('nanoid/non-secure')
let { isAbsolute, resolve } = require('path')
let { SourceMapConsumer, SourceMapGenerator } = require('source-map-js')
let { fileURLToPath, pathToFileURL } = require('url')
let CssSyntaxError = require('./css-syntax-error')
let PreviousMap = require('./previous-map')
let terminalHighlight = require('./terminal-highlight')
let fromOffsetCache = Symbol('fromOffsetCache')
let sourceMapAvailable = Boolean(SourceMapConsumer && SourceMapGenerator)
let pathAvailable = Boolean(resolve && isAbsolute)
class Input {
constructor(css, opts = {}) {
if (
css === null ||
typeof css === 'undefined' ||
(typeof css === 'object' && !css.toString)
) {
throw new Error(`PostCSS received ${css} instead of CSS string`)
}
this.css = css.toString()
if (this.css[0] === '\uFEFF' || this.css[0] === '\uFFFE') {
this.hasBOM = true
this.css = this.css.slice(1)
} else {
this.hasBOM = false
}
if (opts.from) {
if (
!pathAvailable ||
/^\w+:\/\//.test(opts.from) ||
isAbsolute(opts.from)
) {
this.file = opts.from
} else {
this.file = resolve(opts.from)
}
}
if (pathAvailable && sourceMapAvailable) {
let map = new PreviousMap(this.css, opts)
if (map.text) {
this.map = map
let file = map.consumer().file
if (!this.file && file) this.file = this.mapResolve(file)
}
}
if (!this.file) {
this.id = ''
}
if (this.map) this.map.file = this.from
}
error(message, line, column, opts = {}) {
let endColumn, endLine, result
if (line && typeof line === 'object') {
let start = line
let end = column
if (typeof start.offset === 'number') {
let pos = this.fromOffset(start.offset)
line = pos.line
column = pos.col
} else {
line = start.line
column = start.column
}
if (typeof end.offset === 'number') {
let pos = this.fromOffset(end.offset)
endLine = pos.line
endColumn = pos.col
} else {
endLine = end.line
endColumn = end.column
}
} else if (!column) {
let pos = this.fromOffset(line)
line = pos.line
column = pos.col
}
let origin = this.origin(line, column, endLine, endColumn)
if (origin) {
result = new CssSyntaxError(
message,
origin.endLine === undefined
? origin.line
: { column: origin.column, line: origin.line },
origin.endLine === undefined
? origin.column
: { column: origin.endColumn, line: origin.endLine },
origin.source,
origin.file,
opts.plugin
)
} else {
result = new CssSyntaxError(
message,
endLine === undefined ? line : { column, line },
endLine === undefined ? column : { column: endColumn, line: endLine },
this.css,
this.file,
opts.plugin
)
}
result.input = { column, endColumn, endLine, line, source: this.css }
if (this.file) {
if (pathToFileURL) {
result.input.url = pathToFileURL(this.file).toString()
}
result.input.file = this.file
}
return result
}
fromOffset(offset) {
let lastLine, lineToIndex
if (!this[fromOffsetCache]) {
let lines = this.css.split('\n')
lineToIndex = new Array(lines.length)
let prevIndex = 0
for (let i = 0, l = lines.length; i < l; i++) {
lineToIndex[i] = prevIndex
prevIndex += lines[i].length + 1
}
this[fromOffsetCache] = lineToIndex
} else {
lineToIndex = this[fromOffsetCache]
}
lastLine = lineToIndex[lineToIndex.length - 1]
let min = 0
if (offset >= lastLine) {
min = lineToIndex.length - 1
} else {
let max = lineToIndex.length - 2
let mid
while (min < max) {
mid = min + ((max - min) >> 1)
if (offset < lineToIndex[mid]) {
max = mid - 1
} else if (offset >= lineToIndex[mid + 1]) {
min = mid + 1
} else {
min = mid
break
}
}
}
return {
col: offset - lineToIndex[min] + 1,
line: min + 1
}
}
mapResolve(file) {
if (/^\w+:\/\//.test(file)) {
return file
}
return resolve(this.map.consumer().sourceRoot || this.map.root || '.', file)
}
origin(line, column, endLine, endColumn) {
if (!this.map) return false
let consumer = this.map.consumer()
let from = consumer.originalPositionFor({ column, line })
if (!from.source) return false
let to
if (typeof endLine === 'number') {
to = consumer.originalPositionFor({ column: endColumn, line: endLine })
}
let fromUrl
if (isAbsolute(from.source)) {
fromUrl = pathToFileURL(from.source)
} else {
fromUrl = new URL(
from.source,
this.map.consumer().sourceRoot || pathToFileURL(this.map.mapFile)
)
}
let result = {
column: from.column,
endColumn: to && to.column,
endLine: to && to.line,
line: from.line,
url: fromUrl.toString()
}
if (fromUrl.protocol === 'file:') {
if (fileURLToPath) {
result.file = fileURLToPath(fromUrl)
} else {
/* c8 ignore next 2 */
throw new Error(`file: protocol is not available in this PostCSS build`)
}
}
let source = consumer.sourceContentFor(from.source)
if (source) result.source = source
return result
}
toJSON() {
let json = {}
for (let name of ['hasBOM', 'css', 'file', 'id']) {
if (this[name] != null) {
json[name] = this[name]
}
}
if (this.map) {
json.map = { ...this.map }
if (json.map.consumerCache) {
json.map.consumerCache = undefined
}
}
return json
}
get from() {
return this.file || this.id
}
}
module.exports = Input
Input.default = Input
if (terminalHighlight && terminalHighlight.registerInput) {
terminalHighlight.registerInput(Input)
}