use crate::{Confidence, Pillar, Severity};
use serde::Serialize;
use std::collections::BTreeSet;
use std::sync::OnceLock;
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum RuleKind {
Project,
Text,
Rust,
}
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ThresholdDefinition {
pub(crate) default: f64,
}
#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[allow(dead_code)]
pub(crate) enum OptionValueKind {
Boolean,
StringArray,
}
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct OptionDefinition {
pub(crate) name: &'static str,
pub(crate) description: &'static str,
pub(crate) value_kind: OptionValueKind,
}
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct FalsePositiveShape {
pub(crate) shape: &'static str,
pub(crate) mitigation: &'static str,
}
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RuleDefinition {
pub(crate) id: &'static str,
pub(crate) name: &'static str,
pub(crate) pillar: Pillar,
pub(crate) tier: &'static str,
pub(crate) kind: RuleKind,
pub(crate) default_severity: Severity,
pub(crate) confidence: Confidence,
pub(crate) threshold: Option<ThresholdDefinition>,
pub(crate) options: &'static [OptionDefinition],
pub(crate) default_enabled: bool,
pub(crate) description: &'static str,
pub(crate) false_positive_shapes: &'static [FalsePositiveShape],
pub(crate) related_rules: &'static [&'static str],
}
#[derive(Debug)]
pub(crate) struct RuleRegistry {
definitions: Vec<RuleDefinition>,
}
impl RuleRegistry {
pub(crate) fn new(mut definitions: Vec<RuleDefinition>) -> Result<Self, String> {
definitions.sort_by(|left, right| left.id.cmp(right.id));
let mut seen = BTreeSet::new();
for definition in &definitions {
if definition.id.starts_with("custom.") {
return Err(format!(
"built-in rule id `{}` uses reserved custom namespace",
definition.id
));
}
if !seen.insert(definition.id) {
return Err(format!("duplicate rule id `{}`", definition.id));
}
}
Ok(Self { definitions })
}
pub(crate) fn definitions(&self) -> &[RuleDefinition] {
&self.definitions
}
pub(crate) fn get(&self, rule_id: &str) -> Option<&RuleDefinition> {
self.definitions
.binary_search_by(|definition| definition.id.cmp(rule_id))
.ok()
.map(|index| &self.definitions[index])
}
pub(crate) fn contains(&self, rule_id: &str) -> bool {
self.get(rule_id).is_some()
}
pub(crate) fn option_value_kind(&self, rule_id: &str, option: &str) -> Option<OptionValueKind> {
self.get(rule_id).and_then(|definition| {
definition
.options
.iter()
.find(|item| item.name == option)
.map(|item| item.value_kind)
})
}
}
pub(crate) fn builtin_registry() -> RuleRegistry {
match RuleRegistry::new(builtin_definitions()) {
Ok(registry) => registry,
Err(error) => {
panic!("invalid built-in rule definitions: {error}");
}
}
}
pub(crate) fn builtin_registry_cached() -> &'static RuleRegistry {
static REGISTRY: OnceLock<RuleRegistry> = OnceLock::new();
REGISTRY.get_or_init(builtin_registry)
}
const COMPLEXITY_COGNITIVE_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(15.0));
const COMPLEXITY_CYCLOMATIC_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(10.0));
const COMPLEXITY_NESTING_DEPTH_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(4.0));
const ARCHITECTURE_LARGE_MODULE_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(25.0));
const ARCHITECTURE_MODULE_FAN_OUT_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(8.0));
const ARCHITECTURE_PUBLIC_API_SURFACE_THRESHOLD: Option<ThresholdDefinition> =
Some(threshold(12.0));
const DEPENDENCY_DUPLICATE_LOCKED_VERSION_THRESHOLD: Option<ThresholdDefinition> =
Some(threshold(2.0));
const FILE_LENGTH_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(600.0));
const FUNCTION_LENGTH_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(50.0));
const PARAMETER_COUNT_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(7.0));
const TEST_LONG_THRESHOLD: Option<ThresholdDefinition> = Some(threshold(120.0));
macro_rules! rule_definition {
(
$id:literal,
$name:literal,
$pillar:expr,
$kind:expr,
$default_severity:expr,
$confidence:expr,
$threshold:expr,
$description:literal $(,)?
) => {
rule_definition!(
$id,
$name,
$pillar,
$kind,
$default_severity,
$confidence,
$threshold,
$description,
false_positives: &[],
related: &[]
)
};
(
$id:literal,
$name:literal,
$pillar:expr,
$kind:expr,
$default_severity:expr,
$confidence:expr,
$threshold:expr,
$description:literal,
false_positives: $false_positives:expr,
related: $related:expr $(,)?
) => {
RuleDefinition {
id: $id,
name: $name,
pillar: $pillar,
tier: "v0.1",
kind: $kind,
default_severity: $default_severity,
confidence: $confidence,
threshold: $threshold,
options: &[],
default_enabled: true,
description: $description,
false_positive_shapes: $false_positives,
related_rules: $related,
}
};
}
mod idiom_security_size_test_definitions;
mod structure_docs_reliability_definitions;
mod waste_definitions;
use idiom_security_size_test_definitions::{
METADATA_RULES, NAMING_RULES, PERFORMANCE_AND_SECURITY_RULES, SENSITIVE_DATA_RULES, SIZE_RULES,
TEST_QUALITY_RULES,
};
use structure_docs_reliability_definitions::{
ARCHITECTURE_RULES, COMPLEXITY_RULES, CONCURRENCY_RULES, DEAD_CODE_RULES, DEPENDENCY_RULES,
DOCUMENTATION_AND_DESIGN_RULES, ERROR_HANDLING_RULES,
};
use waste_definitions::WASTE_RULES;
fn builtin_definitions() -> Vec<RuleDefinition> {
[
ARCHITECTURE_RULES,
COMPLEXITY_RULES,
DEAD_CODE_RULES,
DEPENDENCY_RULES,
DOCUMENTATION_AND_DESIGN_RULES,
CONCURRENCY_RULES,
ERROR_HANDLING_RULES,
METADATA_RULES,
NAMING_RULES,
PERFORMANCE_AND_SECURITY_RULES,
SENSITIVE_DATA_RULES,
SIZE_RULES,
TEST_QUALITY_RULES,
WASTE_RULES,
]
.into_iter()
.flat_map(|definitions| definitions.iter().copied())
.collect()
}
const fn threshold(default: f64) -> ThresholdDefinition {
ThresholdDefinition { default }
}