mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
feat: Add "--json" flag to deno lint (#6940)
Co-authored-by: JackSkylark <jdslaughter44@gmail.com>
This commit is contained in:
parent
08ab4d46ca
commit
d6cee70695
7 changed files with 233 additions and 37 deletions
36
cli/flags.rs
36
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
|
||||
|
||||
|
@ -1041,6 +1047,12 @@ Ignore linting a file by adding an ignore comment at the top of the file:
|
|||
.require_equals(true)
|
||||
.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)
|
||||
|
@ -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,
|
||||
|
|
168
cli/lint.rs
168
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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
[WILDCARD]
|
||||
Found 1 problems
|
||||
Found 1 problem
|
||||
|
|
43
cli/tests/lint/expected_json.out
Normal file
43
cli/tests/lint/expected_json.out
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
4
cli/tests/lint/malformed.js
Normal file
4
cli/tests/lint/malformed.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
// deno-fmt-ignore-file
|
||||
|
||||
// intentionally malformed file
|
||||
export class A {
|
Loading…
Add table
Reference in a new issue