From 0e9da7e731cea3a073812c22da30d87209537a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Mon, 8 Jun 2020 14:06:20 +0200 Subject: [PATCH] feat: "deno lint" subcommand (#6125) --- Cargo.lock | 16 ++++++++++++ cli/Cargo.toml | 1 + cli/flags.rs | 41 ++++++++++++++++++++++++++--- cli/fmt_errors.rs | 18 +++++++++---- cli/main.rs | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f26d6e7e2f..9eb1e61228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,6 +443,7 @@ dependencies = [ "bytes 0.5.4", "clap", "deno_core", + "deno_lint", "deno_typescript", "dirs", "dissimilar", @@ -503,6 +504,21 @@ dependencies = [ "url 2.1.1", ] +[[package]] +name = "deno_lint" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a3ec96c92609aa121d085f3a1351d3836e55b78a4b8ce79ea771c2ad9bd80b" +dependencies = [ + "lazy_static", + "regex", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_parser", + "swc_ecma_visit", +] + [[package]] name = "deno_typescript" version = "0.47.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 81eb9d7fbf..01a240d3a6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -20,6 +20,7 @@ deno_typescript = { path = "../deno_typescript", version = "0.47.1" } [dependencies] deno_core = { path = "../core", version = "0.47.1" } +deno_lint = { version = "0.1.7" } deno_typescript = { path = "../deno_typescript", version = "0.47.1" } atty = "0.2.14" diff --git a/cli/flags.rs b/cli/flags.rs index 9bf838bfa8..d4e17b9032 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -50,6 +50,9 @@ pub enum DenoSubcommand { root: Option, force: bool, }, + Lint { + files: Vec, + }, Repl, Run { script: String, @@ -260,6 +263,8 @@ pub fn flags_from_vec_safe(args: Vec) -> clap::Result { upgrade_parse(&mut flags, m); } else if let Some(m) = matches.subcommand_matches("doc") { doc_parse(&mut flags, m); + } else if let Some(m) = matches.subcommand_matches("lint") { + lint_parse(&mut flags, m); } else { repl_parse(&mut flags, &matches); } @@ -302,18 +307,19 @@ If the flag is set, restrict these messages to errors.", .global(true), ) .subcommand(bundle_subcommand()) - .subcommand(completions_subcommand()) - .subcommand(eval_subcommand()) .subcommand(cache_subcommand()) + .subcommand(completions_subcommand()) + .subcommand(doc_subcommand()) + .subcommand(eval_subcommand()) .subcommand(fmt_subcommand()) .subcommand(info_subcommand()) .subcommand(install_subcommand()) + .subcommand(lint_subcommand()) .subcommand(repl_subcommand()) .subcommand(run_subcommand()) .subcommand(test_subcommand()) .subcommand(types_subcommand()) .subcommand(upgrade_subcommand()) - .subcommand(doc_subcommand()) .long_about(DENO_HELP) .after_help(ENV_VARIABLES_HELP) } @@ -579,6 +585,16 @@ fn doc_parse(flags: &mut Flags, matches: &clap::ArgMatches) { }; } +fn lint_parse(flags: &mut Flags, matches: &clap::ArgMatches) { + unstable_arg_parse(flags, matches); + let files = matches + .values_of("files") + .unwrap() + .map(String::from) + .collect(); + flags.subcommand = DenoSubcommand::Lint { files }; +} + fn types_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("types") .arg(unstable_arg()) @@ -889,6 +905,25 @@ Show documentation for runtime built-ins: ) } +fn lint_subcommand<'a, 'b>() -> App<'a, 'b> { + SubCommand::with_name("lint") + .about("Lint source files") + .long_about( + "Lint JavaScript/TypeScript source code. + deno lint myfile1.ts myfile2.js + +Ignore diagnostics on next line preceding it with an ignore comment and code: + // deno-lint-ignore no-explicit-any", + ) + .arg(unstable_arg()) + .arg( + Arg::with_name("files") + .takes_value(true) + .required(true) + .min_values(1), + ) +} + fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { app .arg( diff --git a/cli/fmt_errors.rs b/cli/fmt_errors.rs index c8dfc625a2..bfda1feb0d 100644 --- a/cli/fmt_errors.rs +++ b/cli/fmt_errors.rs @@ -10,6 +10,15 @@ use std::ops::Deref; const SOURCE_ABBREV_THRESHOLD: usize = 150; +pub fn format_location(filename: String, line: i64, col: i64) -> String { + format!( + "{}:{}:{}", + colors::cyan(filename), + colors::yellow(line.to_string()), + colors::yellow(col.to_string()) + ) +} + pub fn format_stack( is_error: bool, message_line: String, @@ -137,11 +146,10 @@ impl fmt::Display for JSError { && self.0.line_number.is_some() && self.0.start_column.is_some() { - formatted_frames = vec![format!( - "{}:{}:{}", - colors::cyan(self.0.script_resource_name.clone().unwrap()), - colors::yellow(self.0.line_number.unwrap().to_string()), - colors::yellow((self.0.start_column.unwrap() + 1).to_string()) + formatted_frames = vec![format_location( + self.0.script_resource_name.clone().unwrap(), + self.0.line_number.unwrap(), + self.0.start_column.unwrap() + 1, )] }; diff --git a/cli/main.rs b/cli/main.rs index cf819ce5f8..3c16df974f 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -314,6 +314,71 @@ async fn install_command( .map_err(ErrBox::from) } +async fn lint_command(flags: Flags, files: Vec) -> Result<(), ErrBox> { + let global_state = GlobalState::new(flags)?; + + // TODO(bartlomieju): refactor, it's non-sense to create + // state just to perform unstable check... + use crate::state::State; + let state = State::new( + global_state.clone(), + None, + ModuleSpecifier::resolve_url("file:///dummy.ts").unwrap(), + None, + true, + )?; + + state.check_unstable("lint"); + + let mut error_counts = 0; + + for file in files { + let specifier = ModuleSpecifier::resolve_url_or_path(&file)?; + let source_file = global_state + .file_fetcher + .fetch_source_file(&specifier, None, Permissions::allow_all()) + .await?; + let source_code = String::from_utf8(source_file.source_code)?; + + let mut linter = deno_lint::linter::Linter::default(); + let lint_rules = deno_lint::rules::get_all_rules(); + + let file_diagnostics = linter.lint(file, source_code, lint_rules)?; + + error_counts += file_diagnostics.len(); + for d in file_diagnostics.iter() { + let pretty_message = format!( + "({}) {}", + colors::gray(d.code.to_string()), + d.message.clone() + ); + eprintln!( + "{}\n", + fmt_errors::format_stack( + true, + pretty_message, + Some(d.line_src.clone()), + Some(d.location.col as i64), + Some((d.location.col + d.snippet_length) as i64), + &[fmt_errors::format_location( + d.location.filename.clone(), + d.location.line as i64, + d.location.col as i64, + )], + 0 + ) + ); + } + } + + if error_counts > 0 { + eprintln!("Found {} problems", error_counts); + std::process::exit(1); + } + + Ok(()) +} + async fn cache_command(flags: Flags, files: Vec) -> Result<(), ErrBox> { let main_module = ModuleSpecifier::resolve_url_or_path("./__$deno$fetch.ts").unwrap(); @@ -657,6 +722,7 @@ pub fn main() { } => { install_command(flags, module_url, args, name, root, force).boxed_local() } + DenoSubcommand::Lint { files } => lint_command(flags, files).boxed_local(), DenoSubcommand::Repl => run_repl(flags).boxed_local(), DenoSubcommand::Run { script } => run_command(flags, script).boxed_local(), DenoSubcommand::Test {