diff --git a/src/config_editorconfig.ts b/src/config_editorconfig.ts index aee3ccb..a8815a7 100644 --- a/src/config_editorconfig.ts +++ b/src/config_editorconfig.ts @@ -6,14 +6,15 @@ 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 => { +const getEditorConfigImpl = (folderPath: string, filesNames: string[]): PromiseMaybe => { 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); const getEditorConfigsMap = async (foldersPaths: string[], filesNames: string[]): Promise>> => { const configs = await Promise.all(foldersPaths.map((folderPath) => getEditorConfig(folderPath, filesNames))); @@ -21,14 +22,15 @@ const getEditorConfigsMap = async (foldersPaths: string[], filesNames: string[]) return map; }; -const getEditorConfigsUp = memoize(async (folderPath: string, filesNames: string[]): Promise => { +const getEditorConfigsUpImpl = async (folderPath: string, filesNames: string[]): Promise => { 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 => { const folderPath = path.dirname(filePath); diff --git a/src/config_ignore.ts b/src/config_ignore.ts index 6bc759d..f0504ea 100644 --- a/src/config_ignore.ts +++ b/src/config_ignore.ts @@ -11,12 +11,13 @@ const getIgnoreContent = (folderPath: string, fileName: string): PromiseMaybe => { +const getIgnoresContentImpl = async (folderPath: string, filesNames: string[]): Promise => { 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>> => { const contents = await Promise.all(foldersPaths.map((folderPath) => getIgnoresContent(folderPath, filesNames))); @@ -39,14 +40,15 @@ const getIgnoreBys = (foldersPaths: string[], filesContents: string[][]): Ignore return ignore; }; -const getIgnores = memoize(async (folderPath: string, filesNames: string[]): Promise => { +const getIgnoresImpl = async (folderPath: string, filesNames: string[]): Promise => { 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 => { +const getIgnoresUpImpl = async (folderPath: string, filesNames: string[]): Promise => { const ignore = await getIgnores(folderPath, filesNames); const folderPathUp = path.dirname(folderPath); const ignoreUp = folderPath !== folderPathUp ? await getIgnoresUp(folderPathUp, filesNames) : undefined; @@ -54,7 +56,8 @@ const getIgnoresUp = memoize(async (folderPath: string, filesNames: string[]): P if (!ignores.length) return; const ignoreAll = someOf(ignores); return ignoreAll; -}); +}; +const getIgnoresUp: typeof getIgnoresUpImpl = memoize(getIgnoresUpImpl); const getIgnoreResolved = async (filePath: string, filesNames: string[]): Promise => { const folderPath = path.dirname(filePath); diff --git a/src/config_prettier.ts b/src/config_prettier.ts index 22ebabb..c82ab2c 100644 --- a/src/config_prettier.ts +++ b/src/config_prettier.ts @@ -112,13 +112,14 @@ const getPrettierConfigsMap = async (foldersPaths: string[], filesNames: string[ return map; }; -const getPrettierConfigsUp = memoize(async (folderPath: string, filesNames: string[]): Promise => { +const getPrettierConfigsUpImpl = async (folderPath: string, filesNames: string[]): Promise => { 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 => { const folderPath = path.dirname(filePath); diff --git a/src/index.ts b/src/index.ts index d9b9158..4d0ad2e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import { castArray, getCacheRootPath, getExpandedFoldersPaths, + getFolderChildrenPaths, getFoldersChildrenPaths, getPluginsVersions, getProjectPath, @@ -43,6 +44,36 @@ async function run(options: Options, pluginsDefaultOptions: PluginsOptions, plug } } +async function getConfigNames( + options: Options, + overrides?: Partial>, +): Promise<{ + prettierConfigNames: string[]; + editorConfigNames: string[]; + ignoreNames: string[]; + prettierManualConfig?: Record; + 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 { const stderr = new Logger(options.logLevel, "stderr"); const stdout = new Logger(options.logLevel, "stdout"); @@ -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 => { + 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)"); @@ -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)); @@ -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]); @@ -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; diff --git a/src/utils.ts b/src/utils.ts index a71f4e7..65fcd63 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -131,11 +131,12 @@ function identity(value: T): T { return value; } -const getPlugin = memoize((name: string): Promise => { +const getPluginImpl = (name: string): Promise => { const pluginPath = getPluginPath(name); const plugin = getModule(pluginPath); return plugin; -}); +}; +const getPlugin: typeof getPluginImpl = memoize(getPluginImpl); async function getPluginOrExit(name: string): Promise { try { diff --git a/test/__fixtures__/stdin-filepath-config/.prettierrc.json b/test/__fixtures__/stdin-filepath-config/.prettierrc.json new file mode 100644 index 0000000..544138b --- /dev/null +++ b/test/__fixtures__/stdin-filepath-config/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/test/__tests__/stdin-filepath-config.js b/test/__tests__/stdin-filepath-config.js new file mode 100644 index 0000000..8f8530f --- /dev/null +++ b/test/__tests__/stdin-filepath-config.js @@ -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: [], + }); +});