Load plugins asynchronously (#12790)

This commit is contained in:
fisker Cheung authored and GitHub committed 2022-05-06 09:27:54 +08:00
1 parent 34fadbe67d
commit 193365eb71
17 files changed
+112 -90

No files matched your search

+2 -1
View File
@@ -1,8 +1,9 @@
#### [BREAKING] Change public apis to asynchronous (#12574, #12788 by @fisker) #### [BREAKING] Change public apis to asynchronous (#12574, #12788, #12790 by @fisker)
- `prettier.format()` returns `Promise<string>` - `prettier.format()` returns `Promise<string>`
- `prettier.formatWithCursor()` returns `Promise<{formatted: string, cursorOffset: number}>` - `prettier.formatWithCursor()` returns `Promise<{formatted: string, cursorOffset: number}>`
- `prettier.check()` returns `Promise<boolean>` - `prettier.check()` returns `Promise<boolean>`
- `prettier.getSupportInfo()` returns `Promise`
- `prettier.resolveConfig.sync` is removed - `prettier.resolveConfig.sync` is removed
- `prettier.resolveConfigFile.sync` is removed - `prettier.resolveConfigFile.sync` is removed
- `prettier.getFileInfo.sync` is removed - `prettier.getFileInfo.sync` is removed
+1 -1
View File
@@ -102,7 +102,7 @@ When setting `options.resolveConfig` (`boolean`, default `false`), Prettier will
## `prettier.getSupportInfo()` ## `prettier.getSupportInfo()`
Returns an object representing the options, parsers, languages and file types Prettier supports. Returns a promise which resolves to an object representing the options, parsers, languages and file types Prettier supports.
The support information looks like this: The support information looks like this:
+8 -4
View File
@@ -26,14 +26,18 @@ class Context {
this.rawArguments = rawArguments; this.rawArguments = rawArguments;
this.logger = logger; this.logger = logger;
this.stack = []; this.stack = [];
}
const { plugins, pluginSearchDirs } = parseArgvWithoutPlugins( async init() {
const { rawArguments, logger } = this;
const { plugins, pluginSearchDirs } = await parseArgvWithoutPlugins(
rawArguments, rawArguments,
logger, logger,
["plugin", "plugin-search-dir"] ["plugin", "plugin-search-dir"]
); );
this.pushContextPlugins(plugins, pluginSearchDirs); await this.pushContextPlugins(plugins, pluginSearchDirs);
const argv = parseArgv(rawArguments, this.detailedOptions, logger); const argv = parseArgv(rawArguments, this.detailedOptions, logger);
this.argv = argv; this.argv = argv;
@@ -44,8 +48,8 @@ class Context {
* @param {string[]} plugins * @param {string[]} plugins
* @param {string[]=} pluginSearchDirs * @param {string[]=} pluginSearchDirs
*/ */
pushContextPlugins(plugins, pluginSearchDirs) { async pushContextPlugins(plugins, pluginSearchDirs) {
const options = getContextOptions(plugins, pluginSearchDirs); const options = await getContextOptions(plugins, pluginSearchDirs);
this.stack.push(options); this.stack.push(options);
Object.assign(this, options); Object.assign(this, options);
} }
+5 -5
View File
@@ -14,15 +14,16 @@ async function run(rawArguments) {
let logger = createLogger(); let logger = createLogger();
try { try {
const logLevel = parseArgvWithoutPlugins( const { loglevel: logLevel } = await parseArgvWithoutPlugins(
rawArguments, rawArguments,
logger, logger,
"loglevel" "loglevel"
).loglevel; );
if (logLevel !== logger.logLevel) { if (logLevel !== logger.logLevel) {
logger = createLogger(logLevel); logger = createLogger(logLevel);
} }
const context = new Context({ rawArguments, logger }); const context = new Context({ rawArguments, logger });
await context.init();
if (logger.logLevel !== "debug" && context.performanceTestFlag) { if (logger.logLevel !== "debug" && context.performanceTestFlag) {
context.logger = createLogger("debug"); context.logger = createLogger("debug");
} }
@@ -80,10 +81,9 @@ async function main(context) {
} }
if (context.argv.supportInfo) { if (context.argv.supportInfo) {
const supportInfo = await prettier.getSupportInfo();
printToScreen( printToScreen(
await prettier.format(stringify(prettier.getSupportInfo()), { await prettier.format(stringify(supportInfo), { parser: "json" })
parser: "json",
})
); );
return; return;
} }
+2 -2
View File
@@ -7,8 +7,8 @@ import {
createDetailedOptionMap, createDetailedOptionMap,
} from "./option-map.js"; } from "./option-map.js";
function getContextOptions(plugins, pluginSearchDirs) { async function getContextOptions(plugins, pluginSearchDirs) {
const { options: supportOptions, languages } = getSupportInfo({ const { options: supportOptions, languages } = await getSupportInfo({
showDeprecated: true, showDeprecated: true,
showUnreleased: true, showUnreleased: true,
showInternal: true, showInternal: true,
+1 -1
View File
@@ -107,7 +107,7 @@ async function getOptionsForFile(context, filepath) {
const hasPlugins = options && options.plugins; const hasPlugins = options && options.plugins;
if (hasPlugins) { if (hasPlugins) {
context.pushContextPlugins(options.plugins); await context.pushContextPlugins(options.plugins);
} }
const appliedOptions = { const appliedOptions = {
+7 -5
View File
@@ -39,11 +39,13 @@ function parseArgv(rawArguments, detailedOptions, logger, keys) {
}; };
} }
const detailedOptionsWithoutPlugins = getContextOptions( let detailedOptionsWithoutPlugins;
[], async function parseArgvWithoutPlugins(rawArguments, logger, keys) {
false if (!detailedOptionsWithoutPlugins) {
).detailedOptions; ({ detailedOptions: detailedOptionsWithoutPlugins } =
function parseArgvWithoutPlugins(rawArguments, logger, keys) { await getContextOptions([], false));
}
return parseArgv( return parseArgv(
rawArguments, rawArguments,
detailedOptionsWithoutPlugins, detailedOptionsWithoutPlugins,
+44 -34
View File
@@ -1,5 +1,5 @@
import { createRequire } from "node:module"; import { createRequire } from "node:module";
import fs from "node:fs"; import { promises as fsPromises } from "node:fs";
import path from "node:path"; import path from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import fastGlob from "fast-glob"; import fastGlob from "fast-glob";
@@ -19,7 +19,7 @@ const clearCache = () => {
memClear(memoizedSearch); memClear(memoizedSearch);
}; };
function load(plugins, pluginSearchDirs) { async function load(plugins, pluginSearchDirs) {
if (!plugins) { if (!plugins) {
plugins = []; plugins = [];
} }
@@ -61,36 +61,42 @@ function load(plugins, pluginSearchDirs) {
} }
); );
const externalAutoLoadPluginInfos = pluginSearchDirs.flatMap( const externalAutoLoadPluginInfos = (
(pluginSearchDir) => { await Promise.all(
const resolvedPluginSearchDir = path.resolve( pluginSearchDirs.map(async (pluginSearchDir) => {
process.cwd(), const resolvedPluginSearchDir = path.resolve(
pluginSearchDir process.cwd(),
); pluginSearchDir
const nodeModulesDir = path.resolve(
resolvedPluginSearchDir,
"node_modules"
);
// In some fringe cases (ex: files "mounted" as virtual directories), the
// isDirectory(resolvedPluginSearchDir) check might be false even though
// the node_modules actually exists.
if (
!isDirectory(nodeModulesDir) &&
!isDirectory(resolvedPluginSearchDir)
) {
throw new Error(
`${pluginSearchDir} does not exist or is not a directory`
); );
}
return memoizedSearch(nodeModulesDir).map((pluginName) => ({ const nodeModulesDir = path.resolve(
name: pluginName, resolvedPluginSearchDir,
requirePath: resolve(pluginName, { paths: [resolvedPluginSearchDir] }), "node_modules"
})); );
}
); // In some fringe cases (ex: files "mounted" as virtual directories), the
// isDirectory(resolvedPluginSearchDir) check might be false even though
// the node_modules actually exists.
if (
!(await isDirectory(nodeModulesDir)) &&
!(await isDirectory(resolvedPluginSearchDir))
) {
throw new Error(
`${pluginSearchDir} does not exist or is not a directory`
);
}
const pluginNames = await memoizedSearch(nodeModulesDir);
return pluginNames.map((pluginName) => ({
name: pluginName,
requirePath: resolve(pluginName, {
paths: [resolvedPluginSearchDir],
}),
}));
})
)
).flat();
const externalPlugins = [ const externalPlugins = [
...uniqByKey( ...uniqByKey(
@@ -106,8 +112,8 @@ function load(plugins, pluginSearchDirs) {
return externalPlugins; return externalPlugins;
} }
function findPluginsInNodeModules(nodeModulesDir) { async function findPluginsInNodeModules(nodeModulesDir) {
const pluginPackageJsonPaths = fastGlob.sync( const pluginPackageJsonPaths = await fastGlob(
[ [
"prettier-plugin-*/package.json", "prettier-plugin-*/package.json",
"@*/prettier-plugin-*/package.json", "@*/prettier-plugin-*/package.json",
@@ -120,12 +126,16 @@ function findPluginsInNodeModules(nodeModulesDir) {
return pluginPackageJsonPaths.map(path.dirname); return pluginPackageJsonPaths.map(path.dirname);
} }
function isDirectory(dir) { async function isDirectory(dir) {
let stat;
try { try {
return fs.statSync(dir).isDirectory(); stat = await fsPromises.stat(dir);
} catch { } catch {
return false; return false;
} }
return stat.isDirectory();
} }
export { memoizedLoad as loadPlugins, clearCache }; export { memoizedLoad as loadPlugins, clearCache };
+3 -2
View File
@@ -39,13 +39,14 @@ function withPlugins(
fn, fn,
optsArgIdx = 1 // Usually `opts` is the 2nd argument optsArgIdx = 1 // Usually `opts` is the 2nd argument
) { ) {
return (...args) => { return async (...args) => {
const opts = args[optsArgIdx] || {}; const opts = args[optsArgIdx] || {};
args[optsArgIdx] = { args[optsArgIdx] = {
...opts, ...opts,
plugins: [ plugins: [
...languages, ...languages,
...loadPlugins(opts.plugins, opts.pluginSearchDirs), ...(await loadPlugins(opts.plugins, opts.pluginSearchDirs)),
], ],
}; };
return fn(...args); return fn(...args);
+3 -1
View File
@@ -10,7 +10,9 @@ function withPlugins(
fn, fn,
optsArgIdx = 1 // Usually `opts` is the 2nd argument optsArgIdx = 1 // Usually `opts` is the 2nd argument
) { ) {
return (...args) => { // Returns Promises to consistent with functions in `index.js`
// eslint-disable-next-line require-await
return async (...args) => {
const opts = args[optsArgIdx] || {}; const opts = args[optsArgIdx] || {};
const plugins = opts.plugins || []; const plugins = opts.plugins || [];
+2 -3
View File
@@ -1,8 +1,7 @@
import * as prettier from "../../../../index.js"; import * as prettier from "../../../../index.js";
const parsers = prettier const parsers = (await prettier.getSupportInfo()).options
.getSupportInfo() .find((option) => option.name === "parser")
.options.find((option) => option.name === "parser")
.choices.filter((choice) => !choice.deprecated) .choices.filter((choice) => !choice.deprecated)
.map((choice) => choice.value); .map((choice) => choice.value);
+5 -5
View File
@@ -27,8 +27,8 @@ describe("API", () => {
expect(Array.isArray(ast.program.body)).toBe(true); expect(Array.isArray(ast.program.body)).toBe(true);
}); });
test("prettier.formatAST", () => { test("prettier.formatAST", async () => {
const { formatted: formatResultFromAST } = formatAST(ast, options); const { formatted: formatResultFromAST } = await formatAST(ast, options);
expect(formatResultFromAST).toBe(formatted); expect(formatResultFromAST).toBe(formatted);
}); });
@@ -41,8 +41,8 @@ describe("API", () => {
done(); done();
}); });
test("prettier.printDocToString", () => { test("prettier.printDocToString", async () => {
const { formatted: stringFromDoc } = printDocToString(doc, options); const { formatted: stringFromDoc } = await printDocToString(doc, options);
expect(stringFromDoc).toBe(formatted); expect(stringFromDoc).toBe(formatted);
}); });
@@ -55,7 +55,7 @@ describe("API", () => {
`return ${formatResultFromDoc}` `return ${formatResultFromDoc}`
)(builders); )(builders);
const { formatted: stringFromDoc2 } = printDocToString(doc2, options); const { formatted: stringFromDoc2 } = await printDocToString(doc2, options);
expect(stringFromDoc2).toBe(formatted); expect(stringFromDoc2).toBe(formatted);
const formatResultFromDoc2 = await formatDoc(doc2, options); const formatResultFromDoc2 = await formatDoc(doc2, options);
+10 -10
View File
@@ -2,8 +2,8 @@ import prettier from "prettier-local";
const { formatAST } = prettier.__debug; const { formatAST } = prettier.__debug;
describe("formatAST", () => { describe("formatAST", () => {
const formatExportSpecifier = (specifier) => { const formatExportSpecifier = async (specifier) => {
const { formatted } = formatAST( const { formatted } = await formatAST(
{ {
type: "Program", type: "Program",
body: [ body: [
@@ -19,9 +19,9 @@ describe("formatAST", () => {
return formatted; return formatted;
}; };
test("Shorthand specifier", () => { test("Shorthand specifier", async () => {
expect( expect(
formatExportSpecifier({ await formatExportSpecifier({
type: "ExportSpecifier", type: "ExportSpecifier",
local: { local: {
type: "Identifier", type: "Identifier",
@@ -35,9 +35,9 @@ describe("formatAST", () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
test("Shorthand specifier 2", () => { test("Shorthand specifier 2", async () => {
expect( expect(
formatExportSpecifier({ await formatExportSpecifier({
type: "ExportSpecifier", type: "ExportSpecifier",
local: { local: {
type: "Identifier", type: "Identifier",
@@ -53,9 +53,9 @@ describe("formatAST", () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
test("Shorthand specifier 3", () => { test("Shorthand specifier 3", async () => {
expect( expect(
formatExportSpecifier({ await formatExportSpecifier({
type: "ExportSpecifier", type: "ExportSpecifier",
local: { local: {
type: "Literal", type: "Literal",
@@ -72,9 +72,9 @@ describe("formatAST", () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
test("Shorthand specifier 4", () => { test("Shorthand specifier 4", async () => {
expect( expect(
formatExportSpecifier({ await formatExportSpecifier({
type: "ExportSpecifier", type: "ExportSpecifier",
local: { local: {
type: "Literal", type: "Literal",
+7 -7
View File
@@ -7,15 +7,15 @@ import {
normalizeDetailedOptionMap, normalizeDetailedOptionMap,
} from "../../../src/cli/options/option-map.js"; } from "../../../src/cli/options/option-map.js";
const supportInfo = await prettier.getSupportInfo({
showDeprecated: true,
showUnreleased: true,
showInternal: true,
});
for (const option of arrayify( for (const option of arrayify(
{ {
...createDetailedOptionMap( ...createDetailedOptionMap(supportInfo.options),
prettier.getSupportInfo({
showDeprecated: true,
showUnreleased: true,
showInternal: true,
}).options
),
...normalizeDetailedOptionMap(constant.options), ...normalizeDetailedOptionMap(constant.options),
}, },
"name" "name"
+5 -2
View File
@@ -1,6 +1,9 @@
import prettier from "prettier-local"; import prettier from "prettier-local";
import generateSchema from "../../../scripts/utils/generate-schema.mjs"; import generateSchema from "../../../scripts/utils/generate-schema.mjs";
test("schema", () => { test("schema", async () => {
expect(generateSchema(prettier.getSupportInfo().options)).toMatchSnapshot(); const { options } = await prettier.getSupportInfo();
const schema = generateSchema(options);
expect(schema).toMatchSnapshot();
}); });
+4 -4
View File
@@ -1,16 +1,16 @@
import prettier from "prettier-local"; import prettier from "prettier-local";
import runPrettier from "../run-prettier.js"; import runPrettier from "../run-prettier.js";
test("API getSupportInfo()", () => { test("API getSupportInfo()", async () => {
expect(getCoreInfo()).toMatchSnapshot(); expect(await getCoreInfo()).toMatchSnapshot();
}); });
describe("CLI --support-info", () => { describe("CLI --support-info", () => {
runPrettier("cli", "--support-info").test({ status: 0 }); runPrettier("cli", "--support-info").test({ status: 0 });
}); });
function getCoreInfo() { async function getCoreInfo() {
const supportInfo = prettier.getSupportInfo(); const supportInfo = await prettier.getSupportInfo();
const languages = Object.fromEntries( const languages = Object.fromEntries(
supportInfo.languages.map(({ name, parsers }) => [name, parsers]) supportInfo.languages.map(({ name, parsers }) => [name, parsers])
); );
+3 -3
View File
@@ -59,11 +59,11 @@ async function run(dir, args, options) {
- Test file `./__tests__/plugin-virtual-directory.js` - Test file `./__tests__/plugin-virtual-directory.js`
- Pull request #5819 - Pull request #5819
*/ */
const originalStatSync = fs.statSync; const originalStat = fs.promises.stat;
jest jest
.spyOn(fs, "statSync") .spyOn(fs.promises, "stat")
.mockImplementation((filename) => .mockImplementation((filename) =>
originalStatSync( originalStat(
path.basename(filename) === "virtualDirectory" ? __filename : filename path.basename(filename) === "virtualDirectory" ? __filename : filename
) )
); );