summaryrefslogtreecommitdiff
path: root/cli/lsp
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp')
-rw-r--r--cli/lsp/documents.rs60
-rw-r--r--cli/lsp/language_server.rs63
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(&params));
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(),
);