mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00:00
refactor(lsp): separate document and module storage (#28469)
This commit is contained in:
parent
61b4ef6815
commit
89cdd01131
20 changed files with 4619 additions and 4839 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -1591,6 +1591,7 @@ dependencies = [
|
|||
"unicode-width 0.1.13",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
"weak-table",
|
||||
"winapi",
|
||||
"winres",
|
||||
"zip",
|
||||
|
@ -9402,6 +9403,12 @@ dependencies = [
|
|||
"thiserror 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weak-table"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
|
|
|
@ -327,6 +327,7 @@ url = { version = "2.5", features = ["serde", "expose_internals"] }
|
|||
urlpattern = "0.3.0"
|
||||
uuid = { version = "1.3.0", features = ["v4"] }
|
||||
walkdir = "=2.5.0"
|
||||
weak-table = "0.3.2"
|
||||
web-transport-proto = "0.2.3"
|
||||
webpki-root-certs = "0.26.5"
|
||||
webpki-roots = "0.26"
|
||||
|
|
|
@ -177,6 +177,7 @@ typed-arena.workspace = true
|
|||
unicode-width.workspace = true
|
||||
uuid = { workspace = true, features = ["serde"] }
|
||||
walkdir.workspace = true
|
||||
weak-table.workspace = true
|
||||
zip = { workspace = true, features = ["deflate-flate2"] }
|
||||
zstd.workspace = true
|
||||
|
||||
|
|
|
@ -130,11 +130,27 @@ fn bench_deco_apps_edits(deno_exe: &Path) -> Duration {
|
|||
}
|
||||
}),
|
||||
);
|
||||
let re = lazy_regex::regex!(r"Documents in memory: (\d+)");
|
||||
let open_re = lazy_regex::regex!(r"Open: (\d+)");
|
||||
let server_re = lazy_regex::regex!(r"Server: (\d+)");
|
||||
let res = res.as_str().unwrap().to_string();
|
||||
assert!(res.starts_with("# Deno Language Server Status"));
|
||||
let captures = re.captures(&res).unwrap();
|
||||
let count = captures.get(1).unwrap().as_str().parse::<usize>().unwrap();
|
||||
let open_count = open_re
|
||||
.captures(&res)
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
let server_count = server_re
|
||||
.captures(&res)
|
||||
.unwrap()
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
let count = open_count + server_count;
|
||||
assert!(count > 1000, "count: {}", count);
|
||||
|
||||
client.shutdown();
|
||||
|
|
|
@ -5,12 +5,14 @@ use std::cmp::Ordering;
|
|||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_ast::SourceRange;
|
||||
use deno_ast::SourceRangedForSpanned;
|
||||
use deno_ast::SourceTextInfo;
|
||||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::resolve_url;
|
||||
use deno_core::serde::Deserialize;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::serde_json;
|
||||
|
@ -33,6 +35,7 @@ use deno_semver::SmallStackString;
|
|||
use deno_semver::StackString;
|
||||
use deno_semver::Version;
|
||||
use import_map::ImportMap;
|
||||
use lsp_types::Uri;
|
||||
use node_resolver::InNpmPackageChecker;
|
||||
use node_resolver::NodeResolutionKind;
|
||||
use node_resolver::ResolutionMode;
|
||||
|
@ -45,13 +48,12 @@ use tower_lsp::lsp_types::Range;
|
|||
|
||||
use super::diagnostics::DenoDiagnostic;
|
||||
use super::diagnostics::DiagnosticSource;
|
||||
use super::documents::Documents;
|
||||
use super::documents::DocumentModule;
|
||||
use super::documents::DocumentModules;
|
||||
use super::language_server;
|
||||
use super::resolver::LspResolver;
|
||||
use super::tsc;
|
||||
use super::urls::url_to_uri;
|
||||
use crate::args::jsr_url;
|
||||
use crate::lsp::logging::lsp_warn;
|
||||
use crate::tools::lint::CliLinter;
|
||||
use crate::util::path::relative_specifier;
|
||||
|
||||
|
@ -233,27 +235,27 @@ fn code_as_string(code: &Option<lsp::NumberOrString>) -> String {
|
|||
|
||||
/// Rewrites imports in quick fixes and code changes to be Deno specific.
|
||||
pub struct TsResponseImportMapper<'a> {
|
||||
documents: &'a Documents,
|
||||
document_modules: &'a DocumentModules,
|
||||
scope: Option<Arc<ModuleSpecifier>>,
|
||||
maybe_import_map: Option<&'a ImportMap>,
|
||||
resolver: &'a LspResolver,
|
||||
tsc_specifier_map: &'a tsc::TscSpecifierMap,
|
||||
file_referrer: ModuleSpecifier,
|
||||
}
|
||||
|
||||
impl<'a> TsResponseImportMapper<'a> {
|
||||
pub fn new(
|
||||
documents: &'a Documents,
|
||||
document_modules: &'a DocumentModules,
|
||||
scope: Option<Arc<ModuleSpecifier>>,
|
||||
maybe_import_map: Option<&'a ImportMap>,
|
||||
resolver: &'a LspResolver,
|
||||
tsc_specifier_map: &'a tsc::TscSpecifierMap,
|
||||
file_referrer: &ModuleSpecifier,
|
||||
) -> Self {
|
||||
Self {
|
||||
documents,
|
||||
document_modules,
|
||||
scope,
|
||||
maybe_import_map,
|
||||
resolver,
|
||||
tsc_specifier_map,
|
||||
file_referrer: file_referrer.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,7 +301,7 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
let export = self.resolver.jsr_lookup_export_for_path(
|
||||
&nv,
|
||||
&path,
|
||||
Some(&self.file_referrer),
|
||||
self.scope.as_deref(),
|
||||
)?;
|
||||
let sub_path = (export != ".")
|
||||
.then_some(export)
|
||||
|
@ -327,7 +329,7 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
req = req.or_else(|| {
|
||||
self
|
||||
.resolver
|
||||
.jsr_lookup_req_for_nv(&nv, Some(&self.file_referrer))
|
||||
.jsr_lookup_req_for_nv(&nv, self.scope.as_deref())
|
||||
});
|
||||
let spec_str = if let Some(req) = req {
|
||||
let req_ref = PackageReqReference { req, sub_path };
|
||||
|
@ -357,11 +359,11 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
|
||||
if let Some(npm_resolver) = self
|
||||
.resolver
|
||||
.maybe_managed_npm_resolver(Some(&self.file_referrer))
|
||||
.maybe_managed_npm_resolver(self.scope.as_deref())
|
||||
{
|
||||
let in_npm_pkg = self
|
||||
.resolver
|
||||
.in_npm_pkg_checker(Some(&self.file_referrer))
|
||||
.in_npm_pkg_checker(self.scope.as_deref())
|
||||
.in_npm_package(specifier);
|
||||
if in_npm_pkg {
|
||||
if let Ok(Some(pkg_id)) =
|
||||
|
@ -428,7 +430,7 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
}
|
||||
} else if let Some(dep_name) = self
|
||||
.resolver
|
||||
.file_url_to_package_json_dep(specifier, Some(&self.file_referrer))
|
||||
.file_url_to_package_json_dep(specifier, self.scope.as_deref())
|
||||
{
|
||||
return Some(dep_name);
|
||||
}
|
||||
|
@ -515,7 +517,7 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
for specifier in specifiers {
|
||||
if let Some(specifier) = self
|
||||
.resolver
|
||||
.as_cli_resolver(Some(&self.file_referrer))
|
||||
.as_cli_resolver(self.scope.as_deref())
|
||||
.resolve(
|
||||
&specifier,
|
||||
referrer,
|
||||
|
@ -525,7 +527,11 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
)
|
||||
.ok()
|
||||
.and_then(|s| self.tsc_specifier_map.normalize(s.as_str()).ok())
|
||||
.filter(|s| self.documents.exists(s, Some(&self.file_referrer)))
|
||||
.filter(|s| {
|
||||
self
|
||||
.document_modules
|
||||
.specifier_exists(s, self.scope.as_deref())
|
||||
})
|
||||
{
|
||||
if let Some(specifier) = self
|
||||
.check_specifier(&specifier, referrer)
|
||||
|
@ -547,7 +553,7 @@ impl<'a> TsResponseImportMapper<'a> {
|
|||
) -> bool {
|
||||
self
|
||||
.resolver
|
||||
.as_cli_resolver(Some(&self.file_referrer))
|
||||
.as_cli_resolver(self.scope.as_deref())
|
||||
.resolve(
|
||||
specifier_text,
|
||||
referrer,
|
||||
|
@ -645,6 +651,7 @@ fn try_reverse_map_package_json_exports(
|
|||
/// like an import and rewrite the import specifier to include the extension
|
||||
pub fn fix_ts_import_changes(
|
||||
changes: &[tsc::FileTextChanges],
|
||||
module: &DocumentModule,
|
||||
language_server: &language_server::Inner,
|
||||
token: &CancellationToken,
|
||||
) -> Result<Vec<tsc::FileTextChanges>, AnyError> {
|
||||
|
@ -653,16 +660,29 @@ pub fn fix_ts_import_changes(
|
|||
if token.is_cancelled() {
|
||||
return Err(anyhow!("request cancelled"));
|
||||
}
|
||||
let Ok(referrer) = ModuleSpecifier::parse(&change.file_name) else {
|
||||
let is_new_file = change.is_new_file.unwrap_or(false);
|
||||
let Ok(target_specifier) = resolve_url(&change.file_name) else {
|
||||
continue;
|
||||
};
|
||||
let referrer_doc = language_server.get_asset_or_document(&referrer).ok();
|
||||
let resolution_mode = referrer_doc
|
||||
let target_module = if is_new_file {
|
||||
None
|
||||
} else {
|
||||
let Some(target_module) = language_server
|
||||
.document_modules
|
||||
.inspect_module_for_specifier(
|
||||
&target_specifier,
|
||||
module.scope.as_deref(),
|
||||
)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
Some(target_module)
|
||||
};
|
||||
let resolution_mode = target_module
|
||||
.as_ref()
|
||||
.map(|d| d.resolution_mode())
|
||||
.map(|m| m.resolution_mode)
|
||||
.unwrap_or(ResolutionMode::Import);
|
||||
let import_mapper =
|
||||
language_server.get_ts_response_import_mapper(&referrer);
|
||||
let import_mapper = language_server.get_ts_response_import_mapper(module);
|
||||
let mut text_changes = Vec::new();
|
||||
for text_change in &change.text_changes {
|
||||
let lines = text_change.new_text.split('\n');
|
||||
|
@ -673,7 +693,11 @@ pub fn fix_ts_import_changes(
|
|||
let specifier =
|
||||
captures.iter().skip(1).find_map(|s| s).unwrap().as_str();
|
||||
if let Some(new_specifier) = import_mapper
|
||||
.check_unresolved_specifier(specifier, &referrer, resolution_mode)
|
||||
.check_unresolved_specifier(
|
||||
specifier,
|
||||
&target_specifier,
|
||||
resolution_mode,
|
||||
)
|
||||
{
|
||||
line.replace(specifier, &new_specifier)
|
||||
} else {
|
||||
|
@ -702,9 +726,8 @@ pub fn fix_ts_import_changes(
|
|||
/// Fix tsc import code actions so that the module specifier is correct for
|
||||
/// resolution by Deno (includes the extension).
|
||||
fn fix_ts_import_action<'a>(
|
||||
referrer: &ModuleSpecifier,
|
||||
resolution_mode: ResolutionMode,
|
||||
action: &'a tsc::CodeFixAction,
|
||||
module: &DocumentModule,
|
||||
language_server: &language_server::Inner,
|
||||
) -> Option<Cow<'a, tsc::CodeFixAction>> {
|
||||
if !matches!(
|
||||
|
@ -721,11 +744,11 @@ fn fix_ts_import_action<'a>(
|
|||
let Some(specifier) = specifier else {
|
||||
return Some(Cow::Borrowed(action));
|
||||
};
|
||||
let import_mapper = language_server.get_ts_response_import_mapper(referrer);
|
||||
let import_mapper = language_server.get_ts_response_import_mapper(module);
|
||||
if let Some(new_specifier) = import_mapper.check_unresolved_specifier(
|
||||
specifier,
|
||||
referrer,
|
||||
resolution_mode,
|
||||
&module.specifier,
|
||||
module.resolution_mode,
|
||||
) {
|
||||
let description = action.description.replace(specifier, &new_specifier);
|
||||
let changes = action
|
||||
|
@ -756,8 +779,11 @@ fn fix_ts_import_action<'a>(
|
|||
fix_id: None,
|
||||
fix_all_description: None,
|
||||
}))
|
||||
} else if !import_mapper.is_valid_import(specifier, referrer, resolution_mode)
|
||||
{
|
||||
} else if !import_mapper.is_valid_import(
|
||||
specifier,
|
||||
&module.specifier,
|
||||
module.resolution_mode,
|
||||
) {
|
||||
None
|
||||
} else {
|
||||
Some(Cow::Borrowed(action))
|
||||
|
@ -818,16 +844,14 @@ fn is_preferred(
|
|||
/// for an LSP CodeAction.
|
||||
pub fn ts_changes_to_edit(
|
||||
changes: &[tsc::FileTextChanges],
|
||||
module: &DocumentModule,
|
||||
language_server: &language_server::Inner,
|
||||
) -> Result<Option<lsp::WorkspaceEdit>, AnyError> {
|
||||
let mut text_document_edits = Vec::new();
|
||||
for change in changes {
|
||||
let edit = match change.to_text_document_edit(language_server) {
|
||||
Ok(e) => e,
|
||||
Err(err) => {
|
||||
lsp_warn!("Couldn't covert text document edit: {:#}", err);
|
||||
continue;
|
||||
}
|
||||
let Some(edit) = change.to_text_document_edit(module, language_server)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
text_document_edits.push(edit);
|
||||
}
|
||||
|
@ -841,7 +865,7 @@ pub fn ts_changes_to_edit(
|
|||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeActionData {
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub uri: Uri,
|
||||
pub fix_id: String,
|
||||
}
|
||||
|
||||
|
@ -866,27 +890,27 @@ pub struct CodeActionCollection {
|
|||
impl CodeActionCollection {
|
||||
pub fn add_deno_fix_action(
|
||||
&mut self,
|
||||
uri: &Uri,
|
||||
specifier: &ModuleSpecifier,
|
||||
diagnostic: &lsp::Diagnostic,
|
||||
) -> Result<(), AnyError> {
|
||||
let code_action = DenoDiagnostic::get_code_action(specifier, diagnostic)?;
|
||||
let code_action =
|
||||
DenoDiagnostic::get_code_action(uri, specifier, diagnostic)?;
|
||||
self.actions.push(CodeActionKind::Deno(code_action));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_deno_lint_actions(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
uri: &Uri,
|
||||
module: &DocumentModule,
|
||||
diagnostic: &lsp::Diagnostic,
|
||||
maybe_text_info: Option<&SourceTextInfo>,
|
||||
maybe_parsed_source: Option<&deno_ast::ParsedSource>,
|
||||
) -> Result<(), AnyError> {
|
||||
if let Some(data_quick_fixes) = diagnostic
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|d| serde_json::from_value::<Vec<DataQuickFix>>(d.clone()).ok())
|
||||
{
|
||||
let uri = url_to_uri(specifier)?;
|
||||
for quick_fix in data_quick_fixes {
|
||||
let mut changes = HashMap::new();
|
||||
changes.insert(
|
||||
|
@ -917,22 +941,15 @@ impl CodeActionCollection {
|
|||
self.actions.push(CodeActionKind::DenoLint(code_action));
|
||||
}
|
||||
}
|
||||
self.add_deno_lint_ignore_action(
|
||||
specifier,
|
||||
diagnostic,
|
||||
maybe_text_info,
|
||||
maybe_parsed_source,
|
||||
)
|
||||
self.add_deno_lint_ignore_action(uri, module, diagnostic)
|
||||
}
|
||||
|
||||
fn add_deno_lint_ignore_action(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
uri: &Uri,
|
||||
module: &DocumentModule,
|
||||
diagnostic: &lsp::Diagnostic,
|
||||
maybe_text_info: Option<&SourceTextInfo>,
|
||||
maybe_parsed_source: Option<&deno_ast::ParsedSource>,
|
||||
) -> Result<(), AnyError> {
|
||||
let uri = url_to_uri(specifier)?;
|
||||
let code = diagnostic
|
||||
.code
|
||||
.as_ref()
|
||||
|
@ -941,11 +958,11 @@ impl CodeActionCollection {
|
|||
_ => "".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
let text_info = module.text_info();
|
||||
|
||||
let line_content = maybe_text_info.map(|ti| {
|
||||
ti.line_text(diagnostic.range.start.line as usize)
|
||||
.to_string()
|
||||
});
|
||||
let line_content = text_info
|
||||
.line_text(diagnostic.range.start.line as usize)
|
||||
.to_string();
|
||||
|
||||
let mut changes = HashMap::new();
|
||||
changes.insert(
|
||||
|
@ -953,7 +970,7 @@ impl CodeActionCollection {
|
|||
vec![lsp::TextEdit {
|
||||
new_text: prepend_whitespace(
|
||||
format!("// deno-lint-ignore {code}\n"),
|
||||
line_content,
|
||||
Some(line_content),
|
||||
),
|
||||
range: lsp::Range {
|
||||
start: lsp::Position {
|
||||
|
@ -986,20 +1003,25 @@ impl CodeActionCollection {
|
|||
.push(CodeActionKind::DenoLint(ignore_error_action));
|
||||
|
||||
// Disable a lint error for the entire file.
|
||||
let maybe_ignore_comment = maybe_parsed_source.and_then(|ps| {
|
||||
// Note: we can use ps.get_leading_comments() but it doesn't
|
||||
// work when shebang is present at the top of the file.
|
||||
ps.comments().get_vec().iter().find_map(|c| {
|
||||
let comment_text = c.text.trim();
|
||||
comment_text.split_whitespace().next().and_then(|prefix| {
|
||||
if prefix == "deno-lint-ignore-file" {
|
||||
Some(c.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let maybe_ignore_comment = module
|
||||
.open_data
|
||||
.as_ref()
|
||||
.and_then(|d| d.parsed_source.as_ref())
|
||||
.and_then(|ps| {
|
||||
let ps = ps.as_ref().ok()?;
|
||||
// Note: we can use ps.get_leading_comments() but it doesn't
|
||||
// work when shebang is present at the top of the file.
|
||||
ps.comments().get_vec().iter().find_map(|c| {
|
||||
let comment_text = c.text.trim();
|
||||
comment_text.split_whitespace().next().and_then(|prefix| {
|
||||
if prefix == "deno-lint-ignore-file" {
|
||||
Some(c.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
let mut new_text = format!("// deno-lint-ignore-file {code}\n");
|
||||
let mut range = lsp::Range {
|
||||
|
@ -1017,9 +1039,7 @@ impl CodeActionCollection {
|
|||
if let Some(ignore_comment) = maybe_ignore_comment {
|
||||
new_text = format!(" {code}");
|
||||
// Get the end position of the comment.
|
||||
let line = maybe_text_info
|
||||
.unwrap()
|
||||
.line_and_column_index(ignore_comment.end());
|
||||
let line = text_info.line_and_column_index(ignore_comment.end());
|
||||
let position = lsp::Position {
|
||||
line: line.line_index as u32,
|
||||
character: line.column_index as u32,
|
||||
|
@ -1051,7 +1071,7 @@ impl CodeActionCollection {
|
|||
|
||||
let mut changes = HashMap::new();
|
||||
changes.insert(
|
||||
uri,
|
||||
uri.clone(),
|
||||
vec![lsp::TextEdit {
|
||||
new_text: "// deno-lint-ignore-file\n".to_string(),
|
||||
range: lsp::Range {
|
||||
|
@ -1090,10 +1110,9 @@ impl CodeActionCollection {
|
|||
/// Add a TypeScript code fix action to the code actions collection.
|
||||
pub fn add_ts_fix_action(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
resolution_mode: ResolutionMode,
|
||||
action: &tsc::CodeFixAction,
|
||||
diagnostic: &lsp::Diagnostic,
|
||||
module: &DocumentModule,
|
||||
language_server: &language_server::Inner,
|
||||
) -> Result<(), AnyError> {
|
||||
if action.commands.is_some() {
|
||||
|
@ -1112,12 +1131,11 @@ impl CodeActionCollection {
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
let Some(action) =
|
||||
fix_ts_import_action(specifier, resolution_mode, action, language_server)
|
||||
let Some(action) = fix_ts_import_action(action, module, language_server)
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let edit = ts_changes_to_edit(&action.changes, language_server)?;
|
||||
let edit = ts_changes_to_edit(&action.changes, module, language_server)?;
|
||||
let code_action = lsp::CodeAction {
|
||||
title: action.description.clone(),
|
||||
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||
|
@ -1160,12 +1178,12 @@ impl CodeActionCollection {
|
|||
pub fn add_ts_fix_all_action(
|
||||
&mut self,
|
||||
action: &tsc::CodeFixAction,
|
||||
specifier: &ModuleSpecifier,
|
||||
module: &DocumentModule,
|
||||
diagnostic: &lsp::Diagnostic,
|
||||
) {
|
||||
let data = action.fix_id.as_ref().map(|fix_id| {
|
||||
json!(CodeActionData {
|
||||
specifier: specifier.clone(),
|
||||
uri: module.uri.as_ref().clone(),
|
||||
fix_id: fix_id.clone(),
|
||||
})
|
||||
});
|
||||
|
|
|
@ -19,22 +19,8 @@ use crate::lsp::logging::lsp_log;
|
|||
use crate::lsp::logging::lsp_warn;
|
||||
use crate::sys::CliSys;
|
||||
|
||||
pub fn calculate_fs_version(
|
||||
cache: &LspCache,
|
||||
specifier: &ModuleSpecifier,
|
||||
file_referrer: Option<&ModuleSpecifier>,
|
||||
) -> Option<String> {
|
||||
match specifier.scheme() {
|
||||
"npm" | "node" | "data" | "blob" => None,
|
||||
"file" => url_to_file_path(specifier)
|
||||
.ok()
|
||||
.and_then(|path| calculate_fs_version_at_path(&path)),
|
||||
_ => calculate_fs_version_in_cache(cache, specifier, file_referrer),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate a version for for a given path.
|
||||
pub fn calculate_fs_version_at_path(path: &Path) -> Option<String> {
|
||||
pub fn calculate_fs_version_at_path(path: impl AsRef<Path>) -> Option<String> {
|
||||
let metadata = fs::metadata(path).ok()?;
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
if let Ok(n) = modified.duration_since(SystemTime::UNIX_EPOCH) {
|
||||
|
@ -47,32 +33,11 @@ pub fn calculate_fs_version_at_path(path: &Path) -> Option<String> {
|
|||
}
|
||||
}
|
||||
|
||||
fn calculate_fs_version_in_cache(
|
||||
cache: &LspCache,
|
||||
specifier: &ModuleSpecifier,
|
||||
file_referrer: Option<&ModuleSpecifier>,
|
||||
) -> Option<String> {
|
||||
let http_cache = cache.for_specifier(file_referrer);
|
||||
let Ok(cache_key) = http_cache.cache_item_key(specifier) else {
|
||||
return Some("1".to_string());
|
||||
};
|
||||
match http_cache.read_modified_time(&cache_key) {
|
||||
Ok(Some(modified)) => {
|
||||
match modified.duration_since(SystemTime::UNIX_EPOCH) {
|
||||
Ok(n) => Some(n.as_millis().to_string()),
|
||||
Err(_) => Some("1".to_string()),
|
||||
}
|
||||
}
|
||||
Ok(None) => None,
|
||||
Err(_) => Some("1".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LspCache {
|
||||
deno_dir: DenoDir,
|
||||
global: Arc<GlobalHttpCache>,
|
||||
vendors_by_scope: BTreeMap<ModuleSpecifier, Option<Arc<LocalLspHttpCache>>>,
|
||||
vendors_by_scope: BTreeMap<Arc<Url>, Option<Arc<LocalLspHttpCache>>>,
|
||||
}
|
||||
|
||||
impl Default for LspCache {
|
||||
|
@ -178,11 +143,30 @@ impl LspCache {
|
|||
vendor.get_remote_url(&path)
|
||||
}
|
||||
|
||||
pub fn is_valid_file_referrer(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
if let Ok(path) = url_to_file_path(specifier) {
|
||||
if !path.starts_with(&self.deno_dir().root) {
|
||||
return true;
|
||||
}
|
||||
pub fn in_cache_directory(&self, specifier: &Url) -> bool {
|
||||
let Ok(path) = url_to_file_path(specifier) else {
|
||||
return false;
|
||||
};
|
||||
if path.starts_with(&self.deno_dir().root) {
|
||||
return true;
|
||||
}
|
||||
let Some(vendor) = self
|
||||
.vendors_by_scope
|
||||
.iter()
|
||||
.rfind(|(s, _)| specifier.as_str().starts_with(s.as_str()))
|
||||
.and_then(|(_, c)| c.as_ref())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
vendor.get_remote_url(&path).is_some()
|
||||
}
|
||||
|
||||
pub fn in_global_cache_directory(&self, specifier: &Url) -> bool {
|
||||
let Ok(path) = url_to_file_path(specifier) else {
|
||||
return false;
|
||||
};
|
||||
if path.starts_with(&self.deno_dir().root) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
|
|
@ -11,18 +11,19 @@ use deno_ast::swc::ecma_visit::VisitWith;
|
|||
use deno_ast::ParsedSource;
|
||||
use deno_ast::SourceRange;
|
||||
use deno_ast::SourceRangedForSpanned;
|
||||
use deno_core::anyhow::anyhow;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::resolve_url;
|
||||
use deno_core::serde::Deserialize;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use lazy_regex::lazy_regex;
|
||||
use lsp_types::Uri;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tower_lsp::jsonrpc::Error as LspError;
|
||||
use tower_lsp::jsonrpc::Result as LspResult;
|
||||
use tower_lsp::lsp_types as lsp;
|
||||
|
||||
use super::analysis::source_range_to_lsp_range;
|
||||
|
@ -36,7 +37,7 @@ static ABSTRACT_MODIFIER: Lazy<Regex> = lazy_regex!(r"\babstract\b");
|
|||
|
||||
static EXPORT_MODIFIER: Lazy<Regex> = lazy_regex!(r"\bexport\b");
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)]
|
||||
pub enum CodeLensSource {
|
||||
#[serde(rename = "implementations")]
|
||||
Implementations,
|
||||
|
@ -44,11 +45,11 @@ pub enum CodeLensSource {
|
|||
References,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensData {
|
||||
pub source: CodeLensSource,
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub uri: Uri,
|
||||
}
|
||||
|
||||
struct DenoTestCollector {
|
||||
|
@ -254,83 +255,61 @@ async fn resolve_implementation_code_lens(
|
|||
data: CodeLensData,
|
||||
language_server: &language_server::Inner,
|
||||
token: &CancellationToken,
|
||||
) -> Result<lsp::CodeLens, AnyError> {
|
||||
let asset_or_doc = language_server.get_asset_or_document(&data.specifier)?;
|
||||
let line_index = asset_or_doc.line_index();
|
||||
let maybe_implementations = language_server
|
||||
.ts_server
|
||||
.get_implementations(
|
||||
language_server.snapshot(),
|
||||
data.specifier.clone(),
|
||||
line_index.offset_tsc(code_lens.range.start)?,
|
||||
) -> LspResult<lsp::CodeLens> {
|
||||
let locations = language_server
|
||||
.goto_implementation(
|
||||
lsp::request::GotoImplementationParams {
|
||||
text_document_position_params: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: data.uri.clone(),
|
||||
},
|
||||
position: code_lens.range.start,
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
},
|
||||
token,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
if token.is_cancelled() {
|
||||
anyhow!("request cancelled")
|
||||
} else {
|
||||
anyhow!(
|
||||
"Unable to get implementation locations from TypeScript: {:#}",
|
||||
err
|
||||
)
|
||||
}
|
||||
})?;
|
||||
if let Some(implementations) = maybe_implementations {
|
||||
let mut locations = Vec::new();
|
||||
for implementation in implementations {
|
||||
if token.is_cancelled() {
|
||||
break;
|
||||
}
|
||||
let implementation_specifier =
|
||||
resolve_url(&implementation.document_span.file_name)?;
|
||||
let implementation_location =
|
||||
implementation.to_location(line_index.clone(), language_server);
|
||||
if !(implementation_specifier == data.specifier
|
||||
&& implementation_location.range.start == code_lens.range.start)
|
||||
{
|
||||
locations.push(implementation_location);
|
||||
}
|
||||
}
|
||||
let command = if !locations.is_empty() {
|
||||
let title = if locations.len() > 1 {
|
||||
format!("{} implementations", locations.len())
|
||||
} else {
|
||||
"1 implementation".to_string()
|
||||
};
|
||||
lsp::Command {
|
||||
title,
|
||||
command: "deno.client.showReferences".to_string(),
|
||||
arguments: Some(vec![
|
||||
json!(data.specifier),
|
||||
json!(code_lens.range.start),
|
||||
json!(locations),
|
||||
]),
|
||||
}
|
||||
} else {
|
||||
lsp::Command {
|
||||
title: "0 implementations".to_string(),
|
||||
command: "".to_string(),
|
||||
arguments: None,
|
||||
}
|
||||
};
|
||||
Ok(lsp::CodeLens {
|
||||
range: code_lens.range,
|
||||
command: Some(command),
|
||||
data: None,
|
||||
.await?
|
||||
.map(|r| match r {
|
||||
lsp::GotoDefinitionResponse::Scalar(location) => vec![location],
|
||||
lsp::GotoDefinitionResponse::Array(locations) => locations,
|
||||
lsp::GotoDefinitionResponse::Link(links) => links
|
||||
.into_iter()
|
||||
.map(|l| lsp::Location {
|
||||
uri: l.target_uri,
|
||||
range: l.target_selection_range,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.unwrap_or(Vec::new());
|
||||
let title = if locations.len() == 1 {
|
||||
"1 implementation".to_string()
|
||||
} else {
|
||||
let command = Some(lsp::Command {
|
||||
title: "0 implementations".to_string(),
|
||||
command: "".to_string(),
|
||||
format!("{} implementations", locations.len())
|
||||
};
|
||||
let command = if locations.is_empty() {
|
||||
lsp::Command {
|
||||
title,
|
||||
command: String::new(),
|
||||
arguments: None,
|
||||
});
|
||||
Ok(lsp::CodeLens {
|
||||
range: code_lens.range,
|
||||
command,
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lsp::Command {
|
||||
title,
|
||||
command: "deno.client.showReferences".to_string(),
|
||||
arguments: Some(vec![
|
||||
json!(data.uri),
|
||||
json!(code_lens.range.start),
|
||||
json!(locations),
|
||||
]),
|
||||
}
|
||||
};
|
||||
Ok(lsp::CodeLens {
|
||||
range: code_lens.range,
|
||||
command: Some(command),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
|
||||
async fn resolve_references_code_lens(
|
||||
|
@ -338,59 +317,26 @@ async fn resolve_references_code_lens(
|
|||
data: CodeLensData,
|
||||
language_server: &language_server::Inner,
|
||||
token: &CancellationToken,
|
||||
) -> Result<lsp::CodeLens, AnyError> {
|
||||
fn get_locations(
|
||||
maybe_referenced_symbols: Option<Vec<tsc::ReferencedSymbol>>,
|
||||
language_server: &language_server::Inner,
|
||||
token: &CancellationToken,
|
||||
) -> Result<Vec<lsp::Location>, AnyError> {
|
||||
let symbols = match maybe_referenced_symbols {
|
||||
Some(symbols) => symbols,
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
let mut locations = Vec::new();
|
||||
for reference in symbols.iter().flat_map(|s| &s.references) {
|
||||
if token.is_cancelled() {
|
||||
break;
|
||||
}
|
||||
if reference.is_definition {
|
||||
continue;
|
||||
}
|
||||
let reference_specifier =
|
||||
resolve_url(&reference.entry.document_span.file_name)?;
|
||||
let asset_or_doc =
|
||||
language_server.get_asset_or_document(&reference_specifier)?;
|
||||
locations.push(
|
||||
reference
|
||||
.entry
|
||||
.to_location(asset_or_doc.line_index(), language_server),
|
||||
);
|
||||
}
|
||||
Ok(locations)
|
||||
}
|
||||
|
||||
let asset_or_document =
|
||||
language_server.get_asset_or_document(&data.specifier)?;
|
||||
let line_index = asset_or_document.line_index();
|
||||
|
||||
let maybe_referenced_symbols = language_server
|
||||
.ts_server
|
||||
.find_references(
|
||||
language_server.snapshot(),
|
||||
data.specifier.clone(),
|
||||
line_index.offset_tsc(code_lens.range.start)?,
|
||||
) -> LspResult<lsp::CodeLens> {
|
||||
let locations = language_server
|
||||
.references(
|
||||
lsp::ReferenceParams {
|
||||
text_document_position: lsp::TextDocumentPositionParams {
|
||||
text_document: lsp::TextDocumentIdentifier {
|
||||
uri: data.uri.clone(),
|
||||
},
|
||||
position: code_lens.range.start,
|
||||
},
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
context: lsp::ReferenceContext {
|
||||
include_declaration: false,
|
||||
},
|
||||
},
|
||||
token,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
if token.is_cancelled() {
|
||||
anyhow!("request cancelled")
|
||||
} else {
|
||||
anyhow!("Unable to get references from TypeScript: {:#}", err)
|
||||
}
|
||||
})?;
|
||||
let locations =
|
||||
get_locations(maybe_referenced_symbols, language_server, token)?;
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
let title = if locations.len() == 1 {
|
||||
"1 reference".to_string()
|
||||
} else {
|
||||
|
@ -407,7 +353,7 @@ async fn resolve_references_code_lens(
|
|||
title,
|
||||
command: "deno.client.showReferences".to_string(),
|
||||
arguments: Some(vec![
|
||||
json!(data.specifier),
|
||||
json!(data.uri),
|
||||
json!(code_lens.range.start),
|
||||
json!(locations),
|
||||
]),
|
||||
|
@ -424,9 +370,14 @@ pub async fn resolve_code_lens(
|
|||
code_lens: lsp::CodeLens,
|
||||
language_server: &language_server::Inner,
|
||||
token: &CancellationToken,
|
||||
) -> Result<lsp::CodeLens, AnyError> {
|
||||
) -> LspResult<lsp::CodeLens> {
|
||||
let data: CodeLensData =
|
||||
serde_json::from_value(code_lens.data.clone().unwrap())?;
|
||||
serde_json::from_value(code_lens.data.clone().unwrap()).map_err(|err| {
|
||||
LspError::invalid_params(format!(
|
||||
"Unable to parse code lens data: {:#}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
match data.source {
|
||||
CodeLensSource::Implementations => {
|
||||
resolve_implementation_code_lens(code_lens, data, language_server, token)
|
||||
|
@ -453,7 +404,7 @@ pub fn collect_test(
|
|||
|
||||
/// Return tsc navigation tree code lenses.
|
||||
pub fn collect_tsc(
|
||||
specifier: &ModuleSpecifier,
|
||||
uri: &Uri,
|
||||
code_lens_settings: &CodeLensSettings,
|
||||
line_index: Arc<LineIndex>,
|
||||
navigation_tree: &NavigationTree,
|
||||
|
@ -468,11 +419,7 @@ pub fn collect_tsc(
|
|||
let source = CodeLensSource::Implementations;
|
||||
match i.kind {
|
||||
tsc::ScriptElementKind::InterfaceElement => {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
));
|
||||
code_lenses.push(i.to_code_lens(line_index.clone(), uri, source));
|
||||
}
|
||||
tsc::ScriptElementKind::ClassElement
|
||||
| tsc::ScriptElementKind::MemberFunctionElement
|
||||
|
@ -480,11 +427,7 @@ pub fn collect_tsc(
|
|||
| tsc::ScriptElementKind::MemberGetAccessorElement
|
||||
| tsc::ScriptElementKind::MemberSetAccessorElement => {
|
||||
if ABSTRACT_MODIFIER.is_match(&i.kind_modifiers) {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
));
|
||||
code_lenses.push(i.to_code_lens(line_index.clone(), uri, source));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
@ -496,51 +439,31 @@ pub fn collect_tsc(
|
|||
let source = CodeLensSource::References;
|
||||
if let Some(parent) = &mp {
|
||||
if parent.kind == tsc::ScriptElementKind::EnumElement {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
));
|
||||
code_lenses.push(i.to_code_lens(line_index.clone(), uri, source));
|
||||
}
|
||||
}
|
||||
match i.kind {
|
||||
tsc::ScriptElementKind::FunctionElement => {
|
||||
if code_lens_settings.references_all_functions {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
));
|
||||
code_lenses.push(i.to_code_lens(line_index.clone(), uri, source));
|
||||
}
|
||||
}
|
||||
tsc::ScriptElementKind::ConstElement
|
||||
| tsc::ScriptElementKind::LetElement
|
||||
| tsc::ScriptElementKind::VariableElement => {
|
||||
if EXPORT_MODIFIER.is_match(&i.kind_modifiers) {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
));
|
||||
code_lenses.push(i.to_code_lens(line_index.clone(), uri, source));
|
||||
}
|
||||
}
|
||||
tsc::ScriptElementKind::ClassElement => {
|
||||
if i.text != "<class>" {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
));
|
||||
code_lenses.push(i.to_code_lens(line_index.clone(), uri, source));
|
||||
}
|
||||
}
|
||||
tsc::ScriptElementKind::InterfaceElement
|
||||
| tsc::ScriptElementKind::TypeElement
|
||||
| tsc::ScriptElementKind::EnumElement => {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
));
|
||||
code_lenses.push(i.to_code_lens(line_index.clone(), uri, source));
|
||||
}
|
||||
tsc::ScriptElementKind::LocalFunctionElement
|
||||
| tsc::ScriptElementKind::MemberFunctionElement
|
||||
|
@ -556,8 +479,8 @@ pub fn collect_tsc(
|
|||
| tsc::ScriptElementKind::TypeElement => {
|
||||
code_lenses.push(i.to_code_lens(
|
||||
line_index.clone(),
|
||||
specifier,
|
||||
&source,
|
||||
uri,
|
||||
source,
|
||||
));
|
||||
}
|
||||
_ => (),
|
||||
|
@ -575,6 +498,7 @@ pub fn collect_tsc(
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deno_ast::MediaType;
|
||||
use deno_core::resolve_url;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -25,8 +25,9 @@ use tower_lsp::lsp_types as lsp;
|
|||
use super::client::Client;
|
||||
use super::config::Config;
|
||||
use super::config::WorkspaceSettings;
|
||||
use super::documents::Documents;
|
||||
use super::documents::DocumentsFilter;
|
||||
use super::documents::DocumentModule;
|
||||
use super::documents::DocumentModules;
|
||||
use super::documents::ServerDocumentKind;
|
||||
use super::jsr::CliJsrSearchApi;
|
||||
use super::lsp_custom;
|
||||
use super::npm::CliNpmSearchApi;
|
||||
|
@ -152,38 +153,36 @@ fn to_narrow_lsp_range(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
#[cfg_attr(feature = "lsp-tracing", tracing::instrument(skip_all))]
|
||||
pub async fn get_import_completions(
|
||||
specifier: &ModuleSpecifier,
|
||||
module: &DocumentModule,
|
||||
position: &lsp::Position,
|
||||
config: &Config,
|
||||
client: &Client,
|
||||
module_registries: &ModuleRegistry,
|
||||
jsr_search_api: &CliJsrSearchApi,
|
||||
npm_search_api: &CliNpmSearchApi,
|
||||
documents: &Documents,
|
||||
document_modules: &DocumentModules,
|
||||
resolver: &LspResolver,
|
||||
maybe_import_map: Option<&ImportMap>,
|
||||
) -> Option<lsp::CompletionResponse> {
|
||||
let document = documents.get(specifier)?;
|
||||
let file_referrer = document.file_referrer();
|
||||
let (text, _, graph_range) = document.get_maybe_dependency(position)?;
|
||||
let (text, _, graph_range) = module.dependency_at_position(position)?;
|
||||
let resolution_mode = graph_range
|
||||
.resolution_mode
|
||||
.map(to_node_resolution_mode)
|
||||
.unwrap_or_else(|| document.resolution_mode());
|
||||
let range = to_narrow_lsp_range(document.text_info(), graph_range.range);
|
||||
.unwrap_or_else(|| module.resolution_mode);
|
||||
let range = to_narrow_lsp_range(module.text_info(), graph_range.range);
|
||||
let resolved = resolver
|
||||
.as_cli_resolver(file_referrer)
|
||||
.as_cli_resolver(module.scope.as_deref())
|
||||
.resolve(
|
||||
&text,
|
||||
specifier,
|
||||
text,
|
||||
&module.specifier,
|
||||
deno_graph::Position::zeroed(),
|
||||
resolution_mode,
|
||||
NodeResolutionKind::Execution,
|
||||
)
|
||||
.ok();
|
||||
if let Some(completion_list) = get_jsr_completions(
|
||||
specifier,
|
||||
&text,
|
||||
&module.specifier,
|
||||
text,
|
||||
&range,
|
||||
resolved.as_ref(),
|
||||
jsr_search_api,
|
||||
|
@ -193,39 +192,46 @@ pub async fn get_import_completions(
|
|||
{
|
||||
Some(lsp::CompletionResponse::List(completion_list))
|
||||
} else if let Some(completion_list) =
|
||||
get_npm_completions(specifier, &text, &range, npm_search_api).await
|
||||
get_npm_completions(&module.specifier, text, &range, npm_search_api).await
|
||||
{
|
||||
Some(lsp::CompletionResponse::List(completion_list))
|
||||
} else if let Some(completion_list) = get_node_completions(&text, &range) {
|
||||
} else if let Some(completion_list) = get_node_completions(text, &range) {
|
||||
Some(lsp::CompletionResponse::List(completion_list))
|
||||
} else if let Some(completion_list) =
|
||||
get_import_map_completions(specifier, &text, &range, maybe_import_map)
|
||||
{
|
||||
} else if let Some(completion_list) = get_import_map_completions(
|
||||
&module.specifier,
|
||||
text,
|
||||
&range,
|
||||
maybe_import_map,
|
||||
) {
|
||||
// completions for import map specifiers
|
||||
Some(lsp::CompletionResponse::List(completion_list))
|
||||
} else if let Some(completion_list) =
|
||||
get_local_completions(specifier, resolution_mode, &text, &range, resolver)
|
||||
{
|
||||
} else if let Some(completion_list) = get_local_completions(
|
||||
&module.specifier,
|
||||
resolution_mode,
|
||||
text,
|
||||
&range,
|
||||
resolver,
|
||||
) {
|
||||
// completions for local relative modules
|
||||
Some(lsp::CompletionResponse::List(completion_list))
|
||||
} else if !text.is_empty() {
|
||||
// completion of modules from a module registry or cache
|
||||
check_auto_config_registry(
|
||||
&text,
|
||||
config.workspace_settings_for_specifier(specifier),
|
||||
text,
|
||||
config.workspace_settings_for_specifier(&module.specifier),
|
||||
client,
|
||||
module_registries,
|
||||
)
|
||||
.await;
|
||||
let maybe_list = module_registries
|
||||
.get_completions(&text, &range, resolved.as_ref(), |s| {
|
||||
documents.exists(s, file_referrer)
|
||||
.get_completions(text, &range, resolved.as_ref(), |s| {
|
||||
document_modules.specifier_exists(s, module.scope.as_deref())
|
||||
})
|
||||
.await;
|
||||
let maybe_list = maybe_list
|
||||
.or_else(|| module_registries.get_origin_completions(&text, &range));
|
||||
.or_else(|| module_registries.get_origin_completions(text, &range));
|
||||
let list = maybe_list.unwrap_or_else(|| CompletionList {
|
||||
items: get_workspace_completions(specifier, &text, &range, documents),
|
||||
items: get_remote_completions(module, text, &range, document_modules),
|
||||
is_incomplete: false,
|
||||
});
|
||||
Some(lsp::CompletionResponse::List(list))
|
||||
|
@ -248,10 +254,13 @@ pub async fn get_import_completions(
|
|||
.collect();
|
||||
let mut is_incomplete = false;
|
||||
if let Some(import_map) = maybe_import_map {
|
||||
items.extend(get_base_import_map_completions(import_map, specifier));
|
||||
items.extend(get_base_import_map_completions(
|
||||
import_map,
|
||||
&module.specifier,
|
||||
));
|
||||
}
|
||||
if let Some(origin_items) =
|
||||
module_registries.get_origin_completions(&text, &range)
|
||||
module_registries.get_origin_completions(text, &range)
|
||||
{
|
||||
is_incomplete = origin_items.is_incomplete;
|
||||
items.extend(origin_items.items);
|
||||
|
@ -440,22 +449,6 @@ fn get_local_completions(
|
|||
}
|
||||
}
|
||||
|
||||
fn get_relative_specifiers(
|
||||
base: &ModuleSpecifier,
|
||||
specifiers: Vec<ModuleSpecifier>,
|
||||
) -> Vec<String> {
|
||||
specifiers
|
||||
.iter()
|
||||
.filter_map(|s| {
|
||||
if s != base {
|
||||
Some(relative_specifier(base, s).unwrap_or_else(|| s.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Find the index of the '@' delimiting the package name and version, if any.
|
||||
fn parse_bare_specifier_version_index(bare_specifier: &str) -> Option<usize> {
|
||||
if bare_specifier.starts_with('@') {
|
||||
|
@ -770,34 +763,33 @@ fn get_node_completions(
|
|||
})
|
||||
}
|
||||
|
||||
/// Get workspace completions that include modules in the Deno cache which match
|
||||
/// Get remote completions that include modules in the Deno cache which match
|
||||
/// the current specifier string.
|
||||
fn get_workspace_completions(
|
||||
specifier: &ModuleSpecifier,
|
||||
fn get_remote_completions(
|
||||
module: &DocumentModule,
|
||||
current: &str,
|
||||
range: &lsp::Range,
|
||||
documents: &Documents,
|
||||
document_modules: &DocumentModules,
|
||||
) -> Vec<lsp::CompletionItem> {
|
||||
let workspace_specifiers = documents
|
||||
.documents(DocumentsFilter::AllDiagnosable)
|
||||
.into_iter()
|
||||
.map(|d| d.specifier().clone())
|
||||
.collect();
|
||||
let specifier_strings =
|
||||
get_relative_specifiers(specifier, workspace_specifiers);
|
||||
specifier_strings
|
||||
let specifiers = document_modules
|
||||
.documents
|
||||
.server_docs()
|
||||
.into_iter()
|
||||
.filter_map(|d| {
|
||||
if let ServerDocumentKind::RemoteUrl { url, .. } = &d.kind {
|
||||
if *url == module.specifier {
|
||||
return None;
|
||||
}
|
||||
return Some(
|
||||
relative_specifier(&module.specifier, url)
|
||||
.unwrap_or_else(|| url.to_string()),
|
||||
);
|
||||
}
|
||||
None
|
||||
});
|
||||
specifiers
|
||||
.filter_map(|label| {
|
||||
if label.starts_with(current) {
|
||||
let detail = Some(
|
||||
if label.starts_with("http:") || label.starts_with("https:") {
|
||||
"(remote)".to_string()
|
||||
} else if label.starts_with("data:") {
|
||||
"(data)".to_string()
|
||||
} else {
|
||||
"(local)".to_string()
|
||||
},
|
||||
);
|
||||
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: *range,
|
||||
new_text: label.clone(),
|
||||
|
@ -805,7 +797,7 @@ fn get_workspace_completions(
|
|||
Some(lsp::CompletionItem {
|
||||
label,
|
||||
kind: Some(lsp::CompletionItemKind::FILE),
|
||||
detail,
|
||||
detail: Some("(remote)".to_string()),
|
||||
sort_text: Some("1".to_string()),
|
||||
text_edit,
|
||||
commit_characters: Some(
|
||||
|
@ -831,18 +823,18 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::cache::HttpCache;
|
||||
use crate::lsp::cache::LspCache;
|
||||
use crate::lsp::documents::Documents;
|
||||
use crate::lsp::documents::LanguageId;
|
||||
use crate::lsp::search::tests::TestPackageSearchApi;
|
||||
use crate::lsp::urls::url_to_uri;
|
||||
|
||||
fn setup(
|
||||
open_sources: &[(&str, &str, i32, LanguageId)],
|
||||
fs_sources: &[(&str, &str)],
|
||||
) -> Documents {
|
||||
) -> DocumentModules {
|
||||
let temp_dir = TempDir::new();
|
||||
let cache = LspCache::new(Some(temp_dir.url().join(".deno_dir").unwrap()));
|
||||
let mut documents = Documents::default();
|
||||
documents.update_config(
|
||||
let mut document_modules = DocumentModules::default();
|
||||
document_modules.update_config(
|
||||
&Default::default(),
|
||||
&Default::default(),
|
||||
&cache,
|
||||
|
@ -851,7 +843,13 @@ mod tests {
|
|||
for (specifier, source, version, language_id) in open_sources {
|
||||
let specifier =
|
||||
resolve_url(specifier).expect("failed to create specifier");
|
||||
documents.open(specifier, *version, *language_id, (*source).into(), None);
|
||||
let uri = url_to_uri(&specifier).unwrap();
|
||||
document_modules.open_document(
|
||||
uri,
|
||||
*version,
|
||||
*language_id,
|
||||
(*source).into(),
|
||||
);
|
||||
}
|
||||
for (specifier, source) in fs_sources {
|
||||
let specifier =
|
||||
|
@ -860,32 +858,10 @@ mod tests {
|
|||
.global()
|
||||
.set(&specifier, HashMap::default(), source.as_bytes())
|
||||
.expect("could not cache file");
|
||||
let document = documents
|
||||
.get_or_load(&specifier, Some(&temp_dir.url().join("$").unwrap()));
|
||||
assert!(document.is_some(), "source could not be setup");
|
||||
let module = document_modules.module_for_specifier(&specifier, None);
|
||||
assert!(module.is_some(), "source could not be setup");
|
||||
}
|
||||
documents
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_relative_specifiers() {
|
||||
let base = resolve_url("file:///a/b/c.ts").unwrap();
|
||||
let specifiers = vec![
|
||||
resolve_url("file:///a/b/c.ts").unwrap(),
|
||||
resolve_url("file:///a/b/d.ts").unwrap(),
|
||||
resolve_url("file:///a/c/c.ts").unwrap(),
|
||||
resolve_url("file:///a/b/d/d.ts").unwrap(),
|
||||
resolve_url("https://deno.land/x/a/b/c.ts").unwrap(),
|
||||
];
|
||||
assert_eq!(
|
||||
get_relative_specifiers(&base, specifiers),
|
||||
vec![
|
||||
"./d.ts".to_string(),
|
||||
"../c/c.ts".to_string(),
|
||||
"./d/d.ts".to_string(),
|
||||
"https://deno.land/x/a/b/c.ts".to_string(),
|
||||
]
|
||||
);
|
||||
document_modules
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -940,7 +916,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_workspace_completions() {
|
||||
async fn test_get_remote_completions() {
|
||||
let specifier = resolve_url("file:///a/b/c.ts").unwrap();
|
||||
let range = lsp::Range {
|
||||
start: lsp::Position {
|
||||
|
@ -952,7 +928,7 @@ mod tests {
|
|||
character: 21,
|
||||
},
|
||||
};
|
||||
let documents = setup(
|
||||
let document_modules = setup(
|
||||
&[
|
||||
(
|
||||
"file:///a/b/c.ts",
|
||||
|
@ -964,7 +940,11 @@ mod tests {
|
|||
],
|
||||
&[("https://deno.land/x/a/b/c.ts", "console.log(1);\n")],
|
||||
);
|
||||
let actual = get_workspace_completions(&specifier, "h", &range, &documents);
|
||||
let module = document_modules
|
||||
.module_for_specifier(&specifier, None)
|
||||
.unwrap();
|
||||
let actual =
|
||||
get_remote_completions(&module, "h", &range, &document_modules);
|
||||
assert_eq!(
|
||||
actual,
|
||||
vec![lsp::CompletionItem {
|
||||
|
|
|
@ -54,16 +54,19 @@ use deno_resolver::workspace::WorkspaceResolver;
|
|||
use deno_runtime::deno_node::PackageJson;
|
||||
use indexmap::IndexSet;
|
||||
use lsp_types::ClientCapabilities;
|
||||
use lsp_types::Uri;
|
||||
use tower_lsp::lsp_types as lsp;
|
||||
|
||||
use super::logging::lsp_log;
|
||||
use super::lsp_custom;
|
||||
use super::urls::uri_to_url;
|
||||
use super::urls::url_to_uri;
|
||||
use crate::args::CliLockfile;
|
||||
use crate::args::CliLockfileReadFromPathOptions;
|
||||
use crate::args::ConfigFile;
|
||||
use crate::args::LintFlags;
|
||||
use crate::args::LintOptions;
|
||||
use crate::cache::DenoDir;
|
||||
use crate::file_fetcher::CliFileFetcher;
|
||||
use crate::lsp::logging::lsp_warn;
|
||||
use crate::sys::CliSys;
|
||||
|
@ -821,20 +824,13 @@ impl WorkspaceSettings {
|
|||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Settings {
|
||||
pub unscoped: Arc<WorkspaceSettings>,
|
||||
pub by_workspace_folder:
|
||||
BTreeMap<ModuleSpecifier, Option<Arc<WorkspaceSettings>>>,
|
||||
pub first_folder: Option<ModuleSpecifier>,
|
||||
pub by_workspace_folder: BTreeMap<Arc<Url>, Option<Arc<WorkspaceSettings>>>,
|
||||
pub first_folder: Option<Arc<Url>>,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Returns `None` if the value should be deferred to the presence of a
|
||||
/// `deno.json` file.
|
||||
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> Option<bool> {
|
||||
let Ok(path) = url_to_file_path(specifier) else {
|
||||
// Non-file URLs are not disabled by these settings.
|
||||
return Some(true);
|
||||
};
|
||||
let (settings, mut folder_uri) = self.get_for_specifier(specifier);
|
||||
pub fn path_enabled(&self, path: &Path) -> Option<bool> {
|
||||
let (settings, mut folder_uri) = self.get_for_path(path);
|
||||
folder_uri = folder_uri.or(self.first_folder.as_ref());
|
||||
let mut disable_paths = vec![];
|
||||
let mut enable_paths = None;
|
||||
|
@ -859,7 +855,7 @@ impl Settings {
|
|||
} else if let Some(enable_paths) = &enable_paths {
|
||||
for enable_path in enable_paths {
|
||||
// Also enable if the checked path is a dir containing an enabled path.
|
||||
if path.starts_with(enable_path) || enable_path.starts_with(&path) {
|
||||
if path.starts_with(enable_path) || enable_path.starts_with(path) {
|
||||
return Some(true);
|
||||
}
|
||||
}
|
||||
|
@ -869,17 +865,24 @@ impl Settings {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `None` if the value should be deferred to the presence of a
|
||||
/// `deno.json` file.
|
||||
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> Option<bool> {
|
||||
let Ok(path) = url_to_file_path(specifier) else {
|
||||
// Non-file URLs are not disabled by these settings.
|
||||
return Some(true);
|
||||
};
|
||||
self.path_enabled(&path)
|
||||
}
|
||||
|
||||
pub fn get_unscoped(&self) -> &WorkspaceSettings {
|
||||
&self.unscoped
|
||||
}
|
||||
|
||||
pub fn get_for_specifier(
|
||||
pub fn get_for_path(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> (&WorkspaceSettings, Option<&ModuleSpecifier>) {
|
||||
let Ok(path) = url_to_file_path(specifier) else {
|
||||
return (&self.unscoped, self.first_folder.as_ref());
|
||||
};
|
||||
path: &Path,
|
||||
) -> (&WorkspaceSettings, Option<&Arc<Url>>) {
|
||||
for (folder_uri, settings) in self.by_workspace_folder.iter().rev() {
|
||||
if let Some(settings) = settings {
|
||||
let Ok(folder_path) = url_to_file_path(folder_uri) else {
|
||||
|
@ -893,6 +896,23 @@ impl Settings {
|
|||
(&self.unscoped, self.first_folder.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_for_uri(
|
||||
&self,
|
||||
uri: &Uri,
|
||||
) -> (&WorkspaceSettings, Option<&Arc<Url>>) {
|
||||
self.get_for_specifier(&uri_to_url(uri))
|
||||
}
|
||||
|
||||
pub fn get_for_specifier(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> (&WorkspaceSettings, Option<&Arc<Url>>) {
|
||||
let Ok(path) = url_to_file_path(specifier) else {
|
||||
return (&self.unscoped, self.first_folder.as_ref());
|
||||
};
|
||||
self.get_for_path(&path)
|
||||
}
|
||||
|
||||
pub fn enable_settings_hash(&self) -> u64 {
|
||||
let mut hasher = FastInsecureHasher::new_without_deno_version();
|
||||
let unscoped = self.get_unscoped();
|
||||
|
@ -917,7 +937,7 @@ impl Settings {
|
|||
pub struct Config {
|
||||
pub client_capabilities: Arc<ClientCapabilities>,
|
||||
pub settings: Arc<Settings>,
|
||||
pub workspace_folders: Arc<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>,
|
||||
pub workspace_folders: Arc<Vec<(Arc<Url>, lsp::WorkspaceFolder)>>,
|
||||
pub tree: ConfigTree,
|
||||
}
|
||||
|
||||
|
@ -933,7 +953,7 @@ impl Config {
|
|||
let name = root_url.path_segments().and_then(|s| s.last());
|
||||
let name = name.unwrap_or_default().to_string();
|
||||
folders.push((
|
||||
root_url,
|
||||
Arc::new(root_url),
|
||||
lsp::WorkspaceFolder {
|
||||
uri: root_uri,
|
||||
name,
|
||||
|
@ -946,7 +966,7 @@ impl Config {
|
|||
|
||||
pub fn set_workspace_folders(
|
||||
&mut self,
|
||||
folders: Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>,
|
||||
folders: Vec<(Arc<Url>, lsp::WorkspaceFolder)>,
|
||||
) {
|
||||
self.settings = Arc::new(Settings {
|
||||
unscoped: self.settings.unscoped.clone(),
|
||||
|
@ -962,7 +982,7 @@ impl Config {
|
|||
pub fn set_workspace_settings(
|
||||
&mut self,
|
||||
unscoped: WorkspaceSettings,
|
||||
folder_settings: Vec<(ModuleSpecifier, WorkspaceSettings)>,
|
||||
folder_settings: Vec<(Arc<Url>, WorkspaceSettings)>,
|
||||
) {
|
||||
let mut by_folder = folder_settings.into_iter().collect::<HashMap<_, _>>();
|
||||
self.settings = Arc::new(Settings {
|
||||
|
@ -981,6 +1001,10 @@ impl Config {
|
|||
self.settings.get_unscoped()
|
||||
}
|
||||
|
||||
pub fn workspace_settings_for_uri(&self, uri: &Uri) -> &WorkspaceSettings {
|
||||
self.settings.get_for_uri(uri).0
|
||||
}
|
||||
|
||||
pub fn workspace_settings_for_specifier(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
|
@ -1034,15 +1058,32 @@ impl Config {
|
|||
|| settings.inlay_hints.enum_member_values.enabled
|
||||
}
|
||||
|
||||
pub fn root_uri(&self) -> Option<&Url> {
|
||||
pub fn root_url(&self) -> Option<&Arc<Url>> {
|
||||
self.workspace_folders.first().map(|p| &p.0)
|
||||
}
|
||||
|
||||
pub fn uri_enabled(&self, uri: &Uri) -> bool {
|
||||
if uri.scheme().is_some_and(|s| s.eq_lowercase("deno")) {
|
||||
return true;
|
||||
}
|
||||
self.specifier_enabled(&uri_to_url(uri))
|
||||
}
|
||||
|
||||
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
if self.tree.in_global_npm_cache(specifier) {
|
||||
return true;
|
||||
}
|
||||
let data = self.tree.data_for_specifier(specifier);
|
||||
if let Some(data) = &data {
|
||||
if let Ok(path) = specifier.to_file_path() {
|
||||
if data.exclude_files.matches_path(&path) {
|
||||
// deno_config's exclusion checks exclude vendor dirs invariably. We
|
||||
// don't want that behavior here.
|
||||
if data.exclude_files.matches_path(&path)
|
||||
&& !data
|
||||
.vendor_dir
|
||||
.as_ref()
|
||||
.is_some_and(|p| path.starts_with(p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1221,7 +1262,7 @@ impl ConfigData {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
async fn load(
|
||||
specified_config: Option<&Path>,
|
||||
scope: &ModuleSpecifier,
|
||||
scope: &Arc<Url>,
|
||||
settings: &Settings,
|
||||
file_fetcher: &Arc<CliFileFetcher>,
|
||||
// sync requirement is because the lsp requires sync
|
||||
|
@ -1229,7 +1270,7 @@ impl ConfigData {
|
|||
pkg_json_cache: &(dyn PackageJsonCache + Sync),
|
||||
workspace_cache: &(dyn WorkspaceCache + Sync),
|
||||
) -> Self {
|
||||
let scope = Arc::new(scope.clone());
|
||||
let scope = scope.clone();
|
||||
let discover_result = match scope.to_file_path() {
|
||||
Ok(scope_dir_path) => {
|
||||
let paths = [scope_dir_path];
|
||||
|
@ -1723,14 +1764,12 @@ impl ConfigData {
|
|||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ConfigTree {
|
||||
scopes: Arc<BTreeMap<ModuleSpecifier, Arc<ConfigData>>>,
|
||||
scopes: Arc<BTreeMap<Arc<Url>, Arc<ConfigData>>>,
|
||||
global_npm_cache_url: Option<Arc<Url>>,
|
||||
}
|
||||
|
||||
impl ConfigTree {
|
||||
pub fn scope_for_specifier(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<&ModuleSpecifier> {
|
||||
pub fn scope_for_specifier(&self, specifier: &Url) -> Option<&Arc<Url>> {
|
||||
self
|
||||
.scopes
|
||||
.iter()
|
||||
|
@ -1747,15 +1786,13 @@ impl ConfigTree {
|
|||
.and_then(|s| self.scopes.get(s))
|
||||
}
|
||||
|
||||
pub fn data_by_scope(
|
||||
&self,
|
||||
) -> &Arc<BTreeMap<ModuleSpecifier, Arc<ConfigData>>> {
|
||||
pub fn data_by_scope(&self) -> &Arc<BTreeMap<Arc<Url>, Arc<ConfigData>>> {
|
||||
&self.scopes
|
||||
}
|
||||
|
||||
pub fn workspace_dir_for_specifier(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
specifier: &Url,
|
||||
) -> Option<&WorkspaceDirectory> {
|
||||
self
|
||||
.data_for_specifier(specifier)
|
||||
|
@ -1778,10 +1815,7 @@ impl ConfigTree {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn fmt_config_for_specifier(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Arc<FmtConfig> {
|
||||
pub fn fmt_config_for_specifier(&self, specifier: &Url) -> Arc<FmtConfig> {
|
||||
self
|
||||
.data_for_specifier(specifier)
|
||||
.map(|d| d.fmt_config.clone())
|
||||
|
@ -1791,8 +1825,8 @@ impl ConfigTree {
|
|||
/// Returns (scope_url, type).
|
||||
pub fn watched_file_type(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Option<(&ModuleSpecifier, ConfigWatchedFileType)> {
|
||||
specifier: &Url,
|
||||
) -> Option<(&Arc<Url>, ConfigWatchedFileType)> {
|
||||
for (scope_url, data) in self.scopes.iter() {
|
||||
if let Some(typ) = data.watched_files.get(specifier) {
|
||||
return Some((scope_url, *typ));
|
||||
|
@ -1801,7 +1835,7 @@ impl ConfigTree {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn is_watched_file(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
pub fn is_watched_file(&self, specifier: &Url) -> bool {
|
||||
let path = specifier.path();
|
||||
if path.ends_with("/deno.json")
|
||||
|| path.ends_with("/deno.jsonc")
|
||||
|
@ -1856,14 +1890,29 @@ impl ConfigTree {
|
|||
})
|
||||
})
|
||||
.collect();
|
||||
lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams { data }
|
||||
let deno_dir_npm_folder_uri = self
|
||||
.global_npm_cache_url
|
||||
.as_ref()
|
||||
.and_then(|s| url_to_uri(s).ok());
|
||||
lsp_custom::DidRefreshDenoConfigurationTreeNotificationParams {
|
||||
data,
|
||||
deno_dir_npm_folder_uri,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn in_global_npm_cache(&self, url: &Url) -> bool {
|
||||
self
|
||||
.global_npm_cache_url
|
||||
.as_ref()
|
||||
.is_some_and(|s| url.as_str().starts_with(s.as_str()))
|
||||
}
|
||||
|
||||
pub async fn refresh(
|
||||
&mut self,
|
||||
settings: &Settings,
|
||||
workspace_files: &IndexSet<ModuleSpecifier>,
|
||||
workspace_files: &IndexSet<PathBuf>,
|
||||
file_fetcher: &Arc<CliFileFetcher>,
|
||||
deno_dir: &DenoDir,
|
||||
) {
|
||||
lsp_log!("Refreshing configuration tree...");
|
||||
// since we're resolving a workspace multiple times in different
|
||||
|
@ -1873,24 +1922,24 @@ impl ConfigTree {
|
|||
let pkg_json_cache = PackageJsonMemCache::default();
|
||||
let workspace_cache = WorkspaceMemCache::default();
|
||||
let mut scopes = BTreeMap::new();
|
||||
for (folder_uri, ws_settings) in &settings.by_workspace_folder {
|
||||
for (folder_url, ws_settings) in &settings.by_workspace_folder {
|
||||
let mut ws_settings = ws_settings.as_ref();
|
||||
if Some(folder_uri) == settings.first_folder.as_ref() {
|
||||
if Some(folder_url) == settings.first_folder.as_ref() {
|
||||
ws_settings = ws_settings.or(Some(&settings.unscoped));
|
||||
}
|
||||
if let Some(ws_settings) = ws_settings {
|
||||
let config_file_path = (|| {
|
||||
let config_setting = ws_settings.config.as_ref()?;
|
||||
let config_uri = folder_uri.join(config_setting).ok()?;
|
||||
let config_uri = folder_url.join(config_setting).ok()?;
|
||||
url_to_file_path(&config_uri).ok()
|
||||
})();
|
||||
if config_file_path.is_some() || ws_settings.import_map.is_some() {
|
||||
scopes.insert(
|
||||
folder_uri.clone(),
|
||||
folder_url.clone(),
|
||||
Arc::new(
|
||||
ConfigData::load(
|
||||
config_file_path.as_deref(),
|
||||
folder_uri,
|
||||
folder_url,
|
||||
settings,
|
||||
file_fetcher,
|
||||
&deno_json_cache,
|
||||
|
@ -1904,14 +1953,17 @@ impl ConfigTree {
|
|||
}
|
||||
}
|
||||
|
||||
for specifier in workspace_files {
|
||||
if !(specifier.path().ends_with("/deno.json")
|
||||
|| specifier.path().ends_with("/deno.jsonc")
|
||||
|| specifier.path().ends_with("/package.json"))
|
||||
for path in workspace_files {
|
||||
let Ok(file_url) = Url::from_file_path(path) else {
|
||||
continue;
|
||||
};
|
||||
if !(file_url.path().ends_with("/deno.json")
|
||||
|| file_url.path().ends_with("/deno.jsonc")
|
||||
|| file_url.path().ends_with("/package.json"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let Ok(scope) = specifier.join(".") else {
|
||||
let Ok(scope) = file_url.join(".").map(Arc::new) else {
|
||||
continue;
|
||||
};
|
||||
if scopes.contains_key(&scope) {
|
||||
|
@ -1944,11 +1996,15 @@ impl ConfigTree {
|
|||
&workspace_cache,
|
||||
)
|
||||
.await;
|
||||
scopes.insert(member_scope.as_ref().clone(), Arc::new(member_data));
|
||||
scopes.insert(member_scope.clone(), Arc::new(member_data));
|
||||
}
|
||||
}
|
||||
|
||||
self.scopes = Arc::new(scopes);
|
||||
self.global_npm_cache_url =
|
||||
Url::from_directory_path(deno_dir.npm_folder_path())
|
||||
.ok()
|
||||
.map(Arc::new);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1956,7 +2012,7 @@ impl ConfigTree {
|
|||
use sys_traits::FsCreateDirAll;
|
||||
use sys_traits::FsWrite;
|
||||
|
||||
let scope = config_file.specifier.join(".").unwrap();
|
||||
let scope = Arc::new(config_file.specifier.join(".").unwrap());
|
||||
let json_text = serde_json::to_string(&config_file.json).unwrap();
|
||||
let memory_sys = sys_traits::impls::InMemorySys::default();
|
||||
let config_path = url_to_file_path(&config_file.specifier).unwrap();
|
||||
|
@ -1979,7 +2035,7 @@ impl ConfigTree {
|
|||
let data = Arc::new(
|
||||
ConfigData::load_inner(
|
||||
workspace_dir,
|
||||
Arc::new(scope.clone()),
|
||||
scope.clone(),
|
||||
&Default::default(),
|
||||
None,
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
3078
cli/lsp/documents.rs
3078
cli/lsp/documents.rs
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -57,6 +57,7 @@ pub struct DenoConfigurationData {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DidRefreshDenoConfigurationTreeNotificationParams {
|
||||
pub data: Vec<DenoConfigurationData>,
|
||||
pub deno_dir_npm_folder_uri: Option<lsp::Uri>,
|
||||
}
|
||||
|
||||
pub enum DidRefreshDenoConfigurationTreeNotification {}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
use deno_core::serde::Deserialize;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use lsp_types::Uri;
|
||||
use once_cell::sync::Lazy;
|
||||
use tower_lsp::lsp_types as lsp;
|
||||
|
||||
|
@ -150,7 +150,7 @@ pub static ALL_KNOWN_REFACTOR_ACTION_KINDS: Lazy<
|
|||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RefactorCodeActionData {
|
||||
pub specifier: ModuleSpecifier,
|
||||
pub uri: Uri,
|
||||
pub range: lsp::Range,
|
||||
pub refactor_name: String,
|
||||
pub action_name: String,
|
||||
|
|
|
@ -299,7 +299,7 @@ impl LspScopeResolver {
|
|||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LspResolver {
|
||||
unscoped: Arc<LspScopeResolver>,
|
||||
by_scope: BTreeMap<ModuleSpecifier, Arc<LspScopeResolver>>,
|
||||
by_scope: BTreeMap<Arc<Url>, Arc<LspScopeResolver>>,
|
||||
}
|
||||
|
||||
impl LspResolver {
|
||||
|
@ -357,9 +357,7 @@ impl LspResolver {
|
|||
|
||||
pub async fn set_dep_info_by_scope(
|
||||
&self,
|
||||
dep_info_by_scope: &Arc<
|
||||
BTreeMap<Option<ModuleSpecifier>, Arc<ScopeDepInfo>>,
|
||||
>,
|
||||
dep_info_by_scope: &Arc<BTreeMap<Option<Arc<Url>>, Arc<ScopeDepInfo>>>,
|
||||
) {
|
||||
for (scope, resolver) in [(None, &self.unscoped)]
|
||||
.into_iter()
|
||||
|
|
|
@ -9,6 +9,7 @@ use deno_core::error::AnyError;
|
|||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_runtime::tokio_util::create_basic_runtime;
|
||||
use tokio::sync::mpsc;
|
||||
|
@ -22,7 +23,6 @@ use super::lsp_custom;
|
|||
use crate::lsp::client::Client;
|
||||
use crate::lsp::client::TestingNotification;
|
||||
use crate::lsp::config;
|
||||
use crate::lsp::documents::DocumentsFilter;
|
||||
use crate::lsp::language_server::StateSnapshot;
|
||||
use crate::lsp::performance::Performance;
|
||||
use crate::lsp::urls::url_to_uri;
|
||||
|
@ -63,7 +63,7 @@ impl TestServer {
|
|||
pub fn new(
|
||||
client: Client,
|
||||
performance: Arc<Performance>,
|
||||
maybe_root_uri: Option<ModuleSpecifier>,
|
||||
maybe_root_url: Option<Arc<Url>>,
|
||||
) -> Self {
|
||||
let tests = Default::default();
|
||||
|
||||
|
@ -83,7 +83,7 @@ impl TestServer {
|
|||
let tests = server.tests.clone();
|
||||
let client = server.client.clone();
|
||||
let performance = server.performance.clone();
|
||||
let mru = maybe_root_uri.clone();
|
||||
let mru = maybe_root_url.clone();
|
||||
let _update_join_handle = thread::spawn(move || {
|
||||
let runtime = create_basic_runtime();
|
||||
|
||||
|
@ -99,47 +99,62 @@ impl TestServer {
|
|||
let mut keys: HashSet<ModuleSpecifier> =
|
||||
tests.keys().cloned().collect();
|
||||
for document in snapshot
|
||||
.document_modules
|
||||
.documents
|
||||
.documents(DocumentsFilter::AllDiagnosable)
|
||||
.filtered_docs(|d| d.is_file_like() && d.is_diagnosable())
|
||||
{
|
||||
let specifier = document.specifier();
|
||||
if specifier.scheme() != "file" {
|
||||
let Some(module) =
|
||||
snapshot.document_modules.primary_module(&document)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if module.specifier.scheme() != "file" {
|
||||
continue;
|
||||
}
|
||||
if !snapshot.config.specifier_enabled_for_test(specifier) {
|
||||
if !snapshot
|
||||
.config
|
||||
.specifier_enabled_for_test(&module.specifier)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
keys.remove(specifier);
|
||||
keys.remove(&module.specifier);
|
||||
let script_version = document.script_version();
|
||||
let valid =
|
||||
if let Some((_, old_script_version)) = tests.get(specifier) {
|
||||
old_script_version == &script_version
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let valid = if let Some((_, old_script_version)) =
|
||||
tests.get(&module.specifier)
|
||||
{
|
||||
old_script_version == &script_version
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !valid {
|
||||
let was_empty = tests
|
||||
.remove(specifier)
|
||||
.remove(&module.specifier)
|
||||
.map(|(tm, _)| tm.is_empty())
|
||||
.unwrap_or(true);
|
||||
let test_module = document
|
||||
.maybe_test_module()
|
||||
let test_module = module
|
||||
.test_module()
|
||||
.await
|
||||
.map(|tm| tm.as_ref().clone())
|
||||
.unwrap_or_else(|| TestModule::new(specifier.clone()));
|
||||
.unwrap_or_else(|| {
|
||||
TestModule::new(module.specifier.as_ref().clone())
|
||||
});
|
||||
if !test_module.is_empty() {
|
||||
if let Ok(params) =
|
||||
test_module.as_replace_notification(mru.as_ref())
|
||||
test_module.as_replace_notification(mru.as_deref())
|
||||
{
|
||||
client.send_test_notification(params);
|
||||
}
|
||||
} else if !was_empty {
|
||||
if let Ok(params) = as_delete_notification(specifier) {
|
||||
if let Ok(params) =
|
||||
as_delete_notification(&module.specifier)
|
||||
{
|
||||
client.send_test_notification(params);
|
||||
}
|
||||
}
|
||||
tests
|
||||
.insert(specifier.clone(), (test_module, script_version));
|
||||
tests.insert(
|
||||
module.specifier.as_ref().clone(),
|
||||
(test_module, script_version),
|
||||
);
|
||||
}
|
||||
}
|
||||
for key in &keys {
|
||||
|
@ -169,7 +184,7 @@ impl TestServer {
|
|||
runs.get(&id).cloned()
|
||||
};
|
||||
if let Some(run) = maybe_run {
|
||||
match run.exec(&client, maybe_root_uri.as_ref()).await {
|
||||
match run.exec(&client, maybe_root_url.as_deref()).await {
|
||||
Ok(_) => (),
|
||||
Err(err) => {
|
||||
client.show_message(lsp::MessageType::ERROR, err);
|
||||
|
|
1692
cli/lsp/tsc.rs
1692
cli/lsp/tsc.rs
File diff suppressed because it is too large
Load diff
436
cli/lsp/urls.rs
436
cli/lsp/urls.rs
|
@ -1,36 +1,21 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use deno_ast::MediaType;
|
||||
use deno_config::UrlToFilePathError;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::parking_lot::Mutex;
|
||||
use deno_core::url::Position;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_path_util::url_to_file_path;
|
||||
use lsp_types::Uri;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::cache::LspCache;
|
||||
use super::logging::lsp_warn;
|
||||
|
||||
/// Used in situations where a default URL needs to be used where otherwise a
|
||||
/// panic is undesired.
|
||||
pub static INVALID_SPECIFIER: Lazy<ModuleSpecifier> =
|
||||
Lazy::new(|| ModuleSpecifier::parse("deno://invalid").unwrap());
|
||||
|
||||
/// Used in situations where a default URL needs to be used where otherwise a
|
||||
/// panic is undesired.
|
||||
pub static INVALID_URI: Lazy<Uri> =
|
||||
Lazy::new(|| Uri::from_str("deno://invalid").unwrap());
|
||||
|
||||
/// Matches the `encodeURIComponent()` encoding from JavaScript, which matches
|
||||
/// the component percent encoding set.
|
||||
///
|
||||
/// See: <https://url.spec.whatwg.org/#component-percent-encode-set>
|
||||
const COMPONENT: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
|
||||
pub const COMPONENT: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'"')
|
||||
.add(b'#')
|
||||
|
@ -56,10 +41,24 @@ const COMPONENT: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
|
|||
.add(b'+')
|
||||
.add(b',');
|
||||
|
||||
/// Characters that may be left unencoded in a `Url` path but not valid in a
|
||||
/// `Uri` path.
|
||||
/// Characters that are left unencoded in a `Url` path but will be encoded in a
|
||||
/// VSCode URI.
|
||||
const URL_TO_URI_PATH: &percent_encoding::AsciiSet =
|
||||
&percent_encoding::CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'!')
|
||||
.add(b'$')
|
||||
.add(b'&')
|
||||
.add(b'\'')
|
||||
.add(b'(')
|
||||
.add(b')')
|
||||
.add(b'*')
|
||||
.add(b'+')
|
||||
.add(b',')
|
||||
.add(b':')
|
||||
.add(b';')
|
||||
.add(b'=')
|
||||
.add(b'@')
|
||||
.add(b'[')
|
||||
.add(b']')
|
||||
.add(b'^')
|
||||
|
@ -75,73 +74,6 @@ const URL_TO_URI_QUERY: &percent_encoding::AsciiSet =
|
|||
const URL_TO_URI_FRAGMENT: &percent_encoding::AsciiSet =
|
||||
&URL_TO_URI_PATH.add(b'#').add(b'\\').add(b'{').add(b'}');
|
||||
|
||||
fn hash_data_specifier(specifier: &ModuleSpecifier) -> String {
|
||||
let mut file_name_str = specifier.path().to_string();
|
||||
if let Some(query) = specifier.query() {
|
||||
file_name_str.push('?');
|
||||
file_name_str.push_str(query);
|
||||
}
|
||||
deno_lib::util::checksum::gen(&[file_name_str.as_bytes()])
|
||||
}
|
||||
|
||||
fn to_deno_uri(specifier: &Url) -> String {
|
||||
let mut string = String::with_capacity(specifier.as_str().len() + 6);
|
||||
string.push_str("deno:/");
|
||||
string.push_str(specifier.scheme());
|
||||
for p in specifier[Position::BeforeHost..].split('/') {
|
||||
string.push('/');
|
||||
string.push_str(
|
||||
&percent_encoding::utf8_percent_encode(p, COMPONENT).to_string(),
|
||||
);
|
||||
}
|
||||
string
|
||||
}
|
||||
|
||||
fn from_deno_url(url: &Url) -> Option<Url> {
|
||||
if url.scheme() != "deno" {
|
||||
return None;
|
||||
}
|
||||
let mut segments = url.path_segments()?;
|
||||
let mut string = String::with_capacity(url.as_str().len());
|
||||
string.push_str(segments.next()?);
|
||||
string.push_str("://");
|
||||
string.push_str(
|
||||
&percent_encoding::percent_decode(segments.next()?.as_bytes())
|
||||
.decode_utf8()
|
||||
.ok()?,
|
||||
);
|
||||
for segment in segments {
|
||||
string.push('/');
|
||||
string.push_str(
|
||||
&percent_encoding::percent_decode(segment.as_bytes())
|
||||
.decode_utf8()
|
||||
.ok()?,
|
||||
);
|
||||
}
|
||||
Url::parse(&string).ok()
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct LspUrlMapInner {
|
||||
specifier_to_uri: HashMap<ModuleSpecifier, Uri>,
|
||||
uri_to_specifier: HashMap<Uri, ModuleSpecifier>,
|
||||
}
|
||||
|
||||
impl LspUrlMapInner {
|
||||
fn put(&mut self, specifier: ModuleSpecifier, uri: Uri) {
|
||||
self.uri_to_specifier.insert(uri.clone(), specifier.clone());
|
||||
self.specifier_to_uri.insert(specifier, uri);
|
||||
}
|
||||
|
||||
fn get_uri(&self, specifier: &ModuleSpecifier) -> Option<&Uri> {
|
||||
self.specifier_to_uri.get(specifier)
|
||||
}
|
||||
|
||||
fn get_specifier(&self, uri: &Uri) -> Option<&ModuleSpecifier> {
|
||||
self.uri_to_specifier.get(uri)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uri_parse_unencoded(s: &str) -> Result<Uri, AnyError> {
|
||||
url_to_uri(&Url::parse(s)?)
|
||||
}
|
||||
|
@ -150,10 +82,31 @@ pub fn url_to_uri(url: &Url) -> Result<Uri, AnyError> {
|
|||
let components = deno_core::url::quirks::internal_components(url);
|
||||
let mut input = String::with_capacity(url.as_str().len());
|
||||
input.push_str(&url.as_str()[..components.path_start as usize]);
|
||||
input.push_str(
|
||||
&percent_encoding::utf8_percent_encode(url.path(), URL_TO_URI_PATH)
|
||||
.to_string(),
|
||||
);
|
||||
if cfg!(windows) && url.scheme() == "file" {
|
||||
let path = url.path();
|
||||
let mut chars = path.chars();
|
||||
let has_drive_letter = chars.next().is_some_and(|c| c == '/')
|
||||
&& chars.next().is_some_and(|c| c.is_ascii_alphabetic())
|
||||
&& chars.next().is_some_and(|c| c == ':')
|
||||
&& chars.next().is_none_or(|c| c == '/');
|
||||
if has_drive_letter {
|
||||
input.push_str(&path[..3]);
|
||||
input.push_str(
|
||||
&percent_encoding::utf8_percent_encode(&path[3..], URL_TO_URI_PATH)
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
input.push_str(
|
||||
&percent_encoding::utf8_percent_encode(path, URL_TO_URI_PATH)
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
input.push_str(
|
||||
&percent_encoding::utf8_percent_encode(url.path(), URL_TO_URI_PATH)
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(query) = url.query() {
|
||||
input.push('?');
|
||||
input.push_str(
|
||||
|
@ -174,283 +127,34 @@ pub fn url_to_uri(url: &Url) -> Result<Uri, AnyError> {
|
|||
}
|
||||
|
||||
pub fn uri_to_url(uri: &Uri) -> Url {
|
||||
Url::parse(uri.as_str()).unwrap()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum LspUrlKind {
|
||||
File,
|
||||
Folder,
|
||||
}
|
||||
|
||||
/// A bi-directional map of URLs sent to the LSP client and internal module
|
||||
/// specifiers. We need to map internal specifiers into `deno:` schema URLs
|
||||
/// to allow the Deno language server to manage these as virtual documents.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct LspUrlMap {
|
||||
cache: LspCache,
|
||||
inner: Arc<Mutex<LspUrlMapInner>>,
|
||||
}
|
||||
|
||||
impl LspUrlMap {
|
||||
pub fn set_cache(&mut self, cache: &LspCache) {
|
||||
self.cache = cache.clone();
|
||||
}
|
||||
|
||||
/// Normalize a specifier that is used internally within Deno (or tsc) to a
|
||||
/// URL that can be handled as a "virtual" document by an LSP client.
|
||||
pub fn specifier_to_uri(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
file_referrer: Option<&ModuleSpecifier>,
|
||||
) -> Result<Uri, AnyError> {
|
||||
if let Some(file_url) =
|
||||
self.cache.vendored_specifier(specifier, file_referrer)
|
||||
(|| {
|
||||
let scheme = uri.scheme()?;
|
||||
if !scheme.eq_lowercase("untitled")
|
||||
&& !scheme.eq_lowercase("vscode-notebook-cell")
|
||||
&& !scheme.eq_lowercase("deno-notebook-cell")
|
||||
{
|
||||
return url_to_uri(&file_url);
|
||||
return None;
|
||||
}
|
||||
let mut inner = self.inner.lock();
|
||||
if let Some(uri) = inner.get_uri(specifier).cloned() {
|
||||
Ok(uri)
|
||||
} else {
|
||||
let uri = if specifier.scheme() == "file" {
|
||||
url_to_uri(specifier)?
|
||||
} else {
|
||||
let uri_str = if specifier.scheme() == "asset" {
|
||||
format!("deno:/asset{}", specifier.path())
|
||||
} else if specifier.scheme() == "data" {
|
||||
let data_url =
|
||||
deno_media_type::data_url::RawDataUrl::parse(specifier)?;
|
||||
let media_type = data_url.media_type();
|
||||
let extension = if media_type == MediaType::Unknown {
|
||||
""
|
||||
} else {
|
||||
media_type.as_ts_extension()
|
||||
};
|
||||
format!(
|
||||
"deno:/{}/data_url{}",
|
||||
hash_data_specifier(specifier),
|
||||
extension
|
||||
)
|
||||
} else {
|
||||
to_deno_uri(specifier)
|
||||
};
|
||||
let uri = uri_parse_unencoded(&uri_str)?;
|
||||
inner.put(specifier.clone(), uri.clone());
|
||||
uri
|
||||
};
|
||||
Ok(uri)
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize URLs from the client, where "virtual" `deno:///` URLs are
|
||||
/// converted into proper module specifiers, as well as handle situations
|
||||
/// where the client encodes a file URL differently than Rust does by default
|
||||
/// causing issues with string matching of URLs.
|
||||
///
|
||||
/// Note: Sometimes the url provided by the client may not have a trailing slash,
|
||||
/// so we need to force it to in the mapping and nee to explicitly state whether
|
||||
/// this is a file or directory url.
|
||||
pub fn uri_to_specifier(
|
||||
&self,
|
||||
uri: &Uri,
|
||||
kind: LspUrlKind,
|
||||
) -> ModuleSpecifier {
|
||||
let url = uri_to_url(uri);
|
||||
if let Some(remote_url) = self.cache.unvendored_specifier(&url) {
|
||||
return remote_url;
|
||||
}
|
||||
let mut inner = self.inner.lock();
|
||||
if let Some(specifier) = inner.get_specifier(uri).cloned() {
|
||||
return specifier;
|
||||
}
|
||||
let mut specifier = None;
|
||||
if url.scheme() == "file" {
|
||||
if let Ok(path) = url.to_file_path() {
|
||||
specifier = Some(match kind {
|
||||
LspUrlKind::Folder => Url::from_directory_path(path).unwrap(),
|
||||
LspUrlKind::File => Url::from_file_path(path).unwrap(),
|
||||
});
|
||||
}
|
||||
} else if let Some(s) = file_like_to_file_specifier(&url) {
|
||||
specifier = Some(s);
|
||||
} else if let Some(s) = from_deno_url(&url) {
|
||||
specifier = Some(s);
|
||||
}
|
||||
let specifier = specifier.unwrap_or_else(|| url.clone());
|
||||
inner.put(specifier.clone(), uri.clone());
|
||||
specifier
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a e.g. `vscode-notebook-cell:` specifier to a `file:` specifier.
|
||||
/// ```rust
|
||||
/// assert_eq!(
|
||||
/// file_like_to_file_specifier(
|
||||
/// &Url::parse("vscode-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
|
||||
/// ),
|
||||
/// Some(Url::parse("file:///path/to/file.ipynb?scheme=untitled#abc").unwrap()),
|
||||
/// );
|
||||
fn file_like_to_file_specifier(specifier: &Url) -> Option<Url> {
|
||||
if matches!(
|
||||
specifier.scheme(),
|
||||
"untitled" | "vscode-notebook-cell" | "deno-notebook-cell"
|
||||
) {
|
||||
if let Ok(mut s) = ModuleSpecifier::parse(&format!(
|
||||
Url::parse(&format!(
|
||||
"file:///{}",
|
||||
&specifier.as_str()[deno_core::url::quirks::internal_components(specifier)
|
||||
.host_end as usize..].trim_start_matches('/'),
|
||||
)) {
|
||||
s.query_pairs_mut()
|
||||
.append_pair("scheme", specifier.scheme());
|
||||
return Some(s);
|
||||
}
|
||||
}
|
||||
None
|
||||
&uri.as_str()[uri.path_bounds.0 as usize..uri.path_bounds.1 as usize]
|
||||
.trim_start_matches('/'),
|
||||
))
|
||||
.ok()
|
||||
})()
|
||||
.unwrap_or_else(|| Url::parse(uri.as_str()).unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deno_core::resolve_url;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hash_data_specifier() {
|
||||
let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
|
||||
let actual = hash_data_specifier(&fixture);
|
||||
assert_eq!(
|
||||
actual,
|
||||
"c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsp_url_map() {
|
||||
let map = LspUrlMap::default();
|
||||
let fixture = resolve_url("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap();
|
||||
let actual_uri = map
|
||||
.specifier_to_uri(&fixture, None)
|
||||
.expect("could not handle specifier");
|
||||
assert_eq!(
|
||||
actual_uri.as_str(),
|
||||
"deno:/https/deno.land/x/pkg%401.0.0/mod.ts"
|
||||
);
|
||||
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
|
||||
assert_eq!(actual_specifier, fixture);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsp_url_reverse() {
|
||||
let map = LspUrlMap::default();
|
||||
let fixture =
|
||||
Uri::from_str("deno:/https/deno.land/x/pkg%401.0.0/mod.ts").unwrap();
|
||||
let actual_specifier = map.uri_to_specifier(&fixture, LspUrlKind::File);
|
||||
let expected_specifier =
|
||||
Url::parse("https://deno.land/x/pkg@1.0.0/mod.ts").unwrap();
|
||||
assert_eq!(&actual_specifier, &expected_specifier);
|
||||
|
||||
let actual_uri = map.specifier_to_uri(&actual_specifier, None).unwrap();
|
||||
assert_eq!(actual_uri, fixture);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsp_url_map_complex_encoding() {
|
||||
// Test fix for #9741 - not properly encoding certain URLs
|
||||
let map = LspUrlMap::default();
|
||||
let fixture = resolve_url("https://cdn.skypack.dev/-/postcss@v8.2.9-E4SktPp9c0AtxrJHp8iV/dist=es2020,mode=types/lib/postcss.d.ts").unwrap();
|
||||
let actual_uri = map
|
||||
.specifier_to_uri(&fixture, None)
|
||||
.expect("could not handle specifier");
|
||||
assert_eq!(actual_uri.as_str(), "deno:/https/cdn.skypack.dev/-/postcss%40v8.2.9-E4SktPp9c0AtxrJHp8iV/dist%3Des2020%2Cmode%3Dtypes/lib/postcss.d.ts");
|
||||
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
|
||||
assert_eq!(actual_specifier, fixture);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsp_url_map_data() {
|
||||
let map = LspUrlMap::default();
|
||||
let fixture = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap();
|
||||
let actual_uri = map
|
||||
.specifier_to_uri(&fixture, None)
|
||||
.expect("could not handle specifier");
|
||||
let expected_url = Url::parse("deno:/c21c7fc382b2b0553dc0864aa81a3acacfb7b3d1285ab5ae76da6abec213fb37/data_url.ts").unwrap();
|
||||
assert_eq!(&uri_to_url(&actual_uri), &expected_url);
|
||||
|
||||
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
|
||||
assert_eq!(actual_specifier, fixture);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsp_url_map_host_with_port() {
|
||||
let map = LspUrlMap::default();
|
||||
let fixture = resolve_url("http://localhost:8000/mod.ts").unwrap();
|
||||
let actual_uri = map
|
||||
.specifier_to_uri(&fixture, None)
|
||||
.expect("could not handle specifier");
|
||||
assert_eq!(actual_uri.as_str(), "deno:/http/localhost%3A8000/mod.ts");
|
||||
let actual_specifier = map.uri_to_specifier(&actual_uri, LspUrlKind::File);
|
||||
assert_eq!(actual_specifier, fixture);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_normalize_windows_path() {
|
||||
let map = LspUrlMap::default();
|
||||
let fixture = Uri::from_str(
|
||||
"file:///c%3A/Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
|
||||
)
|
||||
.unwrap();
|
||||
let actual = map.uri_to_specifier(&fixture, LspUrlKind::File);
|
||||
let expected =
|
||||
Url::parse("file:///C:/Users/deno/Desktop/file with spaces in name.txt")
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[test]
|
||||
fn test_normalize_percent_encoded_path() {
|
||||
let map = LspUrlMap::default();
|
||||
let fixture = Uri::from_str(
|
||||
"file:///Users/deno/Desktop/file%20with%20spaces%20in%20name.txt",
|
||||
)
|
||||
.unwrap();
|
||||
let actual = map.uri_to_specifier(&fixture, LspUrlKind::File);
|
||||
let expected =
|
||||
Url::parse("file:///Users/deno/Desktop/file with spaces in name.txt")
|
||||
.unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_deno_status() {
|
||||
let map = LspUrlMap::default();
|
||||
let fixture = Uri::from_str("deno:/status.md").unwrap();
|
||||
let actual = map.uri_to_specifier(&fixture, LspUrlKind::File);
|
||||
assert_eq!(actual.as_str(), fixture.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_like_to_file_specifier() {
|
||||
assert_eq!(
|
||||
file_like_to_file_specifier(
|
||||
&Url::parse("vscode-notebook-cell:/path/to/file.ipynb#abc").unwrap(),
|
||||
),
|
||||
Some(
|
||||
Url::parse(
|
||||
"file:///path/to/file.ipynb?scheme=vscode-notebook-cell#abc"
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
file_like_to_file_specifier(
|
||||
&Url::parse("untitled:/path/to/file.ipynb#123").unwrap(),
|
||||
),
|
||||
Some(
|
||||
Url::parse("file:///path/to/file.ipynb?scheme=untitled#123").unwrap()
|
||||
),
|
||||
);
|
||||
}
|
||||
pub fn uri_to_file_path(uri: &Uri) -> Result<PathBuf, UrlToFilePathError> {
|
||||
url_to_file_path(&uri_to_url(uri))
|
||||
}
|
||||
|
||||
pub fn uri_is_file_like(uri: &Uri) -> bool {
|
||||
let Some(scheme) = uri.scheme() else {
|
||||
return false;
|
||||
};
|
||||
scheme.eq_lowercase("file")
|
||||
|| scheme.eq_lowercase("untitled")
|
||||
|| scheme.eq_lowercase("vscode-notebook-cell")
|
||||
|| scheme.eq_lowercase("deno-notebook-cell")
|
||||
}
|
||||
|
|
|
@ -478,17 +478,17 @@ function serverRequestInner(id, method, args, scope, maybeChange) {
|
|||
// (it's about to be invalidated anyway).
|
||||
const cachedProjectVersion = PROJECT_VERSION_CACHE.get();
|
||||
if (cachedProjectVersion && projectVersion !== cachedProjectVersion) {
|
||||
return respond(id, [{}, null]);
|
||||
return respond(id, [[], null]);
|
||||
}
|
||||
try {
|
||||
/** @type {Record<string, any[]>} */
|
||||
const diagnosticMap = {};
|
||||
/** @type {any[][]} */
|
||||
const diagnosticsList = [];
|
||||
for (const specifier of args[0]) {
|
||||
diagnosticMap[specifier] = fromTypeScriptDiagnostics([
|
||||
diagnosticsList.push(fromTypeScriptDiagnostics([
|
||||
...ls.getSemanticDiagnostics(specifier),
|
||||
...ls.getSuggestionDiagnostics(specifier),
|
||||
...ls.getSyntacticDiagnostics(specifier),
|
||||
].filter(filterMapDiagnostic));
|
||||
].filter(filterMapDiagnostic)));
|
||||
}
|
||||
let ambient =
|
||||
ls.getProgram()?.getTypeChecker().getAmbientModules().map((symbol) =>
|
||||
|
@ -502,18 +502,18 @@ function serverRequestInner(id, method, args, scope, maybeChange) {
|
|||
} else {
|
||||
ambientModulesCacheByScope.set(scope, ambient);
|
||||
}
|
||||
return respond(id, [diagnosticMap, ambient]);
|
||||
return respond(id, [diagnosticsList, ambient]);
|
||||
} catch (e) {
|
||||
if (
|
||||
!isCancellationError(e)
|
||||
) {
|
||||
return respond(
|
||||
id,
|
||||
[{}, null],
|
||||
[[], null],
|
||||
formatErrorWithArgs(e, [id, method, args, scope, maybeChange]),
|
||||
);
|
||||
}
|
||||
return respond(id, [{}, null]);
|
||||
return respond(id, [[], null]);
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -895,96 +895,6 @@ fn lsp_import_map_node_specifiers() {
|
|||
client.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(300_000)]
|
||||
fn lsp_format_vendor_path() {
|
||||
let context = TestContextBuilder::new()
|
||||
.use_http_server()
|
||||
.use_temp_cwd()
|
||||
.build();
|
||||
|
||||
// put this dependency in the global cache
|
||||
context
|
||||
.new_command()
|
||||
.args("cache --allow-import http://localhost:4545/run/002_hello.ts")
|
||||
.run()
|
||||
.skip_output_check();
|
||||
|
||||
let temp_dir = context.temp_dir();
|
||||
temp_dir.write("deno.json", json!({ "vendor": true }).to_string());
|
||||
let mut client = context.new_lsp_command().build();
|
||||
client.initialize_default();
|
||||
let diagnostics = client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": temp_dir.url().join("file.ts").unwrap(),
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": r#"import "http://localhost:4545/run/002_hello.ts";"#,
|
||||
},
|
||||
}));
|
||||
// copying from the global cache to the local cache requires explicitly
|
||||
// running the cache command so that the checksums can be verified
|
||||
assert_eq!(
|
||||
diagnostics
|
||||
.all()
|
||||
.iter()
|
||||
.map(|d| d.message.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
"Uncached or missing remote URL: http://localhost:4545/run/002_hello.ts"
|
||||
]
|
||||
);
|
||||
client.write_request(
|
||||
"workspace/executeCommand",
|
||||
json!({
|
||||
"command": "deno.cache",
|
||||
"arguments": [[], temp_dir.url().join("file.ts").unwrap()],
|
||||
}),
|
||||
);
|
||||
client.read_diagnostics();
|
||||
assert!(temp_dir
|
||||
.path()
|
||||
.join("vendor/http_localhost_4545/run/002_hello.ts")
|
||||
.exists());
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": temp_dir.url().join("vendor/http_localhost_4545/run/002_hello.ts").unwrap(),
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": r#"console.log("Hello World");"#,
|
||||
},
|
||||
}));
|
||||
let res = client.write_request(
|
||||
"textDocument/formatting",
|
||||
json!({
|
||||
"textDocument": {
|
||||
"uri": temp_dir.url().join("vendor/http_localhost_4545/run/002_hello.ts").unwrap(),
|
||||
},
|
||||
"options": {
|
||||
"tabSize": 2,
|
||||
"insertSpaces": true,
|
||||
}
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
res,
|
||||
json!([{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 27,
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 27,
|
||||
},
|
||||
},
|
||||
"newText": "\n",
|
||||
}]),
|
||||
);
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/denoland/deno/issues/19802.
|
||||
// Disable the `workspace/configuration` capability. Ensure the LSP falls back
|
||||
// to using `enablePaths` from the `InitializationOptions`.
|
||||
|
@ -1088,11 +998,12 @@ fn lsp_did_refresh_deno_configuration_tree_notification() {
|
|||
temp_dir.write("non_workspace1/deno.json", json!({}).to_string());
|
||||
let mut client = context.new_lsp_command().build();
|
||||
client.initialize_default();
|
||||
let res = client
|
||||
let mut res = client
|
||||
.read_notification_with_method::<Value>(
|
||||
"deno/didRefreshDenoConfigurationTree",
|
||||
)
|
||||
.unwrap();
|
||||
res.as_object_mut().unwrap().remove("denoDirNpmFolderUri");
|
||||
assert_eq!(
|
||||
res,
|
||||
json!({
|
||||
|
@ -1142,11 +1053,12 @@ fn lsp_did_refresh_deno_configuration_tree_notification() {
|
|||
}],
|
||||
}));
|
||||
client.read_diagnostics();
|
||||
let res = client
|
||||
let mut res = client
|
||||
.read_notification_with_method::<Value>(
|
||||
"deno/didRefreshDenoConfigurationTree",
|
||||
)
|
||||
.unwrap();
|
||||
res.as_object_mut().unwrap().remove("denoDirNpmFolderUri");
|
||||
assert_eq!(
|
||||
res,
|
||||
json!({
|
||||
|
@ -1201,11 +1113,12 @@ fn lsp_did_refresh_deno_configuration_tree_notification() {
|
|||
"disablePaths": ["non_workspace1"],
|
||||
},
|
||||
}));
|
||||
let res = client
|
||||
let mut res = client
|
||||
.read_notification_with_method::<Value>(
|
||||
"deno/didRefreshDenoConfigurationTree",
|
||||
)
|
||||
.unwrap();
|
||||
res.as_object_mut().unwrap().remove("denoDirNpmFolderUri");
|
||||
assert_eq!(
|
||||
res,
|
||||
json!({
|
||||
|
@ -4138,7 +4051,7 @@ fn lsp_code_lens_references() {
|
|||
"end": { "line": 0, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4147,7 +4060,7 @@ fn lsp_code_lens_references() {
|
|||
"end": { "line": 1, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4156,7 +4069,7 @@ fn lsp_code_lens_references() {
|
|||
"end": { "line": 3, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4165,7 +4078,7 @@ fn lsp_code_lens_references() {
|
|||
"end": { "line": 7, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}])
|
||||
|
@ -4178,7 +4091,7 @@ fn lsp_code_lens_references() {
|
|||
"end": { "line": 0, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}),
|
||||
|
@ -4217,7 +4130,7 @@ fn lsp_code_lens_references() {
|
|||
"end": { "line": 14, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}),
|
||||
|
@ -4245,7 +4158,7 @@ fn lsp_code_lens_references() {
|
|||
"end": { "line": 15, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}),
|
||||
|
@ -4325,7 +4238,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 0, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "implementations"
|
||||
}
|
||||
}, {
|
||||
|
@ -4334,7 +4247,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 0, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4343,7 +4256,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 1, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4352,7 +4265,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 4, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4361,7 +4274,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 5, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4370,7 +4283,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 10, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "implementations"
|
||||
}
|
||||
}, {
|
||||
|
@ -4379,7 +4292,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 10, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4388,7 +4301,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 11, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}])
|
||||
|
@ -4401,7 +4314,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 0, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "implementations"
|
||||
}
|
||||
}),
|
||||
|
@ -4438,7 +4351,7 @@ fn lsp_code_lens_implementations() {
|
|||
"end": { "line": 10, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "implementations"
|
||||
}
|
||||
}),
|
||||
|
@ -4816,7 +4729,7 @@ fn lsp_code_lens_non_doc_nav_tree() {
|
|||
"end": { "line": 416, "character": 19 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "asset:///lib.deno.shared_globals.d.ts",
|
||||
"uri": "deno:/asset/lib.deno.shared_globals.d.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}),
|
||||
|
@ -4865,7 +4778,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 0, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "implementations"
|
||||
}
|
||||
}, {
|
||||
|
@ -4874,7 +4787,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 0, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4883,7 +4796,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 1, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4892,7 +4805,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 4, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4901,7 +4814,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 5, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4910,7 +4823,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 10, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "implementations"
|
||||
}
|
||||
}, {
|
||||
|
@ -4919,7 +4832,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 10, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4928,7 +4841,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 11, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}])
|
||||
|
@ -4967,7 +4880,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 0, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "implementations"
|
||||
}
|
||||
}, {
|
||||
|
@ -4976,7 +4889,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 0, "character": 11 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4985,7 +4898,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 1, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -4994,7 +4907,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 4, "character": 7 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}, {
|
||||
|
@ -5003,7 +4916,7 @@ fn lsp_nav_tree_updates() {
|
|||
"end": { "line": 5, "character": 3 }
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}])
|
||||
|
@ -5338,7 +5251,7 @@ fn lsp_code_actions() {
|
|||
"relatedInformation": []
|
||||
}],
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"fixId": "fixAwaitInSyncFunction"
|
||||
}
|
||||
}])
|
||||
|
@ -5361,7 +5274,7 @@ fn lsp_code_actions() {
|
|||
"relatedInformation": []
|
||||
}],
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"fixId": "fixAwaitInSyncFunction"
|
||||
}
|
||||
}),
|
||||
|
@ -5424,7 +5337,7 @@ fn lsp_code_actions() {
|
|||
}]
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"fixId": "fixAwaitInSyncFunction"
|
||||
}
|
||||
})
|
||||
|
@ -6254,7 +6167,7 @@ fn lsp_jsr_code_action_move_to_new_file() {
|
|||
},
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": file.url(),
|
||||
"uri": file.url(),
|
||||
"range": {
|
||||
"start": { "line": 2, "character": 19 },
|
||||
"end": { "line": 2, "character": 28 },
|
||||
|
@ -6594,7 +6507,7 @@ fn lsp_asset_document_dom_code_action() {
|
|||
let res = client.write_request(
|
||||
"textDocument/codeAction",
|
||||
json!({
|
||||
"textDocument": { "uri": "asset:///lib.dom.d.ts" },
|
||||
"textDocument": { "uri": "deno:/asset/lib.dom.d.ts" },
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 0, "character": 0 },
|
||||
|
@ -6816,7 +6729,7 @@ export class DuckConfig {
|
|||
"message": "Cannot find name 'DuckConfigOptions'."
|
||||
}],
|
||||
"data": {
|
||||
"specifier": "file:///a/file00.ts",
|
||||
"uri": "file:///a/file00.ts",
|
||||
"fixId": "fixMissingImport"
|
||||
}
|
||||
}, {
|
||||
|
@ -6874,7 +6787,7 @@ export class DuckConfig {
|
|||
"message": "Cannot find name 'DuckConfig'."
|
||||
}],
|
||||
"data": {
|
||||
"specifier": "file:///a/file00.ts",
|
||||
"uri": "file:///a/file00.ts",
|
||||
"fixId": "fixMissingImport"
|
||||
}
|
||||
}),
|
||||
|
@ -6919,7 +6832,7 @@ export class DuckConfig {
|
|||
}]
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file00.ts",
|
||||
"uri": "file:///a/file00.ts",
|
||||
"fixId": "fixMissingImport"
|
||||
}
|
||||
})
|
||||
|
@ -7150,7 +7063,7 @@ fn lsp_code_actions_refactor() {
|
|||
"kind": "refactor.move.newFile",
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7163,7 +7076,7 @@ fn lsp_code_actions_refactor() {
|
|||
"kind": "refactor.extract.function",
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7176,7 +7089,7 @@ fn lsp_code_actions_refactor() {
|
|||
"kind": "refactor.extract.constant",
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7192,7 +7105,7 @@ fn lsp_code_actions_refactor() {
|
|||
"reason": "This file already has a default export"
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7208,7 +7121,7 @@ fn lsp_code_actions_refactor() {
|
|||
"reason": "This file already has a default export"
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7224,7 +7137,7 @@ fn lsp_code_actions_refactor() {
|
|||
"reason": "Selection is not an import declaration."
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7240,7 +7153,7 @@ fn lsp_code_actions_refactor() {
|
|||
"reason": "Selection is not an import declaration."
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7256,7 +7169,7 @@ fn lsp_code_actions_refactor() {
|
|||
"reason": "Selection is not an import declaration."
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 1, "character": 0 }
|
||||
|
@ -7273,7 +7186,7 @@ fn lsp_code_actions_refactor() {
|
|||
"kind": "refactor.extract.interface",
|
||||
"isPreferred": true,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 7 },
|
||||
"end": { "line": 0, "character": 33 }
|
||||
|
@ -7311,7 +7224,7 @@ fn lsp_code_actions_refactor() {
|
|||
},
|
||||
"isPreferred": true,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 7 },
|
||||
"end": { "line": 0, "character": 33 }
|
||||
|
@ -7448,7 +7361,7 @@ fn lsp_code_actions_imports_respects_fmt_config() {
|
|||
"message": "Cannot find name 'DuckConfigOptions'."
|
||||
}],
|
||||
"data": {
|
||||
"specifier": temp_dir.url().join("file00.ts").unwrap(),
|
||||
"uri": temp_dir.url().join("file00.ts").unwrap(),
|
||||
"fixId": "fixMissingImport"
|
||||
}
|
||||
}),
|
||||
|
@ -7484,7 +7397,7 @@ fn lsp_code_actions_imports_respects_fmt_config() {
|
|||
}]
|
||||
},
|
||||
"data": {
|
||||
"specifier": temp_dir.url().join("file00.ts").unwrap(),
|
||||
"uri": temp_dir.url().join("file00.ts").unwrap(),
|
||||
"fixId": "fixMissingImport"
|
||||
}
|
||||
})
|
||||
|
@ -7679,7 +7592,7 @@ fn lsp_code_actions_refactor_no_disabled_support() {
|
|||
"kind": "refactor.move.newFile",
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 14, "character": 0 }
|
||||
|
@ -7692,7 +7605,7 @@ fn lsp_code_actions_refactor_no_disabled_support() {
|
|||
"kind": "refactor.extract.function",
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 0 },
|
||||
"end": { "line": 14, "character": 0 }
|
||||
|
@ -7863,7 +7776,7 @@ fn lsp_completions() {
|
|||
"insertTextFormat": 1,
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 5,
|
||||
"name": "build",
|
||||
"useCodeSnippet": false
|
||||
|
@ -7952,7 +7865,7 @@ fn lsp_completions_optional() {
|
|||
"commitCharacters": [".", ",", ";", "("],
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 79,
|
||||
"name": "b",
|
||||
"useCodeSnippet": false
|
||||
|
@ -7972,7 +7885,7 @@ fn lsp_completions_optional() {
|
|||
"insertText": "b",
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 79,
|
||||
"name": "b",
|
||||
"useCodeSnippet": false
|
||||
|
@ -8813,7 +8726,7 @@ fn lsp_infer_return_type() {
|
|||
"kind": "refactor.rewrite.function.returnType",
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": file.url(),
|
||||
"uri": file.url(),
|
||||
"range": {
|
||||
"start": { "line": 1, "character": 15 },
|
||||
"end": { "line": 1, "character": 18 },
|
||||
|
@ -8833,7 +8746,7 @@ fn lsp_infer_return_type() {
|
|||
"kind": "refactor.rewrite.function.returnType",
|
||||
"isPreferred": false,
|
||||
"data": {
|
||||
"specifier": file.url(),
|
||||
"uri": file.url(),
|
||||
"range": {
|
||||
"start": { "line": 1, "character": 15 },
|
||||
"end": { "line": 1, "character": 18 },
|
||||
|
@ -9617,7 +9530,7 @@ fn lsp_completions_snippet() {
|
|||
],
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/a.tsx",
|
||||
"uri": "file:///a/a.tsx",
|
||||
"position": 87,
|
||||
"name": "type",
|
||||
"useCodeSnippet": false
|
||||
|
@ -9645,7 +9558,7 @@ fn lsp_completions_snippet() {
|
|||
],
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/a.tsx",
|
||||
"uri": "file:///a/a.tsx",
|
||||
"position": 87,
|
||||
"name": "type",
|
||||
"useCodeSnippet": false
|
||||
|
@ -9716,7 +9629,7 @@ fn lsp_completions_no_snippet() {
|
|||
],
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/a.tsx",
|
||||
"uri": "file:///a/a.tsx",
|
||||
"position": 87,
|
||||
"name": "type",
|
||||
"useCodeSnippet": false
|
||||
|
@ -9819,7 +9732,7 @@ fn lsp_completions_npm() {
|
|||
"insertTextFormat": 1,
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 69,
|
||||
"name": "MyClass",
|
||||
"useCodeSnippet": false
|
||||
|
@ -9836,7 +9749,7 @@ fn lsp_completions_npm() {
|
|||
"insertTextFormat": 1,
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 69,
|
||||
"name": "MyClass",
|
||||
"useCodeSnippet": false
|
||||
|
@ -12731,7 +12644,7 @@ fn lsp_completions_complete_function_calls() {
|
|||
"insertTextFormat": 1,
|
||||
"data": {
|
||||
"tsc": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 3,
|
||||
"name": "map",
|
||||
"useCodeSnippet": true
|
||||
|
@ -12757,6 +12670,107 @@ fn lsp_completions_complete_function_calls() {
|
|||
client.shutdown();
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/denoland/vscode_deno/issues/1276.
|
||||
#[test]
|
||||
#[timeout(300_000)]
|
||||
fn lsp_completions_private_class_fields() {
|
||||
let context = TestContextBuilder::new()
|
||||
.use_http_server()
|
||||
.use_temp_cwd()
|
||||
.build();
|
||||
let mut client = context.new_lsp_command().build();
|
||||
client.initialize_default();
|
||||
client.did_open(json!({
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": r#"
|
||||
export class SomeClass {
|
||||
#prop = 1;
|
||||
foo() {
|
||||
pro;
|
||||
#pro;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
},
|
||||
}));
|
||||
|
||||
let list = client.get_completion_list(
|
||||
"file:///a/file.ts",
|
||||
(4, 15),
|
||||
json!({
|
||||
"triggerKind": 2,
|
||||
"triggerCharacter": ".",
|
||||
}),
|
||||
);
|
||||
assert!(!list.is_incomplete);
|
||||
let item = list.items.iter().find(|i| i.label == "#prop").unwrap();
|
||||
assert_eq!(
|
||||
json!(item),
|
||||
json!({
|
||||
"label": "#prop",
|
||||
"kind": 5,
|
||||
"sortText": "14",
|
||||
"filterText": "prop",
|
||||
"insertText": "this.#prop",
|
||||
"commitCharacters": [
|
||||
".",
|
||||
",",
|
||||
";",
|
||||
"("
|
||||
],
|
||||
"data": {
|
||||
"tsc": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 88,
|
||||
"name": "#prop",
|
||||
"source": "ThisProperty/",
|
||||
"useCodeSnippet": false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
let list = client.get_completion_list(
|
||||
"file:///a/file.ts",
|
||||
(5, 16),
|
||||
json!({
|
||||
"triggerKind": 2,
|
||||
"triggerCharacter": ".",
|
||||
}),
|
||||
);
|
||||
assert!(!list.is_incomplete);
|
||||
let item = list.items.iter().find(|i| i.label == "#prop").unwrap();
|
||||
assert_eq!(
|
||||
json!(item),
|
||||
json!({
|
||||
"label": "#prop",
|
||||
"kind": 5,
|
||||
"sortText": "14",
|
||||
"filterText": "this.#prop",
|
||||
"insertText": "this.#prop",
|
||||
"commitCharacters": [
|
||||
".",
|
||||
",",
|
||||
";",
|
||||
"("
|
||||
],
|
||||
"data": {
|
||||
"tsc": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"position": 106,
|
||||
"name": "#prop",
|
||||
"source": "ThisProperty/",
|
||||
"useCodeSnippet": false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
client.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(300_000)]
|
||||
fn lsp_workspace_symbol() {
|
||||
|
@ -14014,7 +14028,7 @@ fn lsp_closed_file_find_references_low_document_pre_load() {
|
|||
);
|
||||
|
||||
// won't have results because the document won't be pre-loaded
|
||||
assert_eq!(res, json!([]));
|
||||
assert_eq!(res, json!(null));
|
||||
|
||||
client.shutdown();
|
||||
}
|
||||
|
@ -14069,7 +14083,7 @@ fn lsp_closed_file_find_references_excluded_path() {
|
|||
);
|
||||
|
||||
// won't have results because the documents won't be pre-loaded
|
||||
assert_eq!(res, json!([]));
|
||||
assert_eq!(res, json!(null));
|
||||
|
||||
client.shutdown();
|
||||
}
|
||||
|
@ -14128,7 +14142,7 @@ fn lsp_data_urls_with_jsx_compiler_option() {
|
|||
"end": { "line": 1, "character": 1 }
|
||||
}
|
||||
}, {
|
||||
"uri": "deno:/ed0224c51f7e2a845dfc0941ed6959675e5e3e3d2a39b127f0ff569c1ffda8d8/data_url.ts",
|
||||
"uri": "deno:/data_url/ed0224c51f7e2a845dfc0941ed6959675e5e3e3d2a39b127f0ff569c1ffda8d8.ts",
|
||||
"range": {
|
||||
"start": { "line": 0, "character": 7 },
|
||||
"end": {"line": 0, "character": 14 },
|
||||
|
@ -14881,7 +14895,7 @@ fn lsp_deno_json_scopes_node_modules_dir() {
|
|||
assert_eq!(
|
||||
res,
|
||||
json!([{
|
||||
"targetUri": canon_temp_dir.join("project1/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(),
|
||||
"targetUri": canon_temp_dir.join("project1/node_modules/.deno/%40denotest%2Badd%401.0.0/node_modules/%40denotest/add/index.d.ts").unwrap(),
|
||||
"targetRange": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
|
@ -14932,7 +14946,7 @@ fn lsp_deno_json_scopes_node_modules_dir() {
|
|||
assert_eq!(
|
||||
res,
|
||||
json!([{
|
||||
"targetUri": canon_temp_dir.join("project2/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(),
|
||||
"targetUri": canon_temp_dir.join("project2/node_modules/.deno/%40denotest%2Badd%401.0.0/node_modules/%40denotest/add/index.d.ts").unwrap(),
|
||||
"targetRange": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
|
@ -14983,7 +14997,7 @@ fn lsp_deno_json_scopes_node_modules_dir() {
|
|||
assert_eq!(
|
||||
res,
|
||||
json!([{
|
||||
"targetUri": canon_temp_dir.join("project2/project3/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(),
|
||||
"targetUri": canon_temp_dir.join("project2/project3/node_modules/.deno/%40denotest%2Badd%401.0.0/node_modules/%40denotest/add/index.d.ts").unwrap(),
|
||||
"targetRange": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
|
@ -16054,7 +16068,7 @@ fn lsp_deno_json_workspace_node_modules_dir() {
|
|||
assert_eq!(
|
||||
res,
|
||||
json!([{
|
||||
"targetUri": canon_temp_dir.join("project1/node_modules/.deno/@denotest+add@1.0.0/node_modules/@denotest/add/index.d.ts").unwrap(),
|
||||
"targetUri": canon_temp_dir.join("project1/node_modules/.deno/%40denotest%2Badd%401.0.0/node_modules/%40denotest/add/index.d.ts").unwrap(),
|
||||
"targetRange": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
|
@ -16832,7 +16846,7 @@ fn sloppy_imports_not_enabled() {
|
|||
temp_dir.join("a").url_file(),
|
||||
),
|
||||
"data": {
|
||||
"specifier": temp_dir.join("a").url_file(),
|
||||
"uri": temp_dir.join("a").url_file(),
|
||||
"to": temp_dir.join("a.ts").url_file(),
|
||||
"message": "Add a '.ts' extension.",
|
||||
},
|
||||
|
@ -16859,7 +16873,7 @@ fn sloppy_imports_not_enabled() {
|
|||
temp_dir.join("a").url_file(),
|
||||
),
|
||||
"data": {
|
||||
"specifier": temp_dir.join("a").url_file(),
|
||||
"uri": temp_dir.join("a").url_file(),
|
||||
"to": temp_dir.join("a.ts").url_file(),
|
||||
"message": "Add a '.ts' extension.",
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue