Visitar URL original
fix: preserve spaces on selector containers by Aschenbath · Pull Request #356 · postcss/postcss-selector-parser · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,13 @@ h1 h2 {}

Additional whitespace is found in either the `node.spaces.before` and `node.spaces.after` depending on the presence of comments or other whitespace characters. If the actual whitespace does not start or end with a single space, the node's raw value is set to the actual space(s) found in the source.

Spaces set on a `Selector` or `Root` container surround its serialized contents,
including when it has no children. They are added to any spaces on the children;
setting container spaces does not replace or move child spaces. For a `Root`,
the trailing space follows its trailing comma, if present. As with other nodes,
`raws.spaces.before` and `raws.spaces.after` take precedence over the corresponding
`spaces` values.

### `node.source`

An object describing the node's start/end, line/column source position.
Expand Down
68 changes: 68 additions & 0 deletions src/__tests__/container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,74 @@ import test from "./util/runner.mjs";
import parser from "../../dist/index.js";
import { parse } from "./util/helpers.mjs";

for (const type of ["selector", "root"]) {
test(`${type}#toString preserves container spaces before children are appended`, (t) => {
const container = parser[type]({ spaces: { before: " ", after: "\t" } });
t.is(container.toString(), " \t");
container.append(parser.tag({ value: "a" }));
t.is(container.toString(), " a\t");
t.is(container.first.toString(), "a");
});

test(`${type}#toString uses raw spaces in preference to container spaces`, (t) => {
const container = parser[type]({
nodes: [parser.tag({ value: "a" })],
spaces: { before: " ", after: " " },
});
container.rawSpaceBefore = "/*before*/\n";
container.rawSpaceAfter = "\t/*after*/";
t.is(container.toString(), "/*before*/\na\t/*after*/");
container.rawSpaceBefore = "";
container.rawSpaceAfter = "";
t.is(container.toString(), "a");
});

test(`${type}#toString includes both container and child spaces`, (t) => {
const container = parser[type]({
nodes: [parser.tag({ value: "a", spaces: { before: "\t", after: "\n" } })],
spaces: { before: " ", after: " " },
});
t.is(container.toString(), " \ta\n ");
const clone = container.clone({ spaces: { before: "\n", after: "\t" } });
t.is(clone.toString(), "\n\ta\n\t");
t.is(container.toString(), " \ta\n ");
});
}

test("pseudo#toString preserves spaces on each nested selector", (t) => {
const pseudo = parser.pseudo({
value: ":not",
nodes: ["span", "a"].map((value) =>
parser.selector({
nodes: [parser.tag({ value })],
spaces: { before: " ", after: " " },
}),
),
});
t.is(pseudo.toString(), ":not( span , a )");
});

test("root#toString places root spaces outside the selector list and trailing comma", (t) => {
const root = parser().astSync("a,b,");
root.spaces.before = "\n";
root.spaces.after = "\t";
root.first.spaces.before = " ";
root.last.spaces.after = " ";
t.is(root.toString(), "\n a,b ,\t");
});

test("replacing a pseudo with its selector preserves transferred spaces", (t) => {
const root = parser().astSync("a:global(.b)");
const pseudo = root.first.last;
pseudo.spaces.before = " ";
pseudo.spaces.after = "\t";
const expected = root.toString().replace(":global(", "").replace(")", "");
const replacement = pseudo.first.clone({ spaces: { ...pseudo.spaces } });
pseudo.replaceWith(replacement);
t.is(root.toString(), expected);
t.is(root.toString(), "a .b\t");
});

for (const method of ["insertBefore", "insertAfter"]) {
test(`container#${method} sets parents of every inserted node`, (t) => {
const selector = parser().astSync("a").first;
Expand Down
3 changes: 2 additions & 1 deletion src/selectors/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,8 @@ export default class Container extends Node {
}

_stringify(options, depth, max) {
return this.map((child) => this._stringifyChild(child, options, depth, max)).join("");
const str = this.map((child) => this._stringifyChild(child, options, depth, max)).join("");
return [this.rawSpaceBefore, str, this.rawSpaceAfter].join("");
}

// Serialize a child node. Historically `toString` used `this.map(String)`,
Expand Down
5 changes: 4 additions & 1 deletion src/selectors/root.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export default class Root extends Container {
memo.push(this._stringifyChild(selector, options, depth, max));
return memo;
}, []).join(",");
return this.trailingComma ? str + "," : str;
if (this.trailingComma) {
str += ",";
}
return [this.rawSpaceBefore, str, this.rawSpaceAfter].join("");
}

error(message, options) {
Expand Down
Loading