code-gauge 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -108
- package/dist/cli.cjs +3 -3
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/cliConfig.cjs +1 -1
- package/dist/cliConfig.cjs.map +1 -1
- package/dist/cliConfig.d.ts +16 -84
- package/dist/cliConfig.js +1 -1
- package/dist/cliConfig.js.map +1 -1
- package/dist/crossFileDuplication.cjs +1 -1
- package/dist/crossFileDuplication.cjs.map +1 -1
- package/dist/crossFileDuplication.d.ts +9 -0
- package/dist/crossFileDuplication.js +1 -1
- package/dist/crossFileDuplication.js.map +1 -1
- package/dist/duplication.cjs +1 -1
- package/dist/duplication.cjs.map +1 -1
- package/dist/duplication.d.ts +13 -2
- package/dist/duplication.js +1 -1
- package/dist/duplication.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/languages.cjs +1 -1
- package/dist/languages.cjs.map +1 -1
- package/dist/languages.js +1 -1
- package/dist/languages.js.map +1 -1
- package/dist/metrics.cjs +1 -2
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.js +1 -2
- package/dist/metrics.js.map +1 -1
- package/dist/nativeMetrics.cjs +1 -1
- package/dist/nativeMetrics.cjs.map +1 -1
- package/dist/nativeMetrics.d.ts +2 -2
- package/dist/nativeMetrics.js +1 -1
- package/dist/nativeMetrics.js.map +1 -1
- package/dist/types.d.ts +11 -76
- package/package.json +7 -5
- package/dist/architectureMetrics.cjs +0 -2
- package/dist/architectureMetrics.cjs.map +0 -1
- package/dist/architectureMetrics.d.ts +0 -41
- package/dist/architectureMetrics.js +0 -2
- package/dist/architectureMetrics.js.map +0 -1
- package/dist/typescriptProject.cjs +0 -3
- package/dist/typescriptProject.cjs.map +0 -1
- package/dist/typescriptProject.d.ts +0 -24
- package/dist/typescriptProject.js +0 -3
- package/dist/typescriptProject.js.map +0 -1
package/dist/nativeMetrics.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";const e=require("./languages.cjs");let t=require("node:module");const n=new Map(e.defaultLanguages.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{
|
|
1
|
+
"use strict";const e=require("./languages.cjs");let t=require("node:module");const n=new Map(e.defaultLanguages.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{let e=(0,t.createRequire)(require("url").pathToFileURL(__filename).href)(`../native/code-gauge.node`);e.payloadVersion?.()===2&&(i=e)}catch{}return i}exports.isNativeBackendAvailable=o,exports.measureWithNativeBackend=a;
|
|
2
2
|
//# sourceMappingURL=nativeMetrics.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeMetrics.cjs","names":["defaultLanguages","createRequire"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume,
|
|
1
|
+
{"version":3,"file":"nativeMetrics.cjs","names":["defaultLanguages","createRequire"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n /** Absent on stale builds that predate payload versioning. */\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields (e.g. duplication.duplicateLineNumbers) instead of falling back to the\n * TypeScript backend.\n */\nconst expectedPayloadVersion = 2;\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n const binding = requireNative('../native/code-gauge.node') as NativeBinding;\n if (binding.payloadVersion?.() === expectedPayloadVersion) {\n cachedBinding = binding;\n }\n // A version mismatch (or a build too old to report one) leaves the binding unused, like a\n // missing addon: rebuild with `yarn build-native` to re-enable the native backend.\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"6EAiCA,MAEM,EAAwB,IAAI,IAAIA,EAAAA,iBAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,IAAM,GAAA,EADgBC,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IACM,CAAC,CAAC,2BAA2B,EACrD,EAAQ,iBAAiB,IAAM,IACjC,EAAgB,EAIpB,MAAQ,CAER,CACA,OAAO,CACT"}
|
package/dist/nativeMetrics.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CodeMetrics, LanguageDefinition } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Halstead counts measured natively; the derived float metrics (volume,
|
|
3
|
+
* Halstead counts measured natively; the derived float metrics (volume, effort, ...) are
|
|
4
4
|
* computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and
|
|
5
5
|
* the native backend must be bit-identical to the TypeScript one.
|
|
6
6
|
*/
|
|
@@ -10,7 +10,7 @@ export interface NativeHalsteadCounts {
|
|
|
10
10
|
totalOperators: number;
|
|
11
11
|
totalOperands: number;
|
|
12
12
|
}
|
|
13
|
-
export interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | '
|
|
13
|
+
export interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'syntaxTree'> {
|
|
14
14
|
halsteadCounts: NativeHalsteadCounts;
|
|
15
15
|
syntaxTree?: string;
|
|
16
16
|
}
|
package/dist/nativeMetrics.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{defaultLanguages as e}from"./languages.js";import{createRequire as t}from"node:module";const n=new Map(e.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{
|
|
1
|
+
import{defaultLanguages as e}from"./languages.js";import{createRequire as t}from"node:module";const n=new Map(e.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{let e=t(import.meta.url)(`../native/code-gauge.node`);e.payloadVersion?.()===2&&(i=e)}catch{}return i}export{o as isNativeBackendAvailable,a as measureWithNativeBackend};
|
|
2
2
|
//# sourceMappingURL=nativeMetrics.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume,
|
|
1
|
+
{"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, effort, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n /** Absent on stale builds that predate payload versioning. */\n payloadVersion?(): number;\n}\n\n/**\n * Must equal `payload_version` in native/src/lib.rs. A previously built addon survives a\n * `git pull` untouched, so without this handshake it would silently return payloads missing\n * newer fields (e.g. duplication.duplicateLineNumbers) instead of falling back to the\n * TypeScript backend.\n */\nconst expectedPayloadVersion = 2;\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n const binding = requireNative('../native/code-gauge.node') as NativeBinding;\n if (binding.payloadVersion?.() === expectedPayloadVersion) {\n cachedBinding = binding;\n }\n // A version mismatch (or a build too old to report one) leaves the binding unused, like a\n // missing addon: rebuild with `yarn build-native` to re-enable the native backend.\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"8FAiCA,MAEM,EAAwB,IAAI,IAAI,EAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,IAAM,EADgB,EAAc,YAAY,GACpB,CAAC,CAAC,2BAA2B,EACrD,EAAQ,iBAAiB,IAAM,IACjC,EAAgB,EAIpB,MAAQ,CAER,CACA,OAAO,CACT"}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ export interface LanguageDefinition {
|
|
|
6
6
|
parserLanguage: ParserLanguage;
|
|
7
7
|
aliases?: readonly string[];
|
|
8
8
|
functionNodeTypes?: readonly string[];
|
|
9
|
-
classNodeTypes?: readonly string[];
|
|
10
9
|
decisionNodeTypes?: readonly string[];
|
|
11
10
|
nestingNodeTypes?: readonly string[];
|
|
12
11
|
/** Node types that each count as one non-commenting source statement (NCSS). */
|
|
@@ -56,10 +55,7 @@ export interface HalsteadMetrics {
|
|
|
56
55
|
vocabulary: number;
|
|
57
56
|
length: number;
|
|
58
57
|
volume: number;
|
|
59
|
-
difficulty: number;
|
|
60
58
|
effort: number;
|
|
61
|
-
time: number;
|
|
62
|
-
bugs: number;
|
|
63
59
|
}
|
|
64
60
|
export interface FunctionMetrics {
|
|
65
61
|
name?: string;
|
|
@@ -73,8 +69,6 @@ export interface FunctionMetrics {
|
|
|
73
69
|
startLine: number;
|
|
74
70
|
startColumn: number;
|
|
75
71
|
endLine: number;
|
|
76
|
-
returnsJsx: boolean;
|
|
77
|
-
cyclomaticComplexity: number;
|
|
78
72
|
cognitiveComplexity: number;
|
|
79
73
|
nestingDepth: number;
|
|
80
74
|
/**
|
|
@@ -83,62 +77,19 @@ export interface FunctionMetrics {
|
|
|
83
77
|
* class bodies are included, so summing over functions counts shared regions more than once.
|
|
84
78
|
*/
|
|
85
79
|
ncss: number;
|
|
86
|
-
callCount: number;
|
|
87
|
-
uniqueCalleeCount: number;
|
|
88
|
-
fanIn: number;
|
|
89
|
-
fanOut: number;
|
|
90
80
|
parameterCount: number;
|
|
91
|
-
recursive: boolean;
|
|
92
|
-
}
|
|
93
|
-
export interface CallGraphMetrics {
|
|
94
|
-
callCount: number;
|
|
95
|
-
uniqueCalleeCount: number;
|
|
96
|
-
internalEdgeCount: number;
|
|
97
|
-
recursiveFunctionCount: number;
|
|
98
|
-
maxFanIn: number;
|
|
99
|
-
maxFanOut: number;
|
|
100
|
-
maxCallDepth: number;
|
|
101
|
-
}
|
|
102
|
-
export interface CouplingMetrics {
|
|
103
|
-
importCount: number;
|
|
104
|
-
importSourceCount: number;
|
|
105
|
-
relativeImportCount: number;
|
|
106
|
-
externalImportCount: number;
|
|
107
|
-
exportCount: number;
|
|
108
|
-
}
|
|
109
|
-
export interface DeclarationMetrics {
|
|
110
|
-
exported: boolean;
|
|
111
|
-
name: string;
|
|
112
|
-
startLine: number;
|
|
113
|
-
}
|
|
114
|
-
export interface ModuleMetrics {
|
|
115
|
-
declarations: DeclarationMetrics[];
|
|
116
|
-
importSources: string[];
|
|
117
|
-
}
|
|
118
|
-
export interface CohesionMetrics {
|
|
119
|
-
averageFunctionIdentifierOverlap: number;
|
|
120
|
-
sharedIdentifierCount: number;
|
|
121
|
-
uniqueIdentifierCount: number;
|
|
122
|
-
}
|
|
123
|
-
export interface SyntaxFeatureMetrics {
|
|
124
|
-
assignmentCount: number;
|
|
125
|
-
awaitExpressionCount: number;
|
|
126
|
-
loopStatementCount: number;
|
|
127
|
-
mutableBindingCount: number;
|
|
128
|
-
returnStatementCount: number;
|
|
129
|
-
throwStatementCount: number;
|
|
130
|
-
tryStatementCount: number;
|
|
131
81
|
}
|
|
132
82
|
/**
|
|
133
83
|
* Within-file structural duplication: copy-pasted regions whose normalized token sequence repeats.
|
|
134
84
|
* Identifiers are anonymized consistently by first-occurrence order and literals by kind, so
|
|
135
|
-
* consistently renamed copies match.
|
|
85
|
+
* consistently renamed copies match.
|
|
136
86
|
*/
|
|
137
87
|
export interface DuplicationMetrics {
|
|
138
88
|
/**
|
|
139
89
|
* Number of redundant (extra) duplicated regions: sum over groups of (groupSize - 1) times the
|
|
140
|
-
* matched fragments per occurrence, so a gapped
|
|
141
|
-
*
|
|
90
|
+
* matched fragments per occurrence, so merging a gapped clone's fragments into one group does
|
|
91
|
+
* not halve duplicateBlockCount — an edited two-fragment pair still counts 2, exactly as its
|
|
92
|
+
* unmerged fragments did.
|
|
142
93
|
*/
|
|
143
94
|
duplicateBlockCount: number;
|
|
144
95
|
/** Number of distinct normalized token sequences that appear more than once. */
|
|
@@ -147,6 +98,13 @@ export interface DuplicationMetrics {
|
|
|
147
98
|
duplicateBlockGroups: DuplicateBlockOccurrence[][];
|
|
148
99
|
/** Number of distinct lines covered by any counted duplicate occurrence (originals included). */
|
|
149
100
|
duplicateLineCount: number;
|
|
101
|
+
/**
|
|
102
|
+
* The 1-based lines behind duplicateLineCount, sorted ascending: code lines carrying matched
|
|
103
|
+
* tokens. Exposed so consumers combining within-file and cross-file coverage can union exact
|
|
104
|
+
* line sets instead of over-counting from block bounding ranges (which include the unmatched gap
|
|
105
|
+
* of a merged clone and comment/blank lines).
|
|
106
|
+
*/
|
|
107
|
+
duplicateLineNumbers: number[];
|
|
150
108
|
/**
|
|
151
109
|
* duplicateLineCount / code lines (0 when the file has no code). Code lines are the denominator
|
|
152
110
|
* because duplicated lines are counted from matched tokens, which only ever land on code lines;
|
|
@@ -160,40 +118,17 @@ export interface DuplicateBlockOccurrence {
|
|
|
160
118
|
endLine: number;
|
|
161
119
|
startLine: number;
|
|
162
120
|
}
|
|
163
|
-
export interface TypeComplexityMetrics {
|
|
164
|
-
typeAnnotationCount: number;
|
|
165
|
-
typeAliasCount: number;
|
|
166
|
-
interfaceCount: number;
|
|
167
|
-
genericParameterCount: number;
|
|
168
|
-
unionTypeCount: number;
|
|
169
|
-
intersectionTypeCount: number;
|
|
170
|
-
conditionalTypeCount: number;
|
|
171
|
-
typeAssertionCount: number;
|
|
172
|
-
nonNullAssertionCount: number;
|
|
173
|
-
satisfiesExpressionCount: number;
|
|
174
|
-
}
|
|
175
121
|
export interface CodeMetrics {
|
|
176
122
|
language: LanguageName;
|
|
177
123
|
bytes: number;
|
|
178
124
|
lines: LineMetrics;
|
|
179
125
|
functions: FunctionMetrics[];
|
|
180
|
-
classCount: number;
|
|
181
|
-
functionCount: number;
|
|
182
|
-
cyclomaticComplexity: number;
|
|
183
|
-
maxCyclomaticComplexity: number;
|
|
184
126
|
cognitiveComplexity: number;
|
|
185
127
|
maxCognitiveComplexity: number;
|
|
186
128
|
nestingDepth: number;
|
|
187
129
|
/** Non-commenting source statements in the whole file; every statement counts exactly once. */
|
|
188
130
|
ncssCount: number;
|
|
189
|
-
callGraph: CallGraphMetrics;
|
|
190
|
-
coupling: CouplingMetrics;
|
|
191
|
-
module: ModuleMetrics;
|
|
192
|
-
cohesion: CohesionMetrics;
|
|
193
|
-
syntaxFeatures: SyntaxFeatureMetrics;
|
|
194
|
-
typeComplexity: TypeComplexityMetrics;
|
|
195
131
|
duplication: DuplicationMetrics;
|
|
196
132
|
halstead: HalsteadMetrics;
|
|
197
|
-
maintainabilityIndex: number;
|
|
198
133
|
syntaxTree?: string;
|
|
199
134
|
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-gauge",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Measure code metrics with tree-sitter.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
7
7
|
"tree-sitter",
|
|
8
8
|
"metrics",
|
|
9
9
|
"loc",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"cognitive-complexity",
|
|
11
|
+
"ncss",
|
|
12
|
+
"duplication",
|
|
13
|
+
"refactoring"
|
|
12
14
|
],
|
|
13
15
|
"repository": {
|
|
14
16
|
"type": "git",
|
|
@@ -57,8 +59,7 @@
|
|
|
57
59
|
"tree-sitter-python": "^0.21.0",
|
|
58
60
|
"tree-sitter-ruby": "^0.21.0",
|
|
59
61
|
"tree-sitter-rust": "^0.21.0",
|
|
60
|
-
"tree-sitter-typescript": "^0.21.2"
|
|
61
|
-
"typescript": "7.0.2"
|
|
62
|
+
"tree-sitter-typescript": "^0.21.2"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@tsconfig/bun": "1.0.10",
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
"oxlint-tsgolint": "7.0.2001",
|
|
78
79
|
"semantic-release": "25.0.5",
|
|
79
80
|
"sort-package-json": "4.0.0",
|
|
81
|
+
"typescript": "7.0.2",
|
|
80
82
|
"vitest": "4.1.9"
|
|
81
83
|
},
|
|
82
84
|
"engines": {
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);function n(e,n){let i=e.map(e=>({...e,relativeFile:t.default.relative(n,e.file)||t.default.basename(e.file)})),a=r(i),o=b(i),s=w(o),c=i.map(e=>{let t=a.dependenciesByFile.get(e.relativeFile)??new Set,n=a.nonRelativeLocalImportCountByFile.get(e.relativeFile)??0,r=T(e.metrics),i=E(e.metrics,t.size,n);return{file:e.relativeFile,directLocalDependencyCount:t.size,duplicateSymbolGroupCount:s.get(e.relativeFile)??0,structuralBreadthScore:i.length,structuralCoordination:r,structuralFeatureGroups:i,transitiveLocalDependencyCount:y(e.relativeFile,a.dependenciesByFile)}});return{duplicateSymbolGroups:o,files:c,maxDirectLocalDependencyCount:k(c,`directLocalDependencyCount`),maxDuplicateSymbolGroupCount:k(c,`duplicateSymbolGroupCount`),maxStructuralBreadthScore:k(c,`structuralBreadthScore`),maxStructuralCoordinationScore:Math.max(0,...c.map(e=>e.structuralCoordination.score)),maxTransitiveLocalDependencyCount:k(c,`transitiveLocalDependencyCount`)}}function r(e){let t=new Set(e.map(e=>e.relativeFile)),n=m(e),r=new Map,i=new Map;for(let a of e){let e=new Set,o=0;for(let r of a.metrics.module.importSources){let i=s(a.relativeFile,r,t,n);i&&i!==a.relativeFile&&(e.add(i),g(a.relativeFile)===`.java`&&!r.startsWith(`.`)&&(o+=1))}r.set(a.relativeFile,e),i.set(a.relativeFile,o)}return{dependenciesByFile:r,nonRelativeLocalImportCountByFile:i}}const i=new Set([`.js`,`.jsx`,`.ts`,`.tsx`,`.mjs`,`.cjs`,`.mts`,`.cts`]),a=new Set([`.c`,`.c++`,`.cc`,`.cp`,`.cpp`,`.cxx`,`.h`,`.hh`,`.hpp`,`.hxx`,`.tcc`]),o=new Map([[`.js`,[`.ts`,`.tsx`]],[`.jsx`,[`.tsx`]],[`.mjs`,[`.mts`]],[`.cjs`,[`.cts`]]]);function s(e,n,r,a){let s=g(e);if(!n.startsWith(`.`))return s===`.rs`&&/^(?:crate|self|super)(?:::|$)/u.test(n)?u(e,n,r):s===`.java`?h(n,a):void 0;let l=_(e,n);if(i.has(s))for(let e of l){let n=o.get(t.default.extname(e));if(!n)continue;let i=e.slice(0,-t.default.extname(e).length);for(let e of n)if(r.has(`${i}${e}`))return`${i}${e}`}for(let e of l)if(r.has(e))return e;let{extensions:d,indexFiles:f}=c(e);for(let e of l){for(let t of d)if(r.has(`${e}${t}`))return`${e}${t}`;for(let n of f){let i=t.default.join(e,n);if(r.has(i))return i}}}function c(e){let t=g(e);return t===`.rb`?{extensions:[`.rb`],indexFiles:[]}:t===`.py`?{extensions:[`.py`],indexFiles:[`__init__.py`]}:a.has(t)?{extensions:[],indexFiles:[]}:i.has(t)?{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`,`.mts`,`.cts`,`.mjs`,`.cjs`],indexFiles:[`index.ts`,`index.tsx`,`index.js`]}:{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`,`.py`,`.rb`,`.h`,`.hpp`,`.hh`,`.hxx`,`.c`,`.cpp`,`.cc`,`.cxx`],indexFiles:[`index.ts`,`index.tsx`,`index.js`,`__init__.py`]}}const l=new Set([`mod.rs`,`lib.rs`,`main.rs`]);function u(e,n,r){let i=n.split(`::`),a=i.shift(),o=0;for(;a===`super`&&i[0]===`super`;)i.shift(),o+=1;let s=i.join(`/`);if(!s)return;let c=p(t.default.dirname(e)),u=l.has(t.default.basename(e)),m=t.default.join(c,t.default.basename(e,`.rs`)),h;h=a===`self`?u?[c]:[m,c]:a===`super`?(u?[p(t.default.dirname(c)),c]:[c,p(t.default.dirname(c))]).map(e=>f(e,o)):d(c,r);let g=[s,i.slice(0,-1).join(`/`)].filter(Boolean);for(let n of h)for(let i of g)for(let a of[t.default.join(n,`${i}.rs`),t.default.join(n,i,`mod.rs`)])if(a!==e&&r.has(a))return a}function d(e,n){let r=[],i=e;for(;r.push(i),i!==``;)i=p(t.default.dirname(i));let a=r.filter(e=>n.has(t.default.join(e,`lib.rs`))||n.has(t.default.join(e,`main.rs`)));return a.length>0?a:r}function f(e,n){let r=e;for(let e=0;e<n&&r!==``;e+=1)r=p(t.default.dirname(r));return r}function p(e){return e===`.`?``:e}function m(e){let n=new Map;for(let{relativeFile:r,metrics:i}of e){if(g(r)!==`.java`)continue;let e=t.default.basename(r,t.default.extname(r)),a=(e,t)=>{let i=n.get(e)??[];i.some(e=>e.file===r&&e.qualifiedPath===t)||(i.push({file:r,qualifiedPath:t}),n.set(e,i))},o=!1;for(let t of i.module.declarations){let n=t.name.lastIndexOf(`::`),r=n===-1?t.name:t.name.slice(n+2),i=n===-1?void 0:t.name.slice(0,n).replaceAll(`.`,`/`);a(r,i?`${i}/${r}`:void 0),o||=r===e}o||a(e,void 0)}for(let e of n.values())e.sort((e,t)=>e.file.localeCompare(t.file));return n}function h(e,n){if(e.endsWith(`.*`))return;let r=e.split(`.`);for(let e=r.length;e>=1;--e){let i=r.slice(0,e),a=i.at(-1);if(!a)continue;let o=i.join(`/`),s=n.get(a)??[],c=s.find(e=>e.qualifiedPath===o);if(c)return c.file;let l=s.find(e=>{if(e.qualifiedPath)return!1;let n=e.file.replaceAll(`\\`,`/`),r=n.slice(0,n.length-t.default.extname(n).length);return r===o||r.endsWith(`/${o}`)});if(l)return l.file}}function g(e){return t.default.extname(e).toLowerCase()}function _(e,n){let r=t.default.dirname(e);if(v(n))return[t.default.normalize(t.default.join(r,n))];let i=/^(?<dots>\.+)(?<module>.*)$/u.exec(n),a=i?.groups?.dots,o=i?.groups?.module;if(!a||o===void 0)return[];let s=r;for(let e=1;e<a.length;e+=1)s=t.default.dirname(s);let c=o.replaceAll(`.`,t.default.sep);return[t.default.normalize(t.default.join(s,c))]}function v(e){return e.startsWith(`./`)||e.startsWith(`../`)||e.includes(`/`)}function y(e,t){let n=new Set([e]),r=[...t.get(e)??[]];for(;r.length>0;){let e=r.pop();!e||n.has(e)||(n.add(e),r.push(...t.get(e)??[]))}return n.size-1}function b(e){let t=new Map;for(let n of e)for(let e of n.metrics.module.declarations){if(!x(e))continue;let r=t.get(e.name)??[];r.push(S(n.relativeFile,e)),t.set(e.name,r)}return[...t.entries()].flatMap(([e,t])=>{let n=[...new Set(t.map(e=>e.file))].toSorted();return n.length>1?[{declarations:t.toSorted(C),files:n,name:e}]:[]}).toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name))}function x(e){return(e.name.split(/::|\./u).at(-1)??e.name).length>=5}function S(e,t){return{file:e,line:t.startLine}}function C(e,t){return e.file.localeCompare(t.file)||e.line-t.line}function w(e){let t=new Map;for(let n of e)for(let e of n.files)t.set(e,(t.get(e)??0)+1);return t}function T(e){let t=e.syntaxFeatures.awaitExpressionCount,n=e.syntaxFeatures.tryStatementCount+e.syntaxFeatures.throwStatementCount,r=e.syntaxFeatures.mutableBindingCount+e.syntaxFeatures.assignmentCount,i=e.cyclomaticComplexity+e.syntaxFeatures.loopStatementCount+e.syntaxFeatures.returnStatementCount,a=e.callGraph.callCount+e.callGraph.internalEdgeCount*2+e.callGraph.maxCallDepth*3;return{asyncBoundaryCount:t,branchingScore:i,exceptionHandlingCount:n,moduleInteractionScore:a,score:t*2+n*3+r+i+a,stateMutationScore:r}}function E(e,t,n){let r=new Set;return D(r,`class-shapes`,e.classCount>0),D(r,`control-flow`,e.cognitiveComplexity>0),D(r,`external-dependencies`,e.coupling.externalImportCount-n>0),D(r,`functions`,e.functionCount>0),D(r,`local-dependencies`,e.coupling.relativeImportCount>0||t>0),D(r,`module-api`,e.coupling.exportCount>0||e.module.declarations.length>0),D(r,`state-mutation`,e.syntaxFeatures.mutableBindingCount+e.syntaxFeatures.assignmentCount>0),D(r,`type-shapes`,O(e)),[...r].toSorted()}function D(e,t,n){n&&e.add(t)}function O(e){return e.typeComplexity.typeAnnotationCount+e.typeComplexity.typeAliasCount+e.typeComplexity.interfaceCount+e.typeComplexity.genericParameterCount+e.typeComplexity.unionTypeCount+e.typeComplexity.intersectionTypeCount+e.typeComplexity.conditionalTypeCount>0}function k(e,t){return Math.max(0,...e.map(e=>e[t]).filter(e=>typeof e==`number`))}exports.measureArchitecture=n;
|
|
2
|
-
//# sourceMappingURL=architectureMetrics.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"architectureMetrics.cjs","names":["path"],"sources":["../src/architectureMetrics.ts"],"sourcesContent":["import path from 'node:path';\nimport type { CodeMetrics, DeclarationMetrics } from './types.js';\n\nexport interface ArchitectureSourceFile {\n file: string;\n metrics: CodeMetrics;\n}\n\nexport interface ArchitectureFileMetrics {\n directLocalDependencyCount: number;\n duplicateSymbolGroupCount: number;\n file: string;\n structuralBreadthScore: number;\n structuralCoordination: StructuralCoordinationMetrics;\n structuralFeatureGroups: string[];\n transitiveLocalDependencyCount: number;\n}\n\nexport interface ArchitectureMetrics {\n duplicateSymbolGroups: DuplicateSymbolGroup[];\n files: ArchitectureFileMetrics[];\n maxDirectLocalDependencyCount: number;\n maxDuplicateSymbolGroupCount: number;\n maxStructuralBreadthScore: number;\n maxStructuralCoordinationScore: number;\n maxTransitiveLocalDependencyCount: number;\n}\n\nexport interface DuplicateSymbolGroup {\n declarations: DuplicateSymbolDeclaration[];\n files: string[];\n name: string;\n}\n\nexport interface DuplicateSymbolDeclaration {\n file: string;\n line: number;\n}\n\nexport interface StructuralCoordinationMetrics {\n asyncBoundaryCount: number;\n branchingScore: number;\n exceptionHandlingCount: number;\n moduleInteractionScore: number;\n score: number;\n stateMutationScore: number;\n}\n\ninterface SourceFile {\n file: string;\n metrics: CodeMetrics;\n relativeFile: string;\n}\n\nconst duplicateSymbolNameLengthThreshold = 5;\n\nexport function measureArchitecture(\n files: readonly ArchitectureSourceFile[],\n displayRoot: string\n): ArchitectureMetrics {\n const sourceFiles = files.map((file) => ({\n ...file,\n relativeFile: path.relative(displayRoot, file.file) || path.basename(file.file),\n }));\n const dependencyGraph = measureDependencyGraph(sourceFiles);\n const duplicateSymbolGroups = measureDuplicateSymbolGroups(sourceFiles);\n const duplicateGroupCountByFile = measureDuplicateGroupCountByFile(duplicateSymbolGroups);\n const metrics = sourceFiles.map((sourceFile) => {\n const directDependencies = dependencyGraph.dependenciesByFile.get(sourceFile.relativeFile) ?? new Set<string>();\n const nonRelativeLocalImportCount =\n dependencyGraph.nonRelativeLocalImportCountByFile.get(sourceFile.relativeFile) ?? 0;\n const structuralCoordination = measureStructuralCoordination(sourceFile.metrics);\n const structuralFeatureGroups = measureStructuralFeatureGroups(\n sourceFile.metrics,\n directDependencies.size,\n nonRelativeLocalImportCount\n );\n return {\n file: sourceFile.relativeFile,\n directLocalDependencyCount: directDependencies.size,\n duplicateSymbolGroupCount: duplicateGroupCountByFile.get(sourceFile.relativeFile) ?? 0,\n structuralBreadthScore: structuralFeatureGroups.length,\n structuralCoordination,\n structuralFeatureGroups,\n transitiveLocalDependencyCount: measureTransitiveDependencyCount(\n sourceFile.relativeFile,\n dependencyGraph.dependenciesByFile\n ),\n };\n });\n\n return {\n duplicateSymbolGroups,\n files: metrics,\n maxDirectLocalDependencyCount: maxFileMetric(metrics, 'directLocalDependencyCount'),\n maxDuplicateSymbolGroupCount: maxFileMetric(metrics, 'duplicateSymbolGroupCount'),\n maxStructuralBreadthScore: maxFileMetric(metrics, 'structuralBreadthScore'),\n maxStructuralCoordinationScore: Math.max(0, ...metrics.map((file) => file.structuralCoordination.score)),\n maxTransitiveLocalDependencyCount: maxFileMetric(metrics, 'transitiveLocalDependencyCount'),\n };\n}\n\ninterface DependencyGraph {\n dependenciesByFile: Map<string, Set<string>>;\n /** Import sources without a relative prefix (Java dotted paths) that still resolved locally. */\n nonRelativeLocalImportCountByFile: Map<string, number>;\n}\n\nfunction measureDependencyGraph(files: SourceFile[]): DependencyGraph {\n const fileSet = new Set(files.map((file) => file.relativeFile));\n const javaFileIndex = buildJavaFileIndex(files);\n const dependenciesByFile = new Map<string, Set<string>>();\n const nonRelativeLocalImportCountByFile = new Map<string, number>();\n for (const file of files) {\n const dependencies = new Set<string>();\n let nonRelativeLocalImportCount = 0;\n for (const source of file.metrics.module.importSources) {\n const resolved = resolveLocalImport(file.relativeFile, source, fileSet, javaFileIndex);\n // A file importing itself (`import './a.js'` inside a.ts) is not a dependency.\n if (resolved && resolved !== file.relativeFile) {\n dependencies.add(resolved);\n // Only Java needs the external-count correction: its dotted imports classify as external\n // in coupling metrics. Rust crate::/self::/super:: sources already classify as relative,\n // so counting them would double-subtract from externalImportCount.\n if (fileExtension(file.relativeFile) === '.java' && !source.startsWith('.')) {\n nonRelativeLocalImportCount += 1;\n }\n }\n }\n dependenciesByFile.set(file.relativeFile, dependencies);\n nonRelativeLocalImportCountByFile.set(file.relativeFile, nonRelativeLocalImportCount);\n }\n return { dependenciesByFile, nonRelativeLocalImportCountByFile };\n}\n\nconst jsExtensions = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']);\n// Must cover every extension the CLI's languageByExtension maps to C/C++ (uppercase `.C` arrives\n// here lowercased by fileExtension), or importProbes falls into the non-C catch-all branch.\nconst cExtensions = new Set(['.c', '.c++', '.cc', '.cp', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.hxx', '.tcc']);\n\n/**\n * TypeScript's ESM extension substitutions: a JS runtime extension in an import may name the\n * TypeScript source that compiles to it. `.ts`-family extensions are intentionally absent (they\n * resolve exactly), and `.mjs`/`.cjs` never remap to plain `.ts`.\n */\nconst jsImportExtensionSubstitutions = new Map<string, string[]>([\n ['.js', ['.ts', '.tsx']],\n ['.jsx', ['.tsx']],\n ['.mjs', ['.mts']],\n ['.cjs', ['.cts']],\n]);\n\nfunction resolveLocalImport(\n fromFile: string,\n source: string,\n fileSet: Set<string>,\n javaFileIndex: Map<string, JavaClassLocation[]>\n): string | undefined {\n const fromExtension = fileExtension(fromFile);\n if (!source.startsWith('.')) {\n if (fromExtension === '.rs' && /^(?:crate|self|super)(?:::|$)/u.test(source)) {\n return resolveRustLocalImport(fromFile, source, fileSet);\n }\n // Java imports are dotted class paths without a leading dot; other absolute sources are external.\n return fromExtension === '.java' ? resolveJavaImport(source, javaFileIndex) : undefined;\n }\n const bases = localImportBases(fromFile, source);\n\n // The JS-family ESM remap follows TypeScript's substitution table, which tries the TypeScript\n // source BEFORE the named JS file: `./foo.js` names `foo.ts` even when `foo.js` also exists,\n // and `./foo.mjs` may only name `foo.mts` (never an unrelated `foo.ts`), vice versa for `.cjs`.\n // Non-JS importers never remap, so `#include \"config.inc\"` cannot rebind to same-stem files.\n if (jsExtensions.has(fromExtension)) {\n for (const base of bases) {\n const substitutions = jsImportExtensionSubstitutions.get(path.extname(base));\n if (!substitutions) {\n continue;\n }\n const stem = base.slice(0, -path.extname(base).length);\n for (const substitution of substitutions) {\n if (fileSet.has(`${stem}${substitution}`)) {\n return `${stem}${substitution}`;\n }\n }\n }\n }\n\n // An explicit-path source (`#include \"b.hpp\"`, `require_relative \"./b.rb\"`, `./foo.js` with no\n // TypeScript counterpart) resolves exactly before extension probing, so a sibling `b.ts` cannot\n // shadow the named file.\n for (const base of bases) {\n if (fileSet.has(base)) {\n return base;\n }\n }\n\n const { extensions, indexFiles } = importProbes(fromFile);\n\n // Extension probing keeps each base's full name, so `./user.service` prefers `user.service.ts`.\n for (const stem of bases) {\n for (const extension of extensions) {\n if (fileSet.has(`${stem}${extension}`)) {\n return `${stem}${extension}`;\n }\n }\n for (const indexFile of indexFiles) {\n const candidate = path.join(stem, indexFile);\n if (fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n return undefined;\n}\n\n/** Extension-less sources probe only extensions the importing language can actually load. */\nfunction importProbes(fromFile: string): { extensions: string[]; indexFiles: string[] } {\n const fromExtension = fileExtension(fromFile);\n if (fromExtension === '.rb') {\n return { extensions: ['.rb'], indexFiles: [] };\n }\n if (fromExtension === '.py') {\n return { extensions: ['.py'], indexFiles: ['__init__.py'] };\n }\n if (cExtensions.has(fromExtension)) {\n // The C preprocessor searches for the exact requested filename and never appends extensions,\n // so `#include \"config\"` must not resolve to a same-stem `config.h`.\n return { extensions: [], indexFiles: [] };\n }\n if (jsExtensions.has(fromExtension)) {\n return {\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'],\n indexFiles: ['index.ts', 'index.tsx', 'index.js'],\n };\n }\n return {\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.py', '.rb', '.h', '.hpp', '.hh', '.hxx', '.c', '.cpp', '.cc', '.cxx'],\n indexFiles: ['index.ts', 'index.tsx', 'index.js', '__init__.py'],\n };\n}\n\n/** Rust module owner files whose child modules live as siblings in the same directory. */\nconst rustOwnerFileNames = new Set(['mod.rs', 'lib.rs', 'main.rs']);\n\n/**\n * Resolves a Rust in-crate module path (`crate::a::b`, `self::x`, `super::x`) to a scanned file by\n * probing `<path>.rs` and `<path>/mod.rs` against the appropriate base directories. `crate::` roots\n * are inferred from ancestor directories containing `lib.rs`/`main.rs`, falling back to every\n * ancestor so single-crate scans without a crate root file still resolve.\n */\nfunction resolveRustLocalImport(fromFile: string, source: string, fileSet: Set<string>): string | undefined {\n const segments = source.split('::');\n const head = segments.shift();\n // Rust allows repeated `super` segments to ascend several modules; each extra one climbs a\n // directory before the remaining path is probed.\n let extraSuperCount = 0;\n while (head === 'super' && segments[0] === 'super') {\n segments.shift();\n extraSuperCount += 1;\n }\n const modulePath = segments.join('/');\n if (!modulePath) {\n return undefined;\n }\n const fromDirectory = normalizeDirectory(path.dirname(fromFile));\n const isOwner = rustOwnerFileNames.has(path.basename(fromFile));\n const stemDirectory = path.join(fromDirectory, path.basename(fromFile, '.rs'));\n\n let baseDirectories: string[];\n if (head === 'self') {\n // Children of a mod.rs/lib.rs/main.rs module are siblings; children of `a.rs` live in `a/`.\n baseDirectories = isOwner ? [fromDirectory] : [stemDirectory, fromDirectory];\n } else if (head === 'super') {\n baseDirectories = (\n isOwner\n ? [normalizeDirectory(path.dirname(fromDirectory)), fromDirectory]\n : [fromDirectory, normalizeDirectory(path.dirname(fromDirectory))]\n ).map((directory) => ascendDirectory(directory, extraSuperCount));\n } else {\n baseDirectories = rustCrateRootCandidates(fromDirectory, fileSet);\n }\n\n // The leaf may be an item rather than a module (`use crate::b::helper` keeps `helper`), so the\n // parent module path is probed as a fallback for each base directory.\n const modulePaths = [modulePath, segments.slice(0, -1).join('/')].filter(Boolean);\n for (const base of baseDirectories) {\n for (const candidateModulePath of modulePaths) {\n for (const candidate of [\n path.join(base, `${candidateModulePath}.rs`),\n path.join(base, candidateModulePath, 'mod.rs'),\n ]) {\n if (candidate !== fromFile && fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n }\n return undefined;\n}\n\n/** Ancestor directories of `fromDirectory` holding a crate root file, else all ancestors (nearest first). */\nfunction rustCrateRootCandidates(fromDirectory: string, fileSet: Set<string>): string[] {\n const ancestors: string[] = [];\n let current = fromDirectory;\n for (;;) {\n ancestors.push(current);\n if (current === '') {\n break;\n }\n current = normalizeDirectory(path.dirname(current));\n }\n const withRootFile = ancestors.filter(\n (directory) => fileSet.has(path.join(directory, 'lib.rs')) || fileSet.has(path.join(directory, 'main.rs'))\n );\n return withRootFile.length > 0 ? withRootFile : ancestors;\n}\n\nfunction ascendDirectory(directory: string, levels: number): string {\n let current = directory;\n for (let index = 0; index < levels && current !== ''; index += 1) {\n current = normalizeDirectory(path.dirname(current));\n }\n return current;\n}\n\nfunction normalizeDirectory(directory: string): string {\n return directory === '.' ? '' : directory;\n}\n\ninterface JavaClassLocation {\n file: string;\n /** `<packagePath>/<ClassName>` from the file's declared package, when one was parsed. */\n qualifiedPath?: string;\n}\n\n/** Indexes scanned `.java` files by class name so dotted imports resolve without scanning all files. */\nfunction buildJavaFileIndex(files: SourceFile[]): Map<string, JavaClassLocation[]> {\n const index = new Map<string, JavaClassLocation[]>();\n for (const { relativeFile, metrics } of files) {\n if (fileExtension(relativeFile) !== '.java') {\n continue;\n }\n const fileClassName = path.basename(relativeFile, path.extname(relativeFile));\n const addLocation = (className: string, qualifiedPath: string | undefined): void => {\n const locations = index.get(className) ?? [];\n if (!locations.some((location) => location.file === relativeFile && location.qualifiedPath === qualifiedPath)) {\n locations.push({ file: relativeFile, qualifiedPath });\n index.set(className, locations);\n }\n };\n // Declarations carry the package as `com.alpha::ServiceThing`, giving each top-level type's\n // true qualified path regardless of directory layout — including package-private types whose\n // names differ from the file's.\n let sawFileClass = false;\n for (const declaration of metrics.module.declarations) {\n const separatorIndex = declaration.name.lastIndexOf('::');\n const className = separatorIndex === -1 ? declaration.name : declaration.name.slice(separatorIndex + 2);\n const packagePath =\n separatorIndex === -1 ? undefined : declaration.name.slice(0, separatorIndex).replaceAll('.', '/');\n addLocation(className, packagePath ? `${packagePath}/${className}` : undefined);\n sawFileClass ||= className === fileClassName;\n }\n // Filename fallback for files whose public type produced no declaration.\n if (!sawFileClass) {\n addLocation(fileClassName, undefined);\n }\n }\n for (const locations of index.values()) {\n locations.sort((left, right) => left.file.localeCompare(right.file));\n }\n return index;\n}\n\n/**\n * Resolves a Java dotted import (`com.example.Helper`) to a scanned file whose path ends with the\n * package path, tolerating source-root prefixes (`src/main/java/...`). Static imports fall back to\n * the enclosing class once the member segment fails to match; wildcard imports name a package, not\n * a file, and stay unresolved. Ambiguity across source roots resolves to the lexicographically\n * first path for determinism.\n */\nfunction resolveJavaImport(source: string, javaFileIndex: Map<string, JavaClassLocation[]>): string | undefined {\n if (source.endsWith('.*')) {\n return undefined;\n }\n // Trailing segments are dropped one by one so nested types and static members resolve to their\n // enclosing top-level class file (`com.example.Outer.Inner.CONST` -> `Outer.java`).\n const segments = source.split('.');\n for (let end = segments.length; end >= 1; end -= 1) {\n const candidateSegments = segments.slice(0, end);\n const className = candidateSegments.at(-1);\n if (!className) {\n continue;\n }\n const relativeStem = candidateSegments.join('/');\n const locations = javaFileIndex.get(className) ?? [];\n // The declared package is authoritative: matching it exactly stops `import java.util.List`\n // from binding to a project class in package `util` just because the Maven layout contains a\n // `java/` path segment.\n const packageMatch = locations.find((location) => location.qualifiedPath === relativeStem);\n if (packageMatch) {\n return packageMatch.file;\n }\n // Files without a parsed package fall back to path-suffix matching, tolerating source-root\n // prefixes (`src/main/java/...`). Indexed paths use the platform separator, so normalize.\n const suffixMatch = locations.find((location) => {\n if (location.qualifiedPath) {\n return false;\n }\n // Class names stay case-sensitive; only the extension is normalized (`B.JAVA` matches `B`).\n const normalized = location.file.replaceAll('\\\\', '/');\n const stem = normalized.slice(0, normalized.length - path.extname(normalized).length);\n return stem === relativeStem || stem.endsWith(`/${relativeStem}`);\n });\n if (suffixMatch) {\n return suffixMatch.file;\n }\n }\n return undefined;\n}\n\n/** Extensions are matched lowercased so `MAIN.CPP` or `.C` files use their language's rules. */\nfunction fileExtension(file: string): string {\n return path.extname(file).toLowerCase();\n}\n\nfunction localImportBases(fromFile: string, source: string): string[] {\n const fromDirectory = path.dirname(fromFile);\n if (isPathRelativeImport(source)) {\n return [path.normalize(path.join(fromDirectory, source))];\n }\n\n const match = /^(?<dots>\\.+)(?<module>.*)$/u.exec(source);\n const dots = match?.groups?.dots;\n const moduleName = match?.groups?.module;\n if (!dots || moduleName === undefined) {\n return [];\n }\n\n let baseDirectory = fromDirectory;\n for (let index = 1; index < dots.length; index += 1) {\n baseDirectory = path.dirname(baseDirectory);\n }\n\n const modulePath = moduleName.replaceAll('.', path.sep);\n return [path.normalize(path.join(baseDirectory, modulePath))];\n}\n\nfunction isPathRelativeImport(source: string): boolean {\n return source.startsWith('./') || source.startsWith('../') || source.includes('/');\n}\n\nfunction measureTransitiveDependencyCount(file: string, graph: Map<string, Set<string>>): number {\n // Seed with the starting file so dependency cycles do not count it as its own dependency.\n const visited = new Set<string>([file]);\n const pending = [...(graph.get(file) ?? [])];\n while (pending.length > 0) {\n const current = pending.pop();\n if (!current || visited.has(current)) {\n continue;\n }\n visited.add(current);\n pending.push(...(graph.get(current) ?? []));\n }\n return visited.size - 1;\n}\n\nfunction measureDuplicateSymbolGroups(files: SourceFile[]): DuplicateSymbolGroup[] {\n const declarationsBySymbol = new Map<string, DuplicateSymbolDeclaration[]>();\n for (const file of files) {\n for (const declaration of file.metrics.module.declarations) {\n if (!isDuplicateSymbolCandidate(declaration)) {\n continue;\n }\n const declarations = declarationsBySymbol.get(declaration.name) ?? [];\n declarations.push(toDuplicateSymbolDeclaration(file.relativeFile, declaration));\n declarationsBySymbol.set(declaration.name, declarations);\n }\n }\n return [...declarationsBySymbol.entries()]\n .flatMap(([name, declarations]) => {\n const duplicateFiles = [...new Set(declarations.map((declaration) => declaration.file))].toSorted();\n return duplicateFiles.length > 1\n ? [{ declarations: declarations.toSorted(compareDuplicateDeclarations), files: duplicateFiles, name }]\n : [];\n })\n .toSorted((left, right) => right.files.length - left.files.length || left.name.localeCompare(right.name));\n}\n\nfunction isDuplicateSymbolCandidate(declaration: DeclarationMetrics): boolean {\n // The threshold applies to the local name so qualification cannot let short names sneak past\n // it; the codebase emits both `::` (packages/namespaces/modules, C++ out-of-line names) and `.`\n // (Go `Receiver.Method`) separators.\n const localName = declaration.name.split(/::|\\./u).at(-1) ?? declaration.name;\n return localName.length >= duplicateSymbolNameLengthThreshold;\n}\n\nfunction toDuplicateSymbolDeclaration(file: string, declaration: DeclarationMetrics): DuplicateSymbolDeclaration {\n return { file, line: declaration.startLine };\n}\n\nfunction compareDuplicateDeclarations(left: DuplicateSymbolDeclaration, right: DuplicateSymbolDeclaration): number {\n return left.file.localeCompare(right.file) || left.line - right.line;\n}\n\nfunction measureDuplicateGroupCountByFile(groups: DuplicateSymbolGroup[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const group of groups) {\n for (const file of group.files) {\n counts.set(file, (counts.get(file) ?? 0) + 1);\n }\n }\n return counts;\n}\n\nfunction measureStructuralCoordination(metrics: CodeMetrics): StructuralCoordinationMetrics {\n const asyncBoundaryCount = metrics.syntaxFeatures.awaitExpressionCount;\n const exceptionHandlingCount = metrics.syntaxFeatures.tryStatementCount + metrics.syntaxFeatures.throwStatementCount;\n const stateMutationScore = metrics.syntaxFeatures.mutableBindingCount + metrics.syntaxFeatures.assignmentCount;\n const branchingScore =\n metrics.cyclomaticComplexity +\n metrics.syntaxFeatures.loopStatementCount +\n metrics.syntaxFeatures.returnStatementCount;\n const moduleInteractionScore =\n metrics.callGraph.callCount + metrics.callGraph.internalEdgeCount * 2 + metrics.callGraph.maxCallDepth * 3;\n\n return {\n asyncBoundaryCount,\n branchingScore,\n exceptionHandlingCount,\n moduleInteractionScore,\n score:\n asyncBoundaryCount * 2 +\n exceptionHandlingCount * 3 +\n stateMutationScore +\n branchingScore +\n moduleInteractionScore,\n stateMutationScore,\n };\n}\n\n/**\n * Java imports are dotted package paths, so per-file coupling classifies them as external even\n * when they resolve inside the scanned project; the dependency graph corrects both feature tags.\n */\nfunction measureStructuralFeatureGroups(\n metrics: CodeMetrics,\n directLocalDependencyCount: number,\n nonRelativeLocalImportCount: number\n): string[] {\n const groups = new Set<string>();\n addGroup(groups, 'class-shapes', metrics.classCount > 0);\n addGroup(groups, 'control-flow', metrics.cognitiveComplexity > 0);\n addGroup(groups, 'external-dependencies', metrics.coupling.externalImportCount - nonRelativeLocalImportCount > 0);\n addGroup(groups, 'functions', metrics.functionCount > 0);\n addGroup(groups, 'local-dependencies', metrics.coupling.relativeImportCount > 0 || directLocalDependencyCount > 0);\n addGroup(groups, 'module-api', metrics.coupling.exportCount > 0 || metrics.module.declarations.length > 0);\n addGroup(\n groups,\n 'state-mutation',\n metrics.syntaxFeatures.mutableBindingCount + metrics.syntaxFeatures.assignmentCount > 0\n );\n addGroup(groups, 'type-shapes', hasTypeShapeMetrics(metrics));\n return [...groups].toSorted();\n}\n\nfunction addGroup(groups: Set<string>, group: string, condition: boolean): void {\n if (condition) {\n groups.add(group);\n }\n}\n\nfunction hasTypeShapeMetrics(metrics: CodeMetrics): boolean {\n return (\n metrics.typeComplexity.typeAnnotationCount +\n metrics.typeComplexity.typeAliasCount +\n metrics.typeComplexity.interfaceCount +\n metrics.typeComplexity.genericParameterCount +\n metrics.typeComplexity.unionTypeCount +\n metrics.typeComplexity.intersectionTypeCount +\n metrics.typeComplexity.conditionalTypeCount >\n 0\n );\n}\n\nfunction maxFileMetric(metrics: ArchitectureFileMetrics[], key: keyof ArchitectureFileMetrics): number {\n return Math.max(\n 0,\n ...metrics.map((metric) => metric[key]).filter((value): value is number => typeof value === 'number')\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { CodeMetrics } from './types.js';
|
|
2
|
-
export interface ArchitectureSourceFile {
|
|
3
|
-
file: string;
|
|
4
|
-
metrics: CodeMetrics;
|
|
5
|
-
}
|
|
6
|
-
export interface ArchitectureFileMetrics {
|
|
7
|
-
directLocalDependencyCount: number;
|
|
8
|
-
duplicateSymbolGroupCount: number;
|
|
9
|
-
file: string;
|
|
10
|
-
structuralBreadthScore: number;
|
|
11
|
-
structuralCoordination: StructuralCoordinationMetrics;
|
|
12
|
-
structuralFeatureGroups: string[];
|
|
13
|
-
transitiveLocalDependencyCount: number;
|
|
14
|
-
}
|
|
15
|
-
export interface ArchitectureMetrics {
|
|
16
|
-
duplicateSymbolGroups: DuplicateSymbolGroup[];
|
|
17
|
-
files: ArchitectureFileMetrics[];
|
|
18
|
-
maxDirectLocalDependencyCount: number;
|
|
19
|
-
maxDuplicateSymbolGroupCount: number;
|
|
20
|
-
maxStructuralBreadthScore: number;
|
|
21
|
-
maxStructuralCoordinationScore: number;
|
|
22
|
-
maxTransitiveLocalDependencyCount: number;
|
|
23
|
-
}
|
|
24
|
-
export interface DuplicateSymbolGroup {
|
|
25
|
-
declarations: DuplicateSymbolDeclaration[];
|
|
26
|
-
files: string[];
|
|
27
|
-
name: string;
|
|
28
|
-
}
|
|
29
|
-
export interface DuplicateSymbolDeclaration {
|
|
30
|
-
file: string;
|
|
31
|
-
line: number;
|
|
32
|
-
}
|
|
33
|
-
export interface StructuralCoordinationMetrics {
|
|
34
|
-
asyncBoundaryCount: number;
|
|
35
|
-
branchingScore: number;
|
|
36
|
-
exceptionHandlingCount: number;
|
|
37
|
-
moduleInteractionScore: number;
|
|
38
|
-
score: number;
|
|
39
|
-
stateMutationScore: number;
|
|
40
|
-
}
|
|
41
|
-
export declare function measureArchitecture(files: readonly ArchitectureSourceFile[], displayRoot: string): ArchitectureMetrics;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import e from"node:path";function t(t,r){let i=t.map(t=>({...t,relativeFile:e.relative(r,t.file)||e.basename(t.file)})),a=n(i),o=y(i),s=C(o),c=i.map(e=>{let t=a.dependenciesByFile.get(e.relativeFile)??new Set,n=a.nonRelativeLocalImportCountByFile.get(e.relativeFile)??0,r=w(e.metrics),i=T(e.metrics,t.size,n);return{file:e.relativeFile,directLocalDependencyCount:t.size,duplicateSymbolGroupCount:s.get(e.relativeFile)??0,structuralBreadthScore:i.length,structuralCoordination:r,structuralFeatureGroups:i,transitiveLocalDependencyCount:v(e.relativeFile,a.dependenciesByFile)}});return{duplicateSymbolGroups:o,files:c,maxDirectLocalDependencyCount:O(c,`directLocalDependencyCount`),maxDuplicateSymbolGroupCount:O(c,`duplicateSymbolGroupCount`),maxStructuralBreadthScore:O(c,`structuralBreadthScore`),maxStructuralCoordinationScore:Math.max(0,...c.map(e=>e.structuralCoordination.score)),maxTransitiveLocalDependencyCount:O(c,`transitiveLocalDependencyCount`)}}function n(e){let t=new Set(e.map(e=>e.relativeFile)),n=p(e),r=new Map,i=new Map;for(let a of e){let e=new Set,s=0;for(let r of a.metrics.module.importSources){let i=o(a.relativeFile,r,t,n);i&&i!==a.relativeFile&&(e.add(i),h(a.relativeFile)===`.java`&&!r.startsWith(`.`)&&(s+=1))}r.set(a.relativeFile,e),i.set(a.relativeFile,s)}return{dependenciesByFile:r,nonRelativeLocalImportCountByFile:i}}const r=new Set([`.js`,`.jsx`,`.ts`,`.tsx`,`.mjs`,`.cjs`,`.mts`,`.cts`]),i=new Set([`.c`,`.c++`,`.cc`,`.cp`,`.cpp`,`.cxx`,`.h`,`.hh`,`.hpp`,`.hxx`,`.tcc`]),a=new Map([[`.js`,[`.ts`,`.tsx`]],[`.jsx`,[`.tsx`]],[`.mjs`,[`.mts`]],[`.cjs`,[`.cts`]]]);function o(t,n,i,o){let c=h(t);if(!n.startsWith(`.`))return c===`.rs`&&/^(?:crate|self|super)(?:::|$)/u.test(n)?l(t,n,i):c===`.java`?m(n,o):void 0;let u=g(t,n);if(r.has(c))for(let t of u){let n=a.get(e.extname(t));if(!n)continue;let r=t.slice(0,-e.extname(t).length);for(let e of n)if(i.has(`${r}${e}`))return`${r}${e}`}for(let e of u)if(i.has(e))return e;let{extensions:d,indexFiles:f}=s(t);for(let t of u){for(let e of d)if(i.has(`${t}${e}`))return`${t}${e}`;for(let n of f){let r=e.join(t,n);if(i.has(r))return r}}}function s(e){let t=h(e);return t===`.rb`?{extensions:[`.rb`],indexFiles:[]}:t===`.py`?{extensions:[`.py`],indexFiles:[`__init__.py`]}:i.has(t)?{extensions:[],indexFiles:[]}:r.has(t)?{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`,`.mts`,`.cts`,`.mjs`,`.cjs`],indexFiles:[`index.ts`,`index.tsx`,`index.js`]}:{extensions:[`.ts`,`.tsx`,`.js`,`.jsx`,`.py`,`.rb`,`.h`,`.hpp`,`.hh`,`.hxx`,`.c`,`.cpp`,`.cc`,`.cxx`],indexFiles:[`index.ts`,`index.tsx`,`index.js`,`__init__.py`]}}const c=new Set([`mod.rs`,`lib.rs`,`main.rs`]);function l(t,n,r){let i=n.split(`::`),a=i.shift(),o=0;for(;a===`super`&&i[0]===`super`;)i.shift(),o+=1;let s=i.join(`/`);if(!s)return;let l=f(e.dirname(t)),p=c.has(e.basename(t)),m=e.join(l,e.basename(t,`.rs`)),h;h=a===`self`?p?[l]:[m,l]:a===`super`?(p?[f(e.dirname(l)),l]:[l,f(e.dirname(l))]).map(e=>d(e,o)):u(l,r);let g=[s,i.slice(0,-1).join(`/`)].filter(Boolean);for(let n of h)for(let i of g)for(let a of[e.join(n,`${i}.rs`),e.join(n,i,`mod.rs`)])if(a!==t&&r.has(a))return a}function u(t,n){let r=[],i=t;for(;r.push(i),i!==``;)i=f(e.dirname(i));let a=r.filter(t=>n.has(e.join(t,`lib.rs`))||n.has(e.join(t,`main.rs`)));return a.length>0?a:r}function d(t,n){let r=t;for(let t=0;t<n&&r!==``;t+=1)r=f(e.dirname(r));return r}function f(e){return e===`.`?``:e}function p(t){let n=new Map;for(let{relativeFile:r,metrics:i}of t){if(h(r)!==`.java`)continue;let t=e.basename(r,e.extname(r)),a=(e,t)=>{let i=n.get(e)??[];i.some(e=>e.file===r&&e.qualifiedPath===t)||(i.push({file:r,qualifiedPath:t}),n.set(e,i))},o=!1;for(let e of i.module.declarations){let n=e.name.lastIndexOf(`::`),r=n===-1?e.name:e.name.slice(n+2),i=n===-1?void 0:e.name.slice(0,n).replaceAll(`.`,`/`);a(r,i?`${i}/${r}`:void 0),o||=r===t}o||a(t,void 0)}for(let e of n.values())e.sort((e,t)=>e.file.localeCompare(t.file));return n}function m(t,n){if(t.endsWith(`.*`))return;let r=t.split(`.`);for(let t=r.length;t>=1;--t){let i=r.slice(0,t),a=i.at(-1);if(!a)continue;let o=i.join(`/`),s=n.get(a)??[],c=s.find(e=>e.qualifiedPath===o);if(c)return c.file;let l=s.find(t=>{if(t.qualifiedPath)return!1;let n=t.file.replaceAll(`\\`,`/`),r=n.slice(0,n.length-e.extname(n).length);return r===o||r.endsWith(`/${o}`)});if(l)return l.file}}function h(t){return e.extname(t).toLowerCase()}function g(t,n){let r=e.dirname(t);if(_(n))return[e.normalize(e.join(r,n))];let i=/^(?<dots>\.+)(?<module>.*)$/u.exec(n),a=i?.groups?.dots,o=i?.groups?.module;if(!a||o===void 0)return[];let s=r;for(let t=1;t<a.length;t+=1)s=e.dirname(s);let c=o.replaceAll(`.`,e.sep);return[e.normalize(e.join(s,c))]}function _(e){return e.startsWith(`./`)||e.startsWith(`../`)||e.includes(`/`)}function v(e,t){let n=new Set([e]),r=[...t.get(e)??[]];for(;r.length>0;){let e=r.pop();!e||n.has(e)||(n.add(e),r.push(...t.get(e)??[]))}return n.size-1}function y(e){let t=new Map;for(let n of e)for(let e of n.metrics.module.declarations){if(!b(e))continue;let r=t.get(e.name)??[];r.push(x(n.relativeFile,e)),t.set(e.name,r)}return[...t.entries()].flatMap(([e,t])=>{let n=[...new Set(t.map(e=>e.file))].toSorted();return n.length>1?[{declarations:t.toSorted(S),files:n,name:e}]:[]}).toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name))}function b(e){return(e.name.split(/::|\./u).at(-1)??e.name).length>=5}function x(e,t){return{file:e,line:t.startLine}}function S(e,t){return e.file.localeCompare(t.file)||e.line-t.line}function C(e){let t=new Map;for(let n of e)for(let e of n.files)t.set(e,(t.get(e)??0)+1);return t}function w(e){let t=e.syntaxFeatures.awaitExpressionCount,n=e.syntaxFeatures.tryStatementCount+e.syntaxFeatures.throwStatementCount,r=e.syntaxFeatures.mutableBindingCount+e.syntaxFeatures.assignmentCount,i=e.cyclomaticComplexity+e.syntaxFeatures.loopStatementCount+e.syntaxFeatures.returnStatementCount,a=e.callGraph.callCount+e.callGraph.internalEdgeCount*2+e.callGraph.maxCallDepth*3;return{asyncBoundaryCount:t,branchingScore:i,exceptionHandlingCount:n,moduleInteractionScore:a,score:t*2+n*3+r+i+a,stateMutationScore:r}}function T(e,t,n){let r=new Set;return E(r,`class-shapes`,e.classCount>0),E(r,`control-flow`,e.cognitiveComplexity>0),E(r,`external-dependencies`,e.coupling.externalImportCount-n>0),E(r,`functions`,e.functionCount>0),E(r,`local-dependencies`,e.coupling.relativeImportCount>0||t>0),E(r,`module-api`,e.coupling.exportCount>0||e.module.declarations.length>0),E(r,`state-mutation`,e.syntaxFeatures.mutableBindingCount+e.syntaxFeatures.assignmentCount>0),E(r,`type-shapes`,D(e)),[...r].toSorted()}function E(e,t,n){n&&e.add(t)}function D(e){return e.typeComplexity.typeAnnotationCount+e.typeComplexity.typeAliasCount+e.typeComplexity.interfaceCount+e.typeComplexity.genericParameterCount+e.typeComplexity.unionTypeCount+e.typeComplexity.intersectionTypeCount+e.typeComplexity.conditionalTypeCount>0}function O(e,t){return Math.max(0,...e.map(e=>e[t]).filter(e=>typeof e==`number`))}export{t as measureArchitecture};
|
|
2
|
-
//# sourceMappingURL=architectureMetrics.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"architectureMetrics.js","names":[],"sources":["../src/architectureMetrics.ts"],"sourcesContent":["import path from 'node:path';\nimport type { CodeMetrics, DeclarationMetrics } from './types.js';\n\nexport interface ArchitectureSourceFile {\n file: string;\n metrics: CodeMetrics;\n}\n\nexport interface ArchitectureFileMetrics {\n directLocalDependencyCount: number;\n duplicateSymbolGroupCount: number;\n file: string;\n structuralBreadthScore: number;\n structuralCoordination: StructuralCoordinationMetrics;\n structuralFeatureGroups: string[];\n transitiveLocalDependencyCount: number;\n}\n\nexport interface ArchitectureMetrics {\n duplicateSymbolGroups: DuplicateSymbolGroup[];\n files: ArchitectureFileMetrics[];\n maxDirectLocalDependencyCount: number;\n maxDuplicateSymbolGroupCount: number;\n maxStructuralBreadthScore: number;\n maxStructuralCoordinationScore: number;\n maxTransitiveLocalDependencyCount: number;\n}\n\nexport interface DuplicateSymbolGroup {\n declarations: DuplicateSymbolDeclaration[];\n files: string[];\n name: string;\n}\n\nexport interface DuplicateSymbolDeclaration {\n file: string;\n line: number;\n}\n\nexport interface StructuralCoordinationMetrics {\n asyncBoundaryCount: number;\n branchingScore: number;\n exceptionHandlingCount: number;\n moduleInteractionScore: number;\n score: number;\n stateMutationScore: number;\n}\n\ninterface SourceFile {\n file: string;\n metrics: CodeMetrics;\n relativeFile: string;\n}\n\nconst duplicateSymbolNameLengthThreshold = 5;\n\nexport function measureArchitecture(\n files: readonly ArchitectureSourceFile[],\n displayRoot: string\n): ArchitectureMetrics {\n const sourceFiles = files.map((file) => ({\n ...file,\n relativeFile: path.relative(displayRoot, file.file) || path.basename(file.file),\n }));\n const dependencyGraph = measureDependencyGraph(sourceFiles);\n const duplicateSymbolGroups = measureDuplicateSymbolGroups(sourceFiles);\n const duplicateGroupCountByFile = measureDuplicateGroupCountByFile(duplicateSymbolGroups);\n const metrics = sourceFiles.map((sourceFile) => {\n const directDependencies = dependencyGraph.dependenciesByFile.get(sourceFile.relativeFile) ?? new Set<string>();\n const nonRelativeLocalImportCount =\n dependencyGraph.nonRelativeLocalImportCountByFile.get(sourceFile.relativeFile) ?? 0;\n const structuralCoordination = measureStructuralCoordination(sourceFile.metrics);\n const structuralFeatureGroups = measureStructuralFeatureGroups(\n sourceFile.metrics,\n directDependencies.size,\n nonRelativeLocalImportCount\n );\n return {\n file: sourceFile.relativeFile,\n directLocalDependencyCount: directDependencies.size,\n duplicateSymbolGroupCount: duplicateGroupCountByFile.get(sourceFile.relativeFile) ?? 0,\n structuralBreadthScore: structuralFeatureGroups.length,\n structuralCoordination,\n structuralFeatureGroups,\n transitiveLocalDependencyCount: measureTransitiveDependencyCount(\n sourceFile.relativeFile,\n dependencyGraph.dependenciesByFile\n ),\n };\n });\n\n return {\n duplicateSymbolGroups,\n files: metrics,\n maxDirectLocalDependencyCount: maxFileMetric(metrics, 'directLocalDependencyCount'),\n maxDuplicateSymbolGroupCount: maxFileMetric(metrics, 'duplicateSymbolGroupCount'),\n maxStructuralBreadthScore: maxFileMetric(metrics, 'structuralBreadthScore'),\n maxStructuralCoordinationScore: Math.max(0, ...metrics.map((file) => file.structuralCoordination.score)),\n maxTransitiveLocalDependencyCount: maxFileMetric(metrics, 'transitiveLocalDependencyCount'),\n };\n}\n\ninterface DependencyGraph {\n dependenciesByFile: Map<string, Set<string>>;\n /** Import sources without a relative prefix (Java dotted paths) that still resolved locally. */\n nonRelativeLocalImportCountByFile: Map<string, number>;\n}\n\nfunction measureDependencyGraph(files: SourceFile[]): DependencyGraph {\n const fileSet = new Set(files.map((file) => file.relativeFile));\n const javaFileIndex = buildJavaFileIndex(files);\n const dependenciesByFile = new Map<string, Set<string>>();\n const nonRelativeLocalImportCountByFile = new Map<string, number>();\n for (const file of files) {\n const dependencies = new Set<string>();\n let nonRelativeLocalImportCount = 0;\n for (const source of file.metrics.module.importSources) {\n const resolved = resolveLocalImport(file.relativeFile, source, fileSet, javaFileIndex);\n // A file importing itself (`import './a.js'` inside a.ts) is not a dependency.\n if (resolved && resolved !== file.relativeFile) {\n dependencies.add(resolved);\n // Only Java needs the external-count correction: its dotted imports classify as external\n // in coupling metrics. Rust crate::/self::/super:: sources already classify as relative,\n // so counting them would double-subtract from externalImportCount.\n if (fileExtension(file.relativeFile) === '.java' && !source.startsWith('.')) {\n nonRelativeLocalImportCount += 1;\n }\n }\n }\n dependenciesByFile.set(file.relativeFile, dependencies);\n nonRelativeLocalImportCountByFile.set(file.relativeFile, nonRelativeLocalImportCount);\n }\n return { dependenciesByFile, nonRelativeLocalImportCountByFile };\n}\n\nconst jsExtensions = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']);\n// Must cover every extension the CLI's languageByExtension maps to C/C++ (uppercase `.C` arrives\n// here lowercased by fileExtension), or importProbes falls into the non-C catch-all branch.\nconst cExtensions = new Set(['.c', '.c++', '.cc', '.cp', '.cpp', '.cxx', '.h', '.hh', '.hpp', '.hxx', '.tcc']);\n\n/**\n * TypeScript's ESM extension substitutions: a JS runtime extension in an import may name the\n * TypeScript source that compiles to it. `.ts`-family extensions are intentionally absent (they\n * resolve exactly), and `.mjs`/`.cjs` never remap to plain `.ts`.\n */\nconst jsImportExtensionSubstitutions = new Map<string, string[]>([\n ['.js', ['.ts', '.tsx']],\n ['.jsx', ['.tsx']],\n ['.mjs', ['.mts']],\n ['.cjs', ['.cts']],\n]);\n\nfunction resolveLocalImport(\n fromFile: string,\n source: string,\n fileSet: Set<string>,\n javaFileIndex: Map<string, JavaClassLocation[]>\n): string | undefined {\n const fromExtension = fileExtension(fromFile);\n if (!source.startsWith('.')) {\n if (fromExtension === '.rs' && /^(?:crate|self|super)(?:::|$)/u.test(source)) {\n return resolveRustLocalImport(fromFile, source, fileSet);\n }\n // Java imports are dotted class paths without a leading dot; other absolute sources are external.\n return fromExtension === '.java' ? resolveJavaImport(source, javaFileIndex) : undefined;\n }\n const bases = localImportBases(fromFile, source);\n\n // The JS-family ESM remap follows TypeScript's substitution table, which tries the TypeScript\n // source BEFORE the named JS file: `./foo.js` names `foo.ts` even when `foo.js` also exists,\n // and `./foo.mjs` may only name `foo.mts` (never an unrelated `foo.ts`), vice versa for `.cjs`.\n // Non-JS importers never remap, so `#include \"config.inc\"` cannot rebind to same-stem files.\n if (jsExtensions.has(fromExtension)) {\n for (const base of bases) {\n const substitutions = jsImportExtensionSubstitutions.get(path.extname(base));\n if (!substitutions) {\n continue;\n }\n const stem = base.slice(0, -path.extname(base).length);\n for (const substitution of substitutions) {\n if (fileSet.has(`${stem}${substitution}`)) {\n return `${stem}${substitution}`;\n }\n }\n }\n }\n\n // An explicit-path source (`#include \"b.hpp\"`, `require_relative \"./b.rb\"`, `./foo.js` with no\n // TypeScript counterpart) resolves exactly before extension probing, so a sibling `b.ts` cannot\n // shadow the named file.\n for (const base of bases) {\n if (fileSet.has(base)) {\n return base;\n }\n }\n\n const { extensions, indexFiles } = importProbes(fromFile);\n\n // Extension probing keeps each base's full name, so `./user.service` prefers `user.service.ts`.\n for (const stem of bases) {\n for (const extension of extensions) {\n if (fileSet.has(`${stem}${extension}`)) {\n return `${stem}${extension}`;\n }\n }\n for (const indexFile of indexFiles) {\n const candidate = path.join(stem, indexFile);\n if (fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n return undefined;\n}\n\n/** Extension-less sources probe only extensions the importing language can actually load. */\nfunction importProbes(fromFile: string): { extensions: string[]; indexFiles: string[] } {\n const fromExtension = fileExtension(fromFile);\n if (fromExtension === '.rb') {\n return { extensions: ['.rb'], indexFiles: [] };\n }\n if (fromExtension === '.py') {\n return { extensions: ['.py'], indexFiles: ['__init__.py'] };\n }\n if (cExtensions.has(fromExtension)) {\n // The C preprocessor searches for the exact requested filename and never appends extensions,\n // so `#include \"config\"` must not resolve to a same-stem `config.h`.\n return { extensions: [], indexFiles: [] };\n }\n if (jsExtensions.has(fromExtension)) {\n return {\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'],\n indexFiles: ['index.ts', 'index.tsx', 'index.js'],\n };\n }\n return {\n extensions: ['.ts', '.tsx', '.js', '.jsx', '.py', '.rb', '.h', '.hpp', '.hh', '.hxx', '.c', '.cpp', '.cc', '.cxx'],\n indexFiles: ['index.ts', 'index.tsx', 'index.js', '__init__.py'],\n };\n}\n\n/** Rust module owner files whose child modules live as siblings in the same directory. */\nconst rustOwnerFileNames = new Set(['mod.rs', 'lib.rs', 'main.rs']);\n\n/**\n * Resolves a Rust in-crate module path (`crate::a::b`, `self::x`, `super::x`) to a scanned file by\n * probing `<path>.rs` and `<path>/mod.rs` against the appropriate base directories. `crate::` roots\n * are inferred from ancestor directories containing `lib.rs`/`main.rs`, falling back to every\n * ancestor so single-crate scans without a crate root file still resolve.\n */\nfunction resolveRustLocalImport(fromFile: string, source: string, fileSet: Set<string>): string | undefined {\n const segments = source.split('::');\n const head = segments.shift();\n // Rust allows repeated `super` segments to ascend several modules; each extra one climbs a\n // directory before the remaining path is probed.\n let extraSuperCount = 0;\n while (head === 'super' && segments[0] === 'super') {\n segments.shift();\n extraSuperCount += 1;\n }\n const modulePath = segments.join('/');\n if (!modulePath) {\n return undefined;\n }\n const fromDirectory = normalizeDirectory(path.dirname(fromFile));\n const isOwner = rustOwnerFileNames.has(path.basename(fromFile));\n const stemDirectory = path.join(fromDirectory, path.basename(fromFile, '.rs'));\n\n let baseDirectories: string[];\n if (head === 'self') {\n // Children of a mod.rs/lib.rs/main.rs module are siblings; children of `a.rs` live in `a/`.\n baseDirectories = isOwner ? [fromDirectory] : [stemDirectory, fromDirectory];\n } else if (head === 'super') {\n baseDirectories = (\n isOwner\n ? [normalizeDirectory(path.dirname(fromDirectory)), fromDirectory]\n : [fromDirectory, normalizeDirectory(path.dirname(fromDirectory))]\n ).map((directory) => ascendDirectory(directory, extraSuperCount));\n } else {\n baseDirectories = rustCrateRootCandidates(fromDirectory, fileSet);\n }\n\n // The leaf may be an item rather than a module (`use crate::b::helper` keeps `helper`), so the\n // parent module path is probed as a fallback for each base directory.\n const modulePaths = [modulePath, segments.slice(0, -1).join('/')].filter(Boolean);\n for (const base of baseDirectories) {\n for (const candidateModulePath of modulePaths) {\n for (const candidate of [\n path.join(base, `${candidateModulePath}.rs`),\n path.join(base, candidateModulePath, 'mod.rs'),\n ]) {\n if (candidate !== fromFile && fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n }\n return undefined;\n}\n\n/** Ancestor directories of `fromDirectory` holding a crate root file, else all ancestors (nearest first). */\nfunction rustCrateRootCandidates(fromDirectory: string, fileSet: Set<string>): string[] {\n const ancestors: string[] = [];\n let current = fromDirectory;\n for (;;) {\n ancestors.push(current);\n if (current === '') {\n break;\n }\n current = normalizeDirectory(path.dirname(current));\n }\n const withRootFile = ancestors.filter(\n (directory) => fileSet.has(path.join(directory, 'lib.rs')) || fileSet.has(path.join(directory, 'main.rs'))\n );\n return withRootFile.length > 0 ? withRootFile : ancestors;\n}\n\nfunction ascendDirectory(directory: string, levels: number): string {\n let current = directory;\n for (let index = 0; index < levels && current !== ''; index += 1) {\n current = normalizeDirectory(path.dirname(current));\n }\n return current;\n}\n\nfunction normalizeDirectory(directory: string): string {\n return directory === '.' ? '' : directory;\n}\n\ninterface JavaClassLocation {\n file: string;\n /** `<packagePath>/<ClassName>` from the file's declared package, when one was parsed. */\n qualifiedPath?: string;\n}\n\n/** Indexes scanned `.java` files by class name so dotted imports resolve without scanning all files. */\nfunction buildJavaFileIndex(files: SourceFile[]): Map<string, JavaClassLocation[]> {\n const index = new Map<string, JavaClassLocation[]>();\n for (const { relativeFile, metrics } of files) {\n if (fileExtension(relativeFile) !== '.java') {\n continue;\n }\n const fileClassName = path.basename(relativeFile, path.extname(relativeFile));\n const addLocation = (className: string, qualifiedPath: string | undefined): void => {\n const locations = index.get(className) ?? [];\n if (!locations.some((location) => location.file === relativeFile && location.qualifiedPath === qualifiedPath)) {\n locations.push({ file: relativeFile, qualifiedPath });\n index.set(className, locations);\n }\n };\n // Declarations carry the package as `com.alpha::ServiceThing`, giving each top-level type's\n // true qualified path regardless of directory layout — including package-private types whose\n // names differ from the file's.\n let sawFileClass = false;\n for (const declaration of metrics.module.declarations) {\n const separatorIndex = declaration.name.lastIndexOf('::');\n const className = separatorIndex === -1 ? declaration.name : declaration.name.slice(separatorIndex + 2);\n const packagePath =\n separatorIndex === -1 ? undefined : declaration.name.slice(0, separatorIndex).replaceAll('.', '/');\n addLocation(className, packagePath ? `${packagePath}/${className}` : undefined);\n sawFileClass ||= className === fileClassName;\n }\n // Filename fallback for files whose public type produced no declaration.\n if (!sawFileClass) {\n addLocation(fileClassName, undefined);\n }\n }\n for (const locations of index.values()) {\n locations.sort((left, right) => left.file.localeCompare(right.file));\n }\n return index;\n}\n\n/**\n * Resolves a Java dotted import (`com.example.Helper`) to a scanned file whose path ends with the\n * package path, tolerating source-root prefixes (`src/main/java/...`). Static imports fall back to\n * the enclosing class once the member segment fails to match; wildcard imports name a package, not\n * a file, and stay unresolved. Ambiguity across source roots resolves to the lexicographically\n * first path for determinism.\n */\nfunction resolveJavaImport(source: string, javaFileIndex: Map<string, JavaClassLocation[]>): string | undefined {\n if (source.endsWith('.*')) {\n return undefined;\n }\n // Trailing segments are dropped one by one so nested types and static members resolve to their\n // enclosing top-level class file (`com.example.Outer.Inner.CONST` -> `Outer.java`).\n const segments = source.split('.');\n for (let end = segments.length; end >= 1; end -= 1) {\n const candidateSegments = segments.slice(0, end);\n const className = candidateSegments.at(-1);\n if (!className) {\n continue;\n }\n const relativeStem = candidateSegments.join('/');\n const locations = javaFileIndex.get(className) ?? [];\n // The declared package is authoritative: matching it exactly stops `import java.util.List`\n // from binding to a project class in package `util` just because the Maven layout contains a\n // `java/` path segment.\n const packageMatch = locations.find((location) => location.qualifiedPath === relativeStem);\n if (packageMatch) {\n return packageMatch.file;\n }\n // Files without a parsed package fall back to path-suffix matching, tolerating source-root\n // prefixes (`src/main/java/...`). Indexed paths use the platform separator, so normalize.\n const suffixMatch = locations.find((location) => {\n if (location.qualifiedPath) {\n return false;\n }\n // Class names stay case-sensitive; only the extension is normalized (`B.JAVA` matches `B`).\n const normalized = location.file.replaceAll('\\\\', '/');\n const stem = normalized.slice(0, normalized.length - path.extname(normalized).length);\n return stem === relativeStem || stem.endsWith(`/${relativeStem}`);\n });\n if (suffixMatch) {\n return suffixMatch.file;\n }\n }\n return undefined;\n}\n\n/** Extensions are matched lowercased so `MAIN.CPP` or `.C` files use their language's rules. */\nfunction fileExtension(file: string): string {\n return path.extname(file).toLowerCase();\n}\n\nfunction localImportBases(fromFile: string, source: string): string[] {\n const fromDirectory = path.dirname(fromFile);\n if (isPathRelativeImport(source)) {\n return [path.normalize(path.join(fromDirectory, source))];\n }\n\n const match = /^(?<dots>\\.+)(?<module>.*)$/u.exec(source);\n const dots = match?.groups?.dots;\n const moduleName = match?.groups?.module;\n if (!dots || moduleName === undefined) {\n return [];\n }\n\n let baseDirectory = fromDirectory;\n for (let index = 1; index < dots.length; index += 1) {\n baseDirectory = path.dirname(baseDirectory);\n }\n\n const modulePath = moduleName.replaceAll('.', path.sep);\n return [path.normalize(path.join(baseDirectory, modulePath))];\n}\n\nfunction isPathRelativeImport(source: string): boolean {\n return source.startsWith('./') || source.startsWith('../') || source.includes('/');\n}\n\nfunction measureTransitiveDependencyCount(file: string, graph: Map<string, Set<string>>): number {\n // Seed with the starting file so dependency cycles do not count it as its own dependency.\n const visited = new Set<string>([file]);\n const pending = [...(graph.get(file) ?? [])];\n while (pending.length > 0) {\n const current = pending.pop();\n if (!current || visited.has(current)) {\n continue;\n }\n visited.add(current);\n pending.push(...(graph.get(current) ?? []));\n }\n return visited.size - 1;\n}\n\nfunction measureDuplicateSymbolGroups(files: SourceFile[]): DuplicateSymbolGroup[] {\n const declarationsBySymbol = new Map<string, DuplicateSymbolDeclaration[]>();\n for (const file of files) {\n for (const declaration of file.metrics.module.declarations) {\n if (!isDuplicateSymbolCandidate(declaration)) {\n continue;\n }\n const declarations = declarationsBySymbol.get(declaration.name) ?? [];\n declarations.push(toDuplicateSymbolDeclaration(file.relativeFile, declaration));\n declarationsBySymbol.set(declaration.name, declarations);\n }\n }\n return [...declarationsBySymbol.entries()]\n .flatMap(([name, declarations]) => {\n const duplicateFiles = [...new Set(declarations.map((declaration) => declaration.file))].toSorted();\n return duplicateFiles.length > 1\n ? [{ declarations: declarations.toSorted(compareDuplicateDeclarations), files: duplicateFiles, name }]\n : [];\n })\n .toSorted((left, right) => right.files.length - left.files.length || left.name.localeCompare(right.name));\n}\n\nfunction isDuplicateSymbolCandidate(declaration: DeclarationMetrics): boolean {\n // The threshold applies to the local name so qualification cannot let short names sneak past\n // it; the codebase emits both `::` (packages/namespaces/modules, C++ out-of-line names) and `.`\n // (Go `Receiver.Method`) separators.\n const localName = declaration.name.split(/::|\\./u).at(-1) ?? declaration.name;\n return localName.length >= duplicateSymbolNameLengthThreshold;\n}\n\nfunction toDuplicateSymbolDeclaration(file: string, declaration: DeclarationMetrics): DuplicateSymbolDeclaration {\n return { file, line: declaration.startLine };\n}\n\nfunction compareDuplicateDeclarations(left: DuplicateSymbolDeclaration, right: DuplicateSymbolDeclaration): number {\n return left.file.localeCompare(right.file) || left.line - right.line;\n}\n\nfunction measureDuplicateGroupCountByFile(groups: DuplicateSymbolGroup[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const group of groups) {\n for (const file of group.files) {\n counts.set(file, (counts.get(file) ?? 0) + 1);\n }\n }\n return counts;\n}\n\nfunction measureStructuralCoordination(metrics: CodeMetrics): StructuralCoordinationMetrics {\n const asyncBoundaryCount = metrics.syntaxFeatures.awaitExpressionCount;\n const exceptionHandlingCount = metrics.syntaxFeatures.tryStatementCount + metrics.syntaxFeatures.throwStatementCount;\n const stateMutationScore = metrics.syntaxFeatures.mutableBindingCount + metrics.syntaxFeatures.assignmentCount;\n const branchingScore =\n metrics.cyclomaticComplexity +\n metrics.syntaxFeatures.loopStatementCount +\n metrics.syntaxFeatures.returnStatementCount;\n const moduleInteractionScore =\n metrics.callGraph.callCount + metrics.callGraph.internalEdgeCount * 2 + metrics.callGraph.maxCallDepth * 3;\n\n return {\n asyncBoundaryCount,\n branchingScore,\n exceptionHandlingCount,\n moduleInteractionScore,\n score:\n asyncBoundaryCount * 2 +\n exceptionHandlingCount * 3 +\n stateMutationScore +\n branchingScore +\n moduleInteractionScore,\n stateMutationScore,\n };\n}\n\n/**\n * Java imports are dotted package paths, so per-file coupling classifies them as external even\n * when they resolve inside the scanned project; the dependency graph corrects both feature tags.\n */\nfunction measureStructuralFeatureGroups(\n metrics: CodeMetrics,\n directLocalDependencyCount: number,\n nonRelativeLocalImportCount: number\n): string[] {\n const groups = new Set<string>();\n addGroup(groups, 'class-shapes', metrics.classCount > 0);\n addGroup(groups, 'control-flow', metrics.cognitiveComplexity > 0);\n addGroup(groups, 'external-dependencies', metrics.coupling.externalImportCount - nonRelativeLocalImportCount > 0);\n addGroup(groups, 'functions', metrics.functionCount > 0);\n addGroup(groups, 'local-dependencies', metrics.coupling.relativeImportCount > 0 || directLocalDependencyCount > 0);\n addGroup(groups, 'module-api', metrics.coupling.exportCount > 0 || metrics.module.declarations.length > 0);\n addGroup(\n groups,\n 'state-mutation',\n metrics.syntaxFeatures.mutableBindingCount + metrics.syntaxFeatures.assignmentCount > 0\n );\n addGroup(groups, 'type-shapes', hasTypeShapeMetrics(metrics));\n return [...groups].toSorted();\n}\n\nfunction addGroup(groups: Set<string>, group: string, condition: boolean): void {\n if (condition) {\n groups.add(group);\n }\n}\n\nfunction hasTypeShapeMetrics(metrics: CodeMetrics): boolean {\n return (\n metrics.typeComplexity.typeAnnotationCount +\n metrics.typeComplexity.typeAliasCount +\n metrics.typeComplexity.interfaceCount +\n metrics.typeComplexity.genericParameterCount +\n metrics.typeComplexity.unionTypeCount +\n metrics.typeComplexity.intersectionTypeCount +\n metrics.typeComplexity.conditionalTypeCount >\n 0\n );\n}\n\nfunction maxFileMetric(metrics: ArchitectureFileMetrics[], key: keyof ArchitectureFileMetrics): number {\n return Math.max(\n 0,\n ...metrics.map((metric) => metric[key]).filter((value): value is number => typeof value === 'number')\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("node:fs/promises"),n=require("node:path");n=e.__toESM(n,1);let r=require("typescript/unstable/async"),i=require("typescript/unstable/ast");async function a(e,t=[]){let i=new r.API({cwd:process.cwd()});try{let r=(await i.updateSnapshot({openProject:e})).getProjects(),a=await c(t),s=new Set(a.keys()),d=new Set,f={callExpressionCount:0,configDiagnosticCount:0,declarationDiagnosticCount:0,measuredRootFileCount:0,resolvedCallExpressionCount:0,rootFileCount:0,semanticDiagnosticCount:0,suggestionDiagnosticCount:0,syntacticDiagnosticCount:0},p=[],m=new Set;for(let e of r){let t=await Promise.all(e.rootFiles.map(async e=>({canonicalFile:await l(e),file:e})));f.rootFileCount+=t.length,f.measuredRootFileCount+=t.filter(({canonicalFile:e})=>s.has(e)).length;let r=await e.program.getSyntacticDiagnostics(),i=await e.program.getSemanticDiagnostics(),c=await e.program.getSuggestionDiagnostics(),h=await e.program.getDeclarationDiagnostics(),g=await e.program.getConfigFileParsingDiagnostics();f.syntacticDiagnosticCount+=r.length,f.semanticDiagnosticCount+=i.length,f.suggestionDiagnosticCount+=c.length,f.declarationDiagnosticCount+=h.length,f.configDiagnosticCount+=g.length,F(d,[...r,...i,...c,...h,...g]);let _=s.size===0?t:[...a.entries()].map(([e,t])=>({canonicalFile:e,file:t})),v=n.default.dirname(e.configFileName),y=await l(v);for(let{canonicalFile:t,file:n}of _){if(m.has(t))continue;let r=await o(e,n,t,v,y);if(!r)continue;m.add(t);let i=N(r);f.callExpressionCount+=i.length;let s=await u(r,e.checker);p.push(...s.map(e=>({...e,file:a.get(t)??n})));for(let t of i)await e.checker.getResolvedSignature(t)&&(f.resolvedCallExpressionCount+=1)}}let h=f.callExpressionCount-f.resolvedCallExpressionCount;return{...f,configFile:e,diagnosticFileCount:d.size,projectCount:r.length,reactComponentFunctions:p,unresolvedCallExpressionCount:h,resolvedCallExpressionRatio:f.callExpressionCount===0?0:f.resolvedCallExpressionCount/f.callExpressionCount}}finally{await i.close()}}async function o(e,t,n,r,i){for(let a of s(t,n,r,i)){let t=await e.program.getSourceFile(a);if(t)return t}}function s(e,t,r,i){let a=new Set([e,t]);return t.startsWith(`${i}${n.default.sep}`)&&a.add(n.default.join(r,n.default.relative(i,t))),[...a]}async function c(e){let t=new Map;for(let n of e)t.set(await l(n),n);return t}async function l(e){try{return await(0,t.realpath)(e)}catch{return e}}async function u(e,t){let n=[],r=d(e),i=p(e.text);for(let a of r){if(!await f(a,t,i))continue;let r=D(a),o=y(a),s=C(e.text,o,r,e),c=M(e.text,s);n.push({implementationKey:`${o.pos}:${o.end}`,name:r?T(r):void 0,startColumn:c.column,startLine:c.line})}return w(n)}function d(e){let t=[];return P(e,e=>{(_(e)||v(e))&&t.push(e)}),t}async function f(e,t,n){let r=await t.getTypeAtLocation(e);if(!r)return!1;let i=D(e),a=i?T(i):void 0;if(n&&await h(r,e,t))return!0;let o=i?await t.getTypeAtLocation(i):void 0;return n&&o&&await h(o,e,t)?!0:E(a)?await m(r,e,t)||!!(o&&await m(o,e,t)):!1}function p(e){return/\bfrom\s+['"]react['"]|\bimport\s+['"]react['"]|\brequire\(\s*['"]react['"]\s*\)/u.test(e)}async function m(e,t,n){let i=await n.getSignaturesOfType(e,r.SignatureKind.Call);for(let e of i){let r=await n.getReturnTypeOfSignature(e);if(r&&await g(r,t,n))return!0}return!1}async function h(e,t,n){let r=await n.typeToString(e,t);return/\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\b/u.test(r)}async function g(e,t,n){let r=await n.typeToString(e,t);return/\b(?:JSX\.Element|React\.JSX\.Element|ReactElement|ReactNode)\b/u.test(r)}function _(e){return e.kind===i.SyntaxKind.FunctionDeclaration&&!A(e,`body`)?!1:e.kind===i.SyntaxKind.FunctionDeclaration||e.kind===i.SyntaxKind.FunctionExpression||e.kind===i.SyntaxKind.ArrowFunction}function v(e){if(e.kind!==i.SyntaxKind.VariableDeclaration)return!1;let t=A(e,`initializer`),n=t?O(t):void 0;return n?_(n)||n.kind===i.SyntaxKind.CallExpression:!1}function y(e){if(_(e))return e;let t=A(e,`initializer`),n=t?O(t):void 0;return n?_(n)?n:b(n)?S(n)??e:e:e}function b(e){if(e.kind!==i.SyntaxKind.CallExpression)return!1;let t=A(e,`expression`),n=t?x(t):void 0;return n===`memo`||n===`forwardRef`}function x(e){return T(A(e,`name`)||e)}function S(e){let t;return e.forEachChild(e=>{let n=O(e);return _(n)?t=n:b(n)&&(t=S(n)),t}),t}function C(e,t,n,r){let a=(0,i.getTokenPosOfNode)(t,r);if(t.kind!==i.SyntaxKind.FunctionDeclaration||!n)return a;let o=T(n),s=o?e.indexOf(o,a):-1,c=s>=0?e.lastIndexOf(`function`,s):-1;if(c<a)return a;let l=e.slice(a,c),u=/\basync\s*$/u.exec(l);return u?a+u.index:c}function w(e){let t=new Map;for(let n of e){let e=t.get(n.implementationKey);(!e||!e.name&&n.name)&&t.set(n.implementationKey,n)}return[...t.values()].map(e=>({name:e.name,startColumn:e.startColumn,startLine:e.startLine}))}function T(e){if(`escapedText`in e)return String(e.escapedText);if(`text`in e&&typeof e.text==`string`)return e.text}function E(e){return e!==void 0&&/^[A-Z]/u.test(e)}function D(e){let t=A(e,`name`);if(t)return t;let n=e;for(;n;){let e=A(n,`parent`);if(!e)return;if(e.kind===i.SyntaxKind.VariableDeclaration)return A(e,`name`);if(!k(e))return;n=e}}function O(e){let t=e;for(;k(t);){let e=A(t,`expression`);if(!e)return t;t=e}return t}function k(e){return e.kind===i.SyntaxKind.ParenthesizedExpression||e.kind===i.SyntaxKind.AsExpression||e.kind===i.SyntaxKind.TypeAssertionExpression||e.kind===i.SyntaxKind.SatisfiesExpression}function A(e,t){let n=e[t];return j(n)?n:void 0}function j(e){return typeof e==`object`&&!!e&&`kind`in e&&`pos`in e&&`end`in e}function M(e,t){let n=e.slice(0,t).split(`
|
|
2
|
-
`);return{column:n.at(-1)?.length??0,line:n.length}}function N(e){let t=[];return P(e,e=>{e.kind===i.SyntaxKind.CallExpression&&t.push(e)}),t}function P(e,t){t(e),e.forEachChild(e=>{P(e,t)})}function F(e,t){for(let n of t)n.fileName&&e.add(n.fileName)}exports.measureTypeScriptProject=a;
|
|
3
|
-
//# sourceMappingURL=typescriptProject.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"typescriptProject.cjs","names":["API","path","realpath","SignatureKind","SyntaxKind","getTokenPosOfNode"],"sources":["../src/typescriptProject.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { API, SignatureKind, type Checker, type Type } from 'typescript/unstable/async';\nimport { getTokenPosOfNode, SyntaxKind, type Node, type SourceFile } from 'typescript/unstable/ast';\n\nexport interface ReactComponentFunctionMetric {\n file: string;\n name?: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface TypeScriptProjectMetrics {\n callExpressionCount: number;\n configDiagnosticCount: number;\n configFile: string;\n declarationDiagnosticCount: number;\n diagnosticFileCount: number;\n measuredRootFileCount: number;\n projectCount: number;\n reactComponentFunctions: ReactComponentFunctionMetric[];\n resolvedCallExpressionCount: number;\n resolvedCallExpressionRatio: number;\n rootFileCount: number;\n semanticDiagnosticCount: number;\n suggestionDiagnosticCount: number;\n syntacticDiagnosticCount: number;\n unresolvedCallExpressionCount: number;\n}\n\ninterface DiagnosticLike {\n fileName?: string;\n}\n\ninterface ReactComponentCandidateMetric extends Omit<ReactComponentFunctionMetric, 'file'> {\n implementationKey: string;\n}\n\nexport async function measureTypeScriptProject(\n configFile: string,\n measuredFiles: readonly string[] = []\n): Promise<TypeScriptProjectMetrics> {\n const api = new API({ cwd: process.cwd() });\n try {\n const snapshot = await api.updateSnapshot({ openProject: configFile });\n const projects = snapshot.getProjects();\n const measuredFileByCanonicalFile = await mapMeasuredFilesByCanonicalFile(measuredFiles);\n const measuredFileSet = new Set(measuredFileByCanonicalFile.keys());\n const diagnosticFiles = new Set<string>();\n const totals = {\n callExpressionCount: 0,\n configDiagnosticCount: 0,\n declarationDiagnosticCount: 0,\n measuredRootFileCount: 0,\n resolvedCallExpressionCount: 0,\n rootFileCount: 0,\n semanticDiagnosticCount: 0,\n suggestionDiagnosticCount: 0,\n syntacticDiagnosticCount: 0,\n };\n const reactComponentFunctions: ReactComponentFunctionMetric[] = [];\n const analyzedFiles = new Set<string>();\n\n for (const project of projects) {\n const projectRootFiles = await Promise.all(\n project.rootFiles.map(async (file) => ({ canonicalFile: await canonicalizeFile(file), file }))\n );\n totals.rootFileCount += projectRootFiles.length;\n totals.measuredRootFileCount += projectRootFiles.filter(({ canonicalFile }) =>\n measuredFileSet.has(canonicalFile)\n ).length;\n\n const syntacticDiagnostics = await project.program.getSyntacticDiagnostics();\n const semanticDiagnostics = await project.program.getSemanticDiagnostics();\n const suggestionDiagnostics = await project.program.getSuggestionDiagnostics();\n const declarationDiagnostics = await project.program.getDeclarationDiagnostics();\n const configDiagnostics = await project.program.getConfigFileParsingDiagnostics();\n totals.syntacticDiagnosticCount += syntacticDiagnostics.length;\n totals.semanticDiagnosticCount += semanticDiagnostics.length;\n totals.suggestionDiagnosticCount += suggestionDiagnostics.length;\n totals.declarationDiagnosticCount += declarationDiagnostics.length;\n totals.configDiagnosticCount += configDiagnostics.length;\n addDiagnosticFiles(diagnosticFiles, [\n ...syntacticDiagnostics,\n ...semanticDiagnostics,\n ...suggestionDiagnostics,\n ...declarationDiagnostics,\n ...configDiagnostics,\n ]);\n\n const projectAnalysisFiles =\n measuredFileSet.size === 0\n ? projectRootFiles\n : [...measuredFileByCanonicalFile.entries()].map(([canonicalFile, file]) => ({ canonicalFile, file }));\n const configDirectory = path.dirname(project.configFileName);\n const canonicalConfigDirectory = await canonicalizeFile(configDirectory);\n for (const { canonicalFile, file } of projectAnalysisFiles) {\n if (analyzedFiles.has(canonicalFile)) {\n continue;\n }\n const sourceFile = await getProjectSourceFile(\n project,\n file,\n canonicalFile,\n configDirectory,\n canonicalConfigDirectory\n );\n if (!sourceFile) {\n continue;\n }\n analyzedFiles.add(canonicalFile);\n const callExpressions = collectCallExpressions(sourceFile);\n totals.callExpressionCount += callExpressions.length;\n const fileReactComponentFunctions = await collectReactComponentFunctions(sourceFile, project.checker);\n reactComponentFunctions.push(\n ...fileReactComponentFunctions.map((component) => ({\n ...component,\n file: measuredFileByCanonicalFile.get(canonicalFile) ?? file,\n }))\n );\n for (const callExpression of callExpressions) {\n if (await project.checker.getResolvedSignature(callExpression)) {\n totals.resolvedCallExpressionCount += 1;\n }\n }\n }\n }\n\n const unresolvedCallExpressionCount = totals.callExpressionCount - totals.resolvedCallExpressionCount;\n return {\n ...totals,\n configFile,\n diagnosticFileCount: diagnosticFiles.size,\n projectCount: projects.length,\n reactComponentFunctions,\n unresolvedCallExpressionCount,\n resolvedCallExpressionRatio:\n totals.callExpressionCount === 0 ? 0 : totals.resolvedCallExpressionCount / totals.callExpressionCount,\n };\n } finally {\n await api.close();\n }\n}\n\nasync function getProjectSourceFile(\n project: { configFileName: string; program: { getSourceFile: (file: string) => Promise<SourceFile | undefined> } },\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): Promise<SourceFile | undefined> {\n for (const candidate of getProjectFileCandidates(file, canonicalFile, configDirectory, canonicalConfigDirectory)) {\n const sourceFile = await project.program.getSourceFile(candidate);\n if (sourceFile) {\n return sourceFile;\n }\n }\n return undefined;\n}\n\nfunction getProjectFileCandidates(\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): string[] {\n const candidates = new Set([file, canonicalFile]);\n if (canonicalFile.startsWith(`${canonicalConfigDirectory}${path.sep}`)) {\n candidates.add(path.join(configDirectory, path.relative(canonicalConfigDirectory, canonicalFile)));\n }\n return [...candidates];\n}\n\nasync function mapMeasuredFilesByCanonicalFile(files: readonly string[]): Promise<Map<string, string>> {\n const measuredFileByCanonicalFile = new Map<string, string>();\n for (const file of files) {\n measuredFileByCanonicalFile.set(await canonicalizeFile(file), file);\n }\n return measuredFileByCanonicalFile;\n}\n\nasync function canonicalizeFile(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch {\n return file;\n }\n}\n\nasync function collectReactComponentFunctions(\n sourceFile: SourceFile,\n checker: Checker\n): Promise<Omit<ReactComponentFunctionMetric, 'file'>[]> {\n const components: ReactComponentCandidateMetric[] = [];\n const candidates = collectReactComponentCandidates(sourceFile);\n const hasReactReference = containsReactModuleReference(sourceFile.text);\n for (const candidate of candidates) {\n if (!(await isReactComponentCandidate(candidate, checker, hasReactReference))) {\n continue;\n }\n const name = findNameNode(candidate);\n const functionNode = findComponentFunctionNode(candidate);\n const startOffset = findFunctionStartPosition(sourceFile.text, functionNode, name, sourceFile);\n const startPosition = positionToLineColumn(sourceFile.text, startOffset);\n components.push({\n implementationKey: `${functionNode.pos}:${functionNode.end}`,\n name: name ? findCandidateName(name) : undefined,\n startColumn: startPosition.column,\n startLine: startPosition.line,\n });\n }\n return dedupeReactComponentFunctions(components);\n}\n\nfunction collectReactComponentCandidates(root: Node): Node[] {\n const candidates: Node[] = [];\n visitNode(root, (node) => {\n if (isFunctionLikeCandidate(node) || isVariableFunctionCandidate(node)) {\n candidates.push(node);\n }\n });\n return candidates;\n}\n\nasync function isReactComponentCandidate(node: Node, checker: Checker, hasReactReference: boolean): Promise<boolean> {\n const type = await checker.getTypeAtLocation(node);\n if (!type) {\n return false;\n }\n\n const name = findNameNode(node);\n const componentName = name ? findCandidateName(name) : undefined;\n if (hasReactReference && (await hasReactComponentTypeName(type, node, checker))) {\n return true;\n }\n\n const namedType = name ? await checker.getTypeAtLocation(name) : undefined;\n if (hasReactReference && namedType && (await hasReactComponentTypeName(namedType, node, checker))) {\n return true;\n }\n\n if (!isUppercaseComponentName(componentName)) {\n return false;\n }\n\n return (\n (await hasReactRenderableReturnType(type, node, checker)) ||\n Boolean(namedType && (await hasReactRenderableReturnType(namedType, node, checker)))\n );\n}\n\nfunction containsReactModuleReference(text: string): boolean {\n return /\\bfrom\\s+['\"]react['\"]|\\bimport\\s+['\"]react['\"]|\\brequire\\(\\s*['\"]react['\"]\\s*\\)/u.test(text);\n}\n\nasync function hasReactRenderableReturnType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const signatures = await checker.getSignaturesOfType(type, SignatureKind.Call);\n for (const signature of signatures) {\n const returnType = await checker.getReturnTypeOfSignature(signature);\n if (returnType && (await isReactRenderableType(returnType, node, checker))) {\n return true;\n }\n }\n return false;\n}\n\nasync function hasReactComponentTypeName(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\\b/u.test(\n typeName\n );\n}\n\nasync function isReactRenderableType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:JSX\\.Element|React\\.JSX\\.Element|ReactElement|ReactNode)\\b/u.test(typeName);\n}\n\nfunction isFunctionLikeCandidate(node: Node): boolean {\n if (node.kind === SyntaxKind.FunctionDeclaration && !getNodeProperty(node, 'body')) {\n return false;\n }\n\n return (\n node.kind === SyntaxKind.FunctionDeclaration ||\n node.kind === SyntaxKind.FunctionExpression ||\n node.kind === SyntaxKind.ArrowFunction\n );\n}\n\nfunction isVariableFunctionCandidate(node: Node): boolean {\n if (node.kind !== SyntaxKind.VariableDeclaration) {\n return false;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n return unwrappedInitializer\n ? isFunctionLikeCandidate(unwrappedInitializer) || unwrappedInitializer.kind === SyntaxKind.CallExpression\n : false;\n}\n\nfunction findComponentFunctionNode(node: Node): Node {\n if (isFunctionLikeCandidate(node)) {\n return node;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n if (!unwrappedInitializer) {\n return node;\n }\n if (isFunctionLikeCandidate(unwrappedInitializer)) {\n return unwrappedInitializer;\n }\n if (isComponentImplementationWrapperCall(unwrappedInitializer)) {\n return findComponentImplementationWrapperFunction(unwrappedInitializer) ?? node;\n }\n return node;\n}\n\nfunction isComponentImplementationWrapperCall(node: Node): boolean {\n if (node.kind !== SyntaxKind.CallExpression) {\n return false;\n }\n\n const expression = getNodeProperty(node, 'expression');\n const expressionName = expression ? findCallExpressionName(expression) : undefined;\n return expressionName === 'memo' || expressionName === 'forwardRef';\n}\n\nfunction findCallExpressionName(expression: Node): string | undefined {\n const name = getNodeProperty(expression, 'name');\n return name ? findCandidateName(name) : findCandidateName(expression);\n}\n\nfunction findComponentImplementationWrapperFunction(node: Node): Node | undefined {\n let functionNode: Node | undefined;\n node.forEachChild((child) => {\n const unwrappedChild = unwrapExpression(child);\n if (isFunctionLikeCandidate(unwrappedChild)) {\n functionNode = unwrappedChild;\n } else if (isComponentImplementationWrapperCall(unwrappedChild)) {\n functionNode = findComponentImplementationWrapperFunction(unwrappedChild);\n }\n return functionNode;\n });\n return functionNode;\n}\n\nfunction findFunctionStartPosition(\n text: string,\n functionNode: Node,\n name: Node | undefined,\n sourceFile: SourceFile\n): number {\n const tokenPosition = getTokenPosOfNode(functionNode, sourceFile);\n if (functionNode.kind !== SyntaxKind.FunctionDeclaration || !name) {\n return tokenPosition;\n }\n\n const functionName = findCandidateName(name);\n const namePosition = functionName ? text.indexOf(functionName, tokenPosition) : -1;\n const functionPosition = namePosition >= 0 ? text.lastIndexOf('function', namePosition) : -1;\n if (functionPosition < tokenPosition) {\n return tokenPosition;\n }\n\n const modifierText = text.slice(tokenPosition, functionPosition);\n const asyncMatch = /\\basync\\s*$/u.exec(modifierText);\n return asyncMatch ? tokenPosition + asyncMatch.index : functionPosition;\n}\n\nfunction dedupeReactComponentFunctions(\n components: readonly ReactComponentCandidateMetric[]\n): Omit<ReactComponentFunctionMetric, 'file'>[] {\n const componentByKey = new Map<string, ReactComponentCandidateMetric>();\n for (const component of components) {\n const existingComponent = componentByKey.get(component.implementationKey);\n if (!existingComponent || (!existingComponent.name && component.name)) {\n componentByKey.set(component.implementationKey, component);\n }\n }\n return [...componentByKey.values()].map((component) => ({\n name: component.name,\n startColumn: component.startColumn,\n startLine: component.startLine,\n }));\n}\n\nfunction findCandidateName(name: Node): string | undefined {\n if ('escapedText' in name) {\n return String(name.escapedText);\n }\n if ('text' in name && typeof name.text === 'string') {\n return name.text;\n }\n return undefined;\n}\n\nfunction isUppercaseComponentName(name: string | undefined): boolean {\n return name !== undefined && /^[A-Z]/u.test(name);\n}\n\nfunction findNameNode(node: Node): Node | undefined {\n const name = getNodeProperty(node, 'name');\n if (name) {\n return name;\n }\n\n let currentNode: Node | undefined = node;\n while (currentNode) {\n const parent = getNodeProperty(currentNode, 'parent');\n if (!parent) {\n return undefined;\n }\n if (parent.kind === SyntaxKind.VariableDeclaration) {\n return getNodeProperty(parent, 'name');\n }\n if (!isExpressionWrapperNode(parent)) {\n return undefined;\n }\n currentNode = parent;\n }\n return undefined;\n}\n\nfunction unwrapExpression(node: Node): Node {\n let currentNode = node;\n while (isExpressionWrapperNode(currentNode)) {\n const expression = getNodeProperty(currentNode, 'expression');\n if (!expression) {\n return currentNode;\n }\n currentNode = expression;\n }\n return currentNode;\n}\n\nfunction isExpressionWrapperNode(node: Node): boolean {\n return (\n node.kind === SyntaxKind.ParenthesizedExpression ||\n node.kind === SyntaxKind.AsExpression ||\n node.kind === SyntaxKind.TypeAssertionExpression ||\n node.kind === SyntaxKind.SatisfiesExpression\n );\n}\n\nfunction getNodeProperty(\n node: Node,\n property: 'body' | 'expression' | 'initializer' | 'name' | 'parent'\n): Node | undefined {\n const value = (node as Partial<Record<typeof property, Node>>)[property];\n return isNode(value) ? value : undefined;\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && 'kind' in value && 'pos' in value && 'end' in value;\n}\n\nfunction positionToLineColumn(text: string, position: number): { column: number; line: number } {\n const lines = text.slice(0, position).split('\\n');\n return { column: lines.at(-1)?.length ?? 0, line: lines.length };\n}\n\nfunction collectCallExpressions(root: Node): Node[] {\n const calls: Node[] = [];\n visitNode(root, (node) => {\n if (node.kind === SyntaxKind.CallExpression) {\n calls.push(node);\n }\n });\n return calls;\n}\n\nfunction visitNode(node: Node, visitor: (node: Node) => void): void {\n visitor(node);\n node.forEachChild((child) => {\n visitNode(child, visitor);\n return;\n });\n}\n\nfunction addDiagnosticFiles(files: Set<string>, diagnostics: readonly DiagnosticLike[]): void {\n for (const diagnostic of diagnostics) {\n if (diagnostic.fileName) {\n files.add(diagnostic.fileName);\n }\n }\n}\n"],"mappings":"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"}
|