From 0da39ed2c53d1cefd271069da6e5a29028ac2c0c Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Wed, 25 Dec 2024 12:22:48 +0100 Subject: [PATCH 1/3] Add `Input#document` --- lib/input.d.ts | 10 ++++++ lib/input.js | 3 ++ lib/node.js | 16 ++++----- lib/postcss.d.ts | 7 +++- test/node.test.ts | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 9 deletions(-) diff --git a/lib/input.d.ts b/lib/input.d.ts index 46ded0985..856034008 100644 --- a/lib/input.d.ts +++ b/lib/input.d.ts @@ -62,6 +62,16 @@ declare class Input_ { */ css: string + /** + * Input source with support for non-CSS documents. + * + * ```js + * const input = postcss.parse('a{}', { from: file }).input + * input.document //=> "a{}" + * ``` + */ + document: string + /** * The absolute path to the CSS source file defined * with the `from` option. diff --git a/lib/input.js b/lib/input.js index 685bce74b..0306efc3a 100644 --- a/lib/input.js +++ b/lib/input.js @@ -33,6 +33,9 @@ class Input { this.hasBOM = false } + this.document = this.css + if (opts.document) this.document = opts.document.toString() + if (opts.from) { if ( !pathAvailable || diff --git a/lib/node.js b/lib/node.js index 9949be78a..5ef99c938 100644 --- a/lib/node.js +++ b/lib/node.js @@ -209,9 +209,9 @@ class Node { if (opts.index) { pos = this.positionInside(opts.index) } else if (opts.word) { - let stringRepresentation = this.source.input.css.slice( - sourceOffset(this.source.input.css, this.source.start), - sourceOffset(this.source.input.css, this.source.end) + 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) @@ -222,11 +222,11 @@ class Node { positionInside(index) { let column = this.source.start.column let line = this.source.start.line - let offset = sourceOffset(this.source.input.css, this.source.start) + let offset = sourceOffset(this.source.input.document, this.source.start) let end = offset + index for (let i = offset; i < end; i++) { - if (this.source.input.css[i] === '\n') { + if (this.source.input.document[i] === '\n') { column = 1 line += 1 } else { @@ -259,9 +259,9 @@ class Node { } if (opts.word) { - let stringRepresentation = this.source.input.css.slice( - sourceOffset(this.source.input.css, this.source.start), - sourceOffset(this.source.input.css, this.source.end) + 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) { diff --git a/lib/postcss.d.ts b/lib/postcss.d.ts index d0b8b53d5..899c4084f 100644 --- a/lib/postcss.d.ts +++ b/lib/postcss.d.ts @@ -229,7 +229,7 @@ declare namespace postcss { export interface Parser { ( css: { toString(): string } | string, - opts?: Pick + opts?: Pick ): RootNode } @@ -315,6 +315,11 @@ declare namespace postcss { } export interface ProcessOptions { + /** + * The enclosing document for one or more CSS blocks. + */ + document?: string + /** * The path of the CSS source file. You should always set `from`, * because it is used in source map generation and syntax error messages. diff --git a/test/node.test.ts b/test/node.test.ts index f12e5591f..7da164d1e 100755 --- a/test/node.test.ts +++ b/test/node.test.ts @@ -427,6 +427,32 @@ test('positionInside() returns position after AST mutations', () => { equal(two.positionInside(1), { column: 3, line: 3 }) }) +test('positionInside() supports multi-root documents', () => { + let root = parse('a {} b {}', { document: '' }) + is(root.source?.input.css, 'a {} b {}') + is(root.source?.input.document, '') + + let a = root.first as Rule + + // Offset the source location of `a` to mimic syntaxes like `postcss-html` + a.source = { + end: { + column: 12, + line: 1, + offset: 12 + }, + input: a.source!.input, + start: { + column: 8, + line: 1, + offset: 7 + } + } + + equal(a.positionInside(0), { column: 8, line: 1 }) + equal(a.positionInside(1), { column: 9, line: 1 }) +}) + test('positionBy() returns position for word', () => { let css = parse('a { one: X }') let a = css.first as Rule @@ -473,6 +499,33 @@ test('positionBy() returns position for index after AST mutations', () => { equal(two.positionBy({ index: 1 }), { column: 3, line: 3 }) }) +test('positionBy() supports multi-root documents', () => { + let root = parse('a {} b {}', { document: '' }) + is(root.source?.input.css, 'a {} b {}') + is(root.source?.input.document, '') + + let a = root.first as Rule + + // Offset the source location of `a` to mimic syntaxes like `postcss-html` + a.source = { + end: { + column: 12, + line: 1, + offset: 12 + }, + input: a.source!.input, + start: { + column: 8, + line: 1, + offset: 7 + } + } + + equal(a.positionBy({ index: 0 }), { column: 8, line: 1, offset: 7 }) // `offset` is present because the `0` index returns `source.start` + equal(a.positionBy({ index: 1 }), { column: 9, line: 1 }) + equal(a.positionBy({ word: 'a' }), { column: 8, line: 1 }) +}) + test('rangeBy() returns range for word', () => { let css = parse('a { one: X }') let a = css.first as Rule @@ -615,4 +668,37 @@ test('rangeBy() returns range for index and endIndex after AST mutations', () => }) }) +test('rangeBy() supports multi-root documents', () => { + let root = parse('a {} b {}', { document: '' }) + is(root.source?.input.css, 'a {} b {}') + is(root.source?.input.document, '') + + let a = root.first as Rule + + // Offset the source location of `a` to mimic syntaxes like `postcss-html` + a.source = { + end: { + column: 12, + line: 1, + offset: 12 + }, + input: a.source!.input, + start: { + column: 8, + line: 1, + offset: 7 + } + } + + equal(a.rangeBy({ endIndex: 1, index: 0 }), { + end: { column: 9, line: 1 }, + start: { column: 8, line: 1 } + }) + + equal(a.rangeBy({ word: 'a' }), { + end: { column: 9, line: 1 }, + start: { column: 8, line: 1 } + }) +}) + test.run() From 9f44f3f7f1bfe88ff9e36ed3d48437d41eeed279 Mon Sep 17 00:00:00 2001 From: Romain Menke <11521496+romainmenke@users.noreply.github.com> Date: Thu, 26 Dec 2024 12:26:45 +0100 Subject: [PATCH 2/3] Update lib/postcss.d.ts Co-authored-by: Andrey Sitnik --- lib/postcss.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/postcss.d.ts b/lib/postcss.d.ts index 899c4084f..c5e36052e 100644 --- a/lib/postcss.d.ts +++ b/lib/postcss.d.ts @@ -316,7 +316,7 @@ declare namespace postcss { export interface ProcessOptions { /** - * The enclosing document for one or more CSS blocks. + * Input file if it is not simple CSS file, but HTML with ' }).input + * input.document //=> "" + * input.css //=> "a{}" * ``` */ document: string