circle-ir 3.12.0 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/analysis/passes/blocking-main-thread-pass.d.ts +40 -0
  2. package/dist/analysis/passes/blocking-main-thread-pass.js +112 -0
  3. package/dist/analysis/passes/blocking-main-thread-pass.js.map +1 -0
  4. package/dist/analysis/passes/excessive-allocation-pass.d.ts +29 -0
  5. package/dist/analysis/passes/excessive-allocation-pass.js +85 -0
  6. package/dist/analysis/passes/excessive-allocation-pass.js.map +1 -0
  7. package/dist/analysis/passes/feature-envy-pass.d.ts +54 -0
  8. package/dist/analysis/passes/feature-envy-pass.js +132 -0
  9. package/dist/analysis/passes/feature-envy-pass.js.map +1 -0
  10. package/dist/analysis/passes/god-class-pass.d.ts +58 -0
  11. package/dist/analysis/passes/god-class-pass.js +197 -0
  12. package/dist/analysis/passes/god-class-pass.js.map +1 -0
  13. package/dist/analysis/passes/missing-guard-dom-pass.d.ts +18 -0
  14. package/dist/analysis/passes/missing-guard-dom-pass.js +18 -0
  15. package/dist/analysis/passes/missing-guard-dom-pass.js.map +1 -1
  16. package/dist/analysis/passes/missing-stream-pass.d.ts +28 -0
  17. package/dist/analysis/passes/missing-stream-pass.js +173 -0
  18. package/dist/analysis/passes/missing-stream-pass.js.map +1 -0
  19. package/dist/analysis/passes/naming-convention-pass.d.ts +62 -0
  20. package/dist/analysis/passes/naming-convention-pass.js +169 -0
  21. package/dist/analysis/passes/naming-convention-pass.js.map +1 -0
  22. package/dist/analysis/passes/serial-await-pass.js +3 -2
  23. package/dist/analysis/passes/serial-await-pass.js.map +1 -1
  24. package/dist/analyzer.d.ts +28 -12
  25. package/dist/analyzer.js +30 -14
  26. package/dist/analyzer.js.map +1 -1
  27. package/dist/browser/circle-ir.js +635 -91
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/package.json +5 -5
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pass #83: blocking-main-thread (CWE-1050, category: performance)
3
+ *
4
+ * Detects synchronous/blocking operations inside HTTP request handlers
5
+ * that stall the Node.js event loop, degrading latency under load.
6
+ *
7
+ * Scope: JavaScript / TypeScript only.
8
+ *
9
+ * Differentiation from SyncIoAsyncPass (#48):
10
+ * SyncIoAsyncPass catches *Sync calls inside any `async` function.
11
+ * This pass focuses specifically on request handler context
12
+ * (NestJS/Express/Koa/Fastify/Hono) and includes expensive crypto/hashing
13
+ * operations that are particularly harmful in synchronous handlers.
14
+ *
15
+ * Detection strategy:
16
+ * 1. Identify request handler methods by:
17
+ * a. HTTP method decorators in annotations (Get, Post, Put, Patch, Delete)
18
+ * b. Common handler parameter names (req, res, ctx, c)
19
+ * c. Conventional handler method names (handle, handler)
20
+ * 2. Within those method ranges, scan graph.ir.calls for:
21
+ * a. Blocking *Sync calls (readFileSync, execSync, spawnSync, etc.)
22
+ * b. Synchronous crypto operations (createHash, hashSync, pbkdf2Sync,
23
+ * scryptSync, generateKeyPairSync)
24
+ * 3. Emit one warning per blocking call site.
25
+ */
26
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
27
+ export interface BlockingMainThreadResult {
28
+ blockingInHandlers: Array<{
29
+ line: number;
30
+ method: string;
31
+ handler: string;
32
+ reason: 'sync-suffix' | 'crypto';
33
+ }>;
34
+ }
35
+ export declare class BlockingMainThreadPass implements AnalysisPass<BlockingMainThreadResult> {
36
+ readonly name = "blocking-main-thread";
37
+ readonly category: "performance";
38
+ run(ctx: PassContext): BlockingMainThreadResult;
39
+ private isRequestHandler;
40
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Pass #83: blocking-main-thread (CWE-1050, category: performance)
3
+ *
4
+ * Detects synchronous/blocking operations inside HTTP request handlers
5
+ * that stall the Node.js event loop, degrading latency under load.
6
+ *
7
+ * Scope: JavaScript / TypeScript only.
8
+ *
9
+ * Differentiation from SyncIoAsyncPass (#48):
10
+ * SyncIoAsyncPass catches *Sync calls inside any `async` function.
11
+ * This pass focuses specifically on request handler context
12
+ * (NestJS/Express/Koa/Fastify/Hono) and includes expensive crypto/hashing
13
+ * operations that are particularly harmful in synchronous handlers.
14
+ *
15
+ * Detection strategy:
16
+ * 1. Identify request handler methods by:
17
+ * a. HTTP method decorators in annotations (Get, Post, Put, Patch, Delete)
18
+ * b. Common handler parameter names (req, res, ctx, c)
19
+ * c. Conventional handler method names (handle, handler)
20
+ * 2. Within those method ranges, scan graph.ir.calls for:
21
+ * a. Blocking *Sync calls (readFileSync, execSync, spawnSync, etc.)
22
+ * b. Synchronous crypto operations (createHash, hashSync, pbkdf2Sync,
23
+ * scryptSync, generateKeyPairSync)
24
+ * 3. Emit one warning per blocking call site.
25
+ */
26
+ /** HTTP method decorator names (NestJS / Fastify / express-style, without the @ prefix). */
27
+ const HTTP_DECORATORS = new Set([
28
+ 'Get', 'Post', 'Put', 'Patch', 'Delete', 'All', 'Options', 'Head',
29
+ 'Route', 'Handler',
30
+ ]);
31
+ /** Parameter names that indicate an HTTP request handler. */
32
+ const HANDLER_PARAM_NAMES = new Set([
33
+ 'req', 'res', 'request', 'response', 'ctx', 'c', 'event',
34
+ ]);
35
+ /** Method names that strongly suggest HTTP request handling. */
36
+ const HANDLER_METHOD_NAMES = new Set([
37
+ 'handle', 'handler', 'dispatch', 'invoke', 'serve',
38
+ ]);
39
+ /** Synchronous crypto operations that are expensive in the request path. */
40
+ const CRYPTO_BLOCKING_METHODS = new Set([
41
+ 'createHash', 'hashSync', 'pbkdf2Sync', 'scryptSync',
42
+ 'generateKeyPairSync', 'generateKeySync', 'deriveKeySync',
43
+ ]);
44
+ const SYNC_SUFFIX_RE = /Sync$/;
45
+ export class BlockingMainThreadPass {
46
+ name = 'blocking-main-thread';
47
+ category = 'performance';
48
+ run(ctx) {
49
+ const { graph, language } = ctx;
50
+ if (language !== 'javascript' && language !== 'typescript') {
51
+ return { blockingInHandlers: [] };
52
+ }
53
+ const file = graph.ir.meta.file;
54
+ // Collect request handler method line ranges
55
+ const handlerRanges = [];
56
+ for (const type of graph.ir.types) {
57
+ for (const method of type.methods) {
58
+ if (this.isRequestHandler(method)) {
59
+ handlerRanges.push({
60
+ start: method.start_line,
61
+ end: method.end_line,
62
+ name: method.name,
63
+ });
64
+ }
65
+ }
66
+ }
67
+ if (handlerRanges.length === 0)
68
+ return { blockingInHandlers: [] };
69
+ const blockingInHandlers = [];
70
+ for (const call of graph.ir.calls) {
71
+ const name = call.method_name;
72
+ const isCrypto = CRYPTO_BLOCKING_METHODS.has(name);
73
+ const isSyncSuffix = SYNC_SUFFIX_RE.test(name);
74
+ if (!isCrypto && !isSyncSuffix)
75
+ continue;
76
+ const line = call.location.line;
77
+ const range = handlerRanges.find(r => line >= r.start && line <= r.end);
78
+ if (!range)
79
+ continue;
80
+ const reason = isCrypto ? 'crypto' : 'sync-suffix';
81
+ blockingInHandlers.push({ line, method: name, handler: range.name, reason });
82
+ ctx.addFinding({
83
+ id: `blocking-main-thread-${file}-${line}`,
84
+ pass: this.name,
85
+ category: this.category,
86
+ rule_id: this.name,
87
+ cwe: 'CWE-1050',
88
+ severity: 'medium',
89
+ level: 'warning',
90
+ message: `Blocking call \`${name}()\` inside request handler '${range.name}' ` +
91
+ `stalls the event loop under concurrent load`,
92
+ file,
93
+ line,
94
+ fix: 'Move to an async equivalent or offload to a worker thread',
95
+ evidence: { handler: range.name, blocking_method: name, reason },
96
+ });
97
+ }
98
+ return { blockingInHandlers };
99
+ }
100
+ isRequestHandler(method) {
101
+ // NestJS / Fastify HTTP decorators
102
+ if (method.annotations.some(a => HTTP_DECORATORS.has(a)))
103
+ return true;
104
+ // Conventional handler method names
105
+ if (HANDLER_METHOD_NAMES.has(method.name))
106
+ return true;
107
+ // Express/Koa/Hono request handler patterns: (req, res) or (ctx)
108
+ const paramNames = method.parameters.map(p => p.name.toLowerCase());
109
+ return paramNames.some(n => HANDLER_PARAM_NAMES.has(n));
110
+ }
111
+ }
112
+ //# sourceMappingURL=blocking-main-thread-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocking-main-thread-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/blocking-main-thread-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Pass #84: excessive-allocation (CWE-770, category: performance)
3
+ *
4
+ * Detects collection/object allocations inside loop bodies that
5
+ * create unnecessary GC pressure and slow down hot paths.
6
+ * Each allocation forces the garbage collector to do extra work,
7
+ * degrading throughput in tight loops.
8
+ *
9
+ * Detection strategy:
10
+ * 1. Identify loop body line ranges via graph.loopBodies().
11
+ * 2. For each line within a loop body, scan source text for
12
+ * language-specific allocation patterns.
13
+ * 3. Skip lines with explicit reuse signals (pool, cache, preallocat).
14
+ * 4. Emit one warning per allocation site.
15
+ *
16
+ * Languages: Java, JavaScript/TypeScript, Python, Rust. Bash — skipped.
17
+ */
18
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
19
+ export interface ExcessiveAllocationResult {
20
+ allocationsInLoops: Array<{
21
+ line: number;
22
+ pattern: string;
23
+ }>;
24
+ }
25
+ export declare class ExcessiveAllocationPass implements AnalysisPass<ExcessiveAllocationResult> {
26
+ readonly name = "excessive-allocation";
27
+ readonly category: "performance";
28
+ run(ctx: PassContext): ExcessiveAllocationResult;
29
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Pass #84: excessive-allocation (CWE-770, category: performance)
3
+ *
4
+ * Detects collection/object allocations inside loop bodies that
5
+ * create unnecessary GC pressure and slow down hot paths.
6
+ * Each allocation forces the garbage collector to do extra work,
7
+ * degrading throughput in tight loops.
8
+ *
9
+ * Detection strategy:
10
+ * 1. Identify loop body line ranges via graph.loopBodies().
11
+ * 2. For each line within a loop body, scan source text for
12
+ * language-specific allocation patterns.
13
+ * 3. Skip lines with explicit reuse signals (pool, cache, preallocat).
14
+ * 4. Emit one warning per allocation site.
15
+ *
16
+ * Languages: Java, JavaScript/TypeScript, Python, Rust. Bash — skipped.
17
+ */
18
+ /** Allocation patterns keyed by language. */
19
+ const ALLOC_PATTERNS = {
20
+ javascript: /\bnew\s+(Array|Map|Set|Object|WeakMap|WeakSet|Error|RegExp|Date|Buffer|Uint8Array|Int8Array|Float32Array|ArrayBuffer)\s*[(<]|\bArray\.from\s*\(|\bstructuredClone\s*\(|\bObject\.create\s*\(/,
21
+ typescript: /\bnew\s+(Array|Map|Set|Object|WeakMap|WeakSet|Error|RegExp|Date|Buffer|Uint8Array|Int8Array|Float32Array|ArrayBuffer)\s*[(<]|\bArray\.from\s*\(|\bstructuredClone\s*\(|\bObject\.create\s*\(/,
22
+ java: /\bnew\s+(ArrayList|HashMap|HashSet|LinkedList|TreeMap|TreeSet|PriorityQueue|ArrayDeque|StringBuilder|StringBuffer|CopyOnWriteArrayList|ConcurrentHashMap)\s*[(<]|\bnew\s+\w[\w.<>]*\[\s*[a-zA-Z]\w*/,
23
+ python: /\b(list|dict|set|tuple|bytearray|defaultdict|OrderedDict|Counter|deque)\s*\(\s*\)|\[\s*\]|\{\s*\}(?!\s*[}\]])/,
24
+ rust: /\b(Vec|HashMap|HashSet|BTreeMap|BTreeSet|VecDeque|LinkedList|String|Box|Rc|Arc)\s*::\s*new\s*\(/,
25
+ };
26
+ /** Signals that the allocation is intentional / is a reuse pattern. */
27
+ const BENIGN_RE = /\bpool\b|\bcache\b|\breuse\b|\bpreallocat|\brecycl/i;
28
+ export class ExcessiveAllocationPass {
29
+ name = 'excessive-allocation';
30
+ category = 'performance';
31
+ run(ctx) {
32
+ const { graph, code, language } = ctx;
33
+ if (language === 'bash') {
34
+ return { allocationsInLoops: [] };
35
+ }
36
+ const pattern = ALLOC_PATTERNS[language];
37
+ if (!pattern)
38
+ return { allocationsInLoops: [] };
39
+ const loops = graph.loopBodies();
40
+ if (loops.length === 0)
41
+ return { allocationsInLoops: [] };
42
+ const file = graph.ir.meta.file;
43
+ const codeLines = code.split('\n');
44
+ const allocationsInLoops = [];
45
+ const reported = new Set();
46
+ for (const loop of loops) {
47
+ for (let ln = loop.start_line; ln <= loop.end_line; ln++) {
48
+ if (reported.has(ln))
49
+ continue;
50
+ const src = codeLines[ln - 1] ?? '';
51
+ const match = pattern.exec(src);
52
+ if (!match)
53
+ continue;
54
+ if (BENIGN_RE.test(src))
55
+ continue;
56
+ // Extract a short label for the allocation pattern
57
+ const allocLabel = match[0].replace(/\s+/g, ' ').trim();
58
+ allocationsInLoops.push({ line: ln, pattern: allocLabel });
59
+ reported.add(ln);
60
+ ctx.addFinding({
61
+ id: `excessive-allocation-${file}-${ln}`,
62
+ pass: this.name,
63
+ category: this.category,
64
+ rule_id: this.name,
65
+ cwe: 'CWE-770',
66
+ severity: 'medium',
67
+ level: 'warning',
68
+ message: `Repeated allocation inside loop (lines ${loop.start_line}–${loop.end_line}): ` +
69
+ `\`${allocLabel}\` creates GC pressure on every iteration`,
70
+ file,
71
+ line: ln,
72
+ snippet: src.trim(),
73
+ fix: 'Pre-allocate outside the loop and reset/reuse the collection each iteration',
74
+ evidence: {
75
+ allocation: allocLabel,
76
+ loop_start: loop.start_line,
77
+ loop_end: loop.end_line,
78
+ },
79
+ });
80
+ }
81
+ }
82
+ return { allocationsInLoops };
83
+ }
84
+ }
85
+ //# sourceMappingURL=excessive-allocation-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excessive-allocation-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/excessive-allocation-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Pass #87: feature-envy (CWE-1060, category: architecture)
3
+ *
4
+ * @deprecated NOT REGISTERED IN THE DEFAULT PIPELINE
5
+ *
6
+ * This pass was removed from the default AnalysisPipeline in v3.14.0 because
7
+ * the call-count heuristic (external_max ≥ 4 AND margin > 2) fires trivially
8
+ * on legitimate delegation patterns — facades, controllers, service classes —
9
+ * and its fix suggestion ("move this method to OtherClass") is incorrect when
10
+ * the method's design intent is orchestration rather than feature envy.
11
+ * Confirming true feature envy requires understanding design intent, which is
12
+ * LLM territory.
13
+ *
14
+ * The raw signals this pass relies on are already present in CircleIR:
15
+ * • ir.calls — per-callsite receiver, receiver_type, location.line
16
+ * • ir.types — per-method start_line / end_line to scope the calls
17
+ *
18
+ * This file is retained so that circle-ir-ai can consume the per-method
19
+ * call-count breakdown and apply semantic reasoning to distinguish genuine
20
+ * feature envy from intentional delegation.
21
+ *
22
+ * Detects methods that call another class's methods far more often than
23
+ * their own class's — a sign that the method "envies" the other class
24
+ * and should probably be moved there.
25
+ *
26
+ * Detection strategy:
27
+ * 1. For each method in each class, collect all call sites in its line range.
28
+ * 2. Separate calls into:
29
+ * internal — receiver is 'this' / 'self' / null (own class)
30
+ * external — receiver_type != own class name (other classes)
31
+ * 3. Find the external type with the most calls (external_max).
32
+ * 4. Emit a note when:
33
+ * external_max >= 4 (at least 4 calls to another class)
34
+ * AND
35
+ * external_max > internal + 2 (clearly prefers the other class)
36
+ *
37
+ * Languages: Java, TypeScript, Python. Bash / Rust — skipped.
38
+ */
39
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
40
+ export interface FeatureEnvyResult {
41
+ envyMethods: Array<{
42
+ className: string;
43
+ methodName: string;
44
+ line: number;
45
+ enviedClass: string;
46
+ externalCalls: number;
47
+ internalCalls: number;
48
+ }>;
49
+ }
50
+ export declare class FeatureEnvyPass implements AnalysisPass<FeatureEnvyResult> {
51
+ readonly name = "feature-envy";
52
+ readonly category: "architecture";
53
+ run(ctx: PassContext): FeatureEnvyResult;
54
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Pass #87: feature-envy (CWE-1060, category: architecture)
3
+ *
4
+ * @deprecated NOT REGISTERED IN THE DEFAULT PIPELINE
5
+ *
6
+ * This pass was removed from the default AnalysisPipeline in v3.14.0 because
7
+ * the call-count heuristic (external_max ≥ 4 AND margin > 2) fires trivially
8
+ * on legitimate delegation patterns — facades, controllers, service classes —
9
+ * and its fix suggestion ("move this method to OtherClass") is incorrect when
10
+ * the method's design intent is orchestration rather than feature envy.
11
+ * Confirming true feature envy requires understanding design intent, which is
12
+ * LLM territory.
13
+ *
14
+ * The raw signals this pass relies on are already present in CircleIR:
15
+ * • ir.calls — per-callsite receiver, receiver_type, location.line
16
+ * • ir.types — per-method start_line / end_line to scope the calls
17
+ *
18
+ * This file is retained so that circle-ir-ai can consume the per-method
19
+ * call-count breakdown and apply semantic reasoning to distinguish genuine
20
+ * feature envy from intentional delegation.
21
+ *
22
+ * Detects methods that call another class's methods far more often than
23
+ * their own class's — a sign that the method "envies" the other class
24
+ * and should probably be moved there.
25
+ *
26
+ * Detection strategy:
27
+ * 1. For each method in each class, collect all call sites in its line range.
28
+ * 2. Separate calls into:
29
+ * internal — receiver is 'this' / 'self' / null (own class)
30
+ * external — receiver_type != own class name (other classes)
31
+ * 3. Find the external type with the most calls (external_max).
32
+ * 4. Emit a note when:
33
+ * external_max >= 4 (at least 4 calls to another class)
34
+ * AND
35
+ * external_max > internal + 2 (clearly prefers the other class)
36
+ *
37
+ * Languages: Java, TypeScript, Python. Bash / Rust — skipped.
38
+ */
39
+ /** Minimum external calls to flag feature envy (avoids trivial utility calls). */
40
+ const MIN_EXTERNAL_CALLS = 4;
41
+ /** How many more calls to external type than own type to flag. */
42
+ const ENVY_MARGIN = 2;
43
+ export class FeatureEnvyPass {
44
+ name = 'feature-envy';
45
+ category = 'architecture';
46
+ run(ctx) {
47
+ const { graph, language } = ctx;
48
+ if (language === 'bash' || language === 'rust') {
49
+ return { envyMethods: [] };
50
+ }
51
+ const file = graph.ir.meta.file;
52
+ const envyMethods = [];
53
+ for (const type of graph.ir.types) {
54
+ if (type.kind !== 'class')
55
+ continue;
56
+ const ownName = type.name;
57
+ for (const method of type.methods) {
58
+ const start = method.start_line;
59
+ const end = method.end_line;
60
+ // Collect calls within this method's line range
61
+ const callsInMethod = graph.ir.calls.filter(c => c.location.line >= start && c.location.line <= end);
62
+ if (callsInMethod.length === 0)
63
+ continue;
64
+ let internalCalls = 0;
65
+ const externalCallCounts = new Map();
66
+ for (const call of callsInMethod) {
67
+ const receiver = call.receiver?.toLowerCase();
68
+ const receiverType = call.receiver_type;
69
+ // Internal: receiver is 'this'/'self', null, or the own type
70
+ if (receiver == null ||
71
+ receiver === 'this' ||
72
+ receiver === 'self' ||
73
+ receiverType == null ||
74
+ receiverType === ownName) {
75
+ internalCalls++;
76
+ continue;
77
+ }
78
+ // External: strip generic parameters from receiver_type
79
+ const extType = receiverType.replace(/<.*>/, '').trim();
80
+ if (extType && extType !== ownName) {
81
+ externalCallCounts.set(extType, (externalCallCounts.get(extType) ?? 0) + 1);
82
+ }
83
+ }
84
+ if (externalCallCounts.size === 0)
85
+ continue;
86
+ // Find the most-called external type
87
+ let enviedClass = '';
88
+ let externalMax = 0;
89
+ for (const [typeName, count] of externalCallCounts) {
90
+ if (count > externalMax) {
91
+ externalMax = count;
92
+ enviedClass = typeName;
93
+ }
94
+ }
95
+ if (externalMax < MIN_EXTERNAL_CALLS)
96
+ continue;
97
+ if (externalMax <= internalCalls + ENVY_MARGIN)
98
+ continue;
99
+ envyMethods.push({
100
+ className: ownName,
101
+ methodName: method.name,
102
+ line: method.start_line,
103
+ enviedClass,
104
+ externalCalls: externalMax,
105
+ internalCalls,
106
+ });
107
+ ctx.addFinding({
108
+ id: `feature-envy-${file}-${method.start_line}`,
109
+ pass: this.name,
110
+ category: this.category,
111
+ rule_id: this.name,
112
+ cwe: 'CWE-1060',
113
+ severity: 'low',
114
+ level: 'note',
115
+ message: `Feature Envy: \`${ownName}.${method.name}()\` makes ${externalMax} calls to ` +
116
+ `\`${enviedClass}\` vs ${internalCalls} calls to own class`,
117
+ file,
118
+ line: method.start_line,
119
+ fix: `Consider moving \`${method.name}\` to \`${enviedClass}\`, ` +
120
+ `or introducing a collaborator object`,
121
+ evidence: {
122
+ envied_class: enviedClass,
123
+ external_calls: externalMax,
124
+ internal_calls: internalCalls,
125
+ },
126
+ });
127
+ }
128
+ }
129
+ return { envyMethods };
130
+ }
131
+ }
132
+ //# sourceMappingURL=feature-envy-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-envy-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/feature-envy-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Pass #86: god-class (CWE-1060, category: architecture)
3
+ *
4
+ * Detects "God Class" anti-pattern: a class that does too much, is poorly
5
+ * cohesive, and is heavily coupled to external types. These classes are hard
6
+ * to test, maintain, and evolve.
7
+ *
8
+ * Three CK metrics are computed inline (the metrics pipeline runs separately
9
+ * and its results are not available here):
10
+ *
11
+ * WMC — Weighted Methods per Class: Σ v(G) per method, where v(G) is
12
+ * McCabe cyclomatic complexity = CFG edges − nodes + 2.
13
+ * Fallback = 1 per method when CFG data is absent.
14
+ *
15
+ * LCOM2 — Lack of Cohesion of Methods (0–1 scale):
16
+ * (P − Q) / max(1, m*(m−1)/2)
17
+ * P = method pairs sharing no fields, Q = pairs sharing ≥1 field.
18
+ * Field access is inferred from DFG defs/uses whose variable name
19
+ * matches a declared field name (name-match heuristic).
20
+ *
21
+ * CBO — Coupling Between Objects: count of distinct external type names
22
+ * referenced in parameter types, field types, or call receiver_type
23
+ * within the class, excluding primitives and same-class references.
24
+ *
25
+ * A finding is emitted when at least 2 of the 3 thresholds are exceeded:
26
+ * WMC > 47 (SonarQube default)
27
+ * LCOM2 > 0.8
28
+ * CBO > 14 (SATD threshold)
29
+ *
30
+ * Languages: Java, TypeScript, Python. Bash / Rust — skipped.
31
+ */
32
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
33
+ export interface GodClassResult {
34
+ godClasses: Array<{
35
+ className: string;
36
+ line: number;
37
+ wmc: number;
38
+ lcom2: number;
39
+ cbo: number;
40
+ }>;
41
+ }
42
+ export declare class GodClassPass implements AnalysisPass<GodClassResult> {
43
+ readonly name = "god-class";
44
+ readonly category: "architecture";
45
+ run(ctx: PassContext): GodClassResult;
46
+ /** Compute WMC = Σ v(G) for all methods. v(G) = edges − nodes + 2. */
47
+ private computeWMC;
48
+ /**
49
+ * Compute LCOM2 = (P − Q) / max(1, m*(m−1)/2) clamped to [0, 1].
50
+ * Uses DFG variable names intersected with declared field names.
51
+ */
52
+ private computeLCOM2;
53
+ /**
54
+ * Compute CBO = count of distinct external type names referenced in the class.
55
+ * Sources: call receiver_type, method parameter types, field types.
56
+ */
57
+ private computeCBO;
58
+ }