Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 24, 2025

Adds clearLoadedPlugin for clearing loaded plugin state from JS side. When the language server supports jsPlugins, the internal state needs to be cleared and recreated.

The Drop is an extra call, not sure if this is needed. Sounds good for me, but maybe performance?
The goal is that there is one ExternalLinter to manage multiple workspaces. The plugins load/unload for each workspace will be done in some other PR.

Not tested, but all tests are passing 🥳

Copilot Summary

Adds a clearLoadedPlugin function to clear loaded plugin state (registeredRules and registeredPluginPaths). The function is automatically called when the ExternalLinter is destroyed, ensuring plugin state is properly cleaned up after each lint run.

Changes

JavaScript Side

  • src-js/plugins/load.ts: Added clearLoadedPlugin() function to clear internal plugin state
  • src-js/plugins/index.ts: Exported clearLoadedPlugin
  • src-js/index.ts: Exported async wrapper for public API
  • src-js/cli.ts: Added clearLoadedPluginWrapper to pass callback to Rust
  • test/clear-loaded-plugin.test.ts: Added tests verifying function can be called multiple times safely

Rust Side

  • apps/oxlint/src/run.rs: Added JsClearLoadedPluginCb callback type and updated lint function signature
  • apps/oxlint/src/js_plugins/external_linter.rs: Added wrap_clear_loaded_plugin and updated create_external_linter to accept the callback
  • crates/oxc_linter/src/external_linter.rs: Added ExternalLinterClearLoadedPluginCb type, updated ExternalLinter struct, and implemented Drop trait to automatically call clearLoadedPlugin
  • crates/oxc_linter/src/lib.rs: Exported ExternalLinterClearLoadedPluginCb

Usage

The function can be called manually:

import { clearLoadedPlugin } from '#oxlint';

// Clear all loaded plugin state
await clearLoadedPlugin();

Additionally, plugin state is automatically cleared when linting completes, as the ExternalLinter destructor calls clearLoadedPlugin via the Drop trait implementation.

What Gets Cleared

  • registeredPluginPaths Set (tracks loaded plugin paths)
  • registeredRules array (stores loaded rule instances)

Execution Flow

  1. When linting starts, the clearLoadedPlugin callback is passed from JavaScript to Rust
  2. The callback is wrapped and stored in the ExternalLinter struct
  3. When linting completes and ExternalLinter is dropped, the Drop implementation automatically calls clearLoadedPlugin
  4. This ensures plugin state is cleaned up after each lint run without manual intervention
Original prompt

apps/oxlint has a NAPI intergration between rust and js. The current goal is to extend the capability to clear registedRules and registeredPluginPaths (in src-js/plugins/load.ts) with a new clearLoadedPlugin napi function.
Run all napi and oxlint tests to confirm the integration did not break anything.
This function will be later used in the language server (not your current task).


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@graphite-app
Copy link
Contributor

graphite-app bot commented Nov 24, 2025

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

- Add clearLoadedPlugin() JS function in src-js/plugins/load.ts to clear registeredRules and registeredPluginPaths
- Export clearLoadedPlugin from src-js/plugins/index.ts and src-js/index.ts
- Add tests for clearLoadedPlugin functionality
- Function will be used by language server to reset plugin state

Co-authored-by: Sysix <[email protected]>
Copilot AI changed the title [WIP] Add clearLoadedPlugin NAPI function for registered rules and paths Add clearLoadedPlugin function to reset plugin state Nov 24, 2025
Copilot AI requested a review from Sysix November 24, 2025 13:39
Copilot finished work on behalf of Sysix November 24, 2025 13:39
@Sysix
Copy link
Member

Sysix commented Nov 24, 2025

@copilot use the rust function in external_linter.rs create_external_linter

- Add JsClearLoadedPluginCb type to handle plugin state clearing
- Add clearLoadedPlugin callback parameter to create_external_linter
- Implement Drop trait for ExternalLinter to call clearLoadedPlugin
- Update lint and lint_impl to accept and pass clearLoadedPlugin callback
- Update cli.ts to pass clearLoadedPluginWrapper callback
- Export ExternalLinterClearLoadedPluginCb from oxc_linter

The clearLoadedPlugin function is now automatically called when the ExternalLinter is destroyed, ensuring plugin state is properly cleaned up after linting completes.

Co-authored-by: Sysix <[email protected]>
Copy link
Contributor Author

Copilot AI commented Nov 24, 2025

@copilot use the rust function in external_linter.rs create_external_linter

Done! I've implemented clearLoadedPlugin to be called automatically in the ExternalLinter destructor (via the Drop trait).

Changes in commit 3df2272:

  • Added JsClearLoadedPluginCb callback type
  • Added clearLoadedPlugin parameter to create_external_linter function
  • Implemented Drop trait for ExternalLinter to automatically call clearLoadedPlugin when the linter is destroyed
  • Updated lint_impl to pass the callback to create_external_linter

The plugin state is now automatically cleared when linting completes and the ExternalLinter is dropped.

Copilot AI changed the title Add clearLoadedPlugin function to reset plugin state Add clearLoadedPlugin function with automatic cleanup in ExternalLinter Nov 24, 2025
Copilot finished work on behalf of Sysix November 24, 2025 14:00
@overlookmotel
Copy link
Member

Just to say, please ask for my review on this one. I'd like to stay on top of the Rust-JS interop layer for JS plugins.

@Sysix Sysix changed the title Add clearLoadedPlugin function with automatic cleanup in ExternalLinter chore(oxlint/napi): add clearLoadedPlugin function with automatic cleanup in ExternalLinter Nov 24, 2025
@github-actions github-actions bot added A-linter Area - Linter A-cli Area - CLI A-linter-plugins Area - Linter JS plugins C-cleanup Category - technical debt or refactoring. Solution not expected to change behavior labels Nov 24, 2025
@codspeed-hq
Copy link

codspeed-hq bot commented Nov 24, 2025

CodSpeed Performance Report

Merging #16057 will not alter performance

Comparing copilot/add-clear-loaded-plugin-function (bf3b963) with main (8810bd5)

Summary

✅ 4 untouched
⏩ 41 skipped1

Footnotes

  1. 41 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Sysix Sysix marked this pull request as ready for review November 24, 2025 14:48
Copilot AI review requested due to automatic review settings November 24, 2025 14:48
Copilot finished reviewing on behalf of Sysix November 24, 2025 14:50
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds functionality to clear loaded plugin state from both the JavaScript and Rust sides of the oxlint NAPI integration. It introduces a clearLoadedPlugin function that clears internal plugin state (registeredRules and registeredPluginPaths) and integrates it into the ExternalLinter lifecycle via a Drop trait implementation that automatically calls the cleanup function when the linter is destroyed.

Key changes:

  • Added clearLoadedPlugin() function on the JavaScript side to clear plugin state
  • Added ExternalLinterClearLoadedPluginCb callback type on the Rust side
  • Implemented automatic cleanup via Drop trait on ExternalLinter

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/oxc_linter/src/lib.rs Exported ExternalLinterClearLoadedPluginCb type
crates/oxc_linter/src/external_linter.rs Added callback type, updated struct, and implemented Drop trait for automatic cleanup
apps/oxlint/src/run.rs Added JsClearLoadedPluginCb type and updated lint function signature
apps/oxlint/src/js_plugins/external_linter.rs Added wrap_clear_loaded_plugin function and updated create_external_linter
apps/oxlint/src-js/plugins/load.ts Implemented clearLoadedPlugin() function
apps/oxlint/src-js/plugins/index.ts Exported clearLoadedPlugin
apps/oxlint/src-js/index.ts Exported async wrapper for public API
apps/oxlint/src-js/cli.ts Added clearLoadedPluginWrapper and passed it to Rust
apps/oxlint/src-js/bindings.d.ts Added type definitions for the new callback
apps/oxlint/test/clear-loaded-plugin.test.ts Added basic tests for the function
Comments suppressed due to low confidence (1)

apps/oxlint/src-js/index.ts:1

  • The documentation states the function 'will not have any effect if no plugins have been loaded yet', but this is misleading. The function is exported and can be called at any time. It will execute and clear the internal state arrays (which will be empty if no plugins were loaded), but it won't fail or be unavailable. Consider revising to: 'This function can be called safely even if no plugins have been loaded yet, in which case it will be a no-op.'
import { debugAssertIsNonNull } from './utils/asserts.js';

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +70 to +73
Box::new(move || {
// Call the JavaScript callback to clear loaded plugin state
let _ = cb.call((), ThreadsafeFunctionCallMode::Blocking);
})
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result of cb.call() is silently discarded. If the JavaScript callback fails or throws an error, it will be ignored. Consider logging the error or adding a comment explaining why errors can be safely ignored during cleanup. This is especially important since this is called from the Drop implementation where panicking would be problematic.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to 69
impl Drop for ExternalLinter {
fn drop(&mut self) {
// Clear loaded plugin state when the linter is destroyed
(self.clear_loaded_plugin)();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Drop implementation makes a blocking call to JavaScript, which is unsafe and can cause deadlocks or panics. If ExternalLinter is dropped during program shutdown or on a thread without an active JS runtime, the ThreadsafeFunctionCallMode::Blocking call will hang indefinitely or panic. Drop implementations must be infallible and non-blocking.

// Instead, use non-blocking mode and ignore errors
impl Drop for ExternalLinter {
    fn drop(&mut self) {
        // Use NonBlocking mode to avoid deadlocks during shutdown
        let _ = (self.clear_loaded_plugin)();
    }
}

Or remove the Drop implementation entirely and require explicit cleanup by the caller.

Suggested change
impl Drop for ExternalLinter {
fn drop(&mut self) {
// Clear loaded plugin state when the linter is destroyed
(self.clear_loaded_plugin)();
}
impl Drop for ExternalLinter {
fn drop(&mut self) {
// Use NonBlocking mode to avoid deadlocks during shutdown
let _ = (self.clear_loaded_plugin)();
}
}

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Member

Sysix commented Nov 25, 2025


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cli Area - CLI A-linter Area - Linter A-linter-plugins Area - Linter JS plugins C-cleanup Category - technical debt or refactoring. Solution not expected to change behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants