Skip to content

Commit

Permalink
feat: support diagnostics without ranges (#1299)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Jul 24, 2024
1 parent 1cdd0fa commit fc21439
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 96 deletions.
33 changes: 22 additions & 11 deletions examples/dlint/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,28 @@ pub fn display_diagnostics(

fn print_compact(diagnostics: &[LintDiagnostic]) {
for diagnostic in diagnostics {
let display_index = diagnostic
.text_info
.line_and_column_display(diagnostic.range.start);
eprintln!(
"{}: line {}, col {}, Error - {} ({})",
diagnostic.specifier,
display_index.line_number,
display_index.column_number,
diagnostic.message,
diagnostic.code
)
match &diagnostic.range {
Some(range) => {
let display_index =
range.text_info.line_and_column_display(range.range.start);
eprintln!(
"{}: line {}, col {}, Error - {} ({})",
diagnostic.specifier,
display_index.line_number,
display_index.column_number,
diagnostic.details.message,
diagnostic.details.code
)
}
None => {
eprintln!(
"{}: {} ({})",
diagnostic.specifier,
diagnostic.message(),
diagnostic.code()
)
}
}
}
}

Expand Down
140 changes: 92 additions & 48 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use crate::control_flow::ControlFlow;
use crate::diagnostic::{LintDiagnostic, LintFix};
use crate::diagnostic::{
LintDiagnostic, LintDiagnosticDetails, LintDiagnosticRange, LintFix,
};
use crate::ignore_directives::{
parse_line_ignore_directives, CodeStatus, FileIgnoreDirective,
LineIgnoreDirective,
Expand Down Expand Up @@ -234,18 +236,20 @@ impl<'a> Context<'a> {

for diagnostic in self.diagnostics.iter().cloned() {
if let Some(f) = self.file_ignore_directive.as_mut() {
if f.check_used(&diagnostic.code) {
if f.check_used(&diagnostic.details.code) {
continue;
}
}
let Some(range) = diagnostic.range.as_ref() else {
continue;
};

let diagnostic_line =
diagnostic.text_info.line_index(diagnostic.range.start);
let diagnostic_line = range.text_info.line_index(range.range.start);
if diagnostic_line > 0 {
if let Some(l) =
self.line_ignore_directives.get_mut(&(diagnostic_line - 1))
{
if l.check_used(&diagnostic.code) {
if l.check_used(&diagnostic.details.code) {
continue;
}
}
Expand Down Expand Up @@ -290,11 +294,13 @@ impl<'a> Context<'a> {
file_ignore.codes().iter().filter(is_unused_code)
{
let d = self.create_diagnostic(
file_ignore.range(),
CODE,
format!("Ignore for code \"{}\" was not used.", unused_code),
None,
Vec::new(),
Some(self.create_diagnostic_range(file_ignore.range())),
self.create_diagnostic_details(
CODE,
format!("Ignore for code \"{}\" was not used.", unused_code),
None,
Vec::new(),
),
);
diagnostics.push(d);
}
Expand All @@ -309,11 +315,13 @@ impl<'a> Context<'a> {
line_ignore.codes().iter().filter(is_unused_code)
{
let d = self.create_diagnostic(
line_ignore.range(),
CODE,
format!("Ignore for code \"{}\" was not used.", unused_code),
None,
Vec::new(),
Some(self.create_diagnostic_range(line_ignore.range())),
self.create_diagnostic_details(
CODE,
format!("Ignore for code \"{}\" was not used.", unused_code),
None,
Vec::new(),
),
);
diagnostics.push(d);
}
Expand All @@ -337,11 +345,13 @@ impl<'a> Context<'a> {
file_ignore.codes().keys().filter(is_unknown_rule)
{
let d = self.create_diagnostic(
file_ignore.range(),
rules::ban_unknown_rule_code::CODE,
format!("Unknown rule for code \"{}\"", unknown_rule_code),
None,
Vec::new(),
Some(self.create_diagnostic_range(file_ignore.range())),
self.create_diagnostic_details(
rules::ban_unknown_rule_code::CODE,
format!("Unknown rule for code \"{}\"", unknown_rule_code),
None,
Vec::new(),
),
);
diagnostics.push(d);
}
Expand All @@ -352,11 +362,13 @@ impl<'a> Context<'a> {
line_ignore.codes().keys().filter(is_unknown_rule)
{
let d = self.create_diagnostic(
line_ignore.range(),
rules::ban_unknown_rule_code::CODE,
format!("Unknown rule for code \"{}\"", unknown_rule_code),
None,
Vec::new(),
Some(self.create_diagnostic_range(line_ignore.range())),
self.create_diagnostic_details(
rules::ban_unknown_rule_code::CODE,
format!("Unknown rule for code \"{}\"", unknown_rule_code),
None,
Vec::new(),
),
);
diagnostics.push(d);
}
Expand Down Expand Up @@ -386,14 +398,15 @@ impl<'a> Context<'a> {
code: impl ToString,
message: impl ToString,
) {
let diagnostic = self.create_diagnostic(
range,
code,
message.to_string(),
None,
Vec::new(),
self.add_diagnostic_details(
Some(self.create_diagnostic_range(range)),
self.create_diagnostic_details(
code,
message.to_string(),
None,
Vec::new(),
),
);
self.diagnostics.push(diagnostic);
}

pub fn add_diagnostic_with_hint(
Expand All @@ -403,14 +416,15 @@ impl<'a> Context<'a> {
message: impl ToString,
hint: impl ToString,
) {
let diagnostic = self.create_diagnostic(
range,
code,
message,
Some(hint.to_string()),
Vec::new(),
self.add_diagnostic_details(
Some(self.create_diagnostic_range(range)),
self.create_diagnostic_details(
code,
message,
Some(hint.to_string()),
Vec::new(),
),
);
self.diagnostics.push(diagnostic);
}

pub fn add_diagnostic_with_fixes(
Expand All @@ -421,31 +435,61 @@ impl<'a> Context<'a> {
hint: Option<String>,
fixes: Vec<LintFix>,
) {
let diagnostic = self.create_diagnostic(range, code, message, hint, fixes);
self.diagnostics.push(diagnostic);
self.add_diagnostic_details(
Some(self.create_diagnostic_range(range)),
self.create_diagnostic_details(code, message, hint, fixes),
);
}

pub fn add_diagnostic_details(
&mut self,
maybe_range: Option<LintDiagnosticRange>,
details: LintDiagnosticDetails,
) {
self
.diagnostics
.push(self.create_diagnostic(maybe_range, details));
}

pub(crate) fn create_diagnostic(
&self,
range: SourceRange,
maybe_range: Option<LintDiagnosticRange>,
details: LintDiagnosticDetails,
) -> LintDiagnostic {
LintDiagnostic {
specifier: self.specifier().clone(),
range: maybe_range,
details,
}
}

pub(crate) fn create_diagnostic_details(
&self,
code: impl ToString,
message: impl ToString,
maybe_hint: Option<String>,
fixes: Vec<LintFix>,
) -> LintDiagnostic {
LintDiagnostic {
specifier: self.specifier().clone(),
range,
text_info: self.text_info().clone(),
) -> LintDiagnosticDetails {
LintDiagnosticDetails {
message: message.to_string(),
code: code.to_string(),
hint: maybe_hint,
fixes,
custom_docs_url: None,
range_description: None,
info: vec![],
}
}

pub(crate) fn create_diagnostic_range(
&self,
range: SourceRange,
) -> LintDiagnosticRange {
LintDiagnosticRange {
range,
text_info: self.text_info().clone(),
description: None,
}
}
}

/// A struct containing a boolean value to control whether a node's children
Expand Down
67 changes: 45 additions & 22 deletions src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@ pub struct LintFix {
}

#[derive(Clone)]
pub struct LintDiagnostic {
pub specifier: ModuleSpecifier,
pub range: SourceRange,
pub struct LintDiagnosticRange {
pub text_info: SourceTextInfo,
pub range: SourceRange,
/// Additional information displayed beside the highlighted range.
pub description: Option<String>,
}

#[derive(Clone)]
pub struct LintDiagnosticDetails {
pub message: String,
pub code: String,
pub hint: Option<String>,
Expand All @@ -44,67 +49,85 @@ pub struct LintDiagnostic {
/// URL to the lint rule documentation. By default, the url uses the
/// code to link to lint.deno.land
pub custom_docs_url: Option<String>,
/// Additional information displayed beside the highlighted range.
pub range_description: Option<String>,
/// Displays additional information at the end of a diagnostic.
pub info: Vec<Cow<'static, str>>,
}

#[derive(Clone)]
pub struct LintDiagnostic {
pub specifier: ModuleSpecifier,
/// Optional range within the file.
///
/// Diagnostics that don't have a range mean there's something wrong with
/// the whole file.
pub range: Option<LintDiagnosticRange>,
pub details: LintDiagnosticDetails,
}

impl Diagnostic for LintDiagnostic {
fn level(&self) -> DiagnosticLevel {
DiagnosticLevel::Error
}

fn code(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.code)
Cow::Borrowed(&self.details.code)
}

fn message(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.message)
Cow::Borrowed(&self.details.message)
}

fn location(&self) -> DiagnosticLocation {
DiagnosticLocation::ModulePosition {
specifier: Cow::Borrowed(&self.specifier),
text_info: Cow::Borrowed(&self.text_info),
source_pos: DiagnosticSourcePos::SourcePos(self.range.start),
match &self.range {
Some(range) => DiagnosticLocation::ModulePosition {
specifier: Cow::Borrowed(&self.specifier),
text_info: Cow::Borrowed(&range.text_info),
source_pos: DiagnosticSourcePos::SourcePos(range.range.start),
},
None => DiagnosticLocation::Module {
specifier: Cow::Borrowed(&self.specifier),
},
}
}

fn snippet(&self) -> Option<DiagnosticSnippet<'_>> {
let range = DiagnosticSourceRange {
start: DiagnosticSourcePos::SourcePos(self.range.start),
end: DiagnosticSourcePos::SourcePos(self.range.end),
};
let range = self.range.as_ref()?;
Some(DiagnosticSnippet {
source: Cow::Borrowed(&self.text_info),
source: Cow::Borrowed(&range.text_info),
highlights: vec![DiagnosticSnippetHighlight {
range,
range: DiagnosticSourceRange {
start: DiagnosticSourcePos::SourcePos(range.range.start),
end: DiagnosticSourcePos::SourcePos(range.range.end),
},
style: DiagnosticSnippetHighlightStyle::Error,
description: self.range_description.as_deref().map(Cow::Borrowed),
description: range.description.as_deref().map(Cow::Borrowed),
}],
})
}

fn hint(&self) -> Option<Cow<'_, str>> {
self.hint.as_ref().map(|s| Cow::Borrowed(s.as_str()))
self
.details
.hint
.as_ref()
.map(|s| Cow::Borrowed(s.as_str()))
}

fn snippet_fixed(&self) -> Option<DiagnosticSnippet<'_>> {
None // todo
}

fn info(&self) -> Cow<'_, [std::borrow::Cow<'_, str>]> {
Cow::Borrowed(&self.info)
Cow::Borrowed(&self.details.info)
}

fn docs_url(&self) -> Option<Cow<'_, str>> {
if let Some(custom_docs_url) = &self.custom_docs_url {
if let Some(custom_docs_url) = &self.details.custom_docs_url {
Some(Cow::Borrowed(custom_docs_url))
} else {
Some(Cow::Owned(format!(
"https://lint.deno.land/rules/{}",
&self.code
&self.details.code
)))
}
}
Expand Down
Loading

0 comments on commit fc21439

Please sign in to comment.