Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions crates/diagnostics/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,35 @@ pub fn miscased_screaming_snake(span: &Span, suggested_name: &str) -> LisetteDia
.with_help(format!("Rename to `{}`", suggested_name))
}

pub fn enum_variant_names(
span: &Span,
enum_name: &str,
is_prefix: bool,
example_old: &str,
example_new: &str,
) -> LisetteDiagnostic {
let affix = if is_prefix { "prefix" } else { "suffix" };
LisetteDiagnostic::info("Variant names repeat the enum name")
.with_lint_code("enum_variant_names")
.with_span_label(span, "each variant repeats this name")
.with_help(format!(
"Drop the `{enum_name}` {affix} from each variant (`{example_old}` to `{example_new}`). A variant is already written as `{enum_name}.{example_new}`, so the {affix} repeats the enum name."
))
}

pub fn self_named_constructors(
span: &Span,
type_name: &str,
method_name: &str,
) -> LisetteDiagnostic {
LisetteDiagnostic::info("Constructor named after its type")
.with_lint_code("self_named_constructors")
.with_span_label(span, "repeats the type name")
.with_help(format!(
"Calling this as `{type_name}.{method_name}()` repeats the type name. Rename it to `new`, the conventional constructor name."
))
}

pub fn unused_field(span: &Span) -> LisetteDiagnostic {
LisetteDiagnostic::warn("Unused field")
.with_lint_code("unused_struct_field")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::passes::lints::ast_walk::casing::to_snake_case;
use crate::passes::walk::NodeCtx;
use syntax::ast::Expression;

pub fn check_enum_variant_names(expression: &Expression, ctx: &NodeCtx) {
if ctx.is_d_lis {
return;
}

let Expression::Enum {
name,
name_span,
variants,
..
} = expression
else {
return;
};

if variants.len() < 2 {
return;
}

let enum_words = snake_words(name);
if enum_words.is_empty() {
return;
}

let variant_words: Vec<Vec<String>> = variants.iter().map(|v| snake_words(&v.name)).collect();

let is_prefix = variant_words
.iter()
.all(|words| words.len() > enum_words.len() && words[..enum_words.len()] == enum_words[..]);

let is_suffix = !is_prefix
&& variant_words.iter().all(|words| {
words.len() > enum_words.len()
&& words[words.len() - enum_words.len()..] == enum_words[..]
});

if !is_prefix && !is_suffix {
return;
}

let cut: usize = enum_words.iter().map(|word| word.chars().count()).sum();
let first = variants[0].name.as_str();
let first_len = first.chars().count();
let example_new: String = if is_prefix {
first.chars().skip(cut).collect()
} else {
first.chars().take(first_len - cut).collect()
};

ctx.sink.push(diagnostics::lint::enum_variant_names(
name_span,
name,
is_prefix,
first,
&example_new,
));
}

fn snake_words(name: &str) -> Vec<String> {
to_snake_case(name)
.split('_')
.filter(|word| !word.is_empty())
.map(String::from)
.collect()
}
8 changes: 7 additions & 1 deletion crates/passes/src/passes/lints/ast_walk/checks/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use ecow::EcoString;
use rustc_hash::FxHashSet as HashSet;
use syntax::ast::{
BinaryOperator, Expression, FormatStringPart, Literal, Pattern, Span, UnaryOperator,
BinaryOperator, Binding, Expression, FormatStringPart, Literal, Pattern, Span, UnaryOperator,
};
use syntax::program::DefinitionBody;
use syntax::types::{SimpleKind, Type, unqualified_name};
Expand All @@ -13,6 +13,12 @@ pub(super) use crate::passes::comparison::{
expressions_equivalent, flip_comparison, is_side_effect_free, signed_integer_literal,
};

pub(super) fn first_param_is_self(params: &[Binding]) -> bool {
params.first().is_some_and(|param| {
matches!(&param.pattern, Pattern::Identifier { identifier, .. } if identifier == "self")
})
}

pub(super) fn struct_field_names(
store: &Store,
ty: &Type,
Expand Down
4 changes: 4 additions & 0 deletions crates/passes/src/passes/lints/ast_walk/checks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod dup_arg;
mod duplicate_cutset;
mod duplicate_logical_operand;
mod empty_match_arm;
mod enum_variant_names;
mod equal_operands;
mod equatable_if_let;
mod excess_parens_on_condition;
Expand Down Expand Up @@ -81,6 +82,7 @@ mod replaceable_with_zero_fill;
mod rest_only_slice_pattern;
mod self_assignment;
mod self_comparison;
mod self_named_constructors;
mod single_arm_select;
mod single_element_loop;
mod type_limit_comparison;
Expand Down Expand Up @@ -112,6 +114,7 @@ pub use dup_arg::check_dup_arg;
pub use duplicate_cutset::check_duplicate_cutset;
pub use duplicate_logical_operand::check_duplicate_logical_operand;
pub use empty_match_arm::check_empty_match_arm;
pub use enum_variant_names::check_enum_variant_names;
pub use equal_operands::check_equal_operands;
pub use equatable_if_let::check_equatable_if_let;
pub use excess_parens_on_condition::check_excess_parens_on_condition;
Expand Down Expand Up @@ -183,6 +186,7 @@ pub use replaceable_with_zero_fill::check_replaceable_with_zero_fill;
pub use rest_only_slice_pattern::check_rest_only_slice_pattern;
pub use self_assignment::check_self_assignment;
pub use self_comparison::check_self_comparison;
pub use self_named_constructors::check_self_named_constructors;
pub use single_arm_select::check_single_arm_select;
pub use single_element_loop::check_single_element_loop;
pub use type_limit_comparison::check_type_limit_comparison;
Expand Down
11 changes: 4 additions & 7 deletions crates/passes/src/passes/lints/ast_walk/checks/naming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use syntax::ast::{Expression, Generic, Pattern, Span};
use crate::passes::lints::ast_walk::casing::{is_snake_case, to_pascal_case, to_snake_case};
use crate::passes::walk::NodeCtx;

use super::helpers::first_param_is_self;

pub fn check_expression_naming(expression: &Expression, ctx: &NodeCtx) {
let sink = ctx.sink;
let is_d_lis = ctx.is_d_lis;
Expand Down Expand Up @@ -101,13 +103,8 @@ pub fn check_expression_naming(expression: &Expression, ctx: &NodeCtx) {
params,
..
} => {
if !is_d_lis {
let is_method = params.first().is_some_and(|p| {
matches!(&p.pattern, Pattern::Identifier { identifier, .. } if identifier == "self")
});
if !is_method {
check_snake_case(name, name_span, "non_snake_case_function", sink);
}
if !is_d_lis && !first_param_is_self(params) {
check_snake_case(name, name_span, "non_snake_case_function", sink);
}

for generic in generics {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::passes::lints::ast_walk::casing::to_snake_case;
use crate::passes::walk::NodeCtx;
use syntax::ast::{Annotation, Expression};

use super::helpers::first_param_is_self;

pub fn check_self_named_constructors(expression: &Expression, ctx: &NodeCtx) {
if ctx.is_d_lis {
return;
}

let Expression::ImplBlock {
receiver_name,
methods,
..
} = expression
else {
return;
};

if receiver_name.is_empty() {
return;
}

let expected = to_snake_case(receiver_name);

for method in methods {
let Expression::Function {
name,
name_span,
params,
return_annotation,
..
} = method
else {
continue;
};

if first_param_is_self(params) {
continue;
}

if name.as_str() != expected {
continue;
}

let returns_self = matches!(
return_annotation,
Annotation::Constructor { name: returned, .. } if returned == receiver_name
);
if !returns_self {
continue;
}

ctx.sink.push(diagnostics::lint::self_named_constructors(
name_span,
receiver_name,
name,
));
}
}
30 changes: 17 additions & 13 deletions crates/passes/src/passes/lints/ast_walk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ use checks::{
check_bool_literal_comparison, check_collapsible_else_if, check_collapsible_if,
check_collapsible_match, check_double_comparison, check_double_negation, check_dup_arg,
check_duplicate_cutset, check_duplicate_logical_operand, check_empty_match_arm,
check_equal_operands, check_equatable_if_let, check_excess_parens_on_condition,
check_exit_after_defer, check_expression_naming, check_float_cmp,
check_float_equality_without_abs, check_goos_goarch_comparison, check_identical_if_branches,
check_identical_match_arms, check_ineffective_bit_mask, check_integer_division_to_zero,
check_invisible_in_string_expression, check_invisible_in_string_pattern, check_let_and_return,
check_loop_runs_once, check_lost_cancel, check_lost_query_mutation, check_manual_bytes_equal,
check_enum_variant_names, check_equal_operands, check_equatable_if_let,
check_excess_parens_on_condition, check_exit_after_defer, check_expression_naming,
check_float_cmp, check_float_equality_without_abs, check_goos_goarch_comparison,
check_identical_if_branches, check_identical_match_arms, check_ineffective_bit_mask,
check_integer_division_to_zero, check_invisible_in_string_expression,
check_invisible_in_string_pattern, check_let_and_return, check_loop_runs_once,
check_lost_cancel, check_lost_query_mutation, check_manual_bytes_equal,
check_manual_compound_assignment, check_manual_contains, check_manual_equal_fold,
check_manual_filter, check_manual_find, check_manual_is_empty, check_manual_map,
check_manual_map_or, check_manual_ok_err, check_manual_ok_or, check_manual_option_zip,
Expand All @@ -45,13 +46,14 @@ use checks::{
check_redundant_pattern_matching, check_redundant_rebinding, check_redundant_slice_bounds,
check_redundant_sprintf, check_regexp_in_loop, check_replaceable_with_zero_fill,
check_rest_only_slice_pattern, check_self_assignment, check_self_comparison,
check_single_arm_select, check_single_element_loop, check_type_limit_comparison,
check_uninterpolated_fstring, check_unnecessary_bool, check_unnecessary_first_then_check,
check_unnecessary_lazy_evaluations, check_unnecessary_map_on_constructor,
check_unnecessary_min_or_max, check_unnecessary_range_loop,
check_unnecessary_raw_string_expression, check_unnecessary_raw_string_pattern,
check_unnecessary_return, check_unsigned_comparison, check_verbose_failure_propagation,
check_waitgroup_add_in_task, check_while_let_loop, check_wildcard_in_or_patterns,
check_self_named_constructors, check_single_arm_select, check_single_element_loop,
check_type_limit_comparison, check_uninterpolated_fstring, check_unnecessary_bool,
check_unnecessary_first_then_check, check_unnecessary_lazy_evaluations,
check_unnecessary_map_on_constructor, check_unnecessary_min_or_max,
check_unnecessary_range_loop, check_unnecessary_raw_string_expression,
check_unnecessary_raw_string_pattern, check_unnecessary_return, check_unsigned_comparison,
check_verbose_failure_propagation, check_waitgroup_add_in_task, check_while_let_loop,
check_wildcard_in_or_patterns,
};

static LINT_CHECKS: LazyLock<CheckTable> = LazyLock::new(|| {
Expand Down Expand Up @@ -155,6 +157,8 @@ static LINT_CHECKS: LazyLock<CheckTable> = LazyLock::new(|| {
check_expression_naming,
&[Struct, Enum, TypeAlias, Interface, Function],
),
(check_enum_variant_names, &[Enum]),
(check_self_named_constructors, &[ImplBlock]),
(check_replaceable_with_zero_fill, &[StructCall]),
(check_redundant_field_names, &[StructCall]),
(check_needless_update, &[StructCall]),
Expand Down
Loading
Loading