diff --git a/package-lock.json b/package-lock.json index 4eb7671..83355f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@types/js-yaml": "^4.0.9", "@types/node": "^22.15.10", "cross-env": "^7.0.3", + "dedent": "^1.5.3", "jest": "^29.7.0", "jest-snapshot-serializer-ansi": "^2.2.1", "jest-snapshot-serializer-raw": "^2.0.0", @@ -2313,6 +2314,7 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, + "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, diff --git a/package.json b/package.json index 2c332ce..1b62db9 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@types/js-yaml": "^4.0.9", "@types/node": "^22.15.10", "cross-env": "^7.0.3", + "dedent": "^1.5.3", "jest": "^29.7.0", "jest-snapshot-serializer-ansi": "^2.2.1", "jest-snapshot-serializer-raw": "^2.0.0", diff --git a/src/index.ts b/src/index.ts index fd7319d..6b584a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ import { fastRelativePath, isNull, isString, isUndefined, negate, pluralize, tri import type { FormatOptions, Options, PluginsOptions } from "./types.js"; async function run(options: Options, pluginsDefaultOptions: PluginsOptions, pluginsCustomOptions: PluginsOptions): Promise { - if (options.globs.length || !isString(await getStdin())) { + if (options.globs.length || (!isString(await getStdin()) && !("stdinFilepath" in options))) { return runGlobs(options, pluginsDefaultOptions, pluginsCustomOptions); } else { return runStdin(options, pluginsDefaultOptions, pluginsCustomOptions); @@ -28,11 +28,43 @@ async function runStdin(options: Options, pluginsDefaultOptions: PluginsOptions, const stdout = new Logger(options.logLevel, "stdout"); const prettier = await import("./prettier_serial.js"); + const rootPath = process.cwd(); + const projectPath = getProjectPath(rootPath); + const fileName = options.stdinFilepath || "stdin"; const fileContent = (await getStdin()) || ""; + const [_filesPaths, filesNames, filesNamesToPaths, _filesExplicitPaths, filesFoundPaths, foldersFoundPaths] = await getTargetsPaths(rootPath, [fileName], false); // prettier-ignore + const [_foldersPathsTargets, foldersExtraPaths] = getExpandedFoldersPaths(foldersFoundPaths, projectPath); + const filesExtraPaths = await getFoldersChildrenPaths([rootPath, ...foldersExtraPaths]); + const filesExtraNames = filesExtraPaths.map((filePath) => path.basename(filePath)); + + Known.addFilesPaths(filesFoundPaths); + Known.addFilesPaths(filesExtraPaths); + + Known.addFilesNames(filesNames); + Known.addFilesNames(filesExtraNames); + + const ignoreNames = options.ignore ? [".gitignore", ".prettierignore"] : []; + const isIgnored = await getIgnoreResolved(path.join(rootPath, fileName), ignoreNames); + if (isIgnored) { + stdout.always(trimFinalNewline(fileContent)); + process.exitCode = 0; + return; + } + + const editorConfigNames = options.editorConfig ? [".editorconfig"].filter(Known.hasFileName) : []; + const editorConfig = options.editorConfig + ? getEditorConfigFormatOptions(await getEditorConfigResolved(path.join(rootPath, fileName), editorConfigNames)) + : {}; + + const prettierConfigNames = options.config ? without(Object.keys(File2Loader), ["default"]).filter(Known.hasFileName) : []; + const prettierConfig = options.config ? await getPrettierConfigResolved(path.join(rootPath, fileName), prettierConfigNames) : {}; + + const formatOptions = { ...editorConfig, ...prettierConfig, ...options.formatOptions }; + try { - const formatted = await prettier.format(fileName, fileContent, options.formatOptions, options.contextOptions, pluginsDefaultOptions, pluginsCustomOptions); + const formatted = await prettier.format(fileName, fileContent, formatOptions, options.contextOptions, pluginsDefaultOptions, pluginsCustomOptions); if (options.check || options.list) { if (formatted !== fileContent) { stdout.warn("(stdin)"); diff --git a/src/utils.ts b/src/utils.ts index decbab3..7acd5ab 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -41,6 +41,10 @@ function fastRelativeChildPath(fromPath: string, toPath: string): string | undef return toPath.slice(fromPath.length + 1); } } + + if (fromPath === ".") { + return toPath; + } } function findLastIndex(array: T[], predicate: (value: T, index: number, array: T[]) => unknown): number { @@ -343,7 +347,9 @@ async function normalizeOptions(options: unknown, targets: unknown[]): Promise 1 | .name { display: none; } +[error] | ^" +`; + +exports[`throw error if stdin content incompatible with stdin-filepath (stdout) 1`] = `""`; + +exports[`throw error if stdin content incompatible with stdin-filepath (write) 1`] = `[]`; diff --git a/test/__tests__/stdin-filepath.js b/test/__tests__/stdin-filepath.js new file mode 100644 index 0000000..98b486d --- /dev/null +++ b/test/__tests__/stdin-filepath.js @@ -0,0 +1,151 @@ +import { runCli } from "../utils"; +import dedent from "dedent"; + +describe("format correctly if stdin content compatible with stdin-filepath", () => { + runCli( + "", + ["--stdin-filepath", "abc.css"], + { input: ".name { display: none; }" }, // css + ).test({ + status: 0, + }); +}); + +describe("throw error if stdin content incompatible with stdin-filepath", () => { + runCli( + "", + ["--stdin-filepath", "abc.js"], + { input: ".name { display: none; }" }, // css + ).test({ + status: "non-zero", + }); +}); + +describe("gracefully handle stdin-filepath with nonexistent directory", () => { + runCli( + "", + ["--stdin-filepath", "definitely/nonexistent/path.css"], + { input: ".name { display: none; }" }, // css + ).test({ + status: 0, + }); +}); + +describe("apply editorconfig for stdin-filepath with nonexistent file", () => { + runCli("editorconfig", ["--stdin-filepath", "nonexistent.js"], { + input: dedent` + function f() { + console.log("should be indented with a tab"); + } + `, // js + }).test({ + status: 0, + }); +}); + +describe("apply editorconfig for stdin-filepath with nonexistent directory", () => { + runCli( + "editorconfig", + ["--stdin-filepath", "nonexistent/one/two/three.js"], + { + input: dedent` + function f() { + console.log("should be indented with a tab"); + } + `, // js + }, + ).test({ + status: 0, + }); +}); + +describe("apply editorconfig for stdin-filepath with a deep path", () => { + runCli( + "editorconfig", + ["--stdin-filepath", "a/".repeat(30) + "three.js"], + { + input: dedent` + function f() { + console.log("should be indented with a tab"); + } + `, // js + }, + ).test({ + status: 0, + }); +}); + +// TODO: This is currently a false positive as no config actually gets resolved, but Prettier +// somehow formats the input correctly anyway. +describe("apply editorconfig for stdin-filepath in root", () => { + const code = dedent` + function f() { + console.log("should be indented with a tab"); + } + `; + runCli("", ["--stdin-filepath", "/foo.js"], { + input: code, // js + }).test({ + status: 0, + stdout: code, + stderr: "", + write: [], + }); +}); + +describe("apply editorconfig for stdin-filepath with a deep path", () => { + runCli( + "editorconfig", + ["--stdin-filepath", "a/".repeat(30) + "three.js"], + { + input: dedent` + function f() { + console.log("should be indented with a tab"); + } + `, // js + }, + ).test({ + status: 0, + }); +}); + +// TODO: This is currently a false positive. Gotta investigate how it's handled in Prettier v3 to +// gauge the expected behavior. +describe("don't apply editorconfig outside project for stdin-filepath with nonexistent directory", () => { + runCli( + "", + [ + "--stdin-filepath", + "editorconfig/repo-root/nonexistent/one/two/three.js", + ], + { + input: dedent` + function f() { + console.log("should be indented with 2 spaces"); + } + `, // js + }, + ).test({ + status: 0, + }); +}); + +describe("output file as-is if stdin-filepath matched patterns in ignore-path", () => { + runCli("stdin-ignore", ["--stdin-filepath", "ignore/example.js"], { + input: "hello_world( );", + }).test({ + stdout: "hello_world( );", + status: 0, + }); +}); + +describe("Should format stdin even if it's empty", () => { + runCli("", ["--stdin-filepath", "example.js"], { + isTTY: true, + }).test({ + stdout: "", + status: 0, + stderr: "", + write: [], + }); +});