Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/config_prettier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -124,6 +124,10 @@ const getPrettierConfigsUp: typeof getPrettierConfigsUpImpl = memoize(getPrettie
const getPrettierConfigResolved = async (filePath: string, filesNames: string[]): Promise<PrettierConfig> => {
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++) {
Expand All @@ -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 };
Expand All @@ -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 };
31 changes: 21 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<void> {
if (options.globs.length || !isString(await getStdin())) {
Expand All @@ -53,7 +54,7 @@ async function getConfigNames(
prettierConfigNames: string[];
editorConfigNames: string[];
ignoreNames: string[];
prettierManualConfig?: Record<string, unknown>;
prettierManualConfigs?: PrettierConfigWithOverrides[];
prettierManualFilesPaths: string[];
}> {
const useEditorConfig = overrides?.editorConfig ?? options.editorConfig;
Expand All @@ -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,
};
}
Expand All @@ -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<FormatOptions> => {
if (!options.stdinFilepath) {
Expand All @@ -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;
};
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -195,7 +202,11 @@ async function runGlobs(options: Options, pluginsDefaultOptions: PluginsOptions,
if (!isForceIncluded && isExcluded) return;
const getFormatOptions = async (): Promise<FormatOptions> => {
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;
};
Expand Down
34 changes: 18 additions & 16 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PrettierConfigWithOverrides["overrides"]> = (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 });
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions test/__fixtures__/config-path-other-dir/config/prettier.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"semi": true,
"overrides": [
{
"files": "src/*.js",
"options": {
"semi": false
}
},
{
"files": "../lib/*.js",
"options": {
"semi": false
}
}
]
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just a//b/ or foo bar instead of src//lib/

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no particular reason. its just what i was testing it against so the fixture ended up the same name.

happy to change it if needed

1 change: 1 addition & 0 deletions test/__fixtures__/config-path-other-dir/config/src/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const x = "foo";
1 change: 1 addition & 0 deletions test/__fixtures__/config-path-other-dir/lib/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const x = "foo";
1 change: 1 addition & 0 deletions test/__fixtures__/config-path-other-dir/src/foo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const x = "foo";
40 changes: 40 additions & 0 deletions test/__tests__/config-path-other-dir.js
Original file line number Diff line number Diff line change
@@ -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",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classic CLI uses --config, not --config-path, a known bug?

https://prettier.io/docs/next/cli#--find-config-path-and---config

@43081j 43081j Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has always used --config from when Fabio first made it, i just assumed it was on purpose to make it clearer what it is. i don't think it can be considered a bug, its just a choice to be made

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"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: [],
});
});
Loading