0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

fix(lsp): provide diagnostics for import assertions (#13105)

Fixes: #13099
This commit is contained in:
Kitson Kelly 2021-12-16 14:53:17 +11:00 committed by GitHub
parent 0f53b82cd2
commit e28fb70aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 230 additions and 25 deletions

View file

@ -384,31 +384,51 @@ pub struct CodeActionCollection {
impl CodeActionCollection {
pub(crate) fn add_deno_fix_action(
&mut self,
specifier: &ModuleSpecifier,
diagnostic: &lsp::Diagnostic,
) -> Result<(), AnyError> {
if let Some(data) = diagnostic.data.clone() {
let fix_data: DenoFixData = serde_json::from_value(data)?;
let title = if matches!(&diagnostic.code, Some(lsp::NumberOrString::String(code)) if code == "no-cache-data")
{
"Cache the data URL and its dependencies.".to_string()
} else {
format!("Cache \"{}\" and its dependencies.", fix_data.specifier)
};
let code_action = lsp::CodeAction {
title,
kind: Some(lsp::CodeActionKind::QUICKFIX),
diagnostics: Some(vec![diagnostic.clone()]),
edit: None,
command: Some(lsp::Command {
title: "".to_string(),
command: "deno.cache".to_string(),
arguments: Some(vec![json!([fix_data.specifier])]),
}),
is_preferred: None,
disabled: None,
data: None,
};
self.actions.push(CodeActionKind::Deno(code_action));
if let Some(lsp::NumberOrString::String(code)) = &diagnostic.code {
if code == "no-assert-type" {
let code_action = lsp::CodeAction {
title: "Insert import assertion.".to_string(),
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: " assert { type: \"json\" }".to_string(),
range: lsp::Range {
start: diagnostic.range.end,
end: diagnostic.range.end,
},
}],
)])),
..Default::default()
}),
..Default::default()
};
self.actions.push(CodeActionKind::Deno(code_action));
} else if let Some(data) = diagnostic.data.clone() {
let fix_data: DenoFixData = serde_json::from_value(data)?;
let title = if code == "no-cache-data" {
"Cache the data URL and its dependencies.".to_string()
} else {
format!("Cache \"{}\" and its dependencies.", fix_data.specifier)
};
let code_action = lsp::CodeAction {
title,
kind: Some(lsp::CodeActionKind::QUICKFIX),
diagnostics: Some(vec![diagnostic.clone()]),
command: Some(lsp::Command {
title: "".to_string(),
command: "deno.cache".to_string(),
arguments: Some(vec![json!([fix_data.specifier])]),
}),
..Default::default()
};
self.actions.push(CodeActionKind::Deno(code_action));
}
}
Ok(())
}

View file

@ -9,6 +9,7 @@ use super::tsc;
use crate::diagnostics;
use deno_ast::MediaType;
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
use deno_core::resolve_url;
@ -439,6 +440,8 @@ fn diagnose_dependency(
diagnostics: &mut Vec<lsp::Diagnostic>,
documents: &Documents,
resolved: &deno_graph::Resolved,
is_dynamic: bool,
maybe_assert_type: Option<&str>,
) {
match resolved {
Some(Ok((specifier, range))) => {
@ -453,6 +456,34 @@ fn diagnose_dependency(
..Default::default()
})
}
if doc.media_type() == MediaType::Json {
match maybe_assert_type {
// The module has the correct assertion type, no diagnostic
Some("json") => (),
// The dynamic import statement is missing an assertion type, which
// we might not be able to statically detect, therefore we will
// not provide a potentially incorrect diagnostic.
None if is_dynamic => (),
// The module has an incorrect assertion type, diagnostic
Some(assert_type) => diagnostics.push(lsp::Diagnostic {
range: documents::to_lsp_range(range),
severity: Some(lsp::DiagnosticSeverity::ERROR),
code: Some(lsp::NumberOrString::String("invalid-assert-type".to_string())),
source: Some("deno".to_string()),
message: format!("The module is a JSON module and expected an assertion type of \"json\". Instead got \"{}\".", assert_type),
..Default::default()
}),
// The module is missing an assertion type, diagnostic
None => diagnostics.push(lsp::Diagnostic {
range: documents::to_lsp_range(range),
severity: Some(lsp::DiagnosticSeverity::ERROR),
code: Some(lsp::NumberOrString::String("no-assert-type".to_string())),
source: Some("deno".to_string()),
message: "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(),
..Default::default()
}),
}
}
} else {
let (code, message) = match specifier.scheme() {
"file" => (Some(lsp::NumberOrString::String("no-local".to_string())), format!("Unable to load a local module: \"{}\".\n Please check the file path.", specifier)),
@ -508,11 +539,15 @@ async fn generate_deps_diagnostics(
&mut diagnostics,
&snapshot.documents,
&dependency.maybe_code,
dependency.is_dynamic,
dependency.maybe_assert_type.as_deref(),
);
diagnose_dependency(
&mut diagnostics,
&snapshot.documents,
&dependency.maybe_type,
dependency.is_dynamic,
dependency.maybe_assert_type.as_deref(),
);
}
diagnostics_vec.push((

View file

@ -1181,7 +1181,10 @@ impl Inner {
"deno-lint" => matches!(&d.code, Some(_)),
"deno" => match &d.code {
Some(NumberOrString::String(code)) => {
code == "no-cache" || code == "no-cache-data"
matches!(
code.as_str(),
"no-cache" | "no-cache-data" | "no-assert-type"
)
}
_ => false,
},
@ -1241,7 +1244,7 @@ impl Inner {
}
}
Some("deno") => code_actions
.add_deno_fix_action(diagnostic)
.add_deno_fix_action(&specifier, diagnostic)
.map_err(|err| {
error!("{}", err);
LspError::internal_error()

View file

@ -344,6 +344,72 @@ fn lsp_import_map_data_url() {
shutdown(&mut client);
}
#[test]
fn lsp_import_assertions() {
let mut client = init("initialize_params_import_map.json");
client
.write_notification(
"textDocument/didOpen",
json!({
"textDocument": {
"uri": "file:///a/test.json",
"languageId": "json",
"version": 1,
"text": "{\"a\":1}"
}
}),
)
.unwrap();
let mut diagnostics = did_open(
&mut client,
json!({
"textDocument": {
"uri": "file:///a/a.ts",
"languageId": "typescript",
"version": 1,
"text": "import a from \"./test.json\";\n\nconsole.log(a);\n"
}
}),
);
let last = diagnostics.pop().unwrap();
assert_eq!(
json!(last.diagnostics),
json!([
{
"range": {
"start": {
"line": 0,
"character": 14
},
"end": {
"line": 0,
"character": 27
}
},
"severity": 1,
"code": "no-assert-type",
"source": "deno",
"message": "The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \"json\" }` to the import statement."
}
])
);
let (maybe_res, maybe_err) = client
.write_request(
"textDocument/codeAction",
load_fixture("code_action_params_import_assertion.json"),
)
.unwrap();
assert!(maybe_err.is_none());
assert_eq!(
maybe_res,
Some(load_fixture("code_action_response_import_assertion.json"))
);
shutdown(&mut client);
}
#[test]
fn lsp_hover() {
let mut client = init("initialize_params.json");

View file

@ -0,0 +1,38 @@
{
"textDocument": {
"uri": "file:///a/a.ts"
},
"range": {
"start": {
"line": 0,
"character": 14
},
"end": {
"line": 0,
"character": 27
}
},
"context": {
"diagnostics": [
{
"range": {
"start": {
"line": 0,
"character": 14
},
"end": {
"line": 0,
"character": 27
}
},
"severity": 1,
"code": "no-assert-type",
"source": "deno",
"message": "The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \"json\" }` to the import statement."
}
],
"only": [
"quickfix"
]
}
}

View file

@ -0,0 +1,43 @@
[
{
"title": "Insert import assertion.",
"kind": "quickfix",
"diagnostics": [
{
"range": {
"start": {
"line": 0,
"character": 14
},
"end": {
"line": 0,
"character": 27
}
},
"severity": 1,
"code": "no-assert-type",
"source": "deno",
"message": "The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \"json\" }` to the import statement."
}
],
"edit": {
"changes": {
"file:///a/a.ts": [
{
"range": {
"start": {
"line": 0,
"character": 27
},
"end": {
"line": 0,
"character": 27
}
},
"newText": " assert { type: \"json\" }"
}
]
}
}
}
]