1mod ast_lints;
2mod fact_lints;
3mod ref_lints;
4
5use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
6
7use crate::facts::Facts;
8use crate::store::Store;
9use diagnostics::DiagnosticSink;
10use diagnostics::LisetteDiagnostic;
11use syntax::ast::Expression;
12use syntax::program::File;
13use syntax::program::Module;
14use syntax::program::UnusedInfo;
15
16use ast_lints::AstLintGroup;
17use fact_lints::FactLintGroup;
18use ref_lints::RefLintGroup;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum Lint {
22 UnusedVariable,
23 UnusedParameter,
24 UnusedMut,
25 UnusedImport,
26 UnusedType,
27 UnusedFunction,
28 UnusedConstant,
29 UnusedStructField,
30 UnusedEnumVariant,
31 UnusedLiteral,
32 UnusedResult,
33 UnusedOption,
34 UnusedValue,
35 DeadCodeAfterReturn,
36 DeadCodeAfterBreak,
37 DeadCodeAfterContinue,
38 DeadCodeAfterDivergingIf,
39 DeadCodeAfterDivergingMatch,
40 DeadCodeAfterInfiniteLoop,
41 DeadCodeAfterDivergingCall,
42 DoubleBoolNegation,
43 DoubleIntNegation,
44 SelfComparison,
45 SelfAssignment,
46 MatchLiteralCollection,
47 EmptyMatchArm,
48 InternalTypeLeak,
49 UnnecessaryReference,
50 UnusedTypeParameter,
51 RestOnlySlicePattern,
52 NonPascalCaseType,
53 NonPascalCaseTypeParameter,
54 NonPascalCaseEnumVariant,
55 NonSnakeCaseFunction,
56 NonSnakeCaseVariable,
57 NonSnakeCaseParameter,
58 NonSnakeCaseStructField,
59 NonScreamingSnakeCaseConstant,
60 RedundantIfLet,
61 RedundantLetElse,
62 SingleArmMatch,
63 RedundantIfLetElse,
64 UnreachableIfLetElse,
65 TryBlockNoSuccessPath,
66 ExcessParensOnCondition,
67}
68
69#[derive(Debug, Clone, Default)]
70pub struct LintConfig {
71 disabled: HashSet<Lint>,
72}
73
74impl LintConfig {
75 pub fn is_enabled(&self, lint: Lint) -> bool {
76 !self.disabled.contains(&lint)
77 }
78}
79
80pub trait LintRule {
81 fn check(&self, ctx: &LintContext) -> Vec<LisetteDiagnostic>;
82}
83
84pub struct LintContext<'a> {
85 pub ast: &'a [Expression],
86 pub facts: &'a Facts,
87 pub module: Option<&'a Module>,
88 pub config: &'a LintConfig,
89 pub is_d_lis: bool,
90 pub files: &'a HashMap<u32, File>,
92}
93
94fn all_lint_rules() -> Vec<Box<dyn LintRule>> {
95 vec![
96 Box::new(FactLintGroup),
97 Box::new(AstLintGroup),
98 Box::new(RefLintGroup),
99 ]
100}
101
102pub fn lint_all_modules(store: &Store, facts: &Facts, sink: &DiagnosticSink) -> UnusedInfo {
103 let config = LintConfig::default();
104 let mut unused = UnusedInfo::default();
105
106 {
108 let empty_ast = [];
109 let empty_files = HashMap::default();
110 let ctx = LintContext {
111 ast: &empty_ast,
112 facts,
113 module: None,
114 config: &config,
115 is_d_lis: false,
116 files: &empty_files,
117 };
118 let mut diagnostics = FactLintGroup.check(&ctx);
119 diagnostics.sort_by_key(|d| d.primary_offset());
120 for diagnostic in diagnostics {
121 sink.push(diagnostic);
122 }
123 }
124
125 for module in store.modules.values() {
126 if module.is_internal() {
127 continue;
128 }
129 lint_module(module, facts, &config, sink, &mut unused);
130 }
131
132 for b in facts.bindings.values() {
133 if !b.used {
134 unused.mark_binding_unused(b.span);
135 }
136 }
137
138 unused
139}
140
141fn lint_module(
142 module: &Module,
143 facts: &Facts,
144 config: &LintConfig,
145 sink: &DiagnosticSink,
146 unused: &mut UnusedInfo,
147) {
148 let ref_result = ref_lints::run_ref_lints(module, &module.files, config, facts);
150 if !ref_result.unused_import_aliases.is_empty() {
151 unused.imports_by_module.insert(
152 module.id.clone().into(),
153 ref_result
154 .unused_import_aliases
155 .into_iter()
156 .map(|s| s.into())
157 .collect(),
158 );
159 }
160 for span in ref_result.unused_definition_spans {
161 unused.mark_definition_unused(span);
162 }
163 for diagnostic in ref_result.diagnostics {
164 sink.push(diagnostic);
165 }
166
167 for file in module.files.values() {
169 let ctx = LintContext {
170 ast: &file.items,
171 facts,
172 module: Some(module),
173 config,
174 is_d_lis: file.is_d_lis(),
175 files: &module.files,
176 };
177
178 let mut diagnostics = AstLintGroup.check(&ctx);
179 diagnostics.sort_by_key(|d| d.primary_offset());
180 for diagnostic in diagnostics {
181 sink.push(diagnostic);
182 }
183 }
184}
185
186pub fn lint_file(ctx: &LintContext, sink: &DiagnosticSink) {
187 let mut diagnostics: Vec<_> = all_lint_rules()
188 .iter()
189 .flat_map(|lint| lint.check(ctx))
190 .collect();
191
192 diagnostics.sort_by_key(|d| d.primary_offset());
193
194 for diagnostic in diagnostics {
195 sink.push(diagnostic);
196 }
197}