Support URL as ignorePath in getFileInfo() (#15364)

This commit is contained in:
fisker Cheung authored and GitHub committed 2023-09-14 20:10:10 +08:00
1 parent ae6f9ac25b
commit 6c833b4445
7 files changed
+101 -44

No files matched your search

+7 -1
View File
@@ -1,4 +1,4 @@
#### Accept `URL` in `prettier.{resolveConfig,resolveConfigFile,getFileInfo}()` (#15332, #15354, #15360 by @fisker) #### Accept `URL` in `prettier.{resolveConfig,resolveConfigFile,getFileInfo}()` (#15332, #15354, #15360, #15364 by @fisker)
[`prettier.resolveConfig()`](https://prettier.io/docs/en/api#prettierresolveconfigfileurlorpath--options), [`prettier.resolveConfigFile()`](https://prettier.io/docs/en/api#prettierresolveconfigfilefileurlorpath), and [`prettier.getFileInfo()`](https://prettier.io/docs/en/api#prettiergetfileinfofileurlorpath--options) now accepts an [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) with `file:` protocol or a url string starts with `file://`. [`prettier.resolveConfig()`](https://prettier.io/docs/en/api#prettierresolveconfigfileurlorpath--options), [`prettier.resolveConfigFile()`](https://prettier.io/docs/en/api#prettierresolveconfigfilefileurlorpath), and [`prettier.getFileInfo()`](https://prettier.io/docs/en/api#prettiergetfileinfofileurlorpath--options) now accepts an [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) with `file:` protocol or a url string starts with `file://`.
@@ -7,9 +7,15 @@
await prettier.resolveConfig(new URL("./path/to/file", import.meta.url)); await prettier.resolveConfig(new URL("./path/to/file", import.meta.url));
await prettier.resolveConfigFile(new URL("./path/to/file", import.meta.url)); await prettier.resolveConfigFile(new URL("./path/to/file", import.meta.url));
await prettier.getFileInfo(new URL("./path/to/file", import.meta.url)); await prettier.getFileInfo(new URL("./path/to/file", import.meta.url));
await prettier.getFileInfo("/path/to/file", {
ignorePath: new URL("./.eslintignore", import.meta.url),
});
// URL string // URL string
await prettier.resolveConfig("file:///path/to/file"); await prettier.resolveConfig("file:///path/to/file");
await prettier.resolveConfigFile("file:///path/to/file"); await prettier.resolveConfigFile("file:///path/to/file");
await prettier.getFileInfo("file:///path/to/file"); await prettier.getFileInfo("file:///path/to/file");
await prettier.getFileInfo("/path/to/file", {
ignorePath: "file:///path/to/.eslintignore",
});
``` ```
+1 -1
View File
@@ -90,7 +90,7 @@ When Prettier loads configuration files and plugins, the file system structure i
The promise will be rejected if the type of `fileUrlOrPath` is not `string` or `URL`. The promise will be rejected if the type of `fileUrlOrPath` is not `string` or `URL`.
Setting `options.ignorePath` (`string | string[]`) and `options.withNodeModules` (`boolean`) influence the value of `ignored` (`false` by default). Setting `options.ignorePath` (`string | URL | (string | URL)[]`) and `options.withNodeModules` (`boolean`) influence the value of `ignored` (`false` by default).
If the given `fileUrlOrPath` is ignored, the `inferredParser` is always `null`. If the given `fileUrlOrPath` is ignored, the `inferredParser` is always `null`.
+1 -1
View File
@@ -3,7 +3,7 @@ import { resolveConfig } from "../config/resolve-config.js";
import { isIgnored } from "../utils/ignore.js"; import { isIgnored } from "../utils/ignore.js";
/** /**
* @typedef {{ ignorePath?: string, withNodeModules?: boolean, plugins: object, resolveConfig?: boolean }} FileInfoOptions * @typedef {{ ignorePath?: string | URL | (string | URL)[], withNodeModules?: boolean, plugins: object, resolveConfig?: boolean }} FileInfoOptions
* @typedef {{ ignored: boolean, inferredParser: string | null }} FileInfoResult * @typedef {{ ignored: boolean, inferredParser: string | null }} FileInfoResult
*/ */
+1 -1
View File
@@ -787,7 +787,7 @@ export interface SupportInfo {
} }
export interface FileInfoOptions { export interface FileInfoOptions {
ignorePath?: string | string[] | undefined; ignorePath?: string | URL | (string | URL)[] | undefined;
withNodeModules?: boolean | undefined; withNodeModules?: boolean | undefined;
plugins?: string[] | undefined; plugins?: string[] | undefined;
resolveConfig?: boolean | undefined; resolveConfig?: boolean | undefined;
+35 -28
View File
@@ -1,6 +1,6 @@
import path from "node:path"; import path from "node:path";
import url from "node:url"; import url from "node:url";
import { isUrl } from "url-or-path"; import { isUrl, toPath } from "url-or-path";
import ignoreModule from "ignore"; import ignoreModule from "ignore";
import readFile from "../utils/read-file.js"; import readFile from "../utils/read-file.js";
@@ -12,15 +12,35 @@ const slash =
: (filePath) => filePath; : (filePath) => filePath;
/** /**
* @param {string?} ignoreFilePath * @param {string | URL} file
* @param {boolean?} withNodeModules * @param {string | URL | undefined} ignoreFile
* @returns {string}
*/
function getRelativePath(file, ignoreFile) {
const ignoreFilePath = toPath(ignoreFile);
const filePath = isUrl(file)
? url.fileURLToPath(file)
: // @ts-expect-error -- URLs handled by `isUrl`
path.resolve(file);
return path.relative(
// If there's an ignore-path set, the filename must be relative to the
// ignore path, not the current working directory.
ignoreFilePath ? path.dirname(ignoreFilePath) : process.cwd(),
filePath,
);
}
/**
* @param {string | URL | undefined} ignoreFile
* @param {boolean} [withNodeModules]
* @returns {Promise<(file: string | URL) => boolean>} * @returns {Promise<(file: string | URL) => boolean>}
*/ */
async function createSingleIsIgnoredFunction(ignoreFilePath, withNodeModules) { async function createSingleIsIgnoredFunction(ignoreFile, withNodeModules) {
let content = ""; let content = "";
if (ignoreFilePath) { if (ignoreFile) {
content += (await readFile(ignoreFilePath)) ?? ""; content += (await readFile(ignoreFile)) ?? "";
} }
if (!withNodeModules) { if (!withNodeModules) {
@@ -33,37 +53,24 @@ async function createSingleIsIgnoredFunction(ignoreFilePath, withNodeModules) {
const ignore = createIgnore({ allowRelativePaths: true }).add(content); const ignore = createIgnore({ allowRelativePaths: true }).add(content);
return (file) => { return (file) => ignore.ignores(slash(getRelativePath(file, ignoreFile)));
const filePath = isUrl(file)
? url.fileURLToPath(file)
: // @ts-expect-error -- URLs handled by `isUrl`
path.resolve(file);
// If there's an ignore-path set, the filename must be relative to the
// ignore path, not the current working directory.
const relativePath = ignoreFilePath
? path.relative(path.dirname(ignoreFilePath), filePath)
: path.relative(process.cwd(), filePath);
return ignore.ignores(slash(relativePath));
};
} }
/** /**
* @param {string[]} ignoreFilePaths * @param {(string | URL)[]} ignoreFiles
* @param {boolean?} withNodeModules * @param {boolean?} withNodeModules
* @returns {Promise<(file: string | URL) => boolean>} * @returns {Promise<(file: string | URL) => boolean>}
*/ */
async function createIsIgnoredFunction(ignoreFilePaths, withNodeModules) { async function createIsIgnoredFunction(ignoreFiles, withNodeModules) {
// If `ignoreFilePaths` is empty, we still want `withNodeModules` to work // If `ignoreFilePaths` is empty, we still want `withNodeModules` to work
if (ignoreFilePaths.length === 0 && !withNodeModules) { if (ignoreFiles.length === 0 && !withNodeModules) {
ignoreFilePaths = [undefined]; ignoreFiles = [undefined];
} }
const isIgnoredFunctions = ( const isIgnoredFunctions = (
await Promise.all( await Promise.all(
ignoreFilePaths.map((ignoreFilePath) => ignoreFiles.map((ignoreFile) =>
createSingleIsIgnoredFunction(ignoreFilePath, withNodeModules), createSingleIsIgnoredFunction(ignoreFile, withNodeModules),
), ),
) )
).filter(Boolean); ).filter(Boolean);
@@ -77,8 +84,8 @@ async function createIsIgnoredFunction(ignoreFilePaths, withNodeModules) {
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
async function isIgnored(file, options) { async function isIgnored(file, options) {
const { ignorePath, withNodeModules } = options; const { ignorePath: ignoreFiles, withNodeModules } = options;
const isIgnored = await createIsIgnoredFunction(ignorePath, withNodeModules); const isIgnored = await createIsIgnoredFunction(ignoreFiles, withNodeModules);
return isIgnored(file); return isIgnored(file);
} }
+9 -4
View File
@@ -1,18 +1,23 @@
import fs from "node:fs/promises"; import fs from "node:fs/promises";
import { isUrlString } from "url-or-path";
/** /**
* @param {string} filename * @param {string | URL} file
* @returns {Promise<undefined | string>} * @returns {Promise<undefined | string>}
*/ */
async function readFile(filename) { async function readFile(file) {
if (isUrlString(file)) {
file = new URL(file);
}
try { try {
return await fs.readFile(filename, "utf8"); return await fs.readFile(file, "utf8");
} catch (/** @type {any} */ error) { } catch (/** @type {any} */ error) {
if (error.code === "ENOENT") { if (error.code === "ENOENT") {
return; return;
} }
throw new Error(`Unable to read '${filename}': ${error.message}`); throw new Error(`Unable to read '${file}': ${error.message}`);
} }
} }
+47 -8
View File
@@ -221,8 +221,9 @@ test("API getFileInfo with ignorePath", async () => {
"../cli/ignore-path/file-info-test/ignored-by-customignore.js", "../cli/ignore-path/file-info-test/ignored-by-customignore.js",
import.meta.url, import.meta.url,
); );
const ignorePath = path.resolve( const ignorePath = new URL(
path.join(__dirname, "../cli/ignore-path/file-info-test/.customignore"), "../cli/ignore-path/file-info-test/.customignore",
import.meta.url,
); );
await expect(prettier.getFileInfo(file)).resolves.toEqual({ await expect(prettier.getFileInfo(file)).resolves.toEqual({
@@ -241,8 +242,9 @@ test("API getFileInfo with ignorePath containing relative paths", async () => {
"../cli/ignore-relative-path/level1-glob/level2-glob/level3-glob/shouldNotBeFormat.js", "../cli/ignore-relative-path/level1-glob/level2-glob/level3-glob/shouldNotBeFormat.js",
import.meta.url, import.meta.url,
); );
const ignorePath = path.resolve( const ignorePath = new URL(
path.join(__dirname, "../cli/ignore-relative-path/.prettierignore"), "../cli/ignore-relative-path/.prettierignore",
import.meta.url,
); );
await expect(prettier.getFileInfo(file)).resolves.toEqual({ await expect(prettier.getFileInfo(file)).resolves.toEqual({
@@ -359,15 +361,15 @@ test("API getFileInfo with hand-picked plugins", async () => {
}); });
test("API getFileInfo with ignorePath and resolveConfig should infer parser with correct filepath", async () => { test("API getFileInfo with ignorePath and resolveConfig should infer parser with correct filepath", async () => {
const dir = path.join(__dirname, "../cli/ignore-and-config/"); const directory = new URL("../cli/ignore-and-config/", import.meta.url);
const filePath = path.join(dir, "config-dir/foo"); const file = new URL("./config-dir/foo", directory);
const ignorePath = path.join(dir, "ignore-path-dir/.prettierignore"); const ignorePath = new URL("./ignore-path-dir/.prettierignore", directory);
const options = { const options = {
resolveConfig: true, resolveConfig: true,
ignorePath, ignorePath,
}; };
await expect(prettier.getFileInfo(filePath, options)).resolves.toEqual({ await expect(prettier.getFileInfo(file, options)).resolves.toEqual({
ignored: false, ignored: false,
inferredParser: "parser-for-config-dir", inferredParser: "parser-for-config-dir",
}); });
@@ -388,3 +390,40 @@ test("API getFileInfo accepts path or URL", async () => {
expect(resultByPath).toEqual(expectedResult); expect(resultByPath).toEqual(expectedResult);
expect(resultByRelativePath).toEqual(expectedResult); expect(resultByRelativePath).toEqual(expectedResult);
}); });
test("API getFileInfo accepts path or URL as ignorePath", async () => {
const file = new URL(
"../cli/ignore-path/file-info-test/ignored-by-customignore.js",
import.meta.url,
);
const ignoreFileUrl = new URL(
"../cli/ignore-path/file-info-test/.customignore",
import.meta.url,
);
const expectedResult = { ignored: true, inferredParser: null };
const resultByUrl = await prettier.getFileInfo(file, {
ignorePath: ignoreFileUrl,
});
const resultByUrlArray = await prettier.getFileInfo(file, {
ignorePath: [ignoreFileUrl],
});
const resultByUrlHref = await prettier.getFileInfo(file, {
ignorePath: ignoreFileUrl.href,
});
const resultByUrlHrefArray = await prettier.getFileInfo(file, {
ignorePath: [ignoreFileUrl.href],
});
const resultByPath = await prettier.getFileInfo(file, {
ignorePath: url.fileURLToPath(ignoreFileUrl),
});
const resultByPathArray = await prettier.getFileInfo(file, {
ignorePath: [url.fileURLToPath(ignoreFileUrl)],
});
expect(resultByUrl).toEqual(expectedResult);
expect(resultByUrlArray).toEqual(expectedResult);
expect(resultByUrlHref).toEqual(expectedResult);
expect(resultByUrlHrefArray).toEqual(expectedResult);
expect(resultByPath).toEqual(expectedResult);
expect(resultByPathArray).toEqual(expectedResult);
});