diff options
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/documents.rs | 60 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 63 |
2 files changed, 110 insertions, 13 deletions
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index fa8b62306..98c3b715a 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -5,6 +5,7 @@ use super::text::LineIndex; use super::tsc; use super::tsc::AssetDocument; +use crate::args::package_json; use crate::args::ConfigFile; use crate::args::JsxImportSourceConfig; use crate::cache::CachedUrlMetadata; @@ -13,6 +14,7 @@ use crate::cache::HttpCache; use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::map_content_type; use crate::file_fetcher::SUPPORTED_SCHEMES; +use crate::lsp::logging::lsp_log; use crate::node; use crate::node::node_resolve_npm_reference; use crate::node::NodeResolution; @@ -37,9 +39,11 @@ use deno_graph::npm::NpmPackageReqReference; use deno_graph::GraphImport; use deno_graph::Resolution; use deno_runtime::deno_node::NodeResolutionMode; +use deno_runtime::deno_node::PackageJson; use deno_runtime::permissions::PermissionsContainer; use indexmap::IndexMap; use once_cell::sync::Lazy; +use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; @@ -818,8 +822,10 @@ pub struct Documents { /// A resolver that takes into account currently loaded import map and JSX /// settings. resolver: CliGraphResolver, - /// The npm package requirements. - npm_reqs: Arc<HashSet<NpmPackageReq>>, + /// The npm package requirements found in a package.json file. + npm_package_json_reqs: Arc<Vec<NpmPackageReq>>, + /// The npm package requirements found in npm specifiers. + npm_specifier_reqs: Arc<Vec<NpmPackageReq>>, /// Gets if any document had a node: specifier such that a @types/node package /// should be injected. has_injected_types_node_package: bool, @@ -838,7 +844,8 @@ impl Documents { resolver_config_hash: 0, imports: Default::default(), resolver: CliGraphResolver::default(), - npm_reqs: Default::default(), + npm_package_json_reqs: Default::default(), + npm_specifier_reqs: Default::default(), has_injected_types_node_package: false, specifier_resolver: Arc::new(SpecifierResolver::new(location)), } @@ -970,9 +977,15 @@ impl Documents { } /// Returns a collection of npm package requirements. - pub fn npm_package_reqs(&mut self) -> HashSet<NpmPackageReq> { + pub fn npm_package_reqs(&mut self) -> Vec<NpmPackageReq> { self.calculate_dependents_if_dirty(); - (*self.npm_reqs).clone() + let mut reqs = Vec::with_capacity( + self.npm_package_json_reqs.len() + self.npm_specifier_reqs.len(), + ); + // resolve the package.json reqs first, then the npm specifiers + reqs.extend(self.npm_package_json_reqs.iter().cloned()); + reqs.extend(self.npm_specifier_reqs.iter().cloned()); + reqs } /// Returns if a @types/node package was injected into the npm @@ -1150,12 +1163,14 @@ impl Documents { &mut self, maybe_import_map: Option<Arc<import_map::ImportMap>>, maybe_config_file: Option<&ConfigFile>, + maybe_package_json: Option<&PackageJson>, npm_registry_api: NpmRegistryApi, npm_resolution: NpmResolution, ) { fn calculate_resolver_config_hash( maybe_import_map: Option<&import_map::ImportMap>, maybe_jsx_config: Option<&JsxImportSourceConfig>, + maybe_package_json_deps: Option<&HashMap<String, NpmPackageReq>>, ) -> u64 { let mut hasher = FastInsecureHasher::default(); if let Some(import_map) = maybe_import_map { @@ -1165,23 +1180,46 @@ impl Documents { if let Some(jsx_config) = maybe_jsx_config { hasher.write_hashable(&jsx_config); } + if let Some(deps) = maybe_package_json_deps { + let deps = deps.iter().collect::<BTreeMap<_, _>>(); + hasher.write_hashable(&deps); + } hasher.finish() } + let maybe_package_json_deps = maybe_package_json.and_then(|package_json| { + match package_json::get_local_package_json_version_reqs(package_json) { + Ok(deps) => Some(deps), + Err(err) => { + lsp_log!("Error parsing package.json deps: {err:#}"); + None + } + } + }); let maybe_jsx_config = maybe_config_file.and_then(|cf| cf.to_maybe_jsx_import_source_config()); let new_resolver_config_hash = calculate_resolver_config_hash( maybe_import_map.as_deref(), maybe_jsx_config.as_ref(), + maybe_package_json_deps.as_ref(), ); - // TODO(bartlomieju): handle package.json dependencies here + self.npm_package_json_reqs = Arc::new({ + match &maybe_package_json_deps { + Some(deps) => { + let mut reqs = deps.values().cloned().collect::<Vec<_>>(); + reqs.sort(); + reqs + } + None => Vec::new(), + } + }); self.resolver = CliGraphResolver::new( maybe_jsx_config, maybe_import_map, false, npm_registry_api, npm_resolution, - None, + maybe_package_json_deps, ); self.imports = Arc::new( if let Some(Ok(imports)) = @@ -1306,7 +1344,11 @@ impl Documents { } self.dependents_map = Arc::new(doc_analyzer.dependents_map); - self.npm_reqs = Arc::new(npm_reqs); + self.npm_specifier_reqs = Arc::new({ + let mut reqs = npm_reqs.into_iter().collect::<Vec<_>>(); + reqs.sort(); + reqs + }); self.dirty = false; file_system_docs.dirty = false; } @@ -1589,6 +1631,7 @@ console.log(b, "hello deno"); documents.update_config( Some(Arc::new(import_map)), None, + None, npm_registry_api.clone(), npm_resolution.clone(), ); @@ -1627,6 +1670,7 @@ console.log(b, "hello deno"); documents.update_config( Some(Arc::new(import_map)), None, + None, npm_registry_api, npm_resolution, ); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 808a98a2c..dfbb9c2e1 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -9,12 +9,14 @@ use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::serde_json::Value; use deno_core::ModuleSpecifier; +use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_web::BlobStore; use import_map::ImportMap; use log::error; use log::warn; use serde_json::from_value; use std::collections::HashMap; +use std::collections::HashSet; use std::env; use std::fmt::Write as _; use std::path::PathBuf; @@ -58,6 +60,7 @@ use super::tsc::AssetsSnapshot; use super::tsc::TsServer; use super::urls; use crate::args::get_root_cert_store; +use crate::args::package_json; use crate::args::resolve_import_map_from_specifier; use crate::args::CaData; use crate::args::CacheSetting; @@ -130,6 +133,8 @@ pub struct Inner { maybe_import_map: Option<Arc<ImportMap>>, /// The URL for the import map which is used to determine relative imports. maybe_import_map_uri: Option<Url>, + /// An optional package.json configuration file. + maybe_package_json: Option<PackageJson>, /// Configuration for formatter which has been taken from specified config file. fmt_options: FmtOptions, /// An optional configuration for linter which has been taken from specified config file. @@ -376,6 +381,7 @@ impl Inner { maybe_config_file: None, maybe_import_map: None, maybe_import_map_uri: None, + maybe_package_json: None, fmt_options: Default::default(), lint_options: Default::default(), maybe_testing_server: None, @@ -456,8 +462,6 @@ impl Inner { Ok(navigation_tree) } - /// Returns a tuple with parsed `ConfigFile` and `Url` pointing to that file. - /// If there's no config file specified in settings returns `None`. fn get_config_file(&self) -> Result<Option<ConfigFile>, AnyError> { let workspace_settings = self.config.get_workspace_settings(); let maybe_config = workspace_settings.config; @@ -501,6 +505,28 @@ impl Inner { } } + fn get_package_json( + &self, + maybe_config_file: Option<&ConfigFile>, + ) -> Result<Option<PackageJson>, AnyError> { + // It is possible that root_uri is not set, for example when having a single + // file open and not a workspace. In those situations we can't + // automatically discover the configuration + if let Some(root_uri) = &self.config.root_uri { + let root_path = specifier_to_file_path(root_uri)?; + let maybe_package_json = package_json::discover_from( + &root_path, + maybe_config_file.and_then(|f| f.specifier.to_file_path().ok()), + )?; + Ok(maybe_package_json.map(|c| { + lsp_log!(" Auto-resolved package.json: \"{}\"", c.specifier()); + c + })) + } else { + Ok(None) + } + } + fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool { if specifier.scheme() == "asset" { matches!( @@ -785,6 +811,7 @@ impl Inner { fn update_config_file(&mut self) -> Result<(), AnyError> { self.maybe_config_file = None; + self.maybe_package_json = None; self.fmt_options = Default::default(); self.lint_options = Default::default(); @@ -814,6 +841,15 @@ impl Inner { Ok(()) } + /// Updates the package.json. Always ensure this is done after updating + /// the configuration file as the resolution of this depends on that. + fn update_package_json(&mut self) -> Result<(), AnyError> { + self.maybe_package_json = None; + self.maybe_package_json = + self.get_package_json(self.maybe_config_file.as_ref())?; + Ok(()) + } + async fn update_tsconfig(&mut self) -> Result<(), AnyError> { let mark = self.performance.mark("update_tsconfig", None::<()>); let mut tsconfig = TsConfig::new(json!({ @@ -923,6 +959,9 @@ impl Inner { if let Err(err) = self.update_config_file() { self.client.show_message(MessageType::WARNING, err).await; } + if let Err(err) = self.update_package_json() { + self.client.show_message(MessageType::WARNING, err).await; + } if let Err(err) = self.update_tsconfig().await { self.client.show_message(MessageType::WARNING, err).await; } @@ -950,6 +989,7 @@ impl Inner { self.documents.update_config( self.maybe_import_map.clone(), self.maybe_config_file.as_ref(), + self.maybe_package_json.as_ref(), self.npm_resolver.api().clone(), self.npm_resolver.resolution().clone(), ); @@ -1129,6 +1169,9 @@ impl Inner { if let Err(err) = self.update_config_file() { self.client.show_message(MessageType::WARNING, err).await; } + if let Err(err) = self.update_package_json() { + self.client.show_message(MessageType::WARNING, err).await; + } if let Err(err) = self.update_import_map().await { self.client.show_message(MessageType::WARNING, err).await; } @@ -1139,6 +1182,7 @@ impl Inner { self.documents.update_config( self.maybe_import_map.clone(), self.maybe_config_file.as_ref(), + self.maybe_package_json.as_ref(), self.npm_resolver.api().clone(), self.npm_resolver.resolution().clone(), ); @@ -1155,7 +1199,7 @@ impl Inner { .performance .mark("did_change_watched_files", Some(¶ms)); let mut touched = false; - let changes: Vec<Url> = params + let changes: HashSet<Url> = params .changes .iter() .map(|f| self.url_map.normalize_url(&f.uri)) @@ -1163,7 +1207,7 @@ impl Inner { // if the current tsconfig has changed, we need to reload it if let Some(config_file) = &self.maybe_config_file { - if changes.iter().any(|uri| config_file.specifier == *uri) { + if changes.contains(&config_file.specifier) { if let Err(err) = self.update_config_file() { self.client.show_message(MessageType::WARNING, err).await; } @@ -1173,10 +1217,18 @@ impl Inner { touched = true; } } + if let Some(package_json) = &self.maybe_package_json { + if changes.contains(&package_json.specifier()) { + if let Err(err) = self.update_package_json() { + self.client.show_message(MessageType::WARNING, err).await; + } + touched = true; + } + } // if the current import map, or config file has changed, we need to reload // reload the import map if let Some(import_map_uri) = &self.maybe_import_map_uri { - if changes.iter().any(|uri| import_map_uri == uri) || touched { + if changes.contains(import_map_uri) || touched { if let Err(err) = self.update_import_map().await { self.client.show_message(MessageType::WARNING, err).await; } @@ -1187,6 +1239,7 @@ impl Inner { self.documents.update_config( self.maybe_import_map.clone(), self.maybe_config_file.as_ref(), + self.maybe_package_json.as_ref(), self.npm_resolver.api().clone(), self.npm_resolver.resolution().clone(), ); |