0
0
Fork 0
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:
souldzin 2020-08-13 10:30:46 -05:00 committed by GitHub
parent 08ab4d46ca
commit d6cee70695
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 233 additions and 37 deletions

View file

@ -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,

View file

@ -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);
}

View file

@ -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 {

View file

@ -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,
});

View file

@ -1,2 +1,2 @@
[WILDCARD]
Found 1 problems
Found 1 problem

View 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"
}
]
}

View file

@ -0,0 +1,4 @@
// deno-fmt-ignore-file
// intentionally malformed file
export class A {