From 39a171e375901c2d96db0998006a74b5b774ffaa Mon Sep 17 00:00:00 2001 From: camc314 <18101008+camc314@users.noreply.github.com> Date: Mon, 29 Sep 2025 08:35:27 +0000 Subject: [PATCH] fix(linter): get cli args on JS side, to avoid runtime inconsistencies (#14223) fixes #14150 related: #14112, #14071 This seems to be the only reliable way to get arguments across different runtimes. when running with deno, the args from `std::env::args_os()` look like: ``` [ "/Users/cameron/.deno/bin/deno", "run", "--ext=js", "-A", "/Users/cameron/github/camc314/oxlint-repros/issue-14150/node_modules/.deno/oxlint@1.18.0/node_modules/oxlint/bin/oxlint", ".", ] ``` however `process.argv` looks like: ``` [ "oxlint", "/Users/cameron/github/camc314/oxlint-repros/issue-14150/node_modules/.deno/oxlint@1.18.0/node_modules/oxlint/bin/oxlint", "." ] ``` so skipping the first two means that we end up with incorrect args that we're trying to parts. There are (potentially) other solutions e.g. search for one of the args ending in`oxlint`, or other methods of trying to guess. However, this feels like the most reliable solution. --- apps/oxlint/src-js/bindings.d.ts | 9 +++++---- apps/oxlint/src-js/cli.ts | 7 +++++-- apps/oxlint/src/run.rs | 23 +++++++++++++---------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/oxlint/src-js/bindings.d.ts b/apps/oxlint/src-js/bindings.d.ts index f6f01edf9a219..bb40670519105 100644 --- a/apps/oxlint/src-js/bindings.d.ts +++ b/apps/oxlint/src-js/bindings.d.ts @@ -11,10 +11,11 @@ export type JsLoadPluginCb = /** * NAPI entry point. * - * JS side passes in two callbacks: - * 1. `load_plugin`: Load a JS plugin from a file path. - * 2. `lint_file`: Lint a file. + * JS side passes in: + * 1. `args`: Command line arguments (process.argv.slice(2)) + * 2. `load_plugin`: Load a JS plugin from a file path. + * 3. `lint_file`: Lint a file. * * Returns `true` if linting succeeded without errors, `false` otherwise. */ -export declare function lint(loadPlugin: JsLoadPluginCb, lintFile: JsLintFileCb): Promise +export declare function lint(args: Array, loadPlugin: JsLoadPluginCb, lintFile: JsLintFileCb): Promise diff --git a/apps/oxlint/src-js/cli.ts b/apps/oxlint/src-js/cli.ts index 929d2a91f7776..2244f8e010ead 100644 --- a/apps/oxlint/src-js/cli.ts +++ b/apps/oxlint/src-js/cli.ts @@ -20,8 +20,11 @@ function lintFileWrapper(filePath: string, bufferId: number, buffer: Uint8Array return lintFile(filePath, bufferId, buffer, ruleIds); } -// Call Rust, passing `loadPlugin` and `lintFile` as callbacks -const success = await lint(loadPluginWrapper, lintFileWrapper); +// Get command line arguments, skipping first 2 (node binary and script path) +const args = process.argv.slice(2); + +// Call Rust, passing `loadPlugin` and `lintFile` as callbacks, and CLI arguments +const success = await lint(args, loadPluginWrapper, lintFileWrapper); // 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. diff --git a/apps/oxlint/src/run.rs b/apps/oxlint/src/run.rs index bce2905e6679d..bef2944bbc3f6 100644 --- a/apps/oxlint/src/run.rs +++ b/apps/oxlint/src/run.rs @@ -49,27 +49,30 @@ pub type JsLintFileCb = ThreadsafeFunction< /// NAPI entry point. /// -/// JS side passes in two callbacks: -/// 1. `load_plugin`: Load a JS plugin from a file path. -/// 2. `lint_file`: Lint a file. +/// JS side passes in: +/// 1. `args`: Command line arguments (process.argv.slice(2)) +/// 2. `load_plugin`: Load a JS plugin from a file path. +/// 3. `lint_file`: Lint a file. /// /// Returns `true` if linting succeeded without errors, `false` otherwise. #[expect(clippy::allow_attributes)] #[allow(clippy::trailing_empty_array, clippy::unused_async)] // https://github.com/napi-rs/napi-rs/issues/2758 #[napi] -pub async fn lint(load_plugin: JsLoadPluginCb, lint_file: JsLintFileCb) -> bool { - lint_impl(load_plugin, lint_file).report() == ExitCode::SUCCESS +pub async fn lint(args: Vec, load_plugin: JsLoadPluginCb, lint_file: JsLintFileCb) -> bool { + lint_impl(args, load_plugin, lint_file).report() == ExitCode::SUCCESS } /// Run the linter. -fn lint_impl(load_plugin: JsLoadPluginCb, lint_file: JsLintFileCb) -> CliRunResult { +fn lint_impl( + args: Vec, + load_plugin: JsLoadPluginCb, + lint_file: JsLintFileCb, +) -> CliRunResult { init_tracing(); init_miette(); - // 1st arg is path to NodeJS binary. - // 2nd arg is path to `oxlint/.bin/oxlint` (in released packages) - // or `apps/oxlint/dist/cli.js` (in development). - let args = std::env::args_os().skip(2).collect::>(); + // Convert String args to OsString for compatibility with bpaf + let args: Vec = args.into_iter().map(std::ffi::OsString::from).collect(); let cmd = crate::cli::lint_command(); let command = match cmd.run_inner(&*args) {