Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/oxfmt/src-js/cli-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// `oxfmt` CLI - Worker Thread Entry Point

// Re-exports core functions for use in `worker_threads`
export { formatEmbeddedCode, formatFile } from "./libs/prettier";
40 changes: 21 additions & 19 deletions apps/oxfmt/src-js/cli.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { runCli } from "./bindings.js";
import { initExternalFormatter, formatEmbeddedCode, formatFile } from "./prettier-proxy.js";
import { runInit, runMigratePrettier } from "./migration/index.js";
import { runCli } from "./bindings";
import { initExternalFormatter, formatEmbeddedCode, formatFile } from "./cli/worker-proxy";

// napi-JS `oxfmt` CLI entry point
// See also `run_cli()` function in `./src/main_napi.rs`

void (async () => {
const args = process.argv.slice(2);

// Call the Rust CLI to parse args and determine mode
// Call the Rust CLI first, to parse args and determine mode
// NOTE: If the mode is formatter CLI, it will also perform formatting and return an exit code
const [mode, exitCode] = await runCli(
args,
Expand All @@ -14,20 +16,20 @@ void (async () => {
formatFile,
);

switch (mode) {
// Handle `--init` and `--migrate` command in JS
case "init":
await runInit();
break;
case "migrate:prettier":
await runMigratePrettier();
break;
// Other modes are handled by Rust, just need to set `exitCode`
default:
// NOTE: It's recommended to set `process.exitCode` instead of calling `process.exit()`.
// `process.exit()` kills the process immediately and `stdout` may not be flushed before process dies.
// https://nodejs.org/api/process.html#processexitcode
if (exitCode) process.exitCode = exitCode;
break;
// Migration modes are handled by JS
if (mode === "init") {
await import("./cli/migration/init").then((m) => m.runInit());
return;
}
if (mode === "migrate:prettier") {
await import("./cli/migration/migrate-prettier").then((m) => m.runMigratePrettier());
return;
}

// Other modes are handled by Rust, just need to set `exitCode`

// NOTE: It's recommended to set `process.exitCode` instead of calling `process.exit()`.
// `process.exit()` kills the process immediately and `stdout` may not be flushed before process dies.
// https://nodejs.org/api/process.html#processexitcode
process.exitCode = exitCode!;
})();
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
/* oxlint-disable no-console */

import {
hasOxfmtrcFile,
createBlankOxfmtrcFile,
saveOxfmtrcFile,
exitWithError,
} from "./shared.js";
import { hasOxfmtrcFile, createBlankOxfmtrcFile, saveOxfmtrcFile, exitWithError } from "./shared";

/**
* Run the `--init` command to scaffold a default `.oxfmtrc.json` file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@

import { join } from "node:path";
import { readFile } from "node:fs/promises";
import {
hasOxfmtrcFile,
createBlankOxfmtrcFile,
saveOxfmtrcFile,
exitWithError,
} from "./shared.js";
import { hasOxfmtrcFile, createBlankOxfmtrcFile, saveOxfmtrcFile, exitWithError } from "./shared";

/**
* Run the `--migrate prettier` command to migrate various Prettier's config to `.oxfmtrc.json` file.
Expand Down
33 changes: 33 additions & 0 deletions apps/oxfmt/src-js/cli/worker-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Tinypool from "tinypool";
import { resolvePlugins } from "../libs/prettier";
import type { Options } from "prettier";

// Worker pool for parallel Prettier formatting
let pool: Tinypool | null = null;

export async function initExternalFormatter(numThreads: number): Promise<string[]> {
pool = new Tinypool({
filename: new URL("./cli-worker.js", import.meta.url).href,
minThreads: numThreads,
maxThreads: numThreads,
});

return resolvePlugins();
}

export async function formatEmbeddedCode(
options: Options,
tagName: string,
code: string,
): Promise<string> {
return pool!.run({ options, code, tagName }, { name: "formatEmbeddedCode" });
}

export async function formatFile(
options: Options,
parserName: string,
fileName: string,
code: string,
): Promise<string> {
return pool!.run({ options, code, fileName, parserName }, { name: "formatFile" });
}
16 changes: 11 additions & 5 deletions apps/oxfmt/src-js/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { format as napiFormat } from "./bindings.js";
import { initExternalFormatter, formatEmbeddedCode, formatFile } from "./prettier-proxy.js";
import { format as napiFormat } from "./bindings";
import { resolvePlugins, formatEmbeddedCode, formatFile } from "./libs/prettier";

// napi-JS `oxfmt` API entry point
// See also `format()` function in `./src/main_napi.rs`

/**
* Format the given source text according to the specified options.
*/
export async function format(fileName: string, sourceText: string, options?: FormatOptions) {
if (typeof fileName !== "string") throw new TypeError("`fileName` must be a string");
if (typeof sourceText !== "string") throw new TypeError("`sourceText` must be a string");
Expand All @@ -9,9 +15,9 @@ export async function format(fileName: string, sourceText: string, options?: For
fileName,
sourceText,
options ?? {},
initExternalFormatter,
formatEmbeddedCode,
formatFile,
resolvePlugins,
(options, tagName, code) => formatEmbeddedCode({ options, tagName, code }),
(options, parserName, fileName, code) => formatFile({ options, parserName, fileName, code }),
);
}

Expand Down
96 changes: 96 additions & 0 deletions apps/oxfmt/src-js/libs/prettier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { Options } from "prettier";

// Lazy load Prettier
//
// NOTE: In the past, statically importing caused issues with `oxfmt --lsp` not starting.
// However, this issue has not been observed recently, possibly due to changes in the bundling configuration.
// Anyway, we keep lazy loading for now to minimize initial load time.
let prettierCache: typeof import("prettier");

/**
* TODO: Plugins support
* - Read `plugins` field
* - Load plugins dynamically and parse `languages` field
* - Map file extensions and filenames to Prettier parsers
*
* @returns Array of loaded plugin's `languages` info
*/
export async function resolvePlugins(): Promise<string[]> {
return [];
}

const TAG_TO_PARSER: Record<string, string> = {
// CSS
css: "css",
styled: "css",
// GraphQL
gql: "graphql",
graphql: "graphql",
// HTML
html: "html",
// Markdown
md: "markdown",
markdown: "markdown",
};

/**
* Format xxx-in-js code snippets
*
* @returns Formatted code snippet
* TODO: In the future, this should return `Doc` instead of string,
* otherwise, we cannot calculate `printWidth` correctly.
*/
export async function formatEmbeddedCode({
code,
tagName,
options,
}: {
code: string;
tagName: string;
options: Options;
}): Promise<string> {
// TODO: This should be resolved in Rust side
const parserName = TAG_TO_PARSER[tagName];

// Unknown tag, return original code
if (!parserName) return code;

if (!prettierCache) {
prettierCache = await import("prettier");
}

// SAFETY: `options` is created in Rust side, so it's safe to mutate here
options.parser = parserName;
return prettierCache
.format(code, options)
.then((formatted) => formatted.trimEnd())
.catch(() => code);
}

/**
* Format non-js file
*
* @returns Formatted code
*/
export async function formatFile({
code,
parserName,
fileName,
options,
}: {
code: string;
parserName: string;
fileName: string;
options: Options;
}): Promise<string> {
if (!prettierCache) {
prettierCache = await import("prettier");
}

// SAFETY: `options` is created in Rust side, so it's safe to mutate here
// We specify `parser` to skip parser inference for performance
options.parser = parserName;
// But some plugins rely on `filepath`, so we set it too
options.filepath = fileName;
return prettierCache.format(code, options);
}
2 changes: 0 additions & 2 deletions apps/oxfmt/src-js/migration/index.ts

This file was deleted.

109 changes: 0 additions & 109 deletions apps/oxfmt/src-js/prettier-proxy.ts

This file was deleted.

Loading
Loading