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
10 changes: 6 additions & 4 deletions src/config_editorconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,31 @@ import { fastJoinedPath, findLastIndex, isUndefined, memoize, noop, zipObjectUnl
import type { Config, ConfigWithOverrides } from "tiny-editorconfig";
import type { FormatOptions, PromiseMaybe } from "./types.js";

const getEditorConfig = memoize((folderPath: string, filesNames: string[]): PromiseMaybe<ConfigWithOverrides | undefined> => {
const getEditorConfigImpl = (folderPath: string, filesNames: string[]): PromiseMaybe<ConfigWithOverrides | undefined> => {
for (let i = 0, l = filesNames.length; i < l; i++) {
const fileName = filesNames[i];
const filePath = fastJoinedPath(folderPath, fileName);
if (!Known.hasFilePath(filePath)) continue;
return fs.readFile(filePath, "utf8").then(EditorConfig.parse).catch(noop);
}
});
};
const getEditorConfig: typeof getEditorConfigImpl = memoize(getEditorConfigImpl);

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.

typescript errors on main if we don't do this. i guess it got bumped at some point and is no longer happy about the return type using an unexported type (as in we don't re-export it)


const getEditorConfigsMap = async (foldersPaths: string[], filesNames: string[]): Promise<Partial<Record<string, ConfigWithOverrides>>> => {
const configs = await Promise.all(foldersPaths.map((folderPath) => getEditorConfig(folderPath, filesNames)));
const map = zipObjectUnless(foldersPaths, configs, isUndefined);
return map;
};

const getEditorConfigsUp = memoize(async (folderPath: string, filesNames: string[]): Promise<ConfigWithOverrides[]> => {
const getEditorConfigsUpImpl = async (folderPath: string, filesNames: string[]): Promise<ConfigWithOverrides[]> => {
const config = await getEditorConfig(folderPath, filesNames);
const folderPathUp = path.dirname(folderPath);
const configsUp = folderPath !== folderPathUp ? await getEditorConfigsUp(folderPathUp, filesNames) : [];
const configs = config ? [...configsUp, config] : configsUp;
const lastRootIndex = findLastIndex(configs, (config) => config.root);
return lastRootIndex > 0 ? configs.slice(lastRootIndex) : configs;
});
};
const getEditorConfigsUp: typeof getEditorConfigsUpImpl = memoize(getEditorConfigsUpImpl);

const getEditorConfigResolved = async (filePath: string, filesNames: string[]): Promise<Config> => {
const folderPath = path.dirname(filePath);
Expand Down
15 changes: 9 additions & 6 deletions src/config_ignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ const getIgnoreContent = (folderPath: string, fileName: string): PromiseMaybe<st
return fs.readFile(filePath, "utf8").catch(noop);
};

const getIgnoresContent = memoize(async (folderPath: string, filesNames: string[]): Promise<string[] | undefined> => {
const getIgnoresContentImpl = async (folderPath: string, filesNames: string[]): Promise<string[] | undefined> => {
const contentsRaw = await Promise.all(filesNames.map((fileName) => getIgnoreContent(folderPath, fileName)));
const contents = contentsRaw.filter(isString);
if (!contents.length) return;
return contents;
});
};
const getIgnoresContent: typeof getIgnoresContentImpl = memoize(getIgnoresContentImpl);

const getIgnoresContentMap = async (foldersPaths: string[], filesNames: string[]): Promise<Partial<Record<string, string[]>>> => {
const contents = await Promise.all(foldersPaths.map((folderPath) => getIgnoresContent(folderPath, filesNames)));
Expand All @@ -39,22 +40,24 @@ const getIgnoreBys = (foldersPaths: string[], filesContents: string[][]): Ignore
return ignore;
};

const getIgnores = memoize(async (folderPath: string, filesNames: string[]): Promise<Ignore | undefined> => {
const getIgnoresImpl = async (folderPath: string, filesNames: string[]): Promise<Ignore | undefined> => {
const contents = await getIgnoresContent(folderPath, filesNames);
if (!contents?.length) return;
const ignore = getIgnoreBy(folderPath, contents);
return ignore;
});
};
const getIgnores: typeof getIgnoresImpl = memoize(getIgnoresImpl);

const getIgnoresUp = memoize(async (folderPath: string, filesNames: string[]): Promise<Ignore | undefined> => {
const getIgnoresUpImpl = async (folderPath: string, filesNames: string[]): Promise<Ignore | undefined> => {
const ignore = await getIgnores(folderPath, filesNames);
const folderPathUp = path.dirname(folderPath);
const ignoreUp = folderPath !== folderPathUp ? await getIgnoresUp(folderPathUp, filesNames) : undefined;
const ignores = ignore ? (ignoreUp ? [ignore, ignoreUp] : [ignore]) : ignoreUp ? [ignoreUp] : [];
if (!ignores.length) return;
const ignoreAll = someOf(ignores);
return ignoreAll;
});
};
const getIgnoresUp: typeof getIgnoresUpImpl = memoize(getIgnoresUpImpl);

const getIgnoreResolved = async (filePath: string, filesNames: string[]): Promise<boolean> => {
const folderPath = path.dirname(filePath);
Expand Down
5 changes: 3 additions & 2 deletions src/config_prettier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,14 @@ const getPrettierConfigsMap = async (foldersPaths: string[], filesNames: string[
return map;
};

const getPrettierConfigsUp = memoize(async (folderPath: string, filesNames: string[]): Promise<PrettierConfigWithOverrides[]> => {
const getPrettierConfigsUpImpl = async (folderPath: string, filesNames: string[]): Promise<PrettierConfigWithOverrides[]> => {
const config = (await getPrettierConfigs(folderPath, filesNames))?.[0];
const folderPathUp = path.dirname(folderPath);
const configsUp = folderPath !== folderPathUp ? await getPrettierConfigsUp(folderPathUp, filesNames) : [];
const configs = config ? [...configsUp, config] : configsUp;
return configs;
});
};
const getPrettierConfigsUp: typeof getPrettierConfigsUpImpl = memoize(getPrettierConfigsUpImpl);

const getPrettierConfigResolved = async (filePath: string, filesNames: string[]): Promise<PrettierConfig> => {
const folderPath = path.dirname(filePath);
Expand Down
69 changes: 60 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
castArray,
getCacheRootPath,
getExpandedFoldersPaths,
getFolderChildrenPaths,
getFoldersChildrenPaths,
getPluginsVersions,
getProjectPath,
Expand Down Expand Up @@ -43,6 +44,36 @@ async function run(options: Options, pluginsDefaultOptions: PluginsOptions, plug
}
}

async function getConfigNames(
options: Options,
overrides?: Partial<Pick<Options, "ignore" | "config" | "editorConfig">>,
): Promise<{
prettierConfigNames: string[];
editorConfigNames: string[];
ignoreNames: string[];
prettierManualConfig?: Record<string, unknown>;
prettierManualFilesPaths: string[];
}> {
const useEditorConfig = overrides?.editorConfig ?? options.editorConfig;
const useIgnore = overrides?.ignore ?? options.ignore;
const useConfig = overrides?.config ?? options.config;
const editorConfigNames = useEditorConfig ? [".editorconfig"].filter(Known.hasFileName) : [];
const ignoreNames = useIgnore ? [".gitignore", ".prettierignore"].filter(Known.hasFileName) : [];
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;

return {
prettierConfigNames,
editorConfigNames,
ignoreNames,
prettierManualConfig,
prettierManualFilesPaths,
};
}

async function runStdin(options: Options, pluginsDefaultOptions: PluginsOptions, pluginsCustomOptions: PluginsOptions): Promise<void> {
const stderr = new Logger(options.logLevel, "stderr");
const stdout = new Logger(options.logLevel, "stdout");
Expand All @@ -51,8 +82,34 @@ async function runStdin(options: Options, pluginsDefaultOptions: PluginsOptions,
const fileName = options.stdinFilepath || "stdin";
const fileContent = (await getStdin()) || "";

if (options.stdinFilepath) {
const rootPath = process.cwd();
const projectPath = getProjectPath(rootPath);
const fileFolderPath = path.dirname(path.resolve(options.stdinFilepath));
const [foldersPaths, foldersExtraPaths] = getExpandedFoldersPaths([fileFolderPath], projectPath);
const foldersToScan = [rootPath, ...foldersPaths, ...foldersExtraPaths];
const filesPaths = (await Promise.all(foldersToScan.map((folderPath) => getFolderChildrenPaths(folderPath).catch(() => [])))).flat();
const filesNames = filesPaths.map((filePath) => path.basename(filePath));
Known.addFilesPaths(filesPaths);
Known.addFilesNames(filesNames);
}

const { prettierConfigNames, editorConfigNames, prettierManualConfig } = await getConfigNames(options, { ignore: false });

const getFormatOptions = async (): Promise<FormatOptions> => {
if (!options.stdinFilepath) {
return options.formatOptions;
}

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 formatOptions = { ...editorConfig, ...prettierConfig, ...options.formatOptions };
return formatOptions;
};

try {
const formatted = await prettier.format(fileName, fileContent, options.formatOptions, options.contextOptions, pluginsDefaultOptions, pluginsCustomOptions);
const formatted = await prettier.format(fileName, fileContent, getFormatOptions, options.contextOptions, pluginsDefaultOptions, pluginsCustomOptions);
if (options.check || options.list) {
if (formatted !== fileContent) {
stdout.warn("(stdin)");
Expand All @@ -79,7 +136,7 @@ async function runGlobs(options: Options, pluginsDefaultOptions: PluginsOptions,
const [filesPaths, filesNames, filesNamesToPaths, filesExplicitPaths, filesFoundPaths, foldersFoundPaths] = await getTargetsPaths(rootPath, options.globs, options.withNodeModules); // prettier-ignore
const filesExplicitPathsSet = new Set(filesExplicitPaths);
const filesPathsTargets = filesPaths.filter(negate(isBinaryPath)).sort();
const [foldersPathsTargets, foldersExtraPaths] = getExpandedFoldersPaths(foldersFoundPaths, projectPath);
const [, foldersExtraPaths] = getExpandedFoldersPaths(foldersFoundPaths, projectPath);
const filesExtraPaths = await getFoldersChildrenPaths([rootPath, ...foldersExtraPaths]);
const filesExtraNames = filesExtraPaths.map((filePath) => path.basename(filePath));

Expand All @@ -95,9 +152,7 @@ async function runGlobs(options: Options, pluginsDefaultOptions: PluginsOptions,
const pluginsVersions = getPluginsVersions(pluginsNames);
const pluginsVersionsMissing = pluginsVersions.filter(isNull);

const editorConfigNames = options.editorConfig ? [".editorconfig"].filter(Known.hasFileName) : [];
const ignoreNames = options.ignore ? [".gitignore", ".prettierignore"].filter(Known.hasFileName) : [];
const prettierConfigNames = options.config ? without(Object.keys(File2Loader), ["default"]).filter(Known.hasFileName) : [];
const { prettierConfigNames, editorConfigNames, ignoreNames, prettierManualConfig, prettierManualFilesPaths } = await getConfigNames(options);

const fileNames2parentPaths = (names: string[]) => names.flatMap((name) => filesNamesToPaths[name]?.map(path.dirname) || []);
const editorConfigPaths = uniq([...fileNames2parentPaths(editorConfigNames), rootPath, ...foldersExtraPaths]);
Expand All @@ -114,11 +169,7 @@ async function runGlobs(options: Options, pluginsDefaultOptions: PluginsOptions,
const ignoreManualFoldersPaths = ignoreManualFilesPaths.map(path.dirname);
const ignoreManual = getIgnoreBys(ignoreManualFoldersPaths, ignoreManualFilesContents.map(castArray));

const prettierManualFilesNames = options.configPath || [];
const prettierManualFilesPaths = prettierManualFilesNames.map((fileName) => path.resolve(fileName));
const prettierManualFilesContents = await Promise.all(prettierManualFilesPaths.map((filePath) => fs.readFile(filePath, "utf8")));
const prettierManualConfigs = await Promise.all(prettierManualFilesPaths.map(Loaders.auto));
const prettierManualConfig = prettierManualConfigs.length ? Object.assign({}, ...prettierManualConfigs) : undefined;

const cliContextConfig = options.contextOptions;
const cliFormatConfig = options.formatOptions;
Expand Down
5 changes: 3 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,12 @@ function identity<T>(value: T): T {
return value;
}

const getPlugin = memoize((name: string): Promise<PrettierPlugin> => {
const getPluginImpl = (name: string): Promise<PrettierPlugin> => {
const pluginPath = getPluginPath(name);
const plugin = getModule<PrettierPlugin>(pluginPath);
return plugin;
});
};
const getPlugin: typeof getPluginImpl = memoize(getPluginImpl);

async function getPluginOrExit(name: string): Promise<PrettierPlugin> {
try {
Expand Down
3 changes: 3 additions & 0 deletions test/__fixtures__/stdin-filepath-config/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
29 changes: 29 additions & 0 deletions test/__tests__/stdin-filepath-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { runCli } from "../utils";

describe("--stdin-filepath discovers config on disk", () => {
runCli("stdin-filepath-config", [
"--stdin-filepath",
"example.js",
], {
input: `const x = "foo"`,
}).test({
status: 0,
stdout: `const x = 'foo';`,
stderr: "",
write: [],
});
});

describe("--stdin-filepath with a non-existent path still discovers ancestor config", () => {
runCli("stdin-filepath-config", [
"--stdin-filepath",
"./non-existent-dir/non-existent-file.js",
], {
input: `const x = "foo"`,
}).test({
status: 0,
stdout: `const x = 'foo';`,
stderr: "",
write: [],
});
});
Loading