@untangle-code/complexity 0.0.0-stage → 0.1.1
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.
- package/LICENSE +661 -0
- package/NOTICE +90 -0
- package/README.md +235 -2
- package/THIRD_PARTY_LICENSES.md +24 -0
- package/dist/aggregator.d.ts +28 -0
- package/dist/aggregator.d.ts.map +1 -0
- package/dist/aggregator.js +77 -0
- package/dist/aggregator.js.map +1 -0
- package/dist/assemble.d.ts +50 -0
- package/dist/assemble.d.ts.map +1 -0
- package/dist/assemble.js +43 -0
- package/dist/assemble.js.map +1 -0
- package/dist/cognitive.d.ts +62 -0
- package/dist/cognitive.d.ts.map +1 -0
- package/dist/cognitive.js +62 -0
- package/dist/cognitive.js.map +1 -0
- package/dist/cyclomatic.d.ts +29 -0
- package/dist/cyclomatic.d.ts.map +1 -0
- package/dist/cyclomatic.js +52 -0
- package/dist/cyclomatic.js.map +1 -0
- package/dist/entropy.d.ts +5 -0
- package/dist/entropy.d.ts.map +1 -0
- package/dist/entropy.js +29 -0
- package/dist/entropy.js.map +1 -0
- package/dist/explain.d.ts +22 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +35 -0
- package/dist/explain.js.map +1 -0
- package/dist/halstead.d.ts +23 -0
- package/dist/halstead.d.ts.map +1 -0
- package/dist/halstead.js +24 -0
- package/dist/halstead.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/build-ir.d.ts +105 -0
- package/dist/ir/build-ir.d.ts.map +1 -0
- package/dist/ir/build-ir.js +267 -0
- package/dist/ir/build-ir.js.map +1 -0
- package/dist/maintainability.d.ts +15 -0
- package/dist/maintainability.d.ts.map +1 -0
- package/dist/maintainability.js +10 -0
- package/dist/maintainability.js.map +1 -0
- package/dist/node-kind-heuristics.d.ts +24 -0
- package/dist/node-kind-heuristics.d.ts.map +1 -0
- package/dist/node-kind-heuristics.js +37 -0
- package/dist/node-kind-heuristics.js.map +1 -0
- package/dist/prose/metrics.d.ts +81 -0
- package/dist/prose/metrics.d.ts.map +1 -0
- package/dist/prose/metrics.js +103 -0
- package/dist/prose/metrics.js.map +1 -0
- package/dist/scope-aggregate.d.ts +229 -0
- package/dist/scope-aggregate.d.ts.map +1 -0
- package/dist/scope-aggregate.js +588 -0
- package/dist/scope-aggregate.js.map +1 -0
- package/dist/scope-identity.d.ts +85 -0
- package/dist/scope-identity.d.ts.map +1 -0
- package/dist/scope-identity.js +144 -0
- package/dist/scope-identity.js.map +1 -0
- package/dist/scope.d.ts +36 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +60 -0
- package/dist/scope.js.map +1 -0
- package/dist/structural.d.ts +30 -0
- package/dist/structural.d.ts.map +1 -0
- package/dist/structural.js +98 -0
- package/dist/structural.js.map +1 -0
- package/dist/syntactic.d.ts +35 -0
- package/dist/syntactic.d.ts.map +1 -0
- package/dist/syntactic.js +143 -0
- package/dist/syntactic.js.map +1 -0
- package/dist/text-lines.d.ts +34 -0
- package/dist/text-lines.d.ts.map +1 -0
- package/dist/text-lines.js +60 -0
- package/dist/text-lines.js.map +1 -0
- package/dist/textual.d.ts +28 -0
- package/dist/textual.d.ts.map +1 -0
- package/dist/textual.js +75 -0
- package/dist/textual.js.map +1 -0
- package/dist/types.d.ts +112 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -3
- package/src/aggregator.ts +105 -0
- package/src/assemble.ts +109 -0
- package/src/cognitive.ts +127 -0
- package/src/cyclomatic.ts +78 -0
- package/src/entropy.ts +30 -0
- package/src/explain.ts +64 -0
- package/src/halstead.ts +54 -0
- package/src/index.ts +85 -0
- package/src/ir/build-ir.ts +411 -0
- package/src/maintainability.ts +29 -0
- package/src/node-kind-heuristics.ts +55 -0
- package/src/prose/metrics.ts +201 -0
- package/src/scope-aggregate.ts +775 -0
- package/src/scope-identity.ts +240 -0
- package/src/scope.ts +77 -0
- package/src/structural.ts +132 -0
- package/src/syntactic.ts +183 -0
- package/src/text-lines.ts +90 -0
- package/src/textual.ts +119 -0
- package/src/types.ts +121 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function contributionKind(node) {
|
|
2
|
+
if (node.decision)
|
|
3
|
+
return node.decision.kind;
|
|
4
|
+
if (node.kind === "recursion_marker")
|
|
5
|
+
return "recursion";
|
|
6
|
+
return node.kind;
|
|
7
|
+
}
|
|
8
|
+
function isContributing(node) {
|
|
9
|
+
return (node.decision != null || node.kind === "logical_sequence" || node.kind === "recursion_marker");
|
|
10
|
+
}
|
|
11
|
+
function walk(node, state, contributions, seenGroups) {
|
|
12
|
+
if (isContributing(node)) {
|
|
13
|
+
const groupId = node.decision?.logicalGroupId;
|
|
14
|
+
const isDuplicateGroupMember = groupId != null && seenGroups.has(groupId);
|
|
15
|
+
if (groupId != null) {
|
|
16
|
+
seenGroups.add(groupId);
|
|
17
|
+
}
|
|
18
|
+
if (!isDuplicateGroupMember) {
|
|
19
|
+
const base = 1;
|
|
20
|
+
const nestingPenalty = node.kind === "recursion_marker" ? 0 : state.depth;
|
|
21
|
+
contributions.push({
|
|
22
|
+
line: node.range.startLine,
|
|
23
|
+
kind: contributionKind(node),
|
|
24
|
+
base,
|
|
25
|
+
nestingPenalty,
|
|
26
|
+
total: base + nestingPenalty,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const childDepth = node.nestingBoundary === true ? state.depth + 1 : state.depth;
|
|
31
|
+
for (const child of node.children) {
|
|
32
|
+
walk(child, { depth: childDepth }, contributions, seenGroups);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @param symbolRoot The `ComplexityIRNode` (kind `"symbol"`) rooting the
|
|
37
|
+
* symbol's own subtree; traversal starts at depth 0.
|
|
38
|
+
* @param algorithm Caller-supplied algorithm id — pass domain's
|
|
39
|
+
* `COGNITIVE_ALGORITHM_ID` in production; kept as a plain parameter (not
|
|
40
|
+
* hardcoded) so this stays a pure function of its inputs.
|
|
41
|
+
*/
|
|
42
|
+
export function computeCognitive(symbolRoot, algorithm) {
|
|
43
|
+
const contributions = [];
|
|
44
|
+
walk(symbolRoot, { depth: 0 }, contributions, new Set());
|
|
45
|
+
const value = contributions.reduce((sum, c) => sum + c.total, 0);
|
|
46
|
+
assertCognitiveInvariant(contributions, value);
|
|
47
|
+
return { value, algorithm, contributions };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Throws when `sum(contributions[].total) !== total`. `computeCognitive`
|
|
51
|
+
* cannot actually violate this (value is defined as that very sum), but the
|
|
52
|
+
* work order asks for an explicit, independently-testable guard — this is
|
|
53
|
+
* also the function a caller re-assembling a snapshot from stored
|
|
54
|
+
* contributions should re-run to catch data corruption.
|
|
55
|
+
*/
|
|
56
|
+
export function assertCognitiveInvariant(contributions, total) {
|
|
57
|
+
const sum = contributions.reduce((s, c) => s + c.total, 0);
|
|
58
|
+
if (sum !== total) {
|
|
59
|
+
throw new Error(`cognitive complexity invariant violated: sum(contributions.total)=${sum} does not equal total=${total}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=cognitive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cognitive.js","sourceRoot":"","sources":["../src/cognitive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CyclomaticCalculator (spec.md §16.2 "Classic", §16.3 "Extended").
|
|
3
|
+
*
|
|
4
|
+
* Classic: `M = 1 + decision_points`, one point per `DecisionFact` whose
|
|
5
|
+
* `countsForClassic` flag is set — the language adapter, not this
|
|
6
|
+
* calculator, decides which decision kinds are classic decision points
|
|
7
|
+
* (spec.md §16.2 lists if/else-if/loops/catch/ternary/case/guards).
|
|
8
|
+
*
|
|
9
|
+
* Extended: `M = 1 + decision_points`, one point per *independent* decision
|
|
10
|
+
* group whose `countsForExtended` flag is set. "Independent group" is where
|
|
11
|
+
* this calculator's own logic lives: `DecisionFact.logicalGroupId` marks
|
|
12
|
+
* facts that belong to the same short-circuit/pattern-alternative group
|
|
13
|
+
* (spec.md §16.3); this calculator counts each group once (first occurrence
|
|
14
|
+
* by source position wins), not once per operator/alternative.
|
|
15
|
+
*/
|
|
16
|
+
import type { ComplexityFacts } from "@untangle-code/domain";
|
|
17
|
+
import type { CyclomaticResult } from "./types.js";
|
|
18
|
+
/**
|
|
19
|
+
* @param facts Facts already narrowed to the target scope (see `selectSymbolFacts`
|
|
20
|
+
* for `scope: "symbol"`; pass a file's unfiltered facts for `scope: "file"`).
|
|
21
|
+
* @param baseLine Source line the synthetic "base" contribution (the `+1` in
|
|
22
|
+
* `M = 1 + decision_points`) is attributed to — typically the scope's own
|
|
23
|
+
* first line.
|
|
24
|
+
* @param algorithmVersion Caller-supplied algorithm/metrics version stamp
|
|
25
|
+
* (e.g. domain's `METRICS_VERSION`); this calculator is a pure function of
|
|
26
|
+
* its inputs and never invents its own version string.
|
|
27
|
+
*/
|
|
28
|
+
export declare function computeCyclomatic(facts: ComplexityFacts, baseLine: number, algorithmVersion: string): CyclomaticResult;
|
|
29
|
+
//# sourceMappingURL=cyclomatic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cyclomatic.d.ts","sourceRoot":"","sources":["../src/cyclomatic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,uBAAuB,CAAC;AAE3E,OAAO,KAAK,EAA0B,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAM3E;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,GACvB,gBAAgB,CAwClB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function sortByPosition(decisions) {
|
|
2
|
+
return [...decisions].sort((a, b) => a.range.startByte - b.range.startByte);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* @param facts Facts already narrowed to the target scope (see `selectSymbolFacts`
|
|
6
|
+
* for `scope: "symbol"`; pass a file's unfiltered facts for `scope: "file"`).
|
|
7
|
+
* @param baseLine Source line the synthetic "base" contribution (the `+1` in
|
|
8
|
+
* `M = 1 + decision_points`) is attributed to — typically the scope's own
|
|
9
|
+
* first line.
|
|
10
|
+
* @param algorithmVersion Caller-supplied algorithm/metrics version stamp
|
|
11
|
+
* (e.g. domain's `METRICS_VERSION`); this calculator is a pure function of
|
|
12
|
+
* its inputs and never invents its own version string.
|
|
13
|
+
*/
|
|
14
|
+
export function computeCyclomatic(facts, baseLine, algorithmVersion) {
|
|
15
|
+
const contributions = [
|
|
16
|
+
{
|
|
17
|
+
line: baseLine,
|
|
18
|
+
kind: "base",
|
|
19
|
+
value: 1,
|
|
20
|
+
countsForClassic: true,
|
|
21
|
+
countsForExtended: true,
|
|
22
|
+
note: "M = 1 + decision points (spec.md §16.2)",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
const seenGroups = new Set();
|
|
26
|
+
for (const decision of sortByPosition(facts.decisions)) {
|
|
27
|
+
if (!decision.countsForClassic && !decision.countsForExtended) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (decision.logicalGroupId != null) {
|
|
31
|
+
if (seenGroups.has(decision.logicalGroupId)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
seenGroups.add(decision.logicalGroupId);
|
|
35
|
+
}
|
|
36
|
+
contributions.push({
|
|
37
|
+
line: decision.range.startLine,
|
|
38
|
+
kind: decision.kind,
|
|
39
|
+
value: 1,
|
|
40
|
+
countsForClassic: decision.countsForClassic,
|
|
41
|
+
countsForExtended: decision.countsForExtended,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const classic = contributions
|
|
45
|
+
.filter((c) => c.countsForClassic)
|
|
46
|
+
.reduce((sum, c) => sum + c.value, 0);
|
|
47
|
+
const extended = contributions
|
|
48
|
+
.filter((c) => c.countsForExtended)
|
|
49
|
+
.reduce((sum, c) => sum + c.value, 0);
|
|
50
|
+
return { classic, extended, contributions, algorithmVersion };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=cyclomatic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cyclomatic.js","sourceRoot":"","sources":["../src/cyclomatic.ts"],"names":[],"mappings":"AAmBA,SAAS,cAAc,CAAC,SAAkC;IACxD,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAsB,EACtB,QAAgB,EAChB,gBAAwB;IAExB,MAAM,aAAa,GAA6B;QAC9C;YACE,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,CAAC;YACR,gBAAgB,EAAE,IAAI;YACtB,iBAAiB,EAAE,IAAI;YACvB,IAAI,EAAE,yCAAyC;SAChD;KACF,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,QAAQ,IAAI,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,gBAAgB,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,CAAC;YAC9D,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,CAAC,cAAc,IAAI,IAAI,EAAE,CAAC;YACpC,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;gBAC5C,SAAS;YACX,CAAC;YACD,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QAC1C,CAAC;QACD,aAAa,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS;YAC9B,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,KAAK,EAAE,CAAC;YACR,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;YAC3C,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,aAAa;SAC1B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC;SACjC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,aAAa;SAC3B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB,CAAC;SAClC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAExC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Shannon entropy in bits over a frequency distribution (spec.md §16.6, §16.8). */
|
|
2
|
+
export declare function shannonEntropy(frequencies: Iterable<number>): number;
|
|
3
|
+
/** Builds a frequency map by counting occurrences of `key(item)` over `items`. */
|
|
4
|
+
export declare function frequenciesOf<T>(items: readonly T[], key: (item: T) => string): Map<string, number>;
|
|
5
|
+
//# sourceMappingURL=entropy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entropy.d.ts","sourceRoot":"","sources":["../src/entropy.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAepE;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,CAAC,EAC7B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GACvB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrB"}
|
package/dist/entropy.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Shannon entropy in bits over a frequency distribution (spec.md §16.6, §16.8). */
|
|
2
|
+
export function shannonEntropy(frequencies) {
|
|
3
|
+
let total = 0;
|
|
4
|
+
const values = [];
|
|
5
|
+
for (const f of frequencies) {
|
|
6
|
+
if (f <= 0)
|
|
7
|
+
continue;
|
|
8
|
+
values.push(f);
|
|
9
|
+
total += f;
|
|
10
|
+
}
|
|
11
|
+
if (total === 0)
|
|
12
|
+
return 0;
|
|
13
|
+
let entropy = 0;
|
|
14
|
+
for (const f of values) {
|
|
15
|
+
const p = f / total;
|
|
16
|
+
entropy -= p * Math.log2(p);
|
|
17
|
+
}
|
|
18
|
+
return entropy;
|
|
19
|
+
}
|
|
20
|
+
/** Builds a frequency map by counting occurrences of `key(item)` over `items`. */
|
|
21
|
+
export function frequenciesOf(items, key) {
|
|
22
|
+
const counts = new Map();
|
|
23
|
+
for (const item of items) {
|
|
24
|
+
const k = key(item);
|
|
25
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
return counts;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=entropy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entropy.js","sourceRoot":"","sources":["../src/entropy.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,UAAU,cAAc,CAAC,WAA6B;IAC1D,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QACrB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACf,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IACD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1B,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACpB,OAAO,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAC3B,KAAmB,EACnB,GAAwB;IAExB,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explain path (work order: "einen Explain-Pfad"). Produces a ranked,
|
|
3
|
+
* human-readable breakdown of a symbol's cyclomatic and cognitive
|
|
4
|
+
* contributions, largest first.
|
|
5
|
+
*
|
|
6
|
+
* Operates on the *stored* `ComplexitySnapshotV1` shape
|
|
7
|
+
* (`cyclomatic.contributors` is `MetricContribution[]`;
|
|
8
|
+
* `cognitive.contributors` is the full `CognitiveContributionV1[]` the
|
|
9
|
+
* snapshot persists per spec.md §16.4), so it works directly on whatever
|
|
10
|
+
* `assembleComplexitySnapshot` produced or on any snapshot loaded back from
|
|
11
|
+
* storage.
|
|
12
|
+
*/
|
|
13
|
+
import type { ComplexitySnapshotV1 } from "@untangle-code/domain";
|
|
14
|
+
import type { ExplainEntry } from "./types.js";
|
|
15
|
+
/**
|
|
16
|
+
* @param snapshot The symbol's complexity snapshot.
|
|
17
|
+
* @param top Maximum number of entries to return (default 5), largest
|
|
18
|
+
* contribution first; ties keep cyclomatic before cognitive, then source
|
|
19
|
+
* order.
|
|
20
|
+
*/
|
|
21
|
+
export declare function explainSymbol(snapshot: ComplexitySnapshotV1, top?: number): ExplainEntry[];
|
|
22
|
+
//# sourceMappingURL=explain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../src/explain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAgC/C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG,SAAI,GAAG,YAAY,EAAE,CAOrF"}
|
package/dist/explain.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function toCyclomaticEntries(contributions) {
|
|
2
|
+
return contributions
|
|
3
|
+
.filter((c) => c.kind !== "base")
|
|
4
|
+
.map((c) => ({
|
|
5
|
+
metric: "cyclomatic",
|
|
6
|
+
kind: c.kind,
|
|
7
|
+
line: c.line,
|
|
8
|
+
value: c.value,
|
|
9
|
+
text: `line ${c.line}: ${c.kind} contributes ${c.value} to cyclomatic complexity${c.note ? ` (${c.note})` : ""}`,
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
function toCognitiveEntries(contributions) {
|
|
13
|
+
return contributions.map((c) => ({
|
|
14
|
+
metric: "cognitive",
|
|
15
|
+
kind: c.kind,
|
|
16
|
+
line: c.line,
|
|
17
|
+
value: c.total,
|
|
18
|
+
text: `line ${c.line}: ${c.kind} contributes ${c.total} to cognitive complexity (base=${c.base} nestingPenalty=${c.nestingPenalty})${c.note ? ` (${c.note})` : ""}`,
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @param snapshot The symbol's complexity snapshot.
|
|
23
|
+
* @param top Maximum number of entries to return (default 5), largest
|
|
24
|
+
* contribution first; ties keep cyclomatic before cognitive, then source
|
|
25
|
+
* order.
|
|
26
|
+
*/
|
|
27
|
+
export function explainSymbol(snapshot, top = 5) {
|
|
28
|
+
const combined = [
|
|
29
|
+
...toCyclomaticEntries(snapshot.cyclomatic.contributors),
|
|
30
|
+
...toCognitiveEntries(snapshot.cognitive.contributors),
|
|
31
|
+
];
|
|
32
|
+
combined.sort((a, b) => b.value - a.value);
|
|
33
|
+
return combined.slice(0, top).map((entry, index) => ({ ...entry, rank: index + 1 }));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../src/explain.ts"],"names":[],"mappings":"AAoBA,SAAS,mBAAmB,CAC1B,aAA4C;IAE5C,OAAO,aAAa;SACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;SAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACX,MAAM,EAAE,YAAqB;QAC7B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,IAAI,EAAE,QAAQ,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,4BACpD,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAC5B,EAAE;KACH,CAAC,CAAC,CAAC;AACR,CAAC;AAED,SAAS,kBAAkB,CACzB,aAAiD;IAEjD,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,EAAE,WAAoB;QAC5B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,IAAI,EAAE,QAAQ,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,KAAK,kCAAkC,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC,cAAc,IAC/H,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAC5B,EAAE;KACH,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAA8B,EAAE,GAAG,GAAG,CAAC;IACnE,MAAM,QAAQ,GAAG;QACf,GAAG,mBAAmB,CAAC,QAAQ,CAAC,UAAU,CAAC,YAAY,CAAC;QACxD,GAAG,kBAAkB,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC;KACvD,CAAC;IACF,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;AACvF,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HalsteadCalculator (spec.md §16.7). Formulas exactly as specified,
|
|
3
|
+
* including the `max(n, 1)` / `max(n2, 1)` guards against log(0)/div-by-0 on
|
|
4
|
+
* an empty scope.
|
|
5
|
+
*
|
|
6
|
+
* Accepts the domain `HalsteadTokens` shape ({operators, operands}); a full
|
|
7
|
+
* `ComplexityFacts` is also accepted as-is since it structurally satisfies
|
|
8
|
+
* that shape.
|
|
9
|
+
*/
|
|
10
|
+
import type { HalsteadTokens } from "@untangle-code/domain";
|
|
11
|
+
import type { HalsteadResult } from "./types.js";
|
|
12
|
+
export interface HalsteadOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Also compute Time (`effort / 18`, Halstead's Stroud-number heuristic) and
|
|
15
|
+
* Estimated Bugs (`volume / 3000`) — spec.md §16.7 requires both to be
|
|
16
|
+
* explicitly labeled as estimates when present, hence the separate
|
|
17
|
+
* `estimates` field on `HalsteadResult` rather than folding them into
|
|
18
|
+
* `metrics`.
|
|
19
|
+
*/
|
|
20
|
+
includeEstimates?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function computeHalstead(tokens: HalsteadTokens, options?: HalsteadOptions, algorithmVersion?: string): HalsteadResult;
|
|
23
|
+
//# sourceMappingURL=halstead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"halstead.d.ts","sourceRoot":"","sources":["../src/halstead.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAMjD,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,eAAoB,EAC7B,gBAAgB,SAAyB,GACxC,cAAc,CAqBhB"}
|
package/dist/halstead.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function log2(x) {
|
|
2
|
+
return Math.log(x) / Math.LN2;
|
|
3
|
+
}
|
|
4
|
+
export function computeHalstead(tokens, options = {}, algorithmVersion = "untangle-halstead-v1") {
|
|
5
|
+
const N1 = tokens.operators.length;
|
|
6
|
+
const N2 = tokens.operands.length;
|
|
7
|
+
const n1 = new Set(tokens.operators.map((t) => t.text)).size;
|
|
8
|
+
const n2 = new Set(tokens.operands.map((t) => t.text)).size;
|
|
9
|
+
const n = n1 + n2;
|
|
10
|
+
const N = N1 + N2;
|
|
11
|
+
const volume = N * log2(Math.max(n, 1));
|
|
12
|
+
const difficulty = (n1 / 2) * (N2 / Math.max(n2, 1));
|
|
13
|
+
const effort = difficulty * volume;
|
|
14
|
+
const metrics = { n1, n2, N1, N2, n, N, volume, difficulty, effort };
|
|
15
|
+
const result = { metrics, algorithmVersion };
|
|
16
|
+
if (options.includeEstimates) {
|
|
17
|
+
result.estimates = {
|
|
18
|
+
timeSeconds: effort / 18,
|
|
19
|
+
estimatedBugs: volume / 3000,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=halstead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"halstead.js","sourceRoot":"","sources":["../src/halstead.ts"],"names":[],"mappings":"AAaA,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;AAChC,CAAC;AAaD,MAAM,UAAU,eAAe,CAC7B,MAAsB,EACtB,UAA2B,EAAE,EAC7B,gBAAgB,GAAG,sBAAsB;IAEzC,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;IACnC,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,MAAM,EAAE,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAClB,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAClB,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAEnC,MAAM,OAAO,GAA2B,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAE7F,MAAM,MAAM,GAAmB,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC7D,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAC7B,MAAM,CAAC,SAAS,GAAG;YACjB,WAAW,EAAE,MAAM,GAAG,EAAE;YACxB,aAAa,EAAE,MAAM,GAAG,IAAI;SAC7B,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @untangle-code/complexity — the complexity engine (spec.md §16,
|
|
3
|
+
* architecture.md §6.7, §15). Pure, deterministic calculators over
|
|
4
|
+
* `ComplexityFacts`/`ComplexityIRNode` from `@untangle-code/domain`; no I/O,
|
|
5
|
+
* no parser-registry, no embeddings. This module is the ONLY public export
|
|
6
|
+
* of the package.
|
|
7
|
+
*/
|
|
8
|
+
export { containsRange, rangeKey, selectSymbolFacts, sliceByRange, splitLines, strictlyContainsRange, } from "./scope.js";
|
|
9
|
+
export { classifyLines, scopeLines } from "./text-lines.js";
|
|
10
|
+
export { shannonEntropy, frequenciesOf } from "./entropy.js";
|
|
11
|
+
export { classifyNodeKind, isAsyncNodeKind, isExceptionNodeKind } from "./node-kind-heuristics.js";
|
|
12
|
+
export type { NodeKindClass } from "./node-kind-heuristics.js";
|
|
13
|
+
export { buildSymbolIR } from "./ir/build-ir.js";
|
|
14
|
+
export type { BuildSymbolIROptions } from "./ir/build-ir.js";
|
|
15
|
+
export { computeCyclomatic } from "./cyclomatic.js";
|
|
16
|
+
export { computeCognitive, assertCognitiveInvariant } from "./cognitive.js";
|
|
17
|
+
export { computeStructural } from "./structural.js";
|
|
18
|
+
export { computeTextual } from "./textual.js";
|
|
19
|
+
export { computeSyntactic } from "./syntactic.js";
|
|
20
|
+
export { computeHalstead } from "./halstead.js";
|
|
21
|
+
export type { HalsteadOptions } from "./halstead.js";
|
|
22
|
+
export { computeMaintainability, MAINTAINABILITY_FORMULA } from "./maintainability.js";
|
|
23
|
+
export type { MaintainabilityInput } from "./maintainability.js";
|
|
24
|
+
export { aggregate } from "./aggregator.js";
|
|
25
|
+
export { aggregateScopeTree, assertScopeTreeInvariants, checkAllScopeCoverage, checkScopeCoverage, checkScopeTreeInvariants, checkSelectionCoverage, selectionForReading, selectScopeEntries, SCOPE_INVARIANT_CODES, SCOPE_SUM_READINGS, } from "./scope-aggregate.js";
|
|
26
|
+
export type { ScopeInvariantCode, ScopeInvariantViolationV1, ScopeMetricNodeV1, ScopeSelectionKind, ScopeSelectionV1, ScopeSumReading, ScopeTreeAggregateV1, ScopeTreeOptions, } from "./scope-aggregate.js";
|
|
27
|
+
export { withUniqueScopeIds } from "./scope-identity.js";
|
|
28
|
+
export type { ScopeIdCollisionV1, UniqueScopeIdsResultV1 } from "./scope-identity.js";
|
|
29
|
+
export { computeProseMetrics, compareProseByStructureLoad } from "./prose/metrics.js";
|
|
30
|
+
export type { ProseLinkResolver, ProseMetricsOptions } from "./prose/metrics.js";
|
|
31
|
+
export { explainSymbol } from "./explain.js";
|
|
32
|
+
export { assembleComplexitySnapshot } from "./assemble.js";
|
|
33
|
+
export type { SnapshotContext, SnapshotParts } from "./assemble.js";
|
|
34
|
+
export type { AggregationEntry, AggregationResult, CognitiveResult, CyclomaticContribution, CyclomaticResult, ExplainEntry, HalsteadResult, MaintainabilityResult, MetricsResult, StructuralExternalInput, } from "./types.js";
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,aAAa,EACb,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACnG,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACvF,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAItF,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACtF,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC3D,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAGpE,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,uBAAuB,GACxB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @untangle-code/complexity — the complexity engine (spec.md §16,
|
|
3
|
+
* architecture.md §6.7, §15). Pure, deterministic calculators over
|
|
4
|
+
* `ComplexityFacts`/`ComplexityIRNode` from `@untangle-code/domain`; no I/O,
|
|
5
|
+
* no parser-registry, no embeddings. This module is the ONLY public export
|
|
6
|
+
* of the package.
|
|
7
|
+
*/
|
|
8
|
+
// Scoping/text utilities shared across calculators.
|
|
9
|
+
export { containsRange, rangeKey, selectSymbolFacts, sliceByRange, splitLines, strictlyContainsRange, } from "./scope.js";
|
|
10
|
+
export { classifyLines, scopeLines } from "./text-lines.js";
|
|
11
|
+
export { shannonEntropy, frequenciesOf } from "./entropy.js";
|
|
12
|
+
export { classifyNodeKind, isAsyncNodeKind, isExceptionNodeKind } from "./node-kind-heuristics.js";
|
|
13
|
+
// IR producer: ComplexityFacts -> ComplexityIRNode bridge (architecture.md §6.7).
|
|
14
|
+
export { buildSymbolIR } from "./ir/build-ir.js";
|
|
15
|
+
// Calculators (architecture.md §15.2).
|
|
16
|
+
export { computeCyclomatic } from "./cyclomatic.js";
|
|
17
|
+
export { computeCognitive, assertCognitiveInvariant } from "./cognitive.js";
|
|
18
|
+
export { computeStructural } from "./structural.js";
|
|
19
|
+
export { computeTextual } from "./textual.js";
|
|
20
|
+
export { computeSyntactic } from "./syntactic.js";
|
|
21
|
+
export { computeHalstead } from "./halstead.js";
|
|
22
|
+
export { computeMaintainability, MAINTAINABILITY_FORMULA } from "./maintainability.js";
|
|
23
|
+
export { aggregate } from "./aggregator.js";
|
|
24
|
+
// Aggregation ueber den Scope-Baum, ohne Doppelzaehlung (WP-82).
|
|
25
|
+
export { aggregateScopeTree, assertScopeTreeInvariants, checkAllScopeCoverage, checkScopeCoverage, checkScopeTreeInvariants, checkSelectionCoverage, selectionForReading, selectScopeEntries, SCOPE_INVARIANT_CODES, SCOPE_SUM_READINGS, } from "./scope-aggregate.js";
|
|
26
|
+
// Eindeutige Scope-Ids als Voraussetzung der Dedupe ueber metricsScopeId (WP-82).
|
|
27
|
+
export { withUniqueScopeIds } from "./scope-identity.js";
|
|
28
|
+
// Prosa-Metriken als EIGENE Familie (vision.md §1c, WP-217). Ihre Werte sind
|
|
29
|
+
// `ProseNumberV1`-Objekte und koennen deshalb strukturell nie in eine
|
|
30
|
+
// Code-Metrik-Summe geraten.
|
|
31
|
+
export { computeProseMetrics, compareProseByStructureLoad } from "./prose/metrics.js";
|
|
32
|
+
export { explainSymbol } from "./explain.js";
|
|
33
|
+
export { assembleComplexitySnapshot } from "./assemble.js";
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,oDAAoD;AACpD,OAAO,EACL,aAAa,EACb,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,UAAU,EACV,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAGnG,kFAAkF;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,uCAAuC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,iEAAiE;AACjE,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,qBAAqB,EACrB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC;AAW9B,kFAAkF;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,6EAA6E;AAC7E,sEAAsE;AACtE,6BAA6B;AAC7B,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEtF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IR producer: the bridge `ComplexityFacts` -> `ComplexityIRNode`
|
|
3
|
+
* (architecture.md §6.7, spec.md §16.4).
|
|
4
|
+
*
|
|
5
|
+
* architecture.md §6.7 says "Sprachadapter uebersetzen AST-Nodes in diese
|
|
6
|
+
* IR", but the `LanguageAdapter` port (domain `analysis.ts`) deliberately
|
|
7
|
+
* has no IR method — adapters emit flat, language-neutral `ComplexityFacts`.
|
|
8
|
+
* This module closes that gap INSIDE the complexity package: it rebuilds the
|
|
9
|
+
* `ComplexityIRNode` tree `computeCognitive` (and the branching metrics of
|
|
10
|
+
* `computeSyntactic`) require from the byte-ranges the facts already carry,
|
|
11
|
+
* so no language adapter needs to know the IR shape at all.
|
|
12
|
+
*
|
|
13
|
+
* INPUT CONTRACT (facts, as produced by parser-registry's single walk):
|
|
14
|
+
* - every `DecisionFact`/`NestingFact` range is the source range of the AST
|
|
15
|
+
* node that caused it; ranges of distinct facts never partially overlap
|
|
16
|
+
* (they are AST node ranges: equal, disjoint, or fully nested);
|
|
17
|
+
* - an `if`/`loop`/`catch`/`ternary` emits BOTH a `DecisionFact` and a
|
|
18
|
+
* `NestingFact` with the identical range (they are merged into one IR
|
|
19
|
+
* node here);
|
|
20
|
+
* - an `else if` is a `DecisionFact` of kind `"else_if"` plus a
|
|
21
|
+
* `NestingFact` with `continuation: true`, and its range lies INSIDE the
|
|
22
|
+
* range of the `if` it continues (the raw AST "alternate" shape).
|
|
23
|
+
*
|
|
24
|
+
* OUTPUT CONTRACT (what `computeCognitive` requires — see cognitive.ts):
|
|
25
|
+
* - an `if` / `else if` / `else if` chain becomes a FLAT run of sibling IR
|
|
26
|
+
* nodes under the parent of the first `if`, NEVER the raw AST's nested
|
|
27
|
+
* alternate shape. This is what makes "`else if` erhaelt keinen
|
|
28
|
+
* vollstaendigen neuen Nesting-Aufschlag" (spec.md §16.4) fall out of the
|
|
29
|
+
* traversal: siblings share the ambient depth. Implemented via the
|
|
30
|
+
* attachment rule `finalParent(continuationNode) =
|
|
31
|
+
* finalParent(itsContainmentParent)`, which flattens arbitrarily long
|
|
32
|
+
* chains while keeping a genuinely nested `if` (inside another `if`'s
|
|
33
|
+
* BODY) at its deeper level;
|
|
34
|
+
* - each branch keeps its own consequent content as `children` (content
|
|
35
|
+
* ranges nest inside the branch's own range, so containment placement
|
|
36
|
+
* already produces that).
|
|
37
|
+
*
|
|
38
|
+
* Documented mapping decisions (Untangle decisions, mirrored in the README):
|
|
39
|
+
* - nesting constructs map to IR kinds: if->"decision", loop->"loop",
|
|
40
|
+
* catch->"catch", ternary->"ternary", switch->"switch", lambda->"lambda";
|
|
41
|
+
* all of them are nesting boundaries (`nestingBoundary: true`), matching
|
|
42
|
+
* the boundary list documented in cognitive.ts;
|
|
43
|
+
* - a `case` decision (no nesting fact of its own) becomes a `"case"` node
|
|
44
|
+
* that is ALSO a nesting boundary — case bodies are one level deeper than
|
|
45
|
+
* the switch head, per cognitive.ts's documented boundary list;
|
|
46
|
+
* - logical-operator decisions (`logical_and`/`logical_or`/`short_circuit`)
|
|
47
|
+
* become non-boundary `"logical_sequence"` nodes carrying their
|
|
48
|
+
* `DecisionFact` (grouping happens in the consumers via `logicalGroupId`);
|
|
49
|
+
* - CONDITION HOISTING: a logical node whose direct IR parent is a
|
|
50
|
+
* condition-bearing boundary (`decision`/`loop`/`ternary`/`switch`) is
|
|
51
|
+
* hoisted to a sibling placed immediately BEFORE that boundary node. This
|
|
52
|
+
* encodes the untangle-cognitive-v1 convention that a guard's condition
|
|
53
|
+
* operators score at the guard's own ambient depth (see the golden case in
|
|
54
|
+
* cognitive.test.ts), and is exact for `switch` (everything nested in a
|
|
55
|
+
* switch body sits inside a `case` node). For `if`/loops/ternaries it is a
|
|
56
|
+
* documented approximation: facts carry no condition/body split, so a
|
|
57
|
+
* logical group written as a DIRECT body statement of the guard (e.g.
|
|
58
|
+
* `if (x) y = a && b;`) is hoisted too and scores one nesting level lower
|
|
59
|
+
* than a hypothetical exact split would give. Making this exact needs a
|
|
60
|
+
* `conditionRange` on `DecisionFact` (or a `bodyRange` on `NestingFact`)
|
|
61
|
+
* from the adapters — tracked as an open issue, not guessed here;
|
|
62
|
+
* - every descendant symbol scope of the target symbol is included: its
|
|
63
|
+
* facts participate in the target's IR, and the scope itself becomes a
|
|
64
|
+
* `"lambda"` boundary node when it is function-like (function / method /
|
|
65
|
+
* constructor — deduplicated by range against the adapter's own `lambda`
|
|
66
|
+
* nesting events) or a transparent non-boundary `"block"` node otherwise
|
|
67
|
+
* (class/interface/enum/namespace/type/property). This is the "documented
|
|
68
|
+
* nesting context" spec.md §16.4 demands for local functions/lambdas: a
|
|
69
|
+
* symbol's cognitive value includes the control flow of the lambdas it
|
|
70
|
+
* defines, one nesting level deeper — while those lambdas still get their
|
|
71
|
+
* own, separately-scoped snapshot when measured directly;
|
|
72
|
+
* - recursion (spec.md §16.4 "Rekursion wird markiert und KANN einen
|
|
73
|
+
* Beitrag erzeugen"): when the caller supplies the file's `RawCall`s
|
|
74
|
+
* (adapters expose them via `extractCalls`, but they are not part of
|
|
75
|
+
* `ComplexityFacts`), a call site inside the subtree whose unqualified
|
|
76
|
+
* callee name (or `this.`-qualified name, for methods) equals the name of
|
|
77
|
+
* its OWN enclosing scope becomes a `"recursion_marker"` node — direct
|
|
78
|
+
* self-recursion only; mutual recursion needs the call graph and is out of
|
|
79
|
+
* scope here.
|
|
80
|
+
*/
|
|
81
|
+
import type { ComplexityFacts, ComplexityIRNode, RawCall } from "@untangle-code/domain";
|
|
82
|
+
export interface BuildSymbolIROptions {
|
|
83
|
+
/**
|
|
84
|
+
* Raw call sites of the same file (from `LanguageAdapter.extractCalls`).
|
|
85
|
+
* When given, direct self-recursion is marked with `"recursion_marker"`
|
|
86
|
+
* nodes; when omitted, no recursion contributions are produced (allowed:
|
|
87
|
+
* spec.md §16.4 makes the recursion contribution optional).
|
|
88
|
+
*/
|
|
89
|
+
calls?: readonly RawCall[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Builds the `ComplexityIRNode` subtree for one symbol scope (kind
|
|
93
|
+
* `"symbol"`, `nestingBoundary` unset, traversal depth 0 — exactly the root
|
|
94
|
+
* shape `computeCognitive` expects), or the whole-file tree (kind `"root"`)
|
|
95
|
+
* when `symbolScopeId` is the file scope's id.
|
|
96
|
+
*
|
|
97
|
+
* Returns `undefined` when `symbolScopeId` does not exist in
|
|
98
|
+
* `facts.symbolScopes` (mirrors `selectSymbolFacts`' stale-id tolerance).
|
|
99
|
+
*
|
|
100
|
+
* Pass the UNFILTERED file facts, not `selectSymbolFacts` output: the
|
|
101
|
+
* builder selects the symbol's own facts AND its descendant scopes' facts
|
|
102
|
+
* itself (a symbol's lambdas nest into its IR — see the module doc).
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildSymbolIR(facts: ComplexityFacts, symbolScopeId: string, options?: BuildSymbolIROptions): ComplexityIRNode | undefined;
|
|
105
|
+
//# sourceMappingURL=build-ir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-ir.d.ts","sourceRoot":"","sources":["../../src/ir/build-ir.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AACH,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAGhB,OAAO,EAER,MAAM,uBAAuB,CAAC;AAI/B,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC5B;AAmGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,eAAe,EACtB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,oBAAyB,GACjC,gBAAgB,GAAG,SAAS,CAiK9B"}
|