2017-01-10 12:18:22 -05:00
|
|
|
"use strict";
|
2017-04-12 13:41:51 -07:00
|
|
|
|
2017-03-25 16:10:17 +01:00
|
|
|
const fs = require("fs");
|
2018-12-08 18:28:29 +08:00
|
|
|
const path = require("path");
|
2018-09-01 12:11:19 +08:00
|
|
|
const raw = require("jest-snapshot-serializer-raw").wrap;
|
2020-02-14 18:30:58 +08:00
|
|
|
const { isCI } = require("ci-info");
|
2016-12-23 19:31:38 +00:00
|
|
|
|
2020-03-24 23:09:16 +08:00
|
|
|
const { TEST_STANDALONE } = process.env;
|
2020-02-14 18:30:58 +08:00
|
|
|
const AST_COMPARE = isCI || process.env.AST_COMPARE;
|
2020-03-21 19:43:01 +08:00
|
|
|
const DEEP_COMPARE = isCI || process.env.DEEP_COMPARE;
|
2020-03-24 23:09:16 +08:00
|
|
|
const TEST_CRLF =
|
|
|
|
|
(isCI && process.platform === "win32") || process.env.TEST_CRLF;
|
2018-12-08 18:28:29 +08:00
|
|
|
|
|
|
|
|
const CURSOR_PLACEHOLDER = "<|>";
|
|
|
|
|
const RANGE_START_PLACEHOLDER = "<<<PRETTIER_RANGE_START>>>";
|
|
|
|
|
const RANGE_END_PLACEHOLDER = "<<<PRETTIER_RANGE_END>>>";
|
2018-06-12 15:05:40 -03:00
|
|
|
|
|
|
|
|
const prettier = !TEST_STANDALONE
|
|
|
|
|
? require("prettier/local")
|
|
|
|
|
: require("prettier/standalone");
|
2016-12-30 00:01:44 -05:00
|
|
|
|
2020-03-21 19:43:01 +08:00
|
|
|
// TODO: these test files need fix
|
|
|
|
|
const unstableTests = new Map(
|
|
|
|
|
[
|
2020-05-08 00:55:46 +08:00
|
|
|
"js/class-comment/comments.js",
|
|
|
|
|
["js/comments/dangling_array.js", (options) => options.semi === false],
|
|
|
|
|
["js/comments/jsx.js", (options) => options.semi === false],
|
|
|
|
|
"js/comments/binary-expressions-single-comments.js",
|
|
|
|
|
"js/comments/return-statement.js",
|
|
|
|
|
"js/comments/tagged-template-literal.js",
|
|
|
|
|
"js/comments-closure-typecast/iife.js",
|
2020-05-07 23:54:44 +08:00
|
|
|
"markdown/footnoteDefinition/multiline.md",
|
|
|
|
|
"markdown/spec/example-234.md",
|
|
|
|
|
"markdown/spec/example-235.md",
|
2020-05-07 23:05:21 +08:00
|
|
|
"html/multiparser-js/script-tag-escaping.html",
|
2020-03-21 19:43:01 +08:00
|
|
|
[
|
2020-05-08 00:55:46 +08:00
|
|
|
"js/multiparser-markdown/codeblock.js",
|
2020-03-21 22:07:52 +02:00
|
|
|
(options) => options.proseWrap === "always",
|
2020-03-21 19:43:01 +08:00
|
|
|
],
|
2020-05-08 00:55:46 +08:00
|
|
|
["js/no-semi/comments.js", (options) => options.semi === false],
|
2020-05-07 23:54:44 +08:00
|
|
|
"yaml/prettier-ignore/document.yml",
|
2020-03-21 22:07:52 +02:00
|
|
|
].map((fixture) => {
|
2020-03-21 19:43:01 +08:00
|
|
|
const [file, isUnstable = () => true] = Array.isArray(fixture)
|
|
|
|
|
? fixture
|
|
|
|
|
: [fixture];
|
|
|
|
|
return [path.join(__dirname, "../tests/", file), isUnstable];
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
2020-04-14 18:19:13 +08:00
|
|
|
const isTestDirectory = (dirname, name) =>
|
|
|
|
|
dirname.startsWith(path.join(__dirname, "../tests", name));
|
|
|
|
|
|
2020-04-21 15:57:58 +08:00
|
|
|
global.run_spec = (fixtures, parsers, options) => {
|
|
|
|
|
fixtures = typeof fixtures === "string" ? { dirname: fixtures } : fixtures;
|
|
|
|
|
const { dirname } = fixtures;
|
|
|
|
|
|
2020-02-03 07:33:19 +08:00
|
|
|
// `IS_PARSER_INFERENCE_TESTS` mean to test `inferParser` on `standalone`
|
2020-04-14 18:19:13 +08:00
|
|
|
const IS_PARSER_INFERENCE_TESTS = isTestDirectory(
|
|
|
|
|
dirname,
|
|
|
|
|
"parser-inference"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// `IS_ERROR_TESTS` mean to watch errors like:
|
|
|
|
|
// - syntax parser hasn't supported yet
|
|
|
|
|
// - syntax errors that should throws
|
|
|
|
|
const IS_ERROR_TESTS = isTestDirectory(dirname, "errors");
|
|
|
|
|
|
2020-02-03 07:33:19 +08:00
|
|
|
if (IS_PARSER_INFERENCE_TESTS) {
|
|
|
|
|
parsers = [];
|
|
|
|
|
} else if (!parsers || !parsers.length) {
|
2017-11-30 03:31:52 -03:00
|
|
|
throw new Error(`No parsers were specified for ${dirname}`);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-21 15:57:58 +08:00
|
|
|
const snippets = (fixtures.snippets || []).map((test, index) => {
|
|
|
|
|
test = typeof test === "string" ? { code: test } : test;
|
|
|
|
|
return {
|
2020-04-25 09:23:34 +08:00
|
|
|
...test,
|
2020-04-21 15:57:58 +08:00
|
|
|
name: `snippet: ${test.name || `#${index}`}`,
|
|
|
|
|
};
|
|
|
|
|
});
|
2018-12-08 18:28:29 +08:00
|
|
|
|
2020-04-21 15:57:58 +08:00
|
|
|
const files = fs
|
|
|
|
|
.readdirSync(dirname, { withFileTypes: true })
|
|
|
|
|
.map((file) => {
|
|
|
|
|
const basename = file.name;
|
|
|
|
|
const filename = path.join(dirname, basename);
|
|
|
|
|
if (
|
|
|
|
|
path.extname(basename) === ".snap" ||
|
|
|
|
|
!file.isFile() ||
|
|
|
|
|
basename[0] === "." ||
|
|
|
|
|
basename === "jsfmt.spec.js"
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-04-18 21:09:07 +08:00
|
|
|
|
2020-04-21 15:57:58 +08:00
|
|
|
const text = fs.readFileSync(filename, "utf8");
|
2020-04-18 21:09:07 +08:00
|
|
|
|
2020-04-21 15:57:58 +08:00
|
|
|
return {
|
|
|
|
|
name: basename,
|
|
|
|
|
filename,
|
|
|
|
|
code: text,
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter(Boolean);
|
|
|
|
|
|
|
|
|
|
const stringifiedOptions = stringifyOptions(options);
|
|
|
|
|
|
2020-04-25 09:23:34 +08:00
|
|
|
for (const { name, filename, code, output } of [...files, ...snippets]) {
|
2020-04-21 15:57:58 +08:00
|
|
|
describe(`${name}${
|
2020-04-18 21:09:07 +08:00
|
|
|
stringifiedOptions ? ` - ${stringifiedOptions}` : ""
|
|
|
|
|
}`, () => {
|
2020-04-10 19:12:01 +08:00
|
|
|
let rangeStart;
|
|
|
|
|
let rangeEnd;
|
|
|
|
|
let cursorOffset;
|
2018-06-12 15:05:40 -03:00
|
|
|
|
2020-04-21 15:57:58 +08:00
|
|
|
const source = (TEST_CRLF ? code.replace(/\n/g, "\r\n") : code)
|
2020-04-10 19:12:01 +08:00
|
|
|
.replace(RANGE_START_PLACEHOLDER, (match, offset) => {
|
|
|
|
|
rangeStart = offset;
|
|
|
|
|
return "";
|
|
|
|
|
})
|
|
|
|
|
.replace(RANGE_END_PLACEHOLDER, (match, offset) => {
|
|
|
|
|
rangeEnd = offset;
|
|
|
|
|
return "";
|
|
|
|
|
});
|
2018-12-08 18:28:29 +08:00
|
|
|
|
2020-04-10 19:12:01 +08:00
|
|
|
const input = source.replace(CURSOR_PLACEHOLDER, (match, offset) => {
|
|
|
|
|
cursorOffset = offset;
|
2018-05-01 13:42:59 +01:00
|
|
|
return "";
|
|
|
|
|
});
|
|
|
|
|
|
2020-04-10 19:12:01 +08:00
|
|
|
const baseOptions = {
|
|
|
|
|
printWidth: 80,
|
|
|
|
|
...options,
|
|
|
|
|
rangeStart,
|
|
|
|
|
rangeEnd,
|
|
|
|
|
cursorOffset,
|
|
|
|
|
};
|
|
|
|
|
const mainOptions = {
|
|
|
|
|
...baseOptions,
|
|
|
|
|
...(IS_PARSER_INFERENCE_TESTS
|
|
|
|
|
? { filepath: filename }
|
|
|
|
|
: { parser: parsers[0] }),
|
|
|
|
|
};
|
2018-12-08 18:28:29 +08:00
|
|
|
|
2020-04-10 19:12:01 +08:00
|
|
|
const hasEndOfLine = "endOfLine" in mainOptions;
|
2020-04-14 18:19:13 +08:00
|
|
|
|
|
|
|
|
if (IS_ERROR_TESTS) {
|
|
|
|
|
test("error test", () => {
|
|
|
|
|
expect(() => {
|
|
|
|
|
format(input, filename, mainOptions);
|
|
|
|
|
}).toThrowErrorMatchingSnapshot();
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-25 09:23:34 +08:00
|
|
|
const formattedWithCursor = format(input, filename, mainOptions);
|
|
|
|
|
const formatted = formattedWithCursor.replace(CURSOR_PLACEHOLDER, "");
|
|
|
|
|
const visualizedOutput = visualizeEndOfLine(formattedWithCursor);
|
2018-12-08 18:28:29 +08:00
|
|
|
|
2020-04-10 19:12:01 +08:00
|
|
|
test("format", () => {
|
|
|
|
|
expect(visualizedOutput).toEqual(
|
2020-04-25 09:23:34 +08:00
|
|
|
visualizeEndOfLine(consistentEndOfLine(formattedWithCursor))
|
2020-04-10 19:12:01 +08:00
|
|
|
);
|
2020-04-25 09:23:34 +08:00
|
|
|
if (typeof output === "string") {
|
|
|
|
|
expect(formatted).toEqual(output);
|
|
|
|
|
} else {
|
|
|
|
|
expect(
|
|
|
|
|
raw(
|
|
|
|
|
createSnapshot(
|
|
|
|
|
hasEndOfLine
|
|
|
|
|
? visualizeEndOfLine(
|
|
|
|
|
code
|
|
|
|
|
.replace(RANGE_START_PLACEHOLDER, "")
|
|
|
|
|
.replace(RANGE_END_PLACEHOLDER, "")
|
|
|
|
|
)
|
|
|
|
|
: source,
|
|
|
|
|
hasEndOfLine ? visualizedOutput : formattedWithCursor,
|
|
|
|
|
{ ...baseOptions, parsers }
|
|
|
|
|
)
|
2020-04-10 19:12:01 +08:00
|
|
|
)
|
2020-04-25 09:23:34 +08:00
|
|
|
).toMatchSnapshot();
|
|
|
|
|
}
|
2020-03-21 19:43:01 +08:00
|
|
|
});
|
|
|
|
|
|
2020-04-10 19:12:01 +08:00
|
|
|
const parsersToVerify = parsers.slice(1);
|
|
|
|
|
if (parsers.includes("typescript") && !parsers.includes("babel-ts")) {
|
|
|
|
|
parsersToVerify.push("babel-ts");
|
|
|
|
|
}
|
2020-03-18 21:35:13 +08:00
|
|
|
|
2020-04-10 19:12:01 +08:00
|
|
|
for (const parser of parsersToVerify) {
|
|
|
|
|
const verifyOptions = { ...baseOptions, parser };
|
|
|
|
|
|
|
|
|
|
test(`verify (${parser})`, () => {
|
|
|
|
|
if (
|
|
|
|
|
parser === "babel-ts" &&
|
|
|
|
|
options &&
|
|
|
|
|
(options.disableBabelTS === true ||
|
|
|
|
|
(Array.isArray(options.disableBabelTS) &&
|
2020-04-21 15:57:58 +08:00
|
|
|
options.disableBabelTS.includes(name)))
|
2020-04-10 19:12:01 +08:00
|
|
|
) {
|
|
|
|
|
expect(() => {
|
|
|
|
|
format(input, filename, verifyOptions);
|
|
|
|
|
}).toThrow(TEST_STANDALONE ? undefined : SyntaxError);
|
|
|
|
|
} else {
|
|
|
|
|
const verifyOutput = format(input, filename, verifyOptions);
|
|
|
|
|
expect(visualizeEndOfLine(verifyOutput)).toEqual(visualizedOutput);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-10 20:07:07 +08:00
|
|
|
const isUnstable = unstableTests.get(filename);
|
|
|
|
|
const isUnstableTest = isUnstable && isUnstable(options || {});
|
|
|
|
|
if (
|
|
|
|
|
DEEP_COMPARE &&
|
|
|
|
|
(formatted !== input || isUnstableTest) &&
|
|
|
|
|
typeof rangeStart === "undefined" &&
|
|
|
|
|
typeof rangeEnd === "undefined" &&
|
|
|
|
|
typeof cursorOffset === "undefined" &&
|
|
|
|
|
!TEST_CRLF
|
|
|
|
|
) {
|
|
|
|
|
test("second format", () => {
|
|
|
|
|
const secondOutput = format(formatted, filename, mainOptions);
|
|
|
|
|
if (isUnstableTest) {
|
|
|
|
|
// To keep eye on failed tests, this assert never supposed to pass,
|
|
|
|
|
// if it fails, just remove the file from `unstableTests`
|
2020-04-25 09:23:34 +08:00
|
|
|
expect(secondOutput).not.toEqual(formatted);
|
2020-04-10 20:07:07 +08:00
|
|
|
} else {
|
2020-04-25 09:23:34 +08:00
|
|
|
expect(secondOutput).toEqual(formatted);
|
2020-04-10 20:07:07 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-04-10 19:12:01 +08:00
|
|
|
|
2020-04-10 20:07:07 +08:00
|
|
|
if (AST_COMPARE && formatted !== input) {
|
|
|
|
|
test("compare AST", () => {
|
|
|
|
|
const { cursorOffset, ...parseOptions } = mainOptions;
|
|
|
|
|
const originalAst = parse(input, parseOptions);
|
|
|
|
|
const formattedAst = parse(formatted, parseOptions);
|
|
|
|
|
expect(originalAst).toEqual(formattedAst);
|
|
|
|
|
});
|
2020-04-10 19:12:01 +08:00
|
|
|
}
|
|
|
|
|
});
|
2020-01-21 19:00:37 +09:00
|
|
|
}
|
2018-12-08 18:28:29 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function parse(source, options) {
|
|
|
|
|
return prettier.__debug.parse(source, options, /* massage */ true).ast;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function format(source, filename, options) {
|
2020-01-19 07:23:17 +08:00
|
|
|
const result = prettier.formatWithCursor(source, {
|
|
|
|
|
filepath: filename,
|
2020-03-21 22:07:52 +02:00
|
|
|
...options,
|
2020-01-19 07:23:17 +08:00
|
|
|
});
|
2018-12-08 18:28:29 +08:00
|
|
|
|
|
|
|
|
return options.cursorOffset >= 0
|
|
|
|
|
? result.formatted.slice(0, result.cursorOffset) +
|
|
|
|
|
CURSOR_PLACEHOLDER +
|
|
|
|
|
result.formatted.slice(result.cursorOffset)
|
|
|
|
|
: result.formatted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function consistentEndOfLine(text) {
|
|
|
|
|
let firstEndOfLine;
|
2020-03-21 22:07:52 +02:00
|
|
|
return text.replace(/\r\n?|\n/g, (endOfLine) => {
|
2018-12-08 18:28:29 +08:00
|
|
|
if (!firstEndOfLine) {
|
|
|
|
|
firstEndOfLine = endOfLine;
|
|
|
|
|
}
|
|
|
|
|
return firstEndOfLine;
|
2016-12-23 19:31:38 +00:00
|
|
|
});
|
|
|
|
|
}
|
2017-12-29 08:25:11 +11:00
|
|
|
|
2018-12-08 18:28:29 +08:00
|
|
|
function visualizeEndOfLine(text) {
|
2020-03-21 22:07:52 +02:00
|
|
|
return text.replace(/\r\n?|\n/g, (endOfLine) => {
|
2018-12-08 18:28:29 +08:00
|
|
|
switch (endOfLine) {
|
|
|
|
|
case "\n":
|
|
|
|
|
return "<LF>\n";
|
|
|
|
|
case "\r\n":
|
|
|
|
|
return "<CRLF>\n";
|
|
|
|
|
case "\r":
|
|
|
|
|
return "<CR>\n";
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Unexpected end of line ${JSON.stringify(endOfLine)}`);
|
|
|
|
|
}
|
|
|
|
|
});
|
2017-03-25 16:10:17 +01:00
|
|
|
}
|
2018-11-25 16:21:14 +08:00
|
|
|
|
|
|
|
|
function createSnapshot(input, output, options) {
|
|
|
|
|
const separatorWidth = 80;
|
|
|
|
|
const printWidthIndicator =
|
|
|
|
|
options.printWidth > 0 && Number.isFinite(options.printWidth)
|
|
|
|
|
? " ".repeat(options.printWidth) + "| printWidth"
|
|
|
|
|
: [];
|
|
|
|
|
return []
|
|
|
|
|
.concat(
|
|
|
|
|
printSeparator(separatorWidth, "options"),
|
|
|
|
|
printOptions(
|
|
|
|
|
omit(
|
|
|
|
|
options,
|
2020-03-21 22:07:52 +02:00
|
|
|
(k) =>
|
2020-01-29 20:11:16 +08:00
|
|
|
k === "rangeStart" ||
|
|
|
|
|
k === "rangeEnd" ||
|
|
|
|
|
k === "cursorOffset" ||
|
|
|
|
|
k === "disableBabelTS"
|
2018-11-25 16:21:14 +08:00
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
printWidthIndicator,
|
|
|
|
|
printSeparator(separatorWidth, "input"),
|
|
|
|
|
input,
|
|
|
|
|
printSeparator(separatorWidth, "output"),
|
|
|
|
|
output,
|
|
|
|
|
printSeparator(separatorWidth)
|
|
|
|
|
)
|
|
|
|
|
.join("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function printSeparator(width, description) {
|
|
|
|
|
description = description || "";
|
|
|
|
|
const leftLength = Math.floor((width - description.length) / 2);
|
|
|
|
|
const rightLength = width - leftLength - description.length;
|
|
|
|
|
return "=".repeat(leftLength) + description + "=".repeat(rightLength);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function printOptions(options) {
|
|
|
|
|
const keys = Object.keys(options).sort();
|
2020-03-21 22:07:52 +02:00
|
|
|
return keys.map((key) => `${key}: ${stringify(options[key])}`).join("\n");
|
2018-11-25 16:21:14 +08:00
|
|
|
function stringify(value) {
|
|
|
|
|
return value === Infinity
|
|
|
|
|
? "Infinity"
|
|
|
|
|
: Array.isArray(value)
|
2020-03-21 22:07:52 +02:00
|
|
|
? `[${value.map((v) => JSON.stringify(v)).join(", ")}]`
|
2018-11-25 16:21:14 +08:00
|
|
|
: JSON.stringify(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function omit(obj, fn) {
|
|
|
|
|
return Object.keys(obj).reduce((reduced, key) => {
|
|
|
|
|
const value = obj[key];
|
|
|
|
|
if (!fn(key, value)) {
|
|
|
|
|
reduced[key] = value;
|
|
|
|
|
}
|
|
|
|
|
return reduced;
|
|
|
|
|
}, {});
|
|
|
|
|
}
|
2020-04-18 21:09:07 +08:00
|
|
|
|
|
|
|
|
function stringifyOptions(options) {
|
|
|
|
|
const string = JSON.stringify(options || {}, (key, value) =>
|
|
|
|
|
key === "disableBabelTS"
|
|
|
|
|
? undefined
|
|
|
|
|
: value === Infinity
|
|
|
|
|
? "Infinity"
|
|
|
|
|
: value
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return string === "{}" ? "" : string;
|
|
|
|
|
}
|