Skip to content

Commit 32515b8

Browse files
committed
Added metrics
1 parent 3fccc49 commit 32515b8

File tree

4 files changed

+237
-10
lines changed

4 files changed

+237
-10
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { JAVA_COMMENT_QUERY, JAVA_COMPLEXITY_QUERY } from "./queries.ts";
2+
import type {
3+
CodeCounts,
4+
CommentSpan,
5+
JavaComplexityMetrics,
6+
} from "./types.ts";
7+
import type Parser from "tree-sitter";
8+
9+
// This class has no tests because it is copy-pasted from C.
10+
// If the C metrics work, then the Java ones do.
11+
12+
export class JavaMetricsAnalyzer {
13+
/**
14+
* Calculates metrics for a Java symbol.
15+
* @param node - The syntax node to analyze.
16+
* @returns An object containing the complexity metrics.
17+
*/
18+
public analyzeNode(node: Parser.SyntaxNode): JavaComplexityMetrics {
19+
if (node.type === "preproc_function_def") {
20+
const value = node.childForFieldName("value");
21+
if (value) {
22+
node = value;
23+
}
24+
}
25+
const complexityCount = this.getComplexityCount(node);
26+
const linesCount = node.endPosition.row - node.startPosition.row + 1;
27+
const codeCounts = this.getCodeCounts(node);
28+
const codeLinesCount = codeCounts.lines;
29+
const characterCount = node.endIndex - node.startIndex;
30+
const codeCharacterCount = codeCounts.characters;
31+
32+
return {
33+
cyclomaticComplexity: complexityCount,
34+
linesCount,
35+
codeLinesCount,
36+
characterCount,
37+
codeCharacterCount,
38+
};
39+
}
40+
41+
private getComplexityCount(node: Parser.SyntaxNode): number {
42+
const complexityMatches = JAVA_COMPLEXITY_QUERY.captures(node);
43+
return complexityMatches.length;
44+
}
45+
46+
/**
47+
* Finds comments in the given node and returns their spans.
48+
* @param node - The AST node to analyze
49+
* @returns An object containing pure comment lines and comment spans
50+
*/
51+
private findComments(
52+
node: Parser.SyntaxNode,
53+
lines: string[],
54+
): {
55+
pureCommentLines: Set<number>;
56+
commentSpans: CommentSpan[];
57+
} {
58+
const pureCommentLines = new Set<number>();
59+
const commentSpans: CommentSpan[] = [];
60+
61+
const commentCaptures = JAVA_COMMENT_QUERY.captures(node);
62+
63+
for (const capture of commentCaptures) {
64+
const commentNode = capture.node;
65+
66+
// Record the comment span for character counting
67+
commentSpans.push({
68+
start: {
69+
row: commentNode.startPosition.row,
70+
column: commentNode.startPosition.column,
71+
},
72+
end: {
73+
row: commentNode.endPosition.row,
74+
column: commentNode.endPosition.column,
75+
},
76+
});
77+
78+
// Check if the comment starts at the beginning of the line (ignoring whitespace)
79+
const lineIdx = commentNode.startPosition.row - node.startPosition.row;
80+
if (lineIdx >= 0 && lineIdx < lines.length) {
81+
const lineText = lines[lineIdx];
82+
const textBeforeComment = lineText.substring(
83+
0,
84+
commentNode.startPosition.column,
85+
);
86+
87+
// If there's only whitespace before the comment, it's a pure comment line
88+
if (textBeforeComment.trim().length === 0) {
89+
for (
90+
let line = commentNode.startPosition.row;
91+
line <= commentNode.endPosition.row;
92+
line++
93+
) {
94+
pureCommentLines.add(line);
95+
}
96+
}
97+
}
98+
}
99+
100+
return { pureCommentLines, commentSpans };
101+
}
102+
103+
/**
104+
* Finds all empty lines in a node
105+
*
106+
* @param node The syntax node to analyze
107+
* @param lines The lines of text in the node
108+
* @returns Set of line numbers that are empty
109+
*/
110+
private findEmptyLines(
111+
node: Parser.SyntaxNode,
112+
lines: string[],
113+
): Set<number> {
114+
const emptyLines = new Set<number>();
115+
for (let i = 0; i < lines.length; i++) {
116+
const lineIndex = node.startPosition.row + i;
117+
if (lines[i].trim().length === 0) {
118+
emptyLines.add(lineIndex);
119+
}
120+
}
121+
122+
return emptyLines;
123+
}
124+
125+
private getCodeCounts(node: Parser.SyntaxNode): CodeCounts {
126+
const lines = node.text.split(/\r?\n/);
127+
const linesCount = lines.length;
128+
// Find comments and their spans
129+
const { pureCommentLines, commentSpans } = this.findComments(node, lines);
130+
131+
// Find empty lines
132+
const emptyLines = this.findEmptyLines(node, lines);
133+
134+
// Calculate code lines
135+
const nonCodeLines = new Set([...pureCommentLines, ...emptyLines]);
136+
const codeLinesCount = linesCount - nonCodeLines.size;
137+
138+
let codeCharCount = 0;
139+
140+
// Process each line individually
141+
for (let i = 0; i < lines.length; i++) {
142+
const lineIndex = node.startPosition.row + i;
143+
const line = lines[i];
144+
145+
// Skip empty lines and pure comment lines
146+
if (emptyLines.has(lineIndex) || pureCommentLines.has(lineIndex)) {
147+
continue;
148+
}
149+
150+
// Process line for code characters
151+
let lineText = line;
152+
153+
// Remove comment content from the line if present
154+
for (const span of commentSpans) {
155+
if (span.start.row === lineIndex) {
156+
// Comment starts on this line
157+
lineText = lineText.substring(0, span.start.column);
158+
}
159+
}
160+
161+
// Count normalized code characters (trim excessive whitespace)
162+
const normalizedText = lineText.trim().replace(/\s+/g, " ");
163+
codeCharCount += normalizedText.length;
164+
}
165+
166+
return {
167+
lines: codeLinesCount,
168+
characters: codeCharCount,
169+
};
170+
}
171+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Parser from "tree-sitter";
2+
import { javaParser } from "../../../helpers/treeSitter/parsers.ts";
3+
4+
// Tree-sitter query to find complexity-related nodes
5+
export const JAVA_COMPLEXITY_QUERY = new Parser.Query(
6+
javaParser.getLanguage(),
7+
`
8+
(if_statement) @complexity
9+
(while_statement) @complexity
10+
(for_statement) @complexity
11+
(do_statement) @complexity
12+
(switch_block_statement_group) @complexity
13+
(ternary_expression) @complexity
14+
`,
15+
);
16+
17+
export const JAVA_COMMENT_QUERY = new Parser.Query(
18+
javaParser.getLanguage(),
19+
`
20+
(comment) @comment
21+
`,
22+
);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* Interface for code volumes
3+
*/
4+
export interface CodeCounts {
5+
/** Number of lines of code */
6+
lines: number;
7+
/** Number of characters of code */
8+
characters: number;
9+
}
10+
11+
export interface CommentSpan {
12+
start: { row: number; column: number };
13+
end: { row: number; column: number };
14+
}
15+
16+
/**
17+
* Represents complexity metrics for a C symbol
18+
*/
19+
export interface JavaComplexityMetrics {
20+
/** Cyclomatic complexity (McCabe complexity) */
21+
cyclomaticComplexity: number;
22+
/** Code lines (not including whitespace or comments) */
23+
codeLinesCount: number;
24+
/** Total lines (including whitespace and comments) */
25+
linesCount: number;
26+
/** Characters of actual code (excluding comments and excessive whitespace) */
27+
codeCharacterCount: number;
28+
/** Total characters in the entire symbol */
29+
characterCount: number;
30+
}

src/manifest/dependencyManifest/java/index.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import {
1313
import { JavaDependencyFormatter } from "../../../languagePlugins/java/dependencyFormatting/index.ts";
1414
// import { JavaMetricsAnalyzer } from "../../../languagePlugins/java/metrics/index.ts";
1515
import { javaLanguage } from "../../../helpers/treeSitter/parsers.ts";
16+
import { JavaMetricsAnalyzer } from "../../../languagePlugins/java/metrics/index.ts";
1617

1718
export function generateJavaDependencyManifest(
1819
files: Map<string, { path: string; content: string }>,
1920
): DependencyManifest {
2021
console.time("generateJavaDependencyManifest");
2122
console.info("Processing project...");
2223
const formatter = new JavaDependencyFormatter(files);
24+
const metricsAnalyzer = new JavaMetricsAnalyzer();
2325
const manifest: DependencyManifest = {};
2426
const filecount = files.size;
2527
let i = 0;
@@ -31,34 +33,36 @@ export function generateJavaDependencyManifest(
3133
for (const [symName, symbol] of Object.entries(cSyms)) {
3234
const symType = symbol.type;
3335
const dependencies = symbol.dependencies;
36+
const metrics = metricsAnalyzer.analyzeNode(symbol.node);
3437
symbols[symName] = {
3538
id: symName,
3639
type: symType as SymbolType,
3740
metrics: {
38-
[metricCharacterCount]: symbol.characterCount,
39-
[metricCodeCharacterCount]: 0, // TODO : metrics
40-
[metricLinesCount]: symbol.lineCount,
41-
[metricCodeLineCount]: 0, // TODO : metrics
41+
[metricCharacterCount]: metrics.characterCount,
42+
[metricCodeCharacterCount]: metrics.codeCharacterCount,
43+
[metricLinesCount]: metrics.linesCount,
44+
[metricCodeLineCount]: metrics.codeLinesCount,
4245
[metricDependencyCount]: Object.keys(dependencies).length,
4346
[metricDependentCount]: 0,
44-
[metricCyclomaticComplexity]: 0, // TODO : metrics
47+
[metricCyclomaticComplexity]: metrics.cyclomaticComplexity,
4548
},
4649
dependencies: dependencies,
4750
dependents: {},
4851
};
4952
}
53+
const metrics = metricsAnalyzer.analyzeNode(fm.rootNode);
5054
manifest[path] = {
5155
id: fm.id,
5256
filePath: fm.filePath,
5357
language: javaLanguage,
5458
metrics: {
55-
[metricCharacterCount]: fm.characterCount,
56-
[metricCodeCharacterCount]: 0, // TODO : metrics
57-
[metricLinesCount]: fm.lineCount,
58-
[metricCodeLineCount]: 0, // TODO : metrics
59+
[metricCharacterCount]: metrics.characterCount,
60+
[metricCodeCharacterCount]: metrics.codeCharacterCount,
61+
[metricLinesCount]: metrics.linesCount,
62+
[metricCodeLineCount]: metrics.codeLinesCount,
5963
[metricDependencyCount]: Object.keys(fm.dependencies).length,
6064
[metricDependentCount]: 0,
61-
[metricCyclomaticComplexity]: 0, // TODO : metrics
65+
[metricCyclomaticComplexity]: metrics.cyclomaticComplexity,
6266
},
6367
dependencies: fm.dependencies,
6468
symbols: symbols,

0 commit comments

Comments
 (0)