Skip to main content

claude_native/rules/
quality.rs

1use crate::detection::Language;
2use crate::rules::*;
3use crate::scan::ProjectContext;
4
5// ── Rule 5.1: Type annotations exist ────────────────────────────────
6
7pub struct TypeAnnotationsExist;
8
9impl Rule for TypeAnnotationsExist {
10    fn id(&self) -> &str { "5.1" }
11    fn name(&self) -> &str { "Type annotations exist" }
12    fn dimension(&self) -> Dimension { Dimension::CodeQuality }
13    fn severity(&self) -> Severity { Severity::Medium }
14
15    fn check(&self, ctx: &ProjectContext) -> RuleResult {
16        let pt = match &ctx.project_type {
17            Some(pt) => pt,
18            None => return self.skip(),
19        };
20
21        // Only relevant for dynamically-typed languages
22        let needs_types = pt.languages.iter().any(|l| matches!(l,
23            Language::JavaScript | Language::Python | Language::Ruby
24        ));
25
26        if !needs_types {
27            return self.pass(); // Rust, Go, TypeScript, etc. have built-in types
28        }
29
30        // Check for TypeScript (upgrades JS)
31        let has_typescript = pt.languages.iter().any(|l| matches!(l, Language::TypeScript));
32        if has_typescript {
33            return self.pass();
34        }
35
36        // Check for Python type hints (mypy, pyproject.toml with mypy config)
37        if pt.languages.iter().any(|l| matches!(l, Language::Python)) {
38            let has_mypy = ctx.has_file("mypy.ini")
39                || ctx.has_file(".mypy.ini")
40                || ctx.has_file("setup.cfg")
41                || ctx.read_root_file("pyproject.toml")
42                    .map(|c| c.contains("[tool.mypy]") || c.contains("mypy"))
43                    .unwrap_or(false);
44            if has_mypy {
45                return self.pass();
46            }
47        }
48
49        self.fail(
50            "Dynamically-typed language detected without type annotations/checking",
51            Suggestion {
52                priority: SuggestionPriority::HighImpact,
53                title: "Add type annotations".into(),
54                description: "For JS: migrate to TypeScript or add JSDoc types. For Python: add type hints and mypy. Types give Claude contracts to work with — reducing hallucinated return values.".into(),
55                effort: Effort::HalfDay,
56            },
57        )
58    }
59}
60
61// ── Rule 5.2: Tests exist ──────────────────────────────────────────
62
63pub struct TestsExist;
64
65impl Rule for TestsExist {
66    fn id(&self) -> &str { "5.2" }
67    fn name(&self) -> &str { "Tests exist" }
68    fn dimension(&self) -> Dimension { Dimension::CodeQuality }
69    fn severity(&self) -> Severity { Severity::High }
70
71    fn check(&self, ctx: &ProjectContext) -> RuleResult {
72        if !ctx.test_files.is_empty() {
73            let ratio = ctx.test_files.len() as f64 / ctx.source_file_count().max(1) as f64;
74            if ratio < 0.1 {
75                return self.warn(
76                    &format!("Only {} test files for {} source files ({:.0}% ratio)", ctx.test_files.len(), ctx.source_file_count(), ratio * 100.0),
77                    Suggestion {
78                        priority: SuggestionPriority::HighImpact,
79                        title: "Add more tests".into(),
80                        description: "Test coverage is very low. Tests are Claude's primary way to verify changes. Aim for at least 1 test file per 3 source files.".into(),
81                        effort: Effort::HalfDay,
82                    },
83                );
84            }
85            self.pass()
86        } else {
87            self.fail(
88                "No test files found",
89                Suggestion {
90                    priority: SuggestionPriority::HighImpact,
91                    title: "Add tests".into(),
92                    description: "Tests are Claude's primary verification mechanism. Without them, Claude can't validate its own changes. Start with tests for critical paths.".into(),
93                    effort: Effort::HalfDay,
94                },
95            )
96        }
97    }
98}
99
100// Rules 5.3-5.8 are in quality_extra.rs