Add a new `trim` command to trim whitespaces in the current line (#4772)

master
Warren Seine 2018-10-06 14:18:52 +02:00 committed by Ika
parent 30ec30daa9
commit 7920e6fdde
6 changed files with 152 additions and 18 deletions

View File

@ -237,6 +237,14 @@ declare function dedentToRoot(doc: Doc): Doc;
This will dedent the current indentation to the root marked by `markAsRoot`.
### trim
```ts
declare var trim: Doc;
```
This will trim any whitespace or tab character on the current line. This is used for preprocessor directives.
### cursor
```ts

View File

@ -113,6 +113,7 @@ function lineSuffix(contents) {
const lineSuffixBoundary = { type: "line-suffix-boundary" };
const breakParent = { type: "break-parent" };
const trim = { type: "trim" };
const line = { type: "line" };
const softline = { type: "line", soft: true };
const hardline = concat([{ type: "line", hard: true }, breakParent]);
@ -167,6 +168,7 @@ module.exports = {
cursor,
breakParent,
ifBreak,
trim,
indent,
align,
addAlignmentToDoc,

View File

@ -59,6 +59,10 @@ function printDoc(doc) {
return "breakParent";
}
if (doc.type === "trim") {
return "trim";
}
if (doc.type === "concat") {
return "[" + doc.parts.map(printDoc).join(", ") + "]";
}

View File

@ -109,9 +109,37 @@ function generateInd(ind, newPart, options) {
}
}
function trim(out) {
if (out.length === 0) {
return 0;
}
let trimCount = 0;
// Trim whitespace at the end of line
while (
out.length > 0 &&
typeof out[out.length - 1] === "string" &&
out[out.length - 1].match(/^[ \t]*$/)
) {
trimCount += out.pop().length;
}
if (out.length && typeof out[out.length - 1] === "string") {
const trimmed = out[out.length - 1].replace(/[ \t]*$/, "");
trimCount += out[out.length - 1].length - trimmed.length;
out[out.length - 1] = trimmed;
}
return trimCount;
}
function fits(next, restCommands, width, options, mustBeFlat) {
let restIdx = restCommands.length;
const cmds = [next];
// `out` is only used for width counting because `trim` requires to look
// backwards for space characters.
const out = [];
while (width >= 0) {
if (cmds.length === 0) {
if (restIdx === 0) {
@ -130,6 +158,8 @@ function fits(next, restCommands, width, options, mustBeFlat) {
const doc = x[2];
if (typeof doc === "string") {
out.push(doc);
width -= getStringWidth(doc);
} else {
switch (doc.type) {
@ -146,6 +176,10 @@ function fits(next, restCommands, width, options, mustBeFlat) {
case "align":
cmds.push([makeAlign(ind, doc.n, options), mode, doc.contents]);
break;
case "trim":
width += trim(out);
break;
case "group":
if (mustBeFlat && doc.break) {
@ -184,6 +218,8 @@ function fits(next, restCommands, width, options, mustBeFlat) {
case MODE_FLAT:
if (!doc.hard) {
if (!doc.soft) {
out.push(" ");
width -= 1;
}
@ -244,6 +280,10 @@ function printDocToString(doc, options) {
case "align":
cmds.push([makeAlign(ind, doc.n, options), mode, doc.contents]);
break;
case "trim":
pos -= trim(out);
break;
case "group":
switch (mode) {
@ -471,24 +511,7 @@ function printDocToString(doc, options) {
pos = 0;
}
} else {
if (out.length > 0) {
// Trim whitespace at the end of line
while (
out.length > 0 &&
typeof out[out.length - 1] === "string" &&
out[out.length - 1].match(/^[ \t]*$/)
) {
out.pop();
}
if (out.length && typeof out[out.length - 1] === "string") {
out[out.length - 1] = out[out.length - 1].replace(
/[ \t]*$/,
""
);
}
}
pos -= trim(out);
out.push(newLine + ind.value);
pos = ind.length;
}

View File

@ -0,0 +1,38 @@
"use strict";
const prettier = require("prettier/local");
const docPrinter = prettier.doc.printer;
const docBuilders = prettier.doc.builders;
const printDocToString = docPrinter.printDocToString;
const concat = docBuilders.concat;
const hardline = docBuilders.hardline;
const literalline = docBuilders.literalline;
const trim = docBuilders.trim;
const indent = docBuilders.indent;
const markAsRoot = docBuilders.markAsRoot;
describe("markAsRoot", () => {
test.each([
[
"with hardline will insert a newline with current indentation",
concat([indent(markAsRoot(indent(hardline))), "123"]),
"\n 123"
],
[
"with literalline will insert a newline with root indentation",
concat([indent(markAsRoot(indent(literalline))), "123"]),
"\n 123"
],
[
"followed by trim will trims up to the the first column, ignoring indented root",
concat([indent(markAsRoot(indent(literalline))), trim, "123"]),
"\n123"
]
])("%s", (_, doc, expected) => {
const result = printDocToString(doc, { printWidth: 80, tabWidth: 2 });
expect(result).toBeDefined();
expect(result.formatted).toEqual(expected);
});
});

View File

@ -0,0 +1,59 @@
"use strict";
const prettier = require("prettier/local");
const docPrinter = prettier.doc.printer;
const docBuilders = prettier.doc.builders;
const printDocToString = docPrinter.printDocToString;
const concat = docBuilders.concat;
const line = docBuilders.line;
const trim = docBuilders.trim;
const group = docBuilders.group;
const indent = docBuilders.indent;
// These tests don't use `runPrettier` because `trim` is not used by any
// bundled parser (only third-party plugins).
describe("trim", () => {
test.each([
["trims the current line", group(concat(["hello ", trim])), "hello"],
[
"trims existing indentation",
group(
concat([
"function()",
line,
"{",
indent(
concat([
line,
group(concat([trim, "#if DEBUG"])),
line,
"alert(42);",
line,
group(concat([trim, "#endif"]))
])
),
line,
"}"
])
),
`function()
{
#if DEBUG
alert(42);
#endif
}`
],
[
"ignores trimmed characters when fitting the line",
group(concat(["hello ", " ", trim, line, "world!"])),
"hello world!"
]
])("%s", (_, doc, expected) => {
const result = printDocToString(doc, { printWidth: 12, tabWidth: 2 });
expect(result).toBeDefined();
expect(result.formatted).toEqual(expected);
});
});