mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
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
This commit is contained in:
parent
c3001fe280
commit
d93570a619
15 changed files with 484 additions and 31 deletions
|
@ -234,7 +234,7 @@ impl TsConfig {
|
|||
maybe_config_file: Option<&ConfigFile>,
|
||||
) -> Result<Option<IgnoredCompilerOptions>, AnyError> {
|
||||
if let Some(config_file) = maybe_config_file {
|
||||
let (value, maybe_ignored_options) = config_file.as_compiler_options()?;
|
||||
let (value, maybe_ignored_options) = config_file.to_compiler_options()?;
|
||||
self.merge(&value);
|
||||
Ok(maybe_ignored_options)
|
||||
} else {
|
||||
|
@ -266,10 +266,33 @@ impl Serialize for TsConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct LintRulesConfig {
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub include: Option<Vec<String>>,
|
||||
pub exclude: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct LintFilesConfig {
|
||||
pub include: Vec<String>,
|
||||
pub exclude: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct LintConfig {
|
||||
pub rules: LintRulesConfig,
|
||||
pub files: LintFilesConfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConfigFileJson {
|
||||
pub compiler_options: Option<Value>,
|
||||
pub lint: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -328,7 +351,7 @@ impl ConfigFile {
|
|||
|
||||
/// Parse `compilerOptions` and return a serde `Value`.
|
||||
/// The result also contains any options that were ignored.
|
||||
pub fn as_compiler_options(
|
||||
pub fn to_compiler_options(
|
||||
&self,
|
||||
) -> Result<(Value, Option<IgnoredCompilerOptions>), AnyError> {
|
||||
if let Some(compiler_options) = self.json.compiler_options.clone() {
|
||||
|
@ -340,6 +363,16 @@ impl ConfigFile {
|
|||
Ok((json!({}), None))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_lint_config(&self) -> Result<Option<LintConfig>, AnyError> {
|
||||
if let Some(config) = self.json.lint.clone() {
|
||||
let lint_config: LintConfig = serde_json::from_value(config)
|
||||
.context("Failed to parse \"lint\" configuration")?;
|
||||
Ok(Some(lint_config))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -397,12 +430,22 @@ mod tests {
|
|||
"build": true,
|
||||
// comments are allowed
|
||||
"strict": true
|
||||
},
|
||||
"lint": {
|
||||
"files": {
|
||||
"include": ["src/"],
|
||||
"exclude": ["src/testdata/"]
|
||||
},
|
||||
"rules": {
|
||||
"tags": ["recommended"],
|
||||
"include": ["ban-untagged-todo"]
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
let config_path = PathBuf::from("/deno/tsconfig.json");
|
||||
let config_file = ConfigFile::new(config_text, &config_path).unwrap();
|
||||
let (options_value, ignored) =
|
||||
config_file.as_compiler_options().expect("error parsing");
|
||||
config_file.to_compiler_options().expect("error parsing");
|
||||
assert!(options_value.is_object());
|
||||
let options = options_value.as_object().unwrap();
|
||||
assert!(options.contains_key("strict"));
|
||||
|
@ -414,6 +457,22 @@ mod tests {
|
|||
maybe_path: Some(config_path),
|
||||
}),
|
||||
);
|
||||
|
||||
let lint_config = config_file
|
||||
.to_lint_config()
|
||||
.expect("error parsing lint object")
|
||||
.expect("lint object should be defined");
|
||||
assert_eq!(lint_config.files.include, vec!["src/"]);
|
||||
assert_eq!(lint_config.files.exclude, vec!["src/testdata/"]);
|
||||
assert_eq!(
|
||||
lint_config.rules.include,
|
||||
Some(vec!["ban-untagged-todo".to_string()])
|
||||
);
|
||||
assert_eq!(
|
||||
lint_config.rules.tags,
|
||||
Some(vec!["recommended".to_string()])
|
||||
);
|
||||
assert!(lint_config.rules.exclude.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -422,7 +481,7 @@ mod tests {
|
|||
let config_path = PathBuf::from("/deno/tsconfig.json");
|
||||
let config_file = ConfigFile::new(config_text, &config_path).unwrap();
|
||||
let (options_value, _) =
|
||||
config_file.as_compiler_options().expect("error parsing");
|
||||
config_file.to_compiler_options().expect("error parsing");
|
||||
assert!(options_value.is_object());
|
||||
}
|
||||
|
||||
|
@ -432,7 +491,7 @@ mod tests {
|
|||
let config_path = PathBuf::from("/deno/tsconfig.json");
|
||||
let config_file = ConfigFile::new(config_text, &config_path).unwrap();
|
||||
let (options_value, _) =
|
||||
config_file.as_compiler_options().expect("error parsing");
|
||||
config_file.to_compiler_options().expect("error parsing");
|
||||
assert!(options_value.is_object());
|
||||
}
|
||||
|
||||
|
|
108
cli/flags.rs
108
cli/flags.rs
|
@ -90,6 +90,9 @@ pub enum DenoSubcommand {
|
|||
files: Vec<PathBuf>,
|
||||
ignore: Vec<PathBuf>,
|
||||
rules: bool,
|
||||
rules_tags: Vec<String>,
|
||||
rules_include: Vec<String>,
|
||||
rules_exclude: Vec<String>,
|
||||
json: bool,
|
||||
},
|
||||
Repl {
|
||||
|
@ -952,6 +955,35 @@ Ignore linting a file by adding an ignore comment at the top of the file:
|
|||
.long("rules")
|
||||
.help("List available rules"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rules-tags")
|
||||
.long("rules-tags")
|
||||
.require_equals(true)
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.empty_values(true)
|
||||
.conflicts_with("rules")
|
||||
.help("Use set of rules with a tag"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rules-include")
|
||||
.long("rules-include")
|
||||
.require_equals(true)
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.conflicts_with("rules")
|
||||
.help("Include lint rules"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rules-exclude")
|
||||
.long("rules-exclude")
|
||||
.require_equals(true)
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.conflicts_with("rules")
|
||||
.help("Exclude lint rules"),
|
||||
)
|
||||
.arg(config_arg())
|
||||
.arg(
|
||||
Arg::with_name("ignore")
|
||||
.long("ignore")
|
||||
|
@ -1722,6 +1754,7 @@ fn lsp_parse(flags: &mut Flags, _matches: &clap::ArgMatches) {
|
|||
}
|
||||
|
||||
fn lint_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
||||
config_arg_parse(flags, matches);
|
||||
let files = match matches.values_of("files") {
|
||||
Some(f) => f.map(PathBuf::from).collect(),
|
||||
None => vec![],
|
||||
|
@ -1731,10 +1764,25 @@ fn lint_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
|
|||
None => vec![],
|
||||
};
|
||||
let rules = matches.is_present("rules");
|
||||
let rules_tags = match matches.values_of("rules-tags") {
|
||||
Some(f) => f.map(String::from).collect(),
|
||||
None => vec![],
|
||||
};
|
||||
let rules_include = match matches.values_of("rules-include") {
|
||||
Some(f) => f.map(String::from).collect(),
|
||||
None => vec![],
|
||||
};
|
||||
let rules_exclude = match matches.values_of("rules-exclude") {
|
||||
Some(f) => f.map(String::from).collect(),
|
||||
None => vec![],
|
||||
};
|
||||
let json = matches.is_present("json");
|
||||
flags.subcommand = DenoSubcommand::Lint {
|
||||
files,
|
||||
rules,
|
||||
rules_tags,
|
||||
rules_include,
|
||||
rules_exclude,
|
||||
ignore,
|
||||
json,
|
||||
};
|
||||
|
@ -2456,6 +2504,9 @@ mod tests {
|
|||
PathBuf::from("script_2.ts")
|
||||
],
|
||||
rules: false,
|
||||
rules_tags: vec![],
|
||||
rules_include: vec![],
|
||||
rules_exclude: vec![],
|
||||
json: false,
|
||||
ignore: vec![],
|
||||
},
|
||||
|
@ -2471,6 +2522,9 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Lint {
|
||||
files: vec![],
|
||||
rules: false,
|
||||
rules_tags: vec![],
|
||||
rules_include: vec![],
|
||||
rules_exclude: vec![],
|
||||
json: false,
|
||||
ignore: vec![
|
||||
PathBuf::from("script_1.ts"),
|
||||
|
@ -2488,6 +2542,32 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Lint {
|
||||
files: vec![],
|
||||
rules: true,
|
||||
rules_tags: vec![],
|
||||
rules_include: vec![],
|
||||
rules_exclude: vec![],
|
||||
json: false,
|
||||
ignore: vec![],
|
||||
},
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
||||
let r = flags_from_vec(svec![
|
||||
"deno",
|
||||
"lint",
|
||||
"--rules-tags=",
|
||||
"--rules-include=ban-untagged-todo,no-undef",
|
||||
"--rules-exclude=no-const-assign"
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
Flags {
|
||||
subcommand: DenoSubcommand::Lint {
|
||||
files: vec![],
|
||||
rules: false,
|
||||
rules_tags: svec![""],
|
||||
rules_include: svec!["ban-untagged-todo", "no-undef"],
|
||||
rules_exclude: svec!["no-const-assign"],
|
||||
json: false,
|
||||
ignore: vec![],
|
||||
},
|
||||
|
@ -2502,12 +2582,40 @@ mod tests {
|
|||
subcommand: DenoSubcommand::Lint {
|
||||
files: vec![PathBuf::from("script_1.ts")],
|
||||
rules: false,
|
||||
rules_tags: vec![],
|
||||
rules_include: vec![],
|
||||
rules_exclude: vec![],
|
||||
json: true,
|
||||
ignore: vec![],
|
||||
},
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
|
||||
let r = flags_from_vec(svec![
|
||||
"deno",
|
||||
"lint",
|
||||
"--config",
|
||||
"Deno.jsonc",
|
||||
"--json",
|
||||
"script_1.ts"
|
||||
]);
|
||||
assert_eq!(
|
||||
r.unwrap(),
|
||||
Flags {
|
||||
subcommand: DenoSubcommand::Lint {
|
||||
files: vec![PathBuf::from("script_1.ts")],
|
||||
rules: false,
|
||||
rules_tags: vec![],
|
||||
rules_include: vec![],
|
||||
rules_exclude: vec![],
|
||||
json: true,
|
||||
ignore: vec![],
|
||||
},
|
||||
config_path: Some("Deno.jsonc".to_string()),
|
||||
..Flags::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -392,7 +392,7 @@ impl Inner {
|
|||
ConfigFile::read(path)?
|
||||
};
|
||||
let (value, maybe_ignored_options) =
|
||||
config_file.as_compiler_options()?;
|
||||
config_file.to_compiler_options()?;
|
||||
tsconfig.merge(&value);
|
||||
self.maybe_config_file = Some(config_file);
|
||||
self.maybe_config_uri = Some(config_url);
|
||||
|
|
40
cli/main.rs
40
cli/main.rs
|
@ -489,10 +489,14 @@ async fn lsp_command() -> Result<(), AnyError> {
|
|||
lsp::start().await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn lint_command(
|
||||
_flags: Flags,
|
||||
flags: Flags,
|
||||
files: Vec<PathBuf>,
|
||||
list_rules: bool,
|
||||
rules_tags: Vec<String>,
|
||||
rules_include: Vec<String>,
|
||||
rules_exclude: Vec<String>,
|
||||
ignore: Vec<PathBuf>,
|
||||
json: bool,
|
||||
) -> Result<(), AnyError> {
|
||||
|
@ -501,7 +505,24 @@ async fn lint_command(
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
tools::lint::lint_files(files, ignore, json).await
|
||||
let program_state = ProgramState::build(flags.clone()).await?;
|
||||
let maybe_lint_config =
|
||||
if let Some(config_file) = &program_state.maybe_config_file {
|
||||
config_file.to_lint_config()?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
tools::lint::lint_files(
|
||||
maybe_lint_config,
|
||||
rules_tags,
|
||||
rules_include,
|
||||
rules_exclude,
|
||||
files,
|
||||
ignore,
|
||||
json,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn cache_command(
|
||||
|
@ -1183,9 +1204,22 @@ fn get_subcommand(
|
|||
DenoSubcommand::Lint {
|
||||
files,
|
||||
rules,
|
||||
rules_tags,
|
||||
rules_include,
|
||||
rules_exclude,
|
||||
ignore,
|
||||
json,
|
||||
} => lint_command(flags, files, rules, ignore, json).boxed_local(),
|
||||
} => lint_command(
|
||||
flags,
|
||||
files,
|
||||
rules,
|
||||
rules_tags,
|
||||
rules_include,
|
||||
rules_exclude,
|
||||
ignore,
|
||||
json,
|
||||
)
|
||||
.boxed_local(),
|
||||
DenoSubcommand::Repl { eval } => run_repl(flags, eval).boxed_local(),
|
||||
DenoSubcommand::Run { script } => run_command(flags, script).boxed_local(),
|
||||
DenoSubcommand::Test {
|
||||
|
|
|
@ -24,59 +24,85 @@ fn ignore_unexplicit_files() {
|
|||
}
|
||||
|
||||
itest!(all {
|
||||
args: "lint --unstable lint/file1.js lint/file2.ts lint/ignored_file.ts",
|
||||
args: "lint lint/file1.js lint/file2.ts lint/ignored_file.ts",
|
||||
output: "lint/expected.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(quiet {
|
||||
args: "lint --unstable --quiet lint/file1.js",
|
||||
args: "lint --quiet lint/file1.js",
|
||||
output: "lint/expected_quiet.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(json {
|
||||
args:
|
||||
"lint --unstable --json lint/file1.js lint/file2.ts lint/ignored_file.ts lint/malformed.js",
|
||||
"lint --json lint/file1.js lint/file2.ts lint/ignored_file.ts lint/malformed.js",
|
||||
output: "lint/expected_json.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(ignore {
|
||||
args: "lint --unstable --ignore=lint/file1.js,lint/malformed.js lint/",
|
||||
args:
|
||||
"lint --ignore=lint/file1.js,lint/malformed.js,lint/lint_with_config/ lint/",
|
||||
output: "lint/expected_ignore.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(glob {
|
||||
args: "lint --unstable --ignore=lint/malformed.js lint/",
|
||||
args: "lint --ignore=lint/malformed.js,lint/lint_with_config/ lint/",
|
||||
output: "lint/expected_glob.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(stdin {
|
||||
args: "lint --unstable -",
|
||||
args: "lint -",
|
||||
input: Some("let _a: any;"),
|
||||
output: "lint/expected_from_stdin.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(stdin_json {
|
||||
args: "lint --unstable --json -",
|
||||
args: "lint --json -",
|
||||
input: Some("let _a: any;"),
|
||||
output: "lint/expected_from_stdin_json.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(rules {
|
||||
args: "lint --unstable --rules",
|
||||
args: "lint --rules",
|
||||
output: "lint/expected_rules.out",
|
||||
exit_code: 0,
|
||||
});
|
||||
|
||||
// Make sure that the rules are printed if quiet option is enabled.
|
||||
itest!(rules_quiet {
|
||||
args: "lint --unstable --rules -q",
|
||||
args: "lint --rules -q",
|
||||
output: "lint/expected_rules.out",
|
||||
exit_code: 0,
|
||||
});
|
||||
|
||||
itest!(lint_with_config {
|
||||
args: "lint --config lint/Deno.jsonc lint/lint_with_config/",
|
||||
output: "lint/lint_with_config.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
// Check if CLI flags take precedence
|
||||
itest!(lint_with_config_and_flags {
|
||||
args: "lint --config lint/Deno.jsonc --ignore=lint/lint_with_config/a.ts",
|
||||
output: "lint/lint_with_config_and_flags.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(lint_with_malformed_config {
|
||||
args: "lint --config lint/Deno.malformed.jsonc",
|
||||
output: "lint/lint_with_malformed_config.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(lint_with_malformed_config2 {
|
||||
args: "lint --config lint/Deno.malformed2.jsonc",
|
||||
output: "lint/lint_with_malformed_config2.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
|
12
cli/tests/testdata/lint/Deno.jsonc
vendored
Normal file
12
cli/tests/testdata/lint/Deno.jsonc
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"lint": {
|
||||
"files": {
|
||||
"include": ["lint/lint_with_config/"],
|
||||
"exclude": ["lint/lint_with_config/b.ts"]
|
||||
},
|
||||
"rules": {
|
||||
"tags": ["recommended"],
|
||||
"include": ["ban-untagged-todo"]
|
||||
}
|
||||
}
|
||||
}
|
13
cli/tests/testdata/lint/Deno.malformed.jsonc
vendored
Normal file
13
cli/tests/testdata/lint/Deno.malformed.jsonc
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"lint": {
|
||||
"files": {
|
||||
"include": ["lint/lint_with_config/"],
|
||||
"exclude": ["lint/lint_with_config/b.ts"]
|
||||
},
|
||||
"dont_know_this_field": {},
|
||||
"rules": {
|
||||
"tags": ["recommended"],
|
||||
"include": ["ban-untagged-todo"]
|
||||
}
|
||||
}
|
||||
}
|
13
cli/tests/testdata/lint/Deno.malformed2.jsonc
vendored
Normal file
13
cli/tests/testdata/lint/Deno.malformed2.jsonc
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"lint": {
|
||||
"files": {
|
||||
"include": ["lint/lint_with_config/"],
|
||||
"exclude": ["lint/lint_with_config/b.ts"],
|
||||
"dont_know_this_field": {}
|
||||
},
|
||||
"rules": {
|
||||
"tags": ["recommended"],
|
||||
"include": ["ban-untagged-todo"]
|
||||
}
|
||||
}
|
||||
}
|
18
cli/tests/testdata/lint/lint_with_config.out
vendored
Normal file
18
cli/tests/testdata/lint/lint_with_config.out
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
(ban-untagged-todo) TODO should be tagged with (@username) or (#issue)
|
||||
// TODO: foo
|
||||
^^^^^^^^^^^^
|
||||
at [WILDCARD]a.ts:1:0
|
||||
|
||||
hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
||||
help: for further information visit https://lint.deno.land/#ban-untagged-todo
|
||||
|
||||
(no-unused-vars) `add` is never used
|
||||
function add(a: number, b: number): number {
|
||||
^^^
|
||||
at [WILDCARD]a.ts:2:9
|
||||
|
||||
hint: If this is intentional, prefix it with an underscore like `_add`
|
||||
help: for further information visit https://lint.deno.land/#no-unused-vars
|
||||
|
||||
Found 2 problems
|
||||
Checked 1 file
|
4
cli/tests/testdata/lint/lint_with_config/a.ts
vendored
Normal file
4
cli/tests/testdata/lint/lint_with_config/a.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
// TODO: foo
|
||||
function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
4
cli/tests/testdata/lint/lint_with_config/b.ts
vendored
Normal file
4
cli/tests/testdata/lint/lint_with_config/b.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
// TODO: this file should be ignored
|
||||
function subtract(a: number, b: number): number {
|
||||
return a - b;
|
||||
}
|
18
cli/tests/testdata/lint/lint_with_config_and_flags.out
vendored
Normal file
18
cli/tests/testdata/lint/lint_with_config_and_flags.out
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
(ban-untagged-todo) TODO should be tagged with (@username) or (#issue)
|
||||
// TODO: this file should be ignored
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at [WILDCARD]b.ts:1:0
|
||||
|
||||
hint: Add a user tag or issue reference to the TODO comment, e.g. TODO(@djones), TODO(djones), TODO(#123)
|
||||
help: for further information visit https://lint.deno.land/#ban-untagged-todo
|
||||
|
||||
(no-unused-vars) `subtract` is never used
|
||||
function subtract(a: number, b: number): number {
|
||||
^^^^^^^^
|
||||
at [WILDCARD]b.ts:2:9
|
||||
|
||||
hint: If this is intentional, prefix it with an underscore like `_subtract`
|
||||
help: for further information visit https://lint.deno.land/#no-unused-vars
|
||||
|
||||
Found 2 problems
|
||||
Checked 1 file
|
4
cli/tests/testdata/lint/lint_with_malformed_config.out
vendored
Normal file
4
cli/tests/testdata/lint/lint_with_malformed_config.out
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
error: Failed to parse "lint" configuration
|
||||
|
||||
Caused by:
|
||||
unknown field `dont_know_this_field`, expected `rules` or `files`
|
4
cli/tests/testdata/lint/lint_with_malformed_config2.out
vendored
Normal file
4
cli/tests/testdata/lint/lint_with_malformed_config2.out
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
error: Failed to parse "lint" configuration
|
||||
|
||||
Caused by:
|
||||
unknown field `dont_know_this_field`, expected `include` or `exclude`
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue