summaryrefslogtreecommitdiff
path: root/cli/tools/lint.rs
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2021-09-03 17:01:58 +0200
committerGitHub <noreply@github.com>2021-09-03 17:01:58 +0200
commitd93570a6195d2be5fe448bb0d33d4f64e839676e (patch)
treea44431235693f484308aad70cda66827605f4568 /cli/tools/lint.rs
parentc3001fe280add8c695c288fe26b8f81b5f2f0261 (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.rs162
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)
+}