Implement our own config explorer (#15663)
This commit is contained in:
1 parent
117c04fcc8
commit
5f0956bc30
21 files changed
+393
-208
No files matched your search
@@ -0,0 +1,3 @@
|
||||
#### Improve config file search (#15663 by @fisker)
|
||||
|
||||
The Prettier config file search performance has been improved by more effective cache strategy.
|
||||
+1
-1
@@ -66,7 +66,7 @@ If `options.editorconfig` is `true` and an [`.editorconfig` file](https://editor
|
||||
|
||||
The promise will be rejected if there was an error parsing the configuration file.
|
||||
|
||||
The search starts at `process.cwd()`, or at `fileUrlOrPath` if provided. Please see the [lilconfig docs](https://github.com/antonk52/lilconfig) for details on how the resolving works.
|
||||
The search starts at `process.cwd()`, or at the directory of `fileUrlOrPath` if provided.
|
||||
|
||||
```js
|
||||
const configFile = await prettier.resolveConfigFile(filePath);
|
||||
|
||||
@@ -3,7 +3,7 @@ id: configuration
|
||||
title: Configuration File
|
||||
---
|
||||
|
||||
Prettier uses [lilconfig](https://github.com/antonk52/lilconfig) for configuration file support. This means you can configure Prettier via (in order of precedence):
|
||||
You can configure Prettier via (in order of precedence):
|
||||
|
||||
- A `"prettier"` key in your `package.json` file.
|
||||
- A `.prettierrc` file written in JSON or YAML.
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
"js-yaml": "4.1.0",
|
||||
"json5": "2.2.3",
|
||||
"leven": "4.0.0",
|
||||
"lilconfig": "2.1.0",
|
||||
"lines-and-columns": "2.0.4",
|
||||
"linguist-languages": "7.27.0",
|
||||
"mem": "9.0.2",
|
||||
|
||||
@@ -610,11 +610,6 @@ const nodejsFiles = [
|
||||
}),
|
||||
path: require.resolve("@babel/code-frame"),
|
||||
},
|
||||
{
|
||||
module: require.resolve("lilconfig"),
|
||||
find: "exports.lilconfigSync = lilconfigSync;",
|
||||
replacement: "",
|
||||
},
|
||||
],
|
||||
addDefaultExport: true,
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { lilconfig } from "lilconfig";
|
||||
import getStdin from "get-stdin";
|
||||
import { isCI } from "ci-info";
|
||||
|
||||
@@ -8,7 +7,8 @@ function writeFormattedFile(file, data) {
|
||||
}
|
||||
|
||||
const mockable = {
|
||||
lilconfig,
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
getPrettierConfigSearchStopDirectory: () => undefined,
|
||||
getStdin,
|
||||
isCI: () => isCI,
|
||||
writeFormattedFile,
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import { pathToFileURL } from "node:url";
|
||||
import parseToml from "@iarna/toml/parse-async.js";
|
||||
import parseJson5 from "json5/lib/parse.js";
|
||||
import yaml from "js-yaml";
|
||||
import parseJson from "parse-json";
|
||||
import mockable from "../common/mockable.js";
|
||||
import loadExternalConfig from "./load-external-config.js";
|
||||
|
||||
const searchPlaces = [
|
||||
"package.json",
|
||||
".prettierrc",
|
||||
".prettierrc.json",
|
||||
".prettierrc.yaml",
|
||||
".prettierrc.yml",
|
||||
".prettierrc.json5",
|
||||
".prettierrc.js",
|
||||
".prettierrc.mjs",
|
||||
".prettierrc.cjs",
|
||||
"prettier.config.js",
|
||||
"prettier.config.mjs",
|
||||
"prettier.config.cjs",
|
||||
".prettierrc.toml",
|
||||
];
|
||||
|
||||
async function loadJs(filepath /*, content*/) {
|
||||
const module = await import(pathToFileURL(filepath).href);
|
||||
return module.default;
|
||||
}
|
||||
|
||||
function loadYaml(filepath, content) {
|
||||
try {
|
||||
return yaml.load(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `YAML Error in ${filepath}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const loaders = {
|
||||
async ".toml"(filePath, content) {
|
||||
try {
|
||||
return await parseToml(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `TOML Error in ${filePath}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
".json5"(filePath, content) {
|
||||
try {
|
||||
return parseJson5(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `JSON5 Error in ${filePath}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
".json"(filePath, content) {
|
||||
try {
|
||||
return parseJson(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `JSON Error in ${filePath}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
".js": loadJs,
|
||||
".mjs": loadJs,
|
||||
".yaml": loadYaml,
|
||||
".yml": loadYaml,
|
||||
noExt: loadYaml,
|
||||
};
|
||||
|
||||
async function transform(result) {
|
||||
if (!result?.config) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let { config, filepath } = result;
|
||||
|
||||
/*
|
||||
We support external config
|
||||
|
||||
```json
|
||||
{
|
||||
"prettier": "my-prettier-config-package-or-file"
|
||||
}
|
||||
```
|
||||
*/
|
||||
if (typeof config === "string") {
|
||||
config = await loadExternalConfig(config, filepath);
|
||||
result.config = config;
|
||||
}
|
||||
|
||||
if (typeof config !== "object") {
|
||||
throw new TypeError(
|
||||
"Config is only allowed to be an object, " +
|
||||
`but received ${typeof config} in "${filepath}"`,
|
||||
);
|
||||
}
|
||||
|
||||
delete config.$schema;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {ReturnType<import("lilconfig").lilconfig>}
|
||||
*/
|
||||
function getExplorer() {
|
||||
return mockable.lilconfig("prettier", { searchPlaces, loaders, transform });
|
||||
}
|
||||
|
||||
export default getExplorer;
|
||||
@@ -0,0 +1,119 @@
|
||||
import path from "node:path";
|
||||
import iterateDirectoryUp from "iterate-directory-up";
|
||||
import isFile from "../../utils/is-file.js";
|
||||
import { loadConfigFromPackageJson } from "./loaders.js";
|
||||
|
||||
const CONFIG_FILE_NAMES = [
|
||||
"package.json",
|
||||
".prettierrc",
|
||||
".prettierrc.json",
|
||||
".prettierrc.yaml",
|
||||
".prettierrc.yml",
|
||||
".prettierrc.json5",
|
||||
".prettierrc.js",
|
||||
".prettierrc.mjs",
|
||||
".prettierrc.cjs",
|
||||
"prettier.config.js",
|
||||
"prettier.config.mjs",
|
||||
"prettier.config.cjs",
|
||||
".prettierrc.toml",
|
||||
];
|
||||
|
||||
async function isPackageJsonFileWithPrettierConfig(file) {
|
||||
try {
|
||||
return Boolean(await loadConfigFromPackageJson(file));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function searchConfigInDirectory(directory) {
|
||||
for (const fileName of CONFIG_FILE_NAMES) {
|
||||
const file = path.join(directory, fileName);
|
||||
|
||||
if (!(await isFile(file))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
fileName !== "package.json" ||
|
||||
(await isPackageJsonFileWithPrettierConfig(file))
|
||||
) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createCachedSearchConfigInDirectory() {
|
||||
const cache = new Map();
|
||||
|
||||
return async function (fileOrDirectory) {
|
||||
if (cache.has(fileOrDirectory)) {
|
||||
return cache.get(fileOrDirectory);
|
||||
}
|
||||
|
||||
const promise = searchConfigInDirectory(fileOrDirectory);
|
||||
cache.set(fileOrDirectory, promise);
|
||||
const result = await promise;
|
||||
cache.set(fileOrDirectory, result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/** @type {Map} */ // @ts-expect-error -- intentionally not add the `get` method
|
||||
const noopMap = { has: () => false, set() {} };
|
||||
class ConfigSearcher {
|
||||
#shouldCache;
|
||||
#stopDirectory;
|
||||
#searchedFilesStore;
|
||||
#searchConfigInDirectory;
|
||||
constructor({ stopDirectory, shouldCache }) {
|
||||
this.#shouldCache = shouldCache;
|
||||
this.#stopDirectory = stopDirectory;
|
||||
this.#searchConfigInDirectory = shouldCache
|
||||
? createCachedSearchConfigInDirectory()
|
||||
: searchConfigInDirectory;
|
||||
this.#searchedFilesStore = shouldCache ? new Map() : noopMap;
|
||||
}
|
||||
|
||||
async #searchConfigInDirectories(startDirectory) {
|
||||
const store = this.#searchedFilesStore;
|
||||
for (const directory of iterateDirectoryUp(
|
||||
startDirectory,
|
||||
this.#stopDirectory,
|
||||
)) {
|
||||
// The parent directory may already searched
|
||||
if (store.has(directory)) {
|
||||
return store.get(directory);
|
||||
}
|
||||
|
||||
const file = await this.#searchConfigInDirectory(directory);
|
||||
if (file) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async search(startDirectory) {
|
||||
const store = this.#searchedFilesStore;
|
||||
if (store.has(startDirectory)) {
|
||||
return store.get(startDirectory);
|
||||
}
|
||||
|
||||
const configFile = await this.#searchConfigInDirectories(startDirectory);
|
||||
|
||||
// Directories between startDirectory and configFile directory should has the same result
|
||||
if (this.#shouldCache) {
|
||||
for (const directory of iterateDirectoryUp(
|
||||
startDirectory,
|
||||
configFile ? path.dirname(configFile) : startDirectory,
|
||||
)) {
|
||||
store.set(directory, configFile);
|
||||
}
|
||||
}
|
||||
|
||||
return configFile;
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigSearcher;
|
||||
@@ -0,0 +1,64 @@
|
||||
import path from "node:path";
|
||||
import mockable from "../../common/mockable.js";
|
||||
import loadConfigWithoutCache from "./load-config.js";
|
||||
import ConfigSearcher from "./config-searcher.js";
|
||||
|
||||
const loadCache = new Map();
|
||||
const searchCache = new Map();
|
||||
function clearCache() {
|
||||
loadCache.clear();
|
||||
searchCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} configFile
|
||||
* @param {{shouldCache?: boolean}} param1
|
||||
* @returns {Promise<ReturnType<loadConfigWithoutCache>>}
|
||||
*/
|
||||
function loadConfig(configFile, { shouldCache }) {
|
||||
configFile = path.resolve(configFile);
|
||||
if (!shouldCache || !loadCache.has(configFile)) {
|
||||
const promise = loadConfigWithoutCache(configFile);
|
||||
// Even if `shouldCache` is false, we still cache it, so we can use it later
|
||||
loadCache.set(configFile, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
return loadCache.get(configFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{shouldCache?: boolean, stopDirectory?: string}} param0
|
||||
*/
|
||||
function getSearchFunction({ shouldCache, stopDirectory }) {
|
||||
stopDirectory = stopDirectory ? path.resolve(stopDirectory) : undefined;
|
||||
const searchCacheKey = JSON.stringify({ shouldCache, stopDirectory });
|
||||
|
||||
if (!searchCache.has(searchCacheKey)) {
|
||||
const searcher = new ConfigSearcher({ shouldCache, stopDirectory });
|
||||
const searchFunction = searcher.search.bind(searcher);
|
||||
|
||||
searchCache.set(searchCacheKey, searchFunction);
|
||||
}
|
||||
|
||||
return searchCache.get(searchCacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} startDirectory
|
||||
* @param {{shouldCache?: boolean, stopDirectory?: string}} options
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function searchConfig(startDirectory, options = {}) {
|
||||
startDirectory = startDirectory
|
||||
? path.resolve(startDirectory)
|
||||
: process.cwd();
|
||||
|
||||
options.stopDirectory = mockable.getPrettierConfigSearchStopDirectory();
|
||||
|
||||
const search = getSearchFunction(options);
|
||||
|
||||
return search(startDirectory);
|
||||
}
|
||||
|
||||
export { searchConfig, loadConfig, clearCache };
|
||||
@@ -0,0 +1,48 @@
|
||||
import path from "node:path";
|
||||
import loadExternalConfig from "./load-external-config.js";
|
||||
import loaders, { loadConfigFromPackageJson } from "./loaders.js";
|
||||
|
||||
async function loadConfig(configFile) {
|
||||
const { base: fileName, ext: extension } = path.parse(configFile);
|
||||
const load =
|
||||
fileName === "package.json"
|
||||
? loadConfigFromPackageJson
|
||||
: loaders[extension];
|
||||
|
||||
if (!load) {
|
||||
throw new Error(
|
||||
`No loader specified for extension "${extension || "noExt"}"`,
|
||||
);
|
||||
}
|
||||
|
||||
let config = await load(configFile);
|
||||
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
We support external config
|
||||
|
||||
```json
|
||||
{
|
||||
"prettier": "my-prettier-config-package-or-file"
|
||||
}
|
||||
```
|
||||
*/
|
||||
if (typeof config === "string") {
|
||||
config = await loadExternalConfig(config, configFile);
|
||||
}
|
||||
|
||||
if (typeof config !== "object") {
|
||||
throw new TypeError(
|
||||
"Config is only allowed to be an object, " +
|
||||
`but received ${typeof config} in "${configFile}"`,
|
||||
);
|
||||
}
|
||||
|
||||
delete config.$schema;
|
||||
return config;
|
||||
}
|
||||
|
||||
export default loadConfig;
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
import requireFromFile from "../utils/require-from-file.js";
|
||||
import importFromFile from "../utils/import-from-file.js";
|
||||
import requireFromFile from "../../utils/require-from-file.js";
|
||||
import importFromFile from "../../utils/import-from-file.js";
|
||||
|
||||
const requireErrorCodesShouldBeIgnored = new Set([
|
||||
"MODULE_NOT_FOUND",
|
||||
"ERR_REQUIRE_ESM",
|
||||
"ERR_PACKAGE_PATH_NOT_EXPORTED",
|
||||
]);
|
||||
async function loadExternalConfig(config, filepath) {
|
||||
async function loadExternalConfig(externalConfig, configFile) {
|
||||
/*
|
||||
Try `require()` first, this is how it works in Prettier v2.
|
||||
Kept this because the external config path or package may can't load with `import()`:
|
||||
@@ -15,14 +15,14 @@ async function loadExternalConfig(config, filepath) {
|
||||
3. is a dirname with index.js inside
|
||||
*/
|
||||
try {
|
||||
return requireFromFile(config, filepath);
|
||||
return requireFromFile(externalConfig, configFile);
|
||||
} catch (/** @type {any} */ error) {
|
||||
if (!requireErrorCodesShouldBeIgnored.has(error?.code)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const module = await importFromFile(config, filepath);
|
||||
const module = await importFromFile(externalConfig, configFile);
|
||||
return module.default;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { pathToFileURL } from "node:url";
|
||||
import parseToml from "@iarna/toml/parse-async.js";
|
||||
import parseJson5 from "json5/lib/parse.js";
|
||||
import jsYaml from "js-yaml";
|
||||
import parseJson from "parse-json";
|
||||
import readFile from "../../utils/read-file.js";
|
||||
|
||||
async function readJson(file) {
|
||||
const content = await readFile(file);
|
||||
try {
|
||||
return parseJson(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `JSON Error in ${file}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadJs(file) {
|
||||
const module = await import(pathToFileURL(file).href);
|
||||
return module.default;
|
||||
}
|
||||
|
||||
async function loadConfigFromPackageJson(file) {
|
||||
const { prettier } = await readJson(file);
|
||||
return prettier;
|
||||
}
|
||||
|
||||
async function loadYaml(file) {
|
||||
const content = await readFile(file);
|
||||
try {
|
||||
return jsYaml.load(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `YAML Error in ${file}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const loaders = {
|
||||
async ".toml"(file) {
|
||||
const content = await readFile(file);
|
||||
try {
|
||||
return await parseToml(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `TOML Error in ${file}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async ".json5"(file) {
|
||||
const content = await readFile(file);
|
||||
try {
|
||||
return parseJson5(content);
|
||||
} catch (/** @type {any} */ error) {
|
||||
error.message = `JSON5 Error in ${file}:\n${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
".json": readJson,
|
||||
".js": loadJs,
|
||||
".mjs": loadJs,
|
||||
".cjs": loadJs,
|
||||
".yaml": loadYaml,
|
||||
".yml": loadYaml,
|
||||
// No extension
|
||||
"": loadYaml,
|
||||
};
|
||||
|
||||
export default loaders;
|
||||
export { readJson, loadConfigFromPackageJson };
|
||||
@@ -3,15 +3,16 @@ import micromatch from "micromatch";
|
||||
import mem, { memClear } from "mem";
|
||||
import { toPath } from "url-or-path";
|
||||
import partition from "../utils/partition.js";
|
||||
import {
|
||||
searchConfig as searchPrettierConfig,
|
||||
loadConfig as loadPrettierConfigFile,
|
||||
clearCache as clearPrettierConfigCache,
|
||||
} from "./prettier-config-explorer/index.js";
|
||||
import loadEditorConfigWithoutCache from "./resolve-editorconfig.js";
|
||||
import getPrettierConfigExplorerWithoutCache from "./get-prettier-config-explorer.js";
|
||||
|
||||
const getPrettierConfigExplorer = mem(getPrettierConfigExplorerWithoutCache, {
|
||||
cacheKey: ([options]) => options.cache,
|
||||
});
|
||||
const memoizedLoadEditorConfig = mem(loadEditorConfigWithoutCache);
|
||||
function clearCache() {
|
||||
memClear(getPrettierConfigExplorer);
|
||||
clearPrettierConfigCache();
|
||||
memClear(memoizedLoadEditorConfig);
|
||||
}
|
||||
|
||||
@@ -25,22 +26,24 @@ function loadEditorConfig(filePath, options) {
|
||||
)(filePath);
|
||||
}
|
||||
|
||||
function loadPrettierConfig(filePath, options) {
|
||||
const { useCache, config: configPath } = options;
|
||||
const { load, search } = getPrettierConfigExplorer({
|
||||
cache: Boolean(useCache),
|
||||
});
|
||||
async function loadPrettierConfig(filePath, options) {
|
||||
const shouldCache = options.useCache;
|
||||
let configFile = options.config;
|
||||
|
||||
if (configPath) {
|
||||
return load(configPath);
|
||||
if (!configFile) {
|
||||
const directory = filePath
|
||||
? path.dirname(path.resolve(filePath))
|
||||
: undefined;
|
||||
configFile = await searchPrettierConfig(directory, { shouldCache });
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
return search();
|
||||
if (!configFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dirname = path.dirname(path.resolve(filePath));
|
||||
return search(dirname);
|
||||
const config = await loadPrettierConfigFile(configFile, { shouldCache });
|
||||
|
||||
return { config, configFile };
|
||||
}
|
||||
|
||||
async function resolveConfig(fileUrlOrPath, options) {
|
||||
@@ -64,7 +67,7 @@ async function resolveConfig(fileUrlOrPath, options) {
|
||||
if (Array.isArray(merged.plugins)) {
|
||||
merged.plugins = merged.plugins.map((value) =>
|
||||
typeof value === "string" && value.startsWith(".") // relative path
|
||||
? path.resolve(path.dirname(result.filepath), value)
|
||||
? path.resolve(path.dirname(result.configFile), value)
|
||||
: value,
|
||||
);
|
||||
}
|
||||
@@ -73,19 +76,18 @@ async function resolveConfig(fileUrlOrPath, options) {
|
||||
}
|
||||
|
||||
async function resolveConfigFile(fileUrlOrPath) {
|
||||
const { search } = getPrettierConfigExplorerWithoutCache();
|
||||
const dirname = fileUrlOrPath
|
||||
const directory = fileUrlOrPath
|
||||
? path.dirname(path.resolve(toPath(fileUrlOrPath)))
|
||||
: undefined;
|
||||
const result = await search(dirname);
|
||||
return result?.filepath ?? null;
|
||||
const result = await searchPrettierConfig(directory, { shouldCache: false });
|
||||
return result ?? null;
|
||||
}
|
||||
|
||||
function mergeOverrides(configResult, filePath) {
|
||||
const { config, filepath: configPath } = configResult || {};
|
||||
const { config, configFile } = configResult || {};
|
||||
const { overrides, ...options } = config || {};
|
||||
if (filePath && overrides) {
|
||||
const relativeFilePath = path.relative(path.dirname(configPath), filePath);
|
||||
const relativeFilePath = path.relative(path.dirname(configFile), filePath);
|
||||
for (const override of overrides) {
|
||||
if (
|
||||
pathMatchesGlobs(
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import fs from "node:fs/promises";
|
||||
import { toPath } from "url-or-path";
|
||||
|
||||
/**
|
||||
* @param {string | URL} file
|
||||
* @param {{allowSymlinks: boolean}} [options]
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function isFile(file, options) {
|
||||
const allowSymlinks = options?.allowSymlinks ?? true;
|
||||
|
||||
let stats;
|
||||
try {
|
||||
stats = await (allowSymlinks ? fs.stat : fs.lstat)(toPath(file));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return stats.isFile();
|
||||
}
|
||||
|
||||
export default isFile;
|
||||
@@ -63,7 +63,7 @@ exports[`throw error with invalid config precedence option (configPrecedence) (w
|
||||
|
||||
exports[`throw error with invalid config target (directory) (stderr) 1`] = `
|
||||
"[error] Invalid configuration:
|
||||
[error] EISDIR: illegal operation on a directory, read"
|
||||
[error] Unable to read '<cli>/config/invalid/folder/.prettierrc': EISDIR: illegal operation on a directory, read"
|
||||
`;
|
||||
|
||||
exports[`throw error with invalid config target (directory) (stdout) 1`] = `""`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import fs from "node:fs/promises";
|
||||
import outdent from "outdent";
|
||||
import jestPathSerializer from "../path-serializer.js";
|
||||
|
||||
@@ -178,3 +179,31 @@ describe("Invalid config value", () => {
|
||||
stderr: "",
|
||||
});
|
||||
});
|
||||
|
||||
// Can't put a invalid `package.json` file in the test dir
|
||||
test("Invalid package.json", async () => {
|
||||
const packageJsonFile = new URL(
|
||||
"../cli/config/invalid/broken-package-json/package.json",
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
try {
|
||||
await fs.writeFile(packageJsonFile, '{"prettier":{}}');
|
||||
const { stdout: configFileForValidPackageJson } = await runCli(
|
||||
"cli/config/invalid/broken-package-json",
|
||||
["--find-config-path", "foo.js"],
|
||||
);
|
||||
|
||||
expect(configFileForValidPackageJson).toBe("package.json");
|
||||
|
||||
await fs.writeFile(packageJsonFile, '{"prettier":{');
|
||||
const { stdout: configFileForInvalidPackageJson } = await runCli(
|
||||
"cli/config/invalid/broken-package-json",
|
||||
["--find-config-path", "foo.js"],
|
||||
);
|
||||
|
||||
expect(configFileForInvalidPackageJson).toBe(".prettierrc");
|
||||
} finally {
|
||||
await fs.rm(packageJsonFile, { force: true });
|
||||
}
|
||||
});
|
||||
@@ -1,52 +1,7 @@
|
||||
import path from "node:path";
|
||||
import createEsmUtils from "esm-utils";
|
||||
import prettier from "../../config/prettier-entry.js";
|
||||
|
||||
const { __dirname } = createEsmUtils(import.meta);
|
||||
const { mockable } = prettier.__debug;
|
||||
|
||||
// This don't has to be the same result as `prettier.resolveConfig`,
|
||||
// Because we are testing with default `lilconfigOptions`
|
||||
describe("lilconfig", () => {
|
||||
const { lilconfig } = mockable;
|
||||
|
||||
const configs = [
|
||||
{
|
||||
title: "prettier.config.cjs",
|
||||
dirname: path.join(__dirname, "../cli/config/js/"),
|
||||
file: path.join(__dirname, "../cli/config/js/prettier.config.cjs"),
|
||||
value: {
|
||||
endOfLine: "auto",
|
||||
tabWidth: 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "package.json",
|
||||
dirname: path.join(__dirname, "../cli/config/package/"),
|
||||
file: path.join(__dirname, "../cli/config/package/package.json"),
|
||||
value: {
|
||||
tabWidth: 3,
|
||||
overrides: [
|
||||
{
|
||||
files: "*.ts",
|
||||
options: {
|
||||
tabWidth: 5,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (const { title, dirname, file, value } of configs) {
|
||||
test(`async version ${title}`, async () => {
|
||||
const { config, filepath } = await lilconfig("prettier").search(dirname);
|
||||
expect(config).toEqual(value);
|
||||
expect(filepath).toBe(file);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("isCI", () => {
|
||||
expect(typeof mockable.isCI()).toBe("boolean");
|
||||
});
|
||||
@@ -3,7 +3,6 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import url from "node:url";
|
||||
import readline from "node:readline";
|
||||
import { lilconfig } from "lilconfig";
|
||||
import { prettierCli, prettierMainEntry } from "./env.js";
|
||||
|
||||
const normalizeToPosix =
|
||||
@@ -65,11 +64,8 @@ async function run() {
|
||||
// eslint-disable-next-line require-await
|
||||
mockable.getStdin = async () => options.input || "";
|
||||
mockable.isCI = () => Boolean(options.ci);
|
||||
mockable.lilconfig = (moduleName, options) =>
|
||||
lilconfig(moduleName, {
|
||||
...options,
|
||||
stopDir: url.fileURLToPath(new URL("./cli", import.meta.url)),
|
||||
});
|
||||
mockable.getPrettierConfigSearchStopDirectory = () =>
|
||||
url.fileURLToPath(new URL("./cli", import.meta.url));
|
||||
// eslint-disable-next-line require-await
|
||||
mockable.writeFormattedFile = async (filename, content) => {
|
||||
filename = normalizeToPosix(path.relative(process.cwd(), filename));
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
# will be created by test
|
||||
package.json
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -6214,13 +6214,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lilconfig@npm:2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "lilconfig@npm:2.1.0"
|
||||
checksum: 8549bb352b8192375fed4a74694cd61ad293904eee33f9d4866c2192865c44c4eb35d10782966242634e0cbc1e91fe62b1247f148dc5514918e3a966da7ea117
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lines-and-columns@npm:2.0.4, lines-and-columns@npm:^2.0.3":
|
||||
version: 2.0.4
|
||||
resolution: "lines-and-columns@npm:2.0.4"
|
||||
@@ -7371,7 +7364,6 @@ __metadata:
|
||||
js-yaml: 4.1.0
|
||||
json5: 2.2.3
|
||||
leven: 4.0.0
|
||||
lilconfig: 2.1.0
|
||||
lines-and-columns: 2.0.4
|
||||
linguist-languages: 7.27.0
|
||||
magic-string: 0.30.5
|
||||
|
||||
Reference in new issue
Block a user