Syntaxes like `postcss-html` adjust node source locations relative to enclosing non-css documents
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 == 0line == 1andcolumn == 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.
| 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 | |
| } |
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 }