Skip to content

Commit 8aa94a6

Browse files
committed
draft(oxlnt/lsp): support jsPlugins
1 parent 01d89d7 commit 8aa94a6

File tree

16 files changed

+170
-61
lines changed

16 files changed

+170
-61
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"oxc.path.server": "./apps/oxlint/dist/cli.js",
23
"oxc.typeAware": true,
34
"oxc.configPath": "oxlintrc.json",
45
"oxc.fmt.experimental": true,

apps/oxlint/src-js/plugins/load.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { pathToFileURL } from 'node:url';
22

3-
import { createContext } from './context.js';
43
import { getErrorMessage } from '../utils/utils.js';
4+
import { createContext } from './context.js';
55

66
import type { Writable } from 'type-fest';
7+
import type { SetNullable } from '../utils/types.ts';
78
import type { Context } from './context.ts';
89
import type { JsonValue } from './json.ts';
910
import type { RuleMeta } from './rule_meta.ts';
1011
import type { AfterHook, BeforeHook, Visitor, VisitorWithHooks } from './types.ts';
11-
import type { SetNullable } from '../utils/types.ts';
1212

1313
const ObjectKeys = Object.keys;
1414

@@ -128,15 +128,18 @@ export async function loadPlugin(path: string, packageName: string | null): Prom
128128
* @param path - Absolute path of plugin file
129129
* @param packageName - Optional package name from `package.json` (fallback if `plugin.meta.name` is not defined)
130130
* @returns - Plugin details
131-
* @throws {Error} If plugin has already been registered
132131
* @throws {Error} If plugin has no name
133132
* @throws {TypeError} If one of plugin's rules is malformed, or its `createOnce` method returns invalid visitor
134133
* @throws {TypeError} if `plugin.meta.name` is not a string
135134
* @throws {*} If plugin throws an error during import
136135
*/
137136
async function loadPluginImpl(path: string, packageName: string | null): Promise<PluginDetails> {
138137
if (DEBUG) {
139-
if (registeredPluginPaths.has(path)) throw new Error('This plugin has already been registered');
138+
/// if (registeredPluginPaths.has(path)) throw new Error('This plugin has already been registered');
139+
140+
// If plugin was already registered, unregister it first.
141+
// This allows re-loading the plugin when the linter is restarted in `--lsp` mode.
142+
registeredPluginPaths.delete(path);
140143
registeredPluginPaths.add(path);
141144
}
142145

apps/oxlint/src/js_plugins/external_linter.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::{atomic::Ordering, mpsc::channel};
1+
use std::sync::{Arc, atomic::Ordering, mpsc::channel};
22

33
use napi::{
44
Status,
@@ -36,7 +36,7 @@ pub fn create_external_linter(
3636
///
3737
/// The returned function will panic if called outside of a Tokio runtime.
3838
fn wrap_load_plugin(cb: JsLoadPluginCb) -> ExternalLinterLoadPluginCb {
39-
Box::new(move |plugin_path, package_name| {
39+
Arc::new(Box::new(move |plugin_path, package_name| {
4040
let cb = &cb;
4141
tokio::task::block_in_place(|| {
4242
tokio::runtime::Handle::current().block_on(async move {
@@ -49,7 +49,7 @@ fn wrap_load_plugin(cb: JsLoadPluginCb) -> ExternalLinterLoadPluginCb {
4949
Ok(plugin_load_result)
5050
})
5151
})
52-
})
52+
}))
5353
}
5454

5555
/// Result returned by `lintFile` JS callback.
@@ -69,7 +69,7 @@ pub enum LintFileReturnValue {
6969
/// Use an `mpsc::channel` to wait for the result from JS side, and block current thread until `lintFile`
7070
/// completes execution.
7171
fn wrap_lint_file(cb: JsLintFileCb) -> ExternalLinterLintFileCb {
72-
Box::new(
72+
Arc::new(Box::new(
7373
move |file_path: String,
7474
rule_ids: Vec<u32>,
7575
settings_json: String,
@@ -115,7 +115,7 @@ fn wrap_lint_file(cb: JsLintFileCb) -> ExternalLinterLintFileCb {
115115
Err(err) => panic!("Callback did not respond: {err}"),
116116
}
117117
},
118-
)
118+
))
119119
}
120120

121121
/// Get buffer ID of the `Allocator` and, if it hasn't already been sent to JS,

apps/oxlint/src/lsp.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
/// Run the language server
2-
pub async fn run_lsp() {
3-
oxc_language_server::run_server(vec![Box::new(oxc_language_server::ServerLinterBuilder)]).await;
2+
pub async fn run_lsp(external_linter: Option<oxc_linter::ExternalLinter>) {
3+
oxc_language_server::run_server(vec![Box::new(oxc_language_server::ServerLinterBuilder::new(
4+
external_linter,
5+
))])
6+
.await;
47
}

apps/oxlint/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ async fn main() -> CliRunResult {
99

1010
// If --lsp flag is set, run the language server
1111
if command.lsp {
12-
run_lsp().await;
12+
run_lsp(None).await;
1313
return CliRunResult::LintSucceeded;
1414
}
1515

apps/oxlint/src/run.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,18 @@ async fn lint_impl(
9191
}
9292
};
9393

94+
// JS plugins are only supported on 64-bit little-endian platforms at present
95+
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
96+
let external_linter = Some(super::js_plugins::create_external_linter(load_plugin, lint_file));
97+
#[cfg(not(all(target_pointer_width = "64", target_endian = "little")))]
98+
let external_linter = {
99+
let (_, _) = (load_plugin, lint_file);
100+
None
101+
};
102+
94103
// If --lsp flag is set, run the language server
95104
if command.lsp {
96-
crate::lsp::run_lsp().await;
105+
crate::lsp::run_lsp(external_linter).await;
97106
return CliRunResult::LintSucceeded;
98107
}
99108

@@ -102,15 +111,6 @@ async fn lint_impl(
102111

103112
command.handle_threads();
104113

105-
// JS plugins are only supported on 64-bit little-endian platforms at present
106-
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
107-
let external_linter = Some(super::js_plugins::create_external_linter(load_plugin, lint_file));
108-
#[cfg(not(all(target_pointer_width = "64", target_endian = "little")))]
109-
let external_linter = {
110-
let (_, _) = (load_plugin, lint_file);
111-
None
112-
};
113-
114114
// stdio is blocked by LineWriter, use a BufWriter to reduce syscalls.
115115
// See `https://github.com/rust-lang/rust/issues/60673`.
116116
let mut stdout = BufWriter::new(std::io::stdout());

crates/oxc_language_server/src/linter/isolated_lint_handler.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ use tower_lsp_server::{UriExt, lsp_types::Uri};
1010

1111
use oxc_allocator::Allocator;
1212
use oxc_linter::{
13-
AllowWarnDeny, ConfigStore, DisableDirectives, Fix, FixKind, LINTABLE_EXTENSIONS, LintOptions,
14-
LintRunner, LintRunnerBuilder, LintServiceOptions, Linter, Message, PossibleFixes,
15-
RuleCommentType, RuntimeFileSystem, read_to_arena_str, read_to_string,
13+
AllowWarnDeny, ConfigStore, DisableDirectives, ExternalLinter, Fix, FixKind,
14+
LINTABLE_EXTENSIONS, LintOptions, LintRunner, LintRunnerBuilder, LintServiceOptions, Linter,
15+
Message, PossibleFixes, RuleCommentType, RuntimeFileSystem, read_to_arena_str, read_to_string,
1616
};
1717

1818
use super::error_with_position::{
@@ -67,11 +67,12 @@ impl IsolatedLintHandler {
6767
pub fn new(
6868
lint_options: LintOptions,
6969
config_store: ConfigStore,
70+
external_linter: Option<ExternalLinter>,
7071
options: &IsolatedLintHandlerOptions,
7172
) -> Self {
7273
let config_store_clone = config_store.clone();
7374

74-
let linter = Linter::new(lint_options, config_store, None);
75+
let linter = Linter::new(lint_options, config_store, external_linter);
7576
let mut lint_service_options = LintServiceOptions::new(options.root_path.clone())
7677
.with_cross_module(options.use_cross_module);
7778

@@ -121,6 +122,7 @@ impl IsolatedLintHandler {
121122
debug!("lint {}", path.display());
122123
let rope = &Rope::from_str(source_text);
123124

125+
// ToDO: with external linter, we need a new FS (raw) system
124126
let fs = IsolatedLintHandlerFileSystem::new(path.to_path_buf(), Arc::from(source_text));
125127

126128
let mut messages: Vec<DiagnosticReport> = self

0 commit comments

Comments
 (0)