mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
fix(lsp): handle importmaps properly (#11496)
Fixes: #11146 Fixes: #11456 Fixes: #10439
This commit is contained in:
parent
74c7559d20
commit
72ac9c3ae0
8 changed files with 331 additions and 27 deletions
|
@ -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>>,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ¶ms.text_document.text);
|
||||
self.analyze_dependencies(
|
||||
&specifier,
|
||||
&media_type,
|
||||
¶ms.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(¶ms));
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -654,6 +704,102 @@ mod tests {
|
|||
assert_eq!(actual, None);
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -231,6 +231,91 @@ fn lsp_triple_slash_types() {
|
|||
shutdown(&mut client);
|
||||
}
|
||||
|
||||
#[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");
|
||||
|
|
5
cli/tests/lsp/import-map.json
Normal file
5
cli/tests/lsp/import-map.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"imports": {
|
||||
"/~/": "./lib/"
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue