diff options
-rw-r--r-- | cli/lsp/analysis.rs | 66 | ||||
-rw-r--r-- | cli/lsp/diagnostics.rs | 35 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 7 | ||||
-rw-r--r-- | cli/tests/integration/lsp_tests.rs | 66 | ||||
-rw-r--r-- | cli/tests/testdata/lsp/code_action_params_import_assertion.json | 38 | ||||
-rw-r--r-- | cli/tests/testdata/lsp/code_action_response_import_assertion.json | 43 |
6 files changed, 230 insertions, 25 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index de1aa91b5..d21abae87 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -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(()) } diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 82a08c8f9..12d403ebb 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -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(( diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 660ef8d90..384b100b2 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -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() diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 718f73311..a3e1138b6 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -345,6 +345,72 @@ fn lsp_import_map_data_url() { } #[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"); did_open( diff --git a/cli/tests/testdata/lsp/code_action_params_import_assertion.json b/cli/tests/testdata/lsp/code_action_params_import_assertion.json new file mode 100644 index 000000000..67b822a42 --- /dev/null +++ b/cli/tests/testdata/lsp/code_action_params_import_assertion.json @@ -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" + ] + } +} diff --git a/cli/tests/testdata/lsp/code_action_response_import_assertion.json b/cli/tests/testdata/lsp/code_action_response_import_assertion.json new file mode 100644 index 000000000..bff934b21 --- /dev/null +++ b/cli/tests/testdata/lsp/code_action_response_import_assertion.json @@ -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\" }" + } + ] + } + } + } +] |