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
16 changes: 11 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
33 changes: 12 additions & 21 deletions crates/codegraph-core/src/ast_analysis/complexity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}
Expand All @@ -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();
}
Expand All @@ -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)
Expand Down Expand Up @@ -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
})
});
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 9 additions & 9 deletions crates/codegraph-core/src/ast_analysis/dataflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1224,7 +1225,7 @@ fn handle_var_declarator(
node: &Node,
rules: &DataflowRules,
source: &[u8],
scope_stack: &mut Vec<ScopeFrame>,
scope_stack: &mut [ScopeFrame],
assignments: &mut Vec<DataflowAssignment>,
) {
let (name_node, value_node) = resolve_var_declarator_nodes(node, rules);
Expand Down Expand Up @@ -1292,7 +1293,7 @@ fn handle_assignment(
node: &Node,
rules: &DataflowRules,
source: &[u8],
scope_stack: &mut Vec<ScopeFrame>,
scope_stack: &mut [ScopeFrame],
assignments: &mut Vec<DataflowAssignment>,
mutations: &mut Vec<DataflowMutation>,
) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1419,7 +1420,6 @@ fn handle_call_expr(
});
}
}
arg_index += 1;
}
}

Expand Down
33 changes: 17 additions & 16 deletions crates/codegraph-core/src/db/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1409,14 +1409,17 @@ impl NativeDatabase {
let mut block_db_ids: std::collections::HashMap<u32, i64> =
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;
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -1748,7 +1749,7 @@ impl NativeDatabase {
purge_hashes: Option<bool>,
reverse_dep_files: Option<Vec<String>>,
) -> 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()?;
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion crates/codegraph-core/src/db/repository/graph_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(()),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>, bool, Vec<RenamedImport>);

struct TestContext {
reexports: HashMap<String, Vec<(String, Vec<String>, bool, Vec<RenamedImport>)>>,
reexports: HashMap<String, Vec<TestReexportEntry>>,
definitions: HashMap<String, HashSet<String>>,
}

Expand All @@ -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))
}
}

Expand Down
6 changes: 6 additions & 0 deletions crates/codegraph-core/src/domain/graph/builder/incremental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub struct ParseTreeCache {
entries: SendWrapper<HashMap<String, CacheEntry>>,
}

impl Default for ParseTreeCache {
fn default() -> Self {
Self::new()
}
}

#[napi]
impl ParseTreeCache {
#[napi(constructor)]
Expand Down
25 changes: 16 additions & 9 deletions crates/codegraph-core/src/domain/graph/builder/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<String, HashMap<String, (String, f64)>>;

/// Flat map for qualified `Type.method` lookups: `qualified_name → (type_name, confidence)`.
type GlobalReturnTypes = HashMap<String, (String, f64)>;

/// Timing result for each pipeline phase (returned as JSON to JS).
#[derive(Debug, Clone, Serialize, Default)]
#[serde(rename_all = "camelCase")]
Expand Down Expand Up @@ -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<String, FileSymbols>,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1721,11 +1731,8 @@ fn propagate_return_types_across_files(
fn build_return_type_index(
conn: &Connection,
file_symbols: &BTreeMap<String, FileSymbols>,
) -> (
HashMap<String, HashMap<String, (String, f64)>>,
HashMap<String, (String, f64)>,
) {
let mut return_type_index: HashMap<String, HashMap<String, (String, f64)>> = 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;
Expand Down Expand Up @@ -1761,7 +1768,7 @@ fn build_return_type_index(
}
}

let mut global_return_types: HashMap<String, (String, f64)> = 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 {
Expand Down Expand Up @@ -1864,8 +1871,8 @@ fn inject_return_types_for_file(
rel_path: &str,
symbols: &mut FileSymbols,
import_ctx: &ImportEdgeContext,
return_type_index: &HashMap<String, HashMap<String, (String, f64)>>,
global_return_types: &HashMap<String, (String, f64)>,
return_type_index: &ReturnTypeIndex,
global_return_types: &GlobalReturnTypes,
hop_penalty: f64,
) {
let abs_file = Path::new(&import_ctx.root_dir).join(rel_path);
Expand Down
Loading
Loading