From 74b500f430ebe1226065af1a12ef857ba09d38ef Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Thu, 13 Aug 2026 06:49:12 -0600 Subject: [PATCH 1/3] fix(rust): resolve cargo clippy warnings in codegraph-core (#2096 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clears all 102 cargo clippy warnings (21 lint categories) reported against crates/codegraph-core, per issue #2326. Mechanical, behavior-preserving simplifications (17 categories, ~84 sites): unnecessary_map_or, double_ended_iterator_last, manual_contains, collapsible_match, collapsible_if, doc_lazy_continuation, manual_pattern_char_comparison, explicit_counter_loop, needless_lifetimes, needless_borrow, redundant_closure, redundant_pattern_matching, manual_flatten, needless_range_loop, nonminimal_bool, unnecessary_unwrap, manual_is_multiple_of. Each `.split(...).last()` -> `.next_back()` site was individually checked to confirm no other `.last()` (e.g. slice `.last()`) call was accidentally swept up by the same substitution. Judgment-required categories (4 categories, 18 sites), evaluated individually: - ptr_arg (2): narrowed `&mut Vec` to `&mut [ScopeFrame]` in ast_analysis/dataflow.rs, matching the existing slice convention already used by sibling functions (e.g. find_binding, handle_return_stmt) in the same file. - new_without_default (1): added `impl Default for ParseTreeCache` that delegates to the existing `#[napi(constructor)] fn new()`, without touching the constructor itself. - type_complexity (4): introduced named type aliases (LeafRow/CallableRow, ReturnTypeIndex/GlobalReturnTypes, NodeEdge, a test-only TestReexportEntry) for the flagged signatures, applied consistently to sibling functions with the same shape where present. - too_many_arguments (11): all 11 sites are internal (non-pub) hot-path functions in the call/edge-resolution and dataflow subsystems, which must stay behaviorally identical to the TS/WASM engine per CLAUDE.md's dual-engine mandate. A params-struct refactor there is exactly the kind of hasty structural change issue #2326 itself warns against, so each site got a targeted `#[allow(clippy::too_many_arguments)]` with a one-line justification instead. Deferred refactor tracked in optave/ops-codegraph-tool#2481. Verified: cargo test --workspace (1011 passed), cargo fmt --check, npm test (5231 passed), npm run lint, and the dual-engine parity suite (tests/engines/ + tests/integration/build-parity.test.ts, 122 passed) all clean. `node scripts/parity-compare.mjs --hybrid` and `--dataflow` surfaced two divergences (jelly-micro bind.js/fun.js self-referential dyn=1 edges; native producing far fewer dataflow vertices than WASM for several JS/TS/ pts-javascript fixtures) — both reproduce identically on an unmodified origin/main checkout, confirmed via an isolated worktree, so they predate this change and are filed separately as optave/ops-codegraph-tool#2482 and optave/ops-codegraph-tool#2483. docs check acknowledged: internal code-quality cleanup + lint-category triage with no user-facing behavior change or new documented feature: no README/CLAUDE.md/ROADMAP.md update applies. --- .../src/ast_analysis/complexity.rs | 33 +++++--------- .../src/ast_analysis/dataflow.rs | 18 ++++---- crates/codegraph-core/src/db/connection.rs | 33 +++++++------- .../src/db/repository/graph_read.rs | 2 +- .../domain/graph/builder/barrel_resolution.rs | 7 ++- .../src/domain/graph/builder/incremental.rs | 6 +++ .../src/domain/graph/builder/pipeline.rs | 25 +++++++---- .../graph/builder/stages/build_edges.rs | 38 +++++++++++----- .../graph/builder/stages/collect_files.rs | 2 +- .../graph/builder/stages/import_edges.rs | 11 ++++- .../graph/builder/stages/insert_nodes.rs | 13 +++--- .../src/domain/graph/journal.rs | 6 +-- crates/codegraph-core/src/extractors/bash.rs | 7 ++- crates/codegraph-core/src/extractors/c.rs | 2 +- crates/codegraph-core/src/extractors/cpp.rs | 2 +- .../codegraph-core/src/extractors/csharp.rs | 2 +- crates/codegraph-core/src/extractors/cuda.rs | 2 +- crates/codegraph-core/src/extractors/dart.rs | 6 +-- .../codegraph-core/src/extractors/erlang.rs | 8 ++-- .../codegraph-core/src/extractors/fsharp.rs | 2 +- crates/codegraph-core/src/extractors/go.rs | 2 +- .../codegraph-core/src/extractors/groovy.rs | 2 +- .../codegraph-core/src/extractors/haskell.rs | 2 +- .../codegraph-core/src/extractors/helpers.rs | 29 ++++++------ crates/codegraph-core/src/extractors/java.rs | 2 +- .../src/extractors/javascript.rs | 44 ++++++++++--------- crates/codegraph-core/src/extractors/julia.rs | 4 +- .../codegraph-core/src/extractors/kotlin.rs | 2 +- crates/codegraph-core/src/extractors/ocaml.rs | 2 +- crates/codegraph-core/src/extractors/php.rs | 8 ++-- crates/codegraph-core/src/extractors/ruby.rs | 2 +- .../src/extractors/rust_lang.rs | 10 ++--- crates/codegraph-core/src/extractors/scala.rs | 8 ++-- .../codegraph-core/src/extractors/solidity.rs | 4 +- crates/codegraph-core/src/extractors/swift.rs | 2 +- .../codegraph-core/src/extractors/verilog.rs | 2 +- .../codegraph-core/src/features/structure.rs | 8 ++-- .../src/graph/classifiers/roles.rs | 29 ++++++------ 38 files changed, 211 insertions(+), 176 deletions(-) diff --git a/crates/codegraph-core/src/ast_analysis/complexity.rs b/crates/codegraph-core/src/ast_analysis/complexity.rs index 29e8ddb5e..d4287c7f9 100644 --- a/crates/codegraph-core/src/ast_analysis/complexity.rs +++ b/crates/codegraph-core/src/ast_analysis/complexity.rs @@ -1150,9 +1150,9 @@ fn classify_branch(node: &Node, kind: &str, rules: &LangRules, nesting_level: u3 // Pattern A: else clause wraps if (JS/C#/Rust) if let Some(else_type) = rules.else_node_type { if kind == else_type { - let is_else_if = node.named_child(0).map_or(false, |c| { - rules.if_node_type.map_or(false, |if_t| c.kind() == if_t) - }); + let is_else_if = node + .named_child(0) + .is_some_and(|c| rules.if_node_type.is_some_and(|if_t| c.kind() == if_t)); if is_else_if { // else-if: the if_statement child handles its own increment return BranchAction::Handled { @@ -1255,16 +1255,13 @@ fn is_pattern_d_else_if(node: &Node, rules: &LangRules) -> bool { /// Detect whether an if-node is actually an else-if (Pattern A, C, or D). fn detect_else_if(node: &Node, kind: &str, rules: &LangRules) -> bool { - if !rules.if_node_type.map_or(false, |if_t| kind == if_t) { + if rules.if_node_type != Some(kind) { return false; } if rules.else_via_alternative { // Pattern C (Go/Java): if_statement is the alternative of parent if_statement if let Some(parent) = node.parent() { - if rules - .if_node_type - .map_or(false, |if_t| parent.kind() == if_t) - { + if rules.if_node_type == Some(parent.kind()) { if let Some(alt) = parent.child_by_field_name("alternative") { if alt.id() == node.id() { return true; @@ -1275,10 +1272,7 @@ fn detect_else_if(node: &Node, kind: &str, rules: &LangRules) -> bool { } else if rules.else_node_type.is_some() { // Pattern A (JS/C#/Rust): if_statement inside else_clause if let Some(parent) = node.parent() { - if rules - .else_node_type - .map_or(false, |else_t| parent.kind() == else_t) - { + if rules.else_node_type == Some(parent.kind()) { return true; } } @@ -1294,14 +1288,11 @@ fn is_pattern_c_else(node: &Node, kind: &str, rules: &LangRules) -> bool { if !rules.else_via_alternative { return false; } - if rules.if_node_type.map_or(false, |if_t| kind == if_t) { + if rules.if_node_type == Some(kind) { return false; // This is an if, not a plain else block } if let Some(parent) = node.parent() { - if rules - .if_node_type - .map_or(false, |if_t| parent.kind() == if_t) - { + if rules.if_node_type == Some(parent.kind()) { if let Some(alt) = parent.child_by_field_name("alternative") { return alt.id() == node.id(); } @@ -1315,7 +1306,7 @@ fn is_pattern_c_else(node: &Node, kind: &str, rules: &LangRules) -> bool { /// keyword — the Pattern-D counterpart to `is_pattern_c_else`, for grammars /// with no `else_clause` node and no `alternative` field (Solidity). fn is_pattern_d_else(node: &Node, kind: &str, rules: &LangRules) -> bool { - if rules.if_node_type.map_or(false, |if_t| kind == if_t) { + if rules.if_node_type == Some(kind) { return false; // if_statement is handled by detect_else_if instead } is_pattern_d_else_if(node, rules) @@ -1361,9 +1352,9 @@ fn handle_logical_op( // sequence. `effective_parent` walks through transparent wrapper nodes // (e.g. Solidity's `expression`) that would otherwise hide a // same-operator chain's real parent binary_expression (issue #2312). - let same_sequence = effective_parent(node, rules).map_or(false, |parent| { + let same_sequence = effective_parent(node, rules).is_some_and(|parent| { rules.logical_node_types.contains(&parent.kind()) - && parent.child(1).map_or(false, |pop| { + && parent.child(1).is_some_and(|pop| { operator_key(&pop, source, rules.logical_operators_by_text) == op }) }); @@ -2927,7 +2918,7 @@ fn walk_all( let kind = node.kind(); // ── Halstead classification ── - let skip_h = halstead_skip || h_rules.map_or(false, |hr| hr.skip_types.contains(&kind)); + let skip_h = halstead_skip || h_rules.is_some_and(|hr| hr.skip_types.contains(&kind)); if let Some(hr) = h_rules { if !skip_h { diff --git a/crates/codegraph-core/src/ast_analysis/dataflow.rs b/crates/codegraph-core/src/ast_analysis/dataflow.rs index e85b6f2e8..626f6fb0f 100644 --- a/crates/codegraph-core/src/ast_analysis/dataflow.rs +++ b/crates/codegraph-core/src/ast_analysis/dataflow.rs @@ -828,14 +828,12 @@ fn extract_param_names(node: &Node, rules: &DataflowRules, source: &[u8]) -> Vec /// Extract parameters: name + index pairs from formal_parameters node. fn extract_params(params_node: &Node, rules: &DataflowRules, source: &[u8]) -> Vec<(String, u32)> { let mut result = Vec::new(); - let mut index: u32 = 0; let cursor = &mut params_node.walk(); - for child in params_node.named_children(cursor) { + for (index, child) in params_node.named_children(cursor).enumerate() { let names = extract_param_names(&child, rules, source); for name in names { - result.push((name, index)); + result.push((name, index as u32)); } - index += 1; } result } @@ -1197,6 +1195,9 @@ fn resolve_var_declarator_nodes<'a>( } /// Emit assignments for a destructuring pattern (object or array). +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical dataflow path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_destructuring_assignments( name_n: &Node, node: &Node, @@ -1224,7 +1225,7 @@ fn handle_var_declarator( node: &Node, rules: &DataflowRules, source: &[u8], - scope_stack: &mut Vec, + scope_stack: &mut [ScopeFrame], assignments: &mut Vec, ) { let (name_node, value_node) = resolve_var_declarator_nodes(node, rules); @@ -1292,7 +1293,7 @@ fn handle_assignment( node: &Node, rules: &DataflowRules, source: &[u8], - scope_stack: &mut Vec, + scope_stack: &mut [ScopeFrame], assignments: &mut Vec, mutations: &mut Vec, ) { @@ -1371,9 +1372,9 @@ fn handle_call_expr( None => return, }; - let mut arg_index: u32 = 0; let cursor = &mut args_node.walk(); - for arg_raw in args_node.named_children(cursor) { + for (arg_index, arg_raw) in args_node.named_children(cursor).enumerate() { + let arg_index = arg_index as u32; // PHP/Java: unwrap argument wrapper let arg = if rules .argument_wrapper_type @@ -1419,7 +1420,6 @@ fn handle_call_expr( }); } } - arg_index += 1; } } diff --git a/crates/codegraph-core/src/db/connection.rs b/crates/codegraph-core/src/db/connection.rs index 76d3fe5ea..18c20c0e0 100644 --- a/crates/codegraph-core/src/db/connection.rs +++ b/crates/codegraph-core/src/db/connection.rs @@ -1409,14 +1409,17 @@ impl NativeDatabase { let mut block_db_ids: std::collections::HashMap = std::collections::HashMap::new(); for block in &entry.blocks { - if let Ok(_) = block_stmt.execute(params![ - entry.node_id, - block.index, - &block.block_type, - block.start_line, - block.end_line, - &block.label, - ]) { + if block_stmt + .execute(params![ + entry.node_id, + block.index, + &block.block_type, + block.start_line, + block.end_line, + &block.label, + ]) + .is_ok() + { block_db_ids.insert(block.index, tx.last_insert_rowid()); total += 1; } @@ -1633,11 +1636,9 @@ impl NativeDatabase { let rows = stmt .query_map(params![file], |row| row.get::<_, String>(0)) .map_err(|e| napi::Error::from_reason(format!("reverseDeps query failed: {e}")))?; - for row in rows { - if let Ok(dep_file) = row { - if !changed_set.contains(dep_file.as_str()) { - result_set.insert(dep_file); - } + for dep_file in rows.flatten() { + if !changed_set.contains(dep_file.as_str()) { + result_set.insert(dep_file); } } } @@ -1748,7 +1749,7 @@ impl NativeDatabase { purge_hashes: Option, reverse_dep_files: Option>, ) -> napi::Result<()> { - if files.is_empty() && reverse_dep_files.as_ref().map_or(true, |v| v.is_empty()) { + if files.is_empty() && reverse_dep_files.as_ref().is_none_or(|v| v.is_empty()) { return Ok(()); } let conn = self.conn()?; @@ -1955,8 +1956,8 @@ fn row_to_json( col_names: &[String], ) -> serde_json::Value { let mut map = serde_json::Map::with_capacity(col_count); - for i in 0..col_count { - map.insert(col_names[i].clone(), value_ref_to_json(row.get_ref(i))); + for (i, name) in col_names.iter().enumerate().take(col_count) { + map.insert(name.clone(), value_ref_to_json(row.get_ref(i))); } serde_json::Value::Object(map) } diff --git a/crates/codegraph-core/src/db/repository/graph_read.rs b/crates/codegraph-core/src/db/repository/graph_read.rs index a812597a0..0f2e0ead5 100644 --- a/crates/codegraph-core/src/db/repository/graph_read.rs +++ b/crates/codegraph-core/src/db/repository/graph_read.rs @@ -305,7 +305,7 @@ fn expand_method_hierarchy_callers( if node.kind != "method" || !node.name.contains('.') { return Ok(()); } - let method_name = match node.name.split('.').last() { + let method_name = match node.name.split('.').next_back() { Some(n) => n, None => return Ok(()), }; diff --git a/crates/codegraph-core/src/domain/graph/builder/barrel_resolution.rs b/crates/codegraph-core/src/domain/graph/builder/barrel_resolution.rs index eb68b68c6..593ec0a3b 100644 --- a/crates/codegraph-core/src/domain/graph/builder/barrel_resolution.rs +++ b/crates/codegraph-core/src/domain/graph/builder/barrel_resolution.rs @@ -128,8 +128,11 @@ mod tests { use super::*; use std::collections::HashMap; + /// Owned mirror of `ReexportRef`: `(source, names, wildcard_reexport, renames)`. + type TestReexportEntry = (String, Vec, bool, Vec); + struct TestContext { - reexports: HashMap, bool, Vec)>>, + reexports: HashMap>, definitions: HashMap>, } @@ -151,7 +154,7 @@ mod tests { fn has_definition(&self, file_path: &str, symbol: &str) -> bool { self.definitions .get(file_path) - .map_or(false, |defs| defs.contains(symbol)) + .is_some_and(|defs| defs.contains(symbol)) } } diff --git a/crates/codegraph-core/src/domain/graph/builder/incremental.rs b/crates/codegraph-core/src/domain/graph/builder/incremental.rs index ef2dad3d2..184445497 100644 --- a/crates/codegraph-core/src/domain/graph/builder/incremental.rs +++ b/crates/codegraph-core/src/domain/graph/builder/incremental.rs @@ -26,6 +26,12 @@ pub struct ParseTreeCache { entries: SendWrapper>, } +impl Default for ParseTreeCache { + fn default() -> Self { + Self::new() + } +} + #[napi] impl ParseTreeCache { #[napi(constructor)] diff --git a/crates/codegraph-core/src/domain/graph/builder/pipeline.rs b/crates/codegraph-core/src/domain/graph/builder/pipeline.rs index 7636f6b1b..ba9c732eb 100644 --- a/crates/codegraph-core/src/domain/graph/builder/pipeline.rs +++ b/crates/codegraph-core/src/domain/graph/builder/pipeline.rs @@ -12,7 +12,7 @@ //! 5. Insert nodes (existing `insert_nodes::do_insert_nodes`) — file_hashes //! for changed files is NOT written here; see step 7 //! 6. Resolve imports (existing `resolve::resolve_imports_batch`) -//! 6b. Re-parse barrel candidates (incremental only) +//! 6b. Re-parse barrel candidates (incremental only) //! 7. Build import edges + call edges + barrel resolution, then commit //! file_hashes for changed files (`insert_nodes::commit_file_hashes`) now //! that their edges match this revision (#1731) @@ -46,6 +46,12 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::Path; use std::time::Instant; +/// Per-file return-type index: `rel_path → (fn_name → (type_name, confidence))`. +type ReturnTypeIndex = HashMap>; + +/// Flat map for qualified `Type.method` lookups: `qualified_name → (type_name, confidence)`. +type GlobalReturnTypes = HashMap; + /// Timing result for each pipeline phase (returned as JSON to JS). #[derive(Debug, Clone, Serialize, Default)] #[serde(rename_all = "camelCase")] @@ -403,6 +409,9 @@ fn reconnect_saved_reverse_dep_edges( /// cross-directory import neighbors of `removed_files`, captured before they /// were purged — lets that refresh also reach a directory whose only link to /// the touched set was an edge to/from one of those removed files (#1839). +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical build-pipeline phase (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn run_structure_phase( conn: &Connection, file_symbols: &BTreeMap, @@ -1366,6 +1375,7 @@ fn build_file_hash_entries( /// Scoping is gated on: /// - small incremental change set (`file_symbols.len() <= SMALL_FILES`) /// - large-enough existing codebase (`file-node count > MIN_EXISTING`) +/// /// Both gates mirror the JS path in `build-edges.ts` (#976) to avoid /// exercising the scoped path on tiny fixtures where the scoped set can /// miss transitively-required nodes (e.g. a call site whose receiver type @@ -1721,11 +1731,8 @@ fn propagate_return_types_across_files( fn build_return_type_index( conn: &Connection, file_symbols: &BTreeMap, -) -> ( - HashMap>, - HashMap, -) { - let mut return_type_index: HashMap> = HashMap::new(); +) -> (ReturnTypeIndex, GlobalReturnTypes) { + let mut return_type_index: ReturnTypeIndex = HashMap::new(); for (rel_path, symbols) in file_symbols.iter() { if symbols.return_type_map.is_empty() { continue; @@ -1761,7 +1768,7 @@ fn build_return_type_index( } } - let mut global_return_types: HashMap = HashMap::new(); + let mut global_return_types: GlobalReturnTypes = HashMap::new(); let mut sorted_paths: Vec<&String> = return_type_index.keys().collect(); sorted_paths.sort(); for rel_path in sorted_paths { @@ -1864,8 +1871,8 @@ fn inject_return_types_for_file( rel_path: &str, symbols: &mut FileSymbols, import_ctx: &ImportEdgeContext, - return_type_index: &HashMap>, - global_return_types: &HashMap, + return_type_index: &ReturnTypeIndex, + global_return_types: &GlobalReturnTypes, hop_penalty: f64, ) { let abs_file = Path::new(&import_ctx.root_dir).join(rel_path); diff --git a/crates/codegraph-core/src/domain/graph/builder/stages/build_edges.rs b/crates/codegraph-core/src/domain/graph/builder/stages/build_edges.rs index b6a647c49..bea574c09 100644 --- a/crates/codegraph-core/src/domain/graph/builder/stages/build_edges.rs +++ b/crates/codegraph-core/src/domain/graph/builder/stages/build_edges.rs @@ -385,7 +385,7 @@ fn add_to_file_scoped<'a>( /// branch of `collectInstantiatedTypes` in `cha.ts` (constructor-confidence /// 1.0 and type-annotation-confidence 0.9 entries both qualify; native has no /// dedicated `newExpressions` list, so this is the only RTA evidence source). -fn collect_cha_instantiated_types<'a>(files: &'a [FileEdgeInput]) -> HashSet<&'a str> { +fn collect_cha_instantiated_types(files: &[FileEdgeInput]) -> HashSet<&str> { let mut instantiated = HashSet::new(); for file in files { for tm in &file.type_map { @@ -1104,7 +1104,7 @@ struct FileContext<'a> { /// Build the per-file type map from the input's type_map entries. /// Keeps the highest-confidence entry per name (first-wins on tie), matching /// the JS `setTypeMapEntry` behaviour. -fn build_type_map<'a>(file_input: &'a FileEdgeInput) -> HashMap<&'a str, (&'a str, f64)> { +fn build_type_map(file_input: &FileEdgeInput) -> HashMap<&str, (&str, f64)> { let mut type_map: HashMap<&str, (&str, f64)> = HashMap::new(); for tm in &file_input.type_map { let entry = type_map.entry(tm.name.as_str()); @@ -1281,6 +1281,9 @@ fn build_file_context<'a>( /// (c) module-level alias bindings (`const f = handler`, `f = fn.bind(ctx)`) /// — flat key, gated on fnRefBindingLhs so self-seeded local definitions never fire. /// Confidence is penalised by one hop to reflect the indirection. +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical edge-emission path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_no_receiver_pts_edges<'a>( ctx: &EdgeContext<'a>, fc: &FileContext<'a>, @@ -1348,6 +1351,9 @@ fn emit_no_receiver_pts_edges<'a>( /// /// Phase 8.3f: `rest.prop()` resolves when pts["rest.prop"] was seeded by the /// rest-dispatch chain. Builtin receivers are already skipped at the call-loop top. +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical edge-emission path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_receiver_pts_edges<'a>( ctx: &EdgeContext<'a>, fc: &FileContext<'a>, @@ -1690,13 +1696,11 @@ fn find_enclosing_caller<'a>( fn_caller_span = span; } } - } else if is_top_level_binding_kind(def.kind) { - if (span as i64) > var_caller_span { - if let Some(id) = def.node_id { - var_caller_id = Some(id); - var_caller_name = def.name; - var_caller_span = span as i64; - } + } else if is_top_level_binding_kind(def.kind) && (span as i64) > var_caller_span { + if let Some(id) = def.node_id { + var_caller_id = Some(id); + var_caller_name = def.name; + var_caller_span = span as i64; } } } @@ -1889,6 +1893,9 @@ fn prefer_type_aware_over_bare<'a>( /// `resolve_call_targets_core`'s ~15 early returns to a tuple) keeps the /// blast radius of this change small in a function shared by all 34 /// supported languages. +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical call-resolution path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn resolve_call_targets<'a>( ctx: &EdgeContext<'a>, call: &CallInfo, @@ -1952,6 +1959,9 @@ fn caller_has_real_class_ancestor(ctx: &EdgeContext, caller_name: &str, rel_path /// Core multi-strategy call target resolution — see `resolve_call_targets` for /// the public entry point (which additionally applies constructor attribution). +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical call-resolution path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn resolve_call_targets_core<'a>( ctx: &EdgeContext<'a>, call: &CallInfo, @@ -2492,7 +2502,7 @@ fn resolve_call_targets_core<'a>( // traversal starting from that entry point keeps working) — this hint only supplies the // class needed to resolve `this`/`self` here. let is_bare_call = call.receiver.is_none(); - if !caller_name.is_empty() && !(is_bare_call && is_module_scoped_language(rel_path)) { + if !(caller_name.is_empty() || is_bare_call && is_module_scoped_language(rel_path)) { let class_prefix = if let Some(dot_idx) = caller_name.rfind('.') { // Extract only the segment immediately before the method name so that // 'Namespace.ClassName.method' yields 'ClassName', not 'Namespace.ClassName'. @@ -2698,6 +2708,9 @@ fn sort_targets_by_confidence( /// `confidence_override` is set (#1949 CHA typed-dispatch fallback), every /// target uses that flat confidence instead of `resolve::compute_confidence` /// — file proximity is not meaningful for virtual dispatch confidence. +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical edge-emission path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_call_edges( targets: &[&NodeInfo], caller_id: u32, @@ -2744,6 +2757,9 @@ fn emit_call_edges( } /// Emit a receiver edge from caller to the receiver's type node (if applicable). +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical edge-emission path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_receiver_edge( ctx: &EdgeContext, call: &CallInfo, @@ -3156,7 +3172,7 @@ impl<'a> BarrelContext for ImportEdgeContext<'a> { fn has_definition(&self, file_path: &str, symbol: &str) -> bool { self.file_defs .get(file_path) - .map_or(false, |defs| defs.contains(symbol)) + .is_some_and(|defs| defs.contains(symbol)) } } diff --git a/crates/codegraph-core/src/domain/graph/builder/stages/collect_files.rs b/crates/codegraph-core/src/domain/graph/builder/stages/collect_files.rs index d8a022a10..c2c87229b 100644 --- a/crates/codegraph-core/src/domain/graph/builder/stages/collect_files.rs +++ b/crates/codegraph-core/src/domain/graph/builder/stages/collect_files.rs @@ -272,7 +272,7 @@ pub fn collect_files( .filter_entry(move |entry| { let name = entry.file_name().to_str().unwrap_or(""); // Skip ignored directory names - if entry.file_type().map_or(false, |ft| ft.is_dir()) { + if entry.file_type().is_some_and(|ft| ft.is_dir()) { if ignore_set.contains(name) { return false; } diff --git a/crates/codegraph-core/src/domain/graph/builder/stages/import_edges.rs b/crates/codegraph-core/src/domain/graph/builder/stages/import_edges.rs index 2bf4895fc..ae7eaca0b 100644 --- a/crates/codegraph-core/src/domain/graph/builder/stages/import_edges.rs +++ b/crates/codegraph-core/src/domain/graph/builder/stages/import_edges.rs @@ -120,7 +120,7 @@ impl BarrelContext for ImportEdgeContext { fn has_definition(&self, file_path: &str, symbol: &str) -> bool { self.file_symbols .get(file_path) - .map_or(false, |s| s.definitions.iter().any(|d| d.name == symbol)) + .is_some_and(|s| s.definitions.iter().any(|d| d.name == symbol)) } } @@ -643,6 +643,9 @@ fn push_edge_once( }); } +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical import-edge-emission path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_named_symbol_rows( edges: &mut Vec, file_node_id: i64, @@ -681,6 +684,9 @@ fn emit_named_symbol_rows( /// For a non-reexport import targeting a barrel file, emit `imports`-like /// edges to each ultimate definition file reached through the barrel chain. +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical import-edge-emission path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_barrel_through_rows( edges: &mut Vec, file_node_id: i64, @@ -725,6 +731,9 @@ fn emit_barrel_through_rows( } /// Emit all edges produced by a single import on a single source file. +// A params-struct refactor is deferred to avoid a hasty change to this +// parity-critical import-edge-emission path (dual-engine mandate) — tracked in #2481. +#[allow(clippy::too_many_arguments)] fn emit_edges_for_import( edges: &mut Vec, file_node_id: i64, diff --git a/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs b/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs index 959607e26..0cdcb5ae9 100644 --- a/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs +++ b/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs @@ -10,6 +10,9 @@ use napi_derive::napi; use rusqlite::{params, Connection}; use serde::{Deserialize, Serialize}; +/// `(source_node_id, target_node_id)` pair for a `contains`/`parameter_of` edge. +type NodeEdge = (i64, i64); + // ── Input types (received from JS via napi) ───────────────────────── /// Child node of a definition (parameter, nested function, etc.). @@ -233,9 +236,9 @@ fn insert_file_nodes( fn insert_symbol_nodes( tx: &rusqlite::Transaction, batches: &[InsertNodesBatch], -) -> rusqlite::Result<(Vec<(i64, i64)>, Vec<(i64, i64)>)> { - let mut contains_edges: Vec<(i64, i64)> = Vec::new(); - let mut param_of_edges: Vec<(i64, i64)> = Vec::new(); +) -> rusqlite::Result<(Vec, Vec)> { + let mut contains_edges: Vec = Vec::new(); + let mut param_of_edges: Vec = Vec::new(); // Phase 2: query existing node IDs, insert children, collect file→def edges { @@ -325,8 +328,8 @@ fn insert_symbol_nodes( /// [`insert_symbol_nodes`]. Single prepared statement, single pass. fn upsert_node_batch( tx: &rusqlite::Transaction, - contains_edges: &[(i64, i64)], - param_of_edges: &[(i64, i64)], + contains_edges: &[NodeEdge], + param_of_edges: &[NodeEdge], ) -> rusqlite::Result<()> { let mut stmt = tx.prepare_cached( "INSERT OR IGNORE INTO edges (source_id, target_id, kind, confidence, dynamic) \ diff --git a/crates/codegraph-core/src/domain/graph/journal.rs b/crates/codegraph-core/src/domain/graph/journal.rs index 9c814d93f..0dd25dd23 100644 --- a/crates/codegraph-core/src/domain/graph/journal.rs +++ b/crates/codegraph-core/src/domain/graph/journal.rs @@ -85,10 +85,8 @@ pub fn write_journal_header(root_dir: &str, timestamp: f64) { } let content = format!("{HEADER_PREFIX}{timestamp}\n"); - if fs::write(&tmp, &content).is_ok() { - if fs::rename(&tmp, &path).is_err() { - let _ = fs::remove_file(&tmp); - } + if fs::write(&tmp, &content).is_ok() && fs::rename(&tmp, &path).is_err() { + let _ = fs::remove_file(&tmp); } } diff --git a/crates/codegraph-core/src/extractors/bash.rs b/crates/codegraph-core/src/extractors/bash.rs index f96e8d63e..4ccef6893 100644 --- a/crates/codegraph-core/src/extractors/bash.rs +++ b/crates/codegraph-core/src/extractors/bash.rs @@ -59,8 +59,11 @@ fn match_bash_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth .trim_matches(|c| c == '"' || c == '\'') .to_string(); if !path.is_empty() { - let last = - path.split('/').last().unwrap_or(&path).to_string(); + let last = path + .split('/') + .next_back() + .unwrap_or(&path) + .to_string(); let mut imp = Import::new(path, vec![last], start_line(node)); imp.bash_source = Some(true); diff --git a/crates/codegraph-core/src/extractors/c.rs b/crates/codegraph-core/src/extractors/c.rs index ce47ea9d7..b0cca075a 100644 --- a/crates/codegraph-core/src/extractors/c.rs +++ b/crates/codegraph-core/src/extractors/c.rs @@ -305,7 +305,7 @@ fn match_c_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth: u let raw = node_text(&path_node, source); let path = raw.trim_matches(|c| c == '"' || c == '<' || c == '>'); if !path.is_empty() { - let last = path.split('/').last().unwrap_or(path); + let last = path.split('/').next_back().unwrap_or(path); let name = last.strip_suffix(".h").unwrap_or(last); let mut imp = Import::new(path.to_string(), vec![name.to_string()], start_line(node)); diff --git a/crates/codegraph-core/src/extractors/cpp.rs b/crates/codegraph-core/src/extractors/cpp.rs index b31cc2c9a..072ac1e56 100644 --- a/crates/codegraph-core/src/extractors/cpp.rs +++ b/crates/codegraph-core/src/extractors/cpp.rs @@ -352,7 +352,7 @@ fn handle_cpp_preproc_include(node: &Node, source: &[u8], symbols: &mut FileSymb let raw = node_text(&path_node, source); let path = raw.trim_matches(|c| c == '"' || c == '<' || c == '>'); if !path.is_empty() { - let last = path.split('/').last().unwrap_or(path); + let last = path.split('/').next_back().unwrap_or(path); let name = last .strip_suffix(".h") .or_else(|| last.strip_suffix(".hpp")) diff --git a/crates/codegraph-core/src/extractors/csharp.rs b/crates/codegraph-core/src/extractors/csharp.rs index 1f0e6c304..faed24e35 100644 --- a/crates/codegraph-core/src/extractors/csharp.rs +++ b/crates/codegraph-core/src/extractors/csharp.rs @@ -267,7 +267,7 @@ fn handle_using_directive(node: &Node, source: &[u8], symbols: &mut FileSymbols) .or_else(|| find_child(node, "identifier")); if let Some(name_node) = name_node { let full_path = node_text(&name_node, source).to_string(); - let last_name = full_path.split('.').last().unwrap_or("").to_string(); + let last_name = full_path.split('.').next_back().unwrap_or("").to_string(); let mut imp = Import::new(full_path, vec![last_name], start_line(node)); imp.csharp_using = Some(true); symbols.imports.push(imp); diff --git a/crates/codegraph-core/src/extractors/cuda.rs b/crates/codegraph-core/src/extractors/cuda.rs index bb7c26449..5ecb63def 100644 --- a/crates/codegraph-core/src/extractors/cuda.rs +++ b/crates/codegraph-core/src/extractors/cuda.rs @@ -222,7 +222,7 @@ fn is_cuda_method_declarator(node: &Node) -> bool { // data fields, not method declarations. return current .child_by_field_name("declarator") - .map_or(true, |n| n.kind() != "parenthesized_declarator"); + .is_none_or(|n| n.kind() != "parenthesized_declarator"); } _ => return false, } diff --git a/crates/codegraph-core/src/extractors/dart.rs b/crates/codegraph-core/src/extractors/dart.rs index 0332e56af..968b48a74 100644 --- a/crates/codegraph-core/src/extractors/dart.rs +++ b/crates/codegraph-core/src/extractors/dart.rs @@ -37,10 +37,8 @@ fn match_dart_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _depth "enum_declaration" => handle_dart_enum(node, source, symbols), "mixin_declaration" => handle_dart_mixin(node, source, symbols), "extension_declaration" => handle_dart_extension(node, source, symbols), - "function_signature" => { - if !is_inside_class(node) { - handle_dart_function_sig(node, source, symbols); - } + "function_signature" if !is_inside_class(node) => { + handle_dart_function_sig(node, source, symbols); } "library_import" => handle_dart_import(node, source, symbols), "constructor_invocation" | "new_expression" => { diff --git a/crates/codegraph-core/src/extractors/erlang.rs b/crates/codegraph-core/src/extractors/erlang.rs index 443a77e3a..202a1e488 100644 --- a/crates/codegraph-core/src/extractors/erlang.rs +++ b/crates/codegraph-core/src/extractors/erlang.rs @@ -25,11 +25,9 @@ fn match_erlang_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _dep "record_decl" => handle_record_decl(node, source, symbols), "type_alias" | "opaque" => handle_type_alias(node, source, symbols), "fun_decl" => handle_fun_decl(node, source, symbols), - "function_clause" => { - // Only handle if not inside fun_decl (fun_decl handles its own clauses) - if node.parent().map(|p| p.kind()) != Some("fun_decl") { - handle_function_clause(node, source, symbols); - } + // Only handle if not inside fun_decl (fun_decl handles its own clauses) + "function_clause" if node.parent().map(|p| p.kind()) != Some("fun_decl") => { + handle_function_clause(node, source, symbols); } "pp_define" => handle_define(node, source, symbols), "pp_include" | "pp_include_lib" => handle_include(node, source, symbols), diff --git a/crates/codegraph-core/src/extractors/fsharp.rs b/crates/codegraph-core/src/extractors/fsharp.rs index 3fd1d4529..e32a81c67 100644 --- a/crates/codegraph-core/src/extractors/fsharp.rs +++ b/crates/codegraph-core/src/extractors/fsharp.rs @@ -304,7 +304,7 @@ fn handle_import_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) { let source_name = node_text(&module_node, source).to_string(); let last = source_name .split('.') - .last() + .next_back() .unwrap_or(&source_name) .to_string(); diff --git a/crates/codegraph-core/src/extractors/go.rs b/crates/codegraph-core/src/extractors/go.rs index 03a23b7b8..1de684ade 100644 --- a/crates/codegraph-core/src/extractors/go.rs +++ b/crates/codegraph-core/src/extractors/go.rs @@ -333,7 +333,7 @@ fn extract_go_import_spec(spec: &Node, source: &[u8], symbols: &mut FileSymbols) let name_node = spec.child_by_field_name("name"); let alias = match name_node { Some(n) => node_text(&n, source).to_string(), - None => import_path.split('/').last().unwrap_or("").to_string(), + None => import_path.split('/').next_back().unwrap_or("").to_string(), }; let mut imp = Import::new(import_path, vec![alias], start_line(spec)); imp.go_import = Some(true); diff --git a/crates/codegraph-core/src/extractors/groovy.rs b/crates/codegraph-core/src/extractors/groovy.rs index f8c1d11c5..9c405c985 100644 --- a/crates/codegraph-core/src/extractors/groovy.rs +++ b/crates/codegraph-core/src/extractors/groovy.rs @@ -343,7 +343,7 @@ fn handle_import_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) { let names = if has_asterisk { vec!["*".to_string()] } else { - let last = import_path.split('.').last().unwrap_or("").to_string(); + let last = import_path.split('.').next_back().unwrap_or("").to_string(); vec![last] }; let mut imp = Import::new(import_path, names, start_line(node)); diff --git a/crates/codegraph-core/src/extractors/haskell.rs b/crates/codegraph-core/src/extractors/haskell.rs index 147dd08f9..8e8e21d58 100644 --- a/crates/codegraph-core/src/extractors/haskell.rs +++ b/crates/codegraph-core/src/extractors/haskell.rs @@ -306,7 +306,7 @@ fn handle_haskell_import(node: &Node, source: &[u8], symbols: &mut FileSymbols) if names.is_empty() { let last = source_name .split('.') - .last() + .next_back() .unwrap_or(&source_name) .to_string(); names.push(last); diff --git a/crates/codegraph-core/src/extractors/helpers.rs b/crates/codegraph-core/src/extractors/helpers.rs index 7fcf77485..61ca9e187 100644 --- a/crates/codegraph-core/src/extractors/helpers.rs +++ b/crates/codegraph-core/src/extractors/helpers.rs @@ -774,25 +774,24 @@ fn walk_ast_nodes_with_config_depth( "await" => { ast_nodes.push(build_await_node(node, source)); } - "string" => { + "string" if build_string_node(node, source, config) .map(|n| ast_nodes.push(n)) - .is_none() - { - // Short string: recurse children then skip outer loop - for i in 0..node.child_count() { - if let Some(child) = node.child(i) { - walk_ast_nodes_with_config_depth( - &child, - source, - ast_nodes, - config, - depth + 1, - ); - } + .is_none() => + { + // Short string: recurse children then skip outer loop + for i in 0..node.child_count() { + if let Some(child) = node.child(i) { + walk_ast_nodes_with_config_depth( + &child, + source, + ast_nodes, + config, + depth + 1, + ); } - return; } + return; } "regex" => { ast_nodes.push(build_regex_node(node, source)); diff --git a/crates/codegraph-core/src/extractors/java.rs b/crates/codegraph-core/src/extractors/java.rs index 2e32b6f26..27ff57b4c 100644 --- a/crates/codegraph-core/src/extractors/java.rs +++ b/crates/codegraph-core/src/extractors/java.rs @@ -321,7 +321,7 @@ fn handle_import_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) { let names = if has_asterisk { vec!["*".to_string()] } else { - let last = import_path.split('.').last().unwrap_or("").to_string(); + let last = import_path.split('.').next_back().unwrap_or("").to_string(); vec![last] }; push_import(symbols, node, import_path, names, |imp| { diff --git a/crates/codegraph-core/src/extractors/javascript.rs b/crates/codegraph-core/src/extractors/javascript.rs index d2cbcb45c..7927bb2d2 100644 --- a/crates/codegraph-core/src/extractors/javascript.rs +++ b/crates/codegraph-core/src/extractors/javascript.rs @@ -3008,8 +3008,11 @@ fn handle_export_stmt(node: &Node, source: &[u8], symbols: &mut FileSymbols) { let source_node = node .child_by_field_name("source") .or_else(|| find_child(node, "string")); - if source_node.is_some() && decl.is_none() { - handle_reexport(node, &source_node.unwrap(), source, symbols); + match &source_node { + Some(source_node) if decl.is_none() => { + handle_reexport(node, source_node, source, symbols); + } + _ => {} } } @@ -3298,8 +3301,8 @@ fn walk_ast_nodes_depth(node: &Node, source: &[u8], ast_nodes: &mut Vec let raw = node_text(node, source); // Strip quotes to get content let content = raw - .trim_start_matches(|c| c == '\'' || c == '"' || c == '`') - .trim_end_matches(|c| c == '\'' || c == '"' || c == '`'); + .trim_start_matches(['\'', '"', '`']) + .trim_end_matches(['\'', '"', '`']); // Count Unicode code points, not UTF-8 bytes, so the filter matches // helpers.rs `build_string_node` and the WASM visitor — a single non- // ASCII glyph like `─` (3 bytes / 1 code point) must be treated as one @@ -4699,10 +4702,10 @@ fn pattern_binds_name(param_node: &Node, name: &str, source: &[u8], depth: usize continue; }; match child.kind() { - "shorthand_property_identifier_pattern" => { - if node_text(&child, source) == name { - return true; - } + "shorthand_property_identifier_pattern" + if node_text(&child, source) == name => + { + return true; } "pair_pattern" => { if let Some(value) = child.child_by_field_name("value") { @@ -4711,10 +4714,10 @@ fn pattern_binds_name(param_node: &Node, name: &str, source: &[u8], depth: usize } } } - "rest_pattern" | "object_assignment_pattern" => { - if pattern_binds_name(&child, name, source, depth + 1) { - return true; - } + "rest_pattern" | "object_assignment_pattern" + if pattern_binds_name(&child, name, source, depth + 1) => + { + return true; } _ => {} } @@ -4824,7 +4827,7 @@ fn scan_pattern_defaults_for_reference( } } } - "rest_pattern" | "object_assignment_pattern" => { + "rest_pattern" | "object_assignment_pattern" if scan_pattern_defaults_for_reference( &child, name, @@ -4832,9 +4835,9 @@ fn scan_pattern_defaults_for_reference( source, depth + 1, require_call_site, - ) { - return true; - } + ) => + { + return true; } _ => {} } @@ -4929,10 +4932,11 @@ fn block_contains_identifier_excluding( if node.id() == exclude_id { return false; } - if node.kind() == "identifier" && node_text(node, source) == name { - if !require_call_site || is_call_callee(node) { - return true; - } + if node.kind() == "identifier" + && node_text(node, source) == name + && (!require_call_site || is_call_callee(node)) + { + return true; } if SCOPE_NODE_TYPES.contains(&node.kind()) && introduces_shadowed_binding(node, name, source) { return false; diff --git a/crates/codegraph-core/src/extractors/julia.rs b/crates/codegraph-core/src/extractors/julia.rs index c07ad3c80..169c8992d 100644 --- a/crates/codegraph-core/src/extractors/julia.rs +++ b/crates/codegraph-core/src/extractors/julia.rs @@ -779,7 +779,7 @@ mod tests { let names: Vec<&str> = s.definitions.iter().map(|d| d.name.as_str()).collect(); assert!(names.contains(&"Foo.bar"), "got {names:?}"); assert!( - !names.iter().any(|n| *n == "Outer.Foo.bar"), + !names.contains(&"Outer.Foo.bar"), "qualified method got double-prefixed: {names:?}" ); } @@ -794,7 +794,7 @@ mod tests { let names: Vec<&str> = s.definitions.iter().map(|d| d.name.as_str()).collect(); assert!(names.contains(&"Base.show"), "got {names:?}"); assert!( - !names.iter().any(|n| *n == "Foo.Base.show"), + !names.contains(&"Foo.Base.show"), "qualified function def got double-prefixed: {names:?}" ); } diff --git a/crates/codegraph-core/src/extractors/kotlin.rs b/crates/codegraph-core/src/extractors/kotlin.rs index 76b834af7..06e5e769a 100644 --- a/crates/codegraph-core/src/extractors/kotlin.rs +++ b/crates/codegraph-core/src/extractors/kotlin.rs @@ -338,7 +338,7 @@ fn match_kotlin_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _dep "import_header" => { if let Some(id_node) = find_child(node, "identifier") { let path = node_text(&id_node, source).to_string(); - let last = path.split('.').last().unwrap_or("").to_string(); + let last = path.split('.').next_back().unwrap_or("").to_string(); let mut imp = Import::new(path, vec![last], start_line(node)); imp.kotlin_import = Some(true); symbols.imports.push(imp); diff --git a/crates/codegraph-core/src/extractors/ocaml.rs b/crates/codegraph-core/src/extractors/ocaml.rs index 0ce2edc23..bbaea9417 100644 --- a/crates/codegraph-core/src/extractors/ocaml.rs +++ b/crates/codegraph-core/src/extractors/ocaml.rs @@ -240,7 +240,7 @@ fn handle_ocaml_open(node: &Node, source: &[u8], symbols: &mut FileSymbols) { } if let Some(name) = module_name { - let last = name.split('.').last().unwrap_or(&name).to_string(); + let last = name.split('.').next_back().unwrap_or(&name).to_string(); symbols .imports .push(Import::new(name, vec![last], start_line(node))); diff --git a/crates/codegraph-core/src/extractors/php.rs b/crates/codegraph-core/src/extractors/php.rs index 22e18e81d..4bb782080 100644 --- a/crates/codegraph-core/src/extractors/php.rs +++ b/crates/codegraph-core/src/extractors/php.rs @@ -252,7 +252,7 @@ fn handle_namespace_use(node: &Node, source: &[u8], symbols: &mut FileSymbols) { find_child(&child, "qualified_name").or_else(|| find_child(&child, "name")); if let Some(name_node) = name_node { let full_path = node_text(&name_node, source).to_string(); - let last_name = full_path.split('\\').last().unwrap_or("").to_string(); + let last_name = full_path.split('\\').next_back().unwrap_or("").to_string(); let alias = child.child_by_field_name("alias"); let alias_text = alias .map(|a| node_text(&a, source).to_string()) @@ -265,7 +265,7 @@ fn handle_namespace_use(node: &Node, source: &[u8], symbols: &mut FileSymbols) { // Single use clause without wrapper if child.kind() == "qualified_name" || child.kind() == "name" { let full_path = node_text(&child, source).to_string(); - let last_name = full_path.split('\\').last().unwrap_or("").to_string(); + let last_name = full_path.split('\\').next_back().unwrap_or("").to_string(); let mut imp = Import::new(full_path, vec![last_name], start_line(node)); imp.php_use = Some(true); symbols.imports.push(imp); @@ -290,7 +290,7 @@ fn handle_function_call(node: &Node, source: &[u8], symbols: &mut FileSymbols) { } "qualified_name" => { let text = node_text(&fn_node, source); - let last = text.split('\\').last().unwrap_or(""); + let last = text.split('\\').next_back().unwrap_or(""); symbols.calls.push(Call { name: last.to_string(), line: start_line(node), @@ -337,7 +337,7 @@ fn handle_object_creation(node: &Node, source: &[u8], symbols: &mut FileSymbols) return; } let text = node_text(&class_node, source); - let last = text.split('\\').last().unwrap_or(""); + let last = text.split('\\').next_back().unwrap_or(""); symbols.calls.push(Call { name: last.to_string(), line: start_line(node), diff --git a/crates/codegraph-core/src/extractors/ruby.rs b/crates/codegraph-core/src/extractors/ruby.rs index 65f542511..3ec93ac25 100644 --- a/crates/codegraph-core/src/extractors/ruby.rs +++ b/crates/codegraph-core/src/extractors/ruby.rs @@ -193,7 +193,7 @@ fn handle_require_call(node: &Node, source: &[u8], symbols: &mut FileSymbols) { for i in 0..args.child_count() { let Some(arg) = args.child(i) else { continue }; if let Some(content) = extract_ruby_string_content(&arg, source) { - let last = content.split('/').last().unwrap_or("").to_string(); + let last = content.split('/').next_back().unwrap_or("").to_string(); let mut imp = Import::new(content, vec![last], start_line(node)); imp.ruby_require = Some(true); symbols.imports.push(imp); diff --git a/crates/codegraph-core/src/extractors/rust_lang.rs b/crates/codegraph-core/src/extractors/rust_lang.rs index 55a5516bf..425d8c6de 100644 --- a/crates/codegraph-core/src/extractors/rust_lang.rs +++ b/crates/codegraph-core/src/extractors/rust_lang.rs @@ -71,7 +71,7 @@ fn handle_function_item(node: &Node, source: &[u8], symbols: &mut FileSymbols) { if node .parent() .and_then(|p| p.parent()) - .map_or(false, |gp| gp.kind() == "trait_item") + .is_some_and(|gp| gp.kind() == "trait_item") { return; } @@ -511,7 +511,7 @@ fn extract_rust_use_path(node: &Node, source: &[u8]) -> Vec<(String, Vec )] } "use_wildcard" => { - let src = named_child_text(&node, "path", source) + let src = named_child_text(node, "path", source) .map(|s| s.to_string()) .unwrap_or_else(|| "*".to_string()); vec![(src, vec!["*".to_string()])] @@ -526,7 +526,7 @@ fn extract_rust_use_path(node: &Node, source: &[u8]) -> Vec<(String, Vec } fn extract_scoped_use_list(node: &Node, source: &[u8]) -> Vec<(String, Vec)> { - let prefix = named_child_text(&node, "path", source) + let prefix = named_child_text(node, "path", source) .map(|s| s.to_string()) .unwrap_or_default(); let Some(list_node) = node.child_by_field_name("list") else { @@ -748,7 +748,7 @@ fn match_rust_return_type_map( if node .parent() .and_then(|p| p.parent()) - .map_or(false, |gp| gp.kind() == "trait_item") + .is_some_and(|gp| gp.kind() == "trait_item") { return; } @@ -777,7 +777,7 @@ fn match_rust_return_type_map( .iter() .find(|e| e.name == full_name) .map(|e| e.confidence); - if existing_confidence.map_or(true, |c| c < 1.0) { + if existing_confidence.is_none_or(|c| c < 1.0) { symbols.return_type_map.push(TypeMapEntry { name: full_name, type_name: type_name.to_string(), diff --git a/crates/codegraph-core/src/extractors/scala.rs b/crates/codegraph-core/src/extractors/scala.rs index aedcf9b71..819cc4915 100644 --- a/crates/codegraph-core/src/extractors/scala.rs +++ b/crates/codegraph-core/src/extractors/scala.rs @@ -186,10 +186,8 @@ fn extract_scala_import_path(node: &Node, source: &[u8]) -> String { } path.push_str(node_text(&child, source)); } - "." => { - if !path.is_empty() { - path.push('.'); - } + "." if !path.is_empty() => { + path.push('.'); } "import_selectors" => { // e.g. import scala.collection.mutable.{Map, Set} @@ -322,7 +320,7 @@ fn handle_scala_function_definition(node: &Node, source: &[u8], symbols: &mut Fi fn handle_scala_import_declaration(node: &Node, source: &[u8], symbols: &mut FileSymbols) { let path = extract_scala_import_path(node, source); if !path.is_empty() { - let last = path.split('.').last().unwrap_or("").to_string(); + let last = path.split('.').next_back().unwrap_or("").to_string(); let mut imp = Import::new(path, vec![last], start_line(node)); imp.scala_import = Some(true); symbols.imports.push(imp); diff --git a/crates/codegraph-core/src/extractors/solidity.rs b/crates/codegraph-core/src/extractors/solidity.rs index cf3cb2061..01a0f2996 100644 --- a/crates/codegraph-core/src/extractors/solidity.rs +++ b/crates/codegraph-core/src/extractors/solidity.rs @@ -524,8 +524,8 @@ fn find_parent_name(node: &Node, source: &[u8]) -> Option { /// Strip leading/trailing single, double, or backtick quotes. fn strip_quotes(text: &str) -> String { let trimmed = text - .trim_start_matches(|c: char| c == '\'' || c == '"' || c == '`') - .trim_end_matches(|c: char| c == '\'' || c == '"' || c == '`'); + .trim_start_matches(['\'', '"', '`']) + .trim_end_matches(['\'', '"', '`']); trimmed.to_string() } diff --git a/crates/codegraph-core/src/extractors/swift.rs b/crates/codegraph-core/src/extractors/swift.rs index 074f4ae30..44c225013 100644 --- a/crates/codegraph-core/src/extractors/swift.rs +++ b/crates/codegraph-core/src/extractors/swift.rs @@ -385,7 +385,7 @@ fn match_swift_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _dept "import_declaration" => { if let Some(id_node) = find_child(node, "identifier") { let path = node_text(&id_node, source).to_string(); - let last = path.split('.').last().unwrap_or(&path).to_string(); + let last = path.split('.').next_back().unwrap_or(&path).to_string(); let mut imp = Import::new(path, vec![last], start_line(node)); imp.swift_import = Some(true); symbols.imports.push(imp); diff --git a/crates/codegraph-core/src/extractors/verilog.rs b/crates/codegraph-core/src/extractors/verilog.rs index 8a322969f..e828d12b4 100644 --- a/crates/codegraph-core/src/extractors/verilog.rs +++ b/crates/codegraph-core/src/extractors/verilog.rs @@ -304,7 +304,7 @@ fn handle_include_directive(node: &Node, source: &[u8], symbols: &mut FileSymbol } let last = source_path .split('/') - .last() + .next_back() .unwrap_or(&source_path) .to_string(); let mut imp = Import::new(source_path, vec![last], start_line(node)); diff --git a/crates/codegraph-core/src/features/structure.rs b/crates/codegraph-core/src/features/structure.rs index a707d06fd..8e650d550 100644 --- a/crates/codegraph-core/src/features/structure.rs +++ b/crates/codegraph-core/src/features/structure.rs @@ -539,7 +539,7 @@ pub fn build_full_structure( abs.strip_prefix(root) .ok() .and_then(|p| p.to_str()) - .map(|s| normalize_path(s)) + .map(normalize_path) }) .filter(|d| !d.is_empty() && d != ".") .collect(); @@ -793,7 +793,7 @@ fn insert_contains_edges( all_dirs: &HashSet, changed_files: Option<&[String]>, ) { - let affected_dirs = changed_files.map(|cf| get_ancestor_dirs(cf)); + let affected_dirs = changed_files.map(get_ancestor_dirs); let tx = match conn.unchecked_transaction() { Ok(tx) => tx, @@ -1049,7 +1049,7 @@ fn count_directory_edges<'a>( if let Some(src_dirs) = src_dirs { for dir in src_dirs { if let Some(counts) = dir_edge_counts.get_mut(dir) { - if tgt_dirs.map_or(false, |td| td.contains(dir)) { + if tgt_dirs.is_some_and(|td| td.contains(dir)) { counts.0 += 1; // intra } else { counts.2 += 1; // fan_out @@ -1059,7 +1059,7 @@ fn count_directory_edges<'a>( } if let Some(tgt_dirs) = tgt_dirs { for dir in tgt_dirs { - if src_dirs.map_or(true, |sd| !sd.contains(dir)) { + if src_dirs.is_none_or(|sd| !sd.contains(dir)) { if let Some(counts) = dir_edge_counts.get_mut(dir) { counts.1 += 1; // fan_in } diff --git a/crates/codegraph-core/src/graph/classifiers/roles.rs b/crates/codegraph-core/src/graph/classifiers/roles.rs index 0a1e47057..06ae0c824 100644 --- a/crates/codegraph-core/src/graph/classifiers/roles.rs +++ b/crates/codegraph-core/src/graph/classifiers/roles.rs @@ -98,7 +98,7 @@ fn median(sorted: &[u32]) -> f64 { return 0.0; } let mid = sorted.len() / 2; - if sorted.len() % 2 == 0 { + if sorted.len().is_multiple_of(2) { (sorted[mid - 1] as f64 + sorted[mid] as f64) / 2.0 } else { sorted[mid] as f64 @@ -113,7 +113,7 @@ fn compute_type_def_names_by_file( ) -> HashMap> { let mut by_file: HashMap> = HashMap::new(); for (_id, name, kind, file, _fan_in, _fan_out) in rows { - if TYPE_DEF_KINDS.iter().any(|k| *k == kind.as_str()) { + if TYPE_DEF_KINDS.contains(&kind.as_str()) { by_file .entry(file.clone()) .or_default() @@ -185,7 +185,7 @@ fn filter_type_member_property_rows( /// Dead sub-role classification matching JS `classifyDeadSubRole`. fn classify_dead_sub_role(_name: &str, kind: &str, file: &str) -> &'static str { // Leaf kinds - if LEAF_KINDS.iter().any(|k| *k == kind) { + if LEAF_KINDS.contains(&kind) { return "dead-leaf"; } // FFI boundary (checked before dead-entry — an FFI boundary is a more @@ -241,7 +241,7 @@ fn classify_node( // Well-known Commander.js dispatch methods (execute, validate) in framework // directories are confirmed entry points, not candidates. Promote them to // `entry` so they don't appear in `--role dead` output. - if COMMANDER_DISPATCH_NAMES.iter().any(|n| *n == name) + if COMMANDER_DISPATCH_NAMES.contains(&name) && ENTRY_PATH_PATTERNS.iter().any(|p| file.contains(p)) { return "entry"; @@ -257,7 +257,7 @@ fn classify_node( // consumed via type annotations and struct literals — not calls — so they // never get inbound call edges. If the same file has active callables, // these types are almost certainly live — classify as leaf. - if TYPE_DEF_KINDS.iter().any(|k| *k == kind) { + if TYPE_DEF_KINDS.contains(&kind) { return "leaf"; } // Methods implementing interfaces are dispatched via conditional property @@ -719,7 +719,7 @@ fn compute_active_files( let mut active = std::collections::HashSet::new(); let mut called_active = std::collections::HashSet::new(); for (_id, _name, kind, file, fan_in, fan_out) in rows { - if !ANNOTATION_ONLY_KINDS.iter().any(|k| *k == kind.as_str()) { + if !ANNOTATION_ONLY_KINDS.contains(&kind.as_str()) { if *fan_in > 0 || *fan_out > 0 { active.insert(file.clone()); } @@ -813,8 +813,7 @@ fn classify_rows( for (id, name, kind, file, fan_in, fan_out) in rows { let is_exported = exported_ids.contains(id); let prod_fi = prod_fan_in.get(id).copied().unwrap_or(0); - let is_annotation_only = - kind == "constant" || TYPE_DEF_KINDS.iter().any(|k| *k == kind.as_str()); + let is_annotation_only = kind == "constant" || TYPE_DEF_KINDS.contains(&kind.as_str()); // Set has_active_siblings for annotation-only kinds AND for method/function — // the latter two can have fan_in == 0 due to untraced call-site patterns // (interface dispatch, logical-or defaults). The classifier interprets this @@ -914,8 +913,7 @@ fn is_live_root( if is_public_surface { return true; } - COMMANDER_DISPATCH_NAMES.iter().any(|n| *n == name) - && ENTRY_PATH_PATTERNS.iter().any(|p| file.contains(p)) + COMMANDER_DISPATCH_NAMES.contains(&name) && ENTRY_PATH_PATTERNS.iter().any(|p| file.contains(p)) } /// Compute the set of bare (owner-prefix-stripped) member names declared by @@ -1300,6 +1298,12 @@ fn find_neighbour_files( Ok(result) } +/// `(id, name, file)` row for a `kind = 'property'` node. +type LeafRow = (i64, String, String); + +/// `(id, name, kind, file, fan_in, fan_out)` row for a callable node. +type CallableRow = (i64, String, String, String, u32, u32); + /// Query leaf kind node rows and callable node rows for a set of files. /// `parameter` is intentionally excluded from the leaf query (#1723) — see /// `do_classify_full`'s leaf_rows comment for the rationale. Leaf rows carry @@ -1308,10 +1312,7 @@ fn find_neighbour_files( fn query_nodes_for_files( tx: &rusqlite::Transaction, files: &[&str], -) -> rusqlite::Result<( - Vec<(i64, String, String)>, - Vec<(i64, String, String, String, u32, u32)>, -)> { +) -> rusqlite::Result<(Vec, Vec)> { let ph: String = files.iter().map(|_| "?").collect::>().join(","); let leaf_sql = format!( From 44373361b0dad75b9ea645c00d4ddd53587cb9d9 Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Thu, 13 Aug 2026 06:49:32 -0600 Subject: [PATCH 2/3] ci: gate cargo clippy in the rust-check job (#2326) Adds clippy to the Setup Rust step's components list and a new "Check clippy" step running `cargo clippy --workspace --all-targets -- -D warnings` right after the existing `cargo fmt --check` step, now that the crate is clippy-clean (previous commit). Mirrors how #2096 added the fmt gate to this same job. Removes the now-stale comment saying clippy was deliberately not gated yet. docs check acknowledged: CI workflow change only, no user-facing behavior or documented feature change. --- .github/workflows/ci.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2dfdcd0ec..9b1331d39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -405,7 +405,7 @@ jobs: - name: Setup Rust uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - components: rustfmt + components: rustfmt, clippy - name: Rust cache uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 @@ -418,14 +418,20 @@ jobs: # #2096: cargo fmt --check gate — a one-time repo-wide `cargo fmt` pass # (no functional change) landed alongside this step so the gate starts # clean instead of failing every future PR on pre-existing drift. - # cargo clippy is deliberately NOT gated here yet: unlike fmt (a purely - # mechanical, semantics-preserving reformat), clearing its ~98 - # pre-existing warnings needs case-by-case review, not a single - # automated pass — tracked separately. - name: Check formatting working-directory: crates/codegraph-core run: cargo fmt -- --check + # #2326: cargo clippy -D warnings gate — the ~98 pre-existing warnings + # (98 at #2096 investigation time, 102 unique sites by the time this + # landed) were triaged and cleared (mechanical simplification or a + # targeted `#[allow(...)]` with justification, never a bare allow) so + # the gate starts clean instead of failing every future PR on + # pre-existing drift, mirroring the `cargo fmt --check` gate above. + - name: Check clippy + working-directory: crates/codegraph-core + run: cargo clippy --workspace --all-targets -- -D warnings + # ── Pre-publish benchmark gate ── # # Mirrors the gate in publish.yml so every PR catches regressions before From fae478a3004b8bb3a926ddb211c2a43600f5178b Mon Sep 17 00:00:00 2001 From: carlos-alm Date: Thu, 13 Aug 2026 07:10:24 -0600 Subject: [PATCH 3/3] fix(rust): remove redundant borrow flagged by CI's newer clippy toolchain CI's rust-toolchain floats to latest stable (1.97.0), one minor version ahead of what was tested locally (1.95.0) when this PR's clippy pass landed. That gap introduced exactly one new warning at a site this PR never touched: a redundant `&` in a format! argument in insert_symbol_nodes. Updated the local toolchain to 1.97.1 and re-ran `cargo clippy --workspace --all-targets -- -D warnings` to confirm this was the only drift-induced gap, not a first instance of a recurring one. docs check acknowledged: same internal code-quality fix as the parent commits, no README/CLAUDE.md/ROADMAP.md update applies. --- .../src/domain/graph/builder/stages/insert_nodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs b/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs index 0cdcb5ae9..362c73a2d 100644 --- a/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs +++ b/crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs @@ -252,7 +252,7 @@ fn insert_symbol_nodes( for batch in batches { let node_ids = query_node_ids(&mut id_stmt, &batch.file)?; - let file_id = node_ids.get(&format!("{}|file|0", &batch.file)).copied(); + let file_id = node_ids.get(&format!("{}|file|0", batch.file)).copied(); for def in &batch.definitions { let def_key = format!("{}|{}|{}", def.name, def.kind, def.line);