diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 9ea8d710e0..63d39ad6e3 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -4,11 +4,10 @@ use super::diagnostics::DenoDiagnostic; use super::diagnostics::DiagnosticSource; use super::documents::Documents; use super::language_server; +use super::resolver::LspResolver; use super::tsc; use crate::args::jsr_url; -use crate::npm::CliNpmResolver; -use crate::resolver::CliNodeResolver; use crate::tools::lint::create_linter; use deno_runtime::fs_util::specifier_to_file_path; @@ -27,7 +26,6 @@ use deno_lint::diagnostic::LintDiagnostic; use deno_lint::rules::LintRule; use deno_runtime::deno_node::NpmResolver; use deno_runtime::deno_node::PathClean; -use deno_runtime::permissions::PermissionsContainer; use deno_semver::jsr::JsrPackageNvReference; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; @@ -217,22 +215,19 @@ fn code_as_string(code: &Option) -> String { pub struct TsResponseImportMapper<'a> { documents: &'a Documents, maybe_import_map: Option<&'a ImportMap>, - node_resolver: Option<&'a CliNodeResolver>, - npm_resolver: Option<&'a dyn CliNpmResolver>, + resolver: &'a LspResolver, } impl<'a> TsResponseImportMapper<'a> { pub fn new( documents: &'a Documents, maybe_import_map: Option<&'a ImportMap>, - node_resolver: Option<&'a CliNodeResolver>, - npm_resolver: Option<&'a dyn CliNpmResolver>, + resolver: &'a LspResolver, ) -> Self { Self { documents, maybe_import_map, - node_resolver, - npm_resolver, + resolver, } } @@ -304,9 +299,7 @@ impl<'a> TsResponseImportMapper<'a> { return Some(spec_str); } - if let Some(npm_resolver) = - self.npm_resolver.as_ref().and_then(|r| r.as_managed()) - { + if let Some(npm_resolver) = self.resolver.maybe_managed_npm_resolver() { if npm_resolver.in_npm_package(specifier) { if let Ok(Some(pkg_id)) = npm_resolver.resolve_pkg_id_from_specifier(specifier) @@ -370,9 +363,9 @@ impl<'a> TsResponseImportMapper<'a> { &self, specifier: &ModuleSpecifier, ) -> Option { - let node_resolver = self.node_resolver?; - let package_json = node_resolver - .get_closest_package_json(specifier, &PermissionsContainer::allow_all()) + let package_json = self + .resolver + .get_closest_package_json(specifier) .ok() .flatten()?; let root_folder = package_json.path.parent()?; diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 2bbd3ea7e9..0dbfd7589c 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -1150,6 +1150,7 @@ pub enum ConfigWatchedFileType { /// Contains the config file and dependent information. #[derive(Debug, Clone)] pub struct ConfigData { + pub scope: ModuleSpecifier, pub config_file: Option>, pub fmt_options: Arc, pub lint_options: Arc, @@ -1487,6 +1488,7 @@ impl ConfigData { let ts_config = LspTsConfig::new(config_file.as_ref(), import_map.as_ref()); ConfigData { + scope: scope.clone(), config_file: config_file.map(Arc::new), fmt_options, lint_options, diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index b4422d493e..bd4cb7f046 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -809,10 +809,8 @@ fn generate_lint_diagnostics( break; } // ignore any npm package files - if let Some(npm) = &snapshot.npm { - if npm.node_resolver.in_npm_package(specifier) { - continue; - } + if snapshot.resolver.in_npm_package(specifier) { + continue; } let version = document.maybe_lsp_version(); let (lint_options, lint_rules) = config @@ -1347,6 +1345,7 @@ fn diagnose_resolution( diagnostics.push(DenoDiagnostic::DenoWarn(message)); } } + let managed_npm_resolver = snapshot.resolver.maybe_managed_npm_resolver(); if let Some(doc) = snapshot.documents.get(specifier) { if let Some(diagnostic) = check_redirect_diagnostic(specifier, &doc) { diagnostics.push(diagnostic); @@ -1375,11 +1374,7 @@ fn diagnose_resolution( } else if let Ok(pkg_ref) = NpmPackageReqReference::from_specifier(specifier) { - if let Some(npm_resolver) = snapshot - .npm - .as_ref() - .and_then(|n| n.npm_resolver.as_managed()) - { + if let Some(npm_resolver) = managed_npm_resolver { // show diagnostics for npm package references that aren't cached let req = pkg_ref.into_inner().req; if !npm_resolver.is_pkg_req_folder_cached(&req) { @@ -1406,11 +1401,7 @@ fn diagnose_resolution( diagnostics .push(DenoDiagnostic::BareNodeSpecifier(module_name.to_string())); } - } else if let Some(npm_resolver) = snapshot - .npm - .as_ref() - .and_then(|n| n.npm_resolver.as_managed()) - { + } else if let Some(npm_resolver) = managed_npm_resolver { // check that a @types/node package exists in the resolver let types_node_req = PackageReq::from_str("@types/node").unwrap(); if !npm_resolver.is_pkg_req_folder_cached(&types_node_req) { @@ -1451,10 +1442,8 @@ fn diagnose_dependency( dependency_key: &str, dependency: &deno_graph::Dependency, ) { - if let Some(npm) = &snapshot.npm { - if npm.npm_resolver.in_npm_package(referrer) { - return; // ignore, surface typescript errors instead - } + if snapshot.resolver.in_npm_package(referrer) { + return; // ignore, surface typescript errors instead } let import_map = snapshot.config.tree.root_import_map(); @@ -1592,6 +1581,7 @@ mod tests { use crate::lsp::documents::Documents; use crate::lsp::documents::LanguageId; use crate::lsp::language_server::StateSnapshot; + use crate::lsp::resolver::LspResolver; use deno_config::ConfigFile; use pretty_assertions::assert_eq; use std::path::Path; @@ -1630,6 +1620,9 @@ mod tests { .unwrap(); config.tree.inject_config_file(config_file).await; } + let resolver = LspResolver::default() + .with_new_config(&config, None, None) + .await; StateSnapshot { project_version: 0, documents, @@ -1638,7 +1631,7 @@ mod tests { GlobalHttpCache::new(location.to_path_buf(), RealDenoCacheEnv), )), config: config.snapshot(), - npm: None, + resolver, } } diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 2de2a44060..71cc63f836 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -3,22 +3,17 @@ use super::cache::calculate_fs_version; use super::cache::LSP_DISALLOW_GLOBAL_TO_LOCAL_COPY; use super::config::Config; -use super::language_server::StateNpmSnapshot; +use super::resolver::LspResolver; use super::testing::TestCollector; use super::testing::TestModule; use super::text::LineIndex; use super::tsc; use super::tsc::AssetDocument; -use crate::args::package_json; use crate::cache::HttpCache; use crate::graph_util::CliJsrUrlProvider; use crate::jsr::JsrCacheResolver; use crate::lsp::logging::lsp_warn; -use crate::npm::CliNpmResolver; -use crate::resolver::CliGraphResolver; -use crate::resolver::CliGraphResolverOptions; -use crate::resolver::CliNodeResolver; use crate::resolver::SloppyImportsFsEntry; use crate::resolver::SloppyImportsResolution; use crate::resolver::SloppyImportsResolver; @@ -43,12 +38,10 @@ use deno_lockfile::Lockfile; use deno_runtime::deno_node; use deno_runtime::deno_node::NodeResolution; use deno_runtime::deno_node::NodeResolutionMode; -use deno_runtime::permissions::PermissionsContainer; use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use deno_semver::package::PackageReq; use indexmap::IndexMap; -use package_json::PackageJsonDepsProvider; use std::borrow::Cow; use std::collections::BTreeSet; use std::collections::HashMap; @@ -294,7 +287,7 @@ pub struct Document { media_type: MediaType, /// Present if and only if this is an open document. open_data: Option, - resolver: Arc, + resolver: Arc, specifier: ModuleSpecifier, text_info: SourceTextInfo, } @@ -308,8 +301,7 @@ impl Document { maybe_lsp_version: Option, maybe_language_id: Option, maybe_headers: Option>, - resolver: Arc, - maybe_node_resolver: Option<&CliNodeResolver>, + resolver: Arc, config: Arc, cache: &Arc, ) -> Arc { @@ -318,7 +310,7 @@ impl Document { &specifier, maybe_headers.as_ref(), maybe_language_id, - maybe_node_resolver, + &resolver, ); let (maybe_parsed_source, maybe_module) = if media_type_is_diagnosable(media_type) { @@ -366,17 +358,14 @@ impl Document { fn with_new_config( &self, - resolver: Arc, - maybe_node_resolver: Option<&CliNodeResolver>, + resolver: Arc, config: Arc, ) -> Arc { - let graph_resolver = resolver.as_graph_resolver(); - let npm_resolver = resolver.as_graph_npm_resolver(); let media_type = resolve_media_type( &self.specifier, self.maybe_headers.as_ref(), self.maybe_language_id, - maybe_node_resolver, + &resolver, ); let dependencies; let maybe_types_dependency; @@ -403,6 +392,8 @@ impl Document { maybe_test_module_fut = get_maybe_test_module_fut(maybe_parsed_source.as_ref(), &config); } else { + let graph_resolver = resolver.as_graph_resolver(); + let npm_resolver = resolver.as_graph_npm_resolver(); dependencies = Arc::new( self .dependencies @@ -682,20 +673,19 @@ fn resolve_media_type( specifier: &ModuleSpecifier, maybe_headers: Option<&HashMap>, maybe_language_id: Option, - maybe_node_resolver: Option<&CliNodeResolver>, + resolver: &LspResolver, ) -> MediaType { - if let Some(node_resolver) = maybe_node_resolver { - if node_resolver.in_npm_package(specifier) { - match node_resolver.url_to_node_resolution(specifier.clone()) { - Ok(resolution) => { - let (_, media_type) = - NodeResolution::into_specifier_and_media_type(Some(resolution)); - return media_type; - } - Err(err) => { - lsp_warn!("Node resolution failed for '{}': {}", specifier, err); - } + if resolver.in_npm_package(specifier) { + match resolver.url_to_node_resolution(specifier.clone()) { + Ok(Some(resolution)) => { + let (_, media_type) = + NodeResolution::into_specifier_and_media_type(Some(resolution)); + return media_type; } + Err(err) => { + lsp_warn!("Node resolution failed for '{}': {}", specifier, err); + } + _ => {} } } @@ -794,8 +784,7 @@ impl FileSystemDocuments { pub fn get( &self, specifier: &ModuleSpecifier, - resolver: &Arc, - maybe_node_resolver: Option<&CliNodeResolver>, + resolver: &Arc, config: &Arc, cache: &Arc, ) -> Option> { @@ -814,13 +803,7 @@ impl FileSystemDocuments { }; if dirty { // attempt to update the file on the file system - self.refresh_document( - specifier, - resolver, - maybe_node_resolver, - config, - cache, - ) + self.refresh_document(specifier, resolver, config, cache) } else { old_doc } @@ -831,8 +814,7 @@ impl FileSystemDocuments { fn refresh_document( &self, specifier: &ModuleSpecifier, - resolver: &Arc, - maybe_node_resolver: Option<&CliNodeResolver>, + resolver: &Arc, config: &Arc, cache: &Arc, ) -> Option> { @@ -848,7 +830,6 @@ impl FileSystemDocuments { None, None, resolver.clone(), - maybe_node_resolver, config.clone(), cache, ) @@ -864,7 +845,6 @@ impl FileSystemDocuments { None, None, resolver.clone(), - maybe_node_resolver, config.clone(), cache, ) @@ -893,7 +873,6 @@ impl FileSystemDocuments { None, maybe_headers, resolver.clone(), - maybe_node_resolver, config.clone(), cache, ) @@ -942,11 +921,9 @@ pub struct Documents { /// Any imports to the context supplied by configuration files. This is like /// the imports into the a module graph in CLI. imports: Arc>, - /// Resolver for node_modules. - maybe_node_resolver: Option>, /// A resolver that takes into account currently loaded import map and JSX /// settings. - resolver: Arc, + resolver: Arc, jsr_resolver: Arc, lockfile: Option>>, /// The npm package requirements found in npm specifiers. @@ -969,17 +946,7 @@ impl Documents { open_docs: HashMap::default(), file_system_docs: Default::default(), imports: Default::default(), - maybe_node_resolver: None, - resolver: Arc::new(CliGraphResolver::new(CliGraphResolverOptions { - node_resolver: None, - npm_resolver: None, - package_json_deps_provider: Arc::new(PackageJsonDepsProvider::default()), - maybe_jsx_import_source_config: None, - maybe_import_map: None, - maybe_vendor_dir: None, - bare_node_builtins_enabled: false, - sloppy_imports_resolver: None, - })), + resolver: Default::default(), jsr_resolver: Arc::new(JsrCacheResolver::new(cache.clone(), None)), lockfile: None, npm_specifier_reqs: Default::default(), @@ -1022,7 +989,6 @@ impl Documents { // x-typescript-types? None, self.resolver.clone(), - self.maybe_node_resolver.as_deref(), self.config.clone(), &self.cache, ); @@ -1218,7 +1184,6 @@ impl Documents { self.file_system_docs.get( &specifier, &self.resolver, - self.maybe_node_resolver.as_deref(), &self.config, &self.cache, ) @@ -1280,29 +1245,21 @@ impl Documents { &self, specifiers: &[String], referrer: &ModuleSpecifier, - maybe_npm: Option<&StateNpmSnapshot>, ) -> Vec> { let document = self.get(referrer); let dependencies = document.as_ref().map(|d| d.dependencies()); let mut results = Vec::new(); for specifier in specifiers { - if let Some(npm) = maybe_npm { - if npm.node_resolver.in_npm_package(referrer) { - // we're in an npm package, so use node resolution - results.push(Some(NodeResolution::into_specifier_and_media_type( - npm - .node_resolver - .resolve( - specifier, - referrer, - NodeResolutionMode::Types, - &PermissionsContainer::allow_all(), - ) - .ok() - .flatten(), - ))); - continue; - } + if self.resolver.in_npm_package(referrer) { + // we're in an npm package, so use node resolution + results.push(Some(NodeResolution::into_specifier_and_media_type( + self + .resolver + .node_resolve(specifier, referrer, NodeResolutionMode::Types) + .ok() + .flatten(), + ))); + continue; } if specifier.starts_with("asset:") { if let Ok(specifier) = ModuleSpecifier::parse(specifier) { @@ -1315,9 +1272,9 @@ impl Documents { dependencies.as_ref().and_then(|d| d.get(specifier)) { if let Some(specifier) = dep.maybe_type.maybe_specifier() { - results.push(self.resolve_dependency(specifier, maybe_npm, referrer)); + results.push(self.resolve_dependency(specifier, referrer)); } else if let Some(specifier) = dep.maybe_code.maybe_specifier() { - results.push(self.resolve_dependency(specifier, maybe_npm, referrer)); + results.push(self.resolve_dependency(specifier, referrer)); } else { results.push(None); } @@ -1325,14 +1282,14 @@ impl Documents { .resolve_imports_dependency(specifier) .and_then(|r| r.maybe_specifier()) { - results.push(self.resolve_dependency(specifier, maybe_npm, referrer)); + results.push(self.resolve_dependency(specifier, referrer)); } else if let Ok(npm_req_ref) = NpmPackageReqReference::from_str(specifier) { results.push(node_resolve_npm_req_ref( &npm_req_ref, - maybe_npm, referrer, + &self.resolver, )); } else if let Ok(specifier) = self.resolver.as_graph_resolver().resolve( specifier, @@ -1343,7 +1300,7 @@ impl Documents { }, ResolutionMode::Types, ) { - results.push(self.resolve_dependency(&specifier, maybe_npm, referrer)); + results.push(self.resolve_dependency(&specifier, referrer)); } else { results.push(None); } @@ -1372,36 +1329,13 @@ impl Documents { pub fn update_config( &mut self, config: &Config, - node_resolver: Option>, - npm_resolver: Option>, + resolver: &Arc, workspace_files: &BTreeSet, ) { self.config = Arc::new(config.clone()); let config_data = config.tree.root_data(); let config_file = config_data.and_then(|d| d.config_file.as_deref()); - self.maybe_node_resolver = node_resolver.clone(); - self.resolver = Arc::new(CliGraphResolver::new(CliGraphResolverOptions { - node_resolver, - npm_resolver, - package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new( - config_data - .and_then(|d| d.package_json.as_ref()) - .map(|package_json| { - package_json::get_local_package_json_version_reqs(package_json) - }), - )), - maybe_jsx_import_source_config: config_file - .and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()), - maybe_import_map: config_data.and_then(|d| d.import_map.clone()), - maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()), - bare_node_builtins_enabled: config_file - .map(|config| config.has_unstable("bare-node-builtins")) - .unwrap_or(false), - // Don't set this for the LSP because instead we'll use the OpenDocumentsLoader - // because it's much easier and we get diagnostics/quick fixes about a redirected - // specifier for free. - sloppy_imports_resolver: None, - })); + self.resolver = resolver.clone(); self.jsr_resolver = Arc::new(JsrCacheResolver::new( self.cache.clone(), config.tree.root_lockfile().cloned(), @@ -1452,21 +1386,14 @@ impl Documents { if !config.specifier_enabled(doc.specifier()) { continue; } - *doc = doc.with_new_config( - self.resolver.clone(), - self.maybe_node_resolver.as_deref(), - self.config.clone(), - ); + *doc = doc.with_new_config(self.resolver.clone(), self.config.clone()); } for mut doc in self.file_system_docs.docs.iter_mut() { if !config.specifier_enabled(doc.specifier()) { continue; } - *doc.value_mut() = doc.with_new_config( - self.resolver.clone(), - self.maybe_node_resolver.as_deref(), - self.config.clone(), - ); + *doc.value_mut() = + doc.with_new_config(self.resolver.clone(), self.config.clone()); } self.open_docs = open_docs; let mut preload_count = 0; @@ -1484,7 +1411,6 @@ impl Documents { fs_docs.refresh_document( specifier, &self.resolver, - self.maybe_node_resolver.as_deref(), &self.config, &self.cache, ); @@ -1566,7 +1492,6 @@ impl Documents { fn resolve_dependency( &self, specifier: &ModuleSpecifier, - maybe_npm: Option<&StateNpmSnapshot>, referrer: &ModuleSpecifier, ) -> Option<(ModuleSpecifier, MediaType)> { if let Some(module_name) = specifier.as_str().strip_prefix("node:") { @@ -1579,13 +1504,13 @@ impl Documents { } if let Ok(npm_ref) = NpmPackageReqReference::from_specifier(specifier) { - return node_resolve_npm_req_ref(&npm_ref, maybe_npm, referrer); + return node_resolve_npm_req_ref(&npm_ref, referrer, &self.resolver); } let Some(doc) = self.get(specifier) else { return Some((specifier.clone(), MediaType::from_specifier(specifier))); }; if let Some(specifier) = doc.maybe_types_dependency().maybe_specifier() { - self.resolve_dependency(specifier, maybe_npm, referrer) + self.resolve_dependency(specifier, referrer) } else { let media_type = doc.media_type(); Some((doc.specifier().clone(), media_type)) @@ -1608,22 +1533,19 @@ impl Documents { fn node_resolve_npm_req_ref( npm_req_ref: &NpmPackageReqReference, - maybe_npm: Option<&StateNpmSnapshot>, referrer: &ModuleSpecifier, + resolver: &LspResolver, ) -> Option<(ModuleSpecifier, MediaType)> { - maybe_npm.map(|npm| { - NodeResolution::into_specifier_and_media_type( - npm - .node_resolver - .resolve_req_reference( - npm_req_ref, - &PermissionsContainer::allow_all(), - referrer, - NodeResolutionMode::Types, - ) - .ok(), - ) - }) + Some(NodeResolution::into_specifier_and_media_type( + resolver + .resolve_npm_req_reference( + npm_req_ref, + referrer, + NodeResolutionMode::Types, + ) + .ok() + .flatten(), + )) } /// Loader that will look at the open documents. @@ -1717,7 +1639,7 @@ fn parse_and_analyze_module( text_info: SourceTextInfo, maybe_headers: Option<&HashMap>, media_type: MediaType, - resolver: &CliGraphResolver, + resolver: &LspResolver, ) -> (Option, Option) { let parsed_source_result = parse_source(specifier, text_info, media_type); let module_result = @@ -1744,7 +1666,7 @@ fn analyze_module( specifier: &ModuleSpecifier, parsed_source_result: &ParsedSourceResult, maybe_headers: Option<&HashMap>, - resolver: &CliGraphResolver, + resolver: &LspResolver, ) -> ModuleResult { match parsed_source_result { Ok(parsed_source) => Ok(deno_graph::parse_module_from_ast( @@ -1939,7 +1861,10 @@ console.log(b, "hello deno"); ) .await; - documents.update_config(&config, None, None, &workspace_files); + let resolver = LspResolver::default() + .with_new_config(&config, None, None) + .await; + documents.update_config(&config, &resolver, &workspace_files); // open the document let document = documents.open( @@ -1980,7 +1905,10 @@ console.log(b, "hello deno"); ) .await; - documents.update_config(&config, None, None, &workspace_files); + let resolver = LspResolver::default() + .with_new_config(&config, None, None) + .await; + documents.update_config(&config, &resolver, &workspace_files); // check the document's dependencies let document = documents.get(&file1_specifier).unwrap(); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index f15f40f1bf..67bae36513 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -13,9 +13,6 @@ use deno_core::url; use deno_core::ModuleSpecifier; use deno_graph::GraphKind; use deno_graph::Resolution; -use deno_npm::NpmSystemInfo; -use deno_runtime::deno_fs; -use deno_runtime::deno_node::NodeResolver; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_semver::jsr::JsrPackageReqReference; @@ -29,7 +26,6 @@ use std::collections::HashSet; use std::collections::VecDeque; use std::env; use std::fmt::Write as _; -use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::mpsc::unbounded_channel; @@ -52,7 +48,6 @@ use super::client::Client; use super::code_lens; use super::completions; use super::config::Config; -use super::config::ConfigData; use super::config::ConfigSnapshot; use super::config::UpdateImportsOnFileMoveEnabled; use super::config::WorkspaceSettings; @@ -79,6 +74,7 @@ use super::performance::Performance; use super::performance::PerformanceMark; use super::refactor; use super::registries::ModuleRegistry; +use super::resolver::LspResolver; use super::testing; use super::text; use super::tsc; @@ -94,7 +90,6 @@ use crate::args::CacheSetting; use crate::args::CliOptions; use crate::args::Flags; use crate::cache::DenoDir; -use crate::cache::FastInsecureHasher; use crate::cache::GlobalHttpCache; use crate::cache::HttpCache; use crate::cache::LocalLspHttpCache; @@ -106,14 +101,6 @@ use crate::lsp::config::ConfigWatchedFileType; use crate::lsp::logging::init_log_file; use crate::lsp::tsc::file_text_changes_to_workspace_edit; use crate::lsp::urls::LspUrlKind; -use crate::npm::create_cli_npm_resolver_for_lsp; -use crate::npm::CliNpmResolver; -use crate::npm::CliNpmResolverByonmCreateOptions; -use crate::npm::CliNpmResolverCreateOptions; -use crate::npm::CliNpmResolverManagedCreateOptions; -use crate::npm::CliNpmResolverManagedPackageJsonInstallerOption; -use crate::npm::CliNpmResolverManagedSnapshotOption; -use crate::resolver::CliNodeResolver; use crate::tools::fmt::format_file; use crate::tools::fmt::format_parsed_source; use crate::tools::upgrade::check_for_upgrades_for_lsp; @@ -121,8 +108,6 @@ use crate::tools::upgrade::upgrade_check_enabled; use crate::util::fs::remove_dir_all_if_exists; use crate::util::path::is_importable_ext; use crate::util::path::to_percent_decoded_str; -use crate::util::progress_bar::ProgressBar; -use crate::util::progress_bar::ProgressBarStyle; use deno_runtime::fs_util::specifier_to_file_path; struct LspRootCertStoreProvider(RootCertStore); @@ -133,46 +118,9 @@ impl RootCertStoreProvider for LspRootCertStoreProvider { } } -#[derive(Debug)] -struct LspNpmServices { - /// When this hash changes, the services need updating - config_hash: LspNpmConfigHash, - /// Npm's search api. - search_api: CliNpmSearchApi, - /// Node resolver. - node_resolver: Option>, - /// Resolver for npm packages. - resolver: Option>, -} - -#[derive(Debug, PartialEq, Eq)] -struct LspNpmConfigHash(u64); - -impl LspNpmConfigHash { - pub fn from_inner(inner: &Inner) -> Self { - let config_data = inner.config.tree.root_data(); - let node_modules_dir = - config_data.and_then(|d| d.node_modules_dir.as_ref()); - let lockfile = config_data.and_then(|d| d.lockfile.as_ref()); - let mut hasher = FastInsecureHasher::new(); - hasher.write_hashable(node_modules_dir); - hasher.write_hashable(&inner.maybe_global_cache_path); - if let Some(lockfile) = lockfile { - hasher.write_hashable(&*lockfile.lock()); - } - Self(hasher.finish()) - } -} - #[derive(Debug, Clone)] pub struct LanguageServer(Arc>, CancellationToken); -#[derive(Clone, Debug)] -pub struct StateNpmSnapshot { - pub node_resolver: Arc, - pub npm_resolver: Arc, -} - /// Snapshot of the state used by TSC. #[derive(Clone, Debug)] pub struct StateSnapshot { @@ -181,7 +129,7 @@ pub struct StateSnapshot { pub cache_metadata: cache::CacheMetadata, pub config: Arc, pub documents: Documents, - pub npm: Option, + pub resolver: Arc, } type LanguageServerTaskFn = Box; @@ -253,11 +201,11 @@ pub struct Inner { maybe_global_cache_path: Option, /// A lazily create "server" for handling test run requests. maybe_testing_server: Option, - /// Services used for dealing with npm related functionality. - npm: LspNpmServices, + npm_search_api: CliNpmSearchApi, project_version: usize, /// A collection of measurements which instrument that performance of the LSP. performance: Arc, + resolver: Arc, /// A memoized version of fixable diagnostic codes retrieved from TypeScript. ts_fixable_diagnostics: Vec, /// An abstraction that handles interactions with TypeScript. @@ -544,13 +492,9 @@ impl Inner { maybe_testing_server: None, module_registries, module_registries_location, - npm: LspNpmServices { - config_hash: LspNpmConfigHash(0), // this will be updated in initialize - search_api: npm_search_api, - node_resolver: None, - resolver: None, - }, + npm_search_api, performance, + resolver: Default::default(), ts_fixable_diagnostics: Default::default(), ts_server, url_map: Default::default(), @@ -645,35 +589,13 @@ impl Inner { } pub fn snapshot(&self) -> Arc { - let maybe_state_npm_snapshot = self - .npm - .resolver - .as_ref() - .map(|resolver| resolver.clone_snapshotted()) - .map(|resolver| { - let fs = Arc::new(deno_fs::RealFs); - let node_resolver = Arc::new(NodeResolver::new( - fs.clone(), - resolver.clone().into_npm_resolver(), - )); - let cli_node_resolver = Arc::new(CliNodeResolver::new( - None, - fs, - node_resolver, - resolver.clone(), - )); - StateNpmSnapshot { - node_resolver: cli_node_resolver, - npm_resolver: resolver, - } - }); Arc::new(StateSnapshot { project_version: self.project_version, assets: self.assets.snapshot(), cache_metadata: self.cache_metadata.clone(), config: self.config.snapshot(), documents: self.documents.clone(), - npm: maybe_state_npm_snapshot, + resolver: self.resolver.snapshot(), }) } @@ -747,7 +669,7 @@ impl Inner { ); self.jsr_search_api = CliJsrSearchApi::new(self.module_registries.file_fetcher.clone()); - self.npm.search_api = + self.npm_search_api = CliNpmSearchApi::new(self.module_registries.file_fetcher.clone()); // update the cache path let global_cache = Arc::new(GlobalHttpCache::new( @@ -773,42 +695,6 @@ impl Inner { Ok(()) } - async fn recreate_npm_services_if_necessary(&mut self) { - let deno_dir = match DenoDir::new(self.maybe_global_cache_path.clone()) { - Ok(deno_dir) => deno_dir, - Err(err) => { - lsp_warn!("Error getting deno dir: {:#}", err); - return; - } - }; - let config_hash = LspNpmConfigHash::from_inner(self); - if config_hash == self.npm.config_hash { - return; // no need to do anything - } - let config_data = self.config.tree.root_data(); - let npm_resolver = create_npm_resolver( - &deno_dir, - &self.initial_cwd, - &self.http_client, - config_data, - ) - .await; - let node_resolver = Arc::new(NodeResolver::new( - Arc::new(deno_fs::RealFs), - npm_resolver.clone().into_npm_resolver(), - )); - self.npm.node_resolver = Some(Arc::new(CliNodeResolver::new( - None, - Arc::new(deno_fs::RealFs), - node_resolver, - npm_resolver.clone(), - ))); - self.npm.resolver = Some(npm_resolver); - - // update the hash - self.npm.config_hash = config_hash; - } - fn create_file_fetcher(&self, cache_setting: CacheSetting) -> FileFetcher { let mut file_fetcher = FileFetcher::new( self.deps_http_cache.clone(), @@ -844,52 +730,6 @@ impl Inner { } } -async fn create_npm_resolver( - deno_dir: &DenoDir, - initial_cwd: &Path, - http_client: &Arc, - config_data: Option<&ConfigData>, -) -> Arc { - let byonm = config_data.map(|d| d.byonm).unwrap_or(false); - create_cli_npm_resolver_for_lsp(if byonm { - CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions { - fs: Arc::new(deno_fs::RealFs), - root_node_modules_dir: initial_cwd.join("node_modules"), - }) - } else { - CliNpmResolverCreateOptions::Managed(CliNpmResolverManagedCreateOptions { - http_client: http_client.clone(), - snapshot: match config_data.and_then(|d| d.lockfile.as_ref()) { - Some(lockfile) => { - CliNpmResolverManagedSnapshotOption::ResolveFromLockfile( - lockfile.clone(), - ) - } - None => CliNpmResolverManagedSnapshotOption::Specified(None), - }, - // Don't provide the lockfile. We don't want these resolvers - // updating it. Only the cache request should update the lockfile. - maybe_lockfile: None, - fs: Arc::new(deno_fs::RealFs), - npm_global_cache_dir: deno_dir.npm_folder_path(), - // Use an "only" cache setting in order to make the - // user do an explicit "cache" command and prevent - // the cache from being filled with lots of packages while - // the user is typing. - cache_setting: CacheSetting::Only, - text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly), - maybe_node_modules_path: config_data - .and_then(|d| d.node_modules_dir.clone()), - // do not install while resolving in the lsp—leave that to the cache command - package_json_installer: - CliNpmResolverManagedPackageJsonInstallerOption::NoInstall, - npm_registry_url: crate::args::npm_registry_url().to_owned(), - npm_system_info: NpmSystemInfo::default(), - }) - }) - .await -} - // lspower::LanguageServer methods. This file's LanguageServer delegates to us. impl Inner { async fn initialize( @@ -1004,7 +844,6 @@ impl Inner { self.client.show_message(MessageType::WARNING, err); } - self.recreate_npm_services_if_necessary().await; self.assets.initialize(self.snapshot()).await; self.performance.measure(mark); @@ -1178,13 +1017,20 @@ impl Inner { } } } + self.resolver = self + .resolver + .with_new_config( + &self.config, + self.maybe_global_cache_path.as_deref(), + Some(&self.http_client), + ) + .await; } async fn refresh_documents_config(&mut self) { self.documents.update_config( &self.config, - self.npm.node_resolver.clone(), - self.npm.resolver.clone(), + &self.resolver, &self.workspace_files, ); @@ -1262,17 +1108,10 @@ impl Inner { async fn refresh_npm_specifiers(&mut self) { let package_reqs = self.documents.npm_package_reqs(); - let npm_resolver = self.npm.resolver.clone(); + let resolver = self.resolver.clone(); // spawn to avoid the LSP's Send requirements - let handle = spawn(async move { - if let Some(npm_resolver) = - npm_resolver.as_ref().and_then(|r| r.as_managed()) - { - npm_resolver.set_package_reqs(&package_reqs).await - } else { - Ok(()) - } - }); + let handle = + spawn(async move { resolver.set_npm_package_reqs(&package_reqs).await }); if let Err(err) = handle.await.unwrap() { lsp_warn!("Could not set npm package requirements. {:#}", err); } @@ -1331,7 +1170,6 @@ impl Inner { lsp_warn!("Error updating registries: {:#}", err); self.client.show_message(MessageType::WARNING, err); } - self.recreate_npm_services_if_necessary().await; self.refresh_documents_config().await; self.diagnostics_server.invalidate_all(); self.send_diagnostics_update(); @@ -1408,7 +1246,6 @@ impl Inner { }, ); } - self.recreate_npm_services_if_necessary().await; self.refresh_documents_config().await; self.diagnostics_server.invalidate_all(); self.project_changed( @@ -2003,8 +1840,7 @@ impl Inner { TsResponseImportMapper::new( &self.documents, self.config.tree.root_import_map().map(|i| i.as_ref()), - self.npm.node_resolver.as_deref(), - self.npm.resolver.as_deref(), + self.resolver.as_ref(), ) } @@ -2313,7 +2149,7 @@ impl Inner { &self.client, &self.module_registries, &self.jsr_search_api, - &self.npm.search_api, + &self.npm_search_api, &self.documents, self.config.tree.root_import_map().map(|i| i.as_ref()), ) diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index f15d2a3658..07d829d919 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -32,6 +32,7 @@ mod performance; mod refactor; mod registries; mod repl; +mod resolver; mod search; mod semantic_tokens; mod testing; diff --git a/cli/lsp/resolver.rs b/cli/lsp/resolver.rs new file mode 100644 index 0000000000..076d48bb4d --- /dev/null +++ b/cli/lsp/resolver.rs @@ -0,0 +1,328 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use crate::args::package_json; +use crate::args::CacheSetting; +use crate::cache::DenoDir; +use crate::cache::FastInsecureHasher; +use crate::http_util::HttpClient; +use crate::lsp::config::Config; +use crate::lsp::config::ConfigData; +use crate::lsp::logging::lsp_warn; +use crate::npm::create_cli_npm_resolver_for_lsp; +use crate::npm::CliNpmResolver; +use crate::npm::CliNpmResolverByonmCreateOptions; +use crate::npm::CliNpmResolverCreateOptions; +use crate::npm::CliNpmResolverManagedCreateOptions; +use crate::npm::CliNpmResolverManagedPackageJsonInstallerOption; +use crate::npm::CliNpmResolverManagedSnapshotOption; +use crate::npm::ManagedCliNpmResolver; +use crate::resolver::CliGraphResolver; +use crate::resolver::CliGraphResolverOptions; +use crate::resolver::CliNodeResolver; +use crate::util::progress_bar::ProgressBar; +use crate::util::progress_bar::ProgressBarStyle; +use deno_core::error::AnyError; +use deno_graph::source::NpmResolver; +use deno_graph::source::Resolver; +use deno_graph::ModuleSpecifier; +use deno_npm::NpmSystemInfo; +use deno_runtime::deno_fs; +use deno_runtime::deno_node::NodeResolution; +use deno_runtime::deno_node::NodeResolutionMode; +use deno_runtime::deno_node::NodeResolver; +use deno_runtime::deno_node::PackageJson; +use deno_runtime::fs_util::specifier_to_file_path; +use deno_runtime::permissions::PermissionsContainer; +use deno_semver::npm::NpmPackageReqReference; +use deno_semver::package::PackageReq; +use package_json::PackageJsonDepsProvider; +use std::path::Path; +use std::rc::Rc; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub struct LspResolver { + graph_resolver: Arc, + npm_resolver: Option>, + node_resolver: Option>, + npm_config_hash: LspNpmConfigHash, + config: Arc, +} + +impl Default for LspResolver { + fn default() -> Self { + Self { + graph_resolver: create_graph_resolver(&Default::default(), None, None), + npm_resolver: None, + node_resolver: None, + npm_config_hash: LspNpmConfigHash(0), + config: Default::default(), + } + } +} + +impl LspResolver { + pub async fn with_new_config( + &self, + config: &Config, + global_cache_path: Option<&Path>, + http_client: Option<&Arc>, + ) -> Arc { + let npm_config_hash = LspNpmConfigHash::new(config, global_cache_path); + let mut npm_resolver = None; + let mut node_resolver = None; + if npm_config_hash != self.npm_config_hash { + if let (Some(http_client), Some(config_data)) = + (http_client, config.tree.root_data()) + { + npm_resolver = + create_npm_resolver(config_data, global_cache_path, http_client) + .await; + node_resolver = create_node_resolver(npm_resolver.as_ref()); + } + } else { + npm_resolver = self.npm_resolver.clone(); + node_resolver = self.node_resolver.clone(); + } + let graph_resolver = create_graph_resolver( + config, + npm_resolver.as_ref(), + node_resolver.as_ref(), + ); + Arc::new(Self { + graph_resolver, + npm_resolver, + node_resolver, + npm_config_hash, + config: Arc::new(config.clone()), + }) + } + + pub fn snapshot(&self) -> Arc { + let npm_resolver = + self.npm_resolver.as_ref().map(|r| r.clone_snapshotted()); + let node_resolver = create_node_resolver(npm_resolver.as_ref()); + let graph_resolver = create_graph_resolver( + &self.config, + npm_resolver.as_ref(), + node_resolver.as_ref(), + ); + Arc::new(Self { + graph_resolver, + npm_resolver, + node_resolver, + npm_config_hash: self.npm_config_hash.clone(), + config: self.config.clone(), + }) + } + + pub async fn set_npm_package_reqs( + &self, + reqs: &[PackageReq], + ) -> Result<(), AnyError> { + if let Some(npm_resolver) = self.npm_resolver.as_ref() { + if let Some(npm_resolver) = npm_resolver.as_managed() { + return npm_resolver.set_package_reqs(reqs).await; + } + } + Ok(()) + } + + pub fn as_graph_resolver(&self) -> &dyn Resolver { + self.graph_resolver.as_ref() + } + + pub fn as_graph_npm_resolver(&self) -> &dyn NpmResolver { + self.graph_resolver.as_ref() + } + + pub fn maybe_managed_npm_resolver(&self) -> Option<&ManagedCliNpmResolver> { + self.npm_resolver.as_ref().and_then(|r| r.as_managed()) + } + + pub fn in_npm_package(&self, specifier: &ModuleSpecifier) -> bool { + if let Some(npm_resolver) = &self.npm_resolver { + return npm_resolver.in_npm_package(specifier); + } + false + } + + pub fn node_resolve( + &self, + specifier: &str, + referrer: &ModuleSpecifier, + mode: NodeResolutionMode, + ) -> Result, AnyError> { + let Some(node_resolver) = self.node_resolver.as_ref() else { + return Ok(None); + }; + node_resolver.resolve( + specifier, + referrer, + mode, + &PermissionsContainer::allow_all(), + ) + } + + pub fn resolve_npm_req_reference( + &self, + req_ref: &NpmPackageReqReference, + referrer: &ModuleSpecifier, + mode: NodeResolutionMode, + ) -> Result, AnyError> { + let Some(node_resolver) = self.node_resolver.as_ref() else { + return Ok(None); + }; + node_resolver + .resolve_req_reference( + req_ref, + &PermissionsContainer::allow_all(), + referrer, + mode, + ) + .map(Some) + } + + pub fn url_to_node_resolution( + &self, + specifier: ModuleSpecifier, + ) -> Result, AnyError> { + let Some(node_resolver) = self.node_resolver.as_ref() else { + return Ok(None); + }; + node_resolver.url_to_node_resolution(specifier).map(Some) + } + + pub fn get_closest_package_json( + &self, + referrer: &ModuleSpecifier, + ) -> Result>, AnyError> { + let Some(node_resolver) = self.node_resolver.as_ref() else { + return Ok(None); + }; + node_resolver + .get_closest_package_json(referrer, &PermissionsContainer::allow_all()) + } +} + +async fn create_npm_resolver( + config_data: &ConfigData, + global_cache_path: Option<&Path>, + http_client: &Arc, +) -> Option> { + let deno_dir = DenoDir::new(global_cache_path.map(|p| p.to_owned())) + .inspect_err(|err| { + lsp_warn!("Error getting deno dir: {:#}", err); + }) + .ok()?; + let node_modules_dir = config_data + .node_modules_dir + .clone() + .or_else(|| specifier_to_file_path(&config_data.scope).ok())?; + let options = if config_data.byonm { + CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions { + fs: Arc::new(deno_fs::RealFs), + root_node_modules_dir: node_modules_dir, + }) + } else { + CliNpmResolverCreateOptions::Managed(CliNpmResolverManagedCreateOptions { + http_client: http_client.clone(), + snapshot: match config_data.lockfile.as_ref() { + Some(lockfile) => { + CliNpmResolverManagedSnapshotOption::ResolveFromLockfile( + lockfile.clone(), + ) + } + None => CliNpmResolverManagedSnapshotOption::Specified(None), + }, + // Don't provide the lockfile. We don't want these resolvers + // updating it. Only the cache request should update the lockfile. + maybe_lockfile: None, + fs: Arc::new(deno_fs::RealFs), + npm_global_cache_dir: deno_dir.npm_folder_path(), + // Use an "only" cache setting in order to make the + // user do an explicit "cache" command and prevent + // the cache from being filled with lots of packages while + // the user is typing. + cache_setting: CacheSetting::Only, + text_only_progress_bar: ProgressBar::new(ProgressBarStyle::TextOnly), + maybe_node_modules_path: config_data.node_modules_dir.clone(), + // do not install while resolving in the lsp—leave that to the cache command + package_json_installer: + CliNpmResolverManagedPackageJsonInstallerOption::NoInstall, + npm_registry_url: crate::args::npm_registry_url().to_owned(), + npm_system_info: NpmSystemInfo::default(), + }) + }; + Some(create_cli_npm_resolver_for_lsp(options).await) +} + +fn create_node_resolver( + npm_resolver: Option<&Arc>, +) -> Option> { + let npm_resolver = npm_resolver?; + let fs = Arc::new(deno_fs::RealFs); + let node_resolver_inner = Arc::new(NodeResolver::new( + fs.clone(), + npm_resolver.clone().into_npm_resolver(), + )); + Some(Arc::new(CliNodeResolver::new( + None, + fs, + node_resolver_inner, + npm_resolver.clone(), + ))) +} + +fn create_graph_resolver( + config: &Config, + npm_resolver: Option<&Arc>, + node_resolver: Option<&Arc>, +) -> Arc { + let config_data = config.tree.root_data(); + let config_file = config_data.and_then(|d| d.config_file.as_deref()); + Arc::new(CliGraphResolver::new(CliGraphResolverOptions { + node_resolver: node_resolver.cloned(), + npm_resolver: npm_resolver.cloned(), + package_json_deps_provider: Arc::new(PackageJsonDepsProvider::new( + config_data + .and_then(|d| d.package_json.as_ref()) + .map(|package_json| { + package_json::get_local_package_json_version_reqs(package_json) + }), + )), + maybe_jsx_import_source_config: config_file + .and_then(|cf| cf.to_maybe_jsx_import_source_config().ok().flatten()), + maybe_import_map: config_data.and_then(|d| d.import_map.clone()), + maybe_vendor_dir: config_data.and_then(|d| d.vendor_dir.as_ref()), + bare_node_builtins_enabled: config_file + .map(|config| config.has_unstable("bare-node-builtins")) + .unwrap_or(false), + // Don't set this for the LSP because instead we'll use the OpenDocumentsLoader + // because it's much easier and we get diagnostics/quick fixes about a redirected + // specifier for free. + sloppy_imports_resolver: None, + })) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct LspNpmConfigHash(u64); + +impl LspNpmConfigHash { + pub fn new(config: &Config, global_cache_path: Option<&Path>) -> Self { + let config_data = config.tree.root_data(); + let scope = config_data.map(|d| &d.scope); + let node_modules_dir = + config_data.and_then(|d| d.node_modules_dir.as_ref()); + let lockfile = config_data.and_then(|d| d.lockfile.as_ref()); + let mut hasher = FastInsecureHasher::new(); + hasher.write_hashable(scope); + hasher.write_hashable(node_modules_dir); + hasher.write_hashable(global_cache_path); + if let Some(lockfile) = lockfile { + hasher.write_hashable(&*lockfile.lock()); + } + hasher.write_hashable(global_cache_path); + Self(hasher.finish()) + } +} diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index ef096ab8bd..485401cdff 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -3999,12 +3999,7 @@ fn op_is_node_file(state: &mut OpState, #[string] path: String) -> bool { let state = state.borrow::(); let mark = state.performance.mark("tsc.op.op_is_node_file"); let r = match ModuleSpecifier::parse(&path) { - Ok(specifier) => state - .state_snapshot - .npm - .as_ref() - .map(|n| n.npm_resolver.in_npm_package(&specifier)) - .unwrap_or(false), + Ok(specifier) => state.state_snapshot.resolver.in_npm_package(&specifier), Err(_) => false, }; state.performance.measure(mark); @@ -4089,11 +4084,7 @@ fn op_resolve_inner( let specifiers = state .state_snapshot .documents - .resolve( - &args.specifiers, - &referrer, - state.state_snapshot.npm.as_ref(), - ) + .resolve(&args.specifiers, &referrer) .into_iter() .map(|o| { o.map(|(s, mt)| { @@ -4408,7 +4399,7 @@ deno_core::extension!(deno_tsc, cache_metadata: CacheMetadata::new(options.cache.clone()), config: Default::default(), documents: Documents::new(options.cache.clone()), - npm: None, + resolver: Default::default(), }), options.specifier_map, options.performance, @@ -5052,17 +5043,17 @@ impl TscRequest { #[cfg(test)] mod tests { - use super::*; use crate::cache::GlobalHttpCache; use crate::cache::HttpCache; use crate::cache::RealDenoCacheEnv; use crate::http_util::HeadersMap; use crate::lsp::cache::CacheMetadata; - use crate::lsp::config::ConfigSnapshot; + use crate::lsp::config::Config; use crate::lsp::config::WorkspaceSettings; use crate::lsp::documents::Documents; use crate::lsp::documents::LanguageId; + use crate::lsp::resolver::LspResolver; use crate::lsp::text::LineIndex; use pretty_assertions::assert_eq; use std::path::Path; @@ -5088,7 +5079,7 @@ mod tests { (*source).into(), ); } - let mut config = ConfigSnapshot::default(); + let mut config = Config::default(); config .tree .inject_config_file( @@ -5103,13 +5094,16 @@ mod tests { .unwrap(), ) .await; + let resolver = LspResolver::default() + .with_new_config(&config, None, None) + .await; StateSnapshot { project_version: 0, documents, assets: Default::default(), cache_metadata: CacheMetadata::new(cache), - config: Arc::new(config), - npm: None, + config: config.snapshot(), + resolver, } }