@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,85 @@
1
+ /**
2
+ * Eindeutige Scope-Identitaet fuer die Aggregation (WP-82).
3
+ *
4
+ * ## Warum es dieses Modul gibt
5
+ *
6
+ * spec.md §16.10 verlangt die Deduplizierung ueber `metrics_scope_id`. Das
7
+ * setzt voraus, dass diese Id GENAU EINEN Scope bezeichnet. Der Bezeichner,
8
+ * den die Sprachadapter heute liefern — `SymbolScopeFact.symbolScopeId` — tut
9
+ * das innerhalb einer Datei NICHT: er wird aus dem qualifizierten Namen plus
10
+ * einem Ordinal gebildet, das je Elternscope zaehlt. Zwei anonyme Callbacks
11
+ * unter zwei verschiedenen, ebenfalls anonymen Eltern bekommen damit beide den
12
+ * qualifizierten Namen `<anonymous>.<anonymous>` und beide das Ordinal 0 —
13
+ * dieselbe Id fuer zwei verschiedene Scopes. Am eigenen Repo betrifft das rund
14
+ * ein Fuenftel aller Scopes.
15
+ *
16
+ * Die Folge waere nicht nur eine schiefe Summe, sondern stiller Inhaltsverlust
17
+ * (E4): `aggregate()` verwirft den zweiten Scope als Dublette,
18
+ * `selectSymbolFacts()` liefert die VEREINIGUNG der Facts beider Scopes, und
19
+ * `buildSymbolIR()` baut die IR fuer denjenigen, der die interne Map zuletzt
20
+ * gewonnen hat.
21
+ *
22
+ * ## Was dieses Modul tut
23
+ *
24
+ * `withUniqueScopeIds()` schreibt einen Facts-Satz auf nachweislich eindeutige
25
+ * Scope-Ids um: Byte-Range statt Name plus Ordinal. Jede Fact-Zuordnung wird
26
+ * ueber Byte-Containment neu aufgeloest (der ENGSTE Scope, der die Id des
27
+ * Facts traegt und dessen Range den Fact enthaelt). Die aufgetretenen
28
+ * Kollisionen werden GEZAEHLT UND ZURUECKGEGEBEN, nicht stillschweigend
29
+ * geheilt — wer den Bericht liest, sieht, dass die Adapter-Id das Problem hat,
30
+ * und nicht nur, dass jemand daran vorbeigearbeitet hat.
31
+ *
32
+ * Das ist eine Reparatur an der Datenkante, KEIN Ersatz fuer die Behebung im
33
+ * Adapter. Sobald `symbolScopeId` je Datei eindeutig ist, bleibt `collisions`
34
+ * leer und dieses Modul kann entfallen.
35
+ *
36
+ * ## Grenze (bewusst, nicht verschwiegen)
37
+ *
38
+ * `NodeKindFact` traegt als einzige Fact-Art KEINE `SourceRange` (domain
39
+ * `analysis.ts`); ihre Zuordnung ist ueber Containment daher nicht
40
+ * reparierbar. Sie wird auf den ERSTEN Scope mit der jeweiligen Adapter-Id
41
+ * abgebildet und die Zahl der so behandelten Facts in
42
+ * `nodeKindsAttributedByFallback` ausgewiesen. Betroffen sind nur die
43
+ * syntaktischen Metriken (`computeSyntactic`), nicht Cyclomatic/Cognitive.
44
+ */
45
+ import type { ComplexityFacts, RawCall } from "@untangle-code/domain";
46
+ /** Eine Adapter-Id, die mehr als einen Scope bezeichnet. */
47
+ export interface ScopeIdCollisionV1 {
48
+ /** Die mehrdeutige `symbolScopeId` aus dem Sprachadapter. */
49
+ adapterScopeId: string;
50
+ /** Wie viele verschiedene Scopes sie bezeichnet (>= 2). */
51
+ scopeCount: number;
52
+ /** Startzeilen der betroffenen Scopes, aufsteigend — zum Nachschlagen. */
53
+ startLines: number[];
54
+ }
55
+ export interface UniqueScopeIdsResultV1 {
56
+ /** Facts mit eindeutigen Scope-Ids; sonst strukturgleich zur Eingabe. */
57
+ facts: ComplexityFacts;
58
+ /** Aufrufe mit umgeschriebenem `callerScopeId`. */
59
+ calls: RawCall[];
60
+ /** Abbildung neue Id -> urspruengliche Adapter-Id, fuer Diagnosen. */
61
+ originalScopeIds: Map<string, string>;
62
+ /** Jede mehrdeutige Adapter-Id, nach Codepoints sortiert. Leer heisst: alles war eindeutig. */
63
+ collisions: ScopeIdCollisionV1[];
64
+ /** `NodeKindFact`s ohne Range, die per Fallback zugeordnet wurden (siehe Modul-Doku). */
65
+ nodeKindsAttributedByFallback: number;
66
+ }
67
+ /**
68
+ * Schreibt einen Facts-Satz auf eindeutige Scope-Ids um.
69
+ *
70
+ * @param facts Facts einer Datei, wie der Sprachadapter sie liefert.
71
+ * @param calls Rohaufrufe derselben Datei (`extractCalls`); ihr
72
+ * `callerScopeId` wird mit umgeschrieben, damit die Rekursionsmarkierung in
73
+ * `buildSymbolIR` weiter greift.
74
+ * @param idPrefix Praefix der neuen Ids, ueblicherweise der repo-relative
75
+ * Pfad. Er macht die Ids ueber Dateigrenzen hinweg eindeutig; innerhalb der
76
+ * Datei sorgt die Byte-Range dafuer.
77
+ *
78
+ * Kann ein Fact ueber Containment keinem Scope zugeordnet werden (er laege
79
+ * ausserhalb jeder Range seines eigenen Scopes — ein Adapterfehler anderer
80
+ * Art), bleibt seine Adapter-Id stehen. Die Aggregation sieht dann einen
81
+ * unbekannten Eltern-Scope und meldet ihn als Invariantenverstoss, statt eine
82
+ * saubere Zahl vorzutaeuschen.
83
+ */
84
+ export declare function withUniqueScopeIds(facts: ComplexityFacts, calls: readonly RawCall[], idPrefix: string): UniqueScopeIdsResultV1;
85
+ //# sourceMappingURL=scope-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-identity.d.ts","sourceRoot":"","sources":["../src/scope-identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EACV,eAAe,EAEf,OAAO,EAGR,MAAM,uBAAuB,CAAC;AAK/B,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,KAAK,EAAE,eAAe,CAAC;IACvB,mDAAmD;IACnD,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,sEAAsE;IACtE,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,+FAA+F;IAC/F,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,yFAAyF;IACzF,6BAA6B,EAAE,MAAM,CAAC;CACvC;AAiCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,KAAK,EAAE,SAAS,OAAO,EAAE,EACzB,QAAQ,EAAE,MAAM,GACf,sBAAsB,CA6GxB"}
@@ -0,0 +1,144 @@
1
+ import { ID_COMPONENT_SEPARATOR, compareCodepoints } from "@untangle-code/domain";
2
+ import { containsRange } from "./scope.js";
3
+ /** Byte-Range-basierte, innerhalb einer Datei eindeutige Id. */
4
+ function uniqueIdFor(prefix, range) {
5
+ return [prefix, "scope", String(range.startByte), String(range.endByte)].join(ID_COMPONENT_SEPARATOR);
6
+ }
7
+ /** Innerster Scope aus `candidates`, dessen Range `range` enthaelt. */
8
+ function innermostOwner(candidates, range) {
9
+ let best;
10
+ for (const candidate of candidates) {
11
+ if (!containsRange(candidate.scope.range, range))
12
+ continue;
13
+ if (best === undefined ||
14
+ candidate.scope.range.endByte - candidate.scope.range.startByte <
15
+ best.scope.range.endByte - best.scope.range.startByte) {
16
+ best = candidate;
17
+ }
18
+ }
19
+ return best;
20
+ }
21
+ /**
22
+ * Schreibt einen Facts-Satz auf eindeutige Scope-Ids um.
23
+ *
24
+ * @param facts Facts einer Datei, wie der Sprachadapter sie liefert.
25
+ * @param calls Rohaufrufe derselben Datei (`extractCalls`); ihr
26
+ * `callerScopeId` wird mit umgeschrieben, damit die Rekursionsmarkierung in
27
+ * `buildSymbolIR` weiter greift.
28
+ * @param idPrefix Praefix der neuen Ids, ueblicherweise der repo-relative
29
+ * Pfad. Er macht die Ids ueber Dateigrenzen hinweg eindeutig; innerhalb der
30
+ * Datei sorgt die Byte-Range dafuer.
31
+ *
32
+ * Kann ein Fact ueber Containment keinem Scope zugeordnet werden (er laege
33
+ * ausserhalb jeder Range seines eigenen Scopes — ein Adapterfehler anderer
34
+ * Art), bleibt seine Adapter-Id stehen. Die Aggregation sieht dann einen
35
+ * unbekannten Eltern-Scope und meldet ihn als Invariantenverstoss, statt eine
36
+ * saubere Zahl vorzutaeuschen.
37
+ */
38
+ export function withUniqueScopeIds(facts, calls, idPrefix) {
39
+ const rewritten = facts.symbolScopes.map((scope) => ({
40
+ scope,
41
+ uniqueId: uniqueIdFor(idPrefix, scope.range),
42
+ }));
43
+ const byAdapterId = new Map();
44
+ for (const entry of rewritten) {
45
+ const bucket = byAdapterId.get(entry.scope.symbolScopeId) ?? [];
46
+ bucket.push(entry);
47
+ byAdapterId.set(entry.scope.symbolScopeId, bucket);
48
+ }
49
+ const collisions = [];
50
+ for (const [adapterScopeId, bucket] of byAdapterId) {
51
+ if (bucket.length < 2)
52
+ continue;
53
+ collisions.push({
54
+ adapterScopeId,
55
+ scopeCount: bucket.length,
56
+ startLines: bucket.map((entry) => entry.scope.range.startLine).sort((a, b) => a - b),
57
+ });
58
+ }
59
+ collisions.sort((a, b) => compareCodepoints(a.adapterScopeId, b.adapterScopeId));
60
+ const originalScopeIds = new Map();
61
+ for (const entry of rewritten)
62
+ originalScopeIds.set(entry.uniqueId, entry.scope.symbolScopeId);
63
+ /** Neue Id fuer einen Fact mit Range. */
64
+ const resolve = (adapterScopeId, range) => {
65
+ const bucket = byAdapterId.get(adapterScopeId);
66
+ if (bucket === undefined || bucket.length === 0)
67
+ return adapterScopeId;
68
+ const first = bucket[0];
69
+ if (bucket.length === 1)
70
+ return first.uniqueId;
71
+ const owner = innermostOwner(bucket, range);
72
+ return owner === undefined ? adapterScopeId : owner.uniqueId;
73
+ };
74
+ let nodeKindsAttributedByFallback = 0;
75
+ const mappedNodeKinds = facts.nodeKinds.map((fact) => {
76
+ const bucket = byAdapterId.get(fact.symbolScopeId);
77
+ if (bucket === undefined || bucket.length === 0)
78
+ return fact;
79
+ if (bucket.length > 1)
80
+ nodeKindsAttributedByFallback++;
81
+ return { ...fact, symbolScopeId: bucket[0].uniqueId };
82
+ });
83
+ const mappedScopes = rewritten.map((entry) => {
84
+ const parentAdapterId = entry.scope.parentScopeId;
85
+ if (parentAdapterId === undefined) {
86
+ return { ...entry.scope, symbolScopeId: entry.uniqueId };
87
+ }
88
+ const parentBucket = byAdapterId.get(parentAdapterId);
89
+ let parentId;
90
+ if (parentBucket === undefined || parentBucket.length === 0) {
91
+ parentId = parentAdapterId;
92
+ }
93
+ else if (parentBucket.length === 1) {
94
+ parentId = parentBucket[0].uniqueId;
95
+ }
96
+ else {
97
+ const owner = innermostOwner(parentBucket.filter((candidate) => candidate !== entry), entry.scope.range);
98
+ parentId = owner === undefined ? parentAdapterId : owner.uniqueId;
99
+ }
100
+ return { ...entry.scope, symbolScopeId: entry.uniqueId, parentScopeId: parentId };
101
+ });
102
+ const nextFacts = {
103
+ symbolScopes: mappedScopes,
104
+ decisions: facts.decisions.map((fact) => ({
105
+ ...fact,
106
+ symbolScopeId: resolve(fact.symbolScopeId, fact.range),
107
+ })),
108
+ nestingEvents: facts.nestingEvents.map((fact) => ({
109
+ ...fact,
110
+ symbolScopeId: resolve(fact.symbolScopeId, fact.range),
111
+ })),
112
+ operators: facts.operators.map((fact) => ({
113
+ ...fact,
114
+ symbolScopeId: resolve(fact.symbolScopeId, fact.range),
115
+ })),
116
+ operands: facts.operands.map((fact) => ({
117
+ ...fact,
118
+ symbolScopeId: resolve(fact.symbolScopeId, fact.range),
119
+ })),
120
+ nodeKinds: mappedNodeKinds,
121
+ comments: facts.comments,
122
+ identifiers: facts.identifiers.map((fact) => ({
123
+ ...fact,
124
+ symbolScopeId: resolve(fact.symbolScopeId, fact.range),
125
+ })),
126
+ literals: facts.literals.map((fact) => ({
127
+ ...fact,
128
+ symbolScopeId: resolve(fact.symbolScopeId, fact.range),
129
+ })),
130
+ parseDiagnostics: facts.parseDiagnostics,
131
+ };
132
+ const nextCalls = calls.map((call) => ({
133
+ ...call,
134
+ callerScopeId: resolve(call.callerScopeId, call.range),
135
+ }));
136
+ return {
137
+ facts: nextFacts,
138
+ calls: nextCalls,
139
+ originalScopeIds,
140
+ collisions,
141
+ nodeKindsAttributedByFallback,
142
+ };
143
+ }
144
+ //# sourceMappingURL=scope-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-identity.js","sourceRoot":"","sources":["../src/scope-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Facts scoping and byte/line utilities shared by every calculator
3
+ * (architecture.md §15.1). Every calculator is scope-agnostic: it operates on
4
+ * whatever `ComplexityFacts` it is handed. `selectSymbolFacts` is how a
5
+ * caller narrows file-level facts down to one symbol's facts before calling
6
+ * a calculator for `scope: "symbol"`; passing the file's facts unfiltered is
7
+ * how a caller computes `scope: "file"`.
8
+ */
9
+ import type { ComplexityFacts, SourceRange } from "@untangle-code/domain";
10
+ /** True iff `inner` lies fully inside `outer` (byte-interval containment, both inclusive/exclusive per domain convention). */
11
+ export declare function containsRange(outer: SourceRange, inner: SourceRange): boolean;
12
+ /** True iff `outer` strictly contains `inner` (contains, but is not the identical range). */
13
+ export declare function strictlyContainsRange(outer: SourceRange, inner: SourceRange): boolean;
14
+ /** Stable identity key for deduplicating facts that reference the same source span. */
15
+ export declare function rangeKey(range: SourceRange): string;
16
+ /**
17
+ * Narrows file-level `ComplexityFacts` to the facts belonging to one symbol
18
+ * scope. Fact kinds that carry `symbolScopeId` (decisions, nestingEvents,
19
+ * operators, operands, nodeKinds, identifiers, literals) are filtered by
20
+ * exact id match — a symbol's own facts are attributed directly to it, never
21
+ * inherited from nested named symbols, which get their own `SymbolScopeFact`
22
+ * and their own facts (architecture.md §15.1; Untangle decision, see
23
+ * README "Entscheidungen" #1). `comments` and `parseDiagnostics` carry no
24
+ * `symbolScopeId` and are instead filtered by byte-range containment inside
25
+ * the symbol's own range.
26
+ *
27
+ * Returns empty fact arrays (rather than throwing) when `symbolScopeId` is
28
+ * not present in `facts.symbolScopes` — a caller passing a stale id gets an
29
+ * empty-but-valid `ComplexityFacts`, not a crash.
30
+ */
31
+ export declare function selectSymbolFacts(facts: ComplexityFacts, symbolScopeId: string): ComplexityFacts;
32
+ /** Slices `sourceText` by a byte-offset `SourceRange` (UTF-8, per domain convention). */
33
+ export declare function sliceByRange(sourceText: string, range: SourceRange): string;
34
+ /** Splits text into physical lines, normalizing CRLF/CR to LF first. */
35
+ export declare function splitLines(text: string): string[];
36
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAS1E,8HAA8H;AAC9H,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAE7E;AAED,6FAA6F;AAC7F,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAGrF;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAEnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,GAAG,eAAe,CAiBhG;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAG3E;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjD"}
package/dist/scope.js ADDED
@@ -0,0 +1,60 @@
1
+ function byScope(items, symbolScopeId) {
2
+ return items.filter((item) => item.symbolScopeId === symbolScopeId);
3
+ }
4
+ /** True iff `inner` lies fully inside `outer` (byte-interval containment, both inclusive/exclusive per domain convention). */
5
+ export function containsRange(outer, inner) {
6
+ return outer.startByte <= inner.startByte && inner.endByte <= outer.endByte;
7
+ }
8
+ /** True iff `outer` strictly contains `inner` (contains, but is not the identical range). */
9
+ export function strictlyContainsRange(outer, inner) {
10
+ if (!containsRange(outer, inner))
11
+ return false;
12
+ return outer.startByte !== inner.startByte || outer.endByte !== inner.endByte;
13
+ }
14
+ /** Stable identity key for deduplicating facts that reference the same source span. */
15
+ export function rangeKey(range) {
16
+ return `${range.startByte}:${range.endByte}`;
17
+ }
18
+ /**
19
+ * Narrows file-level `ComplexityFacts` to the facts belonging to one symbol
20
+ * scope. Fact kinds that carry `symbolScopeId` (decisions, nestingEvents,
21
+ * operators, operands, nodeKinds, identifiers, literals) are filtered by
22
+ * exact id match — a symbol's own facts are attributed directly to it, never
23
+ * inherited from nested named symbols, which get their own `SymbolScopeFact`
24
+ * and their own facts (architecture.md §15.1; Untangle decision, see
25
+ * README "Entscheidungen" #1). `comments` and `parseDiagnostics` carry no
26
+ * `symbolScopeId` and are instead filtered by byte-range containment inside
27
+ * the symbol's own range.
28
+ *
29
+ * Returns empty fact arrays (rather than throwing) when `symbolScopeId` is
30
+ * not present in `facts.symbolScopes` — a caller passing a stale id gets an
31
+ * empty-but-valid `ComplexityFacts`, not a crash.
32
+ */
33
+ export function selectSymbolFacts(facts, symbolScopeId) {
34
+ const symbolScope = facts.symbolScopes.find((s) => s.symbolScopeId === symbolScopeId);
35
+ const range = symbolScope?.range;
36
+ return {
37
+ symbolScopes: facts.symbolScopes.filter((s) => s.symbolScopeId === symbolScopeId),
38
+ decisions: byScope(facts.decisions, symbolScopeId),
39
+ nestingEvents: byScope(facts.nestingEvents, symbolScopeId),
40
+ operators: byScope(facts.operators, symbolScopeId),
41
+ operands: byScope(facts.operands, symbolScopeId),
42
+ nodeKinds: byScope(facts.nodeKinds, symbolScopeId),
43
+ comments: range ? facts.comments.filter((r) => containsRange(range, r)) : [],
44
+ identifiers: byScope(facts.identifiers, symbolScopeId),
45
+ literals: byScope(facts.literals, symbolScopeId),
46
+ parseDiagnostics: range
47
+ ? facts.parseDiagnostics.filter((d) => containsRange(range, d.range))
48
+ : [],
49
+ };
50
+ }
51
+ /** Slices `sourceText` by a byte-offset `SourceRange` (UTF-8, per domain convention). */
52
+ export function sliceByRange(sourceText, range) {
53
+ const buf = Buffer.from(sourceText, "utf8");
54
+ return buf.subarray(range.startByte, range.endByte).toString("utf8");
55
+ }
56
+ /** Splits text into physical lines, normalizing CRLF/CR to LF first. */
57
+ export function splitLines(text) {
58
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
59
+ }
60
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * StructuralCalculator (spec.md §16.5).
3
+ *
4
+ * Facts-derivable metrics are computed directly from `ComplexityFacts` (plus
5
+ * `sourceText`/`scopeRange` for the line-based ones). Coupling and API-surface
6
+ * metrics (`fan_in`, `fan_out`, `import_in_degree`, `import_out_degree`,
7
+ * `afferent_coupling`, `efferent_coupling`, `instability`,
8
+ * `inheritance_depth`, `public_api_count`) need whole-repository graph/export
9
+ * knowledge this facts-only package does not have — per the work order they
10
+ * are accepted as `StructuralExternalInput` and are only ever present in the
11
+ * output when the caller actually supplies them; they are never defaulted or
12
+ * guessed (see README "Entscheidungen" #4 for `instability`'s one derived
13
+ * exception).
14
+ *
15
+ * `parameter_count`, `return_count` and `exit_point_count` use a best-effort
16
+ * node-kind classifier (see `node-kind-heuristics.ts`) since `SymbolScopeFact`
17
+ * carries no structured parameter list — see README "Entscheidungen" #5 and
18
+ * `openIssues`.
19
+ */
20
+ import type { ComplexityFacts, SourceRange } from "@untangle-code/domain";
21
+ import type { MetricsResult, StructuralExternalInput } from "./types.js";
22
+ /**
23
+ * @param facts Facts already narrowed to the target scope (see `selectSymbolFacts`).
24
+ * @param sourceText Full original file content (UTF-8) — needed because
25
+ * `SourceRange` byte offsets are file-absolute.
26
+ * @param scopeRange The scope's own range (a `SymbolScopeFact.range` for
27
+ * `scope: "symbol"`, or the whole-file range for `scope: "file"`).
28
+ */
29
+ export declare function computeStructural(facts: ComplexityFacts, sourceText: string, scopeRange: SourceRange, external?: StructuralExternalInput, algorithmVersion?: string): MetricsResult;
30
+ //# sourceMappingURL=structural.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural.d.ts","sourceRoot":"","sources":["../src/structural.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAK1E,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AA0BzE;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,WAAW,EACvB,QAAQ,GAAE,uBAA4B,EACtC,gBAAgB,SAA2B,GAC1C,aAAa,CAoEf"}
@@ -0,0 +1,98 @@
1
+ import { classifyNodeKind } from "./node-kind-heuristics.js";
2
+ import { strictlyContainsRange } from "./scope.js";
3
+ import { classifyLines } from "./text-lines.js";
4
+ const FUNCTION_KINDS = new Set(["function", "arrow_function", "function_expression"]);
5
+ const METHOD_KINDS = new Set(["method", "constructor"]);
6
+ const CLASS_KINDS = new Set(["class"]);
7
+ const INTERFACE_KINDS = new Set(["interface"]);
8
+ function countByScopeKind(facts, kinds) {
9
+ return facts.symbolScopes.filter((s) => kinds.has(s.kind.toLowerCase())).length;
10
+ }
11
+ /** Interval-nesting depth of `target` among `pool`: 1 + number of other ranges in `pool` that strictly contain it. */
12
+ function nestingDepthOf(target, pool) {
13
+ let depth = 1;
14
+ for (const other of pool) {
15
+ if (other === target)
16
+ continue;
17
+ if (strictlyContainsRange(other, target))
18
+ depth++;
19
+ }
20
+ return depth;
21
+ }
22
+ function mean(values) {
23
+ if (values.length === 0)
24
+ return 0;
25
+ return values.reduce((sum, v) => sum + v, 0) / values.length;
26
+ }
27
+ /**
28
+ * @param facts Facts already narrowed to the target scope (see `selectSymbolFacts`).
29
+ * @param sourceText Full original file content (UTF-8) — needed because
30
+ * `SourceRange` byte offsets are file-absolute.
31
+ * @param scopeRange The scope's own range (a `SymbolScopeFact.range` for
32
+ * `scope: "symbol"`, or the whole-file range for `scope: "file"`).
33
+ */
34
+ export function computeStructural(facts, sourceText, scopeRange, external = {}, algorithmVersion = "untangle-structural-v1") {
35
+ const { physicalLoc, commentLines, docLines, blankLines } = classifyLines(sourceText, scopeRange, facts.comments);
36
+ const sloc = Math.max(physicalLoc - blankLines.size - commentLines.size, 0);
37
+ const nonContinuationNesting = facts.nestingEvents
38
+ .filter((n) => !n.continuation)
39
+ .map((n) => n.range);
40
+ const nestingDepths = nonContinuationNesting.map((range) => nestingDepthOf(range, nonContinuationNesting));
41
+ let parameterCount = 0;
42
+ let returnCount = 0;
43
+ let exitPointCount = 0;
44
+ for (const node of facts.nodeKinds) {
45
+ const classified = classifyNodeKind(node.kind);
46
+ if (classified === "parameter")
47
+ parameterCount++;
48
+ if (classified === "return") {
49
+ returnCount++;
50
+ exitPointCount++;
51
+ }
52
+ if (classified === "exit")
53
+ exitPointCount++;
54
+ }
55
+ const metrics = {
56
+ physical_loc: physicalLoc,
57
+ sloc,
58
+ blank_lines: blankLines.size,
59
+ comment_lines: commentLines.size,
60
+ doc_lines: docLines.size,
61
+ function_count: countByScopeKind(facts, FUNCTION_KINDS),
62
+ method_count: countByScopeKind(facts, METHOD_KINDS),
63
+ class_count: countByScopeKind(facts, CLASS_KINDS),
64
+ interface_count: countByScopeKind(facts, INTERFACE_KINDS),
65
+ parameter_count: parameterCount,
66
+ return_count: returnCount,
67
+ exit_point_count: exitPointCount,
68
+ max_nesting: nestingDepths.length > 0 ? Math.max(...nestingDepths) : 0,
69
+ avg_nesting: mean(nestingDepths),
70
+ };
71
+ if (external.fanIn !== undefined)
72
+ metrics["fan_in"] = external.fanIn;
73
+ if (external.fanOut !== undefined)
74
+ metrics["fan_out"] = external.fanOut;
75
+ if (external.importInDegree !== undefined)
76
+ metrics["import_in_degree"] = external.importInDegree;
77
+ if (external.importOutDegree !== undefined)
78
+ metrics["import_out_degree"] = external.importOutDegree;
79
+ if (external.afferentCoupling !== undefined)
80
+ metrics["afferent_coupling"] = external.afferentCoupling;
81
+ if (external.efferentCoupling !== undefined)
82
+ metrics["efferent_coupling"] = external.efferentCoupling;
83
+ if (external.inheritanceDepth !== undefined)
84
+ metrics["inheritance_depth"] = external.inheritanceDepth;
85
+ if (external.publicApiCount !== undefined)
86
+ metrics["public_api_count"] = external.publicApiCount;
87
+ if (external.instability !== undefined) {
88
+ metrics["instability"] = external.instability;
89
+ }
90
+ else if (external.afferentCoupling !== undefined && external.efferentCoupling !== undefined) {
91
+ const denom = external.afferentCoupling + external.efferentCoupling;
92
+ if (denom > 0) {
93
+ metrics["instability"] = external.efferentCoupling / denom;
94
+ }
95
+ }
96
+ return { metrics, algorithmVersion };
97
+ }
98
+ //# sourceMappingURL=structural.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structural.js","sourceRoot":"","sources":["../src/structural.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * SyntacticCalculator (spec.md §16.8).
3
+ *
4
+ * `ast_node_count`/`ast_max_depth`/`ast_avg_depth`/`node_kind_entropy` and the
5
+ * construct counters are derived straight from `ComplexityFacts.nodeKinds`
6
+ * (each entry already carries its own AST depth) and the other typed fact
7
+ * arrays.
8
+ *
9
+ * `ast_branching_factor_avg`/`ast_branching_factor_max` (spec.md §16.8,
10
+ * "Mindestens") are reconstructed EXACTLY from the `nodeKinds` sequence:
11
+ * the adapter's single walk emits one `NodeKindFact` per visited node in
12
+ * DFS PRE-ORDER (that ordering is this calculator's input contract for the
13
+ * two branching keys; `selectSymbolFacts` filtering preserves it), and in a
14
+ * pre-order depth sequence every node's parent is the nearest preceding
15
+ * entry with a smaller depth — so per-node child counts, and from them the
16
+ * branching factors, follow without any parent pointers. This is the real
17
+ * AST branching factor, not an approximation (see README "Entscheidungen"
18
+ * #7); the optional `irRoot` is only used as a fallback when `nodeKinds` is
19
+ * empty (e.g. hand-built IR fixtures), because the sparse control-flow IR
20
+ * would otherwise understate branching.
21
+ */
22
+ import type { ComplexityFacts, ComplexityIRNode } from "@untangle-code/domain";
23
+ import type { MetricsResult } from "./types.js";
24
+ /**
25
+ * @param facts Facts already narrowed to the target scope.
26
+ * @param sloc The scope's own SLOC, as computed by `computeStructural` —
27
+ * passed in rather than recomputed so `ast_nodes_per_sloc` is consistent
28
+ * with the structural snapshot instead of a second, possibly divergent,
29
+ * line-scan.
30
+ * @param irRoot Optional IR subtree for the same scope; only used as the
31
+ * branching-factor source when `facts.nodeKinds` is empty (the pre-order
32
+ * reconstruction over real node-kind facts is exact and takes precedence).
33
+ */
34
+ export declare function computeSyntactic(facts: ComplexityFacts, sloc: number, irRoot?: ComplexityIRNode, algorithmVersion?: string): MetricsResult;
35
+ //# sourceMappingURL=syntactic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syntactic.d.ts","sourceRoot":"","sources":["../src/syntactic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAgB,MAAM,uBAAuB,CAAC;AAK7F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA6DhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,gBAAgB,EACzB,gBAAgB,SAA0B,GACzC,aAAa,CAgFf"}