summaryrefslogtreecommitdiff
path: root/cli/lsp/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/lsp/diagnostics.rs')
-rw-r--r--cli/lsp/diagnostics.rs268
1 files changed, 268 insertions, 0 deletions
diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs
new file mode 100644
index 000000000..a7f027c1b
--- /dev/null
+++ b/cli/lsp/diagnostics.rs
@@ -0,0 +1,268 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+use super::analysis::get_lint_references;
+use super::analysis::references_to_diagnostics;
+use super::memory_cache::FileId;
+use super::state::ServerStateSnapshot;
+use super::tsc;
+
+use crate::diagnostics;
+use crate::media_type::MediaType;
+
+use deno_core::error::AnyError;
+use deno_core::serde_json;
+use deno_core::serde_json::Value;
+use deno_core::url::Url;
+use deno_core::JsRuntime;
+use std::collections::HashMap;
+use std::collections::HashSet;
+use std::mem;
+
+impl<'a> From<&'a diagnostics::DiagnosticCategory>
+ for lsp_types::DiagnosticSeverity
+{
+ fn from(category: &'a diagnostics::DiagnosticCategory) -> Self {
+ match category {
+ diagnostics::DiagnosticCategory::Error => {
+ lsp_types::DiagnosticSeverity::Error
+ }
+ diagnostics::DiagnosticCategory::Warning => {
+ lsp_types::DiagnosticSeverity::Warning
+ }
+ diagnostics::DiagnosticCategory::Suggestion => {
+ lsp_types::DiagnosticSeverity::Hint
+ }
+ diagnostics::DiagnosticCategory::Message => {
+ lsp_types::DiagnosticSeverity::Information
+ }
+ }
+ }
+}
+
+impl<'a> From<&'a diagnostics::Position> for lsp_types::Position {
+ fn from(pos: &'a diagnostics::Position) -> Self {
+ Self {
+ line: pos.line as u32,
+ character: pos.character as u32,
+ }
+ }
+}
+
+fn to_lsp_range(
+ start: &diagnostics::Position,
+ end: &diagnostics::Position,
+) -> lsp_types::Range {
+ lsp_types::Range {
+ start: start.into(),
+ end: end.into(),
+ }
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+pub enum DiagnosticSource {
+ Lint,
+ TypeScript,
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct DiagnosticCollection {
+ map: HashMap<(FileId, DiagnosticSource), Vec<lsp_types::Diagnostic>>,
+ versions: HashMap<FileId, i32>,
+ changes: HashSet<FileId>,
+}
+
+impl DiagnosticCollection {
+ pub fn set(
+ &mut self,
+ file_id: FileId,
+ source: DiagnosticSource,
+ version: Option<i32>,
+ diagnostics: Vec<lsp_types::Diagnostic>,
+ ) {
+ self.map.insert((file_id, source), diagnostics);
+ if let Some(version) = version {
+ self.versions.insert(file_id, version);
+ }
+ self.changes.insert(file_id);
+ }
+
+ pub fn diagnostics_for(
+ &self,
+ file_id: FileId,
+ source: DiagnosticSource,
+ ) -> impl Iterator<Item = &lsp_types::Diagnostic> {
+ self.map.get(&(file_id, source)).into_iter().flatten()
+ }
+
+ pub fn get_version(&self, file_id: &FileId) -> Option<i32> {
+ self.versions.get(file_id).cloned()
+ }
+
+ pub fn take_changes(&mut self) -> Option<HashSet<FileId>> {
+ if self.changes.is_empty() {
+ return None;
+ }
+ Some(mem::take(&mut self.changes))
+ }
+}
+
+pub type DiagnosticVec = Vec<(FileId, Option<i32>, Vec<lsp_types::Diagnostic>)>;
+
+pub fn generate_linting_diagnostics(
+ state: &ServerStateSnapshot,
+) -> DiagnosticVec {
+ if !state.config.settings.lint {
+ return Vec::new();
+ }
+ let mut diagnostics = Vec::new();
+ let file_cache = state.file_cache.read().unwrap();
+ for (specifier, doc_data) in state.doc_data.iter() {
+ let file_id = file_cache.lookup(specifier).unwrap();
+ let version = doc_data.version;
+ let current_version = state.diagnostics.get_version(&file_id);
+ if version != current_version {
+ let media_type = MediaType::from(specifier);
+ if let Ok(source_code) = file_cache.get_contents(file_id) {
+ if let Ok(references) =
+ get_lint_references(specifier, &media_type, &source_code)
+ {
+ if !references.is_empty() {
+ diagnostics.push((
+ file_id,
+ version,
+ references_to_diagnostics(references),
+ ));
+ } else {
+ diagnostics.push((file_id, version, Vec::new()));
+ }
+ }
+ } else {
+ error!("Missing file contents for: {}", specifier);
+ }
+ }
+ }
+
+ diagnostics
+}
+
+type TsDiagnostics = Vec<diagnostics::Diagnostic>;
+
+fn get_diagnostic_message(diagnostic: &diagnostics::Diagnostic) -> String {
+ if let Some(message) = diagnostic.message_text.clone() {
+ message
+ } else if let Some(message_chain) = diagnostic.message_chain.clone() {
+ message_chain.format_message(0)
+ } else {
+ "[missing message]".to_string()
+ }
+}
+
+fn to_lsp_related_information(
+ related_information: &Option<Vec<diagnostics::Diagnostic>>,
+) -> Option<Vec<lsp_types::DiagnosticRelatedInformation>> {
+ if let Some(related) = related_information {
+ Some(
+ related
+ .iter()
+ .filter_map(|ri| {
+ if let (Some(source), Some(start), Some(end)) =
+ (&ri.source, &ri.start, &ri.end)
+ {
+ let uri = Url::parse(&source).unwrap();
+ Some(lsp_types::DiagnosticRelatedInformation {
+ location: lsp_types::Location {
+ uri,
+ range: to_lsp_range(start, end),
+ },
+ message: get_diagnostic_message(&ri),
+ })
+ } else {
+ None
+ }
+ })
+ .collect(),
+ )
+ } else {
+ None
+ }
+}
+
+fn ts_json_to_diagnostics(
+ value: Value,
+) -> Result<Vec<lsp_types::Diagnostic>, AnyError> {
+ let ts_diagnostics: TsDiagnostics = serde_json::from_value(value)?;
+ Ok(
+ ts_diagnostics
+ .iter()
+ .filter_map(|d| {
+ if let (Some(start), Some(end)) = (&d.start, &d.end) {
+ Some(lsp_types::Diagnostic {
+ range: to_lsp_range(start, end),
+ severity: Some((&d.category).into()),
+ code: Some(lsp_types::NumberOrString::Number(d.code as i32)),
+ code_description: None,
+ source: Some("deno-ts".to_string()),
+ message: get_diagnostic_message(d),
+ related_information: to_lsp_related_information(
+ &d.related_information,
+ ),
+ tags: match d.code {
+ // These are codes that indicate the variable is unused.
+ 6133 | 6192 | 6196 => {
+ Some(vec![lsp_types::DiagnosticTag::Unnecessary])
+ }
+ _ => None,
+ },
+ data: None,
+ })
+ } else {
+ None
+ }
+ })
+ .collect(),
+ )
+}
+
+pub fn generate_ts_diagnostics(
+ state: &ServerStateSnapshot,
+ runtime: &mut JsRuntime,
+) -> Result<DiagnosticVec, AnyError> {
+ if !state.config.settings.enable {
+ return Ok(Vec::new());
+ }
+ let mut diagnostics = Vec::new();
+ let file_cache = state.file_cache.read().unwrap();
+ for (specifier, doc_data) in state.doc_data.iter() {
+ let file_id = file_cache.lookup(specifier).unwrap();
+ let version = doc_data.version;
+ let current_version = state.diagnostics.get_version(&file_id);
+ if version != current_version {
+ // TODO(@kitsonk): consider refactoring to get all diagnostics in one shot
+ // for a file.
+ let request_semantic_diagnostics =
+ tsc::RequestMethod::GetSemanticDiagnostics(specifier.clone());
+ let mut ts_diagnostics = ts_json_to_diagnostics(tsc::request(
+ runtime,
+ state,
+ request_semantic_diagnostics,
+ )?)?;
+ let request_suggestion_diagnostics =
+ tsc::RequestMethod::GetSuggestionDiagnostics(specifier.clone());
+ ts_diagnostics.append(&mut ts_json_to_diagnostics(tsc::request(
+ runtime,
+ state,
+ request_suggestion_diagnostics,
+ )?)?);
+ let request_syntactic_diagnostics =
+ tsc::RequestMethod::GetSyntacticDiagnostics(specifier.clone());
+ ts_diagnostics.append(&mut ts_json_to_diagnostics(tsc::request(
+ runtime,
+ state,
+ request_syntactic_diagnostics,
+ )?)?);
+ diagnostics.push((file_id, version, ts_diagnostics));
+ }
+ }
+
+ Ok(diagnostics)
+}