mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
feat(lsp): provide import map remapping diags and fixes (#15165)
This commit is contained in:
parent
294b27717c
commit
7e06d33b34
4 changed files with 263 additions and 31 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -812,7 +812,7 @@ dependencies = [
|
||||||
"fwdansi",
|
"fwdansi",
|
||||||
"google-storage1",
|
"google-storage1",
|
||||||
"http",
|
"http",
|
||||||
"import_map",
|
"import_map 0.12.1",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"jsonc-parser",
|
"jsonc-parser",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -977,7 +977,7 @@ dependencies = [
|
||||||
"deno_ast",
|
"deno_ast",
|
||||||
"deno_graph",
|
"deno_graph",
|
||||||
"futures",
|
"futures",
|
||||||
"import_map",
|
"import_map 0.11.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2186,6 +2186,19 @@ dependencies = [
|
||||||
"url 2.2.2",
|
"url 2.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "import_map"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b827962ca5aa6d5bbe313c14e73d7cc517487fa3bad380bb6bdbd8421e591a29"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"log 0.4.17",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url 2.2.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
|
|
|
@ -71,7 +71,7 @@ env_logger = "=0.9.0"
|
||||||
eszip = "=0.22.0"
|
eszip = "=0.22.0"
|
||||||
fancy-regex = "=0.9.0"
|
fancy-regex = "=0.9.0"
|
||||||
http = "=0.2.6"
|
http = "=0.2.6"
|
||||||
import_map = "=0.11.0"
|
import_map = "=0.12.1"
|
||||||
indexmap = "1.8.1"
|
indexmap = "1.8.1"
|
||||||
jsonc-parser = { version = "=0.19.0", features = ["serde"] }
|
jsonc-parser = { version = "=0.19.0", features = ["serde"] }
|
||||||
libc = "=0.2.126"
|
libc = "=0.2.126"
|
||||||
|
|
|
@ -6,7 +6,6 @@ use super::client::Client;
|
||||||
use super::config::ConfigSnapshot;
|
use super::config::ConfigSnapshot;
|
||||||
use super::documents;
|
use super::documents;
|
||||||
use super::documents::Document;
|
use super::documents::Document;
|
||||||
use super::documents::Documents;
|
|
||||||
use super::language_server;
|
use super::language_server;
|
||||||
use super::language_server::StateSnapshot;
|
use super::language_server::StateSnapshot;
|
||||||
use super::performance::Performance;
|
use super::performance::Performance;
|
||||||
|
@ -270,7 +269,7 @@ impl DiagnosticsServer {
|
||||||
}
|
}
|
||||||
let mark =
|
let mark =
|
||||||
performance.mark("update_diagnostics_deps", None::<()>);
|
performance.mark("update_diagnostics_deps", None::<()>);
|
||||||
let diagnostics = generate_deps_diagnostics(
|
let diagnostics = generate_deno_diagnostics(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
&config,
|
&config,
|
||||||
token.clone(),
|
token.clone(),
|
||||||
|
@ -572,11 +571,21 @@ struct DiagnosticDataRedirect {
|
||||||
pub redirect: ModuleSpecifier,
|
pub redirect: ModuleSpecifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct DiagnosticDataImportMapRemap {
|
||||||
|
pub from: String,
|
||||||
|
pub to: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// An enum which represents diagnostic errors which originate from Deno itself.
|
/// An enum which represents diagnostic errors which originate from Deno itself.
|
||||||
pub enum DenoDiagnostic {
|
pub enum DenoDiagnostic {
|
||||||
/// A `x-deno-warning` is associated with the specifier and should be displayed
|
/// A `x-deno-warning` is associated with the specifier and should be displayed
|
||||||
/// as a warning to the user.
|
/// as a warning to the user.
|
||||||
DenoWarn(String),
|
DenoWarn(String),
|
||||||
|
/// An informational diagnostic that indicates an existing specifier can be
|
||||||
|
/// remapped to an import map import specifier.
|
||||||
|
ImportMapRemap { from: String, to: String },
|
||||||
/// The import assertion type is incorrect.
|
/// The import assertion type is incorrect.
|
||||||
InvalidAssertType(String),
|
InvalidAssertType(String),
|
||||||
/// A module requires an assertion type to be a valid import.
|
/// A module requires an assertion type to be a valid import.
|
||||||
|
@ -606,6 +615,7 @@ impl DenoDiagnostic {
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::DenoWarn(_) => "deno-warn",
|
Self::DenoWarn(_) => "deno-warn",
|
||||||
|
Self::ImportMapRemap { .. } => "import-map-remap",
|
||||||
Self::InvalidAssertType(_) => "invalid-assert-type",
|
Self::InvalidAssertType(_) => "invalid-assert-type",
|
||||||
Self::NoAssertType => "no-assert-type",
|
Self::NoAssertType => "no-assert-type",
|
||||||
Self::NoCache(_) => "no-cache",
|
Self::NoCache(_) => "no-cache",
|
||||||
|
@ -633,6 +643,33 @@ impl DenoDiagnostic {
|
||||||
) -> Result<lsp::CodeAction, AnyError> {
|
) -> Result<lsp::CodeAction, AnyError> {
|
||||||
if let Some(lsp::NumberOrString::String(code)) = &diagnostic.code {
|
if let Some(lsp::NumberOrString::String(code)) = &diagnostic.code {
|
||||||
let code_action = match code.as_str() {
|
let code_action = match code.as_str() {
|
||||||
|
"import-map-remap" => {
|
||||||
|
let data = diagnostic
|
||||||
|
.data
|
||||||
|
.clone()
|
||||||
|
.ok_or_else(|| anyhow!("Diagnostic is missing data"))?;
|
||||||
|
let DiagnosticDataImportMapRemap { from, to } =
|
||||||
|
serde_json::from_value(data)?;
|
||||||
|
lsp::CodeAction {
|
||||||
|
title: format!(
|
||||||
|
"Update \"{}\" to \"{}\" to use import map.",
|
||||||
|
from, to
|
||||||
|
),
|
||||||
|
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||||
|
diagnostics: Some(vec![diagnostic.clone()]),
|
||||||
|
edit: Some(lsp::WorkspaceEdit {
|
||||||
|
changes: Some(HashMap::from([(
|
||||||
|
specifier.clone(),
|
||||||
|
vec![lsp::TextEdit {
|
||||||
|
new_text: format!("\"{}\"", to),
|
||||||
|
range: diagnostic.range,
|
||||||
|
}],
|
||||||
|
)])),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
"no-assert-type" => lsp::CodeAction {
|
"no-assert-type" => lsp::CodeAction {
|
||||||
title: "Insert import assertion.".to_string(),
|
title: "Insert import assertion.".to_string(),
|
||||||
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
kind: Some(lsp::CodeActionKind::QUICKFIX),
|
||||||
|
@ -717,7 +754,11 @@ impl DenoDiagnostic {
|
||||||
if let Some(lsp::NumberOrString::String(code)) = code {
|
if let Some(lsp::NumberOrString::String(code)) = code {
|
||||||
matches!(
|
matches!(
|
||||||
code.as_str(),
|
code.as_str(),
|
||||||
"no-cache" | "no-cache-data" | "no-assert-type" | "redirect"
|
"import-map-remap"
|
||||||
|
| "no-cache"
|
||||||
|
| "no-cache-data"
|
||||||
|
| "no-assert-type"
|
||||||
|
| "redirect"
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -729,6 +770,7 @@ impl DenoDiagnostic {
|
||||||
pub fn to_lsp_diagnostic(&self, range: &lsp::Range) -> lsp::Diagnostic {
|
pub fn to_lsp_diagnostic(&self, range: &lsp::Range) -> lsp::Diagnostic {
|
||||||
let (severity, message, data) = match self {
|
let (severity, message, data) = match self {
|
||||||
Self::DenoWarn(message) => (lsp::DiagnosticSeverity::WARNING, message.to_string(), None),
|
Self::DenoWarn(message) => (lsp::DiagnosticSeverity::WARNING, message.to_string(), None),
|
||||||
|
Self::ImportMapRemap { from, to } => (lsp::DiagnosticSeverity::HINT, format!("The import specifier can be remapped to \"{}\" which will resolve it via the active import map.", to), Some(json!({ "from": from, "to": to }))),
|
||||||
Self::InvalidAssertType(assert_type) => (lsp::DiagnosticSeverity::ERROR, format!("The module is a JSON module and expected an assertion type of \"json\". Instead got \"{}\".", assert_type), None),
|
Self::InvalidAssertType(assert_type) => (lsp::DiagnosticSeverity::ERROR, format!("The module is a JSON module and expected an assertion type of \"json\". Instead got \"{}\".", assert_type), None),
|
||||||
Self::NoAssertType => (lsp::DiagnosticSeverity::ERROR, "The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \"json\" }` to the import statement.".to_string(), None),
|
Self::NoAssertType => (lsp::DiagnosticSeverity::ERROR, "The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \"json\" }` to the import statement.".to_string(), None),
|
||||||
Self::NoCache(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing remote URL: \"{}\".", specifier), Some(json!({ "specifier": specifier }))),
|
Self::NoCache(specifier) => (lsp::DiagnosticSeverity::ERROR, format!("Uncached or missing remote URL: \"{}\".", specifier), Some(json!({ "specifier": specifier }))),
|
||||||
|
@ -750,10 +792,9 @@ impl DenoDiagnostic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diagnose_dependency(
|
fn diagnose_resolved(
|
||||||
diagnostics: &mut Vec<lsp::Diagnostic>,
|
diagnostics: &mut Vec<lsp::Diagnostic>,
|
||||||
documents: &Documents,
|
snapshot: &language_server::StateSnapshot,
|
||||||
cache_metadata: &cache::CacheMetadata,
|
|
||||||
resolved: &deno_graph::Resolved,
|
resolved: &deno_graph::Resolved,
|
||||||
is_dynamic: bool,
|
is_dynamic: bool,
|
||||||
maybe_assert_type: Option<&str>,
|
maybe_assert_type: Option<&str>,
|
||||||
|
@ -765,7 +806,7 @@ fn diagnose_dependency(
|
||||||
let range = documents::to_lsp_range(range);
|
let range = documents::to_lsp_range(range);
|
||||||
// If the module is a remote module and has a `X-Deno-Warning` header, we
|
// If the module is a remote module and has a `X-Deno-Warning` header, we
|
||||||
// want a warning diagnostic with that message.
|
// want a warning diagnostic with that message.
|
||||||
if let Some(metadata) = cache_metadata.get(specifier) {
|
if let Some(metadata) = snapshot.cache_metadata.get(specifier) {
|
||||||
if let Some(message) =
|
if let Some(message) =
|
||||||
metadata.get(&cache::MetadataKey::Warning).cloned()
|
metadata.get(&cache::MetadataKey::Warning).cloned()
|
||||||
{
|
{
|
||||||
|
@ -773,7 +814,7 @@ fn diagnose_dependency(
|
||||||
.push(DenoDiagnostic::DenoWarn(message).to_lsp_diagnostic(&range));
|
.push(DenoDiagnostic::DenoWarn(message).to_lsp_diagnostic(&range));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(doc) = documents.get(specifier) {
|
if let Some(doc) = snapshot.documents.get(specifier) {
|
||||||
let doc_specifier = doc.specifier();
|
let doc_specifier = doc.specifier();
|
||||||
// If the module was redirected, we want to issue an informational
|
// If the module was redirected, we want to issue an informational
|
||||||
// diagnostic that indicates this. This then allows us to issue a code
|
// diagnostic that indicates this. This then allows us to issue a code
|
||||||
|
@ -828,9 +869,54 @@ fn diagnose_dependency(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate diagnostics for dependencies of a module, attempting to resolve
|
/// Generate diagnostics related to a dependency. The dependency is analyzed to
|
||||||
/// dependencies on the local file system or in the DENO_DIR cache.
|
/// determine if it can be remapped to the active import map as well as surface
|
||||||
async fn generate_deps_diagnostics(
|
/// any diagnostics related to the resolved code or type dependency.
|
||||||
|
fn diagnose_dependency(
|
||||||
|
diagnostics: &mut Vec<lsp::Diagnostic>,
|
||||||
|
snapshot: &language_server::StateSnapshot,
|
||||||
|
referrer: &ModuleSpecifier,
|
||||||
|
dependency_key: &str,
|
||||||
|
dependency: &deno_graph::Dependency,
|
||||||
|
) {
|
||||||
|
if let Some(import_map) = &snapshot.maybe_import_map {
|
||||||
|
if let Resolved::Ok {
|
||||||
|
specifier, range, ..
|
||||||
|
} = &dependency.maybe_code
|
||||||
|
{
|
||||||
|
if let Some(to) = import_map.lookup(specifier, referrer) {
|
||||||
|
if dependency_key != to {
|
||||||
|
diagnostics.push(
|
||||||
|
DenoDiagnostic::ImportMapRemap {
|
||||||
|
from: dependency_key.to_string(),
|
||||||
|
to,
|
||||||
|
}
|
||||||
|
.to_lsp_diagnostic(&documents::to_lsp_range(range)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diagnose_resolved(
|
||||||
|
diagnostics,
|
||||||
|
snapshot,
|
||||||
|
&dependency.maybe_code,
|
||||||
|
dependency.is_dynamic,
|
||||||
|
dependency.maybe_assert_type.as_deref(),
|
||||||
|
);
|
||||||
|
diagnose_resolved(
|
||||||
|
diagnostics,
|
||||||
|
snapshot,
|
||||||
|
&dependency.maybe_type,
|
||||||
|
dependency.is_dynamic,
|
||||||
|
dependency.maybe_assert_type.as_deref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate diagnostics that come from Deno module resolution logic (like
|
||||||
|
/// dependencies) or other Deno specific diagnostics, like the ability to use
|
||||||
|
/// an import map to shorten an URL.
|
||||||
|
async fn generate_deno_diagnostics(
|
||||||
snapshot: &language_server::StateSnapshot,
|
snapshot: &language_server::StateSnapshot,
|
||||||
config: &ConfigSnapshot,
|
config: &ConfigSnapshot,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
|
@ -844,22 +930,13 @@ async fn generate_deps_diagnostics(
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
let specifier = document.specifier();
|
let specifier = document.specifier();
|
||||||
if config.specifier_enabled(specifier) {
|
if config.specifier_enabled(specifier) {
|
||||||
for (_, dependency) in document.dependencies() {
|
for (dependency_key, dependency) in document.dependencies() {
|
||||||
diagnose_dependency(
|
diagnose_dependency(
|
||||||
&mut diagnostics,
|
&mut diagnostics,
|
||||||
&snapshot.documents,
|
snapshot,
|
||||||
&snapshot.cache_metadata,
|
specifier,
|
||||||
&dependency.maybe_code,
|
&dependency_key,
|
||||||
dependency.is_dynamic,
|
&dependency,
|
||||||
dependency.maybe_assert_type.as_deref(),
|
|
||||||
);
|
|
||||||
diagnose_dependency(
|
|
||||||
&mut diagnostics,
|
|
||||||
&snapshot.documents,
|
|
||||||
&snapshot.cache_metadata,
|
|
||||||
&dependency.maybe_type,
|
|
||||||
dependency.is_dynamic,
|
|
||||||
dependency.maybe_assert_type.as_deref(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -880,15 +957,18 @@ mod tests {
|
||||||
use crate::lsp::config::Settings;
|
use crate::lsp::config::Settings;
|
||||||
use crate::lsp::config::SpecifierSettings;
|
use crate::lsp::config::SpecifierSettings;
|
||||||
use crate::lsp::config::WorkspaceSettings;
|
use crate::lsp::config::WorkspaceSettings;
|
||||||
|
use crate::lsp::documents::Documents;
|
||||||
use crate::lsp::documents::LanguageId;
|
use crate::lsp::documents::LanguageId;
|
||||||
use crate::lsp::language_server::StateSnapshot;
|
use crate::lsp::language_server::StateSnapshot;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
use test_util::TempDir;
|
use test_util::TempDir;
|
||||||
|
|
||||||
fn mock_state_snapshot(
|
fn mock_state_snapshot(
|
||||||
fixtures: &[(&str, &str, i32, LanguageId)],
|
fixtures: &[(&str, &str, i32, LanguageId)],
|
||||||
location: &Path,
|
location: &Path,
|
||||||
|
maybe_import_map: Option<(&str, &str)>,
|
||||||
) -> StateSnapshot {
|
) -> StateSnapshot {
|
||||||
let mut documents = Documents::new(location);
|
let mut documents = Documents::new(location);
|
||||||
for (specifier, source, version, language_id) in fixtures {
|
for (specifier, source, version, language_id) in fixtures {
|
||||||
|
@ -901,8 +981,17 @@ mod tests {
|
||||||
(*source).into(),
|
(*source).into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
let maybe_import_map = maybe_import_map.map(|(base, json_string)| {
|
||||||
|
let base_url = ModuleSpecifier::parse(base).unwrap();
|
||||||
|
let result = import_map::parse_from_json(&base_url, json_string).unwrap();
|
||||||
|
if !result.diagnostics.is_empty() {
|
||||||
|
panic!("unexpected import map diagnostics");
|
||||||
|
}
|
||||||
|
Arc::new(result.import_map)
|
||||||
|
});
|
||||||
StateSnapshot {
|
StateSnapshot {
|
||||||
documents,
|
documents,
|
||||||
|
maybe_import_map,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -924,9 +1013,11 @@ mod tests {
|
||||||
fn setup(
|
fn setup(
|
||||||
temp_dir: &TempDir,
|
temp_dir: &TempDir,
|
||||||
sources: &[(&str, &str, i32, LanguageId)],
|
sources: &[(&str, &str, i32, LanguageId)],
|
||||||
|
maybe_import_map: Option<(&str, &str)>,
|
||||||
) -> (StateSnapshot, PathBuf) {
|
) -> (StateSnapshot, PathBuf) {
|
||||||
let location = temp_dir.path().join("deps");
|
let location = temp_dir.path().join("deps");
|
||||||
let state_snapshot = mock_state_snapshot(sources, &location);
|
let state_snapshot =
|
||||||
|
mock_state_snapshot(sources, &location, maybe_import_map);
|
||||||
(state_snapshot, location)
|
(state_snapshot, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -945,6 +1036,7 @@ let c: number = "a";
|
||||||
1,
|
1,
|
||||||
LanguageId::TypeScript,
|
LanguageId::TypeScript,
|
||||||
)],
|
)],
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
let snapshot = Arc::new(snapshot);
|
let snapshot = Arc::new(snapshot);
|
||||||
let ts_server = TsServer::new(Default::default());
|
let ts_server = TsServer::new(Default::default());
|
||||||
|
@ -969,7 +1061,7 @@ let c: number = "a";
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 4);
|
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 4);
|
||||||
let diagnostics = generate_deps_diagnostics(
|
let diagnostics = generate_deno_diagnostics(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
&enabled_config,
|
&enabled_config,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
@ -1010,7 +1102,7 @@ let c: number = "a";
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 0);
|
assert_eq!(get_diagnostics_for_single(diagnostics).len(), 0);
|
||||||
let diagnostics = generate_deps_diagnostics(
|
let diagnostics = generate_deno_diagnostics(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
&disabled_config,
|
&disabled_config,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
@ -1039,6 +1131,7 @@ let c: number = "a";
|
||||||
1,
|
1,
|
||||||
LanguageId::TypeScript,
|
LanguageId::TypeScript,
|
||||||
)],
|
)],
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
let snapshot = Arc::new(snapshot);
|
let snapshot = Arc::new(snapshot);
|
||||||
let ts_server = TsServer::new(Default::default());
|
let ts_server = TsServer::new(Default::default());
|
||||||
|
@ -1053,4 +1146,128 @@ let c: number = "a";
|
||||||
// should be none because it's cancelled
|
// should be none because it's cancelled
|
||||||
assert_eq!(diagnostics.len(), 0);
|
assert_eq!(diagnostics.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deno_diagnostics_with_import_map() {
|
||||||
|
let temp_dir = TempDir::new();
|
||||||
|
let (snapshot, _) = setup(
|
||||||
|
&temp_dir,
|
||||||
|
&[
|
||||||
|
("file:///std/testing/asserts.ts", "export function assert() {}", 1, LanguageId::TypeScript),
|
||||||
|
("file:///a/file.ts", "import { assert } from \"../std/testing/asserts.ts\";\n\nassert();\n", 1, LanguageId::TypeScript),
|
||||||
|
],
|
||||||
|
Some(("file:///a/import-map.json", r#"{
|
||||||
|
"imports": {
|
||||||
|
"/~/std/": "../std/"
|
||||||
|
}
|
||||||
|
}"#)),
|
||||||
|
);
|
||||||
|
let config = mock_config();
|
||||||
|
let token = CancellationToken::new();
|
||||||
|
let actual = generate_deno_diagnostics(&snapshot, &config, token).await;
|
||||||
|
assert_eq!(actual.len(), 2);
|
||||||
|
for (specifier, _, diagnostics) in actual {
|
||||||
|
match specifier.as_str() {
|
||||||
|
"file:///std/testing/asserts.ts" => {
|
||||||
|
assert_eq!(json!(diagnostics), json!([]))
|
||||||
|
}
|
||||||
|
"file:///a/file.ts" => assert_eq!(
|
||||||
|
json!(diagnostics),
|
||||||
|
json!([
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 23
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"severity": 4,
|
||||||
|
"code": "import-map-remap",
|
||||||
|
"source": "deno",
|
||||||
|
"message": "The import specifier can be remapped to \"/~/std/testing/asserts.ts\" which will resolve it via the active import map.",
|
||||||
|
"data": {
|
||||||
|
"from": "../std/testing/asserts.ts",
|
||||||
|
"to": "/~/std/testing/asserts.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
),
|
||||||
|
_ => unreachable!("unexpected specifier {}", specifier),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_code_action_import_map_remap() {
|
||||||
|
let specifier = ModuleSpecifier::parse("file:///a/file.ts").unwrap();
|
||||||
|
let result = DenoDiagnostic::get_code_action(&specifier, &lsp::Diagnostic {
|
||||||
|
range: lsp::Range {
|
||||||
|
start: lsp::Position { line: 0, character: 23 },
|
||||||
|
end: lsp::Position { line: 0, character: 50 },
|
||||||
|
},
|
||||||
|
severity: Some(lsp::DiagnosticSeverity::HINT),
|
||||||
|
code: Some(lsp::NumberOrString::String("import-map-remap".to_string())),
|
||||||
|
source: Some("deno".to_string()),
|
||||||
|
message: "The import specifier can be remapped to \"/~/std/testing/asserts.ts\" which will resolve it via the active import map.".to_string(),
|
||||||
|
data: Some(json!({
|
||||||
|
"from": "../std/testing/asserts.ts",
|
||||||
|
"to": "/~/std/testing/asserts.ts"
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let actual = result.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
json!(actual),
|
||||||
|
json!({
|
||||||
|
"title": "Update \"../std/testing/asserts.ts\" to \"/~/std/testing/asserts.ts\" to use import map.",
|
||||||
|
"kind": "quickfix",
|
||||||
|
"diagnostics": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 23
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"severity": 4,
|
||||||
|
"code": "import-map-remap",
|
||||||
|
"source": "deno",
|
||||||
|
"message": "The import specifier can be remapped to \"/~/std/testing/asserts.ts\" which will resolve it via the active import map.",
|
||||||
|
"data": {
|
||||||
|
"from": "../std/testing/asserts.ts",
|
||||||
|
"to": "/~/std/testing/asserts.ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edit": {
|
||||||
|
"changes": {
|
||||||
|
"file:///a/file.ts": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 23
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"newText": "\"/~/std/testing/asserts.ts\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@ pub struct StateSnapshot {
|
||||||
pub assets: AssetsSnapshot,
|
pub assets: AssetsSnapshot,
|
||||||
pub cache_metadata: cache::CacheMetadata,
|
pub cache_metadata: cache::CacheMetadata,
|
||||||
pub documents: Documents,
|
pub documents: Documents,
|
||||||
|
pub maybe_import_map: Option<Arc<ImportMap>>,
|
||||||
pub root_uri: Option<Url>,
|
pub root_uri: Option<Url>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,6 +426,7 @@ impl Inner {
|
||||||
assets: self.assets.snapshot(),
|
assets: self.assets.snapshot(),
|
||||||
cache_metadata: self.cache_metadata.clone(),
|
cache_metadata: self.cache_metadata.clone(),
|
||||||
documents: self.documents.clone(),
|
documents: self.documents.clone(),
|
||||||
|
maybe_import_map: self.maybe_import_map.clone(),
|
||||||
root_uri: self.config.root_uri.clone(),
|
root_uri: self.config.root_uri.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue