diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2021-09-03 17:01:58 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-03 17:01:58 +0200 |
commit | d93570a6195d2be5fe448bb0d33d4f64e839676e (patch) | |
tree | a44431235693f484308aad70cda66827605f4568 /cli/tools/lint.rs | |
parent | c3001fe280add8c695c288fe26b8f81b5f2f0261 (diff) |
feat(lint): add support for config file and CLI flags for rules (#11776)
This commit adds support for following flags in deno lint subcommand:
--config - allows to load configuration file and parses "lint" object
--rules-tags=<tags> - allows specifying which set of tagged rules should be run
--rules-include=<rules> - allow specifying which rules should be run
--rules-exclude=<rules> - allow specifying which rules should not be run
Diffstat (limited to 'cli/tools/lint.rs')
-rw-r--r-- | cli/tools/lint.rs | 162 |
1 files changed, 149 insertions, 13 deletions
diff --git a/cli/tools/lint.rs b/cli/tools/lint.rs index 7e6d2e923..8a912a45c 100644 --- a/cli/tools/lint.rs +++ b/cli/tools/lint.rs @@ -8,11 +8,12 @@ //! the same functions as ops available in JS runtime. use crate::ast; use crate::colors; +use crate::config_file::LintConfig; use crate::fmt_errors; use crate::fs_util::{collect_files, is_supported_ext}; use crate::media_type::MediaType; use crate::tools::fmt::run_parallelized; -use deno_core::error::{generic_error, AnyError, JsStackFrame}; +use deno_core::error::{anyhow, generic_error, AnyError, JsStackFrame}; use deno_core::serde_json; use deno_lint::diagnostic::LintDiagnostic; use deno_lint::linter::Linter; @@ -42,21 +43,60 @@ fn create_reporter(kind: LintReporterKind) -> Box<dyn LintReporter + Send> { } pub async fn lint_files( + maybe_lint_config: Option<LintConfig>, + rules_tags: Vec<String>, + rules_include: Vec<String>, + rules_exclude: Vec<String>, args: Vec<PathBuf>, ignore: Vec<PathBuf>, json: bool, ) -> Result<(), AnyError> { if args.len() == 1 && args[0].to_string_lossy() == "-" { - return lint_stdin(json); + return lint_stdin( + json, + maybe_lint_config.as_ref(), + rules_tags, + rules_include, + rules_exclude, + ); } + + // Collect included and ignored files. CLI flags take precendence + // over config file, ie. if there's `files.ignore` in config file + // and `--ignore` CLI flag, only the flag value is taken into account. + let mut include_files = args; + let mut exclude_files = ignore; + + if let Some(lint_config) = maybe_lint_config.as_ref() { + if include_files.is_empty() { + include_files = lint_config + .files + .include + .iter() + .map(PathBuf::from) + .collect::<Vec<PathBuf>>(); + } + + if exclude_files.is_empty() { + exclude_files = lint_config + .files + .exclude + .iter() + .map(PathBuf::from) + .collect::<Vec<PathBuf>>(); + } + } + let target_files = - collect_files(&args, &ignore, is_supported_ext).and_then(|files| { - if files.is_empty() { - Err(generic_error("No target files found.")) - } else { - Ok(files) - } - })?; + collect_files(&include_files, &exclude_files, is_supported_ext).and_then( + |files| { + if files.is_empty() { + Err(generic_error("No target files found.")) + } else { + Ok(files) + } + }, + )?; debug!("Found {} files", target_files.len()); let target_files_len = target_files.len(); @@ -69,11 +109,29 @@ pub async fn lint_files( }; let reporter_lock = Arc::new(Mutex::new(create_reporter(reporter_kind))); + // Try to get configured rules. CLI flags take precendence + // over config file, ie. if there's `rules.include` in config file + // and `--rules-include` CLI flag, only the flag value is taken into account. + // TODO(bartlomieju): this is done multiple times for each file because + // Vec<Box<dyn LintRule>> is not clonable, this should be optimized. + get_configured_rules( + maybe_lint_config.as_ref(), + rules_tags.clone(), + rules_include.clone(), + rules_exclude.clone(), + )?; + run_parallelized(target_files, { let reporter_lock = reporter_lock.clone(); let has_error = has_error.clone(); move |file_path| { - let r = lint_file(file_path.clone()); + let r = lint_file( + file_path.clone(), + maybe_lint_config.as_ref(), + rules_tags.clone(), + rules_include.clone(), + rules_exclude.clone(), + ); let mut reporter = reporter_lock.lock().unwrap(); match r { @@ -144,13 +202,24 @@ pub fn create_linter(syntax: Syntax, rules: Vec<Box<dyn LintRule>>) -> Linter { fn lint_file( file_path: PathBuf, + maybe_lint_config: Option<&LintConfig>, + rules_tags: Vec<String>, + rules_include: Vec<String>, + rules_exclude: Vec<String>, ) -> Result<(Vec<LintDiagnostic>, String), AnyError> { let file_name = file_path.to_string_lossy().to_string(); let source_code = fs::read_to_string(&file_path)?; let media_type = MediaType::from(&file_path); let syntax = ast::get_syntax(&media_type); - let lint_rules = rules::get_recommended_rules(); + // Obtaining rules from config is infallible at this point. + let lint_rules = get_configured_rules( + maybe_lint_config, + rules_tags, + rules_include, + rules_exclude, + ) + .unwrap(); let linter = create_linter(syntax, lint_rules); let (_, file_diagnostics) = linter.lint(file_name, source_code.clone())?; @@ -161,7 +230,13 @@ fn lint_file( /// Lint stdin and write result to stdout. /// Treats input as TypeScript. /// Compatible with `--json` flag. -fn lint_stdin(json: bool) -> Result<(), AnyError> { +fn lint_stdin( + json: bool, + maybe_lint_config: Option<&LintConfig>, + rules_tags: Vec<String>, + rules_include: Vec<String>, + rules_exclude: Vec<String>, +) -> Result<(), AnyError> { let mut source = String::new(); if stdin().read_to_string(&mut source).is_err() { return Err(generic_error("Failed to read from stdin")); @@ -173,7 +248,12 @@ fn lint_stdin(json: bool) -> Result<(), AnyError> { LintReporterKind::Pretty }; let mut reporter = create_reporter(reporter_kind); - let lint_rules = rules::get_recommended_rules(); + let lint_rules = get_configured_rules( + maybe_lint_config, + rules_tags, + rules_include, + rules_exclude, + )?; let syntax = ast::get_syntax(&MediaType::TypeScript); let linter = create_linter(syntax, lint_rules); let mut has_error = false; @@ -385,3 +465,59 @@ fn sort_diagnostics(diagnostics: &mut Vec<LintDiagnostic>) { } }); } + +fn get_configured_rules( + maybe_lint_config: Option<&LintConfig>, + rules_tags: Vec<String>, + rules_include: Vec<String>, + rules_exclude: Vec<String>, +) -> Result<Vec<Box<dyn LintRule>>, AnyError> { + if maybe_lint_config.is_none() + && rules_tags.is_empty() + && rules_include.is_empty() + && rules_exclude.is_empty() + { + return Ok(rules::get_recommended_rules()); + } + + let (config_file_tags, config_file_include, config_file_exclude) = + if let Some(lint_config) = maybe_lint_config.as_ref() { + ( + lint_config.rules.tags.clone(), + lint_config.rules.include.clone(), + lint_config.rules.exclude.clone(), + ) + } else { + (None, None, None) + }; + + let maybe_configured_include = if !rules_include.is_empty() { + Some(rules_include) + } else { + config_file_include + }; + + let maybe_configured_exclude = if !rules_exclude.is_empty() { + Some(rules_exclude) + } else { + config_file_exclude + }; + + let configured_tags = if !rules_tags.is_empty() { + rules_tags + } else { + config_file_tags.unwrap_or_else(Vec::new) + }; + + let configured_rules = rules::get_filtered_rules( + Some(configured_tags), + maybe_configured_exclude, + maybe_configured_include, + ); + + if configured_rules.is_empty() { + anyhow!("No rules have been configured"); + } + + Ok(configured_rules) +} |