@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,267 @@
1
+ import { containsRange, rangeKey } from "../scope.js";
2
+ const FUNCTION_LIKE_SCOPE_KINDS = new Set([
3
+ "function",
4
+ "method",
5
+ "constructor",
6
+ ]);
7
+ const LOGICAL_DECISION_KINDS = new Set([
8
+ "logical_and",
9
+ "logical_or",
10
+ "short_circuit",
11
+ ]);
12
+ /** Boundary IR kinds whose head expression (condition) can carry logical groups. */
13
+ const CONDITION_BEARING_KINDS = new Set([
14
+ "decision",
15
+ "loop",
16
+ "ternary",
17
+ "switch",
18
+ ]);
19
+ function irKindForConstruct(construct) {
20
+ switch (construct) {
21
+ case "if":
22
+ return "decision";
23
+ case "loop":
24
+ return "loop";
25
+ case "catch":
26
+ return "catch";
27
+ case "ternary":
28
+ return "ternary";
29
+ case "switch":
30
+ return "switch";
31
+ case "lambda":
32
+ return "lambda";
33
+ default:
34
+ // Unknown future constructs still nest conservatively (they were
35
+ // emitted as nesting events, so they ARE nesting constructs).
36
+ return "other";
37
+ }
38
+ }
39
+ function isLogicalDecisionNode(node) {
40
+ return (node.decision !== undefined &&
41
+ LOGICAL_DECISION_KINDS.has(node.decision.kind) &&
42
+ node.nestingBoundary !== true);
43
+ }
44
+ function isConditionBearingBoundary(node) {
45
+ return node.nestingBoundary === true && CONDITION_BEARING_KINDS.has(node.kind);
46
+ }
47
+ /**
48
+ * Condition hoisting (see "CONDITION HOISTING" above): a logical node that
49
+ * is a direct child of a condition-bearing boundary moves up EXACTLY ONE
50
+ * level, inserted immediately before its former parent — that places the
51
+ * group at the guard's own ambient depth (the guard's children sit one
52
+ * boundary deeper than the guard itself). Never more than one level: the
53
+ * sweep for `parent` runs before recursing into its children, so a node
54
+ * hoisted out of a grandchild boundary is not swept again by any ancestor.
55
+ */
56
+ function hoistConditionGroups(parent) {
57
+ const reordered = [];
58
+ for (const child of parent.children) {
59
+ if (isConditionBearingBoundary(child)) {
60
+ const staying = [];
61
+ for (const grandchild of child.children) {
62
+ if (isLogicalDecisionNode(grandchild)) {
63
+ reordered.push(grandchild);
64
+ }
65
+ else {
66
+ staying.push(grandchild);
67
+ }
68
+ }
69
+ child.children = staying;
70
+ }
71
+ reordered.push(child);
72
+ }
73
+ parent.children = reordered;
74
+ for (const child of parent.children) {
75
+ hoistConditionGroups(child);
76
+ }
77
+ }
78
+ function isDirectSelfRecursion(call, enclosingScope) {
79
+ if (enclosingScope.name.length === 0)
80
+ return false;
81
+ if (call.calleeName !== enclosingScope.name)
82
+ return false;
83
+ // `foo()` inside function foo, or `this.foo()` inside method foo.
84
+ return call.qualifier === undefined || call.qualifier === "this";
85
+ }
86
+ /**
87
+ * Builds the `ComplexityIRNode` subtree for one symbol scope (kind
88
+ * `"symbol"`, `nestingBoundary` unset, traversal depth 0 — exactly the root
89
+ * shape `computeCognitive` expects), or the whole-file tree (kind `"root"`)
90
+ * when `symbolScopeId` is the file scope's id.
91
+ *
92
+ * Returns `undefined` when `symbolScopeId` does not exist in
93
+ * `facts.symbolScopes` (mirrors `selectSymbolFacts`' stale-id tolerance).
94
+ *
95
+ * Pass the UNFILTERED file facts, not `selectSymbolFacts` output: the
96
+ * builder selects the symbol's own facts AND its descendant scopes' facts
97
+ * itself (a symbol's lambdas nest into its IR — see the module doc).
98
+ */
99
+ export function buildSymbolIR(facts, symbolScopeId, options = {}) {
100
+ const scopesById = new Map();
101
+ for (const scope of facts.symbolScopes)
102
+ scopesById.set(scope.symbolScopeId, scope);
103
+ const targetScope = scopesById.get(symbolScopeId);
104
+ if (targetScope === undefined)
105
+ return undefined;
106
+ // Target scope plus all transitive descendants (lambdas, local functions,
107
+ // nested classes/methods, ...).
108
+ const childScopesByParent = new Map();
109
+ for (const scope of facts.symbolScopes) {
110
+ if (scope.parentScopeId === undefined)
111
+ continue;
112
+ const siblings = childScopesByParent.get(scope.parentScopeId) ?? [];
113
+ siblings.push(scope);
114
+ childScopesByParent.set(scope.parentScopeId, siblings);
115
+ }
116
+ const relevantScopeIds = new Set([symbolScopeId]);
117
+ const descendantScopes = [];
118
+ const pending = [symbolScopeId];
119
+ while (pending.length > 0) {
120
+ const current = pending.pop();
121
+ for (const child of childScopesByParent.get(current) ?? []) {
122
+ if (relevantScopeIds.has(child.symbolScopeId))
123
+ continue;
124
+ relevantScopeIds.add(child.symbolScopeId);
125
+ descendantScopes.push(child);
126
+ pending.push(child.symbolScopeId);
127
+ }
128
+ }
129
+ // ---- Proto nodes, deduplicated by range (an if/loop/catch/ternary emits
130
+ // a DecisionFact AND a NestingFact for the same AST node). ----
131
+ const protoByRange = new Map();
132
+ const ensureNode = (key, create, continuation) => {
133
+ const existing = protoByRange.get(key);
134
+ if (existing !== undefined)
135
+ return existing;
136
+ const created = { node: create(), continuation };
137
+ protoByRange.set(key, created);
138
+ return created;
139
+ };
140
+ for (const nesting of facts.nestingEvents) {
141
+ if (!relevantScopeIds.has(nesting.symbolScopeId))
142
+ continue;
143
+ ensureNode(rangeKey(nesting.range), () => ({
144
+ kind: irKindForConstruct(nesting.construct),
145
+ range: nesting.range,
146
+ children: [],
147
+ nestingBoundary: true,
148
+ symbolScopeId: nesting.symbolScopeId,
149
+ }), nesting.continuation);
150
+ }
151
+ for (const decision of facts.decisions) {
152
+ if (!relevantScopeIds.has(decision.symbolScopeId))
153
+ continue;
154
+ const proto = ensureNode(rangeKey(decision.range), () => decisionOnlyNode(decision), false);
155
+ if (proto.node.decision === undefined)
156
+ proto.node.decision = decision;
157
+ }
158
+ for (const scope of descendantScopes) {
159
+ const key = rangeKey(scope.range);
160
+ if (FUNCTION_LIKE_SCOPE_KINDS.has(scope.kind)) {
161
+ // Deduplicates against the adapter's own "lambda" nesting event (same
162
+ // AST node, same range) when the enclosing scope was executable.
163
+ ensureNode(key, () => ({
164
+ kind: "lambda",
165
+ range: scope.range,
166
+ children: [],
167
+ nestingBoundary: true,
168
+ symbolScopeId: scope.symbolScopeId,
169
+ }), false);
170
+ }
171
+ else {
172
+ ensureNode(key, () => ({
173
+ kind: "block",
174
+ range: scope.range,
175
+ children: [],
176
+ symbolScopeId: scope.symbolScopeId,
177
+ }), false);
178
+ }
179
+ }
180
+ for (const call of options.calls ?? []) {
181
+ if (!relevantScopeIds.has(call.callerScopeId))
182
+ continue;
183
+ const enclosingScope = scopesById.get(call.callerScopeId);
184
+ if (enclosingScope === undefined)
185
+ continue;
186
+ if (!isDirectSelfRecursion(call, enclosingScope))
187
+ continue;
188
+ ensureNode(rangeKey(call.range), () => ({
189
+ kind: "recursion_marker",
190
+ range: call.range,
191
+ children: [],
192
+ symbolScopeId: call.callerScopeId,
193
+ }), false);
194
+ }
195
+ // ---- Containment placement with else-if sibling flattening. ----
196
+ const root = {
197
+ kind: targetScope.kind === "file" ? "root" : "symbol",
198
+ range: targetScope.range,
199
+ children: [],
200
+ symbolScopeId,
201
+ };
202
+ const sorted = [...protoByRange.values()].sort((a, b) => a.node.range.startByte - b.node.range.startByte ||
203
+ b.node.range.endByte - a.node.range.endByte);
204
+ const containmentStack = [];
205
+ // The node each proto was ATTACHED to (differs from the containment parent
206
+ // for continuation nodes — that difference IS the flattening).
207
+ const attachedTo = new Map();
208
+ for (const proto of sorted) {
209
+ if (!containsRange(root.range, proto.node.range))
210
+ continue; // defensive: stray fact outside the symbol
211
+ while (containmentStack.length > 0 &&
212
+ !containsRange(containmentStack[containmentStack.length - 1].node.range, proto.node.range)) {
213
+ containmentStack.pop();
214
+ }
215
+ const containmentParent = containmentStack[containmentStack.length - 1];
216
+ let parent;
217
+ if (proto.continuation && containmentParent !== undefined) {
218
+ // finalParent(else_if) = finalParent(the if it continues); the owning
219
+ // if is exactly the containment parent (nothing sits between an
220
+ // if_statement and the if_statement in its else-clause).
221
+ parent = attachedTo.get(containmentParent) ?? root;
222
+ }
223
+ else {
224
+ parent = containmentParent?.node ?? root;
225
+ }
226
+ parent.children.push(proto.node);
227
+ attachedTo.set(proto, parent);
228
+ containmentStack.push(proto);
229
+ }
230
+ // ---- Condition hoisting (one level; see module doc). ----
231
+ hoistConditionGroups(root);
232
+ return root;
233
+ }
234
+ function decisionOnlyNode(decision) {
235
+ if (decision.kind === "case") {
236
+ // Case bodies nest one level below the switch head (cognitive.ts's
237
+ // documented boundary list includes "case").
238
+ return {
239
+ kind: "case",
240
+ range: decision.range,
241
+ children: [],
242
+ nestingBoundary: true,
243
+ decision,
244
+ symbolScopeId: decision.symbolScopeId,
245
+ };
246
+ }
247
+ if (LOGICAL_DECISION_KINDS.has(decision.kind)) {
248
+ return {
249
+ kind: "logical_sequence",
250
+ range: decision.range,
251
+ children: [],
252
+ decision,
253
+ symbolScopeId: decision.symbolScopeId,
254
+ };
255
+ }
256
+ // guard/if/loop/... decisions normally merge with their nesting event by
257
+ // range; a decision kind without one (e.g. "pattern_alternative") becomes
258
+ // a plain non-boundary decision node.
259
+ return {
260
+ kind: "decision",
261
+ range: decision.range,
262
+ children: [],
263
+ decision,
264
+ symbolScopeId: decision.symbolScopeId,
265
+ };
266
+ }
267
+ //# sourceMappingURL=build-ir.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-ir.js","sourceRoot":"","sources":["../../src/ir/build-ir.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * MaintainabilityCalculator (spec.md §16.9). Exact default formula, no
3
+ * comment bonus (v1 deliberately omits it per spec.md §16.9, for
4
+ * cross-language stability), clamped to [0, 100]. The formula string itself
5
+ * is always returned, as required by the work order.
6
+ */
7
+ import type { MaintainabilityResult } from "./types.js";
8
+ export declare const MAINTAINABILITY_FORMULA = "MI = clamp((171 - 5.2*ln(max(HalsteadVolume,1)) - 0.23*CyclomaticClassic - 16.2*ln(max(SLOC,1))) * 100 / 171, 0, 100)";
9
+ export interface MaintainabilityInput {
10
+ halsteadVolume: number;
11
+ cyclomaticClassic: number;
12
+ sloc: number;
13
+ }
14
+ export declare function computeMaintainability(input: MaintainabilityInput, algorithmVersion?: string): MaintainabilityResult;
15
+ //# sourceMappingURL=maintainability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maintainability.d.ts","sourceRoot":"","sources":["../src/maintainability.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,eAAO,MAAM,uBAAuB,0HACqF,CAAC;AAE1H,MAAM,WAAW,oBAAoB;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,oBAAoB,EAC3B,gBAAgB,SAAgC,GAC/C,qBAAqB,CAQvB"}
@@ -0,0 +1,10 @@
1
+ export const MAINTAINABILITY_FORMULA = "MI = clamp((171 - 5.2*ln(max(HalsteadVolume,1)) - 0.23*CyclomaticClassic - 16.2*ln(max(SLOC,1))) * 100 / 171, 0, 100)";
2
+ export function computeMaintainability(input, algorithmVersion = "untangle-maintainability-v1") {
3
+ const volumeTerm = Math.log(Math.max(input.halsteadVolume, 1));
4
+ const slocTerm = Math.log(Math.max(input.sloc, 1));
5
+ const raw = 171 - 5.2 * volumeTerm - 0.23 * input.cyclomaticClassic - 16.2 * slocTerm;
6
+ const scaled = (raw * 100) / 171;
7
+ const value = Math.min(100, Math.max(0, scaled));
8
+ return { value, raw, formula: MAINTAINABILITY_FORMULA, algorithmVersion };
9
+ }
10
+ //# sourceMappingURL=maintainability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maintainability.js","sourceRoot":"","sources":["../src/maintainability.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,uBAAuB,GAClC,uHAAuH,CAAC;AAQ1H,MAAM,UAAU,sBAAsB,CACpC,KAA2B,EAC3B,gBAAgB,GAAG,6BAA6B;IAEhD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,UAAU,GAAG,IAAI,GAAG,KAAK,CAAC,iBAAiB,GAAG,IAAI,GAAG,QAAQ,CAAC;IACtF,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAEjD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Best-effort classification of raw, adapter-specific AST node kind strings
3
+ * (`NodeKindFact.kind`, `ComplexityFacts.nodeKinds`) into the language-neutral
4
+ * buckets `StructuralCalculator`/`SyntacticCalculator` need for
5
+ * `parameter_count`, `return_count`, `exit_point_count`, `statement_count`,
6
+ * `expression_count` and `declaration_count` (spec.md §16.5, §16.8).
7
+ *
8
+ * `ComplexityFacts` deliberately keeps `kind` as an opaque, grammar-specific
9
+ * string (e.g. tree-sitter's `"required_parameter"`, `"return_statement"`) —
10
+ * domain does not define a normalized node-kind taxonomy. This module is
11
+ * Untangle's own, documented, versioned answer to that gap (see README
12
+ * "Entscheidungen" #5); it is intentionally conservative (substring match on
13
+ * common, cross-grammar naming conventions) and is flagged in `openIssues` as
14
+ * a candidate for an additive domain extension once real adapter output
15
+ * exists to validate against.
16
+ */
17
+ export type NodeKindClass = "parameter" | "return" | "exit" | "statement" | "expression" | "declaration" | "other";
18
+ /** Classifies one raw node-kind string. Order matters: more specific buckets are checked first. */
19
+ export declare function classifyNodeKind(kind: string): NodeKindClass;
20
+ /** True when a raw node-kind string looks like an async construct (best-effort, see module docs). */
21
+ export declare function isAsyncNodeKind(kind: string): boolean;
22
+ /** True when a raw node-kind string looks like an exception construct (best-effort, see module docs). */
23
+ export declare function isExceptionNodeKind(kind: string): boolean;
24
+ //# sourceMappingURL=node-kind-heuristics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-kind-heuristics.d.ts","sourceRoot":"","sources":["../src/node-kind-heuristics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,aAAa,GACvB,WAAW,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,aAAa,GAAG,OAAO,CAAC;AAazF,mGAAmG;AACnG,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAS5D;AAID,qGAAqG;AACrG,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAID,yGAAyG;AACzG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzD"}
@@ -0,0 +1,37 @@
1
+ const RETURN_MARKERS = ["return_statement", "return"];
2
+ const EXIT_MARKERS = ["throw_statement", "throw", "break_statement", "continue_statement"];
3
+ const PARAMETER_MARKERS = ["parameter"];
4
+ const DECLARATION_MARKERS = ["declaration", "declarator"];
5
+ const STATEMENT_MARKERS = ["statement"];
6
+ const EXPRESSION_MARKERS = ["expression"];
7
+ function matches(normalizedKind, markers) {
8
+ return markers.some((marker) => normalizedKind === marker || normalizedKind.includes(marker));
9
+ }
10
+ /** Classifies one raw node-kind string. Order matters: more specific buckets are checked first. */
11
+ export function classifyNodeKind(kind) {
12
+ const normalized = kind.toLowerCase();
13
+ if (matches(normalized, RETURN_MARKERS))
14
+ return "return";
15
+ if (matches(normalized, EXIT_MARKERS))
16
+ return "exit";
17
+ if (matches(normalized, PARAMETER_MARKERS))
18
+ return "parameter";
19
+ if (matches(normalized, DECLARATION_MARKERS))
20
+ return "declaration";
21
+ if (matches(normalized, STATEMENT_MARKERS))
22
+ return "statement";
23
+ if (matches(normalized, EXPRESSION_MARKERS))
24
+ return "expression";
25
+ return "other";
26
+ }
27
+ const ASYNC_MARKERS = ["async", "await"];
28
+ /** True when a raw node-kind string looks like an async construct (best-effort, see module docs). */
29
+ export function isAsyncNodeKind(kind) {
30
+ return matches(kind.toLowerCase(), ASYNC_MARKERS);
31
+ }
32
+ const EXCEPTION_MARKERS = ["throw_statement", "throw", "try_statement", "catch_clause"];
33
+ /** True when a raw node-kind string looks like an exception construct (best-effort, see module docs). */
34
+ export function isExceptionNodeKind(kind) {
35
+ return matches(kind.toLowerCase(), EXCEPTION_MARKERS);
36
+ }
37
+ //# sourceMappingURL=node-kind-heuristics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-kind-heuristics.js","sourceRoot":"","sources":["../src/node-kind-heuristics.ts"],"names":[],"mappings":"AAmBA,MAAM,cAAc,GAAG,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AACtD,MAAM,YAAY,GAAG,CAAC,iBAAiB,EAAE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,CAAC,CAAC;AAC3F,MAAM,iBAAiB,GAAG,CAAC,WAAW,CAAC,CAAC;AACxC,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;AAC1D,MAAM,iBAAiB,GAAG,CAAC,WAAW,CAAC,CAAC;AACxC,MAAM,kBAAkB,GAAG,CAAC,YAAY,CAAC,CAAC;AAE1C,SAAS,OAAO,CAAC,cAAsB,EAAE,OAA0B;IACjE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,cAAc,KAAK,MAAM,IAAI,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAChG,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACtC,IAAI,OAAO,CAAC,UAAU,EAAE,cAAc,CAAC;QAAE,OAAO,QAAQ,CAAC;IACzD,IAAI,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC;QAAE,OAAO,MAAM,CAAC;IACrD,IAAI,OAAO,CAAC,UAAU,EAAE,iBAAiB,CAAC;QAAE,OAAO,WAAW,CAAC;IAC/D,IAAI,OAAO,CAAC,UAAU,EAAE,mBAAmB,CAAC;QAAE,OAAO,aAAa,CAAC;IACnE,IAAI,OAAO,CAAC,UAAU,EAAE,iBAAiB,CAAC;QAAE,OAAO,WAAW,CAAC;IAC/D,IAAI,OAAO,CAAC,UAAU,EAAE,kBAAkB,CAAC;QAAE,OAAO,YAAY,CAAC;IACjE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAEzC,qGAAqG;AACrG,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,aAAa,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,iBAAiB,GAAG,CAAC,iBAAiB,EAAE,OAAO,EAAE,eAAe,EAAE,cAAc,CAAC,CAAC;AAExF,yGAAyG;AACzG,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,iBAAiB,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Prosa-Metriken (vision.md §1c/§1d, WP-217).
3
+ *
4
+ * WARUM ES DIESE DATEI GIBT
5
+ *
6
+ * Alles andere in diesem Paket rechnet ueber `ComplexityFacts`: Scopes,
7
+ * Entscheidungspunkte, Verschachtelung, Operatoren. Ein Markdown-Dokument
8
+ * hat davon NICHTS — nicht wenig, sondern nichts. Der Prosa-Adapter liefert
9
+ * deshalb leere Facts, und Prosa wird hier ueber ihre EIGENEN Groessen
10
+ * gemessen: Struktur- und Verweislast.
11
+ *
12
+ * Die Trennung ist strukturell, nicht per Konvention: jeder Rueckgabewert
13
+ * ist ein {@link ProseNumberV1}, also ein Objekt. Ein Prosa-Messwert kann
14
+ * dadurch an keiner Stelle stehen, an der `computeCognitive` oder
15
+ * `aggregate` eine Zahl erwarten — der Uebersetzer lehnt es ab, und
16
+ * `assertUntangleMetricNumber` lehnt es zur Laufzeit ab. Vorbild ist
17
+ * `ExternalNumberV1` (domain `types/extensions.ts`), das externe Befunde
18
+ * genauso von Untangle-Metriken fernhaelt.
19
+ *
20
+ * DIE FUENF MASSE, UND WARUM GENAU DIESE
21
+ *
22
+ * Jedes Mass muss ohne Modellannahme lesbar sein — sonst ist es eine
23
+ * erfundene Zahl mit Nachkommastellen. Was hier steht, laesst sich am
24
+ * Dokument nachzaehlen:
25
+ *
26
+ * 1. `sectionDepth` — tiefste benutzte Ueberschriftenebene.
27
+ * 2. `maxSectionLines` — laengster Abschnitt in Zeilen.
28
+ * 3. `linkDensity` — Verweise je 100 Inhaltszeilen.
29
+ * 4. `unresolvedLinkRatio` — Anteil toter relativer Dateiverweise, oder
30
+ * `null`, wenn niemand aufgeloest hat.
31
+ * 5. `repetitionRatio` — Anteil woertlich wiederholter Inhaltszeilen.
32
+ *
33
+ * Dazu der abgeleitete Ordnungsschluessel `structureLoad`: die Summe der
34
+ * Ueberschriftenebenen aller Abschnitte. Bewusst keine gewichtete Mischung
35
+ * ungleicher Groessen — das waere der eine „Quality Score", den vision.md
36
+ * §7.6 ablehnt —, sondern eine abzaehlbare Groesse: die Gesamtzahl der
37
+ * Gliederungsstufen, die das Dokument aufspannt. Ein Dokument mit 40
38
+ * Abschnitten auf Ebene 3 traegt mehr Gliederung als eines mit 40
39
+ * Abschnitten auf Ebene 1, und beide mehr als eines mit vier.
40
+ *
41
+ * DER AUFLOESER
42
+ *
43
+ * `unresolvedLinkRatio` ist der einzige Wert, der etwas ausserhalb des
44
+ * Dokuments braucht. Ohne `resolveLink` bleibt er `null` — ein
45
+ * Zustandsvermerk, kein Messwert von 0. vision.md §1d ist an genau dieser
46
+ * Stelle deutlich: „niemand hat gesucht" darf nie wie ein Befund ueber den
47
+ * Inhalt aussehen. Deshalb ist der Aufloeser ein Parameter und keine
48
+ * Dateizugriffsfunktion in diesem Paket: `@untangle-code/complexity` bleibt
49
+ * frei von I/O.
50
+ */
51
+ import type { ProseMetricsV1 } from "@untangle-code/domain";
52
+ /**
53
+ * Beantwortet, ob das Ziel eines relativen Dateiverweises existiert.
54
+ * `documentPath` ist der Pfad des VERWEISENDEN Dokuments, damit der
55
+ * Aufloeser relativ dazu suchen kann.
56
+ */
57
+ export type ProseLinkResolver = (documentPath: string, targetFilePath: string) => boolean;
58
+ export interface ProseMetricsOptions {
59
+ /**
60
+ * Ohne Aufloeser bleibt `unresolvedLinkRatio` `null`. Das ist Absicht:
61
+ * ein ungeprueftes 0 waere die Behauptung, alle Verweise seien heil.
62
+ */
63
+ resolveLink?: ProseLinkResolver;
64
+ }
65
+ /**
66
+ * Prosa-Metriken eines Dokuments.
67
+ *
68
+ * `path` ist der "/"-normalisierte, repo-relative Pfad; er wird nur
69
+ * durchgereicht (an den Aufloeser und in das Ergebnis), nie geoeffnet.
70
+ */
71
+ export declare function computeProseMetrics(path: string, content: string, options?: ProseMetricsOptions): ProseMetricsV1;
72
+ /**
73
+ * Ordnet zwei Dokumente nach Strukturlast, absteigend, mit vollstaendig
74
+ * deterministischem Gleichstandsbruch (nie `localeCompare`).
75
+ *
76
+ * `compare` liefert eine Ordnung, keine Bewertung: das Dokument mit der
77
+ * hoechsten Strukturlast ist nicht „das schlechteste", sondern das mit der
78
+ * meisten Gliederung — die Deutung gehoert dem Leser, die Zahl dem Werkzeug.
79
+ */
80
+ export declare function compareProseByStructureLoad(a: ProseMetricsV1, b: ProseMetricsV1): number;
81
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/prose/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,OAAO,KAAK,EAEV,cAAc,EAGf,MAAM,uBAAuB,CAAC;AAY/B;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,OAAO,CAAC;AAE1F,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC;AAiBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,cAAc,CAmEhB;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,cAAc,GAAG,MAAM,CAMxF"}
@@ -0,0 +1,103 @@
1
+ import { PROSE_METRICS_VERSION, classifyLinkTarget, countContentLines, findMarkdownLinks, linkFilePath, makeProseNumber, normalizedContentLines, outlineMarkdown, } from "@untangle-code/domain";
2
+ /** Physische Zeilen ohne Zeilenende; ein abschliessendes `\r` faellt weg. */
3
+ function splitLines(content) {
4
+ return content.split("\n").map((line) => (line.endsWith("\r") ? line.slice(0, -1) : line));
5
+ }
6
+ /**
7
+ * Rundet auf eine Nachkommastelle. Verhaeltniswerte werden gespeichert und
8
+ * verglichen; ohne feste Rundung waeren zwei Laeufe ueber dieselbe Datei
9
+ * durch Gleitkommarauschen nicht bitgleich.
10
+ */
11
+ function round(value, digits) {
12
+ const factor = 10 ** digits;
13
+ return Math.round(value * factor) / factor;
14
+ }
15
+ /**
16
+ * Prosa-Metriken eines Dokuments.
17
+ *
18
+ * `path` ist der "/"-normalisierte, repo-relative Pfad; er wird nur
19
+ * durchgereicht (an den Aufloeser und in das Ergebnis), nie geoeffnet.
20
+ */
21
+ export function computeProseMetrics(path, content, options = {}) {
22
+ const lines = splitLines(content);
23
+ const outline = outlineMarkdown(lines);
24
+ const sections = outline.map((section) => ({
25
+ breadcrumb: section.breadcrumb,
26
+ level: section.level,
27
+ startLine: section.startLine,
28
+ endLine: section.endLine,
29
+ lines: Math.max(0, section.endLine - section.startLine + 1),
30
+ }));
31
+ const contentLines = countContentLines(lines);
32
+ const rawLinks = findMarkdownLinks(lines);
33
+ const resolver = options.resolveLink;
34
+ const links = rawLinks.map((link) => {
35
+ const kind = classifyLinkTarget(link.target);
36
+ let resolved = null;
37
+ if (kind === "relative_file" && resolver !== undefined) {
38
+ const target = linkFilePath(link.target);
39
+ // Ein Verweis, der nur auf einen Anker im selben Dokument zeigt, hat
40
+ // keinen Dateiteil; er ist nicht "tot", er ist keine Dateikante.
41
+ resolved = target === "" ? true : resolver(path, target);
42
+ }
43
+ return { target: link.target, text: link.text, line: link.line, kind, resolved };
44
+ });
45
+ const resolvable = links.filter((link) => link.resolved !== null);
46
+ const unresolved = resolvable.filter((link) => link.resolved === false);
47
+ const normalized = normalizedContentLines(lines);
48
+ const seen = new Set();
49
+ let repeated = 0;
50
+ for (const line of normalized) {
51
+ if (seen.has(line))
52
+ repeated += 1;
53
+ else
54
+ seen.add(line);
55
+ }
56
+ const depth = sections.reduce((max, section) => Math.max(max, section.level), 0);
57
+ const maxLines = sections.reduce((max, section) => Math.max(max, section.lines), 0);
58
+ const structureLoad = sections.reduce((sum, section) => sum + section.level, 0);
59
+ return {
60
+ schemaVersion: 1,
61
+ metricsVersion: PROSE_METRICS_VERSION,
62
+ path,
63
+ sectionDepth: makeProseNumber(depth, "levels"),
64
+ maxSectionLines: makeProseNumber(maxLines, "lines"),
65
+ linkDensity: makeProseNumber(contentLines === 0 ? 0 : round((links.length / contentLines) * 100, 1), "per_100_lines"),
66
+ unresolvedLinkRatio: resolvable.length === 0
67
+ ? null
68
+ : makeProseNumber(round(unresolved.length / resolvable.length, 4), "ratio"),
69
+ repetitionRatio: makeProseNumber(normalized.length === 0 ? 0 : round(repeated / normalized.length, 4), "ratio"),
70
+ structureLoad: makeProseNumber(structureLoad, "count"),
71
+ sectionCount: makeProseNumber(sections.length, "count"),
72
+ contentLines: makeProseNumber(contentLines, "count"),
73
+ sections,
74
+ links,
75
+ };
76
+ }
77
+ /**
78
+ * Ordnet zwei Dokumente nach Strukturlast, absteigend, mit vollstaendig
79
+ * deterministischem Gleichstandsbruch (nie `localeCompare`).
80
+ *
81
+ * `compare` liefert eine Ordnung, keine Bewertung: das Dokument mit der
82
+ * hoechsten Strukturlast ist nicht „das schlechteste", sondern das mit der
83
+ * meisten Gliederung — die Deutung gehoert dem Leser, die Zahl dem Werkzeug.
84
+ */
85
+ export function compareProseByStructureLoad(a, b) {
86
+ const byLoad = valueOf(b.structureLoad) - valueOf(a.structureLoad);
87
+ if (byLoad !== 0)
88
+ return byLoad;
89
+ const byDepth = valueOf(b.sectionDepth) - valueOf(a.sectionDepth);
90
+ if (byDepth !== 0)
91
+ return byDepth;
92
+ return a.path < b.path ? -1 : a.path > b.path ? 1 : 0;
93
+ }
94
+ /**
95
+ * Der Rohwert eines Prosa-Messwerts fuer die SORTIERUNG innerhalb dieser
96
+ * Familie. Bewusst paketlokal und nicht exportiert: eine allgemein
97
+ * verfuegbare `number`-Entpackung waere genau das Loch, das
98
+ * {@link ProseNumberV1} zumauert.
99
+ */
100
+ function valueOf(value) {
101
+ return value.value;
102
+ }
103
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/prose/metrics.ts"],"names":[],"mappings":"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"}