use harn_parser::SNode;
use std::path::Path;
use crate::diagnostic::LintDiagnostic;
pub(crate) struct RuleCtx<'a> {
pub source: Option<&'a str>,
pub file_path: Option<&'a Path>,
}
pub(crate) trait Rule {
fn id(&self) -> &str;
fn visits_nodes(&self) -> bool {
false
}
fn check_program(
&mut self,
_program: &[SNode],
_ctx: &RuleCtx<'_>,
_out: &mut Vec<LintDiagnostic>,
) {
}
fn check_node(&mut self, _node: &SNode, _ctx: &RuleCtx<'_>, _out: &mut Vec<LintDiagnostic>) {}
fn finalize(&mut self, _ctx: &RuleCtx<'_>, _out: &mut Vec<LintDiagnostic>) {}
}
pub(crate) fn builtin_rules() -> Vec<Box<dyn Rule>> {
let rules: Vec<Box<dyn Rule>> = vec![
Box::new(LegacyDocComments),
Box::new(BlankLineBetweenItems),
Box::new(TrailingComma),
Box::new(ImportOrder),
Box::new(PreferOptionalShorthand),
Box::new(UnnecessaryParentheses),
Box::new(DeprecatedLlmOptions),
Box::new(ReminderLifecycle),
Box::new(ReminderProviderCount),
Box::new(ReminderRoleHint),
];
if cfg!(debug_assertions) {
let mut ids: Vec<&str> = rules.iter().map(|rule| rule.id()).collect();
let count = ids.len();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), count, "built-in lint rule ids must be unique");
}
rules
}
macro_rules! program_rule {
($name:ident, $id:literal, src, $func:path) => {
struct $name;
impl Rule for $name {
fn id(&self) -> &str {
$id
}
fn check_program(
&mut self,
program: &[SNode],
ctx: &RuleCtx<'_>,
out: &mut Vec<LintDiagnostic>,
) {
if let Some(src) = ctx.source {
$func(src, program, out);
}
}
}
};
($name:ident, $id:literal, ast, $func:path) => {
struct $name;
impl Rule for $name {
fn id(&self) -> &str {
$id
}
fn check_program(
&mut self,
program: &[SNode],
_ctx: &RuleCtx<'_>,
out: &mut Vec<LintDiagnostic>,
) {
$func(program, out);
}
}
};
($name:ident, $id:literal, srconly, $func:path) => {
struct $name;
impl Rule for $name {
fn id(&self) -> &str {
$id
}
fn check_program(
&mut self,
_program: &[SNode],
ctx: &RuleCtx<'_>,
out: &mut Vec<LintDiagnostic>,
) {
if let Some(src) = ctx.source {
$func(src, out);
}
}
}
};
}
program_rule!(
LegacyDocComments,
"legacy-doc-comment",
src,
crate::harndoc::check_legacy_doc_comments
);
program_rule!(
BlankLineBetweenItems,
"blank-line-between-items",
src,
crate::rules::blank_lines::check_blank_line_between_items
);
program_rule!(
ImportOrder,
"import-order",
src,
crate::rules::import_order::check_import_order
);
program_rule!(
PreferOptionalShorthand,
"prefer-optional-shorthand",
src,
crate::rules::optional_shorthand::check_prefer_optional_shorthand
);
program_rule!(
UnnecessaryParentheses,
"unnecessary-parentheses",
src,
crate::rules::unnecessary_parentheses::check_unnecessary_parentheses
);
program_rule!(
DeprecatedLlmOptions,
"deprecated_llm_options",
ast,
crate::rules::deprecated_llm_options::check_deprecated_llm_options
);
program_rule!(
ReminderLifecycle,
"reminder-infinite-discardable",
ast,
crate::rules::reminder_lifecycle::check_reminder_lifecycle_literals
);
program_rule!(
ReminderProviderCount,
"reminder-provider-count",
ast,
crate::rules::reminder_provider_count::check_reminder_provider_count
);
program_rule!(
ReminderRoleHint,
"reminder-role-hint-capability",
ast,
crate::rules::reminder_role_hint::check_reminder_role_hint_capabilities
);
program_rule!(
TrailingComma,
"trailing-comma",
srconly,
crate::rules::trailing_comma::check_trailing_comma
);
#[cfg(test)]
mod tests {
use super::builtin_rules;
use std::collections::HashSet;
#[test]
fn builtin_rule_ids_are_unique_and_nonempty() {
let rules = builtin_rules();
let mut seen = HashSet::new();
for rule in &rules {
let id = rule.id();
assert!(!id.is_empty(), "rule id must not be empty");
assert!(seen.insert(id), "duplicate built-in rule id: {id}");
}
}
}