Refactor builtin plugins creation (#18481)

This commit is contained in:
fisker Cheung authored and GitHub committed 2025-12-14 23:22:28 +08:00
1 parent a5684d0aaf
commit 01363e7310
36 files changed
+779 -259

No files matched your search

+2 -2
View File
@@ -40,8 +40,8 @@ if (isProduction) {
);
}
if (nodejsMajorVersion <= 16) {
// Uses import attributes
if (nodejsMajorVersion <= 18) {
// Uses import attributes and `Array#toSorted()`
testPathIgnorePatterns.push(
"<rootDir>/tests/integration/__tests__/help-options.js",
);
+5 -3
View File
@@ -206,13 +206,15 @@
"fix": "run-s --continue-on-error fix:eslint fix:prettier",
"fix:eslint": "yarn lint:eslint --fix",
"fix:prettier": "yarn lint:prettier --write",
"fix:builtin-plugins": "yarn generate-builtin-plugins",
"build": "node ./scripts/build/build.js",
"build:website": "node ./scripts/build-website.js",
"build:website:pr": "cross-env PULL_REQUEST=true yarn build:website",
"generate-changelog": "node ./scripts/generate-changelog.js",
"debug": "node bin/prettier.js --ignore-path=.prettierignore --plugin=./packages/plugin-oxc/index.js --plugin=./packages/plugin-hermes/index.js",
"debug:watch": "node --watch bin/prettier.js --ignore-path=.prettierignore --plugin=./packages/plugin-oxc/index.js --plugin=./packages/plugin-hermes/index.js",
"debug:inspect": "node --inspect-brk bin/prettier.js --ignore-path=.prettierignore --plugin=./packages/plugin-oxc/index.js --plugin=./packages/plugin-hermes/index.js"
"generate-builtin-plugins": "node scripts/generate-builtin-plugins.js",
"debug": "node bin/prettier.js --ignore-path=.prettierignore",
"debug:watch": "node --watch bin/prettier.js --ignore-path=.prettierignore",
"debug:inspect": "node --inspect-brk bin/prettier.js --ignore-path=.prettierignore"
},
"c8": {
"reporter": [
+1 -1
View File
@@ -1,5 +1,5 @@
import * as hermesParsers from "../../src/language-js/parse/hermes.js";
import * as estreePrinter from "../../src/language-js/printer.js";
import { estree as estreePrinter } from "../../src/language-js/printers.js";
const AST_FORMAT = "estree-hermes";
+1 -1
View File
@@ -1,5 +1,5 @@
import * as oxcParsers from "../../src/language-js/parse/oxc.js";
import * as estreePrinter from "../../src/language-js/printer.js";
import { estree as estreePrinter } from "../../src/language-js/printers.js";
const AST_FORMAT = "estree-oxc";
+259
View File
@@ -0,0 +1,259 @@
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import path from "node:path";
import { isValidIdentifier } from "@babel/types";
import { outdent } from "outdent";
import { format } from "prettier";
import { toPath } from "url-or-path";
const projectRoot = new URL("../", import.meta.url);
const sourceDirectory = new URL("./src/", projectRoot);
const buildScript = `node ${import.meta.url.slice(projectRoot.href.length)}`;
const pluginFiles = [
{
kind: "development",
pattern: "language-*/index.js",
file: new URL(
"main/plugins/builtin-plugins/development-plugins.js",
sourceDirectory,
),
getPluginName: (file) => path.dirname(file).slice("language-".length),
},
{
kind: "production",
pattern: "plugins/*.js",
file: new URL(
"main/plugins/builtin-plugins/production-plugins.js",
sourceDirectory,
),
getPluginName: (file) => path.basename(file, path.extname(file)),
},
];
function getImportSource(from, to) {
from = toPath(from);
to = toPath(to);
const relation = path.relative(path.dirname(from), to);
return relation.replaceAll("\\", "/");
}
async function getPluginData(file, pluginFile, getPluginName, kind) {
const pluginName = getPluginName(pluginFile);
assert.ok(
isValidIdentifier(pluginName),
`Invalid plugin name '${pluginName}'.`,
);
pluginFile = new URL(pluginFile, sourceDirectory);
const implementation = await import(pluginFile);
const importSource = getImportSource(file, pluginFile);
const pluginData = {
name: pluginName,
importSource,
file: pluginFile,
parserNames: Object.hasOwn(implementation, "parsers")
? Object.keys(implementation.parsers)
: undefined,
printerNames: Object.hasOwn(implementation, "printers")
? Object.keys(implementation.printers)
: undefined,
};
for (const property of ["languages", "options"]) {
if (!Object.hasOwn(implementation, property)) {
continue;
}
const value = implementation[property];
pluginData[property] = {
value,
variableName: `${pluginName}${property[0].toUpperCase() + property.slice(1)}`,
};
const entry =
kind === "development"
? // If we import `languages` and `options` from the `index.js`
// It will be much slower to load builtin plugins
await locateLanguageOrOptions(pluginData, property)
: {
url: pluginFile,
specifier: property,
};
pluginData[property].entry = entry;
}
return pluginData;
}
function getImportStatements(file, plugin) {
const statements = new Map();
for (const property of ["languages", "options"]) {
if (!plugin[property]) {
continue;
}
const { entry, variableName } = plugin[property];
const { url } = entry;
const specifier = {
imported: entry.specifier,
local: variableName,
};
if (!statements.has(url)) {
statements.set(url, []);
}
statements.get(url).push(specifier);
}
if (statements.size === 0) {
return;
}
const statementsText = [];
for (const [url, specifiers] of statements) {
let defaultSpecifier = "";
const namedSpecifiers = [];
for (const [index, { local, imported }] of specifiers.entries()) {
if (imported === "default") {
assert.ok(
index === 0,
`Unexpected specifier '${imported}' at index '${index}'.`,
);
}
if (imported === "default") {
defaultSpecifier = local;
} else {
namedSpecifiers.push(`${imported} as ${local}`);
}
}
let statementText = "import";
if (defaultSpecifier) {
statementText += ` ${defaultSpecifier}`;
}
if (namedSpecifiers.length > 0) {
if (defaultSpecifier) {
statementText += ",";
}
statementText += ` {${namedSpecifiers.join(",")}}`;
}
const source = getImportSource(file, url);
statementText += ` from "${source}";`;
statementsText.push(statementText);
}
return statementsText.join("\n");
}
function getPluginExportStatement(plugin) {
const properties = {
name: JSON.stringify(plugin.name),
importPlugin: `() => import("${plugin.importSource}")`,
};
if (plugin.options) {
properties.options = plugin.options.variableName;
}
if (plugin.languages) {
properties.languages = plugin.languages.variableName;
}
if (plugin.parserNames) {
properties.parserNames = JSON.stringify(plugin.parserNames);
}
if (plugin.printerNames) {
properties.printerNames = JSON.stringify(plugin.printerNames);
}
return outdent`
export const ${plugin.name} = /* @__PURE__ */ toLazyLoadPlugin({
${Object.entries(properties)
.map(([property, code]) => `${property}: ${code},`)
.join("\n")}
});
`;
}
async function buildPlugins({ kind, file, pattern, getPluginName }) {
const plugins = await Array.fromAsync(
fs.glob(pattern, { cwd: sourceDirectory }),
(pluginFile) => getPluginData(file, pluginFile, getPluginName, kind),
);
// Split js plugin for https://github.com/prettier/prettier/pull/18481
if (kind === "development") {
const jsPlugin = plugins.find(({ name }) => name === "js");
const estreePlugin = { ...jsPlugin, name: "estree" };
delete estreePlugin.languages;
delete estreePlugin.options;
delete estreePlugin.parserNames;
delete jsPlugin.printerNames;
plugins.push(estreePlugin);
}
plugins.sort((pluginA, pluginB) =>
pluginA.importSource.localeCompare(pluginB.importSource),
);
const code = outdent`
/*
Generated file, do NOT edit
Run \`${buildScript}\` to regenerate
*/
${plugins
.map((plugin) => getImportStatements(file, plugin))
.filter(Boolean)
.join("\n")}
import {toLazyLoadPlugin} from "./utilities.js";
${plugins.map((plugin) => getPluginExportStatement(plugin, kind)).join("\n")}
`;
const formatted = await format(code, { parser: "meriyah" });
await fs.writeFile(file, formatted);
}
async function locateLanguageOrOptions(plugin, property) {
const directory = new URL("./", plugin.file);
const possibleEntries = await Array.fromAsync(
fs.glob(
property === "languages"
? ["languages.js", "languages.evaluate.js"]
: ["options.js"],
{ cwd: directory },
),
);
assert.equal(
possibleEntries.length,
1,
`Can not locate '${property}' entry for plugin '${plugin.name}'.`,
);
const [file] = possibleEntries;
const url = new URL(file, directory);
const implementation = await import(url);
if (implementation.default === plugin[property].value) {
return {
url,
specifier: "default",
};
}
throw new Error(
`Can not locate '${property}' entry for plugin '${plugin.name}'.`,
);
}
await Promise.all(pluginFiles.map((plugins) => buildPlugins(plugins)));
+4
View File
@@ -0,0 +1,4 @@
export { default as languages } from "./languages.evaluate.js";
export { default as options } from "./options.js";
export * as parsers from "./parse/index.js";
export * as printers from "./printers.js";
+1 -1
View File
@@ -99,4 +99,4 @@ function parse(text, options) {
return postprocess(ast, { text });
}
export const acorn = createParser(parse);
export const acorn = /* @__PURE__ */ createParser(parse);
+8 -4
View File
@@ -35,7 +35,11 @@ function createParser(parseMethod) {
};
}
export const __ng_action = createParser(parseAction);
export const __ng_binding = createParser(parseBinding);
export const __ng_interpolation = createParser(parseInterpolationExpression);
export const __ng_directive = createParser(parseTemplateBindings);
export const __ng_action = /* @__PURE__ */ createParser(parseAction);
export const __ng_binding = /* @__PURE__ */ createParser(parseBinding);
export const __ng_interpolation = /* @__PURE__ */ createParser(
parseInterpolationExpression,
);
export const __ng_directive = /* @__PURE__ */ createParser(
parseTemplateBindings,
);
+11 -9
View File
@@ -230,39 +230,41 @@ const allowedReasonCodes = new Set([
]);
const babelParserOptionsCombinations = [appendPlugins(["jsx"])];
const babel = createBabelParser({
const babel = /* @__PURE__ */ createBabelParser({
optionsCombinations: babelParserOptionsCombinations,
});
const babelTs = createBabelParser({
const babelTs = /* @__PURE__ */ createBabelParser({
optionsCombinations: [
appendPlugins(["jsx", "typescript"]),
appendPlugins(["typescript"]),
],
});
const babelExpression = createBabelParser({
const babelExpression = /* @__PURE__ */ createBabelParser({
isExpression: true,
optionsCombinations: [appendPlugins(["jsx"])],
});
const babelTSExpression = createBabelParser({
const babelTSExpression = /* @__PURE__ */ createBabelParser({
isExpression: true,
optionsCombinations: [appendPlugins(["typescript"])],
});
const babelFlow = createBabelParser({
const babelFlow = /* @__PURE__ */ createBabelParser({
optionsCombinations: [
appendPlugins(["jsx", ["flow", { all: true }], "flowComments"]),
],
});
const babelEstree = createBabelParser({
const babelEstree = /* @__PURE__ */ createBabelParser({
optionsCombinations: babelParserOptionsCombinations.map((options) =>
appendPlugins(["estree"], options),
),
});
export { babel, babelFlow as "babel-flow", babelTs as "babel-ts" };
/** @internal */
// eslint-disable-next-line simple-import-sort/exports
export {
babel,
babelFlow as "babel-flow",
babelTs as "babel-ts",
/** @internal */
babelExpression as __js_expression,
babelTSExpression as __ts_expression,
/** for vue filter */
+1 -1
View File
@@ -55,4 +55,4 @@ function parse(text, options) {
return postprocess(ast, { parser: "espree", text });
}
export const espree = createParser(parse);
export const espree = /* @__PURE__ */ createParser(parse);
+1 -1
View File
@@ -57,4 +57,4 @@ function parse(text) {
return postprocess(ast, { parser: "flow", text });
}
export const flow = createParser(parse);
export const flow = /* @__PURE__ */ createParser(parse);
+1 -1
View File
@@ -48,4 +48,4 @@ async function parse(text /* , options*/) {
return postprocess(ast, { text, parser: "hermes" });
}
export const hermes = createParser(parse);
export const hermes = /* @__PURE__ */ createParser(parse);
+9
View File
@@ -0,0 +1,9 @@
export * from "./acorn.js";
export * from "./angular.js";
export * from "./babel.js";
export * from "./espree.js";
export * from "./flow.js";
export * from "./hermes.js";
export * from "./meriyah.js";
export * from "./oxc.js";
export * from "./typescript.js";
+1 -1
View File
@@ -102,4 +102,4 @@ function parse(text, options) {
return postprocess(ast, { parser: "meriyah", text });
}
export const meriyah = createParser(parse);
export const meriyah = /* @__PURE__ */ createParser(parse);
+2 -2
View File
@@ -151,7 +151,7 @@ async function parseTs(text, options) {
return postprocess(ast, { text, parser: "oxc", oxcAstType: "ts" });
}
const oxc = createParser(parseJs);
const oxcTs = createParser(parseTs);
const oxc = /* @__PURE__ */ createParser(parseJs);
const oxcTs = /* @__PURE__ */ createParser(parseTs);
export { oxc, oxcTs as "oxc-ts" };
+1 -1
View File
@@ -106,4 +106,4 @@ function parse(text, options) {
return postprocess(ast, { parser: "typescript", text });
}
export const typescript = createParser(parse);
export const typescript = /* @__PURE__ */ createParser(parse);
+1 -1
View File
@@ -98,4 +98,4 @@ function print(path, options, print, args) {
]);
}
export default print;
export { print as printEstree };
-19
View File
@@ -1,19 +0,0 @@
export const features = {
// TODO: Make this default behavior
experimental_avoidAstMutation: true,
};
export { default as massageAstNode } from "./clean.js";
export { default as canAttachComment } from "./comments/can-attach-comment.js";
export { default as handleComments } from "./comments/handle-comments.js";
export { default as isGap } from "./comments/is-gap.js";
export { default as willPrintOwnComments } from "./comments/will-print-own-comments.js";
export { default as embed } from "./embed/index.js";
export { insertPragma } from "./pragma.js";
export { printComment } from "./print/comment.js";
export {
default as print,
default as printPrettierIgnored,
} from "./print/index.js";
export { default as getVisitorKeys } from "./traverse/get-visitor-keys.js";
export { default as isBlockComment } from "./utilities/is-block-comment.js";
export { default as hasPrettierIgnore } from "./utilities/is-ignored.js";
+35
View File
@@ -0,0 +1,35 @@
const features = {
// TODO: Make this default behavior
experimental_avoidAstMutation: true,
};
import massageAstNode from "./clean.js";
import canAttachComment from "./comments/can-attach-comment.js";
import handleComments from "./comments/handle-comments.js";
import isGap from "./comments/is-gap.js";
import willPrintOwnComments from "./comments/will-print-own-comments.js";
import embed from "./embed/index.js";
import { insertPragma } from "./pragma.js";
import { printComment } from "./print/comment.js";
import { printEstree } from "./print/index.js";
import getVisitorKeys from "./traverse/get-visitor-keys.js";
import isBlockComment from "./utilities/is-block-comment.js";
import hasPrettierIgnore from "./utilities/is-ignored.js";
const estree = {
features,
massageAstNode,
canAttachComment,
handleComments,
isGap,
willPrintOwnComments,
embed,
insertPragma,
printComment,
printPrettierIgnored: printEstree,
print: printEstree,
getVisitorKeys,
isBlockComment,
hasPrettierIgnore,
};
export { estree };
+3
View File
@@ -0,0 +1,3 @@
export { default as languages } from "./languages.evaluate.js";
export * as parsers from "./parsers.js";
export * as printers from "./printers.js";
@@ -1,9 +1,8 @@
import { parse, parseExpression } from "@babel/parser";
import createError from "../common/parser-create-error.js";
import createBabelParseError from "../language-js/parse/utilities/create-babel-parse-error.js";
import createParser from "../language-js/parse/utilities/create-parser.js";
import wrapBabelExpression from "../language-js/parse/utilities/wrap-babel-expression.js";
import isNonEmptyArray from "../utilities/is-non-empty-array.js";
import createError from "../../common/parser-create-error.js";
import createBabelParseError from "../../language-js/parse/utilities/create-babel-parse-error.js";
import wrapBabelExpression from "../../language-js/parse/utilities/wrap-babel-expression.js";
import isNonEmptyArray from "../../utilities/is-non-empty-array.js";
const babelParseOptions = {
tokens: false,
@@ -172,16 +171,4 @@ function assertJsonNode(node) {
}
}
const json = createParser({
parse: (text) => parseJson(text),
hasPragma: () => true,
hasIgnorePragma: () => false,
});
const json5 = createParser((text) => parseJson(text));
const jsonc = createParser((text) => parseJson(text, { allowEmpty: true }));
const jsonStringify = createParser({
parse: (text) => parseJson(text, { allowComments: false }),
astFormat: "estree-json",
});
export { json, json5, jsonc, jsonStringify as "json-stringify" };
export { parseJson };
+18
View File
@@ -0,0 +1,18 @@
import createParser from "../language-js/parse/utilities/create-parser.js";
import { parseJson } from "./parse/json.js";
const json = /* @__PURE__ */ createParser({
parse: (text) => parseJson(text),
hasPragma: () => true,
hasIgnorePragma: () => false,
});
const json5 = /* @__PURE__ */ createParser((text) => parseJson(text));
const jsonc = /* @__PURE__ */ createParser((text) =>
parseJson(text, { allowEmpty: true }),
);
const jsonStringify = /* @__PURE__ */ createParser({
parse: (text) => parseJson(text, { allowComments: false }),
astFormat: "estree-json",
});
export { json, json5, jsonc, jsonStringify as "json-stringify" };
+1
View File
@@ -0,0 +1 @@
export { printJson } from "./json.js";
@@ -1,8 +1,9 @@
import { hardline, indent, join } from "../document/index.js";
import printString from "../utilities/print-string.js";
import UnexpectedNodeError from "../utilities/unexpected-node-error.js";
import { hardline, indent, join } from "../../document/index.js";
import printString from "../../utilities/print-string.js";
import UnexpectedNodeError from "../../utilities/unexpected-node-error.js";
import { getRaw } from "../utilities.js";
function genericPrint(path, options, print) {
function printJson(path, options, print) {
const { node } = path;
switch (node.type) {
case "JsonRoot":
@@ -69,10 +70,6 @@ function genericPrint(path, options, print) {
}
}
function getRaw(node) {
return node.extra.raw;
}
function isObjectKey(path) {
return path.key === "key" && path.parent.type === "ObjectProperty";
}
@@ -153,5 +150,4 @@ function clean(original, cloned /* , parent*/) {
clean.ignoredProperties = ignoredProperties;
export { clean as massageAstNode, genericPrint as print };
export { default as getVisitorKeys } from "./get-visitor-keys.js";
export { clean, printJson };
+12
View File
@@ -0,0 +1,12 @@
import getVisitorKeys from "./get-visitor-keys.js";
import { printJson } from "./print/index.js";
import { clean as massageAstNode } from "./print/json.js";
const estreeJsonPrinter = {
massageAstNode,
print: printJson,
getVisitorKeys,
};
export { estreeJsonPrinter as "estree-json" };
export { estree } from "../language-js/printers.js";
+5
View File
@@ -0,0 +1,5 @@
function getRaw(node) {
return node.extra.raw;
}
export { getRaw };
@@ -0,0 +1,110 @@
/*
Generated file, do NOT edit
Run `node scripts/generate-builtin-plugins.js` to regenerate
*/
import cssLanguages from "../../../language-css/languages.evaluate.js";
import cssOptions from "../../../language-css/options.js";
import graphqlLanguages from "../../../language-graphql/languages.evaluate.js";
import graphqlOptions from "../../../language-graphql/options.js";
import handlebarsLanguages from "../../../language-handlebars/languages.evaluate.js";
import htmlLanguages from "../../../language-html/languages.evaluate.js";
import htmlOptions from "../../../language-html/options.js";
import jsLanguages from "../../../language-js/languages.evaluate.js";
import jsOptions from "../../../language-js/options.js";
import jsonLanguages from "../../../language-json/languages.evaluate.js";
import markdownLanguages from "../../../language-markdown/languages.evaluate.js";
import markdownOptions from "../../../language-markdown/options.js";
import yamlLanguages from "../../../language-yaml/languages.evaluate.js";
import yamlOptions from "../../../language-yaml/options.js";
import { toLazyLoadPlugin } from "./utilities.js";
export const css = /* @__PURE__ */ toLazyLoadPlugin({
name: "css",
importPlugin: () => import("../../../language-css/index.js"),
options: cssOptions,
languages: cssLanguages,
parserNames: ["css", "less", "scss"],
printerNames: ["postcss"],
});
export const graphql = /* @__PURE__ */ toLazyLoadPlugin({
name: "graphql",
importPlugin: () => import("../../../language-graphql/index.js"),
options: graphqlOptions,
languages: graphqlLanguages,
parserNames: ["graphql"],
printerNames: ["graphql"],
});
export const handlebars = /* @__PURE__ */ toLazyLoadPlugin({
name: "handlebars",
importPlugin: () => import("../../../language-handlebars/index.js"),
languages: handlebarsLanguages,
parserNames: ["glimmer"],
printerNames: ["glimmer"],
});
export const html = /* @__PURE__ */ toLazyLoadPlugin({
name: "html",
importPlugin: () => import("../../../language-html/index.js"),
options: htmlOptions,
languages: htmlLanguages,
parserNames: ["angular", "html", "lwc", "mjml", "vue"],
printerNames: ["html"],
});
export const js = /* @__PURE__ */ toLazyLoadPlugin({
name: "js",
importPlugin: () => import("../../../language-js/index.js"),
options: jsOptions,
languages: jsLanguages,
parserNames: [
"__babel_estree",
"__js_expression",
"__ng_action",
"__ng_binding",
"__ng_directive",
"__ng_interpolation",
"__ts_expression",
"__vue_event_binding",
"__vue_expression",
"__vue_ts_event_binding",
"__vue_ts_expression",
"acorn",
"babel",
"babel-flow",
"babel-ts",
"espree",
"flow",
"hermes",
"meriyah",
"oxc",
"oxc-ts",
"typescript",
],
});
export const estree = /* @__PURE__ */ toLazyLoadPlugin({
name: "estree",
importPlugin: () => import("../../../language-js/index.js"),
printerNames: ["estree"],
});
export const json = /* @__PURE__ */ toLazyLoadPlugin({
name: "json",
importPlugin: () => import("../../../language-json/index.js"),
languages: jsonLanguages,
parserNames: ["json", "json-stringify", "json5", "jsonc"],
printerNames: ["estree", "estree-json"],
});
export const markdown = /* @__PURE__ */ toLazyLoadPlugin({
name: "markdown",
importPlugin: () => import("../../../language-markdown/index.js"),
options: markdownOptions,
languages: markdownLanguages,
parserNames: ["markdown", "mdx", "remark"],
printerNames: ["mdast"],
});
export const yaml = /* @__PURE__ */ toLazyLoadPlugin({
name: "yaml",
importPlugin: () => import("../../../language-yaml/index.js"),
options: yamlOptions,
languages: yamlLanguages,
parserNames: ["yaml"],
printerNames: ["yaml"],
});
@@ -0,0 +1,136 @@
/*
Generated file, do NOT edit
Run `node scripts/generate-builtin-plugins.js` to regenerate
*/
import {
languages as estreeLanguages,
options as estreeOptions,
} from "../../../plugins/estree.js";
import { languages as glimmerLanguages } from "../../../plugins/glimmer.js";
import {
languages as graphqlLanguages,
options as graphqlOptions,
} from "../../../plugins/graphql.js";
import {
languages as htmlLanguages,
options as htmlOptions,
} from "../../../plugins/html.js";
import {
languages as markdownLanguages,
options as markdownOptions,
} from "../../../plugins/markdown.js";
import {
languages as postcssLanguages,
options as postcssOptions,
} from "../../../plugins/postcss.js";
import {
languages as yamlLanguages,
options as yamlOptions,
} from "../../../plugins/yaml.js";
import { toLazyLoadPlugin } from "./utilities.js";
export const acorn = /* @__PURE__ */ toLazyLoadPlugin({
name: "acorn",
importPlugin: () => import("../../../plugins/acorn.js"),
parserNames: ["acorn", "espree"],
});
export const angular = /* @__PURE__ */ toLazyLoadPlugin({
name: "angular",
importPlugin: () => import("../../../plugins/angular.js"),
parserNames: [
"__ng_action",
"__ng_binding",
"__ng_directive",
"__ng_interpolation",
],
});
export const babel = /* @__PURE__ */ toLazyLoadPlugin({
name: "babel",
importPlugin: () => import("../../../plugins/babel.js"),
parserNames: [
"__babel_estree",
"__js_expression",
"__ts_expression",
"__vue_event_binding",
"__vue_expression",
"__vue_ts_event_binding",
"__vue_ts_expression",
"babel",
"babel-flow",
"babel-ts",
"json",
"json-stringify",
"json5",
"jsonc",
],
});
export const estree = /* @__PURE__ */ toLazyLoadPlugin({
name: "estree",
importPlugin: () => import("../../../plugins/estree.js"),
options: estreeOptions,
languages: estreeLanguages,
printerNames: ["estree", "estree-json"],
});
export const flow = /* @__PURE__ */ toLazyLoadPlugin({
name: "flow",
importPlugin: () => import("../../../plugins/flow.js"),
parserNames: ["flow"],
});
export const glimmer = /* @__PURE__ */ toLazyLoadPlugin({
name: "glimmer",
importPlugin: () => import("../../../plugins/glimmer.js"),
languages: glimmerLanguages,
parserNames: ["glimmer"],
printerNames: ["glimmer"],
});
export const graphql = /* @__PURE__ */ toLazyLoadPlugin({
name: "graphql",
importPlugin: () => import("../../../plugins/graphql.js"),
options: graphqlOptions,
languages: graphqlLanguages,
parserNames: ["graphql"],
printerNames: ["graphql"],
});
export const html = /* @__PURE__ */ toLazyLoadPlugin({
name: "html",
importPlugin: () => import("../../../plugins/html.js"),
options: htmlOptions,
languages: htmlLanguages,
parserNames: ["angular", "html", "lwc", "mjml", "vue"],
printerNames: ["html"],
});
export const markdown = /* @__PURE__ */ toLazyLoadPlugin({
name: "markdown",
importPlugin: () => import("../../../plugins/markdown.js"),
options: markdownOptions,
languages: markdownLanguages,
parserNames: ["markdown", "mdx", "remark"],
printerNames: ["mdast"],
});
export const meriyah = /* @__PURE__ */ toLazyLoadPlugin({
name: "meriyah",
importPlugin: () => import("../../../plugins/meriyah.js"),
parserNames: ["meriyah"],
});
export const postcss = /* @__PURE__ */ toLazyLoadPlugin({
name: "postcss",
importPlugin: () => import("../../../plugins/postcss.js"),
options: postcssOptions,
languages: postcssLanguages,
parserNames: ["css", "less", "scss"],
printerNames: ["postcss"],
});
export const typescript = /* @__PURE__ */ toLazyLoadPlugin({
name: "typescript",
importPlugin: () => import("../../../plugins/typescript.js"),
parserNames: ["typescript"],
});
export const yaml = /* @__PURE__ */ toLazyLoadPlugin({
name: "yaml",
importPlugin: () => import("../../../plugins/yaml.js"),
options: yamlOptions,
languages: yamlLanguages,
parserNames: ["yaml"],
printerNames: ["yaml"],
});
@@ -0,0 +1,58 @@
/**
@param {{
name: string,
importPlugin: () => Promise<any>,
options?: any,
languages?: any[],
parserNames?: string[],
printerNames?: string[],
}} param0
@returns
*/
function toLazyLoadPlugin({
name,
importPlugin,
options,
languages,
parserNames,
printerNames,
}) {
const plugin = { name };
if (options) {
plugin.options = options;
}
if (languages) {
plugin.languages = languages;
}
for (const { property, names } of [
{ property: "parsers", names: parserNames },
{ property: "printers", names: printerNames },
]) {
if (names) {
plugin[property] = Object.fromEntries(
names.map((name) => [
name,
process.env.NODE_ENV === "production"
? async () => {
const loaded = await importPlugin();
Object.assign(plugin, loaded);
return loaded[property][name];
}
: async () => {
const loaded = await importPlugin();
// Hide `estree` printer in js plugin
plugin[property] = loaded[property];
return loaded[property][name];
},
]),
);
}
}
return plugin;
}
export { toLazyLoadPlugin };
+14 -2
View File
@@ -1,7 +1,19 @@
import builtinPluginsProxy from "../../plugins/builtin-plugins-proxy.js";
const loadBuiltinPluginsWithoutCache =
process.env.NODE_ENV === "production"
? async function () {
return Object.values(
await import("./builtin-plugins/production-plugins.js"),
);
}
: async function () {
return Object.values(
await import("./builtin-plugins/development-plugins.js"),
);
};
let cache;
function loadBuiltinPlugins() {
return builtinPluginsProxy;
return (cache ??= loadBuiltinPluginsWithoutCache());
}
export default loadBuiltinPlugins;
+3 -3
View File
@@ -1,4 +1,4 @@
import { acorn } from "../language-js/parse/acorn.js";
import { espree } from "../language-js/parse/espree.js";
import * as acornParsers from "../language-js/parse/acorn.js";
import * as espreeParsers from "../language-js/parse/espree.js";
export const parsers = { acorn, espree };
export const parsers = { ...acornParsers, ...espreeParsers };
+1 -1
View File
@@ -1,5 +1,5 @@
import * as babelParsers from "../language-js/parse/babel.js";
// JSON parsers are based on babel, bundle together to reduce package size
import * as jsonParsers from "../language-json/parser-json.js";
import * as jsonParsers from "../language-json/parsers.js";
export const parsers = { ...babelParsers, ...jsonParsers };
-153
View File
@@ -1,153 +0,0 @@
import cssLanguages from "../language-css/languages.evaluate.js";
import cssOptions from "../language-css/options.js";
import graphqlLanguages from "../language-graphql/languages.evaluate.js";
import graphqlOptions from "../language-graphql/options.js";
import handlebarsLanguages from "../language-handlebars/languages.evaluate.js";
import htmlLanguages from "../language-html/languages.evaluate.js";
import htmlOptions from "../language-html/options.js";
import jsLanguages from "../language-js/languages.evaluate.js";
import jsOptions from "../language-js/options.js";
import jsonLanguages from "../language-json/languages.evaluate.js";
import markdownLanguages from "../language-markdown/languages.evaluate.js";
import markdownOptions from "../language-markdown/options.js";
import yamlLanguages from "../language-yaml/languages.evaluate.js";
import yamlOptions from "../language-yaml/options.js";
// Lazy load the plugins
function createParsersAndPrinters(modules) {
const parsers = Object.create(null);
const printers = Object.create(null);
for (const {
importPlugin,
parsers: parserNames = [],
printers: printerNames = [],
} of modules) {
const loadPlugin = async () => {
const plugin = await importPlugin();
Object.assign(parsers, plugin.parsers);
Object.assign(printers, plugin.printers);
return plugin;
};
for (const parserName of parserNames) {
parsers[parserName] = async () =>
(await loadPlugin()).parsers[parserName];
}
for (const printerName of printerNames) {
printers[printerName] = async () =>
(await loadPlugin()).printers[printerName];
}
}
return { parsers, printers };
}
// We need create estree as a separate plugin
// For details see tests/integration/__tests__/plugin-override-builtin-printers.js
const estreePlugin = createParsersAndPrinters([
{
importPlugin: () => import("./estree.js"),
printers: ["estree", "estree-json"],
},
]);
const options = {
...cssOptions,
...graphqlOptions,
...htmlOptions,
...jsOptions,
...markdownOptions,
...yamlOptions,
};
const languages = [
...cssLanguages,
...graphqlLanguages,
...handlebarsLanguages,
...htmlLanguages,
...jsLanguages,
...jsonLanguages,
...markdownLanguages,
...yamlLanguages,
];
const { parsers, printers } = createParsersAndPrinters([
{
importPlugin: () => import("./acorn.js"),
parsers: ["acorn", "espree"],
},
{
importPlugin: () => import("./angular.js"),
parsers: [
"__ng_action",
"__ng_binding",
"__ng_interpolation",
"__ng_directive",
],
},
{
importPlugin: () => import("./babel.js"),
parsers: [
"babel",
"babel-flow",
"babel-ts",
"__js_expression",
"__ts_expression",
"__vue_expression",
"__vue_ts_expression",
"__vue_event_binding",
"__vue_ts_event_binding",
"__babel_estree",
"json",
"json5",
"jsonc",
"json-stringify",
],
},
{
importPlugin: () => import("./flow.js"),
parsers: ["flow"],
},
{
importPlugin: () => import("./glimmer.js"),
parsers: ["glimmer"],
printers: ["glimmer"],
},
{
importPlugin: () => import("./graphql.js"),
parsers: ["graphql"],
printers: ["graphql"],
},
{
importPlugin: () => import("./html.js"),
parsers: ["html", "angular", "vue", "lwc", "mjml"],
printers: ["html"],
},
{
importPlugin: () => import("./markdown.js"),
parsers: ["markdown", "mdx", "remark"],
printers: ["mdast"],
},
{
importPlugin: () => import("./meriyah.js"),
parsers: ["meriyah"],
},
{
importPlugin: () => import("./postcss.js"),
parsers: ["css", "less", "scss"],
printers: ["postcss"],
},
{
importPlugin: () => import("./typescript.js"),
parsers: ["typescript"],
},
{
importPlugin: () => import("./yaml.js"),
parsers: ["yaml"],
printers: ["yaml"],
},
]);
export default [estreePlugin, { options, languages, parsers, printers }];
+15 -6
View File
@@ -1,11 +1,20 @@
// Can't bundle due to side effects
// import {
// languages as jsLanguages,
// options as jsOptions,
// printers as jsPrinters,
// } from "../language-js/index.js";
import jsLanguages from "../language-js/languages.evaluate.js";
import * as estreePrinter from "../language-js/printer.js";
import jsonLanguages from "../language-json/languages.evaluate.js";
import * as estreeJsonPrinter from "../language-json/printer-estree-json.js";
import jsOptions from "../language-js/options.js";
import * as jsPrinters from "../language-js/printers.js";
import {
languages as jsonLanguages,
printers as jsonPrinters,
} from "../language-json/index.js";
export const printers = {
estree: estreePrinter,
"estree-json": estreeJsonPrinter,
...jsPrinters,
...jsonPrinters,
};
export const languages = [...jsLanguages, ...jsonLanguages];
export { default as options } from "../language-js/options.js";
export const options = { ...jsOptions };
+45 -18
View File
@@ -570,27 +570,54 @@ async function format(originalText, originalOptions) {
};
}
const externalPlugins = new Map([
["oxc", "plugin-oxc"],
["oxc-ts", "plugin-oxc"],
["hermes", "plugin-hermes"],
]);
async function getExternalPlugins() {
const distDirectory = new URL("../../dist/", import.meta.url);
const directories = fs.readdirSync(distDirectory);
const plugins = await Promise.all(
directories
.filter((directory) => directory.startsWith("plugin-"))
.map(async (directory) => {
const url = new URL(`./${directory}/index.mjs`, distDirectory);
const implementation = await import(url);
return {
url,
implementation,
};
}),
);
const externalParsers = new Map();
for (const plugin of plugins) {
const { parsers } = plugin.implementation;
if (parsers) {
for (const parser of Object.keys(parsers)) {
externalParsers.set(parser, plugin);
}
}
}
return externalParsers;
}
let externalParsers;
async function loadPlugins(options) {
const { parser } = options;
if (externalPlugins.has(options.parser)) {
const plugins = options.plugins ?? [];
const pluginName = externalPlugins.get(parser);
const url = new URL(
isProduction
? `../../dist/${pluginName}/index.mjs`
: `../../packages/${pluginName}/index.js`,
import.meta.url,
);
plugins.push(TEST_STANDALONE ? await import(url) : url);
return { ...options, plugins };
if (!isProduction || !options.parser) {
return options;
}
return options;
const { parser } = options;
if (!externalParsers) {
externalParsers = getExternalPlugins();
externalParsers = await externalParsers;
}
if (!externalParsers.has(parser)) {
return options;
}
const plugin = externalParsers.get(parser);
const plugins = options.plugins ?? [];
plugins.push(TEST_STANDALONE ? plugin.implementation : plugin.url);
return { ...options, plugins };
}
export default runFormatTest;
+3
View File
@@ -29,6 +29,7 @@ test("builtin parsers", async () => {
"flow",
"glimmer",
"graphql",
"hermes",
"html",
"json",
"json-stringify",
@@ -40,6 +41,8 @@ test("builtin parsers", async () => {
"mdx",
"meriyah",
"mjml",
"oxc",
"oxc-ts",
"remark",
"scss",
"typescript",