Files
test-repo/tests_config/run_spec.js
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

459 lines
12 KiB
JavaScript
Raw Normal View History

2017-01-10 12:18:22 -05:00
"use strict";
2017-04-12 13:41:51 -07:00
2020-06-26 20:30:21 +08:00
const { TEST_STANDALONE } = process.env;
const fs = require("fs");
2018-12-08 18:28:29 +08:00
const path = require("path");
2020-06-26 20:30:21 +08:00
const prettier = !TEST_STANDALONE
2020-09-18 14:05:29 +08:00
? require("prettier-local")
: require("prettier-standalone");
2020-05-08 10:56:09 +08:00
const checkParsers = require("./utils/check-parsers");
2020-05-28 09:29:21 +08:00
const createSnapshot = require("./utils/create-snapshot");
const visualizeEndOfLine = require("./utils/visualize-end-of-line");
const consistentEndOfLine = require("./utils/consistent-end-of-line");
const stringifyOptionsForTitle = require("./utils/stringify-options-for-title");
2016-12-23 19:31:38 +00:00
2020-09-04 23:43:41 +08:00
const { FULL_TEST } = process.env;
const BOM = "\uFEFF";
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>>>";
2020-03-21 19:43:01 +08:00
// TODO: these test files need fix
const unstableTests = new Map(
[
2020-05-24 21:53:29 +03:00
"js/class-comment/misc.js",
2020-03-21 19:43:01 +08:00
["js/comments/dangling_array.js", (options) => options.semi === false],
["js/comments/jsx.js", (options) => options.semi === false],
"js/comments/return-statement.js",
"js/comments/tagged-template-literal.js",
"markdown/spec/example-234.md",
"markdown/spec/example-235.md",
"html/multiparser/js/script-tag-escaping.html",
2020-03-21 19:43:01 +08:00
[
"js/multiparser-markdown/codeblock.js",
(options) => options.proseWrap === "always",
],
["js/no-semi/comments.js", (options) => options.semi === false],
["flow/no-semi/comments.js", (options) => options.semi === false],
"typescript/prettier-ignore/mapped-types.ts",
2020-11-05 14:08:37 +08:00
"js/comments/html-like/comment.js",
2020-03-21 19:43:01 +08:00
].map((fixture) => {
const [file, isUnstable = () => true] = Array.isArray(fixture)
? fixture
: [fixture];
return [path.join(__dirname, "../tests/", file), isUnstable];
})
);
// This is temporary.
// This will be removed once https://github.com/babel/babel/issues/12915 is fixed
const unstableAstTests = new Map(
[
[
"flow/function-parentheses/single.js",
(options) =>
options.parser.startsWith("babel") && options.arrowParens !== "avoid",
],
[
"flow/function-parentheses/test.js",
(options) => options.parser.startsWith("babel"),
],
].map((fixture) => {
const [file, isUnstable = () => true] = Array.isArray(fixture)
? fixture
: [fixture];
return [path.join(__dirname, "../tests/", file), isUnstable];
})
);
2020-10-13 09:14:02 +08:00
const espreeDisabledTests = new Set(
[
// These tests only work for `babel`
"comments-closure-typecast",
].map((directory) => path.join(__dirname, "../tests/js", directory))
);
2020-11-05 14:08:37 +08:00
const meriyahDisabledTests = espreeDisabledTests;
2020-10-13 09:14:02 +08:00
2020-08-10 22:17:51 +08:00
const isUnstable = (filename, options) => {
const testFunction = unstableTests.get(filename);
if (!testFunction) {
return false;
}
return testFunction(options);
};
const isAstUnstable = (filename, options) => {
const testFunction = unstableAstTests.get(filename);
if (!testFunction) {
return false;
}
return testFunction(options);
};
2020-09-04 23:43:41 +08:00
const shouldThrowOnFormat = (filename, options) => {
const { errors = {} } = options;
if (errors === true) {
return true;
}
2020-10-13 09:14:02 +08:00
const files = errors[options.parser];
2020-08-10 22:17:51 +08:00
2020-10-13 09:14:02 +08:00
if (files === true || (Array.isArray(files) && files.includes(filename))) {
2020-08-10 22:17:51 +08:00
return true;
}
return false;
};
2020-04-14 18:19:13 +08:00
const isTestDirectory = (dirname, name) =>
2020-10-13 09:14:02 +08:00
(dirname + path.sep).startsWith(
path.join(__dirname, "../tests", name) + path.sep
);
2020-04-14 18:19:13 +08:00
2020-08-28 10:02:08 +08:00
function runSpec(fixtures, parsers, options) {
2020-09-04 23:43:41 +08:00
let { dirname, snippets = [] } =
typeof fixtures === "string" ? { dirname: fixtures } : 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,
"misc/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, "misc/errors");
if (IS_ERROR_TESTS) {
options = { errors: true, ...options };
}
2020-04-14 18:19:13 +08:00
2021-02-25 19:28:06 +09:00
const IS_TYPESCRIPT_ONLY_TEST = isTestDirectory(
dirname,
"misc/typescript-only"
);
2020-02-03 07:33:19 +08:00
if (IS_PARSER_INFERENCE_TESTS) {
2020-09-04 23:43:41 +08:00
parsers = [undefined];
}
2020-09-04 23:43:41 +08:00
snippets = snippets.map((test, index) => {
test = typeof test === "string" ? { code: test } : test;
return {
2020-04-25 09:23:34 +08:00
...test,
name: `snippet: ${test.name || `#${index}`}`,
};
});
2018-12-08 18:28:29 +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
const text = fs.readFileSync(filename, "utf8");
2020-04-18 21:09:07 +08:00
return {
name: basename,
filename,
code: text,
};
})
.filter(Boolean);
2020-05-21 21:10:00 +08:00
// Make sure tests are in correct location
2020-09-04 23:43:41 +08:00
if (process.env.CHECK_TEST_PARSERS) {
if (!Array.isArray(parsers) || parsers.length === 0) {
2020-09-04 23:43:41 +08:00
throw new Error(`No parsers were specified for ${dirname}`);
}
2020-05-21 21:10:00 +08:00
checkParsers({ dirname, files }, parsers);
}
2020-09-04 23:43:41 +08:00
const [parser] = parsers;
const allParsers = [...parsers];
2020-11-05 14:08:37 +08:00
if (!IS_ERROR_TESTS) {
2021-02-25 19:28:06 +09:00
if (
parsers.includes("typescript") &&
!parsers.includes("babel-ts") &&
!IS_TYPESCRIPT_ONLY_TEST
) {
allParsers.push("babel-ts");
2020-11-05 14:08:37 +08:00
}
if (parsers.includes("babel") && isTestDirectory(dirname, "js")) {
if (!parsers.includes("espree") && !espreeDisabledTests.has(dirname)) {
allParsers.push("espree");
}
if (!parsers.includes("meriyah") && !meriyahDisabledTests.has(dirname)) {
allParsers.push("meriyah");
}
2020-11-05 14:08:37 +08:00
}
2020-10-13 09:14:02 +08:00
}
2020-09-04 23:43:41 +08:00
const stringifiedOptions = stringifyOptionsForTitle(options);
2020-04-25 09:23:34 +08:00
for (const { name, filename, code, output } of [...files, ...snippets]) {
2020-09-04 23:43:41 +08:00
const title = `${name}${
2020-04-18 21:09:07 +08:00
stringifiedOptions ? ` - ${stringifiedOptions}` : ""
2020-09-04 23:43:41 +08:00
}`;
describe(title, () => {
2020-08-10 22:17:51 +08:00
const formatOptions = {
2020-04-10 19:12:01 +08:00
printWidth: 80,
...options,
2020-08-10 22:17:51 +08:00
filepath: filename,
parser,
2020-04-10 19:12:01 +08:00
};
const mainParserFormatResult = shouldThrowOnFormat(name, formatOptions)
? { options: formatOptions, error: true }
: format(code, formatOptions);
2018-12-08 18:28:29 +08:00
2020-09-04 23:43:41 +08:00
for (const currentParser of allParsers) {
runTest({
parsers,
name,
filename,
code,
output,
parser: currentParser,
mainParserFormatResult,
mainParserFormatOptions: formatOptions,
});
}
});
}
}
2020-08-10 22:17:51 +08:00
2020-09-04 23:43:41 +08:00
function runTest({
parsers,
name,
filename,
code,
output,
parser,
mainParserFormatResult,
mainParserFormatOptions,
}) {
let formatOptions = mainParserFormatOptions;
let formatResult = mainParserFormatResult;
let formatTestTitle = "format";
2020-08-10 22:17:51 +08:00
// Verify parsers or error tests
if (
mainParserFormatResult.error ||
mainParserFormatOptions.parser !== parser
) {
2020-11-06 15:27:04 +08:00
formatTestTitle = `[${parser}] format`;
2020-09-04 23:43:41 +08:00
formatOptions = { ...mainParserFormatResult.options, parser };
const runFormat = () => format(code, formatOptions);
2020-08-10 22:17:51 +08:00
2020-09-04 23:43:41 +08:00
if (shouldThrowOnFormat(name, formatOptions)) {
2020-11-06 15:27:04 +08:00
test(formatTestTitle, () => {
expect(runFormat).toThrowErrorMatchingSnapshot();
2020-03-21 19:43:01 +08:00
});
2020-09-04 23:43:41 +08:00
return;
}
2020-03-21 19:43:01 +08:00
2020-09-04 23:43:41 +08:00
// Verify parsers format result should be the same as main parser
output = mainParserFormatResult.outputWithCursor;
formatResult = runFormat();
}
2020-04-10 19:12:01 +08:00
2020-09-04 23:43:41 +08:00
test(formatTestTitle, () => {
// Make sure output has consistent EOL
expect(formatResult.eolVisualizedOutput).toEqual(
visualizeEndOfLine(consistentEndOfLine(formatResult.outputWithCursor))
);
2020-08-10 22:17:51 +08:00
2020-09-04 23:43:41 +08:00
// The result is assert to equals to `output`
if (typeof output === "string") {
expect(formatResult.eolVisualizedOutput).toEqual(
visualizeEndOfLine(output)
);
return;
}
// All parsers have the same result, only snapshot the result from main parser
expect(
createSnapshot(formatResult, {
parsers,
formatOptions,
CURSOR_PLACEHOLDER,
})
2020-09-04 23:43:41 +08:00
).toMatchSnapshot();
});
if (!FULL_TEST) {
return;
}
const isUnstableTest = isUnstable(filename, formatOptions);
if (
(formatResult.changed || isUnstableTest) &&
// No range and cursor
formatResult.input === code
) {
test(`[${parser}] second format`, () => {
const { eolVisualizedOutput: firstOutput, output } = formatResult;
const { eolVisualizedOutput: secondOutput } = format(
output,
formatOptions
);
if (isUnstableTest) {
// To keep eye on failed tests, this assert never supposed to pass,
// if it fails, just remove the file from `unstableTests`
expect(secondOutput).not.toEqual(firstOutput);
} else {
expect(secondOutput).toEqual(firstOutput);
2020-04-10 19:12:01 +08:00
}
2020-09-04 23:43:41 +08:00
});
}
2020-04-10 19:12:01 +08:00
const isAstUnstableTest = isAstUnstable(filename, formatOptions);
2020-09-04 23:43:41 +08:00
// Some parsers skip parsing empty files
if (formatResult.changed && code.trim()) {
test(`[${parser}] compare AST`, () => {
const { input, output } = formatResult;
const originalAst = parse(input, formatOptions);
const formattedAst = parse(output, formatOptions);
if (isAstUnstableTest) {
expect(formattedAst).not.toEqual(originalAst);
} else {
expect(formattedAst).toEqual(originalAst);
}
2020-09-04 23:43:41 +08:00
});
}
2020-04-10 19:12:01 +08:00
2020-11-30 12:29:48 +08:00
if (!shouldSkipEolTest(code, formatResult.options)) {
2020-09-17 07:35:33 +08:00
for (const eol of ["\r\n", "\r"]) {
2020-09-04 23:43:41 +08:00
test(`[${parser}] EOL ${JSON.stringify(eol)}`, () => {
const output = format(code.replace(/\n/g, eol), formatOptions)
.eolVisualizedOutput;
// Only if `endOfLine: "auto"` the result will be different
const expected =
formatOptions.endOfLine === "auto"
? visualizeEndOfLine(
// All `code` use `LF`, so the `eol` of result is always `LF`
formatResult.outputWithCursor.replace(/\n/g, eol)
)
: formatResult.eolVisualizedOutput;
expect(output).toEqual(expected);
});
}
}
if (code.charAt(0) !== BOM) {
test(`[${parser}] BOM`, () => {
const output = format(BOM + code, formatOptions).eolVisualizedOutput;
const expected = BOM + formatResult.eolVisualizedOutput;
expect(output).toEqual(expected);
2020-04-10 19:12:01 +08:00
});
}
2020-08-28 10:02:08 +08:00
}
2018-12-08 18:28:29 +08:00
2020-11-30 12:29:48 +08:00
function shouldSkipEolTest(code, options) {
if (code.includes("\r")) {
return true;
}
const { requirePragma, rangeStart, rangeEnd } = options;
if (requirePragma) {
return true;
}
if (
typeof rangeStart === "number" &&
typeof rangeEnd === "number" &&
rangeStart >= rangeEnd
) {
return true;
}
return false;
}
2018-12-08 18:28:29 +08:00
function parse(source, options) {
return prettier.__debug.parse(source, options, /* massage */ true).ast;
}
2020-09-17 07:31:29 +08:00
const indexProperties = [
{
property: "cursorOffset",
placeholder: CURSOR_PLACEHOLDER,
},
{
property: "rangeStart",
placeholder: RANGE_START_PLACEHOLDER,
},
{
property: "rangeEnd",
placeholder: RANGE_END_PLACEHOLDER,
},
];
function replacePlaceholders(originalText, originalOptions) {
const indexes = indexProperties
.map(({ property, placeholder }) => {
const value = originalText.indexOf(placeholder);
return value === -1 ? undefined : { property, value, placeholder };
2020-08-10 22:17:51 +08:00
})
2020-09-17 07:31:29 +08:00
.filter(Boolean)
.sort((a, b) => a.value - b.value);
2020-08-10 22:17:51 +08:00
2020-09-17 07:31:29 +08:00
const options = { ...originalOptions };
let text = originalText;
let offset = 0;
for (const { property, value, placeholder } of indexes) {
text = text.replace(placeholder, "");
options[property] = value + offset;
offset -= placeholder.length;
}
return { text, options };
}
2018-12-08 18:28:29 +08:00
2020-09-17 07:31:29 +08:00
const insertCursor = (text, cursorOffset) =>
cursorOffset >= 0
? text.slice(0, cursorOffset) +
CURSOR_PLACEHOLDER +
text.slice(cursorOffset)
: text;
function format(originalText, originalOptions) {
const { text: input, options } = replacePlaceholders(
originalText,
originalOptions
);
const inputWithCursor = insertCursor(input, options.cursorOffset);
2020-08-10 22:17:51 +08:00
2020-09-17 07:31:29 +08:00
const { formatted: output, cursorOffset } = prettier.formatWithCursor(
input,
options
);
const outputWithCursor = insertCursor(output, cursorOffset);
2020-08-10 22:17:51 +08:00
const eolVisualizedOutput = visualizeEndOfLine(outputWithCursor);
const changed = outputWithCursor !== inputWithCursor;
return {
changed,
options,
input,
inputWithCursor,
output,
outputWithCursor,
eolVisualizedOutput,
};
2018-12-08 18:28:29 +08:00
}
2020-08-28 10:02:08 +08:00
module.exports = runSpec;