diff --git a/src/config_prettier.ts b/src/config_prettier.ts index c82ab2c..b09e4d6 100644 --- a/src/config_prettier.ts +++ b/src/config_prettier.ts @@ -3,7 +3,7 @@ import path from "node:path"; import url from "node:url"; import zeptomatch from "zeptomatch"; import Known from "./known.js"; -import { fastJoinedPath, fastRelativeChildPath, getModulePath } from "./utils.js"; +import { fastJoinedPath, fastRelativeChildPath, getModulePath, normalizePathSeparatorsToPosix } from "./utils.js"; import { isObject, isString, isTruthy, isUndefined, memoize, noop, normalizePrettierOptions, omit, zipObjectUnless } from "./utils.js"; import type { PrettierConfig, PrettierConfigWithOverrides, PromiseMaybe } from "./types.js"; @@ -124,6 +124,10 @@ const getPrettierConfigsUp: typeof getPrettierConfigsUpImpl = memoize(getPrettie const getPrettierConfigResolved = async (filePath: string, filesNames: string[]): Promise => { const folderPath = path.dirname(filePath); const configs = await getPrettierConfigsUp(folderPath, filesNames); + return resolvePrettierConfigs(configs, filePath); +}; + +const resolvePrettierConfigs = (configs: PrettierConfigWithOverrides[], filePath: string): PrettierConfig => { let resolved: PrettierConfig = {}; for (let ci = 0, cl = configs.length; ci < cl; ci++) { @@ -135,8 +139,8 @@ const getPrettierConfigResolved = async (filePath: string, filesNames: string[]) if (overrides) { for (let oi = 0, ol = overrides.length; oi < ol; oi++) { const override = overrides[oi]; - const filePathRelative = fastRelativeChildPath(override.folder, filePath); - if (!filePathRelative) continue; + const filePathRelative = + fastRelativeChildPath(override.folder, filePath) ?? normalizePathSeparatorsToPosix(path.relative(override.folder, filePath)); if (!zeptomatch(override.filesPositive, filePathRelative)) continue; if (zeptomatch(override.filesNegative, filePathRelative)) continue; resolved = { ...resolved, ...override.options }; @@ -147,4 +151,4 @@ const getPrettierConfigResolved = async (filePath: string, filesNames: string[]) return resolved; }; -export { Loaders, File2Loader, Ext2Loader, getPrettierConfig, getPrettierConfigsMap, getPrettierConfigsUp, getPrettierConfigResolved }; +export { Loaders, File2Loader, Ext2Loader, getPrettierConfig, getPrettierConfigsMap, getPrettierConfigsUp, getPrettierConfigResolved, resolvePrettierConfigs }; diff --git a/src/index.ts b/src/index.ts index 688e14e..b834d59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import process from "node:process"; import Cache from "./cache.js"; import { getEditorConfigsMap, getEditorConfigResolved, getEditorConfigFormatOptions } from "./config_editorconfig.js"; import { getIgnoresContentMap, getIgnoreBys, getIgnoreResolved } from "./config_ignore.js"; -import { Loaders, File2Loader, getPrettierConfigsMap, getPrettierConfigResolved } from "./config_prettier.js"; +import { Loaders, File2Loader, getPrettierConfigsMap, getPrettierConfigResolved, resolvePrettierConfigs } from "./config_prettier.js"; import { PRETTIER_VERSION, CLI_VERSION } from "./constants.js"; import Known from "./known.js"; import Logger from "./logger.js"; @@ -31,12 +31,13 @@ import { isUndefined, negate, normalizePathSeparatorsToPosix, + normalizePrettierOptions, pluralize, trimFinalNewline, uniq, without, } from "./utils.js"; -import type { FormatOptions, Options, PluginsOptions } from "./types.js"; +import type { FormatOptions, Options, PluginsOptions, PrettierConfigWithOverrides } from "./types.js"; async function run(options: Options, pluginsDefaultOptions: PluginsOptions, pluginsCustomOptions: PluginsOptions): Promise { if (options.globs.length || !isString(await getStdin())) { @@ -53,7 +54,7 @@ async function getConfigNames( prettierConfigNames: string[]; editorConfigNames: string[]; ignoreNames: string[]; - prettierManualConfig?: Record; + prettierManualConfigs?: PrettierConfigWithOverrides[]; prettierManualFilesPaths: string[]; }> { const useEditorConfig = overrides?.editorConfig ?? options.editorConfig; @@ -64,14 +65,16 @@ async function getConfigNames( const prettierConfigNames = useConfig ? without(Object.keys(File2Loader), ["default"]).filter(Known.hasFileName) : []; const prettierManualFilesNames = options.configPath || []; const prettierManualFilesPaths = prettierManualFilesNames.map((fileName) => path.resolve(fileName)); - const prettierManualConfigs = await Promise.all(prettierManualFilesPaths.map(Loaders.auto)); - const prettierManualConfig = prettierManualConfigs.length ? Object.assign({}, ...prettierManualConfigs) : undefined; + const prettierManualConfigsRaw = await Promise.all(prettierManualFilesPaths.map(Loaders.auto)); + const prettierManualConfigs = prettierManualConfigsRaw.length + ? prettierManualConfigsRaw.map((config, i) => normalizePrettierOptions(config, path.dirname(prettierManualFilesPaths[i]))) + : undefined; return { prettierConfigNames, editorConfigNames, ignoreNames, - prettierManualConfig, + prettierManualConfigs, prettierManualFilesPaths, }; } @@ -96,7 +99,7 @@ async function runStdin(options: Options, pluginsDefaultOptions: PluginsOptions, Known.addFilesNames(filesNames); } - const { prettierConfigNames, editorConfigNames, prettierManualConfig } = await getConfigNames(options, { ignore: false }); + const { prettierConfigNames, editorConfigNames, prettierManualConfigs } = await getConfigNames(options, { ignore: false }); const getFormatOptions = async (): Promise => { if (!options.stdinFilepath) { @@ -105,7 +108,11 @@ async function runStdin(options: Options, pluginsDefaultOptions: PluginsOptions, const filePath = path.resolve(options.stdinFilepath); const editorConfig = options.editorConfig ? getEditorConfigFormatOptions(await getEditorConfigResolved(filePath, editorConfigNames)) : {}; - const prettierConfig = prettierManualConfig || (options.config ? await getPrettierConfigResolved(filePath, prettierConfigNames) : {}); + const prettierConfig = prettierManualConfigs + ? resolvePrettierConfigs(prettierManualConfigs, filePath) + : options.config + ? await getPrettierConfigResolved(filePath, prettierConfigNames) + : {}; const formatOptions = { ...editorConfig, ...prettierConfig, ...options.formatOptions }; return formatOptions; }; @@ -154,7 +161,7 @@ async function runGlobs(options: Options, pluginsDefaultOptions: PluginsOptions, const pluginsVersions = getPluginsVersions(pluginsNames); const pluginsVersionsMissing = pluginsVersions.filter(isNull); - const { prettierConfigNames, editorConfigNames, ignoreNames, prettierManualConfig, prettierManualFilesPaths } = await getConfigNames(options); + const { prettierConfigNames, editorConfigNames, ignoreNames, prettierManualConfigs, prettierManualFilesPaths } = await getConfigNames(options); const fileNames2parentPaths = (names: string[]) => names.flatMap((name) => filesNamesToPaths[name]?.map(path.dirname) || []); const editorConfigPaths = uniq([...fileNames2parentPaths(editorConfigNames), rootPath, ...foldersExtraPaths]); @@ -195,7 +202,11 @@ async function runGlobs(options: Options, pluginsDefaultOptions: PluginsOptions, if (!isForceIncluded && isExcluded) return; const getFormatOptions = async (): Promise => { const editorConfig = options.editorConfig ? getEditorConfigFormatOptions(await getEditorConfigResolved(filePath, editorConfigNames)) : {}; - const prettierConfig = prettierManualConfig || (options.config ? await getPrettierConfigResolved(filePath, prettierConfigNames) : {}); + const prettierConfig = prettierManualConfigs + ? resolvePrettierConfigs(prettierManualConfigs, filePath) + : options.config + ? await getPrettierConfigResolved(filePath, prettierConfigNames) + : {}; const formatOptions = { ...editorConfig, ...prettierConfig, ...options.formatOptions }; return formatOptions; }; diff --git a/src/utils.ts b/src/utils.ts index b3679cd..8ecfd2d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -578,22 +578,24 @@ function normalizePrettierOptions(options: unknown, folderPath: string): Prettie const config: PrettierConfigWithOverrides = normalizeFormatOptions(options); - if ("overrides" in options && isArray(options.overrides)) { - const overridesRaw = options.overrides; - for (let i = 0, l = overridesRaw.length; i < l; i++) { - const overrideRaw = overridesRaw[i]; - if (!isObject(overrideRaw)) continue; - if (!("files" in overrideRaw)) continue; - if (!isString(overrideRaw.files) && (!isArray(overrideRaw.files) || !overrideRaw.files.every(isString))) continue; - if (isArray(overrideRaw.files) && !overrideRaw.files.length) continue; - if (!("options" in overrideRaw)) continue; - if (!isObject(overrideRaw.options)) continue; - const overrides = (config.overrides ||= []); - const filesPositive = castArray(overrideRaw.files); - const filesNegative = "filesNegative" in overrideRaw && (isString(overrideRaw.filesNegative) || (isArray(overrideRaw.filesNegative) && overrideRaw.filesNegative.every(isString))) ? castArray(overrideRaw.filesNegative) : []; // prettier-ignore - const folder = folderPath; - const options = normalizeFormatOptions(overrideRaw.options); - overrides.push({ filesPositive, filesNegative, folder, options }); + if ("overrides" in options) { + const overrides: NonNullable = (config.overrides = []); + if (isArray(options.overrides)) { + const overridesRaw = options.overrides; + for (let i = 0, l = overridesRaw.length; i < l; i++) { + const overrideRaw = overridesRaw[i]; + if (!isObject(overrideRaw)) continue; + if (!("files" in overrideRaw)) continue; + if (!isString(overrideRaw.files) && (!isArray(overrideRaw.files) || !overrideRaw.files.every(isString))) continue; + if (isArray(overrideRaw.files) && !overrideRaw.files.length) continue; + if (!("options" in overrideRaw)) continue; + if (!isObject(overrideRaw.options)) continue; + const filesPositive = castArray(overrideRaw.files); + const filesNegative = "filesNegative" in overrideRaw && (isString(overrideRaw.filesNegative) || (isArray(overrideRaw.filesNegative) && overrideRaw.filesNegative.every(isString))) ? castArray(overrideRaw.filesNegative) : []; // prettier-ignore + const folder = folderPath; + const options = normalizeFormatOptions(overrideRaw.options); + overrides.push({ filesPositive, filesNegative, folder, options }); + } } } diff --git a/test/__fixtures__/config-path-other-dir/config/prettier.json b/test/__fixtures__/config-path-other-dir/config/prettier.json new file mode 100644 index 0000000..a879f0c --- /dev/null +++ b/test/__fixtures__/config-path-other-dir/config/prettier.json @@ -0,0 +1,17 @@ +{ + "semi": true, + "overrides": [ + { + "files": "src/*.js", + "options": { + "semi": false + } + }, + { + "files": "../lib/*.js", + "options": { + "semi": false + } + } + ] +} diff --git a/test/__fixtures__/config-path-other-dir/config/src/foo.js b/test/__fixtures__/config-path-other-dir/config/src/foo.js new file mode 100644 index 0000000..ce4dbb2 --- /dev/null +++ b/test/__fixtures__/config-path-other-dir/config/src/foo.js @@ -0,0 +1 @@ +const x = "foo"; diff --git a/test/__fixtures__/config-path-other-dir/lib/foo.js b/test/__fixtures__/config-path-other-dir/lib/foo.js new file mode 100644 index 0000000..ce4dbb2 --- /dev/null +++ b/test/__fixtures__/config-path-other-dir/lib/foo.js @@ -0,0 +1 @@ +const x = "foo"; diff --git a/test/__fixtures__/config-path-other-dir/src/foo.js b/test/__fixtures__/config-path-other-dir/src/foo.js new file mode 100644 index 0000000..ce4dbb2 --- /dev/null +++ b/test/__fixtures__/config-path-other-dir/src/foo.js @@ -0,0 +1 @@ +const x = "foo"; diff --git a/test/__tests__/config-path-other-dir.js b/test/__tests__/config-path-other-dir.js new file mode 100644 index 0000000..2069701 --- /dev/null +++ b/test/__tests__/config-path-other-dir.js @@ -0,0 +1,40 @@ +import { runCli } from "../utils"; + +describe("--config-path in another dir resolves override globs relative to the config file", () => { + runCli("config-path-other-dir", [ + "--config-path", + "config/prettier.json", + "config/src/foo.js", + ]).test({ + status: 0, + stdout: `const x = "foo"`, + stderr: "", + write: [], + }); +}); + +describe("--config-path in another dir does not resolve override globs relative to the cwd", () => { + runCli("config-path-other-dir", [ + "--config-path", + "config/prettier.json", + "src/foo.js", + ]).test({ + status: 0, + stdout: `const x = "foo";`, + stderr: "", + write: [], + }); +}); + +describe("--config-path in another dir resolves override globs pointing outside the config dir", () => { + runCli("config-path-other-dir", [ + "--config-path", + "config/prettier.json", + "lib/foo.js", + ]).test({ + status: 0, + stdout: `const x = "foo"`, + stderr: "", + write: [], + }); +});