@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.
Files changed (105) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +90 -0
  3. package/README.md +235 -2
  4. package/THIRD_PARTY_LICENSES.md +24 -0
  5. package/dist/aggregator.d.ts +28 -0
  6. package/dist/aggregator.d.ts.map +1 -0
  7. package/dist/aggregator.js +77 -0
  8. package/dist/aggregator.js.map +1 -0
  9. package/dist/assemble.d.ts +50 -0
  10. package/dist/assemble.d.ts.map +1 -0
  11. package/dist/assemble.js +43 -0
  12. package/dist/assemble.js.map +1 -0
  13. package/dist/cognitive.d.ts +62 -0
  14. package/dist/cognitive.d.ts.map +1 -0
  15. package/dist/cognitive.js +62 -0
  16. package/dist/cognitive.js.map +1 -0
  17. package/dist/cyclomatic.d.ts +29 -0
  18. package/dist/cyclomatic.d.ts.map +1 -0
  19. package/dist/cyclomatic.js +52 -0
  20. package/dist/cyclomatic.js.map +1 -0
  21. package/dist/entropy.d.ts +5 -0
  22. package/dist/entropy.d.ts.map +1 -0
  23. package/dist/entropy.js +29 -0
  24. package/dist/entropy.js.map +1 -0
  25. package/dist/explain.d.ts +22 -0
  26. package/dist/explain.d.ts.map +1 -0
  27. package/dist/explain.js +35 -0
  28. package/dist/explain.js.map +1 -0
  29. package/dist/halstead.d.ts +23 -0
  30. package/dist/halstead.d.ts.map +1 -0
  31. package/dist/halstead.js +24 -0
  32. package/dist/halstead.js.map +1 -0
  33. package/dist/index.d.ts +35 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +34 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/ir/build-ir.d.ts +105 -0
  38. package/dist/ir/build-ir.d.ts.map +1 -0
  39. package/dist/ir/build-ir.js +267 -0
  40. package/dist/ir/build-ir.js.map +1 -0
  41. package/dist/maintainability.d.ts +15 -0
  42. package/dist/maintainability.d.ts.map +1 -0
  43. package/dist/maintainability.js +10 -0
  44. package/dist/maintainability.js.map +1 -0
  45. package/dist/node-kind-heuristics.d.ts +24 -0
  46. package/dist/node-kind-heuristics.d.ts.map +1 -0
  47. package/dist/node-kind-heuristics.js +37 -0
  48. package/dist/node-kind-heuristics.js.map +1 -0
  49. package/dist/prose/metrics.d.ts +81 -0
  50. package/dist/prose/metrics.d.ts.map +1 -0
  51. package/dist/prose/metrics.js +103 -0
  52. package/dist/prose/metrics.js.map +1 -0
  53. package/dist/scope-aggregate.d.ts +229 -0
  54. package/dist/scope-aggregate.d.ts.map +1 -0
  55. package/dist/scope-aggregate.js +588 -0
  56. package/dist/scope-aggregate.js.map +1 -0
  57. package/dist/scope-identity.d.ts +85 -0
  58. package/dist/scope-identity.d.ts.map +1 -0
  59. package/dist/scope-identity.js +144 -0
  60. package/dist/scope-identity.js.map +1 -0
  61. package/dist/scope.d.ts +36 -0
  62. package/dist/scope.d.ts.map +1 -0
  63. package/dist/scope.js +60 -0
  64. package/dist/scope.js.map +1 -0
  65. package/dist/structural.d.ts +30 -0
  66. package/dist/structural.d.ts.map +1 -0
  67. package/dist/structural.js +98 -0
  68. package/dist/structural.js.map +1 -0
  69. package/dist/syntactic.d.ts +35 -0
  70. package/dist/syntactic.d.ts.map +1 -0
  71. package/dist/syntactic.js +143 -0
  72. package/dist/syntactic.js.map +1 -0
  73. package/dist/text-lines.d.ts +34 -0
  74. package/dist/text-lines.d.ts.map +1 -0
  75. package/dist/text-lines.js +60 -0
  76. package/dist/text-lines.js.map +1 -0
  77. package/dist/textual.d.ts +28 -0
  78. package/dist/textual.d.ts.map +1 -0
  79. package/dist/textual.js +75 -0
  80. package/dist/textual.js.map +1 -0
  81. package/dist/types.d.ts +112 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +2 -0
  84. package/dist/types.js.map +1 -0
  85. package/package.json +60 -3
  86. package/src/aggregator.ts +105 -0
  87. package/src/assemble.ts +109 -0
  88. package/src/cognitive.ts +127 -0
  89. package/src/cyclomatic.ts +78 -0
  90. package/src/entropy.ts +30 -0
  91. package/src/explain.ts +64 -0
  92. package/src/halstead.ts +54 -0
  93. package/src/index.ts +85 -0
  94. package/src/ir/build-ir.ts +411 -0
  95. package/src/maintainability.ts +29 -0
  96. package/src/node-kind-heuristics.ts +55 -0
  97. package/src/prose/metrics.ts +201 -0
  98. package/src/scope-aggregate.ts +775 -0
  99. package/src/scope-identity.ts +240 -0
  100. package/src/scope.ts +77 -0
  101. package/src/structural.ts +132 -0
  102. package/src/syntactic.ts +183 -0
  103. package/src/text-lines.ts +90 -0
  104. package/src/textual.ts +119 -0
  105. 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"}
@@ -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"}
@@ -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"}
@@ -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"}
@@ -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"}