333 lines
7.9 KiB
JavaScript
333 lines
7.9 KiB
JavaScript
import { createDocExplorerPlugin } from "./prettier-plugin-doc-explorer.mjs";
|
|
|
|
/**
|
|
@import {PlaygroundSettings} from "../playground/composables/playground-settings.js"
|
|
@typedef {{
|
|
type: "meta",
|
|
version: "stable" | "next",
|
|
}} MetaMessage
|
|
@typedef {{
|
|
type: "format",
|
|
code: string,
|
|
options: any,
|
|
settings: PlaygroundSettings,
|
|
version: "stable" | "next",
|
|
}} FormatMessage
|
|
*/
|
|
|
|
const workerUrl = new URL(import.meta.url);
|
|
const version =
|
|
workerUrl.searchParams.get("version") === "next" ? "next" : "stable";
|
|
|
|
let prettierCache;
|
|
let prettierPackageManifestCache;
|
|
let prettierPluginDocExplorerCache;
|
|
let pluginsCache;
|
|
|
|
async function loadPrettier() {
|
|
prettierCache ??= import(`./lib/${version}/prettier/standalone.mjs`);
|
|
return await prettierCache;
|
|
}
|
|
|
|
async function loadPrettierPackageManifest() {
|
|
prettierPackageManifestCache ??= import(
|
|
`./lib/${version}/package-manifest.mjs`
|
|
).then((m) => m.default);
|
|
return await prettierPackageManifestCache;
|
|
}
|
|
|
|
async function loadPrettierPluginDocExplorer() {
|
|
if (!prettierPluginDocExplorerCache) {
|
|
const prettier = await loadPrettier();
|
|
const prettierPackageManifest = await loadPrettierPackageManifest();
|
|
prettierPluginDocExplorerCache = createDocExplorerPlugin(
|
|
prettier,
|
|
prettierPackageManifest,
|
|
version,
|
|
);
|
|
}
|
|
return prettierPluginDocExplorerCache;
|
|
}
|
|
|
|
async function loadPlugins() {
|
|
if (!pluginsCache) {
|
|
const prettierPackageManifest = await loadPrettierPackageManifest();
|
|
const prettierPluginDocExplorer = await loadPrettierPluginDocExplorer();
|
|
pluginsCache = [
|
|
...prettierPackageManifest.plugins.map((plugin) => createPlugin(plugin)),
|
|
prettierPluginDocExplorer,
|
|
];
|
|
}
|
|
return pluginsCache;
|
|
}
|
|
|
|
const pluginLoadPromises = new Map();
|
|
async function importPlugin(plugin) {
|
|
if (!pluginLoadPromises.has(plugin)) {
|
|
pluginLoadPromises.set(plugin, import(`./lib/${version}/${plugin.file}`));
|
|
}
|
|
|
|
try {
|
|
return await pluginLoadPromises.get(plugin);
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(error);
|
|
throw new Error(`Load plugin '${plugin.file}' failed.`, { cause: error });
|
|
}
|
|
}
|
|
|
|
// Similar to `createParsersAndPrinters` in `src/plugins/builtin-plugins-proxy.js`
|
|
function createPlugin(pluginManifest) {
|
|
const { languages, options, defaultOptions } = pluginManifest;
|
|
|
|
const parsers = Object.create(null);
|
|
const printers = Object.create(null);
|
|
const plugin = { languages, options, defaultOptions, parsers, printers };
|
|
|
|
const loadPlugin = async () => {
|
|
const plugin = await importPlugin(pluginManifest);
|
|
Object.assign(parsers, plugin.parsers);
|
|
Object.assign(printers, plugin.printers);
|
|
return plugin;
|
|
};
|
|
|
|
for (const property of ["parsers", "printers"]) {
|
|
for (const name of pluginManifest[property] ?? []) {
|
|
plugin[property][name] = async () => (await loadPlugin())[property][name];
|
|
}
|
|
}
|
|
|
|
return plugin;
|
|
}
|
|
|
|
self.onmessage = async function (event) {
|
|
self.postMessage({
|
|
uid: event.data.uid,
|
|
message: await handleMessage(event.data.message),
|
|
});
|
|
};
|
|
|
|
function serializeError(error) {
|
|
const serialized = {
|
|
name: error.name,
|
|
message: error.message,
|
|
...error,
|
|
};
|
|
|
|
if (error.cause instanceof Error) {
|
|
serialized.cause = serializeError(error.cause);
|
|
}
|
|
|
|
return serialized;
|
|
}
|
|
|
|
function serializeAst(ast) {
|
|
return JSON.stringify(
|
|
ast,
|
|
(_, value) => {
|
|
if (value instanceof Error) {
|
|
return serializeError(value);
|
|
}
|
|
|
|
if (typeof value === "bigint") {
|
|
return `BigInt('${String(value)}')`;
|
|
}
|
|
|
|
if (typeof value === "symbol") {
|
|
return String(value);
|
|
}
|
|
|
|
return value;
|
|
},
|
|
2,
|
|
);
|
|
}
|
|
|
|
/**
|
|
@param {MetaMessage | FormatMessage} message
|
|
*/
|
|
function handleMessage(message) {
|
|
switch (message.type) {
|
|
case "meta":
|
|
return handleMetaMessage();
|
|
case "format":
|
|
return handleFormatMessage(message);
|
|
}
|
|
}
|
|
|
|
async function handleMetaMessage() {
|
|
const prettier = await loadPrettier();
|
|
const prettierPackageManifest = await loadPrettierPackageManifest();
|
|
|
|
const plugins = await loadPlugins();
|
|
const supportInfo = await prettier.getSupportInfo({ plugins });
|
|
|
|
return {
|
|
type: "meta",
|
|
// eslint-disable-next-line unicorn/prefer-structured-clone
|
|
supportInfo: JSON.parse(JSON.stringify(supportInfo)),
|
|
version: prettierPackageManifest.version,
|
|
};
|
|
}
|
|
|
|
/**
|
|
@param {FormatMessage} message
|
|
*/
|
|
async function handleFormatMessage(message) {
|
|
const prettier = await loadPrettier();
|
|
const plugins = await loadPlugins();
|
|
let { options, code, settings } = message;
|
|
options = { ...options, plugins };
|
|
|
|
const isDocExplorer = options.parser === "doc-explorer";
|
|
|
|
const formatResult = await formatCode(code, options, settings);
|
|
|
|
const response = {
|
|
formatted: formatResult.formatted,
|
|
cursorOffset: formatResult.cursorOffset,
|
|
error: formatResult.error,
|
|
debug: {
|
|
ast: null,
|
|
doc: null,
|
|
comments: null,
|
|
reformatted: null,
|
|
},
|
|
};
|
|
|
|
for (const { resultKey, settingsKey, preprocessForPrint } of [
|
|
{
|
|
resultKey: "ast",
|
|
settingsKey: "showAst",
|
|
preprocessForPrint: false,
|
|
},
|
|
{
|
|
resultKey: "preprocessedAst",
|
|
settingsKey: "showPreprocessedAst",
|
|
preprocessForPrint: true,
|
|
},
|
|
]) {
|
|
if (!settings[settingsKey]) {
|
|
continue;
|
|
}
|
|
|
|
if (isDocExplorer && preprocessForPrint) {
|
|
continue;
|
|
}
|
|
|
|
let ast;
|
|
let errored = false;
|
|
try {
|
|
const parsed = await prettier.__debug.parse(code, options, {
|
|
preprocessForPrint,
|
|
});
|
|
ast = serializeAst(parsed.ast);
|
|
} catch (e) {
|
|
errored = true;
|
|
ast = String(e);
|
|
}
|
|
|
|
if (!errored) {
|
|
try {
|
|
ast = (await formatCode(ast, { parser: "json", plugins })).formatted;
|
|
} catch {
|
|
ast = serializeAst(ast);
|
|
}
|
|
}
|
|
response.debug[resultKey] = ast;
|
|
}
|
|
|
|
if (!isDocExplorer && settings.showDoc) {
|
|
if (formatResult.error) {
|
|
response.debug.doc = formatResult.formatted;
|
|
} else {
|
|
try {
|
|
response.debug.doc = await prettier.__debug.formatDoc(
|
|
await prettier.__debug.printToDoc(code, options),
|
|
{ plugins },
|
|
);
|
|
} catch {
|
|
response.debug.doc = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isDocExplorer && settings.showComments) {
|
|
if (formatResult.error) {
|
|
response.debug.comments = formatResult.formatted;
|
|
} else {
|
|
response.debug.comments = (
|
|
await formatCode(JSON.stringify(formatResult.comments || []), {
|
|
parser: "json",
|
|
plugins,
|
|
})
|
|
).formatted;
|
|
}
|
|
}
|
|
|
|
if (!isDocExplorer && settings.showSecondFormat) {
|
|
response.debug.reformatted = (
|
|
await formatCode(response.formatted, options, settings)
|
|
).formatted;
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
@param {string} text
|
|
@param {any} options
|
|
@param {PlaygroundSettings} settings
|
|
*/
|
|
async function formatCode(text, options, settings = {}) {
|
|
const prettier = await loadPrettier();
|
|
if (options.parser === "doc-explorer") {
|
|
options = {
|
|
...options,
|
|
cursorOffset: undefined,
|
|
rangeStart: undefined,
|
|
rangeEnd: undefined,
|
|
};
|
|
}
|
|
|
|
try {
|
|
self.PRETTIER_DEBUG = settings.rethrowEmbedErrors;
|
|
return await prettier.formatWithCursor(text, options);
|
|
} catch (error) {
|
|
if (error.constructor?.name === "SyntaxError") {
|
|
// Likely something wrong with the user's code
|
|
return { formatted: String(error), error: true };
|
|
}
|
|
// Likely a bug in Prettier
|
|
// Provide the whole stack for debugging
|
|
return {
|
|
formatted: stringifyError(error),
|
|
error: true,
|
|
};
|
|
} finally {
|
|
self.PRETTIER_DEBUG = undefined;
|
|
}
|
|
}
|
|
|
|
function stringifyError(error) {
|
|
const { stack, cause } = error;
|
|
|
|
let stringified = String(error);
|
|
|
|
if (typeof stack === "string") {
|
|
if (stack.includes(stringified)) {
|
|
// Chrome
|
|
stringified = stack;
|
|
} else {
|
|
// Firefox
|
|
stringified += "\n" + error.stack;
|
|
}
|
|
}
|
|
|
|
if (cause instanceof Error) {
|
|
stringified += "\nCause: " + stringifyError(cause);
|
|
}
|
|
|
|
return stringified;
|
|
}
|