diff --git a/Cargo.toml b/Cargo.toml index f6bc07e2..bf37e0d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,5 +42,36 @@ tempfile = { version = "3.27.0", optional = true } tokio = { version = "1.50.0", features = ["full"], optional = true } tower-lsp = { version = "0.20.0", optional = true } +[lints.clippy] +# ── Deny: these break CI immediately ── +collapsible_if = "deny" +collapsible_else_if = "deny" +needless_bool = "deny" +needless_return = "deny" +redundant_else = "deny" +manual_let_else = "deny" +unnested_or_patterns = "deny" +single_match_else = "deny" +fn_params_excessive_bools = "deny" +struct_excessive_bools = "deny" + +# ── Complexity ── +cognitive_complexity = "deny" +too_many_lines = "deny" +match_same_arms = "deny" + +# ── Code quality (pedantic) ── +redundant_closure = "deny" +needless_pass_by_value = "deny" +wildcard_imports = "deny" +manual_string_new = "deny" +uninlined_format_args = "deny" +needless_continue = "deny" +cast_lossless = "deny" +explicit_iter_loop = "deny" +unused_self = "deny" +unnecessary_wraps = "deny" +match_wildcard_for_single_variants = "deny" + [dev-dependencies] insta = "1.47.2" diff --git a/src/checker/expr.rs b/src/checker/expr.rs index 99b3286a..0f911b5e 100644 --- a/src/checker/expr.rs +++ b/src/checker/expr.rs @@ -133,8 +133,7 @@ impl Checker { let inner_ty = self.check_expr(inner); Type::Settable(Box::new(inner_ty)) } - ExprKind::Clear => Type::Settable(Box::new(Type::Unknown)), - ExprKind::Unchanged => Type::Settable(Box::new(Type::Unknown)), + ExprKind::Clear | ExprKind::Unchanged => Type::Settable(Box::new(Type::Unknown)), ExprKind::Todo => { self.emit_warning_with_help( "`todo` is a placeholder that will panic at runtime", @@ -168,13 +167,12 @@ impl Checker { } last_type }), - ExprKind::Grouped(inner) => self.check_expr(inner), + ExprKind::Grouped(inner) | ExprKind::Spread(inner) => self.check_expr(inner), ExprKind::Array(elements) => self.check_array(elements), ExprKind::Tuple(elements) => { let types: Vec = elements.iter().map(|el| self.check_expr(el)).collect(); Type::Tuple(types) } - ExprKind::Spread(inner) => self.check_expr(inner), ExprKind::Object(fields) => { let field_types: Vec<(String, Type)> = fields .iter() @@ -418,8 +416,7 @@ impl Checker { Type::Unknown } } - Type::Unknown | Type::Foreign(_) | Type::Never => Type::Unknown, - Type::Var(_) => Type::Unknown, + Type::Unknown | Type::Foreign(_) | Type::Never | Type::Var(_) => Type::Unknown, _ => { if let Type::Named(name) = &obj_ty && self.env.lookup_type(name).is_none() diff --git a/src/checker/items.rs b/src/checker/items.rs index 69fc1b7c..f0554ba1 100644 --- a/src/checker/items.rs +++ b/src/checker/items.rs @@ -256,8 +256,7 @@ impl Checker { fn tuple_element_type(final_type: &Type, i: usize) -> Type { match final_type { Type::Tuple(types) => types.get(i).cloned().unwrap_or(Type::Unknown), - Type::Unknown | Type::Var(_) => Type::Unknown, - _ => Type::Unknown, + Type::Unknown | Type::Var(_) | _ => Type::Unknown, } } diff --git a/src/checker/match_check.rs b/src/checker/match_check.rs index 8e6924d6..b783c640 100644 --- a/src/checker/match_check.rs +++ b/src/checker/match_check.rs @@ -58,7 +58,6 @@ impl Checker { // Resolve Named types to their actual definitions. let resolved_ty; let subject_ty = match subject_ty { - Type::Foreign(_) | Type::Promise(_) => subject_ty, Type::Named(type_name) => { if let Some(actual) = self.env.lookup(type_name) { resolved_ty = actual.clone(); @@ -67,7 +66,7 @@ impl Checker { subject_ty } } - _ => subject_ty, + Type::Foreign(_) | Type::Promise(_) | _ => subject_ty, }; let has_catch_all = arms.iter().any(|arm| { diff --git a/src/checker/traits.rs b/src/checker/traits.rs index 2daa9902..43db9c7a 100644 --- a/src/checker/traits.rs +++ b/src/checker/traits.rs @@ -42,18 +42,17 @@ impl Checker { functions: &[FunctionDecl], span: Span, ) { - let trait_methods = match self.traits.trait_defs.get(trait_name) { - Some(methods) => methods.clone(), - None => { - self.emit_error_with_help( - format!("unknown trait `{trait_name}`"), - span, - ErrorCode::UnknownTrait, - "not defined", - "check the spelling or define this trait", - ); - return; - } + let trait_methods = if let Some(methods) = self.traits.trait_defs.get(trait_name) { + methods.clone() + } else { + self.emit_error_with_help( + format!("unknown trait `{trait_name}`"), + span, + ErrorCode::UnknownTrait, + "not defined", + "check the spelling or define this trait", + ); + return; }; // Check that all required methods are implemented diff --git a/src/checker/type_compat.rs b/src/checker/type_compat.rs index eb4b7c26..a43a414f 100644 --- a/src/checker/type_compat.rs +++ b/src/checker/type_compat.rs @@ -40,8 +40,9 @@ impl Checker { _ => true, } } - (Type::Promise(a), Type::Promise(b)) => self.types_unifiable(a, b), - (Type::Array(a), Type::Array(b)) => self.types_unifiable(a, b), + (Type::Promise(a), Type::Promise(b)) | (Type::Array(a), Type::Array(b)) => { + self.types_unifiable(a, b) + } (Type::Tuple(a), Type::Tuple(b)) => { a.len() == b.len() && a.iter() @@ -209,8 +210,8 @@ impl Checker { | (Type::Unit, Type::Unit) | (Type::Undefined, Type::Undefined) => true, (Type::String, Type::StringLiteral(_)) => true, - (Type::StringLiteral(a), Type::StringLiteral(b)) => a == b, - (Type::Named(a), Type::Named(b)) => a == b, + (Type::StringLiteral(a), Type::StringLiteral(b)) + | (Type::Named(a), Type::Named(b)) => a == b, (Type::Named(a), Type::Union { name: b, .. }) | (Type::Union { name: a, .. }, Type::Named(b)) => a == b, (expected, actual) if expected.is_result() && actual.is_result() => { diff --git a/src/codegen.rs b/src/codegen.rs index 23910594..554a2cfb 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -646,10 +646,7 @@ fn collect_value_names_from_expr(expr: &Expr, names: &mut HashSet) { collect_value_names_from_expr(right, names); } ExprKind::Unary { operand, .. } => collect_value_names_from_expr(operand, names), - ExprKind::Unwrap(e) => { - collect_value_names_from_expr(e, names); - } - ExprKind::Value(e) => { + ExprKind::Unwrap(e) | ExprKind::Value(e) => { collect_value_names_from_expr(e, names); } ExprKind::Match { subject, arms } => { diff --git a/src/codegen/expr.rs b/src/codegen/expr.rs index f2f2607a..d54b8c88 100644 --- a/src/codegen/expr.rs +++ b/src/codegen/expr.rs @@ -331,7 +331,8 @@ impl Codegen { } // Unchanged → should only appear inside Construct args (filtered out) - ExprKind::Unchanged => { + // Unit → undefined + ExprKind::Unchanged | ExprKind::Unit => { self.push("undefined"); } @@ -345,10 +346,6 @@ impl Codegen { self.push(THROW_UNREACHABLE); } - ExprKind::Unit => { - self.push("undefined"); - } - ExprKind::Jsx(element) => { self.has_jsx = true; self.emit_jsx(element); @@ -410,8 +407,8 @@ impl Codegen { } ExprKind::DotShorthand { field, predicate } => { - match predicate { - Some((op, rhs)) => match op { + if let Some((op, rhs)) = predicate { + match op { BinOp::Eq => { self.needs_deep_equal = true; self.push(&format!("(_x) => {DEEP_EQUAL_FN}(_x.")); @@ -434,12 +431,11 @@ impl Codegen { self.push(&format!(" {} ", binop_str(*op))); self.emit_expr(rhs); } - }, - None => { - // `.field` → `(_x) => _x.field` - self.push("(_x) => _x."); - self.push(field); } + } else { + // `.field` → `(_x) => _x.field` + self.push("(_x) => _x."); + self.push(field); } } } @@ -576,62 +572,56 @@ impl Codegen { /// Like emit_block_expr but adds implicit return to the last expression. pub(super) fn emit_block_expr_with_return(&mut self, expr: &Expr) { - match &expr.kind { - ExprKind::Block(items) => { - self.push("{"); - self.newline(); - self.indent += 1; - for (i, item) in items.iter().enumerate() { - let is_last = i == items.len() - 1; - if is_last && matches!(item.kind, ItemKind::Expr(_)) { - self.emit_indent(); - self.push("return "); - if let ItemKind::Expr(e) = &item.kind { - self.emit_expr(e); - } - self.push(";"); - } else { - self.emit_item(item); + if let ExprKind::Block(items) = &expr.kind { + self.push("{"); + self.newline(); + self.indent += 1; + for (i, item) in items.iter().enumerate() { + let is_last = i == items.len() - 1; + if is_last && matches!(item.kind, ItemKind::Expr(_)) { + self.emit_indent(); + self.push("return "); + if let ItemKind::Expr(e) = &item.kind { + self.emit_expr(e); } - self.newline(); + self.push(";"); + } else { + self.emit_item(item); } - self.indent -= 1; - self.emit_indent(); - self.push("}"); - } - _ => { - self.push("{"); - self.newline(); - self.indent += 1; - self.emit_indent(); - self.push("return "); - self.emit_expr(expr); - self.push(";"); self.newline(); - self.indent -= 1; - self.emit_indent(); - self.push("}"); } + self.indent -= 1; + self.emit_indent(); + self.push("}"); + } else { + self.push("{"); + self.newline(); + self.indent += 1; + self.emit_indent(); + self.push("return "); + self.emit_expr(expr); + self.push(";"); + self.newline(); + self.indent -= 1; + self.emit_indent(); + self.push("}"); } } pub(super) fn emit_block_expr(&mut self, expr: &Expr) { - match &expr.kind { - ExprKind::Block(items) => { - self.emit_block_items(items); - } - _ => { - self.push("{"); - self.newline(); - self.indent += 1; - self.emit_indent(); - self.emit_expr(expr); - self.push(";"); - self.newline(); - self.indent -= 1; - self.emit_indent(); - self.push("}"); - } + if let ExprKind::Block(items) = &expr.kind { + self.emit_block_items(items); + } else { + self.push("{"); + self.newline(); + self.indent += 1; + self.emit_indent(); + self.emit_expr(expr); + self.push(";"); + self.newline(); + self.indent -= 1; + self.emit_indent(); + self.push("}"); } } diff --git a/src/codegen/match_emit.rs b/src/codegen/match_emit.rs index 1a6cef0d..5dbd58fd 100644 --- a/src/codegen/match_emit.rs +++ b/src/codegen/match_emit.rs @@ -410,10 +410,10 @@ fn collect_bindings_inner( bindings.push((name.clone(), rest_access)); } } - PatternKind::StringPattern { .. } => { - // String pattern bindings are handled directly in emit_match_body - } - PatternKind::Wildcard | PatternKind::Literal(_) | PatternKind::Range { .. } => {} + PatternKind::StringPattern { .. } + | PatternKind::Wildcard + | PatternKind::Literal(_) + | PatternKind::Range { .. } => {} } } diff --git a/src/cst.rs b/src/cst.rs index 5b84fe73..65148228 100644 --- a/src/cst.rs +++ b/src/cst.rs @@ -276,11 +276,9 @@ impl<'src> CstParser<'src> { // - EOF !matches!( self.current_kind(), - Some(TokenKind::LessThan) - | Some(TokenKind::LeftBrace) - | Some(TokenKind::RightBrace) - | Some(TokenKind::Eof) - | None + Some( + TokenKind::LessThan | TokenKind::LeftBrace | TokenKind::RightBrace | TokenKind::Eof + ) | None ) } diff --git a/src/cst/exprs.rs b/src/cst/exprs.rs index 9be5c3f8..eb27ecd8 100644 --- a/src/cst/exprs.rs +++ b/src/cst/exprs.rs @@ -119,7 +119,7 @@ impl<'src> CstParser<'src> { fn parse_unary_expr(&mut self) { match self.current_kind() { - Some(TokenKind::Bang) | Some(TokenKind::Minus) => { + Some(TokenKind::Bang | TokenKind::Minus) => { self.builder.start_node(SyntaxKind::UNARY_EXPR.into()); self.bump(); self.eat_trivia(); @@ -153,22 +153,24 @@ impl<'src> CstParser<'src> { if self.is_ident() || matches!( self.current_kind(), - Some(TokenKind::Number(_)) - | Some(TokenKind::Banned(_)) - | Some(TokenKind::Parse) - | Some(TokenKind::Match) - | Some(TokenKind::For) - | Some(TokenKind::From) - | Some(TokenKind::Type) - | Some(TokenKind::Export) - | Some(TokenKind::Import) - | Some(TokenKind::Const) - | Some(TokenKind::Fn) - | Some(TokenKind::Trait) - | Some(TokenKind::Collect) - | Some(TokenKind::Deriving) - | Some(TokenKind::When) - | Some(TokenKind::SelfKw) + Some( + TokenKind::Number(_) + | TokenKind::Banned(_) + | TokenKind::Parse + | TokenKind::Match + | TokenKind::For + | TokenKind::From + | TokenKind::Type + | TokenKind::Export + | TokenKind::Import + | TokenKind::Const + | TokenKind::Fn + | TokenKind::Trait + | TokenKind::Collect + | TokenKind::Deriving + | TokenKind::When + | TokenKind::SelfKw + ) ) { self.bump(); @@ -226,15 +228,17 @@ impl<'src> CstParser<'src> { fn parse_primary_expr(&mut self) { match self.current_kind() { - Some(TokenKind::Number(_)) => self.bump(), - Some(TokenKind::String(_)) => self.bump(), - Some(TokenKind::TemplateLiteral(_)) => self.bump(), - Some(TokenKind::Bool(_)) => self.bump(), - Some(TokenKind::Underscore) => self.bump(), - Some(TokenKind::Clear) => self.bump(), - Some(TokenKind::Unchanged) => self.bump(), - Some(TokenKind::Todo) => self.bump(), - Some(TokenKind::Unreachable) => self.bump(), + Some( + TokenKind::Number(_) + | TokenKind::String(_) + | TokenKind::TemplateLiteral(_) + | TokenKind::Bool(_) + | TokenKind::Underscore + | TokenKind::Clear + | TokenKind::Unchanged + | TokenKind::Todo + | TokenKind::Unreachable, + ) => self.bump(), Some(TokenKind::Value) => { self.builder.start_node(SyntaxKind::VALUE_EXPR.into()); @@ -504,10 +508,7 @@ impl<'src> CstParser<'src> { // Punning: `label:` without a value — next non-trivia is `)` or `,` let next = self.next_non_trivia_kind(); - let is_pun = matches!( - next, - Some(TokenKind::RightParen) | Some(TokenKind::Comma) | None - ); + let is_pun = matches!(next, Some(TokenKind::RightParen | TokenKind::Comma) | None); if !is_pun { self.eat_trivia(); self.parse_expr(); @@ -650,13 +651,7 @@ impl<'src> CstParser<'src> { self.builder.start_node(SyntaxKind::PATTERN.into()); match self.current_kind() { - Some(TokenKind::Underscore) => { - self.bump(); - } - Some(TokenKind::Bool(_)) => { - self.bump(); - } - Some(TokenKind::String(_)) => { + Some(TokenKind::Underscore | TokenKind::Bool(_) | TokenKind::String(_)) => { self.bump(); } Some(TokenKind::Minus) => { @@ -702,7 +697,7 @@ impl<'src> CstParser<'src> { // Expect identifier or _ after .. if matches!( self.current_kind(), - Some(TokenKind::Identifier(_)) | Some(TokenKind::Underscore) + Some(TokenKind::Identifier(_) | TokenKind::Underscore) ) { self.bump(); } else { diff --git a/src/cst/items.rs b/src/cst/items.rs index a895f8fd..eb8d1bfa 100644 --- a/src/cst/items.rs +++ b/src/cst/items.rs @@ -40,7 +40,7 @@ impl<'src> CstParser<'src> { self.parse_function_decl(); self.builder.finish_node(); } - Some(TokenKind::Opaque) | Some(TokenKind::Type) => { + Some(TokenKind::Opaque | TokenKind::Type) => { self.builder .start_node_at(checkpoint, SyntaxKind::ITEM.into()); self.parse_type_decl(); diff --git a/src/formatter.rs b/src/formatter.rs index d22ea752..b064d0d0 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -96,8 +96,9 @@ impl<'src> Formatter<'src> { SyntaxKind::TYPE_DEF_RECORD => self.fmt_record_def(node), SyntaxKind::TYPE_DEF_ALIAS => self.fmt_type_alias_def(node), SyntaxKind::TYPE_EXPR => self.fmt_type_expr(node), - SyntaxKind::COLLECT_EXPR => self.fmt_verbatim(node), - SyntaxKind::TEST_BLOCK | SyntaxKind::ASSERT_EXPR => self.fmt_verbatim(node), + SyntaxKind::COLLECT_EXPR | SyntaxKind::TEST_BLOCK | SyntaxKind::ASSERT_EXPR => { + self.fmt_verbatim(node) + } _ => self.fmt_verbatim(node), } } diff --git a/src/interop/tsgo/probe_gen.rs b/src/interop/tsgo/probe_gen.rs index 47496c2b..f8fcb359 100644 --- a/src/interop/tsgo/probe_gen.rs +++ b/src/interop/tsgo/probe_gen.rs @@ -909,11 +909,7 @@ fn collect_member_accesses_expr( } } } - ExprKind::Binary { left, right, .. } => { - collect_member_accesses_expr(left, imported_names, accesses); - collect_member_accesses_expr(right, imported_names, accesses); - } - ExprKind::Pipe { left, right } => { + ExprKind::Binary { left, right, .. } | ExprKind::Pipe { left, right } => { collect_member_accesses_expr(left, imported_names, accesses); collect_member_accesses_expr(right, imported_names, accesses); } @@ -954,7 +950,7 @@ fn collect_member_accesses_expr( collect_member_accesses_expr(value, imported_names, accesses); } } - ExprKind::Array(elems) => { + ExprKind::Array(elems) | ExprKind::Tuple(elems) => { for e in elems { collect_member_accesses_expr(e, imported_names, accesses); } diff --git a/src/lower/pattern.rs b/src/lower/pattern.rs index 61f883fc..cf8c38ed 100644 --- a/src/lower/pattern.rs +++ b/src/lower/pattern.rs @@ -195,12 +195,11 @@ impl<'src> Lowerer<'src> { }, span, }); - } else { - return Some(Pattern { - kind: PatternKind::Binding(name), - span, - }); } + return Some(Pattern { + kind: PatternKind::Binding(name), + span, + }); } SyntaxKind::L_BRACE => { // Record pattern