Skip to content

Commit 44fe899

Browse files
feat: add Halstead, LOC, and MI metrics to Rust native engine (#159)
The native engine previously computed only cognitive, cyclomatic, and maxNesting complexity. Halstead metrics, LOC, and Maintainability Index were only available via the WASM fallback path, leaving native users with incomplete (all-zero) data for those columns. This adds full-fidelity computation of all metrics in the Rust engine: - Add HalsteadMetrics and LocMetrics NAPI structs to types.rs - Extend ComplexityMetrics with optional halstead, loc, and MI fields - Add HalsteadRules struct with per-language classification tables for all 8 supported languages (JS/TS, Python, Go, Rust, Java, C#, Ruby, PHP), mirroring the JS HALSTEAD_RULES - Add compute_all_metrics() single-pass DFS that computes complexity + Halstead + LOC + MI in one tree walk - Update all 8 extractors to call compute_all_metrics - Update normalizeNativeSymbols in parser.js to pass through new fields - Update buildComplexityMetrics precomputed branch to use actual native values instead of hardcoded zeros Impact: 21 functions changed, 56 affected Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent ea6b050 commit 44fe899

File tree

12 files changed

+953
-76
lines changed

12 files changed

+953
-76
lines changed

crates/codegraph-core/src/complexity.rs

Lines changed: 617 additions & 5 deletions
Large diffs are not rendered by default.

crates/codegraph-core/src/extractors/csharp.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, CSHARP_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -104,7 +104,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
104104
line: start_line(&child),
105105
end_line: Some(end_line(&child)),
106106
decorators: None,
107-
complexity: Some(compute_function_complexity(&child, &CSHARP_RULES)),
107+
complexity: compute_all_metrics(&child, source, "c_sharp"),
108108
});
109109
}
110110
}
@@ -141,7 +141,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
141141
line: start_line(node),
142142
end_line: Some(end_line(node)),
143143
decorators: None,
144-
complexity: Some(compute_function_complexity(node, &CSHARP_RULES)),
144+
complexity: compute_all_metrics(node, source, "c_sharp"),
145145
});
146146
}
147147
}
@@ -160,7 +160,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
160160
line: start_line(node),
161161
end_line: Some(end_line(node)),
162162
decorators: None,
163-
complexity: Some(compute_function_complexity(node, &CSHARP_RULES)),
163+
complexity: compute_all_metrics(node, source, "c_sharp"),
164164
});
165165
}
166166
}
@@ -179,7 +179,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
179179
line: start_line(node),
180180
end_line: Some(end_line(node)),
181181
decorators: None,
182-
complexity: Some(compute_function_complexity(node, &CSHARP_RULES)),
182+
complexity: compute_all_metrics(node, source, "c_sharp"),
183183
});
184184
}
185185
}

crates/codegraph-core/src/extractors/go.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, GO_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -24,7 +24,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
2424
line: start_line(node),
2525
end_line: Some(end_line(node)),
2626
decorators: None,
27-
complexity: Some(compute_function_complexity(node, &GO_RULES)),
27+
complexity: compute_all_metrics(node, source, "go"),
2828
});
2929
}
3030
}
@@ -60,7 +60,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
6060
line: start_line(node),
6161
end_line: Some(end_line(node)),
6262
decorators: None,
63-
complexity: Some(compute_function_complexity(node, &GO_RULES)),
63+
complexity: compute_all_metrics(node, source, "go"),
6464
});
6565
}
6666
}

crates/codegraph-core/src/extractors/java.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, JAVA_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -110,7 +110,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
110110
line: start_line(&child),
111111
end_line: Some(end_line(&child)),
112112
decorators: None,
113-
complexity: Some(compute_function_complexity(&child, &JAVA_RULES)),
113+
complexity: compute_all_metrics(&child, source, "java"),
114114
});
115115
}
116116
}
@@ -147,7 +147,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
147147
line: start_line(node),
148148
end_line: Some(end_line(node)),
149149
decorators: None,
150-
complexity: Some(compute_function_complexity(node, &JAVA_RULES)),
150+
complexity: compute_all_metrics(node, source, "java"),
151151
});
152152
}
153153
}
@@ -166,7 +166,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
166166
line: start_line(node),
167167
end_line: Some(end_line(node)),
168168
decorators: None,
169-
complexity: Some(compute_function_complexity(node, &JAVA_RULES)),
169+
complexity: compute_all_metrics(node, source, "java"),
170170
});
171171
}
172172
}

crates/codegraph-core/src/extractors/javascript.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, JS_TS_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -24,7 +24,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
2424
line: start_line(node),
2525
end_line: Some(end_line(node)),
2626
decorators: None,
27-
complexity: Some(compute_function_complexity(node, &JS_TS_RULES)),
27+
complexity: compute_all_metrics(node, source, "javascript"),
2828
});
2929
}
3030
}
@@ -80,7 +80,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
8080
line: start_line(node),
8181
end_line: Some(end_line(node)),
8282
decorators: None,
83-
complexity: Some(compute_function_complexity(node, &JS_TS_RULES)),
83+
complexity: compute_all_metrics(node, source, "javascript"),
8484
});
8585
}
8686
}
@@ -138,7 +138,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
138138
line: start_line(node),
139139
end_line: Some(end_line(&value_n)),
140140
decorators: None,
141-
complexity: Some(compute_function_complexity(&value_n, &JS_TS_RULES)),
141+
complexity: compute_all_metrics(&value_n, source, "javascript"),
142142
});
143143
}
144144
}
@@ -562,7 +562,7 @@ fn extract_callback_definition(call_node: &Node, source: &[u8]) -> Option<Defini
562562
line: start_line(&cb),
563563
end_line: Some(end_line(&cb)),
564564
decorators: None,
565-
complexity: Some(compute_function_complexity(&cb, &JS_TS_RULES)),
565+
complexity: compute_all_metrics(&cb, source, "javascript"),
566566
});
567567
}
568568

@@ -579,7 +579,7 @@ fn extract_callback_definition(call_node: &Node, source: &[u8]) -> Option<Defini
579579
line: start_line(&cb),
580580
end_line: Some(end_line(&cb)),
581581
decorators: None,
582-
complexity: Some(compute_function_complexity(&cb, &JS_TS_RULES)),
582+
complexity: compute_all_metrics(&cb, source, "javascript"),
583583
});
584584
}
585585

@@ -593,7 +593,7 @@ fn extract_callback_definition(call_node: &Node, source: &[u8]) -> Option<Defini
593593
line: start_line(&cb),
594594
end_line: Some(end_line(&cb)),
595595
decorators: None,
596-
complexity: Some(compute_function_complexity(&cb, &JS_TS_RULES)),
596+
complexity: compute_all_metrics(&cb, source, "javascript"),
597597
});
598598
}
599599

crates/codegraph-core/src/extractors/php.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, PHP_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -40,7 +40,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
4040
line: start_line(node),
4141
end_line: Some(end_line(node)),
4242
decorators: None,
43-
complexity: Some(compute_function_complexity(node, &PHP_RULES)),
43+
complexity: compute_all_metrics(node, source, "php"),
4444
});
4545
}
4646
}
@@ -122,7 +122,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
122122
line: start_line(&child),
123123
end_line: Some(end_line(&child)),
124124
decorators: None,
125-
complexity: Some(compute_function_complexity(&child, &PHP_RULES)),
125+
complexity: compute_all_metrics(&child, source, "php"),
126126
});
127127
}
128128
}
@@ -172,7 +172,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
172172
line: start_line(node),
173173
end_line: Some(end_line(node)),
174174
decorators: None,
175-
complexity: Some(compute_function_complexity(node, &PHP_RULES)),
175+
complexity: compute_all_metrics(node, source, "php"),
176176
});
177177
}
178178
}

crates/codegraph-core/src/extractors/python.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, PYTHON_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -40,7 +40,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
4040
} else {
4141
Some(decorators)
4242
},
43-
complexity: Some(compute_function_complexity(node, &PYTHON_RULES)),
43+
complexity: compute_all_metrics(node, source, "python"),
4444
});
4545
}
4646
}

crates/codegraph-core/src/extractors/ruby.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, RUBY_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -77,7 +77,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
7777
line: start_line(node),
7878
end_line: Some(end_line(node)),
7979
decorators: None,
80-
complexity: Some(compute_function_complexity(node, &RUBY_RULES)),
80+
complexity: compute_all_metrics(node, source, "ruby"),
8181
});
8282
}
8383
}
@@ -96,7 +96,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
9696
line: start_line(node),
9797
end_line: Some(end_line(node)),
9898
decorators: None,
99-
complexity: Some(compute_function_complexity(node, &RUBY_RULES)),
99+
complexity: compute_all_metrics(node, source, "ruby"),
100100
});
101101
}
102102
}

crates/codegraph-core/src/extractors/rust_lang.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use tree_sitter::{Node, Tree};
2-
use crate::complexity::{compute_function_complexity, RUST_LANG_RULES};
2+
use crate::complexity::compute_all_metrics;
33
use crate::types::*;
44
use super::helpers::*;
55
use super::SymbolExtractor;
@@ -43,7 +43,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
4343
line: start_line(node),
4444
end_line: Some(end_line(node)),
4545
decorators: None,
46-
complexity: Some(compute_function_complexity(node, &RUST_LANG_RULES)),
46+
complexity: compute_all_metrics(node, source, "rust"),
4747
});
4848
}
4949
}
@@ -102,7 +102,7 @@ fn walk_node(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
102102
line: start_line(&child),
103103
end_line: Some(end_line(&child)),
104104
decorators: None,
105-
complexity: Some(compute_function_complexity(&child, &RUST_LANG_RULES)),
105+
complexity: compute_all_metrics(&child, source, "rust"),
106106
});
107107
}
108108
}

crates/codegraph-core/src/types.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,58 @@
11
use napi_derive::napi;
22
use serde::{Deserialize, Serialize};
33

4+
#[napi(object)]
5+
#[derive(Debug, Clone, Serialize, Deserialize)]
6+
pub struct HalsteadMetrics {
7+
pub n1: u32,
8+
pub n2: u32,
9+
#[napi(js_name = "bigN1")]
10+
pub big_n1: u32,
11+
#[napi(js_name = "bigN2")]
12+
pub big_n2: u32,
13+
pub vocabulary: u32,
14+
pub length: u32,
15+
pub volume: f64,
16+
pub difficulty: f64,
17+
pub effort: f64,
18+
pub bugs: f64,
19+
}
20+
21+
#[napi(object)]
22+
#[derive(Debug, Clone, Serialize, Deserialize)]
23+
pub struct LocMetrics {
24+
pub loc: u32,
25+
pub sloc: u32,
26+
#[napi(js_name = "commentLines")]
27+
pub comment_lines: u32,
28+
}
29+
430
#[napi(object)]
531
#[derive(Debug, Clone, Serialize, Deserialize)]
632
pub struct ComplexityMetrics {
733
pub cognitive: u32,
834
pub cyclomatic: u32,
935
#[napi(js_name = "maxNesting")]
1036
pub max_nesting: u32,
37+
pub halstead: Option<HalsteadMetrics>,
38+
pub loc: Option<LocMetrics>,
39+
#[napi(js_name = "maintainabilityIndex")]
40+
pub maintainability_index: Option<f64>,
41+
}
42+
43+
impl ComplexityMetrics {
44+
/// Construct a basic metrics result with only cognitive/cyclomatic/maxNesting.
45+
/// Used by `compute_function_complexity` and existing tests.
46+
pub fn basic(cognitive: u32, cyclomatic: u32, max_nesting: u32) -> Self {
47+
Self {
48+
cognitive,
49+
cyclomatic,
50+
max_nesting,
51+
halstead: None,
52+
loc: None,
53+
maintainability_index: None,
54+
}
55+
}
1156
}
1257

1358
#[napi(object)]

0 commit comments

Comments
 (0)