Description
For example:
<p>Hello</p>
<style>
p {
color: green;
}
</style>
The p {}
rule when parsed with postcss-html
has source locations that are offset relative to the entire html doc.
So p {}
starts on line 4, not on line 1.
This is very useful when reporting errors (i.e. Stylelint) to developers in a code editors.
As the error will be reported on the line and column in the entire document.
However this unfortunately conflicts with the changes I introduced in #1980
Since those changes, we use node.source.input.css
to infer positions.
But we assume the start of node.source.input.css
corresponds with:
index == 0
line == 1
andcolumn == 1
Because postcss-html
offsets the positions relative to the enclosing document this assumptions proves to be incorrect, making any position calculations incorrect.
The simplest way forward I could think of was to add an extra field on Input
to keep track of both the source of the enclosing document and of the CSS block independently.
Lines 17 to 34 in 99da2f2
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
}
this.document = this.css
if (opts.document) this.document = opts.document.toString()
// ...
For almost all usage of PostCSS input.document
would correspond to input.css
as the document is the CSS stylesheet.
But for CSS-in-X
syntaxes (like postcss-html
) the syntax authors could set document
to the enclosing source (e.g. the html document).
When inferring positions we would use node.source.input.document
instead of node.source.input.css
in PostCSS itself.
positionBy(opts) {
let pos = this.source.start
if (opts.index) {
pos = this.positionInside(opts.index)
} else if (opts.word) {
let stringRepresentation = this.source.input.document.slice(
sourceOffset(this.source.input.document, this.source.start),
sourceOffset(this.source.input.document, this.source.end)
)
let index = stringRepresentation.indexOf(opts.word)
if (index !== -1) pos = this.positionInside(index)
}
return pos
}