summaryrefslogtreecommitdiff
path: root/cli/tools/lint.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tools/lint.rs')
-rw-r--r--cli/tools/lint.rs207
1 files changed, 104 insertions, 103 deletions
diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs
index 20fd12ce2..8de8160de 100644
--- a/cli/tools/lint.rs
+++ b/cli/tools/lint.rs
@@ -8,6 +8,16 @@ use crate::args::LintOptions;
use crate::args::LintReporterKind;
use crate::args::LintRulesConfig;
use crate::colors;
+use crate::diagnostics::Diagnostic;
+use crate::diagnostics::DiagnosticLevel;
+use crate::diagnostics::DiagnosticLocation;
+use crate::diagnostics::DiagnosticSnippet;
+use crate::diagnostics::DiagnosticSnippetHighlight;
+use crate::diagnostics::DiagnosticSnippetHighlightStyle;
+use crate::diagnostics::DiagnosticSnippetSource;
+use crate::diagnostics::DiagnosticSourcePos;
+use crate::diagnostics::DiagnosticSourceRange;
+use crate::diagnostics::SourceTextStore;
use crate::factory::CliFactory;
use crate::tools::fmt::run_parallelized;
use crate::util::file_watcher;
@@ -16,22 +26,25 @@ use crate::util::fs::FileCollector;
use crate::util::path::is_script_ext;
use crate::util::sync::AtomicFlag;
use deno_ast::MediaType;
+use deno_ast::ModuleSpecifier;
+use deno_ast::ParsedSource;
+use deno_ast::SourceTextInfo;
use deno_config::glob::FilePatterns;
use deno_core::anyhow::bail;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
-use deno_core::error::JsStackFrame;
use deno_core::serde_json;
+use deno_core::url;
use deno_lint::diagnostic::LintDiagnostic;
use deno_lint::linter::LintFileOptions;
use deno_lint::linter::Linter;
use deno_lint::linter::LinterBuilder;
use deno_lint::rules;
use deno_lint::rules::LintRule;
-use deno_runtime::fmt_errors::format_location;
use log::debug;
use log::info;
use serde::Serialize;
+use std::borrow::Cow;
use std::fs;
use std::io::stdin;
use std::io::Read;
@@ -42,7 +55,7 @@ use std::sync::Mutex;
use crate::cache::IncrementalCache;
-static STDIN_FILE_NAME: &str = "_stdin.ts";
+static STDIN_FILE_NAME: &str = "$deno$stdin.ts";
fn create_reporter(kind: LintReporterKind) -> Box<dyn LintReporter + Send> {
match kind {
@@ -110,9 +123,10 @@ pub async fn lint(flags: Flags, lint_flags: LintFlags) -> Result<(), AnyError> {
let reporter_kind = lint_options.reporter_kind;
let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind)));
let lint_rules = get_config_rules_err_empty(lint_options.rules)?;
- let r = lint_stdin(lint_rules);
- let success =
- handle_lint_result(STDIN_FILE_NAME, r, reporter_lock.clone());
+ let file_path = cli_options.initial_cwd().join(STDIN_FILE_NAME);
+ let file_path = file_path.to_string_lossy();
+ let r = lint_stdin(&file_path, lint_rules);
+ let success = handle_lint_result(&file_path, r, reporter_lock.clone());
reporter_lock.lock().unwrap().close(1);
success
} else {
@@ -173,10 +187,11 @@ async fn lint_files(
}
let r = lint_file(&file_path, file_text, lint_rules);
- if let Ok((file_diagnostics, file_text)) = &r {
+ if let Ok((file_diagnostics, file_source)) = &r {
if file_diagnostics.is_empty() {
// update the incremental cache if there were no diagnostics
- incremental_cache.update_file(&file_path, file_text)
+ incremental_cache
+ .update_file(&file_path, file_source.text_info().text_str())
}
}
@@ -262,27 +277,28 @@ fn lint_file(
file_path: &Path,
source_code: String,
lint_rules: Vec<&'static dyn LintRule>,
-) -> Result<(Vec<LintDiagnostic>, String), AnyError> {
+) -> Result<(Vec<LintDiagnostic>, ParsedSource), AnyError> {
let filename = file_path.to_string_lossy().to_string();
let media_type = MediaType::from_path(file_path);
let linter = create_linter(lint_rules);
- let (_, file_diagnostics) = linter.lint_file(LintFileOptions {
+ let (source, file_diagnostics) = linter.lint_file(LintFileOptions {
filename,
media_type,
source_code: source_code.clone(),
})?;
- Ok((file_diagnostics, source_code))
+ Ok((file_diagnostics, source))
}
/// Lint stdin and write result to stdout.
/// Treats input as TypeScript.
/// Compatible with `--json` flag.
fn lint_stdin(
+ file_path: &str,
lint_rules: Vec<&'static dyn LintRule>,
-) -> Result<(Vec<LintDiagnostic>, String), AnyError> {
+) -> Result<(Vec<LintDiagnostic>, ParsedSource), AnyError> {
let mut source_code = String::new();
if stdin().read_to_string(&mut source_code).is_err() {
return Err(generic_error("Failed to read from stdin"));
@@ -290,18 +306,18 @@ fn lint_stdin(
let linter = create_linter(lint_rules);
- let (_, file_diagnostics) = linter.lint_file(LintFileOptions {
- filename: STDIN_FILE_NAME.to_string(),
+ let (source, file_diagnostics) = linter.lint_file(LintFileOptions {
+ filename: file_path.to_string(),
source_code: source_code.clone(),
media_type: MediaType::TypeScript,
})?;
- Ok((file_diagnostics, source_code))
+ Ok((file_diagnostics, source))
}
fn handle_lint_result(
file_path: &str,
- result: Result<(Vec<LintDiagnostic>, String), AnyError>,
+ result: Result<(Vec<LintDiagnostic>, ParsedSource), AnyError>,
reporter_lock: Arc<Mutex<Box<dyn LintReporter + Send>>>,
) -> bool {
let mut reporter = reporter_lock.lock().unwrap();
@@ -310,7 +326,7 @@ fn handle_lint_result(
Ok((mut file_diagnostics, source)) => {
sort_diagnostics(&mut file_diagnostics);
for d in file_diagnostics.iter() {
- reporter.visit_diagnostic(d, source.split('\n').collect());
+ reporter.visit_diagnostic(d, &source);
}
file_diagnostics.is_empty()
}
@@ -322,7 +338,7 @@ fn handle_lint_result(
}
trait LintReporter {
- fn visit_diagnostic(&mut self, d: &LintDiagnostic, source_lines: Vec<&str>);
+ fn visit_diagnostic(&mut self, d: &LintDiagnostic, source: &ParsedSource);
fn visit_error(&mut self, file_path: &str, err: &AnyError);
fn close(&mut self, check_count: usize);
}
@@ -343,29 +359,77 @@ impl PrettyLintReporter {
}
}
+impl Diagnostic for LintDiagnostic {
+ fn level(&self) -> DiagnosticLevel {
+ DiagnosticLevel::Error
+ }
+
+ fn code(&self) -> impl std::fmt::Display + '_ {
+ &self.code
+ }
+
+ fn message(&self) -> impl std::fmt::Display + '_ {
+ &self.message
+ }
+
+ fn location(&self) -> DiagnosticLocation {
+ let specifier = url::Url::from_file_path(&self.filename).unwrap();
+ DiagnosticLocation::PositionInFile {
+ specifier: Cow::Owned(specifier),
+ source_pos: DiagnosticSourcePos::ByteIndex(self.range.start.byte_index),
+ }
+ }
+
+ fn snippet(&self) -> Option<DiagnosticSnippet<'_>> {
+ let specifier = url::Url::from_file_path(&self.filename).unwrap();
+ let range = DiagnosticSourceRange {
+ start: DiagnosticSourcePos::ByteIndex(self.range.start.byte_index),
+ end: DiagnosticSourcePos::ByteIndex(self.range.end.byte_index),
+ };
+ Some(DiagnosticSnippet {
+ source: DiagnosticSnippetSource::Specifier(Cow::Owned(specifier)),
+ highlight: DiagnosticSnippetHighlight {
+ range,
+ style: DiagnosticSnippetHighlightStyle::Error,
+ description: None,
+ },
+ })
+ }
+
+ fn hint(&self) -> Option<impl std::fmt::Display + '_> {
+ self.hint.as_ref().map(|h| h as &dyn std::fmt::Display)
+ }
+
+ fn snippet_fixed(&self) -> Option<DiagnosticSnippet<'_>> {
+ None // todo
+ }
+
+ fn info(&self) -> Cow<'_, [std::borrow::Cow<'_, str>]> {
+ Cow::Borrowed(&[])
+ }
+
+ fn docs_url(&self) -> Option<impl std::fmt::Display + '_> {
+ Some(format!("https://lint.deno.land/#{}", &self.code))
+ }
+}
+
+struct OneSource<'a>(&'a ParsedSource);
+
+impl SourceTextStore for OneSource<'_> {
+ fn get_source_text<'a>(
+ &'a self,
+ _specifier: &ModuleSpecifier,
+ ) -> Option<Cow<'a, SourceTextInfo>> {
+ Some(Cow::Borrowed(self.0.text_info()))
+ }
+}
+
impl LintReporter for PrettyLintReporter {
- fn visit_diagnostic(&mut self, d: &LintDiagnostic, source_lines: Vec<&str>) {
+ fn visit_diagnostic(&mut self, d: &LintDiagnostic, source: &ParsedSource) {
self.lint_count += 1;
- let pretty_message = format!("({}) {}", colors::red(&d.code), &d.message);
-
- let message = format_diagnostic(
- &d.code,
- &pretty_message,
- &source_lines,
- &d.range,
- d.hint.as_ref(),
- &format_location(&JsStackFrame::from_location(
- Some(d.filename.clone()),
- // todo(dsherret): these should use "display positions"
- // which take into account the added column index of tab
- // indentation
- Some(d.range.start.line_index as i64 + 1),
- Some(d.range.start.column_index as i64 + 1),
- )),
- );
-
- eprintln!("{message}\n");
+ let sources = OneSource(source);
+ eprintln!("{}", d.display(&sources));
}
fn visit_error(&mut self, file_path: &str, err: &AnyError) {
@@ -399,7 +463,7 @@ impl CompactLintReporter {
}
impl LintReporter for CompactLintReporter {
- fn visit_diagnostic(&mut self, d: &LintDiagnostic, _source_lines: Vec<&str>) {
+ fn visit_diagnostic(&mut self, d: &LintDiagnostic, _source: &ParsedSource) {
self.lint_count += 1;
eprintln!(
@@ -432,69 +496,6 @@ impl LintReporter for CompactLintReporter {
}
}
-pub fn format_diagnostic(
- diagnostic_code: &str,
- message_line: &str,
- source_lines: &[&str],
- range: &deno_lint::diagnostic::Range,
- maybe_hint: Option<&String>,
- formatted_location: &str,
-) -> String {
- let mut lines = vec![];
-
- for (i, line) in source_lines
- .iter()
- .enumerate()
- .take(range.end.line_index + 1)
- .skip(range.start.line_index)
- {
- lines.push(line.to_string());
- if range.start.line_index == range.end.line_index {
- lines.push(format!(
- "{}{}",
- " ".repeat(range.start.column_index),
- colors::red(
- &"^".repeat(range.end.column_index - range.start.column_index)
- )
- ));
- } else {
- let line_len = line.len();
- if range.start.line_index == i {
- lines.push(format!(
- "{}{}",
- " ".repeat(range.start.column_index),
- colors::red(&"^".repeat(line_len - range.start.column_index))
- ));
- } else if range.end.line_index == i {
- lines
- .push(colors::red(&"^".repeat(range.end.column_index)).to_string());
- } else if line_len != 0 {
- lines.push(colors::red(&"^".repeat(line_len)).to_string());
- }
- }
- }
-
- let hint = if let Some(hint) = maybe_hint {
- format!(" {} {}\n", colors::cyan("hint:"), hint)
- } else {
- "".to_string()
- };
- let help = format!(
- " {} for further information visit https://lint.deno.land/#{}",
- colors::cyan("help:"),
- diagnostic_code
- );
-
- format!(
- "{message_line}\n{snippets}\n at {formatted_location}\n\n{hint}{help}",
- message_line = message_line,
- snippets = lines.join("\n"),
- formatted_location = formatted_location,
- hint = hint,
- help = help
- )
-}
-
#[derive(Serialize)]
struct JsonLintReporter {
diagnostics: Vec<LintDiagnostic>,
@@ -511,7 +512,7 @@ impl JsonLintReporter {
}
impl LintReporter for JsonLintReporter {
- fn visit_diagnostic(&mut self, d: &LintDiagnostic, _source_lines: Vec<&str>) {
+ fn visit_diagnostic(&mut self, d: &LintDiagnostic, _source: &ParsedSource) {
self.diagnostics.push(d.clone());
}