1use crate::LisetteDiagnostic;
2use syntax::ast::{DeadCodeCause, Span};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum IssueKind {
6 RedundantLetElse,
7 RedundantIfLet,
8 UnreachableIfLetElse,
9 RedundantIfLetElse,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum UnusedExpressionKind {
14 Literal,
15 Result,
16 Option,
17 Partial,
18 Value,
19}
20
21impl UnusedExpressionKind {
22 pub fn lint_name(&self) -> &'static str {
23 match self {
24 Self::Literal => "unused_literal",
25 Self::Result => "unused_result",
26 Self::Option => "unused_option",
27 Self::Partial => "unused_partial",
28 Self::Value => "unused_value",
29 }
30 }
31}
32
33pub fn unused_variable(span: &Span, name: &str, is_struct_field: bool) -> LisetteDiagnostic {
34 let help = if is_struct_field {
35 format!(
36 "Use this variable or prefix it with an underscore: `{}: _{}`.",
37 name, name
38 )
39 } else {
40 format!(
41 "Use this variable or prefix it with an underscore: `_{}`.",
42 name
43 )
44 };
45 LisetteDiagnostic::warn("Unused variable")
46 .with_lint_code("unused_variable")
47 .with_span_label(span, "never used")
48 .with_help(help)
49}
50
51pub fn unused_parameter(span: &Span, name: &str) -> LisetteDiagnostic {
52 LisetteDiagnostic::warn("Unused parameter")
53 .with_lint_code("unused_param")
54 .with_span_label(span, "never used")
55 .with_help(format!(
56 "Use this parameter or prefix it with an underscore: `_{}`.",
57 name
58 ))
59}
60
61pub fn unused_mut(span: &Span) -> LisetteDiagnostic {
62 LisetteDiagnostic::warn("Unused `mut`")
63 .with_lint_code("unnecessary_mut")
64 .with_span_label(span, "declared as mutable")
65 .with_help("Remove `mut` from the declaration if you do not need to mutate the variable")
66}
67
68pub fn written_but_not_read(span: &Span, name: &str) -> LisetteDiagnostic {
69 LisetteDiagnostic::warn("Variable assigned but never read")
70 .with_lint_code("assigned_but_never_read")
71 .with_span_label(span, format!("`{}` is assigned but never read", name))
72 .with_help(
73 "Read the variable after assigning it, or explicitly discard it with `let _ = ...`",
74 )
75}
76
77pub fn dead_code(span: &Span, cause: DeadCodeCause) -> LisetteDiagnostic {
78 let (code, msg) = match cause {
79 DeadCodeCause::Return => ("dead_code_after_return", "Unreachable code after return"),
80 DeadCodeCause::Break => ("dead_code_after_break", "Unreachable code after break"),
81 DeadCodeCause::Continue => (
82 "dead_code_after_continue",
83 "Unreachable code after continue",
84 ),
85 DeadCodeCause::DivergingIf => (
86 "dead_code_after_diverging_if",
87 "Unreachable code after diverging if/else",
88 ),
89 DeadCodeCause::DivergingMatch => (
90 "dead_code_after_diverging_match",
91 "Unreachable code after diverging match",
92 ),
93 DeadCodeCause::InfiniteLoop => (
94 "dead_code_after_infinite_loop",
95 "Unreachable code after infinite loop",
96 ),
97 DeadCodeCause::DivergingCall => (
98 "dead_code_after_diverging_call",
99 "Unreachable code after diverging function call",
100 ),
101 };
102 LisetteDiagnostic::warn(msg)
103 .with_lint_code(code)
104 .with_span_label(span, "unreachable from this point onward")
105 .with_help("Remove this line and all code after it")
106}
107
108pub fn pattern_issue(span: &Span, kind: IssueKind) -> LisetteDiagnostic {
109 let (code, message, label, help) = match kind {
110 IssueKind::RedundantLetElse => (
111 "redundant_let_else",
112 "Redundant `else` in `let...else`",
113 "always matches",
114 "Remove the `else` block since the pattern cannot fail",
115 ),
116 IssueKind::RedundantIfLet => (
117 "redundant_if_let",
118 "Redundant `if let` pattern",
119 "always matches",
120 "Use `let` instead of `if let` since the pattern cannot fail",
121 ),
122 IssueKind::UnreachableIfLetElse => (
123 "unreachable_if_let_else",
124 "Unreachable `else` branch",
125 "this branch can never execute",
126 "Remove the `else` branch since the pattern always matches",
127 ),
128 IssueKind::RedundantIfLetElse => (
129 "redundant_if_let_else",
130 "Redundant `else` branch",
131 "this branch does nothing",
132 "Remove the `else` branch",
133 ),
134 };
135
136 LisetteDiagnostic::warn(message)
137 .with_lint_code(code)
138 .with_span_label(span, label)
139 .with_help(help)
140}
141
142pub fn discarded_result_in_tail(span: &Span, return_type: &str) -> LisetteDiagnostic {
143 LisetteDiagnostic::warn("`Result` is silently discarded")
144 .with_lint_code("unused_result")
145 .with_span_label(span, "failure will go unnoticed")
146 .with_help(format!(
147 "Handle this `Result` with `?` or `match`, explicitly discard it with `let _ = ...`, or return it by adding `-> {}` to the function signature",
148 return_type
149 ))
150}
151
152pub fn discarded_option_in_tail(span: &Span, return_type: &str) -> LisetteDiagnostic {
153 LisetteDiagnostic::warn("Unused Option")
154 .with_lint_code("unused_option")
155 .with_span_label(span, "this `Option` is discarded")
156 .with_help(format!(
157 "Handle this `Option`, explicitly discard it with `let _ = ...`, or return it by adding `-> {}` to the function signature",
158 return_type
159 ))
160}
161
162pub fn discarded_partial_in_tail(span: &Span, return_type: &str) -> LisetteDiagnostic {
163 LisetteDiagnostic::warn("`Partial` is silently discarded")
164 .with_lint_code("unused_partial")
165 .with_span_label(span, "partial result will go unnoticed")
166 .with_help(format!(
167 "Handle this `Partial` with `match`, explicitly discard it with `let _ = ...`, or return it by adding `-> {}` to the function signature",
168 return_type
169 ))
170}
171
172pub fn unused_expression(span: &Span, kind: UnusedExpressionKind) -> LisetteDiagnostic {
173 let (code, msg, label, help) = match kind {
174 UnusedExpressionKind::Literal => (
175 "unused_literal",
176 "Unused literal",
177 "this literal has no effect",
178 "Remove this literal",
179 ),
180 UnusedExpressionKind::Result => (
181 "unused_result",
182 "`Result` is silently discarded",
183 "failure will go unnoticed",
184 "Handle this `Result` with `?` or `match`, or explicitly discard it with `let _ = ...`",
185 ),
186 UnusedExpressionKind::Option => (
187 "unused_option",
188 "Unused Option",
189 "this `Option` is discarded",
190 "Handle this `Option`, or explicitly discard it with `let _ = ...`",
191 ),
192 UnusedExpressionKind::Partial => (
193 "unused_partial",
194 "`Partial` is silently discarded",
195 "partial result will go unnoticed",
196 "Handle this `Partial` with `match`, or explicitly discard it with `let _ = ...`",
197 ),
198 UnusedExpressionKind::Value => (
199 "unused_value",
200 "Unused expression value",
201 "this value is discarded",
202 "Use the value, or ignore with `let _ = ...`",
203 ),
204 };
205 LisetteDiagnostic::warn(msg)
206 .with_lint_code(code)
207 .with_span_label(span, label)
208 .with_help(help)
209}
210
211pub fn unnecessary_reference(span: &Span, name: Option<&str>) -> LisetteDiagnostic {
212 let (label, help) = match name {
213 Some(n) => (
214 format!("`{}` is already a reference", n),
215 format!("Remove the `&` operator from `{}`", n),
216 ),
217 None => (
218 "value is already a reference".to_string(),
219 "Remove the `&` operator".to_string(),
220 ),
221 };
222 LisetteDiagnostic::warn("Unnecessary `&`")
223 .with_lint_code("unnecessary_reference")
224 .with_span_label(span, label)
225 .with_help(help)
226}
227
228pub fn unused_type_parameter(span: &Span) -> LisetteDiagnostic {
229 LisetteDiagnostic::warn("Unused type parameter")
230 .with_lint_code("unused_type_param")
231 .with_span_label(span, "never used")
232 .with_help("Remove the unused type parameter or use it in the signature")
233}
234
235pub fn type_param_only_in_bound(span: &Span, name: &str) -> LisetteDiagnostic {
236 LisetteDiagnostic::warn("Type parameter only used in bound")
237 .with_lint_code("type_param_only_in_bound")
238 .with_span_label(
239 span,
240 format!("`{}` is only used inside another parameter's bound", name),
241 )
242 .with_help("Remove it, or use it in a parameter type, return type, or bound left-hand side")
243}
244
245pub fn ineffective_try_block(span: &Span) -> LisetteDiagnostic {
246 LisetteDiagnostic::warn("Ineffective `try` block")
247 .with_lint_code("try_block_no_success_path")
248 .with_span_label(span, "always propagates")
249 .with_help("A `try` block is effective only if the expression may succeed or fail")
250}
251
252pub fn double_negation(span: &Span, is_bool: bool) -> LisetteDiagnostic {
253 let (code, msg) = if is_bool {
254 ("double_bool_negation", "Double boolean negation")
255 } else {
256 ("double_int_negation", "Double numeric negation")
257 };
258
259 LisetteDiagnostic::warn(msg)
260 .with_lint_code(code)
261 .with_span_label(span, "accidental double negation")
262 .with_help("Remove one of the negation operators")
263}
264
265pub fn tautological_comparison(span: &Span, always_true: bool) -> LisetteDiagnostic {
266 let result = if always_true { "true" } else { "false" };
267
268 LisetteDiagnostic::warn("Tautological comparison")
269 .with_lint_code("self_comparison")
270 .with_span_label(span, "comparing to itself")
271 .with_help(format!(
272 "This condition is always {}. Did you mean to compare different values?",
273 result
274 ))
275}
276
277pub fn self_assignment(span: &Span) -> LisetteDiagnostic {
278 LisetteDiagnostic::warn("Self-assignment")
279 .with_lint_code("self_assignment")
280 .with_span_label(span, "assigning to itself")
281 .with_help("Correct this assignment")
282}
283
284pub fn duplicate_logical_operand(span: &Span, operand_text: &str) -> LisetteDiagnostic {
285 LisetteDiagnostic::warn("Duplicate logical operand")
286 .with_lint_code("duplicate_logical_operand")
287 .with_span_label(span, "accidental repetition")
288 .with_help(format!("Simplify to `{operand_text}`"))
289}
290
291pub fn bool_literal_comparison(span: &Span, replacement: &str) -> LisetteDiagnostic {
292 LisetteDiagnostic::warn("Redundant comparison to boolean literal")
293 .with_lint_code("bool_literal_comparison")
294 .with_span_label(span, "needlessly verbose")
295 .with_help(format!("Simplify to `{replacement}`"))
296}
297
298pub fn identical_if_branches(span: &Span) -> LisetteDiagnostic {
299 LisetteDiagnostic::warn("Identical if-else branches")
300 .with_lint_code("identical_if_branches")
301 .with_span_label(span, "both branches are equivalent")
302 .with_help("Remove the `if` and keep a single copy of the branch body")
303}
304
305pub fn empty_match_arm(span: &Span) -> LisetteDiagnostic {
306 LisetteDiagnostic::warn("Empty match arm")
307 .with_lint_code("empty_match_arm")
308 .with_span_label(span, "forgotten stub?")
309 .with_help("Return `()` to indicate an intentional no-op in a match arm")
310}
311
312pub fn unnecessary_parens(span: &Span, keyword: &str) -> LisetteDiagnostic {
313 LisetteDiagnostic::warn("Unnecessary parens")
314 .with_lint_code("excess_parens_on_condition")
315 .with_span_label(span, "remove parens")
316 .with_help(format!(
317 "Lisette does not require parens around `{}` conditions",
318 keyword
319 ))
320}
321
322pub fn match_on_literal(span: &Span) -> LisetteDiagnostic {
323 LisetteDiagnostic::warn("Ineffective match")
324 .with_lint_code("match_on_literal")
325 .with_span_label(span, "already known")
326 .with_help(
327 "Matching on a literal is ineffective, because this always succeeds. Did you mean to match on a variable?",
328 )
329}
330
331pub fn single_arm_match(span: &Span, pattern_suggestion: &str) -> LisetteDiagnostic {
332 LisetteDiagnostic::warn("Ineffective match")
333 .with_lint_code("single_arm_match")
334 .with_span_label(span, "should be `if let`")
335 .with_help(format!(
336 "A match with a single meaningful arm is ineffective. Use `if let {} = value {{ ... }}` instead.",
337 pattern_suggestion
338 ))
339}
340
341pub fn uninterpolated_fstring(span: &Span) -> LisetteDiagnostic {
342 LisetteDiagnostic::warn("Uninterpolated f-string")
343 .with_lint_code("uninterpolated_fstring")
344 .with_span_label(span, "zero interpolations")
345 .with_help("Remove the `f` prefix. A string without interpolations does not need to be a format string")
346}
347
348pub fn unnecessary_raw_string(span: &Span) -> LisetteDiagnostic {
349 LisetteDiagnostic::warn("Unnecessary raw string")
350 .with_lint_code("unnecessary_raw_string")
351 .with_span_label(span, "no backslashes")
352 .with_help("Remove the `r` prefix. A string without backslashes does not need to be raw")
353}
354
355pub fn expression_only_fstring(span: &Span) -> LisetteDiagnostic {
356 LisetteDiagnostic::warn("Expression-only f-string")
357 .with_lint_code("expression_only_fstring")
358 .with_span_label(span, "the entire f-string is an expression")
359 .with_help("Use the expression directly. Wrapping it in an f-string adds no value")
360}
361
362pub fn rest_only_slice_pattern(span: &Span, help: impl Into<String>) -> LisetteDiagnostic {
363 LisetteDiagnostic::warn("Ineffective pattern")
364 .with_lint_code("rest_only_slice_pattern")
365 .with_span_label(span, "always matches")
366 .with_help(help)
367}
368
369pub fn miscased_pascal(span: &Span, code: &str, suggested_name: &str) -> LisetteDiagnostic {
370 LisetteDiagnostic::warn("Miscased name")
371 .with_lint_code(code)
372 .with_span_label(span, "expected PascalCase")
373 .with_help(format!("Rename to `{}`", suggested_name))
374}
375
376pub fn miscased_snake(span: &Span, code: &str, suggested_name: &str) -> LisetteDiagnostic {
377 LisetteDiagnostic::warn("Miscased name")
378 .with_lint_code(code)
379 .with_span_label(span, "expected snake_case")
380 .with_help(format!("Rename to `{}`", suggested_name))
381}
382
383pub fn miscased_screaming_snake(span: &Span, suggested_name: &str) -> LisetteDiagnostic {
384 LisetteDiagnostic::warn("Miscased name")
385 .with_lint_code("constant_not_screaming_snake_case")
386 .with_span_label(span, "expected SCREAMING_SNAKE_CASE")
387 .with_help(format!("Rename to `{}`", suggested_name))
388}
389
390pub fn unused_field(span: &Span) -> LisetteDiagnostic {
391 LisetteDiagnostic::warn("Unused field")
392 .with_lint_code("unused_struct_field")
393 .with_span_label(span, "never read")
394 .with_help("Use or remove this field")
395}
396
397pub fn unused_variant(span: &Span) -> LisetteDiagnostic {
398 LisetteDiagnostic::warn("Unused variant")
399 .with_lint_code("unused_enum_variant")
400 .with_span_label(span, "never constructed or matched")
401 .with_help("Use or remove this enum variant")
402}
403
404pub fn unused_import(span: &Span) -> LisetteDiagnostic {
405 LisetteDiagnostic::warn("Unused import")
406 .with_lint_code("unused_import")
407 .with_span_label(span, "never used")
408 .with_help("Use or remove this import")
409}
410
411pub fn unused_type(span: &Span) -> LisetteDiagnostic {
412 LisetteDiagnostic::warn("Unused type")
413 .with_lint_code("unused_type")
414 .with_span_label(span, "never used")
415 .with_help("Use or remove this type")
416}
417
418pub fn unused_function(span: &Span) -> LisetteDiagnostic {
419 LisetteDiagnostic::warn("Unused function")
420 .with_lint_code("unused_function")
421 .with_span_label(span, "never called")
422 .with_help("Call or remove this function")
423}
424
425pub fn unused_constant(span: &Span) -> LisetteDiagnostic {
426 LisetteDiagnostic::warn("Unused constant")
427 .with_lint_code("unused_constant")
428 .with_span_label(span, "never used")
429 .with_help("Use or remove this constant")
430}
431
432pub fn private_type_in_public_api(
433 span: Option<&Span>,
434 private_type: &str,
435 public_definition: &str,
436) -> LisetteDiagnostic {
437 let mut diagnostic = LisetteDiagnostic::warn(format!(
438 "Private type `{}` in public API",
439 private_type
440 ))
441 .with_lint_code("internal_type_leak")
442 .with_help(format!(
443 "`{}` is private but exposed by `{}`, which is public. Add `pub` to the private type or remove it from the public API",
444 private_type, public_definition
445 ));
446
447 if let Some(s) = span {
448 diagnostic = diagnostic.with_span_label(s, "private");
449 }
450
451 diagnostic
452}
453
454pub fn unknown_attribute(span: &Span, name: &str) -> LisetteDiagnostic {
455 LisetteDiagnostic::warn("Unknown attribute")
456 .with_lint_code("unknown_attribute")
457 .with_span_label(span, "not recognized")
458 .with_help(format!(
459 "`{}` is not a recognized attribute. Known attributes: `#[json]`, `#[xml]`, `#[yaml]`, `#[toml]`, `#[db]`, `#[bson]`, `#[msgpack]`, `#[mapstructure]`, `#[tag]`",
460 name
461 ))
462}
463
464pub fn field_attribute_without_struct_attribute(
465 field_span: &Span,
466 attribute_name: &str,
467) -> LisetteDiagnostic {
468 LisetteDiagnostic::error("Orphan field attribute")
469 .with_lint_code("orphan_field_attribute")
470 .with_span_label(field_span, "field has attribute but struct does not")
471 .with_help(format!(
472 "Add `#[{}]` atop the struct definition to enable field-level attributes",
473 attribute_name
474 ))
475}
476
477pub fn duplicate_tag_key(span: &Span, key: &str, first_span: &Span) -> LisetteDiagnostic {
478 LisetteDiagnostic::error("Duplicate tag")
479 .with_lint_code("duplicate_tag")
480 .with_span_label(span, "duplicate")
481 .with_span_label(first_span, "first occurrence")
482 .with_help(format!(
483 "Remove one of the `{}` attributes - each tag key may appear only once per field",
484 key
485 ))
486}
487
488pub fn conflicting_case_transforms(span: &Span) -> LisetteDiagnostic {
489 LisetteDiagnostic::error("Conflicting case transforms")
490 .with_lint_code("conflicting_case_transforms")
491 .with_span_label(span, "conflicting")
492 .with_help("Choose either `snake_case` or `camel_case`, not both")
493}
494
495pub fn tag_has_alias(span: &Span, key: &str) -> LisetteDiagnostic {
496 LisetteDiagnostic::warn("Prefer predefined tag alias")
497 .with_lint_code("tag_has_alias")
498 .with_span_label(span, "use alias instead")
499 .with_help(format!(
500 "Use `#[{}(...)]` instead of `#[tag(...)]` for better validation",
501 key
502 ))
503}
504
505pub fn unknown_tag_option(span: &Span, option: &str) -> LisetteDiagnostic {
506 LisetteDiagnostic::warn("Unknown tag option")
507 .with_lint_code("unknown_tag_option")
508 .with_span_label(span, "not recognized")
509 .with_help(format!(
510 "`{}` is not a recognized tag option. Known options: `snake_case`, `camel_case`, `omitempty`, `!omitempty`, `skip`, `string`",
511 option
512 ))
513}