Skip to content
Open
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
8 changes: 7 additions & 1 deletion src/metricsAnalyzer/languages/jsLikeAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ export class JsLikeMetricsAnalyzer {
const isFunctionNode =
node.type === "function_declaration" ||
node.type === "function_expression" ||
node.type === "generator_function_declaration" ||
node.type === "generator_function" ||
node.type === "method_definition" ||
node.type === "arrow_function";

Expand Down Expand Up @@ -164,6 +166,8 @@ export class JsLikeMetricsAnalyzer {
return "arrow function (nested)";
case "function_expression":
return "function expression (nested)";
case "generator_function":
return "generator function expression (nested)";
case "method_definition":
return "method (nested)";
default:
Expand All @@ -178,7 +182,8 @@ export class JsLikeMetricsAnalyzer {
* @returns The function name or a descriptive placeholder
*/
private getFunctionName(node: Parser.SyntaxNode): string {
if (node.type === "function_declaration" || node.type === "function_expression") {
if (node.type === "function_declaration" || node.type === "function_expression" ||
node.type === "generator_function_declaration" || node.type === "generator_function") {
// Use childForFieldName for O(1) field lookup (tree-sitter exposes "name" for both node types)
const nameNode = node.childForFieldName("name");
if (nameNode) {
Expand Down Expand Up @@ -325,6 +330,7 @@ export class JsLikeMetricsAnalyzer {
private isNestedFunction(node: Parser.SyntaxNode): boolean {
return (
node.type === "function_expression" ||
node.type === "generator_function" ||
node.type === "arrow_function" ||
node.type === "method_definition"
);
Expand Down
86 changes: 86 additions & 0 deletions src/unit/unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2924,4 +2924,90 @@ public abstract class Shape {
assert.strictEqual(results[0].complexity, 0, "toString has no control flow");
});
});

describe("JS/TS Generator Function Coverage", () => {
it("should collect a top-level generator function declaration", () => {
const sourceCode = `
function* counter() {
let i = 0;
while (true) {
yield i++;
}
}
`;
const results = JavaScriptMetricsAnalyzer.analyzeFile(sourceCode);
assert.strictEqual(results.length, 1, "generator function should be collected");
assert.strictEqual(results[0].name, "counter");
assert.strictEqual(results[0].complexity, 1, "while loop adds 1");
});

it("should collect an async generator function declaration", () => {
const sourceCode = `
async function* asyncCounter() {
if (true) {
yield 1;
}
}
`;
const results = JavaScriptMetricsAnalyzer.analyzeFile(sourceCode);
assert.strictEqual(results.length, 1, "async generator should be collected");
assert.strictEqual(results[0].name, "asyncCounter");
assert.strictEqual(results[0].complexity, 1, "if adds 1");
});

it("should collect a named generator function expression", () => {
const sourceCode = `
const myGen = function* namedGen() {
if (true) { yield 1; }
};
`;
const results = JavaScriptMetricsAnalyzer.analyzeFile(sourceCode);
assert.strictEqual(results.length, 1, "named generator expression should be collected");
assert.strictEqual(results[0].name, "namedGen");
});

it("should collect an anonymous generator function expression", () => {
const sourceCode = `
const gen = function* () {
yield 1;
};
`;
const results = JavaScriptMetricsAnalyzer.analyzeFile(sourceCode);
assert.strictEqual(results.length, 1, "anonymous generator expression should be collected");
assert.ok(
results[0].name === "(anonymous)" || results[0].name === "gen",
"anonymous generator should have a placeholder name"
);
});

it("should apply nesting penalty to a generator function expression nested inside another function", () => {
const sourceCode = `
function outer() {
const gen = function* () {
if (true) { yield 1; }
};
}
`;
const results = JavaScriptMetricsAnalyzer.analyzeFile(sourceCode);
assert.strictEqual(results.length, 1, "only outer should be top-level");
// nested generator adds +1 (for the nesting penalty)
// the if inside the generator adds +1 base + 1 nesting = 2
// total outer = 1 (nested generator) + 2 (if inside) = 3
assert.ok(results[0].complexity >= 1, "nested generator should contribute complexity to outer");
Comment on lines +2993 to +2996
});

it("should collect a TypeScript generator function", () => {
const sourceCode = `
function* tsGen(): Generator<number> {
for (let i = 0; i < 3; i++) {
yield i;
}
}
`;
const results = TypeScriptMetricsAnalyzer.analyzeFile(sourceCode);
assert.strictEqual(results.length, 1, "TS generator should be collected");
assert.strictEqual(results[0].name, "tsGen");
assert.strictEqual(results[0].complexity, 1, "for loop adds 1");
});
});
});
Loading