543 lines
16 KiB
JavaScript
543 lines
16 KiB
JavaScript
import fs from "node:fs/promises";
|
||
import path from "node:path";
|
||
import url from "node:url";
|
||
import prettier from "../../config/prettier-entry.js";
|
||
|
||
test("resolves configuration from external files and overrides by extname", async () => {
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL("../cli/config/external-overrides/file.js", import.meta.url),
|
||
),
|
||
).resolves.toStrictEqual({ tabWidth: 3, semi: false });
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL("../cli/config/external-overrides/file.ts", import.meta.url),
|
||
),
|
||
).resolves.toStrictEqual({ tabWidth: 3, semi: true });
|
||
});
|
||
|
||
describe("accepts configuration from --config", () => {
|
||
runCli("cli/config/", ["--config", ".prettierrc", "./js/file.js"]).test({
|
||
status: 0,
|
||
});
|
||
});
|
||
|
||
describe("resolves external configuration from package.json (cjs package)", () => {
|
||
runCli("cli/config/external-config/cjs-package", ["index.js"]).test({
|
||
status: 0,
|
||
stderr: "",
|
||
write: [],
|
||
});
|
||
});
|
||
|
||
describe("resolves external configuration from package.json (esm package)", () => {
|
||
runCli("cli/config/external-config/esm-package", ["index.js"]).test({
|
||
status: 0,
|
||
stderr: "",
|
||
write: [],
|
||
});
|
||
});
|
||
|
||
describe("resolves external configuration from package.json (esm package with TLA)", () => {
|
||
runCli("cli/config/external-config/esm-package-with-tla", ["index.js"]).test({
|
||
status: 0,
|
||
stderr: "",
|
||
write: [],
|
||
});
|
||
});
|
||
|
||
describe("resolves external configuration from package.json (esm file)", () => {
|
||
runCli("cli/config/external-config/esm-package", ["index.js"]).test({
|
||
status: 0,
|
||
stderr: "",
|
||
write: [],
|
||
});
|
||
});
|
||
|
||
describe("resolves configuration file with --find-config-path file", () => {
|
||
runCli("cli/config/", ["--find-config-path", "no-config/file.js"]).test({
|
||
status: 0,
|
||
});
|
||
});
|
||
|
||
describe("resolves json configuration file with --find-config-path file", () => {
|
||
runCli("cli/config/", ["--find-config-path", "rc-json/file.js"]).test({
|
||
status: 0,
|
||
});
|
||
});
|
||
|
||
describe("resolves yaml configuration file with --find-config-path file", () => {
|
||
runCli("cli/config/", ["--find-config-path", "rc-yaml/file.js"]).test({
|
||
status: 0,
|
||
});
|
||
});
|
||
|
||
describe("resolves toml configuration file with --find-config-path file", () => {
|
||
runCli("cli/config/", ["--find-config-path", "rc-toml/file.js"]).test({
|
||
status: 0,
|
||
});
|
||
});
|
||
|
||
describe("prints error message when no file found with --find-config-path", () => {
|
||
runCli("cli/config/", [
|
||
"--end-of-line",
|
||
"lf",
|
||
"--find-config-path",
|
||
"../--non-exits-filename--",
|
||
]).test({
|
||
stdout: "",
|
||
status: 1,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig with no args", async () => {
|
||
await expect(prettier.resolveConfig()).resolves.toStrictEqual({});
|
||
});
|
||
|
||
test("API resolveConfig with file arg", async () => {
|
||
const file = new URL("../cli/config/js/file.js", import.meta.url);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
tabWidth: 8,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig with file arg and extension override", async () => {
|
||
const file = new URL("../cli/config/no-config/file.ts", import.meta.url);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
semi: true,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig with file arg and .editorconfig", async () => {
|
||
const file = new URL("../cli/config/editorconfig/file.js", import.meta.url);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
semi: false,
|
||
});
|
||
|
||
await expect(
|
||
prettier.resolveConfig(file, { editorconfig: true }),
|
||
).resolves.toMatchObject({
|
||
useTabs: true,
|
||
tabWidth: 8,
|
||
printWidth: 100,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig with file arg and .editorconfig (key = unset)", async () => {
|
||
const file = new URL(
|
||
"../cli/config/editorconfig/tab_width=unset.js",
|
||
import.meta.url,
|
||
);
|
||
await expect(
|
||
prettier.resolveConfig(file, { editorconfig: true }),
|
||
).resolves.not.toMatchObject({ tabWidth: "unset" });
|
||
});
|
||
|
||
test("API resolveConfig with nested file arg and .editorconfig", async () => {
|
||
const file = new URL(
|
||
"../cli/config/editorconfig/lib/file.js",
|
||
import.meta.url,
|
||
);
|
||
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
semi: false,
|
||
});
|
||
|
||
await expect(
|
||
prettier.resolveConfig(file, { editorconfig: true }),
|
||
).resolves.toMatchObject({
|
||
useTabs: false,
|
||
tabWidth: 2,
|
||
printWidth: 100,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig with nested file arg and .editorconfig and indent_size = tab", async () => {
|
||
const file = new URL(
|
||
"../cli/config/editorconfig/lib/indent_size=tab.js",
|
||
import.meta.url,
|
||
);
|
||
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
semi: false,
|
||
});
|
||
|
||
await expect(
|
||
prettier.resolveConfig(file, { editorconfig: true }),
|
||
).resolves.toMatchObject({
|
||
useTabs: false,
|
||
tabWidth: 8,
|
||
printWidth: 100,
|
||
});
|
||
});
|
||
|
||
describe("API resolveConfig doesn’t apply editorconfig outside project when .git is present", () => {
|
||
async function run() {
|
||
const file = new URL(
|
||
"../cli/config/editorconfig/repo-root-git/file.js",
|
||
import.meta.url,
|
||
);
|
||
await expect(
|
||
prettier.resolveConfig(file, { editorconfig: true }),
|
||
).resolves.toMatchObject({
|
||
endOfLine: "auto",
|
||
semi: false,
|
||
});
|
||
await expect(
|
||
prettier.resolveConfig(file, { editorconfig: true }),
|
||
).resolves.not.toMatchObject({
|
||
useTabs: true,
|
||
tabWidth: 8,
|
||
printWidth: 100,
|
||
});
|
||
}
|
||
|
||
describe("when .git is a file", () => {
|
||
const gitFilePath = new URL(
|
||
"../cli/config/editorconfig/repo-root-git/.git",
|
||
import.meta.url,
|
||
);
|
||
|
||
beforeAll(async () => {
|
||
await fs.rm(gitFilePath, { recursive: true, force: true });
|
||
await fs.writeFile(gitFilePath, "");
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await fs.rm(gitFilePath, { recursive: true, force: true });
|
||
});
|
||
|
||
test("API resolveConfig doesn’t apply editorconfig outside project when .git is a file", async () => {
|
||
await run();
|
||
});
|
||
});
|
||
|
||
describe("when .git is a directory", () => {
|
||
const gitDirPath = new URL(
|
||
"../cli/config/editorconfig/repo-root-git/.git",
|
||
import.meta.url,
|
||
);
|
||
|
||
beforeAll(async () => {
|
||
await fs.rm(gitDirPath, { recursive: true, force: true });
|
||
await fs.mkdir(gitDirPath, { recursive: true });
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await fs.rm(gitDirPath, { recursive: true, force: true });
|
||
});
|
||
|
||
test("API resolveConfig doesn’t apply editorconfig outside project when .git is a directory", async () => {
|
||
await run();
|
||
});
|
||
});
|
||
});
|
||
|
||
test("API clearConfigCache", () => {
|
||
expect(() => prettier.clearConfigCache()).not.toThrow();
|
||
});
|
||
|
||
test("API resolveConfig overrides work with dotfiles", async () => {
|
||
const file = new URL(
|
||
"../cli/config/dot-overrides/.foo.json",
|
||
import.meta.url,
|
||
);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
tabWidth: 4,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig overrides work with absolute paths", async () => {
|
||
// Absolute path
|
||
const file = url.fileURLToPath(
|
||
new URL("../cli/config/filepath/subfolder/file.js", import.meta.url),
|
||
);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
tabWidth: 6,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig overrides excludeFiles", async () => {
|
||
const notOverride = new URL(
|
||
"../cli/config/overrides-exclude-files/foo",
|
||
import.meta.url,
|
||
);
|
||
await expect(prettier.resolveConfig(notOverride)).resolves.toMatchObject({
|
||
singleQuote: true,
|
||
trailingComma: "all",
|
||
});
|
||
|
||
const singleQuote = new URL(
|
||
"../cli/config/overrides-exclude-files/single-quote.js",
|
||
import.meta.url,
|
||
);
|
||
await expect(prettier.resolveConfig(singleQuote)).resolves.toMatchObject({
|
||
singleQuote: true,
|
||
trailingComma: "es5",
|
||
});
|
||
|
||
const doubleQuote = new URL(
|
||
"../cli/config/overrides-exclude-files/double-quote.js",
|
||
import.meta.url,
|
||
);
|
||
await expect(prettier.resolveConfig(doubleQuote)).resolves.toMatchObject({
|
||
singleQuote: false,
|
||
trailingComma: "es5",
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig removes $schema option", async () => {
|
||
const file = new URL("../cli/config/$schema/index.js", import.meta.url);
|
||
await expect(prettier.resolveConfig(file)).resolves.toStrictEqual({
|
||
tabWidth: 42,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig resolves relative path values based on config filepath", async () => {
|
||
const currentDir = new URL(
|
||
"../cli/config/resolve-relative/",
|
||
import.meta.url,
|
||
);
|
||
const parentDir = path.resolve(url.fileURLToPath(currentDir), "..");
|
||
await expect(
|
||
prettier.resolveConfig(new URL("index.js", currentDir)),
|
||
).resolves.toMatchObject({
|
||
plugins: [path.join(parentDir, "path-to-plugin")],
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig de-references to an external module", async () => {
|
||
const config = { printWidth: 77, semi: false };
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL(
|
||
"../cli/config/external-config/cjs-package/index.js",
|
||
import.meta.url,
|
||
),
|
||
),
|
||
).resolves.toStrictEqual(config);
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL(
|
||
"../cli/config/external-config/esm-package/index.js",
|
||
import.meta.url,
|
||
),
|
||
),
|
||
).resolves.toStrictEqual(config);
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL(
|
||
"../cli/config/external-config/esm-file/index.js",
|
||
import.meta.url,
|
||
),
|
||
),
|
||
).resolves.toStrictEqual(config);
|
||
});
|
||
|
||
test(".js config file", async () => {
|
||
const parentDirectory = new URL("../cli/config/rc-js/", import.meta.url);
|
||
|
||
const config = {
|
||
trailingComma: "all",
|
||
singleQuote: true,
|
||
};
|
||
|
||
for (const directoryName of [
|
||
"cjs-prettier-config-js-in-type-commonjs",
|
||
"cjs-prettier-config-js-in-type-none",
|
||
"cjs-prettierrc-js-in-type-commonjs",
|
||
// Node.js v22.7 throws `MODULE_TYPELESS_PACKAGE_JSON` when `type` missed in package.json
|
||
// "cjs-prettierrc-js-in-type-none",
|
||
"mjs-prettier-config-js-in-type-module",
|
||
"mjs-prettierrc-js-in-type-module",
|
||
]) {
|
||
const file = new URL(`${directoryName}/foo.js`, parentDirectory);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject(config);
|
||
}
|
||
|
||
const cjsError = /module is not defined in ES module scope/;
|
||
for (const directoryName of [
|
||
"cjs-prettier-config-js-in-type-module",
|
||
"cjs-prettierrc-js-in-type-module",
|
||
]) {
|
||
const file = new URL(`./${directoryName}/foo.js`, parentDirectory);
|
||
await expect(prettier.resolveConfig(file)).rejects.toThrow(cjsError);
|
||
}
|
||
|
||
const mjsError = /Unexpected token 'export'/;
|
||
for (const directoryName of [
|
||
"mjs-prettier-config-js-in-type-commonjs",
|
||
// Node.js v22.7 throws `MODULE_TYPELESS_PACKAGE_JSON` when `type` missed in package.json
|
||
// "mjs-prettier-config-js-in-type-none",
|
||
"mjs-prettierrc-js-in-type-commonjs",
|
||
// Node.js v22.7 throws `MODULE_TYPELESS_PACKAGE_JSON` when `type` missed in package.json
|
||
// "mjs-prettierrc-js-in-type-none",
|
||
]) {
|
||
const file = new URL(`./${directoryName}/foo.js`, parentDirectory);
|
||
await expect(prettier.resolveConfig(file)).rejects.toThrow(mjsError);
|
||
}
|
||
});
|
||
|
||
test(".cjs config file", async () => {
|
||
const parentDirectory = new URL("../cli/config/rc-cjs/", import.meta.url);
|
||
|
||
const config = {
|
||
trailingComma: "all",
|
||
singleQuote: true,
|
||
};
|
||
|
||
for (const directoryName of [
|
||
"prettierrc-cjs-in-type-module",
|
||
"prettierrc-cjs-in-type-commonjs",
|
||
"prettierrc-cjs-in-type-none",
|
||
"prettier-config-cjs-in-type-commonjs",
|
||
"prettier-config-cjs-in-type-none",
|
||
"prettier-config-cjs-in-type-module",
|
||
]) {
|
||
const file = new URL(`./${directoryName}/foo.js`, parentDirectory);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject(config);
|
||
}
|
||
});
|
||
|
||
test(".mjs config file", async () => {
|
||
const parentDirectory = new URL("../cli/config/rc-mjs/", import.meta.url);
|
||
|
||
const config = {
|
||
trailingComma: "all",
|
||
singleQuote: true,
|
||
};
|
||
|
||
for (const directoryName of [
|
||
"prettierrc-mjs-in-type-module",
|
||
"prettierrc-mjs-in-type-commonjs",
|
||
"prettierrc-mjs-in-type-none",
|
||
"prettier-config-mjs-in-type-commonjs",
|
||
"prettier-config-mjs-in-type-none",
|
||
"prettier-config-mjs-in-type-module",
|
||
]) {
|
||
const file = new URL(`./${directoryName}/foo.js`, parentDirectory);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject(config);
|
||
}
|
||
});
|
||
|
||
test(".json5 config file", async () => {
|
||
const parentDirectory = new URL("../cli/config/rc-json5/", import.meta.url);
|
||
const config = {
|
||
trailingComma: "all",
|
||
printWidth: 81,
|
||
tabWidth: 3,
|
||
};
|
||
const file = new URL("./json5/foo.js", parentDirectory);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject(config);
|
||
});
|
||
|
||
test(".json5 config file(invalid)", async () => {
|
||
const parentDirectory = new URL("../cli/config/rc-json5/", import.meta.url);
|
||
const file = new URL("./invalid/foo.js", parentDirectory);
|
||
const error = /JSON5: invalid end of input at 2:1/;
|
||
await expect(prettier.resolveConfig(file)).rejects.toThrow(error);
|
||
});
|
||
|
||
test("support external module with `module` only `exports`", async () => {
|
||
const file = new URL(
|
||
"../cli/config/external-config/esm-package-forbids-require/index.js",
|
||
import.meta.url,
|
||
);
|
||
await expect(prettier.resolveConfig(file)).resolves.toMatchObject({
|
||
printWidth: 79,
|
||
});
|
||
});
|
||
|
||
test("API resolveConfig accepts path or URL", async () => {
|
||
const fileUrl = new URL("../cli/config/js/file.js", import.meta.url);
|
||
const expectedResult = { tabWidth: 8 };
|
||
|
||
const resultByUrl = await prettier.resolveConfig(fileUrl);
|
||
const resultByUrlHref = await prettier.resolveConfig(fileUrl.href);
|
||
const resultByPath = await prettier.resolveConfig(url.fileURLToPath(fileUrl));
|
||
const resultByRelativePath = await prettier.resolveConfig(
|
||
path.relative(process.cwd(), url.fileURLToPath(fileUrl)),
|
||
);
|
||
expect(resultByUrl).toMatchObject(expectedResult);
|
||
expect(resultByUrlHref).toMatchObject(expectedResult);
|
||
expect(resultByPath).toMatchObject(expectedResult);
|
||
expect(resultByRelativePath).toMatchObject(expectedResult);
|
||
});
|
||
|
||
test("Search from directory, not treat file as directory", async () => {
|
||
// CLI
|
||
const getConfigFileByCli = async (file) => {
|
||
const { stdout: configFile } = await runCli("cli/config/config-position/", [
|
||
"--find-config-path",
|
||
file,
|
||
]);
|
||
return configFile;
|
||
};
|
||
|
||
expect(await getConfigFileByCli("file.js")).toBe(".prettierrc");
|
||
expect(await getConfigFileByCli("directory/file-in-child-directory.js")).toBe(
|
||
"directory/.prettierrc",
|
||
);
|
||
|
||
// API
|
||
const directory = new URL("../cli/config/config-position/", import.meta.url);
|
||
const getConfigFileByApi = async (file) => {
|
||
const configFile = await prettier.resolveConfigFile(
|
||
new URL(file, directory),
|
||
);
|
||
return url.pathToFileURL(configFile).href.slice(directory.href.length);
|
||
};
|
||
expect(await getConfigFileByApi("file.js")).toBe(".prettierrc");
|
||
expect(await getConfigFileByApi("directory/file-in-child-directory.js")).toBe(
|
||
"directory/.prettierrc",
|
||
);
|
||
});
|
||
|
||
test("package.json/package.yaml", async () => {
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL("../cli/config/package/file.js", import.meta.url),
|
||
),
|
||
).resolves.toMatchInlineSnapshot(`
|
||
{
|
||
"tabWidth": 3,
|
||
}
|
||
`);
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL("../cli/config/package/file.ts", import.meta.url),
|
||
),
|
||
).resolves.toMatchInlineSnapshot(`
|
||
{
|
||
"tabWidth": 5,
|
||
}
|
||
`);
|
||
await expect(
|
||
prettier.resolveConfig(
|
||
new URL("../cli/config/package-yaml/file.ts", import.meta.url),
|
||
),
|
||
).resolves.toMatchInlineSnapshot(`
|
||
{
|
||
"printWidth": 101,
|
||
}
|
||
`);
|
||
});
|
||
|
||
test("'config' option should accept `URL` and `string`", async () => {
|
||
const configFileUrl = new URL(
|
||
"../cli/config/custom-config-file-location/my-prettier-config.mjs",
|
||
import.meta.url,
|
||
);
|
||
const file = new URL("./file.js", configFileUrl);
|
||
|
||
for (const configFile of [
|
||
configFileUrl,
|
||
configFileUrl.href,
|
||
url.fileURLToPath(configFileUrl),
|
||
]) {
|
||
await expect(
|
||
prettier.resolveConfig(file, { config: configFile }),
|
||
).resolves.toStrictEqual({ tabWidth: 7 });
|
||
}
|
||
});
|