mirror of
https://github.com/denoland/deno.git
synced 2025-09-22 10:22:34 +00:00

Some checks are pending
ci / pre-build (push) Waiting to run
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / build libs (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
Lint rule that's enabled by default in a workspace (when there's a deno.json or package.json) in order to encourage using bare specifiers and storing dependencies in a single place in the config file.
220 lines
6.7 KiB
Rust
220 lines
6.7 KiB
Rust
// Copyright 2018-2025 the Deno authors. MIT license.
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use dashmap::DashMap;
|
|
use deno_config::glob::FilePatterns;
|
|
use deno_config::workspace::WorkspaceDirLintConfig;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::url::Url;
|
|
use deno_lint::linter::LintConfig;
|
|
use deno_resolver::deno_json::CompilerOptionsKey;
|
|
use deno_runtime::tokio_util::create_basic_runtime;
|
|
use once_cell::sync::Lazy;
|
|
|
|
use crate::args::LintFlags;
|
|
use crate::args::LintOptions;
|
|
use crate::lsp::compiler_options::LspCompilerOptionsResolver;
|
|
use crate::lsp::config::Config;
|
|
use crate::lsp::documents::DocumentModule;
|
|
use crate::lsp::logging::lsp_log;
|
|
use crate::lsp::logging::lsp_warn;
|
|
use crate::lsp::resolver::LspResolver;
|
|
use crate::tools::lint::CliLinter;
|
|
use crate::tools::lint::CliLinterOptions;
|
|
use crate::tools::lint::LintRuleProvider;
|
|
use crate::tools::lint::PluginHostProxy;
|
|
|
|
#[derive(Debug)]
|
|
pub struct LspLinter {
|
|
pub inner: CliLinter,
|
|
pub lint_config: WorkspaceDirLintConfig,
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct LspLinterResolver {
|
|
config: Config,
|
|
compiler_options_resolver: Arc<LspCompilerOptionsResolver>,
|
|
resolver: Arc<LspResolver>,
|
|
linters: DashMap<(CompilerOptionsKey, Option<Arc<Url>>), Arc<LspLinter>>,
|
|
}
|
|
|
|
impl LspLinterResolver {
|
|
pub fn new(
|
|
config: &Config,
|
|
compiler_options_resolver: &Arc<LspCompilerOptionsResolver>,
|
|
resolver: &Arc<LspResolver>,
|
|
) -> Self {
|
|
Self {
|
|
config: config.clone(),
|
|
compiler_options_resolver: compiler_options_resolver.clone(),
|
|
resolver: resolver.clone(),
|
|
linters: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn for_module(&self, module: &DocumentModule) -> Arc<LspLinter> {
|
|
self
|
|
.linters
|
|
.entry((module.compiler_options_key.clone(), module.scope.clone()))
|
|
.or_insert_with(|| {
|
|
let config_data = module
|
|
.scope
|
|
.as_ref()
|
|
.and_then(|s| self.config.tree.data_for_specifier(s));
|
|
let workspace_resolver = self
|
|
.resolver
|
|
.get_scoped_resolver(config_data.map(|d| d.scope.as_ref()))
|
|
.as_workspace_resolver()
|
|
.clone();
|
|
let lint_rule_provider =
|
|
LintRuleProvider::new(Some(workspace_resolver));
|
|
let lint_config = config_data
|
|
.and_then(|d| {
|
|
d.member_dir
|
|
.to_lint_config(FilePatterns::new_with_base(
|
|
d.member_dir.dir_path(),
|
|
))
|
|
.inspect_err(|err| {
|
|
lsp_warn!("Couldn't read lint configuration: {}", err)
|
|
})
|
|
.ok()
|
|
})
|
|
.unwrap_or_else(|| WorkspaceDirLintConfig {
|
|
rules: Default::default(),
|
|
plugins: Default::default(),
|
|
files: FilePatterns::new_with_base(PathBuf::from("/")),
|
|
});
|
|
let lint_options =
|
|
LintOptions::resolve(lint_config.clone(), &LintFlags::default())
|
|
.inspect_err(|err| {
|
|
lsp_warn!("Failed to resolve linter options: {}", err)
|
|
})
|
|
.ok()
|
|
.unwrap_or_default();
|
|
let compiler_options_data = self
|
|
.compiler_options_resolver
|
|
.for_key(&module.compiler_options_key)
|
|
.expect("Key should be in sync with resolver.");
|
|
let deno_lint_config = if compiler_options_data
|
|
.compiler_options
|
|
.0
|
|
.get("jsx")
|
|
.and_then(|v| v.as_str())
|
|
== Some("react")
|
|
{
|
|
let default_jsx_factory = compiler_options_data
|
|
.compiler_options
|
|
.0
|
|
.get("jsxFactory")
|
|
.and_then(|v| v.as_str());
|
|
let default_jsx_fragment_factory = compiler_options_data
|
|
.compiler_options
|
|
.0
|
|
.get("jsxFragmentFactory")
|
|
.and_then(|v| v.as_str());
|
|
LintConfig {
|
|
default_jsx_factory: default_jsx_factory.map(String::from),
|
|
default_jsx_fragment_factory: default_jsx_fragment_factory
|
|
.map(String::from),
|
|
}
|
|
} else {
|
|
LintConfig {
|
|
default_jsx_factory: None,
|
|
default_jsx_fragment_factory: None,
|
|
}
|
|
};
|
|
let mut plugin_runner = None;
|
|
if !lint_options.plugins.is_empty() {
|
|
let load_plugins_result = LOAD_PLUGINS_THREAD.load_plugins(
|
|
lint_options.plugins.clone(),
|
|
lint_options.rules.exclude.clone(),
|
|
);
|
|
match load_plugins_result {
|
|
Ok(runner) => {
|
|
plugin_runner = Some(Arc::new(runner));
|
|
}
|
|
Err(err) => {
|
|
lsp_warn!("Failed to load lint plugins: {}", err);
|
|
}
|
|
}
|
|
}
|
|
let inner = CliLinter::new(CliLinterOptions {
|
|
configured_rules: lint_rule_provider.resolve_lint_rules(
|
|
lint_options.rules,
|
|
config_data.map(|d| d.member_dir.as_ref()),
|
|
),
|
|
fix: false,
|
|
deno_lint_config,
|
|
maybe_plugin_runner: plugin_runner,
|
|
});
|
|
Arc::new(LspLinter { inner, lint_config })
|
|
})
|
|
.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct LoadPluginsRequest {
|
|
plugins: Vec<Url>,
|
|
exclude: Option<Vec<String>>,
|
|
response_tx: std::sync::mpsc::Sender<Result<PluginHostProxy, AnyError>>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct LoadPluginsThread {
|
|
join_handle: Option<std::thread::JoinHandle<()>>,
|
|
request_tx: Option<tokio::sync::mpsc::UnboundedSender<LoadPluginsRequest>>,
|
|
}
|
|
|
|
impl LoadPluginsThread {
|
|
fn create() -> Self {
|
|
let (request_tx, mut request_rx) =
|
|
tokio::sync::mpsc::unbounded_channel::<LoadPluginsRequest>();
|
|
let join_handle = std::thread::spawn(move || {
|
|
create_basic_runtime().block_on(async move {
|
|
while let Some(request) = request_rx.recv().await {
|
|
let result = crate::tools::lint::create_runner_and_load_plugins(
|
|
request.plugins,
|
|
crate::tools::lint::PluginLogger::new(|msg, _is_err| {
|
|
lsp_log!("pluggin runner - {}", msg);
|
|
}),
|
|
request.exclude,
|
|
)
|
|
.await;
|
|
request.response_tx.send(result).unwrap();
|
|
}
|
|
});
|
|
});
|
|
Self {
|
|
join_handle: Some(join_handle),
|
|
request_tx: Some(request_tx),
|
|
}
|
|
}
|
|
|
|
fn load_plugins(
|
|
&self,
|
|
plugins: Vec<Url>,
|
|
exclude: Option<Vec<String>>,
|
|
) -> Result<PluginHostProxy, AnyError> {
|
|
let request_tx = self.request_tx.as_ref().unwrap();
|
|
let (response_tx, response_rx) = std::sync::mpsc::channel();
|
|
let _ = request_tx.send(LoadPluginsRequest {
|
|
plugins,
|
|
exclude,
|
|
response_tx,
|
|
});
|
|
response_rx.recv().unwrap()
|
|
}
|
|
}
|
|
|
|
impl Drop for LoadPluginsThread {
|
|
fn drop(&mut self) {
|
|
drop(self.request_tx.take());
|
|
self.join_handle.take().unwrap().join().unwrap();
|
|
}
|
|
}
|
|
|
|
static LOAD_PLUGINS_THREAD: Lazy<LoadPluginsThread> =
|
|
Lazy::new(LoadPluginsThread::create);
|