Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/dlint/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn run_linter(
let linter = Linter::new(LinterOptions {
rules,
all_rule_codes,
rule_severities: Default::default(),
custom_ignore_file_directive: None,
custom_ignore_diagnostic_directive: None,
});
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod lint_tests {
let linter = Linter::new(LinterOptions {
rules,
all_rule_codes,
rule_severities: Default::default(),
custom_ignore_diagnostic_directive: None,
custom_ignore_file_directive: None,
});
Expand Down Expand Up @@ -74,6 +75,7 @@ mod lint_tests {
let linter = Linter::new(LinterOptions {
rules,
all_rule_codes,
rule_severities: Default::default(),
custom_ignore_diagnostic_directive: None,
custom_ignore_file_directive: None,
});
Expand Down
82 changes: 82 additions & 0 deletions src/linter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::ast_parser::parse_program;
use crate::context::Context;
use crate::diagnostic::LintDiagnostic;
use crate::diagnostic::LintDiagnosticSeverity;
use crate::ignore_directives::parse_file_ignore_directives;
use crate::performance_mark::PerformanceMark;
use crate::rules::{ban_unknown_rule_code::BanUnknownRuleCode, LintRule};
Expand All @@ -11,6 +12,7 @@ use deno_ast::MediaType;
use deno_ast::ParsedSource;
use deno_ast::{ModuleSpecifier, ParseDiagnostic};
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;

Expand All @@ -19,6 +21,12 @@ pub struct LinterOptions {
pub rules: Vec<Box<dyn LintRule>>,
/// Collection of all the lint rule codes.
pub all_rule_codes: HashSet<Cow<'static, str>>,
/// Per-rule diagnostic severity, keyed by rule code. Diagnostics from a rule
/// whose code isn't present default to [`LintDiagnosticSeverity::Error`], so
/// callers that don't configure severities keep the historical behavior.
///
/// See [`crate::rules::config`] for producing this from configuration.
pub rule_severities: HashMap<Cow<'static, str>, LintDiagnosticSeverity>,
/// Defaults to "deno-lint-ignore-file"
pub custom_ignore_file_directive: Option<&'static str>,
/// Defaults to "deno-lint-ignore"
Expand All @@ -43,6 +51,8 @@ pub(crate) struct LinterContext {
/// Rules are sorted by priority
pub rules: Vec<Box<dyn LintRule>>,
pub all_rule_codes: HashSet<Cow<'static, str>>,
/// Per-rule diagnostic severity, keyed by rule code.
pub rule_severities: HashMap<Cow<'static, str>, LintDiagnosticSeverity>,
}

impl LinterContext {
Expand All @@ -63,6 +73,7 @@ impl LinterContext {
check_unknown_rules,
rules,
all_rule_codes: options.all_rule_codes,
rule_severities: options.rule_severities,
}
}
}
Expand Down Expand Up @@ -174,6 +185,21 @@ impl Linter {
// Run `ban-unused-ignore`
diagnostics.extend(context.ban_unused_ignore(&enabled_rules));

// Stamp each diagnostic with its rule's configured severity. Rules without
// a configured severity keep the default (`Error`), so this is a no-op
// unless the caller supplied `rule_severities`.
if !self.ctx.rule_severities.is_empty() {
for diagnostic in &mut diagnostics {
if let Some(severity) = self
.ctx
.rule_severities
.get(diagnostic.details.code.as_str())
{
diagnostic.severity = *severity;
}
}
}

// Finally sort by position the diagnostics originates on then by code
diagnostics.sort_by(|a, b| {
let a_range = a.range.as_ref().map(|r| r.range.start);
Expand Down Expand Up @@ -261,6 +287,7 @@ mod tests {
let linter = Linter::new(LinterOptions {
rules: vec![Box::new(NoDebugger)],
all_rule_codes: [Cow::from("no-debugger")].into_iter().collect(),
rule_severities: Default::default(),
custom_ignore_file_directive: None,
custom_ignore_diagnostic_directive,
});
Expand Down Expand Up @@ -309,4 +336,59 @@ mod tests {
let source = "// deno-lint-ignore no-debugger\ndebugger;";
assert!(lint_with_directives(source, None).is_empty());
}

// End-to-end: configuring a rule's severity flows all the way to the emitted
// diagnostic. This is the payoff of the `rules::config` layer.
#[test]
fn configured_severity_reaches_diagnostic() {
use crate::diagnostic::LintDiagnosticSeverity;
use crate::rules::config::{
configure_rules, split_configured, RuleConfig, RuleSeverity,
};
use crate::rules::no_console::NoConsole;
use std::collections::HashMap;

let mut user = HashMap::new();
user.insert(
"no-console".to_string(),
RuleConfig {
severity: Some(RuleSeverity::Warn),
options: None,
},
);

let defs = vec![NoConsole::def()];
let configured = configure_rules(&defs, &user).unwrap();
let (rules, rule_severities) = split_configured(configured);
let all_rule_codes = rules
.iter()
.map(|r| Cow::from(r.code()))
.collect::<HashSet<_>>();

let linter = Linter::new(LinterOptions {
rules,
all_rule_codes,
rule_severities,
custom_ignore_file_directive: None,
custom_ignore_diagnostic_directive: None,
});

let (_, diagnostics) = linter
.lint_file(LintFileOptions {
specifier: ModuleSpecifier::parse("file:///foo.ts").unwrap(),
source_code: "console.log('x');".to_string(),
media_type: MediaType::TypeScript,
config: LintConfig {
default_jsx_factory: None,
default_jsx_fragment_factory: None,
},
external_linter: None,
})
.unwrap();

assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].details.code, "no-console");
// Without configuration this would default to `Error`.
assert_eq!(diagnostics[0].severity, LintDiagnosticSeverity::Warning);
}
}
9 changes: 5 additions & 4 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod ban_untagged_ignore;
pub mod ban_untagged_todo;
pub mod ban_unused_ignore;
pub mod camelcase;
pub mod config;
pub mod constructor_super;
pub mod default_param_last;
pub mod eqeqeq;
Expand Down Expand Up @@ -262,7 +263,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
Box::new(camelcase::Camelcase),
Box::new(constructor_super::ConstructorSuper),
Box::new(default_param_last::DefaultParamLast),
Box::new(eqeqeq::Eqeqeq),
Box::new(eqeqeq::Eqeqeq::default()),
Box::new(explicit_function_return_type::ExplicitFunctionReturnType),
Box::new(explicit_module_boundary_types::ExplicitModuleBoundaryTypes),
Box::new(for_direction::ForDirection),
Expand Down Expand Up @@ -290,7 +291,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
Box::new(no_class_assign::NoClassAssign),
Box::new(no_compare_neg_zero::NoCompareNegZero),
Box::new(no_cond_assign::NoCondAssign),
Box::new(no_console::NoConsole),
Box::new(no_console::NoConsole::default()),
Box::new(no_const_assign::NoConstAssign),
Box::new(no_constant_condition::NoConstantCondition),
Box::new(no_control_regex::NoControlRegex),
Expand All @@ -302,7 +303,7 @@ fn get_all_rules_raw() -> Vec<Box<dyn LintRule>> {
Box::new(no_dupe_else_if::NoDupeElseIf),
Box::new(no_dupe_keys::NoDupeKeys),
Box::new(no_duplicate_case::NoDuplicateCase),
Box::new(no_empty::NoEmpty),
Box::new(no_empty::NoEmpty::default()),
Box::new(no_empty_character_class::NoEmptyCharacterClass),
Box::new(no_empty_enum::NoEmptyEnum),
Box::new(no_empty_interface::NoEmptyInterface),
Expand Down Expand Up @@ -512,7 +513,7 @@ mod tests {
Box::new(ban_unknown_rule_code::BanUnknownRuleCode),
Box::new(ban_unused_ignore::BanUnusedIgnore),
Box::new(no_redeclare::NoRedeclare),
Box::new(eqeqeq::Eqeqeq),
Box::new(eqeqeq::Eqeqeq::default()),
];

sort_rules_by_priority(&mut rules);
Expand Down
Loading