From ca5c11e95d1d7fc1f3f7499caea79b40af5f71dc Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Tue, 25 Nov 2025 02:13:03 +0800 Subject: [PATCH] Enable `@prettier/plugin-oxc` in playground (#18315) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- knip.config.js | 7 +- package.json | 1 + packages/plugin-oxc/package.json | 1 + scripts/build-website.js | 3 + scripts/build/build-javascript-module.js | 2 + scripts/build/build-oxc-wasm-parser.js | 99 +++++++++++++++++++ scripts/build/build.js | 1 + scripts/build/config.js | 32 +++++- .../build/esbuild-plugins/throw-warnings.js | 7 ++ src/language-js/parse/acorn.js | 4 +- src/language-js/parse/babel.js | 4 +- src/language-js/parse/espree.js | 4 +- src/language-js/parse/meriyah.js | 4 +- src/language-js/parse/oxc.js | 4 +- src/language-js/parse/typescript.js | 4 +- src/utils/try-combinations.js | 7 +- website/playground/codeSamples.mjs | 2 + yarn.lock | 8 ++ 18 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 scripts/build/build-oxc-wasm-parser.js diff --git a/knip.config.js b/knip.config.js index d2f5ba576..99a925395 100644 --- a/knip.config.js +++ b/knip.config.js @@ -24,7 +24,12 @@ const config = { "src/experimental-cli/**", "src/universal/*.browser.js", ], - ignoreDependencies: ["ts-expect", "buffer", "deno-path-from-file-url"], + ignoreDependencies: [ + "ts-expect", + "buffer", + "deno-path-from-file-url", + "base64-arraybuffer-es6", + ], ignoreBinaries: ["test-coverage"], }, website: { diff --git a/package.json b/package.json index 37a0a2a42..58f94ca36 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "@types/jsesc": "3.0.3", "@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/parser": "8.47.0", + "base64-arraybuffer-es6": "3.1.0", "browserslist": "4.28.0", "browserslist-to-esbuild": "2.1.1", "buffer": "6.0.3", diff --git a/packages/plugin-oxc/package.json b/packages/plugin-oxc/package.json index 3af6f1472..ccfa4e616 100644 --- a/packages/plugin-oxc/package.json +++ b/packages/plugin-oxc/package.json @@ -6,6 +6,7 @@ "exports": { ".": { "types": "./index.d.ts", + "browser": "./index.browser.mjs", "default": "./index.mjs" }, "./*": "./*" diff --git a/scripts/build-website.js b/scripts/build-website.js index d9867ac3b..55e1804f8 100644 --- a/scripts/build-website.js +++ b/scripts/build-website.js @@ -68,6 +68,9 @@ async function buildPlaygroundFiles() { for (const pluginName of ["plugin-hermes"]) { pluginFiles.push(`${pluginName}/index.mjs`); } + for (const pluginName of ["plugin-oxc"]) { + pluginFiles.push(`${pluginName}/index.browser.mjs`); + } } const packageManifest = { diff --git a/scripts/build/build-javascript-module.js b/scripts/build/build-javascript-module.js index 2a67e2803..fe78ab6a7 100644 --- a/scripts/build/build-javascript-module.js +++ b/scripts/build/build-javascript-module.js @@ -200,6 +200,7 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { esbuildPluginThrowWarnings({ allowDynamicRequire: file.platform === "node", allowDynamicImport: file.platform === "node", + allowedWarnings: buildOptions.allowedWarnings, }), buildOptions.addDefaultExport && esbuildPluginAddDefaultExport(), ].filter(Boolean), @@ -217,6 +218,7 @@ function getEsbuildOptions({ packageConfig, file, cliOptions }) { // https://esbuild.github.io/api/#main-fields mainFields: file.platform === "node" ? ["module", "main"] : undefined, supported: { + ...buildOptions.supported, // https://github.com/evanw/esbuild/issues/3471 "regexp-unicode-property-escapes": true, // Maybe because Node.js v14 doesn't support "spread parameters after optional chaining" https://node.green/ diff --git a/scripts/build/build-oxc-wasm-parser.js b/scripts/build/build-oxc-wasm-parser.js new file mode 100644 index 000000000..3b235608e --- /dev/null +++ b/scripts/build/build-oxc-wasm-parser.js @@ -0,0 +1,99 @@ +import assert from "node:assert/strict"; +import { existsSync, promises as fs } from "node:fs"; +import url from "node:url"; +import spawn from "nano-spawn"; +import { outdent } from "outdent"; +import packageJson from "../../package.json" with { type: "json" }; + +const DIRECTORY_NAME = "prettier-oxc-wasm-parser"; +const PACKAGE_NAME = "@oxc-parser/binding-wasm32-wasi"; + +const TEMPORARY_DIRECTORY = new URL("../../.tmp/", import.meta.url); + +const runYarn = (command, options) => + spawn("yarn", command.split(" "), options); + +async function install(version) { + const directory = new URL(`./installing.${Date.now()}/`, TEMPORARY_DIRECTORY); + + await fs.rm(directory, { force: true, recursive: true }); + await fs.mkdir(directory, { recursive: true }); + await runYarn("init", { cwd: directory }); + await fs.writeFile(new URL("./yarn.lock", directory), ""); + await fs.writeFile(new URL(".yarnrc.yml", directory), ""); + await runYarn("config set supportedArchitectures.cpu wasm32", { + cwd: directory, + }); + + await runYarn(`add ${PACKAGE_NAME}@${version}`, { cwd: directory }); + + assert.ok(existsSync(new URL(`./node_modules/${PACKAGE_NAME}/`, directory))); + + const { version: installedVersion } = JSON.parse( + await fs.readFile( + new URL(`./node_modules/${PACKAGE_NAME}/package.json`, directory), + ), + ); + + assert.equal(installedVersion, version); + + return directory; +} + +async function inlineWasmBinary(directory) { + const packageDirectory = new URL( + "./node_modules/@oxc-parser/binding-wasm32-wasi/", + directory, + ); + const entryFile = new URL("./browser-bundle.js", packageDirectory); + const wasmFile = new URL("./parser.wasm32-wasi.wasm", packageDirectory); + + let text = await fs.readFile(entryFile, "utf8"); + const wasm = await fs.readFile(wasmFile, "base64"); + + text = outdent` + import { decode as __decode } from "base64-arraybuffer-es6"; + const __base64ToArrayBuffer = Uint8Array.fromBase64 + ? (string) => Uint8Array.fromBase64(string).buffer + : __decode; + + ${text} + `; + + text = text.replace( + "await fetch(__wasmUrl).then((res) => res.arrayBuffer())", + `__base64ToArrayBuffer(${JSON.stringify(wasm)})`, + ); + + await fs.writeFile(entryFile, text); +} + +async function buildOxcWasmParser() { + const version = packageJson.dependencies["oxc-parser"]; + const directory = new URL( + `./${DIRECTORY_NAME}@${version}/`, + TEMPORARY_DIRECTORY, + ); + const entry = new URL("./index.mjs", directory); + + if (!existsSync(entry)) { + await fs.rm(directory, { recursive: true, force: true }); + + const installDirectory = await install(version); + await fs.rename(installDirectory, directory); + + await fs.writeFile( + entry, + `export {parseSync as parse} from '${PACKAGE_NAME}/browser-bundle.js'`, + ); + + await inlineWasmBinary(directory); + } + + return { + entry: url.fileURLToPath(entry), + directory: url.fileURLToPath(directory), + }; +} + +export default buildOxcWasmParser; diff --git a/scripts/build/build.js b/scripts/build/build.js index 2db789581..bbf30a308 100644 --- a/scripts/build/build.js +++ b/scripts/build/build.js @@ -51,6 +51,7 @@ async function buildFile({ packageConfig, file, cliOptions, results }) { if ( (file.platform === "universal" && file.output.format !== "esm") || (file.output.file.startsWith("index.") && file.output.format !== "esm") || + file.output.file.endsWith(".browser.mjs") || file.kind === "types" ) { displayName = ` ${displayName}`; diff --git a/scripts/build/config.js b/scripts/build/config.js index c62c149d8..79b88b961 100644 --- a/scripts/build/config.js +++ b/scripts/build/config.js @@ -6,6 +6,7 @@ import { outdent } from "outdent"; import { copyFile, DIST_DIR, PROJECT_ROOT } from "../utils/index.js"; import buildDependenciesLicense from "./build-dependencies-license.js"; import buildJavascriptModule from "./build-javascript-module.js"; +import buildOxcWasmParser from "./build-oxc-wasm-parser.js"; import { buildPluginHermesPackageJson, buildPluginOxcPackageJson, @@ -28,6 +29,7 @@ const copyFileBuilder = ({ packageConfig, file }) => path.join(PROJECT_ROOT, file.input), path.join(packageConfig.distDirectory, file.output.file), ); +const oxcWasmParser = await buildOxcWasmParser(); function getTypesFileConfig({ input: jsFileInput, outputBaseName, isPlugin }) { let input = jsFileInput; @@ -948,7 +950,6 @@ export default [ { input: "packages/plugin-oxc/index.js", addDefaultExport: true, - external: ["oxc-parser"], }, ].flatMap((file) => { let { input, output, outputBaseName, ...buildOptions } = file; @@ -963,11 +964,36 @@ export default [ format, file: `${outputBaseName}${extensions[format]}`, }, - platform: "universal", - buildOptions, + platform: "node", + buildOptions: { + ...buildOptions, + external: ["oxc-parser"], + }, build: buildJavascriptModule, kind: "javascript", }, + { + input, + output: { + format, + file: `${outputBaseName}.browser${extensions[format]}`, + }, + platform: "universal", + buildOptions: { + ...buildOptions, + replaceModule: [ + ...(buildOptions.replaceModule ?? []), + { + module: getPackageFile("oxc-parser/src-js/wasm.js"), + path: oxcWasmParser.entry, + }, + ], + allowedWarnings: ["indirect-require", "package.json"], + }, + build: buildJavascriptModule, + kind: "javascript", + playground: true, + }, getTypesFileConfig({ input, outputBaseName, isPlugin: true }), ]; }), diff --git a/scripts/build/esbuild-plugins/throw-warnings.js b/scripts/build/esbuild-plugins/throw-warnings.js index 144606e2a..cec5c5707 100644 --- a/scripts/build/esbuild-plugins/throw-warnings.js +++ b/scripts/build/esbuild-plugins/throw-warnings.js @@ -71,7 +71,10 @@ const logOverride = Object.fromEntries( export default function esbuildPluginThrowWarnings({ allowDynamicRequire, allowDynamicImport, + allowedWarnings, }) { + allowedWarnings = new Set(allowedWarnings); + return { name: "throw-warnings", setup(build) { @@ -87,6 +90,10 @@ export default function esbuildPluginThrowWarnings({ } for (const warning of result.warnings) { + if (allowedWarnings.has(warning.id)) { + continue; + } + if ( allowDynamicRequire && ["unsupported-require-call", "indirect-require"].includes( diff --git a/src/language-js/parse/acorn.js b/src/language-js/parse/acorn.js index abe7dd12c..d93e2df4b 100644 --- a/src/language-js/parse/acorn.js +++ b/src/language-js/parse/acorn.js @@ -1,7 +1,7 @@ import { Parser as AcornParser } from "acorn"; import acornJsx from "acorn-jsx"; import createError from "../../common/parser-create-error.js"; -import tryCombinations from "../../utils/try-combinations.js"; +import { tryCombinationsSync } from "../../utils/try-combinations.js"; import postprocess from "./postprocess/index.js"; import createParser from "./utils/create-parser.js"; import { @@ -81,7 +81,7 @@ function parse(text, options) { let ast; try { - ast = tryCombinations(combinations); + ast = tryCombinationsSync(combinations); } catch (/** @type {any} */ { errors: [error] }) { throw createParseError(error); } diff --git a/src/language-js/parse/babel.js b/src/language-js/parse/babel.js index 9e0e57219..49007165d 100644 --- a/src/language-js/parse/babel.js +++ b/src/language-js/parse/babel.js @@ -1,6 +1,6 @@ import { parse as babelParse, parseExpression } from "@babel/parser"; import getNextNonSpaceNonCommentCharacterIndex from "../../utils/get-next-non-space-non-comment-character-index.js"; -import tryCombinations from "../../utils/try-combinations.js"; +import { tryCombinationsSync } from "../../utils/try-combinations.js"; import getShebang from "../utils/get-shebang.js"; import postprocess from "./postprocess/index.js"; import createBabelParseError from "./utils/create-babel-parse-error.js"; @@ -149,7 +149,7 @@ function createParse({ isExpression = false, optionsCombinations }) { let ast; try { - ast = tryCombinations( + ast = tryCombinationsSync( combinations.map( (options) => () => parseWithOptions(parseFunction, text, options), ), diff --git a/src/language-js/parse/espree.js b/src/language-js/parse/espree.js index 1df42d7e7..cf2790314 100644 --- a/src/language-js/parse/espree.js +++ b/src/language-js/parse/espree.js @@ -1,6 +1,6 @@ import { parse as espreeParse } from "espree"; import createError from "../../common/parser-create-error.js"; -import tryCombinations from "../../utils/try-combinations.js"; +import { tryCombinationsSync } from "../../utils/try-combinations.js"; import postprocess from "./postprocess/index.js"; import createParser from "./utils/create-parser.js"; import { @@ -48,7 +48,7 @@ function parse(text, options) { let ast; try { - ast = tryCombinations(combinations); + ast = tryCombinationsSync(combinations); } catch (/** @type {any} */ { errors: [error] }) { throw createParseError(error); } diff --git a/src/language-js/parse/meriyah.js b/src/language-js/parse/meriyah.js index cceba7717..14a70a114 100644 --- a/src/language-js/parse/meriyah.js +++ b/src/language-js/parse/meriyah.js @@ -1,6 +1,6 @@ import { parse as meriyahParse } from "meriyah"; import createError from "../../common/parser-create-error.js"; -import tryCombinations from "../../utils/try-combinations.js"; +import { tryCombinationsSync } from "../../utils/try-combinations.js"; import postprocess from "./postprocess/index.js"; import createParser from "./utils/create-parser.js"; import { @@ -86,7 +86,7 @@ function parse(text, options) { let ast; try { - ast = tryCombinations(combinations); + ast = tryCombinationsSync(combinations); } catch (/** @type {any} */ { errors: [error] }) { throw createParseError(error); } diff --git a/src/language-js/parse/oxc.js b/src/language-js/parse/oxc.js index c29ac9eaa..b48a36782 100644 --- a/src/language-js/parse/oxc.js +++ b/src/language-js/parse/oxc.js @@ -1,7 +1,7 @@ import indexToPosition from "index-to-position"; import { parse as oxcParse } from "oxc-parser"; import createError from "../../common/parser-create-error.js"; -import { tryCombinationsAsync } from "../../utils/try-combinations.js"; +import { tryCombinations } from "../../utils/try-combinations.js"; import postprocess from "./postprocess/index.js"; import createParser from "./utils/create-parser.js"; import jsxRegexp from "./utils/jsx-regexp.evaluate.js"; @@ -113,7 +113,7 @@ async function parseTs(text, options) { let result; try { - result = await tryCombinationsAsync( + result = await tryCombinations( languageCombinations.map( (language) => () => parseWithOptions(filepath, text, { diff --git a/src/language-js/parse/typescript.js b/src/language-js/parse/typescript.js index 987ff56e1..a2c6cbab7 100644 --- a/src/language-js/parse/typescript.js +++ b/src/language-js/parse/typescript.js @@ -1,6 +1,6 @@ import { parse as parseTypeScript } from "@typescript-eslint/typescript-estree"; import createError from "../../common/parser-create-error.js"; -import tryCombinations from "../../utils/try-combinations.js"; +import { tryCombinationsSync } from "../../utils/try-combinations.js"; import postprocess from "./postprocess/index.js"; import createParser from "./utils/create-parser.js"; import jsxRegexp from "./utils/jsx-regexp.evaluate.js"; @@ -88,7 +88,7 @@ function parse(text, options) { let ast; try { - ast = tryCombinations( + ast = tryCombinationsSync( parseOptionsCombinations.map( (parseOptions) => () => parseTypeScript(textToParse, parseOptions), ), diff --git a/src/utils/try-combinations.js b/src/utils/try-combinations.js index 5ba940306..0cc2d052e 100644 --- a/src/utils/try-combinations.js +++ b/src/utils/try-combinations.js @@ -3,7 +3,7 @@ * @param {T[]} combinations * @returns {ReturnType} */ -function tryCombinations(combinations) { +function tryCombinationsSync(combinations) { const errors = []; for (const fn of combinations) { try { @@ -23,7 +23,7 @@ function tryCombinations(combinations) { * @param {T[]} combinations * @returns {Promise>} */ -async function tryCombinationsAsync(combinations) { +async function tryCombinations(combinations) { const errors = []; for (const fn of combinations) { try { @@ -38,5 +38,4 @@ async function tryCombinationsAsync(combinations) { throw Object.assign(new Error("All combinations failed"), { errors }); } -export default tryCombinations; -export { tryCombinationsAsync }; +export { tryCombinations, tryCombinationsSync }; diff --git a/website/playground/codeSamples.mjs b/website/playground/codeSamples.mjs index af2a8efdb..17adbf6d9 100644 --- a/website/playground/codeSamples.mjs +++ b/website/playground/codeSamples.mjs @@ -4,6 +4,7 @@ export default function getCodeSamples(parser) { case "acorn": case "espree": case "meriyah": + case "oxc": return [ 'function HelloWorld({greeting = "hello", greeted = \'"World"\', silent = false, onMouseOver,}) {', "", @@ -42,6 +43,7 @@ export default function getCodeSamples(parser) { ].join("\n"); case "typescript": case "babel-ts": + case "oxc-ts": return [ "interface MyInterface {", " foo(): string,", diff --git a/yarn.lock b/yarn.lock index c12ad4d08..e8e1e4c0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3100,6 +3100,13 @@ __metadata: languageName: node linkType: hard +"base64-arraybuffer-es6@npm:3.1.0": + version: 3.1.0 + resolution: "base64-arraybuffer-es6@npm:3.1.0" + checksum: 10/a623b7e396785b67e87ee92b474506a8ba1e26b977260bd2b97fde1332973de86689018dc847a7a4b1a238b09a5b3be0b51b9d3879815c220b2171c1722f3b6f + languageName: node + linkType: hard + "base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" @@ -7501,6 +7508,7 @@ __metadata: acorn-jsx: "npm:5.3.2" angular-estree-parser: "npm:14.1.0" angular-html-parser: "npm:10.1.1" + base64-arraybuffer-es6: "npm:3.1.0" browserslist: "npm:4.28.0" browserslist-to-esbuild: "npm:2.1.1" buffer: "npm:6.0.3"