summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsouldzin <4908127+souldzin@users.noreply.github.com>2020-08-13 10:30:46 -0500
committerGitHub <noreply@github.com>2020-08-13 17:30:46 +0200
commitd6cee706955f300086f41f9318d23631f64f036b (patch)
tree61ff94128f1f4a03af2574a488d04301bbfcf3f7
parent08ab4d46ca1860da16b5250fe3872cefbe682637 (diff)
feat: Add "--json" flag to deno lint (#6940)
Co-authored-by: JackSkylark <jdslaughter44@gmail.com>
-rw-r--r--cli/flags.rs36
-rw-r--r--cli/lint.rs168
-rw-r--r--cli/main.rs6
-rw-r--r--cli/tests/integration_tests.rs11
-rw-r--r--cli/tests/lint/expected_ignore.out2
-rw-r--r--cli/tests/lint/expected_json.out43
-rw-r--r--cli/tests/lint/malformed.js4
7 files changed, 233 insertions, 37 deletions
diff --git a/cli/flags.rs b/cli/flags.rs
index 6c58238de..537e768f2 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -57,6 +57,7 @@ pub enum DenoSubcommand {
files: Vec<String>,
ignore: Vec<String>,
rules: bool,
+ json: bool,
},
Repl,
Run {
@@ -636,10 +637,12 @@ fn lint_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
None => vec![],
};
let rules = matches.is_present("rules");
+ let json = matches.is_present("json");
flags.subcommand = DenoSubcommand::Lint {
files,
rules,
ignore,
+ json,
};
}
@@ -1008,6 +1011,9 @@ fn lint_subcommand<'a, 'b>() -> App<'a, 'b> {
deno lint --unstable
deno lint --unstable myfile1.ts myfile2.js
+Print result as JSON:
+ deno lint --unstable --json
+
List available rules:
deno lint --unstable --rules
@@ -1042,6 +1048,12 @@ Ignore linting a file by adding an ignore comment at the top of the file:
.help("Ignore linting particular source files."),
)
.arg(
+ Arg::with_name("json")
+ .long("json")
+ .help("Output lint result in JSON format.")
+ .takes_value(false),
+ )
+ .arg(
Arg::with_name("files")
.takes_value(true)
.multiple(true)
@@ -1761,6 +1773,7 @@ mod tests {
subcommand: DenoSubcommand::Lint {
files: vec!["script_1.ts".to_string(), "script_2.ts".to_string()],
rules: false,
+ json: false,
ignore: vec![],
},
unstable: true,
@@ -1780,6 +1793,7 @@ mod tests {
subcommand: DenoSubcommand::Lint {
files: vec![],
rules: false,
+ json: false,
ignore: svec!["script_1.ts", "script_2.ts"],
},
unstable: true,
@@ -1794,6 +1808,28 @@ mod tests {
subcommand: DenoSubcommand::Lint {
files: vec![],
rules: true,
+ json: false,
+ ignore: vec![],
+ },
+ unstable: true,
+ ..Flags::default()
+ }
+ );
+
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "lint",
+ "--unstable",
+ "--json",
+ "script_1.ts"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Lint {
+ files: vec!["script_1.ts".to_string()],
+ rules: false,
+ json: true,
ignore: vec![],
},
unstable: true,
diff --git a/cli/lint.rs b/cli/lint.rs
index 3e9380d9d..bf6dc16b6 100644
--- a/cli/lint.rs
+++ b/cli/lint.rs
@@ -6,7 +6,6 @@
//! At the moment it is only consumed using CLI but in
//! the future it can be easily extended to provide
//! the same functions as ops available in JS runtime.
-
use crate::colors;
use crate::file_fetcher::map_file_extension;
use crate::fmt::collect_files;
@@ -19,15 +18,29 @@ use deno_lint::linter::Linter;
use deno_lint::linter::LinterBuilder;
use deno_lint::rules;
use deno_lint::rules::LintRule;
+use serde::Serialize;
use std::fs;
use std::path::PathBuf;
-use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use swc_ecmascript::parser::Syntax;
+pub enum LintReporterKind {
+ Pretty,
+ Json,
+}
+
+fn create_reporter(kind: LintReporterKind) -> Box<dyn LintReporter + Send> {
+ match kind {
+ LintReporterKind::Pretty => Box::new(PrettyLintReporter::new()),
+ LintReporterKind::Json => Box::new(JsonLintReporter::new()),
+ }
+}
+
pub async fn lint_files(
args: Vec<String>,
ignore: Vec<String>,
+ json: bool,
) -> Result<(), ErrBox> {
let mut target_files = collect_files(args)?;
if !ignore.is_empty() {
@@ -38,28 +51,32 @@ pub async fn lint_files(
}
debug!("Found {} files", target_files.len());
- let error_count = Arc::new(AtomicUsize::new(0));
+ let has_error = Arc::new(AtomicBool::new(false));
- // prevent threads outputting at the same time
- let output_lock = Arc::new(Mutex::new(0));
+ let reporter_kind = if json {
+ LintReporterKind::Json
+ } else {
+ LintReporterKind::Pretty
+ };
+ let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind)));
run_parallelized(target_files, {
- let error_count = error_count.clone();
+ let reporter_lock = reporter_lock.clone();
+ let has_error = has_error.clone();
move |file_path| {
let r = lint_file(file_path.clone());
+ let mut reporter = reporter_lock.lock().unwrap();
match r {
Ok(file_diagnostics) => {
- error_count.fetch_add(file_diagnostics.len(), Ordering::SeqCst);
- let _g = output_lock.lock().unwrap();
for d in file_diagnostics.iter() {
- let fmt_diagnostic = format_diagnostic(d);
- eprintln!("{}\n", fmt_diagnostic);
+ has_error.store(true, Ordering::Relaxed);
+ reporter.visit(&d);
}
}
Err(err) => {
- eprintln!("Error linting: {}", file_path.to_string_lossy());
- eprintln!(" {}", err);
+ has_error.store(true, Ordering::Relaxed);
+ reporter.visit_error(&file_path.to_string_lossy().to_string(), &err);
}
}
Ok(())
@@ -67,9 +84,11 @@ pub async fn lint_files(
})
.await?;
- let error_count = error_count.load(Ordering::SeqCst);
- if error_count > 0 {
- eprintln!("Found {} problems", error_count);
+ let has_error = has_error.load(Ordering::Relaxed);
+
+ reporter_lock.lock().unwrap().close();
+
+ if has_error {
std::process::exit(1);
}
@@ -170,21 +189,106 @@ fn lint_file(file_path: PathBuf) -> Result<Vec<LintDiagnostic>, ErrBox> {
Ok(file_diagnostics)
}
-fn format_diagnostic(d: &LintDiagnostic) -> String {
- let pretty_message =
- format!("({}) {}", colors::gray(&d.code), d.message.clone());
-
- fmt_errors::format_stack(
- true,
- &pretty_message,
- Some(&d.line_src),
- Some(d.location.col as i64),
- Some((d.location.col + d.snippet_length) as i64),
- &[fmt_errors::format_location(
- &d.location.filename,
- d.location.line as i64,
- d.location.col as i64,
- )],
- 0,
- )
+trait LintReporter {
+ fn visit(&mut self, d: &LintDiagnostic);
+ fn visit_error(&mut self, file_path: &str, err: &ErrBox);
+ fn close(&mut self);
+}
+
+#[derive(Serialize)]
+struct LintError {
+ file_path: String,
+ message: String,
+}
+
+struct PrettyLintReporter {
+ lint_count: u32,
+}
+
+impl PrettyLintReporter {
+ fn new() -> PrettyLintReporter {
+ PrettyLintReporter { lint_count: 0 }
+ }
+}
+
+impl LintReporter for PrettyLintReporter {
+ fn visit(&mut self, d: &LintDiagnostic) {
+ self.lint_count += 1;
+
+ let pretty_message =
+ format!("({}) {}", colors::gray(&d.code), d.message.clone());
+
+ let message = fmt_errors::format_stack(
+ true,
+ &pretty_message,
+ Some(&d.line_src),
+ Some(d.location.col as i64),
+ Some((d.location.col + d.snippet_length) as i64),
+ &[fmt_errors::format_location(
+ &d.location.filename,
+ d.location.line as i64,
+ d.location.col as i64,
+ )],
+ 0,
+ );
+
+ eprintln!("{}\n", message);
+ }
+
+ fn visit_error(&mut self, file_path: &str, err: &ErrBox) {
+ eprintln!("Error linting: {}", file_path);
+ eprintln!(" {}", err);
+ }
+
+ fn close(&mut self) {
+ match self.lint_count {
+ 1 => eprintln!("Found 1 problem"),
+ n if n > 1 => eprintln!("Found {} problems", self.lint_count),
+ _ => (),
+ }
+ }
+}
+
+#[derive(Serialize)]
+struct JsonLintReporter {
+ diagnostics: Vec<LintDiagnostic>,
+ errors: Vec<LintError>,
+}
+
+impl JsonLintReporter {
+ fn new() -> JsonLintReporter {
+ JsonLintReporter {
+ diagnostics: Vec::new(),
+ errors: Vec::new(),
+ }
+ }
+}
+
+impl LintReporter for JsonLintReporter {
+ fn visit(&mut self, d: &LintDiagnostic) {
+ self.diagnostics.push(d.clone());
+ }
+
+ fn visit_error(&mut self, file_path: &str, err: &ErrBox) {
+ self.errors.push(LintError {
+ file_path: file_path.to_string(),
+ message: err.to_string(),
+ });
+ }
+
+ fn close(&mut self) {
+ // Sort so that we guarantee a deterministic output which is useful for tests
+ self
+ .diagnostics
+ .sort_by(|a, b| get_sort_key(&a).cmp(&get_sort_key(&b)));
+
+ let json = serde_json::to_string_pretty(&self);
+ eprintln!("{}", json.unwrap());
+ }
+}
+
+pub fn get_sort_key(a: &LintDiagnostic) -> String {
+ let location = &a.location;
+
+ return format!("{}:{}:{}", location.filename, location.line, location.col);
}
diff --git a/cli/main.rs b/cli/main.rs
index 27948e14f..b5eb0b745 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -346,6 +346,7 @@ async fn lint_command(
files: Vec<String>,
list_rules: bool,
ignore: Vec<String>,
+ json: bool,
) -> Result<(), ErrBox> {
if !flags.unstable {
exit_unstable("lint");
@@ -356,7 +357,7 @@ async fn lint_command(
return Ok(());
}
- lint::lint_files(files, ignore).await
+ lint::lint_files(files, ignore, json).await
}
async fn cache_command(flags: Flags, files: Vec<String>) -> Result<(), ErrBox> {
@@ -738,7 +739,8 @@ pub fn main() {
files,
rules,
ignore,
- } => lint_command(flags, files, rules, ignore).boxed_local(),
+ json,
+ } => lint_command(flags, files, rules, ignore, json).boxed_local(),
DenoSubcommand::Repl => run_repl(flags).boxed_local(),
DenoSubcommand::Run { script } => run_command(flags, script).boxed_local(),
DenoSubcommand::Test {
diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs
index bb7ba0d6f..6e487d12f 100644
--- a/cli/tests/integration_tests.rs
+++ b/cli/tests/integration_tests.rs
@@ -2216,14 +2216,21 @@ itest!(deno_lint {
exit_code: 1,
});
+itest!(deno_lint_json {
+ args:
+ "lint --unstable --json lint/file1.js lint/file2.ts lint/ignored_file.ts lint/malformed.js",
+ output: "lint/expected_json.out",
+ exit_code: 1,
+});
+
itest!(deno_lint_ignore {
- args: "lint --unstable --ignore=lint/file1.js lint/",
+ args: "lint --unstable --ignore=lint/file1.js,lint/malformed.js lint/",
output: "lint/expected_ignore.out",
exit_code: 1,
});
itest!(deno_lint_glob {
- args: "lint --unstable lint/",
+ args: "lint --unstable --ignore=lint/malformed.js lint/",
output: "lint/expected_glob.out",
exit_code: 1,
});
diff --git a/cli/tests/lint/expected_ignore.out b/cli/tests/lint/expected_ignore.out
index 6041d1c6a..02b9d917c 100644
--- a/cli/tests/lint/expected_ignore.out
+++ b/cli/tests/lint/expected_ignore.out
@@ -1,2 +1,2 @@
[WILDCARD]
-Found 1 problems
+Found 1 problem
diff --git a/cli/tests/lint/expected_json.out b/cli/tests/lint/expected_json.out
new file mode 100644
index 000000000..b4ce63395
--- /dev/null
+++ b/cli/tests/lint/expected_json.out
@@ -0,0 +1,43 @@
+{
+ "diagnostics": [
+ {
+ "location": {
+ "filename": "[WILDCARD]",
+ "line": 1,
+ "col": 0
+ },
+ "message": "Ignore directive requires lint rule code",
+ "code": "ban-untagged-ignore",
+ "line_src": "// deno-lint-ignore",
+ "snippet_length": 19
+ },
+ {
+ "location": {
+ "filename": "[WILDCARD]",
+ "line": 2,
+ "col": 14
+ },
+ "message": "Empty block statement",
+ "code": "no-empty",
+ "line_src": "while (false) {}",
+ "snippet_length": 2
+ },
+ {
+ "location": {
+ "filename": "[WILDCARD]",
+ "line": 3,
+ "col": 12
+ },
+ "message": "Empty block statement",
+ "code": "no-empty",
+ "line_src": "} catch (e) {}",
+ "snippet_length": 2
+ }
+ ],
+ "errors": [
+ {
+ "file_path": "[WILDCARD]malformed.js",
+ "message": "Expected RBrace, got None at [WILDCARD]malformed.js:4:15"
+ }
+ ]
+}
diff --git a/cli/tests/lint/malformed.js b/cli/tests/lint/malformed.js
new file mode 100644
index 000000000..5ad4650d6
--- /dev/null
+++ b/cli/tests/lint/malformed.js
@@ -0,0 +1,4 @@
+// deno-fmt-ignore-file
+
+// intentionally malformed file
+export class A { \ No newline at end of file