summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/diagnostics.rs9
-rw-r--r--cli/lsp/documents.rs25
-rw-r--r--cli/lsp/language_server.rs54
-rw-r--r--cli/lsp/sources.rs148
-rw-r--r--cli/lsp/tsc.rs26
-rw-r--r--cli/lsp/urls.rs6
-rw-r--r--cli/tests/integration/lsp_tests.rs85
-rw-r--r--cli/tests/lsp/import-map.json5
8 files changed, 331 insertions, 27 deletions
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
index 18cff6ea7..a7051ddf1 100644
--- a/cli/lsp/diagnostics.rs
+++ b/cli/lsp/diagnostics.rs
@@ -122,11 +122,16 @@ impl DiagnosticsServer {
pub(crate) async fn invalidate(&self, specifiers: Vec<ModuleSpecifier>) {
let mut collection = self.collection.lock().await;
- for specifier in specifiers {
- collection.versions.remove(&specifier);
+ for specifier in &specifiers {
+ collection.versions.remove(specifier);
}
}
+ pub(crate) async fn invalidate_all(&self) {
+ let mut collection = self.collection.lock().await;
+ collection.versions.clear();
+ }
+
pub(crate) fn start(
&mut self,
language_server: Arc<Mutex<language_server::Inner>>,
diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs
index 00a4aa156..911e30389 100644
--- a/cli/lsp/documents.rs
+++ b/cli/lsp/documents.rs
@@ -81,7 +81,7 @@ pub struct DocumentData {
bytes: Option<Vec<u8>>,
dependencies: Option<HashMap<String, analysis::Dependency>>,
dependency_ranges: Option<analysis::DependencyRanges>,
- language_id: LanguageId,
+ pub(crate) language_id: LanguageId,
line_index: Option<LineIndex>,
maybe_navigation_tree: Option<tsc::NavigationTree>,
specifier: ModuleSpecifier,
@@ -180,7 +180,7 @@ impl DocumentData {
#[derive(Debug, Clone, Default)]
pub struct DocumentCache {
dependents_graph: HashMap<ModuleSpecifier, HashSet<ModuleSpecifier>>,
- pub docs: HashMap<ModuleSpecifier, DocumentData>,
+ pub(crate) docs: HashMap<ModuleSpecifier, DocumentData>,
}
impl DocumentCache {
@@ -272,16 +272,29 @@ impl DocumentCache {
&self,
specifier: &ModuleSpecifier,
) -> Option<HashMap<String, analysis::Dependency>> {
- let doc = self.docs.get(specifier)?;
- doc.dependencies.clone()
+ self
+ .docs
+ .get(specifier)
+ .map(|doc| doc.dependencies.clone())
+ .flatten()
+ }
+
+ pub fn get_language_id(
+ &self,
+ specifier: &ModuleSpecifier,
+ ) -> Option<LanguageId> {
+ self.docs.get(specifier).map(|doc| doc.language_id.clone())
}
pub fn get_navigation_tree(
&self,
specifier: &ModuleSpecifier,
) -> Option<tsc::NavigationTree> {
- let doc = self.docs.get(specifier)?;
- doc.maybe_navigation_tree.clone()
+ self
+ .docs
+ .get(specifier)
+ .map(|doc| doc.maybe_navigation_tree.clone())
+ .flatten()
}
/// Determines if the specifier should be processed for diagnostics and other
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 7f8086466..25b588ae6 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -163,15 +163,15 @@ impl Inner {
fn analyze_dependencies(
&mut self,
specifier: &ModuleSpecifier,
+ media_type: &MediaType,
source: &str,
) {
- let media_type = MediaType::from(specifier);
if let Ok(parsed_module) =
- analysis::parse_module(specifier, source, &media_type)
+ analysis::parse_module(specifier, source, media_type)
{
let (mut deps, _) = analysis::analyze_dependencies(
specifier,
- &media_type,
+ media_type,
&parsed_module,
&self.maybe_import_map,
);
@@ -194,6 +194,24 @@ impl Inner {
}
}
+ /// Analyzes all dependencies for all documents that have been opened in the
+ /// editor and sets the dependencies property on the documents.
+ fn analyze_dependencies_all(&mut self) {
+ let docs: Vec<(ModuleSpecifier, String, MediaType)> = self
+ .documents
+ .docs
+ .iter()
+ .filter_map(|(s, doc)| {
+ let source = doc.content().ok().flatten()?;
+ let media_type = MediaType::from(&doc.language_id);
+ Some((s.clone(), source, media_type))
+ })
+ .collect();
+ for (specifier, source, media_type) in docs {
+ self.analyze_dependencies(&specifier, &media_type, &source);
+ }
+ }
+
/// Searches assets, open documents and external sources for a line_index,
/// which might be performed asynchronously, hydrating in memory caches for
/// subsequent requests.
@@ -445,8 +463,10 @@ impl Inner {
let import_map =
ImportMap::from_json(&import_map_url.to_string(), &import_map_json)?;
self.maybe_import_map_uri = Some(import_map_url);
- self.maybe_import_map = Some(import_map);
+ self.maybe_import_map = Some(import_map.clone());
+ self.sources.set_import_map(Some(import_map));
} else {
+ self.sources.set_import_map(None);
self.maybe_import_map = None;
}
self.performance.measure(mark);
@@ -694,6 +714,7 @@ impl Inner {
LanguageId::TypeScript
}
};
+ let media_type = MediaType::from(&language_id);
self.documents.open(
specifier.clone(),
params.text_document.version,
@@ -702,7 +723,11 @@ impl Inner {
);
if self.documents.is_diagnosable(&specifier) {
- self.analyze_dependencies(&specifier, &params.text_document.text);
+ self.analyze_dependencies(
+ &specifier,
+ &media_type,
+ &params.text_document.text,
+ );
self
.diagnostics_server
.invalidate(self.documents.dependents(&specifier))
@@ -724,7 +749,10 @@ impl Inner {
) {
Ok(Some(source)) => {
if self.documents.is_diagnosable(&specifier) {
- self.analyze_dependencies(&specifier, &source);
+ let media_type = MediaType::from(
+ &self.documents.get_language_id(&specifier).unwrap(),
+ );
+ self.analyze_dependencies(&specifier, &media_type, &source);
self
.diagnostics_server
.invalidate(self.documents.dependents(&specifier))
@@ -825,12 +853,14 @@ impl Inner {
let mark = self
.performance
.mark("did_change_watched_files", Some(&params));
+ let mut touched = false;
// if the current import map has changed, we need to reload it
if let Some(import_map_uri) = &self.maybe_import_map_uri {
if params.changes.iter().any(|fe| *import_map_uri == fe.uri) {
if let Err(err) = self.update_import_map().await {
self.client.show_message(MessageType::Warning, err).await;
}
+ touched = true;
}
}
// if the current tsconfig has changed, we need to reload it
@@ -839,6 +869,14 @@ impl Inner {
if let Err(err) = self.update_tsconfig().await {
self.client.show_message(MessageType::Warning, err).await;
}
+ touched = true;
+ }
+ }
+ if touched {
+ self.analyze_dependencies_all();
+ self.diagnostics_server.invalidate_all().await;
+ if let Err(err) = self.diagnostics_server.update() {
+ error!("Cannot update diagnostics: {}", err);
}
}
self.performance.measure(mark);
@@ -2392,7 +2430,9 @@ impl Inner {
// invalidate some diagnostics
if self.documents.contains_key(&referrer) {
if let Some(source) = self.documents.content(&referrer).unwrap() {
- self.analyze_dependencies(&referrer, &source);
+ let media_type =
+ MediaType::from(&self.documents.get_language_id(&referrer).unwrap());
+ self.analyze_dependencies(&referrer, &media_type, &source);
}
self.diagnostics_server.invalidate(vec![referrer]).await;
}
diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs
index 1b7d6f3fa..62c76260c 100644
--- a/cli/lsp/sources.rs
+++ b/cli/lsp/sources.rs
@@ -3,6 +3,7 @@
use super::analysis;
use super::text::LineIndex;
use super::tsc;
+use super::urls::INVALID_SPECIFIER;
use crate::config_file::ConfigFile;
use crate::file_fetcher::get_source_from_bytes;
@@ -105,7 +106,7 @@ fn resolve_specifier(
}
}
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
struct Metadata {
dependencies: Option<HashMap<String, analysis::Dependency>>,
length_utf16: usize,
@@ -115,9 +116,27 @@ struct Metadata {
maybe_warning: Option<String>,
media_type: MediaType,
source: String,
+ specifier: ModuleSpecifier,
version: String,
}
+impl Default for Metadata {
+ fn default() -> Self {
+ Self {
+ dependencies: None,
+ length_utf16: 0,
+ line_index: LineIndex::default(),
+ maybe_navigation_tree: None,
+ maybe_types: None,
+ maybe_warning: None,
+ media_type: MediaType::default(),
+ source: String::default(),
+ specifier: INVALID_SPECIFIER.clone(),
+ version: String::default(),
+ }
+ }
+}
+
impl Metadata {
fn new(
specifier: &ModuleSpecifier,
@@ -151,9 +170,28 @@ impl Metadata {
maybe_warning,
media_type: media_type.to_owned(),
source: source.to_string(),
+ specifier: specifier.clone(),
version: version.to_string(),
}
}
+
+ fn refresh(&mut self, maybe_import_map: &Option<ImportMap>) {
+ let (dependencies, maybe_types) = if let Ok(parsed_module) =
+ analysis::parse_module(&self.specifier, &self.source, &self.media_type)
+ {
+ let (deps, maybe_types) = analysis::analyze_dependencies(
+ &self.specifier,
+ &self.media_type,
+ &parsed_module,
+ maybe_import_map,
+ );
+ (Some(deps), maybe_types)
+ } else {
+ (None, None)
+ };
+ self.dependencies = dependencies;
+ self.maybe_types = maybe_types;
+ }
}
#[derive(Debug, Clone, Default)]
@@ -239,6 +277,10 @@ impl Sources {
self.0.lock().metadata.keys().cloned().collect()
}
+ pub fn set_import_map(&self, maybe_import_map: Option<ImportMap>) {
+ self.0.lock().set_import_map(maybe_import_map)
+ }
+
pub fn set_navigation_tree(
&self,
specifier: &ModuleSpecifier,
@@ -483,6 +525,13 @@ impl Inner {
}
}
+ fn set_import_map(&mut self, maybe_import_map: Option<ImportMap>) {
+ for (_, metadata) in self.metadata.iter_mut() {
+ metadata.refresh(&maybe_import_map);
+ }
+ self.maybe_import_map = maybe_import_map;
+ }
+
fn set_maybe_type(
&mut self,
specifier: &str,
@@ -517,6 +566,7 @@ mod tests {
use super::*;
use deno_core::resolve_path;
use deno_core::resolve_url;
+ use deno_core::serde_json::json;
use std::env;
use tempfile::TempDir;
@@ -655,6 +705,102 @@ mod tests {
}
#[test]
+ fn test_resolve_with_import_map() {
+ let (sources, location) = setup();
+ let import_map_json = json!({
+ "imports": {
+ "mylib": "https://deno.land/x/myLib/index.js"
+ }
+ });
+ let import_map = ImportMap::from_json(
+ "https://deno.land/x/",
+ &import_map_json.to_string(),
+ )
+ .unwrap();
+ sources.set_import_map(Some(import_map));
+ let cache = HttpCache::new(&location);
+ let mylib_specifier =
+ resolve_url("https://deno.land/x/myLib/index.js").unwrap();
+ let mut mylib_headers_map = HashMap::new();
+ mylib_headers_map.insert(
+ "content-type".to_string(),
+ "application/javascript".to_string(),
+ );
+ cache
+ .set(
+ &mylib_specifier,
+ mylib_headers_map,
+ b"export const a = \"a\";\n",
+ )
+ .unwrap();
+ let referrer = resolve_url("https://deno.land/x/mod.ts").unwrap();
+ cache
+ .set(
+ &referrer,
+ Default::default(),
+ b"export { a } from \"mylib\";",
+ )
+ .unwrap();
+ let actual = sources.resolve_import("mylib", &referrer);
+ assert_eq!(actual, Some((mylib_specifier, MediaType::JavaScript)));
+ }
+
+ #[test]
+ fn test_update_import_map() {
+ let (sources, location) = setup();
+ let import_map_json = json!({
+ "imports": {
+ "otherlib": "https://deno.land/x/otherlib/index.js"
+ }
+ });
+ let import_map = ImportMap::from_json(
+ "https://deno.land/x/",
+ &import_map_json.to_string(),
+ )
+ .unwrap();
+ sources.set_import_map(Some(import_map));
+ let cache = HttpCache::new(&location);
+ let mylib_specifier =
+ resolve_url("https://deno.land/x/myLib/index.js").unwrap();
+ let mut mylib_headers_map = HashMap::new();
+ mylib_headers_map.insert(
+ "content-type".to_string(),
+ "application/javascript".to_string(),
+ );
+ cache
+ .set(
+ &mylib_specifier,
+ mylib_headers_map,
+ b"export const a = \"a\";\n",
+ )
+ .unwrap();
+ let referrer = resolve_url("https://deno.land/x/mod.ts").unwrap();
+ cache
+ .set(
+ &referrer,
+ Default::default(),
+ b"export { a } from \"mylib\";",
+ )
+ .unwrap();
+ let actual = sources.resolve_import("mylib", &referrer);
+ assert_eq!(actual, None);
+ let import_map_json = json!({
+ "imports": {
+ "otherlib": "https://deno.land/x/otherlib/index.js",
+ "mylib": "https://deno.land/x/myLib/index.js"
+ }
+ });
+ let import_map = ImportMap::from_json(
+ "https://deno.land/x/",
+ &import_map_json.to_string(),
+ )
+ .unwrap();
+ sources.set_import_map(Some(import_map));
+ let actual = sources.resolve_import("mylib", &referrer);
+ assert_eq!(actual, Some((mylib_specifier, MediaType::JavaScript)));
+ }
+
+ #[test]
fn test_sources_resolve_specifier_non_supported_schema() {
let (sources, _) = setup();
let specifier =
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index aed1dda9d..3f52beb6b 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -10,6 +10,7 @@ use super::semantic_tokens::SemanticTokensBuilder;
use super::semantic_tokens::TsTokenEncodingConsts;
use super::text;
use super::text::LineIndex;
+use super::urls::INVALID_SPECIFIER;
use crate::config_file::TsConfig;
use crate::media_type::MediaType;
@@ -577,7 +578,7 @@ impl DocumentSpan {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> Option<lsp::LocationLink> {
- let target_specifier = normalize_specifier(&self.file_name).unwrap();
+ let target_specifier = normalize_specifier(&self.file_name).ok()?;
let target_line_index = language_server
.get_line_index(target_specifier.clone())
.await
@@ -585,7 +586,7 @@ impl DocumentSpan {
let target_uri = language_server
.url_map
.normalize_specifier(&target_specifier)
- .unwrap();
+ .ok()?;
let (target_range, target_selection_range) =
if let Some(context_span) = &self.context_span {
(
@@ -778,11 +779,12 @@ impl ImplementationLocation {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> lsp::Location {
- let specifier = normalize_specifier(&self.document_span.file_name).unwrap();
+ let specifier = normalize_specifier(&self.document_span.file_name)
+ .unwrap_or_else(|_| ModuleSpecifier::parse("deno://invalid").unwrap());
let uri = language_server
.url_map
.normalize_specifier(&specifier)
- .unwrap();
+ .unwrap_or_else(|_| ModuleSpecifier::parse("deno://invalid").unwrap());
lsp::Location {
uri,
range: self.document_span.text_span.to_range(line_index),
@@ -1107,11 +1109,12 @@ impl ReferenceEntry {
line_index: &LineIndex,
language_server: &mut language_server::Inner,
) -> lsp::Location {
- let specifier = normalize_specifier(&self.document_span.file_name).unwrap();
+ let specifier = normalize_specifier(&self.document_span.file_name)
+ .unwrap_or_else(|_| INVALID_SPECIFIER.clone());
let uri = language_server
.url_map
.normalize_specifier(&specifier)
- .unwrap();
+ .unwrap_or_else(|_| INVALID_SPECIFIER.clone());
lsp::Location {
uri,
range: self.document_span.text_span.to_range(line_index),
@@ -1139,7 +1142,7 @@ impl CallHierarchyItem {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyItem> {
- let target_specifier = normalize_specifier(&self.file).unwrap();
+ let target_specifier = normalize_specifier(&self.file).ok()?;
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1158,11 +1161,12 @@ impl CallHierarchyItem {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> lsp::CallHierarchyItem {
- let target_specifier = normalize_specifier(&self.file).unwrap();
+ let target_specifier = normalize_specifier(&self.file)
+ .unwrap_or_else(|_| INVALID_SPECIFIER.clone());
let uri = language_server
.url_map
.normalize_specifier(&target_specifier)
- .unwrap();
+ .unwrap_or_else(|_| INVALID_SPECIFIER.clone());
let use_file_name = self.is_source_file_item();
let maybe_file_path = if uri.scheme() == "file" {
@@ -1239,7 +1243,7 @@ impl CallHierarchyIncomingCall {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyIncomingCall> {
- let target_specifier = normalize_specifier(&self.from.file).unwrap();
+ let target_specifier = normalize_specifier(&self.from.file).ok()?;
let target_line_index = language_server
.get_line_index(target_specifier)
.await
@@ -1274,7 +1278,7 @@ impl CallHierarchyOutgoingCall {
language_server: &mut language_server::Inner,
maybe_root_path: Option<&Path>,
) -> Option<lsp::CallHierarchyOutgoingCall> {
- let target_specifier = normalize_specifier(&self.to.file).unwrap();
+ let target_specifier = normalize_specifier(&self.to.file).ok()?;
let target_line_index = language_server
.get_line_index(target_specifier)
.await
diff --git a/cli/lsp/urls.rs b/cli/lsp/urls.rs
index 49049b535..52876157a 100644
--- a/cli/lsp/urls.rs
+++ b/cli/lsp/urls.rs
@@ -11,6 +11,12 @@ use deno_core::url::Url;
use deno_core::ModuleSpecifier;
use std::collections::HashMap;
+lazy_static::lazy_static! {
+ /// Used in situations where a default URL needs to be used where otherwise a
+ /// panic is undesired.
+ pub(crate) static ref INVALID_SPECIFIER: ModuleSpecifier = ModuleSpecifier::parse("deno://invalid").unwrap();
+}
+
/// Matches the `encodeURIComponent()` encoding from JavaScript, which matches
/// the component percent encoding set.
///
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index 81eb64b7a..7f4f4e591 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -232,6 +232,91 @@ fn lsp_triple_slash_types() {
}
#[test]
+fn lsp_import_map() {
+ let temp_dir = TempDir::new().expect("could not create temp dir");
+ let mut params: lsp::InitializeParams =
+ serde_json::from_value(load_fixture("initialize_params.json")).unwrap();
+ let import_map =
+ serde_json::to_vec_pretty(&load_fixture("import-map.json")).unwrap();
+ fs::write(temp_dir.path().join("import-map.json"), import_map).unwrap();
+ fs::create_dir(temp_dir.path().join("lib")).unwrap();
+ fs::write(
+ temp_dir.path().join("lib").join("b.ts"),
+ r#"export const b = "b";"#,
+ )
+ .unwrap();
+
+ params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap());
+ if let Some(Value::Object(mut map)) = params.initialization_options {
+ map.insert("importMap".to_string(), json!("import-map.json"));
+ params.initialization_options = Some(Value::Object(map));
+ }
+
+ let deno_exe = deno_exe_path();
+ let mut client = LspClient::new(&deno_exe).unwrap();
+ client
+ .write_request::<_, _, Value>("initialize", params)
+ .unwrap();
+
+ client.write_notification("initialized", json!({})).unwrap();
+ let uri = Url::from_file_path(temp_dir.path().join("a.ts")).unwrap();
+
+ let diagnostics = did_open(
+ &mut client,
+ json!({
+ "textDocument": {
+ "uri": uri,
+ "languageId": "typescript",
+ "version": 1,
+ "text": "import { b } from \"/~/b.ts\";\n\nconsole.log(b);\n"
+ }
+ }),
+ );
+
+ let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics);
+ assert_eq!(diagnostics.count(), 0);
+
+ let (maybe_res, maybe_err) = client
+ .write_request::<_, _, Value>(
+ "textDocument/hover",
+ json!({
+ "textDocument": {
+ "uri": uri
+ },
+ "position": {
+ "line": 2,
+ "character": 12
+ }
+ }),
+ )
+ .unwrap();
+ assert!(maybe_err.is_none());
+ assert_eq!(
+ maybe_res,
+ Some(json!({
+ "contents": [
+ {
+ "language": "typescript",
+ "value":"(alias) const b: \"b\"\nimport b"
+ },
+ ""
+ ],
+ "range": {
+ "start": {
+ "line": 2,
+ "character": 12
+ },
+ "end": {
+ "line": 2,
+ "character": 13
+ }
+ }
+ }))
+ );
+ shutdown(&mut client);
+}
+
+#[test]
fn lsp_hover() {
let mut client = init("initialize_params.json");
did_open(
diff --git a/cli/tests/lsp/import-map.json b/cli/tests/lsp/import-map.json
new file mode 100644
index 000000000..75d5d0849
--- /dev/null
+++ b/cli/tests/lsp/import-map.json
@@ -0,0 +1,5 @@
+{
+ "imports": {
+ "/~/": "./lib/"
+ }
+}