code-gauge 1.8.0 → 1.10.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 CHANGED
@@ -16,7 +16,7 @@ npm install -g code-gauge
16
16
  code-gauge path/to/project
17
17
  ```
18
18
 
19
- The CLI scans JavaScript, JSX, TypeScript, TSX, Python, Go, and Rust files. By default it skips generated, vendor, test, and tool directories, prints a summary, and lists the highest-risk findings. TypeScript project metrics and React component classification turn on automatically when a `tsconfig.json` is found.
19
+ The CLI scans JavaScript, JSX, TypeScript, TSX, Python, Go, Rust, Java, Ruby, C, and C++ files. By default it skips generated, vendor, test, and tool directories, prints a summary, and lists the highest-risk findings. TypeScript project metrics and React component classification turn on automatically when a `tsconfig.json` is found.
20
20
 
21
21
  ## Options
22
22
 
@@ -26,6 +26,7 @@ The CLI scans JavaScript, JSX, TypeScript, TSX, Python, Go, and Rust files. By d
26
26
  | `--include-tests` | Include test files and test directories. |
27
27
  | `--tsconfig <path>` | Use this `tsconfig.json` instead of the auto-detected one. |
28
28
  | `--max-findings <n>` | Maximum number of findings to print (default: 20). |
29
+ | `--largest-files <n>` | List the `n` largest files by code LOC (config key: `largestFiles`). |
29
30
  | `--json` | Print machine-readable JSON. |
30
31
  | `--fail-on-risk` | Exit with code 1 when any high-risk finding is reported. |
31
32
  | `--fail-on-error` | Exit with code 1 when any file or directory cannot be scanned. |
@@ -50,6 +51,7 @@ A finding is reported when a measured value is **greater than or equal to** its
50
51
  "fanOut": 10,
51
52
  "parameter": 8,
52
53
  "duplicateBlock": 2,
54
+ "duplicationRatioPercent": 30,
53
55
  "transitiveDependency": 25,
54
56
  "structuralBreadth": 8,
55
57
  "structuralCoordination": 300,
@@ -58,6 +60,7 @@ A finding is reported when a measured value is **greater than or equal to** its
58
60
  },
59
61
  "languageThresholds": {
60
62
  "python": { "stateMutation": 90, "structuralCoordination": 350 },
63
+ "ruby": { "stateMutation": 90, "structuralCoordination": 350 },
61
64
  "react": { "import": 30 }
62
65
  },
63
66
  "maxFindings": 20,
@@ -67,23 +70,24 @@ A finding is reported when a measured value is **greater than or equal to** its
67
70
  }
68
71
  ```
69
72
 
70
- | Threshold | CLI flag | Reports when a … |
71
- | ------------------------ | ------------------------------------- | ------------------------------------------------ |
72
- | `fileLoc` | `--file-loc-threshold` | file's code LOC is large. |
73
- | `functionLoc` | `--function-loc-threshold` | function's physical LOC span is large. |
74
- | `componentLoc` | `--component-loc-threshold` | React component's physical LOC span is large. |
75
- | `cognitive` | `--cognitive-threshold` | function's cognitive complexity is high. |
76
- | `cyclomatic` | `--cyclomatic-threshold` | function's cyclomatic complexity is high. |
77
- | `call` | `--call-threshold` | function makes many calls. |
78
- | `import` | `--import-threshold` | file has many unique import sources. |
79
- | `fanOut` | `--fan-out-threshold` | function calls many other in-file functions. |
80
- | `parameter` | `--parameter-threshold` | function declares many parameters. |
81
- | `duplicateBlock` | `--duplicate-block-threshold` | file contains copy-pasted code blocks. |
82
- | `transitiveDependency` | `--transitive-dependency-threshold` | file transitively reaches many local files. |
83
- | `structuralBreadth` | `--structural-breadth-threshold` | file coordinates many structural concerns. |
84
- | `structuralCoordination` | `--structural-coordination-threshold` | file's structural coordination score is high. |
85
- | `stateMutation` | `--state-mutation-threshold` | file mutates state heavily. |
86
- | `duplicateSymbolGroup` | `--duplicate-symbol-group-threshold` | file shares many duplicated symbols with others. |
73
+ | Threshold | CLI flag | Reports when a … |
74
+ | ------------------------- | --------------------------------------- | ------------------------------------------------------ |
75
+ | `fileLoc` | `--file-loc-threshold` | file's code LOC is large. |
76
+ | `functionLoc` | `--function-loc-threshold` | function's physical LOC span is large. |
77
+ | `componentLoc` | `--component-loc-threshold` | React component's physical LOC span is large. |
78
+ | `cognitive` | `--cognitive-threshold` | function's cognitive complexity is high. |
79
+ | `cyclomatic` | `--cyclomatic-threshold` | function's cyclomatic complexity is high. |
80
+ | `call` | `--call-threshold` | function makes many calls. |
81
+ | `import` | `--import-threshold` | file has many unique import sources. |
82
+ | `fanOut` | `--fan-out-threshold` | function calls many other in-file functions. |
83
+ | `parameter` | `--parameter-threshold` | function declares many parameters. |
84
+ | `duplicateBlock` | `--duplicate-block-threshold` | file contains copy-pasted code blocks. |
85
+ | `duplicationRatioPercent` | `--duplication-ratio-percent-threshold` | large percentage of a file's code lines is duplicated. |
86
+ | `transitiveDependency` | `--transitive-dependency-threshold` | file transitively reaches many local files. |
87
+ | `structuralBreadth` | `--structural-breadth-threshold` | file coordinates many structural concerns. |
88
+ | `structuralCoordination` | `--structural-coordination-threshold` | file's structural coordination score is high. |
89
+ | `stateMutation` | `--state-mutation-threshold` | file mutates state heavily. |
90
+ | `duplicateSymbolGroup` | `--duplicate-symbol-group-threshold` | file shares many duplicated symbols with others. |
87
91
 
88
92
  ### Per-language thresholds
89
93
 
@@ -92,9 +96,11 @@ over-flags one language or under-flags another. `languageThresholds` overrides i
92
96
  profile without repeating the whole set. Each file resolves its thresholds as **base → its language profile →
93
97
  the `react` profile** (the last applies when the file contains a React component), so later profiles win.
94
98
 
95
- Valid profile keys are `javascript`, `jsx`, `typescript`, `tsx`, `python`, `go`, `rust`, and `react`. Built-in
96
- overrides raise `stateMutation` and `structuralCoordination` for Python (every binding is an assignment, so
97
- these run far higher than in TypeScript) and raise `import` for React files (which pull in many components).
99
+ Valid profile keys are `javascript`, `jsx`, `typescript`, `tsx`, `python`, `go`, `rust`, `java`, `ruby`, `c`,
100
+ `cpp`, and `react`. Built-in
101
+ overrides raise `stateMutation` and `structuralCoordination` for Python and Ruby (every binding is an
102
+ assignment, so these run far higher than in TypeScript) and raise `import` for React files (which pull in
103
+ many components).
98
104
  Anything you specify is merged on top of the built-in overrides, so `{ "python": { "stateMutation": 8 } }`
99
105
  restores the global value for Python while keeping the other built-in adjustments.
100
106
 
@@ -116,7 +122,7 @@ Command-line `--<metric>-threshold` flags set the global base only; use the conf
116
122
  - Cyclomatic and cognitive complexity (per function and maximum)
117
123
  - Nesting depth
118
124
  - Intra-file call graph metrics: call counts, fan-in/fan-out, recursion, call depth, and parameter counts
119
- - Within-file structural duplication: copy-pasted code blocks (distinct from cross-file duplicate symbol names)
125
+ - Within-file duplication: copy-pasted blocks and statement runs matched on normalized tokens (identifiers anonymized consistently, literals by kind), plus duplicated line count and ratio (distinct from cross-file duplicate symbol names)
120
126
  - File coupling (imports/exports) and cohesion (shared function identifiers)
121
127
  - Architecture metrics: transitive local dependencies, structural coordination and breadth, state mutation, and cross-file duplicate symbols
122
128
  - TypeScript type-shape metrics: annotations, aliases, interfaces, generics, unions, intersections, assertions, and conditional types
@@ -124,7 +130,7 @@ Command-line `--<metric>-threshold` flags set the global base only; use the conf
124
130
 
125
131
  ## Supported languages
126
132
 
127
- Built-in parsers cover JavaScript, JSX, TypeScript, TSX, Python, Go, and Rust. Additional tree-sitter grammars can be registered with `TreeMeasurer.registerLanguage`.
133
+ Built-in parsers cover JavaScript, JSX, TypeScript, TSX, Python, Go, Rust, Java, Ruby, C, and C++. Additional tree-sitter grammars can be registered with `TreeMeasurer.registerLanguage`.
128
134
 
129
135
  ## Programmatic API
130
136
 
@@ -0,0 +1 @@
1
+ "use strict";var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));exports.__toESM=s;
@@ -1,2 +1,2 @@
1
- "use strict";var t=require("node:path");function e(e,n,o){if(!n.startsWith("."))return;const r=function(e,n){const o=t.dirname(e);if(function(t){return t.startsWith("./")||t.startsWith("../")||t.includes("/")}(n))return[t.normalize(t.join(o,n))];const r=/^(?<dots>\.+)(?<module>.*)$/u.exec(n),a=r?.groups?.dots,i=r?.groups?.module;if(!a||void 0===i)return[];let u=o;for(let e=1;e<a.length;e+=1)u=t.dirname(u);const l=i.replaceAll(".",t.sep);return[t.normalize(t.join(u,l))]}(e,n),a=r.flatMap(e=>{const n=t.extname(e);return n?[e.slice(0,-n.length),e]:[e]});for(const e of a)for(const n of[`${e}.ts`,`${e}.tsx`,`${e}.js`,`${e}.jsx`,`${e}.py`,t.join(e,"index.ts"),t.join(e,"index.tsx"),t.join(e,"index.js"),t.join(e,"__init__.py")])if(o.has(n))return n}function n(t,e){const n=new Set,o=[...e.get(t)??[]];for(;o.length>0;){const t=o.pop();t&&!n.has(t)&&(n.add(t),o.push(...e.get(t)??[]))}return n.size}function o(t){return t.name.length>=5}function r(t,e){return{file:t,line:e.startLine}}function a(t,e){return t.file.localeCompare(e.file)||t.line-e.line}function i(t,e,n){n&&t.add(e)}function u(t,e){return Math.max(0,...t.map(t=>t[e]).filter(t=>"number"==typeof t))}exports.measureArchitecture=function(l,s){const c=l.map(e=>({...e,relativeFile:t.relative(s,e.file)||t.basename(e.file)})),p=function(t){const n=new Set(t.map(t=>t.relativeFile)),o=new Map;for(const r of t){const t=new Set;for(const o of r.metrics.module.importSources){const a=e(r.relativeFile,o,n);a&&t.add(a)}o.set(r.relativeFile,t)}return o}(c),m=function(t){const e=new Map;for(const n of t)for(const t of n.metrics.module.declarations){if(!o(t))continue;const a=e.get(t.name)??[];a.push(r(n.relativeFile,t)),e.set(t.name,a)}return[...e.entries()].flatMap(([t,e])=>{const n=[...new Set(e.map(t=>t.file))].toSorted();return n.length>1?[{declarations:e.toSorted(a),files:n,name:t}]:[]}).toSorted((t,e)=>e.files.length-t.files.length||t.name.localeCompare(e.name))}(c),f=function(t){const e=new Map;for(const n of t)for(const t of n.files)e.set(t,(e.get(t)??0)+1);return e}(m),d=c.map(t=>{const e=p.get(t.relativeFile)??new Set,o=function(t){const e=t.syntaxFeatures.awaitExpressionCount,n=t.syntaxFeatures.tryStatementCount+t.syntaxFeatures.throwStatementCount,o=t.syntaxFeatures.mutableBindingCount+t.syntaxFeatures.assignmentCount,r=t.cyclomaticComplexity+t.syntaxFeatures.loopStatementCount+t.syntaxFeatures.returnStatementCount,a=t.callGraph.callCount+2*t.callGraph.internalEdgeCount+3*t.callGraph.maxCallDepth;return{asyncBoundaryCount:e,branchingScore:r,exceptionHandlingCount:n,moduleInteractionScore:a,score:2*e+3*n+o+r+a,stateMutationScore:o}}(t.metrics),r=function(t){const e=new Set;return i(e,"class-shapes",t.classCount>0),i(e,"control-flow",t.cognitiveComplexity>0),i(e,"external-dependencies",t.coupling.externalImportCount>0),i(e,"functions",t.functionCount>0),i(e,"local-dependencies",t.coupling.relativeImportCount>0),i(e,"module-api",t.coupling.exportCount>0||t.module.declarations.length>0),i(e,"state-mutation",t.syntaxFeatures.mutableBindingCount+t.syntaxFeatures.assignmentCount>0),i(e,"type-shapes",function(t){return t.typeComplexity.typeAnnotationCount+t.typeComplexity.typeAliasCount+t.typeComplexity.interfaceCount+t.typeComplexity.genericParameterCount+t.typeComplexity.unionTypeCount+t.typeComplexity.intersectionTypeCount+t.typeComplexity.conditionalTypeCount>0}(t)),[...e].toSorted()}(t.metrics);return{file:t.relativeFile,directLocalDependencyCount:e.size,duplicateSymbolGroupCount:f.get(t.relativeFile)??0,structuralBreadthScore:r.length,structuralCoordination:o,structuralFeatureGroups:r,transitiveLocalDependencyCount:n(t.relativeFile,p)}});return{duplicateSymbolGroups:m,files:d,maxDirectLocalDependencyCount:u(d,"directLocalDependencyCount"),maxDuplicateSymbolGroupCount:u(d,"duplicateSymbolGroupCount"),maxStructuralBreadthScore:u(d,"structuralBreadthScore"),maxStructuralCoordinationScore:Math.max(0,...d.map(t=>t.structuralCoordination.score)),maxTransitiveLocalDependencyCount:u(d,"transitiveLocalDependencyCount")}};
2
- //# sourceMappingURL=architectureMetrics.cjs.map
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 +1 @@
1
- {"version":3,"file":"architectureMetrics.cjs","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.get(sourceFile.relativeFile) ?? new Set<string>();\n const structuralCoordination = measureStructuralCoordination(sourceFile.metrics);\n const structuralFeatureGroups = measureStructuralFeatureGroups(sourceFile.metrics);\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(sourceFile.relativeFile, dependencyGraph),\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\nfunction measureDependencyGraph(files: SourceFile[]): Map<string, Set<string>> {\n const fileSet = new Set(files.map((file) => file.relativeFile));\n const graph = new Map<string, Set<string>>();\n for (const file of files) {\n const dependencies = new Set<string>();\n for (const source of file.metrics.module.importSources) {\n const resolved = resolveLocalImport(file.relativeFile, source, fileSet);\n if (resolved) {\n dependencies.add(resolved);\n }\n }\n graph.set(file.relativeFile, dependencies);\n }\n return graph;\n}\n\nfunction resolveLocalImport(fromFile: string, source: string, fileSet: Set<string>): string | undefined {\n if (!source.startsWith('.')) {\n return undefined;\n }\n const bases = localImportBases(fromFile, source);\n const stems = bases.flatMap((base) => {\n const extension = path.extname(base);\n return extension ? [base.slice(0, -extension.length), base] : [base];\n });\n for (const stem of stems) {\n for (const candidate of [\n `${stem}.ts`,\n `${stem}.tsx`,\n `${stem}.js`,\n `${stem}.jsx`,\n `${stem}.py`,\n path.join(stem, 'index.ts'),\n path.join(stem, 'index.tsx'),\n path.join(stem, 'index.js'),\n path.join(stem, '__init__.py'),\n ]) {\n if (fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n return undefined;\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 const visited = new Set<string>();\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;\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 return declaration.name.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\nfunction measureStructuralFeatureGroups(metrics: CodeMetrics): 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 > 0);\n addGroup(groups, 'functions', metrics.functionCount > 0);\n addGroup(groups, 'local-dependencies', metrics.coupling.relativeImportCount > 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"],"names":["resolveLocalImport","fromFile","source","fileSet","startsWith","bases","fromDirectory","path","dirname","includes","isPathRelativeImport","normalize","join","match","exec","dots","groups","moduleName","module","undefined","baseDirectory","index","length","modulePath","replaceAll","sep","localImportBases","stems","flatMap","base","extension","extname","slice","stem","candidate","has","measureTransitiveDependencyCount","file","graph","visited","Set","pending","get","current","pop","add","push","size","isDuplicateSymbolCandidate","declaration","name","toDuplicateSymbolDeclaration","line","startLine","compareDuplicateDeclarations","left","right","localeCompare","addGroup","group","condition","maxFileMetric","metrics","key","Math","max","map","metric","filter","value","files","displayRoot","sourceFiles","relativeFile","relative","basename","dependencyGraph","Map","dependencies","importSources","resolved","set","measureDependencyGraph","duplicateSymbolGroups","declarationsBySymbol","declarations","entries","duplicateFiles","toSorted","measureDuplicateSymbolGroups","duplicateGroupCountByFile","counts","measureDuplicateGroupCountByFile","sourceFile","directDependencies","structuralCoordination","asyncBoundaryCount","syntaxFeatures","awaitExpressionCount","exceptionHandlingCount","tryStatementCount","throwStatementCount","stateMutationScore","mutableBindingCount","assignmentCount","branchingScore","cyclomaticComplexity","loopStatementCount","returnStatementCount","moduleInteractionScore","callGraph","callCount","internalEdgeCount","maxCallDepth","score","measureStructuralCoordination","structuralFeatureGroups","classCount","cognitiveComplexity","coupling","externalImportCount","functionCount","relativeImportCount","exportCount","typeComplexity","typeAnnotationCount","typeAliasCount","interfaceCount","genericParameterCount","unionTypeCount","intersectionTypeCount","conditionalTypeCount","hasTypeShapeMetrics","measureStructuralFeatureGroups","directLocalDependencyCount","duplicateSymbolGroupCount","structuralBreadthScore","transitiveLocalDependencyCount","maxDirectLocalDependencyCount","maxDuplicateSymbolGroupCount","maxStructuralBreadthScore","maxStructuralCoordinationScore","maxTransitiveLocalDependencyCount"],"mappings":"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"}
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":"6GAwDA,SAAgB,EACd,EACA,EACqB,CACrB,IAAM,EAAc,EAAM,IAAK,IAAU,CACvC,GAAG,EACH,aAAcA,EAAAA,QAAK,SAAS,EAAa,EAAK,IAAI,GAAKA,EAAAA,QAAK,SAAS,EAAK,IAAI,CAChF,EAAE,EACI,EAAkB,EAAuB,CAAW,EACpD,EAAwB,EAA6B,CAAW,EAChE,EAA4B,EAAiC,CAAqB,EAClF,EAAU,EAAY,IAAK,GAAe,CAC9C,IAAM,EAAqB,EAAgB,mBAAmB,IAAI,EAAW,YAAY,GAAK,IAAI,IAC5F,EACJ,EAAgB,kCAAkC,IAAI,EAAW,YAAY,GAAK,EAC9E,EAAyB,EAA8B,EAAW,OAAO,EACzE,EAA0B,EAC9B,EAAW,QACX,EAAmB,KACnB,CACF,EACA,MAAO,CACL,KAAM,EAAW,aACjB,2BAA4B,EAAmB,KAC/C,0BAA2B,EAA0B,IAAI,EAAW,YAAY,GAAK,EACrF,uBAAwB,EAAwB,OAChD,yBACA,0BACA,+BAAgC,EAC9B,EAAW,aACX,EAAgB,kBAClB,CACF,CACF,CAAC,EAED,MAAO,CACL,wBACA,MAAO,EACP,8BAA+B,EAAc,EAAS,4BAA4B,EAClF,6BAA8B,EAAc,EAAS,2BAA2B,EAChF,0BAA2B,EAAc,EAAS,wBAAwB,EAC1E,+BAAgC,KAAK,IAAI,EAAG,GAAG,EAAQ,IAAK,GAAS,EAAK,uBAAuB,KAAK,CAAC,EACvG,kCAAmC,EAAc,EAAS,gCAAgC,CAC5F,CACF,CAQA,SAAS,EAAuB,EAAsC,CACpE,IAAM,EAAU,IAAI,IAAI,EAAM,IAAK,GAAS,EAAK,YAAY,CAAC,EACxD,EAAgB,EAAmB,CAAK,EACxC,EAAqB,IAAI,IACzB,EAAoC,IAAI,IAC9C,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAe,IAAI,IACrB,EAA8B,EAClC,IAAK,IAAM,KAAU,EAAK,QAAQ,OAAO,cAAe,CACtD,IAAM,EAAW,EAAmB,EAAK,aAAc,EAAQ,EAAS,CAAa,EAEjF,GAAY,IAAa,EAAK,eAChC,EAAa,IAAI,CAAQ,EAIrB,EAAc,EAAK,YAAY,IAAM,SAAW,CAAC,EAAO,WAAW,GAAG,IACxE,GAA+B,GAGrC,CACA,EAAmB,IAAI,EAAK,aAAc,CAAY,EACtD,EAAkC,IAAI,EAAK,aAAc,CAA2B,CACtF,CACA,MAAO,CAAE,qBAAoB,mCAAkC,CACjE,CAEA,MAAM,EAAe,IAAI,IAAI,CAAC,MAAO,OAAQ,MAAO,OAAQ,OAAQ,OAAQ,OAAQ,MAAM,CAAC,EAGrF,EAAc,IAAI,IAAI,CAAC,KAAM,OAAQ,MAAO,MAAO,OAAQ,OAAQ,KAAM,MAAO,OAAQ,OAAQ,MAAM,CAAC,EAOvG,EAAiC,IAAI,IAAsB,CAC/D,CAAC,MAAO,CAAC,MAAO,MAAM,CAAC,EACvB,CAAC,OAAQ,CAAC,MAAM,CAAC,EACjB,CAAC,OAAQ,CAAC,MAAM,CAAC,EACjB,CAAC,OAAQ,CAAC,MAAM,CAAC,CACnB,CAAC,EAED,SAAS,EACP,EACA,EACA,EACA,EACoB,CACpB,IAAM,EAAgB,EAAc,CAAQ,EAC5C,GAAI,CAAC,EAAO,WAAW,GAAG,EAKxB,OAJI,IAAkB,OAAS,iCAAiC,KAAK,CAAM,EAClE,EAAuB,EAAU,EAAQ,CAAO,EAGlD,IAAkB,QAAU,EAAkB,EAAQ,CAAa,EAAI,IAAA,GAEhF,IAAM,EAAQ,EAAiB,EAAU,CAAM,EAM/C,GAAI,EAAa,IAAI,CAAa,EAChC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAgB,EAA+B,IAAIA,EAAAA,QAAK,QAAQ,CAAI,CAAC,EAC3E,GAAI,CAAC,EACH,SAEF,IAAM,EAAO,EAAK,MAAM,EAAG,CAACA,EAAAA,QAAK,QAAQ,CAAI,CAAC,CAAC,MAAM,EACrD,IAAK,IAAM,KAAgB,EACzB,GAAI,EAAQ,IAAI,GAAG,IAAO,GAAc,EACtC,MAAO,GAAG,IAAO,GAGvB,CAMF,IAAK,IAAM,KAAQ,EACjB,GAAI,EAAQ,IAAI,CAAI,EAClB,OAAO,EAIX,GAAM,CAAE,aAAY,cAAe,EAAa,CAAQ,EAGxD,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAK,IAAM,KAAa,EACtB,GAAI,EAAQ,IAAI,GAAG,IAAO,GAAW,EACnC,MAAO,GAAG,IAAO,IAGrB,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAYA,EAAAA,QAAK,KAAK,EAAM,CAAS,EAC3C,GAAI,EAAQ,IAAI,CAAS,EACvB,OAAO,CAEX,CACF,CAEF,CAGA,SAAS,EAAa,EAAkE,CACtF,IAAM,EAAgB,EAAc,CAAQ,EAkB5C,OAjBI,IAAkB,MACb,CAAE,WAAY,CAAC,KAAK,EAAG,WAAY,CAAC,CAAE,EAE3C,IAAkB,MACb,CAAE,WAAY,CAAC,KAAK,EAAG,WAAY,CAAC,aAAa,CAAE,EAExD,EAAY,IAAI,CAAa,EAGxB,CAAE,WAAY,CAAC,EAAG,WAAY,CAAC,CAAE,EAEtC,EAAa,IAAI,CAAa,EACzB,CACL,WAAY,CAAC,MAAO,OAAQ,MAAO,OAAQ,OAAQ,OAAQ,OAAQ,MAAM,EACzE,WAAY,CAAC,WAAY,YAAa,UAAU,CAClD,EAEK,CACL,WAAY,CAAC,MAAO,OAAQ,MAAO,OAAQ,MAAO,MAAO,KAAM,OAAQ,MAAO,OAAQ,KAAM,OAAQ,MAAO,MAAM,EACjH,WAAY,CAAC,WAAY,YAAa,WAAY,aAAa,CACjE,CACF,CAGA,MAAM,EAAqB,IAAI,IAAI,CAAC,SAAU,SAAU,SAAS,CAAC,EAQlE,SAAS,EAAuB,EAAkB,EAAgB,EAA0C,CAC1G,IAAM,EAAW,EAAO,MAAM,IAAI,EAC5B,EAAO,EAAS,MAAM,EAGxB,EAAkB,EACtB,KAAO,IAAS,SAAW,EAAS,KAAO,SACzC,EAAS,MAAM,EACf,GAAmB,EAErB,IAAM,EAAa,EAAS,KAAK,GAAG,EACpC,GAAI,CAAC,EACH,OAEF,IAAM,EAAgB,EAAmBA,EAAAA,QAAK,QAAQ,CAAQ,CAAC,EACzD,EAAU,EAAmB,IAAIA,EAAAA,QAAK,SAAS,CAAQ,CAAC,EACxD,EAAgBA,EAAAA,QAAK,KAAK,EAAeA,EAAAA,QAAK,SAAS,EAAU,KAAK,CAAC,EAEzE,EACJ,AAUE,EAVE,IAAS,OAEO,EAAU,CAAC,CAAa,EAAI,CAAC,EAAe,CAAa,EAClE,IAAS,SAEhB,EACI,CAAC,EAAmBA,EAAAA,QAAK,QAAQ,CAAa,CAAC,EAAG,CAAa,EAC/D,CAAC,EAAe,EAAmBA,EAAAA,QAAK,QAAQ,CAAa,CAAC,CAAC,EAAA,CACnE,IAAK,GAAc,EAAgB,EAAW,CAAe,CAAC,EAE9C,EAAwB,EAAe,CAAO,EAKlE,IAAM,EAAc,CAAC,EAAY,EAAS,MAAM,EAAG,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,OAAO,EAChF,IAAK,IAAM,KAAQ,EACjB,IAAK,IAAM,KAAuB,EAChC,IAAK,IAAM,IAAa,CACtBA,EAAAA,QAAK,KAAK,EAAM,GAAG,EAAoB,IAAI,EAC3CA,EAAAA,QAAK,KAAK,EAAM,EAAqB,QAAQ,CAC/C,EACE,GAAI,IAAc,GAAY,EAAQ,IAAI,CAAS,EACjD,OAAO,CAMjB,CAGA,SAAS,EAAwB,EAAuB,EAAgC,CACtF,IAAM,EAAsB,CAAC,EACzB,EAAU,EACd,KACE,EAAU,KAAK,CAAO,EAClB,IAAY,IAGhB,EAAU,EAAmBA,EAAAA,QAAK,QAAQ,CAAO,CAAC,EAEpD,IAAM,EAAe,EAAU,OAC5B,GAAc,EAAQ,IAAIA,EAAAA,QAAK,KAAK,EAAW,QAAQ,CAAC,GAAK,EAAQ,IAAIA,EAAAA,QAAK,KAAK,EAAW,SAAS,CAAC,CAC3G,EACA,OAAO,EAAa,OAAS,EAAI,EAAe,CAClD,CAEA,SAAS,EAAgB,EAAmB,EAAwB,CAClE,IAAI,EAAU,EACd,IAAK,IAAI,EAAQ,EAAG,EAAQ,GAAU,IAAY,GAAI,GAAS,EAC7D,EAAU,EAAmBA,EAAAA,QAAK,QAAQ,CAAO,CAAC,EAEpD,OAAO,CACT,CAEA,SAAS,EAAmB,EAA2B,CACrD,OAAO,IAAc,IAAM,GAAK,CAClC,CASA,SAAS,EAAmB,EAAuD,CACjF,IAAM,EAAQ,IAAI,IAClB,IAAK,GAAM,CAAE,eAAc,aAAa,EAAO,CAC7C,GAAI,EAAc,CAAY,IAAM,QAClC,SAEF,IAAM,EAAgBA,EAAAA,QAAK,SAAS,EAAcA,EAAAA,QAAK,QAAQ,CAAY,CAAC,EACtE,GAAe,EAAmB,IAA4C,CAClF,IAAM,EAAY,EAAM,IAAI,CAAS,GAAK,CAAC,EACtC,EAAU,KAAM,GAAa,EAAS,OAAS,GAAgB,EAAS,gBAAkB,CAAa,IAC1G,EAAU,KAAK,CAAE,KAAM,EAAc,eAAc,CAAC,EACpD,EAAM,IAAI,EAAW,CAAS,EAElC,EAII,EAAe,GACnB,IAAK,IAAM,KAAe,EAAQ,OAAO,aAAc,CACrD,IAAM,EAAiB,EAAY,KAAK,YAAY,IAAI,EAClD,EAAY,IAAmB,GAAK,EAAY,KAAO,EAAY,KAAK,MAAM,EAAiB,CAAC,EAChG,EACJ,IAAmB,GAAK,IAAA,GAAY,EAAY,KAAK,MAAM,EAAG,CAAc,CAAC,CAAC,WAAW,IAAK,GAAG,EACnG,EAAY,EAAW,EAAc,GAAG,EAAY,GAAG,IAAc,IAAA,EAAS,EAC9E,IAAiB,IAAc,CACjC,CAEK,GACH,EAAY,EAAe,IAAA,EAAS,CAExC,CACA,IAAK,IAAM,KAAa,EAAM,OAAO,EACnC,EAAU,MAAM,EAAM,IAAU,EAAK,KAAK,cAAc,EAAM,IAAI,CAAC,EAErE,OAAO,CACT,CASA,SAAS,EAAkB,EAAgB,EAAqE,CAC9G,GAAI,EAAO,SAAS,IAAI,EACtB,OAIF,IAAM,EAAW,EAAO,MAAM,GAAG,EACjC,IAAK,IAAI,EAAM,EAAS,OAAQ,GAAO,EAAG,IAAU,CAClD,IAAM,EAAoB,EAAS,MAAM,EAAG,CAAG,EACzC,EAAY,EAAkB,GAAG,EAAE,EACzC,GAAI,CAAC,EACH,SAEF,IAAM,EAAe,EAAkB,KAAK,GAAG,EACzC,EAAY,EAAc,IAAI,CAAS,GAAK,CAAC,EAI7C,EAAe,EAAU,KAAM,GAAa,EAAS,gBAAkB,CAAY,EACzF,GAAI,EACF,OAAO,EAAa,KAItB,IAAM,EAAc,EAAU,KAAM,GAAa,CAC/C,GAAI,EAAS,cACX,MAAO,GAGT,IAAM,EAAa,EAAS,KAAK,WAAW,KAAM,GAAG,EAC/C,EAAO,EAAW,MAAM,EAAG,EAAW,OAASA,EAAAA,QAAK,QAAQ,CAAU,CAAC,CAAC,MAAM,EACpF,OAAO,IAAS,GAAgB,EAAK,SAAS,IAAI,GAAc,CAClE,CAAC,EACD,GAAI,EACF,OAAO,EAAY,IAEvB,CAEF,CAGA,SAAS,EAAc,EAAsB,CAC3C,OAAOA,EAAAA,QAAK,QAAQ,CAAI,CAAC,CAAC,YAAY,CACxC,CAEA,SAAS,EAAiB,EAAkB,EAA0B,CACpE,IAAM,EAAgBA,EAAAA,QAAK,QAAQ,CAAQ,EAC3C,GAAI,EAAqB,CAAM,EAC7B,MAAO,CAACA,EAAAA,QAAK,UAAUA,EAAAA,QAAK,KAAK,EAAe,CAAM,CAAC,CAAC,EAG1D,IAAM,EAAQ,+BAA+B,KAAK,CAAM,EAClD,EAAO,GAAO,QAAQ,KACtB,EAAa,GAAO,QAAQ,OAClC,GAAI,CAAC,GAAQ,IAAe,IAAA,GAC1B,MAAO,CAAC,EAGV,IAAI,EAAgB,EACpB,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,GAAS,EAChD,EAAgBA,EAAAA,QAAK,QAAQ,CAAa,EAG5C,IAAM,EAAa,EAAW,WAAW,IAAKA,EAAAA,QAAK,GAAG,EACtD,MAAO,CAACA,EAAAA,QAAK,UAAUA,EAAAA,QAAK,KAAK,EAAe,CAAU,CAAC,CAAC,CAC9D,CAEA,SAAS,EAAqB,EAAyB,CACrD,OAAO,EAAO,WAAW,IAAI,GAAK,EAAO,WAAW,KAAK,GAAK,EAAO,SAAS,GAAG,CACnF,CAEA,SAAS,EAAiC,EAAc,EAAyC,CAE/F,IAAM,EAAU,IAAI,IAAY,CAAC,CAAI,CAAC,EAChC,EAAU,CAAC,GAAI,EAAM,IAAI,CAAI,GAAK,CAAC,CAAE,EAC3C,KAAO,EAAQ,OAAS,GAAG,CACzB,IAAM,EAAU,EAAQ,IAAI,EACxB,CAAC,GAAW,EAAQ,IAAI,CAAO,IAGnC,EAAQ,IAAI,CAAO,EACnB,EAAQ,KAAK,GAAI,EAAM,IAAI,CAAO,GAAK,CAAC,CAAE,EAC5C,CACA,OAAO,EAAQ,KAAO,CACxB,CAEA,SAAS,EAA6B,EAA6C,CACjF,IAAM,EAAuB,IAAI,IACjC,IAAK,IAAM,KAAQ,EACjB,IAAK,IAAM,KAAe,EAAK,QAAQ,OAAO,aAAc,CAC1D,GAAI,CAAC,EAA2B,CAAW,EACzC,SAEF,IAAM,EAAe,EAAqB,IAAI,EAAY,IAAI,GAAK,CAAC,EACpE,EAAa,KAAK,EAA6B,EAAK,aAAc,CAAW,CAAC,EAC9E,EAAqB,IAAI,EAAY,KAAM,CAAY,CACzD,CAEF,MAAO,CAAC,GAAG,EAAqB,QAAQ,CAAC,CAAC,CACvC,SAAS,CAAC,EAAM,KAAkB,CACjC,IAAM,EAAiB,CAAC,GAAG,IAAI,IAAI,EAAa,IAAK,GAAgB,EAAY,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,EAClG,OAAO,EAAe,OAAS,EAC3B,CAAC,CAAE,aAAc,EAAa,SAAS,CAA4B,EAAG,MAAO,EAAgB,MAAK,CAAC,EACnG,CAAC,CACP,CAAC,CAAC,CACD,UAAU,EAAM,IAAU,EAAM,MAAM,OAAS,EAAK,MAAM,QAAU,EAAK,KAAK,cAAc,EAAM,IAAI,CAAC,CAC5G,CAEA,SAAS,EAA2B,EAA0C,CAK5E,OADkB,EAAY,KAAK,MAAM,QAAQ,CAAC,CAAC,GAAG,EAAE,GAAK,EAAY,KAAA,CACxD,QAAU,CAC7B,CAEA,SAAS,EAA6B,EAAc,EAA6D,CAC/G,MAAO,CAAE,OAAM,KAAM,EAAY,SAAU,CAC7C,CAEA,SAAS,EAA6B,EAAkC,EAA2C,CACjH,OAAO,EAAK,KAAK,cAAc,EAAM,IAAI,GAAK,EAAK,KAAO,EAAM,IAClE,CAEA,SAAS,EAAiC,EAAqD,CAC7F,IAAM,EAAS,IAAI,IACnB,IAAK,IAAM,KAAS,EAClB,IAAK,IAAM,KAAQ,EAAM,MACvB,EAAO,IAAI,GAAO,EAAO,IAAI,CAAI,GAAK,GAAK,CAAC,EAGhD,OAAO,CACT,CAEA,SAAS,EAA8B,EAAqD,CAC1F,IAAM,EAAqB,EAAQ,eAAe,qBAC5C,EAAyB,EAAQ,eAAe,kBAAoB,EAAQ,eAAe,oBAC3F,EAAqB,EAAQ,eAAe,oBAAsB,EAAQ,eAAe,gBACzF,EACJ,EAAQ,qBACR,EAAQ,eAAe,mBACvB,EAAQ,eAAe,qBACnB,EACJ,EAAQ,UAAU,UAAY,EAAQ,UAAU,kBAAoB,EAAI,EAAQ,UAAU,aAAe,EAE3G,MAAO,CACL,qBACA,iBACA,yBACA,yBACA,MACE,EAAqB,EACrB,EAAyB,EACzB,EACA,EACA,EACF,oBACF,CACF,CAMA,SAAS,EACP,EACA,EACA,EACU,CACV,IAAM,EAAS,IAAI,IAanB,OAZA,EAAS,EAAQ,eAAgB,EAAQ,WAAa,CAAC,EACvD,EAAS,EAAQ,eAAgB,EAAQ,oBAAsB,CAAC,EAChE,EAAS,EAAQ,wBAAyB,EAAQ,SAAS,oBAAsB,EAA8B,CAAC,EAChH,EAAS,EAAQ,YAAa,EAAQ,cAAgB,CAAC,EACvD,EAAS,EAAQ,qBAAsB,EAAQ,SAAS,oBAAsB,GAAK,EAA6B,CAAC,EACjH,EAAS,EAAQ,aAAc,EAAQ,SAAS,YAAc,GAAK,EAAQ,OAAO,aAAa,OAAS,CAAC,EACzG,EACE,EACA,iBACA,EAAQ,eAAe,oBAAsB,EAAQ,eAAe,gBAAkB,CACxF,EACA,EAAS,EAAQ,cAAe,EAAoB,CAAO,CAAC,EACrD,CAAC,GAAG,CAAM,CAAC,CAAC,SAAS,CAC9B,CAEA,SAAS,EAAS,EAAqB,EAAe,EAA0B,CAC1E,GACF,EAAO,IAAI,CAAK,CAEpB,CAEA,SAAS,EAAoB,EAA+B,CAC1D,OACE,EAAQ,eAAe,oBACrB,EAAQ,eAAe,eACvB,EAAQ,eAAe,eACvB,EAAQ,eAAe,sBACvB,EAAQ,eAAe,eACvB,EAAQ,eAAe,sBACvB,EAAQ,eAAe,qBACzB,CAEJ,CAEA,SAAS,EAAc,EAAoC,EAA4C,CACrG,OAAO,KAAK,IACV,EACA,GAAG,EAAQ,IAAK,GAAW,EAAO,EAAI,CAAC,CAAC,OAAQ,GAA2B,OAAO,GAAU,QAAQ,CACtG,CACF"}
@@ -1,2 +1,2 @@
1
- import t from"node:path";function e(e,s){const c=e.map(e=>({...e,relativeFile:t.relative(s,e.file)||t.basename(e.file)})),p=function(t){const e=new Set(t.map(t=>t.relativeFile)),o=new Map;for(const r of t){const t=new Set;for(const o of r.metrics.module.importSources){const a=n(r.relativeFile,o,e);a&&t.add(a)}o.set(r.relativeFile,t)}return o}(c),m=function(t){const e=new Map;for(const n of t)for(const t of n.metrics.module.declarations){if(!r(t))continue;const o=e.get(t.name)??[];o.push(a(n.relativeFile,t)),e.set(t.name,o)}return[...e.entries()].flatMap(([t,e])=>{const n=[...new Set(e.map(t=>t.file))].toSorted();return n.length>1?[{declarations:e.toSorted(i),files:n,name:t}]:[]}).toSorted((t,e)=>e.files.length-t.files.length||t.name.localeCompare(e.name))}(c),f=function(t){const e=new Map;for(const n of t)for(const t of n.files)e.set(t,(e.get(t)??0)+1);return e}(m),d=c.map(t=>{const e=p.get(t.relativeFile)??new Set,n=function(t){const e=t.syntaxFeatures.awaitExpressionCount,n=t.syntaxFeatures.tryStatementCount+t.syntaxFeatures.throwStatementCount,o=t.syntaxFeatures.mutableBindingCount+t.syntaxFeatures.assignmentCount,r=t.cyclomaticComplexity+t.syntaxFeatures.loopStatementCount+t.syntaxFeatures.returnStatementCount,a=t.callGraph.callCount+2*t.callGraph.internalEdgeCount+3*t.callGraph.maxCallDepth;return{asyncBoundaryCount:e,branchingScore:r,exceptionHandlingCount:n,moduleInteractionScore:a,score:2*e+3*n+o+r+a,stateMutationScore:o}}(t.metrics),r=function(t){const e=new Set;return u(e,"class-shapes",t.classCount>0),u(e,"control-flow",t.cognitiveComplexity>0),u(e,"external-dependencies",t.coupling.externalImportCount>0),u(e,"functions",t.functionCount>0),u(e,"local-dependencies",t.coupling.relativeImportCount>0),u(e,"module-api",t.coupling.exportCount>0||t.module.declarations.length>0),u(e,"state-mutation",t.syntaxFeatures.mutableBindingCount+t.syntaxFeatures.assignmentCount>0),u(e,"type-shapes",function(t){return t.typeComplexity.typeAnnotationCount+t.typeComplexity.typeAliasCount+t.typeComplexity.interfaceCount+t.typeComplexity.genericParameterCount+t.typeComplexity.unionTypeCount+t.typeComplexity.intersectionTypeCount+t.typeComplexity.conditionalTypeCount>0}(t)),[...e].toSorted()}(t.metrics);return{file:t.relativeFile,directLocalDependencyCount:e.size,duplicateSymbolGroupCount:f.get(t.relativeFile)??0,structuralBreadthScore:r.length,structuralCoordination:n,structuralFeatureGroups:r,transitiveLocalDependencyCount:o(t.relativeFile,p)}});return{duplicateSymbolGroups:m,files:d,maxDirectLocalDependencyCount:l(d,"directLocalDependencyCount"),maxDuplicateSymbolGroupCount:l(d,"duplicateSymbolGroupCount"),maxStructuralBreadthScore:l(d,"structuralBreadthScore"),maxStructuralCoordinationScore:Math.max(0,...d.map(t=>t.structuralCoordination.score)),maxTransitiveLocalDependencyCount:l(d,"transitiveLocalDependencyCount")}}function n(e,n,o){if(!n.startsWith("."))return;const r=function(e,n){const o=t.dirname(e);if(function(t){return t.startsWith("./")||t.startsWith("../")||t.includes("/")}(n))return[t.normalize(t.join(o,n))];const r=/^(?<dots>\.+)(?<module>.*)$/u.exec(n),a=r?.groups?.dots,i=r?.groups?.module;if(!a||void 0===i)return[];let u=o;for(let e=1;e<a.length;e+=1)u=t.dirname(u);const l=i.replaceAll(".",t.sep);return[t.normalize(t.join(u,l))]}(e,n),a=r.flatMap(e=>{const n=t.extname(e);return n?[e.slice(0,-n.length),e]:[e]});for(const e of a)for(const n of[`${e}.ts`,`${e}.tsx`,`${e}.js`,`${e}.jsx`,`${e}.py`,t.join(e,"index.ts"),t.join(e,"index.tsx"),t.join(e,"index.js"),t.join(e,"__init__.py")])if(o.has(n))return n}function o(t,e){const n=new Set,o=[...e.get(t)??[]];for(;o.length>0;){const t=o.pop();t&&!n.has(t)&&(n.add(t),o.push(...e.get(t)??[]))}return n.size}function r(t){return t.name.length>=5}function a(t,e){return{file:t,line:e.startLine}}function i(t,e){return t.file.localeCompare(e.file)||t.line-e.line}function u(t,e,n){n&&t.add(e)}function l(t,e){return Math.max(0,...t.map(t=>t[e]).filter(t=>"number"==typeof t))}export{e as measureArchitecture};
2
- //# sourceMappingURL=architectureMetrics.js.map
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 +1 @@
1
- {"version":3,"file":"architectureMetrics.js","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.get(sourceFile.relativeFile) ?? new Set<string>();\n const structuralCoordination = measureStructuralCoordination(sourceFile.metrics);\n const structuralFeatureGroups = measureStructuralFeatureGroups(sourceFile.metrics);\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(sourceFile.relativeFile, dependencyGraph),\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\nfunction measureDependencyGraph(files: SourceFile[]): Map<string, Set<string>> {\n const fileSet = new Set(files.map((file) => file.relativeFile));\n const graph = new Map<string, Set<string>>();\n for (const file of files) {\n const dependencies = new Set<string>();\n for (const source of file.metrics.module.importSources) {\n const resolved = resolveLocalImport(file.relativeFile, source, fileSet);\n if (resolved) {\n dependencies.add(resolved);\n }\n }\n graph.set(file.relativeFile, dependencies);\n }\n return graph;\n}\n\nfunction resolveLocalImport(fromFile: string, source: string, fileSet: Set<string>): string | undefined {\n if (!source.startsWith('.')) {\n return undefined;\n }\n const bases = localImportBases(fromFile, source);\n const stems = bases.flatMap((base) => {\n const extension = path.extname(base);\n return extension ? [base.slice(0, -extension.length), base] : [base];\n });\n for (const stem of stems) {\n for (const candidate of [\n `${stem}.ts`,\n `${stem}.tsx`,\n `${stem}.js`,\n `${stem}.jsx`,\n `${stem}.py`,\n path.join(stem, 'index.ts'),\n path.join(stem, 'index.tsx'),\n path.join(stem, 'index.js'),\n path.join(stem, '__init__.py'),\n ]) {\n if (fileSet.has(candidate)) {\n return candidate;\n }\n }\n }\n return undefined;\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 const visited = new Set<string>();\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;\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 return declaration.name.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\nfunction measureStructuralFeatureGroups(metrics: CodeMetrics): 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 > 0);\n addGroup(groups, 'functions', metrics.functionCount > 0);\n addGroup(groups, 'local-dependencies', metrics.coupling.relativeImportCount > 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"],"names":["measureArchitecture","files","displayRoot","sourceFiles","map","file","relativeFile","path","relative","basename","dependencyGraph","fileSet","Set","graph","Map","dependencies","source","metrics","module","importSources","resolved","resolveLocalImport","add","set","measureDependencyGraph","duplicateSymbolGroups","declarationsBySymbol","declaration","declarations","isDuplicateSymbolCandidate","get","name","push","toDuplicateSymbolDeclaration","entries","flatMap","duplicateFiles","toSorted","length","compareDuplicateDeclarations","left","right","localeCompare","measureDuplicateSymbolGroups","duplicateGroupCountByFile","groups","counts","group","measureDuplicateGroupCountByFile","sourceFile","directDependencies","structuralCoordination","asyncBoundaryCount","syntaxFeatures","awaitExpressionCount","exceptionHandlingCount","tryStatementCount","throwStatementCount","stateMutationScore","mutableBindingCount","assignmentCount","branchingScore","cyclomaticComplexity","loopStatementCount","returnStatementCount","moduleInteractionScore","callGraph","callCount","internalEdgeCount","maxCallDepth","score","measureStructuralCoordination","structuralFeatureGroups","addGroup","classCount","cognitiveComplexity","coupling","externalImportCount","functionCount","relativeImportCount","exportCount","typeComplexity","typeAnnotationCount","typeAliasCount","interfaceCount","genericParameterCount","unionTypeCount","intersectionTypeCount","conditionalTypeCount","hasTypeShapeMetrics","measureStructuralFeatureGroups","directLocalDependencyCount","size","duplicateSymbolGroupCount","structuralBreadthScore","transitiveLocalDependencyCount","measureTransitiveDependencyCount","maxDirectLocalDependencyCount","maxFileMetric","maxDuplicateSymbolGroupCount","maxStructuralBreadthScore","maxStructuralCoordinationScore","Math","max","maxTransitiveLocalDependencyCount","fromFile","startsWith","bases","fromDirectory","dirname","includes","isPathRelativeImport","normalize","join","match","exec","dots","moduleName","undefined","baseDirectory","index","modulePath","replaceAll","sep","localImportBases","stems","base","extension","extname","slice","stem","candidate","has","visited","pending","current","pop","line","startLine","condition","key","metric","filter","value"],"mappings":"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"}
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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"}
package/dist/cli.cjs CHANGED
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";var t=require("node:fs/promises"),e=require("node:os"),n=require("node:path"),i=require("commander"),o=require("./architectureMetrics.cjs"),r=require("./cliConfig.cjs"),a=require("./metrics.cjs"),c=require("./typescriptProject.cjs");const s=new Map([[".cjs","javascript"],[".cts","typescript"],[".go","go"],[".js","javascript"],[".jsx","jsx"],[".mjs","javascript"],[".mts","typescript"],[".py","python"],[".rs","rust"],[".ts","typescript"],[".tsx","tsx"]]),l=new Set([".agents",".claude",".cursor",".git",".next",".playwright-cli",".tox",".tmp",".turbo",".venv",".yarn","__fixtures__","__generated__","__pycache__","coverage","dist","fixtures","generated","node_modules","target","test-fixtures","vendor","venv"]),u=new Set(["__tests__","test","tests"]),m=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-]test)\.[^.]+$/iu;function p(t){return"~"===t?e.homedir():t.startsWith("~/")?n.join(e.homedir(),t.slice(2)):n.resolve(t)}async function d(e){try{return(await t.stat(e)).isFile()}catch{return!1}}async function f(e,i,o,r,a,c,s){let l,u;try{l=await t.realpath(e)}catch(t){return void r.push(`${R(e,s)}: ${T(t)}`)}if(E(l,s)&&!a.has(l)){a.add(l);try{u=await t.readdir(e,{withFileTypes:!0})}catch(t){return void r.push(`${R(e,s)}: ${T(t)}`)}for(const t of u){const l=n.join(e,t.name);if(t.isSymbolicLink())await h(t.name,l,i,o,r,a,c,s);else if(t.isDirectory()){if(_(t.name,i))continue;await f(l,i,o,r,a,c,s)}else t.isFile()&&await y(l,i,o,r,c,s)}}}async function h(e,i,o,r,a,c,s,l){let u,m;try{u=await t.realpath(i)}catch(t){return void a.push(`${R(i,l)}: ${T(t)}`)}if(E(u,l)){try{m=await t.stat(i)}catch(t){return void a.push(`${R(i,l)}: ${T(t)}`)}if(m.isDirectory()){if(_(e,o)||_(n.basename(u),o))return;await f(i,o,r,a,c,s,l)}else m.isFile()&&await y(i,o,r,a,s,l,u,u)}}async function y(t,e,n,i,o,r,a=t,c){const s=M(a,e);s&&await g(t,s,n,i,o,r,c)}async function g(e,n,i,o,r,c,s){try{const o=s??await t.realpath(e);if(r.has(o))return;r.add(o);const c=await t.readFile(e,"utf8");i.push({file:e,metrics:a.measureCode(c,{language:n})})}catch(t){o.push(`${R(e,c)}: ${T(t)}`)}}function C(t,e,n,i,o){const r=[],a=R(t,o);if($(r,"file LOC",e.lines.code,i.fileLoc),$(r,"import sources",e.coupling.importSourceCount,i.import),$(r,"duplicated blocks",e.duplication.duplicateBlockCount,i.duplicateBlock),n){(e.lines.code>=100||n.directLocalDependencyCount>=8)&&$(r,"transitive local dependencies",n.transitiveLocalDependencyCount,i.transitiveDependency),(r.length>0||n.directLocalDependencyCount>=8||n.structuralCoordination.score>=i.structuralCoordination)&&$(r,"structural breadth",n.structuralBreadthScore,i.structuralBreadth),$(r,"structural coordination",n.structuralCoordination.score,i.structuralCoordination),$(r,"state mutation",n.structuralCoordination.stateMutationScore,i.stateMutation),$(r,"duplicate symbol groups",n.duplicateSymbolGroupCount,i.duplicateSymbolGroup)}return 0===r.length?[]:[{file:a,language:e.language,kind:"file",cyclomaticComplexity:e.cyclomaticComplexity,cognitiveComplexity:e.cognitiveComplexity,triggers:r,score:b(r)}]}function $(t,e,n,i){n<i||t.push({metric:e,value:n,threshold:i,score:n/i})}function x(t,e,n,i){return n?.has(v(t,e.startLine,e.startColumn))||!!e.name&&i?.has(w(t,e.name,e.startLine))||!1}function v(t,e,i){return`${n.resolve(t)}:${e}:${i}`}function w(t,e,i){return`${n.resolve(t)}:${e}:${i}`}function b(t){return Math.max(...t.map(t=>t.score))}function j(t,e){return e.score-t.score||t.file.localeCompare(e.file)||(t.startLine??0)-(e.startLine??0)||(t.endLine??0)-(e.endLine??0)||t.kind.localeCompare(e.kind)}function L(t){return void 0===t.startLine||void 0===t.endLine?t.file:`${t.file}:${t.startLine}-${t.endLine}`}function S(t){return t.name?`${t.kind} ${t.name}`:t.kind}function k(t){return`(${t.triggers.map(t=>`${t.metric} ${O(t.value)} >= ${O(t.threshold)}`).join(", ")}; cyclomatic ${t.cyclomaticComplexity}, cognitive ${t.cognitiveComplexity})`}function O(t){return Number.isInteger(t)?String(t):t.toFixed(2)}function F(t){let e=0,n=0,i=0,o=0,r=0,a=0,c=0,s=0,l=0,u=0,m=0,p=0,d=0,f=0,h=0,y=0;for(const g of t)e+=g.metrics.functionCount,n+=g.metrics.lines.code,i=Math.max(i,g.metrics.maxCyclomaticComplexity),o=Math.max(o,g.metrics.maxCognitiveComplexity),r+=g.metrics.callGraph.callCount,a+=g.metrics.callGraph.internalCallCount,c=Math.max(c,g.metrics.callGraph.maxCallDepth),s+=g.metrics.coupling.importSourceCount,l+=g.metrics.coupling.relativeImportCount,u+=g.metrics.coupling.externalImportCount,m+=g.metrics.coupling.exportCount,p+=g.metrics.cohesion.averageFunctionIdentifierOverlap,d+=g.metrics.typeComplexity.typeAnnotationCount,f+=g.metrics.typeComplexity.typeAliasCount,h+=g.metrics.typeComplexity.interfaceCount,y+=g.metrics.typeComplexity.genericParameterCount;return{fileCount:t.length,functionCount:e,linesOfCode:n,maxCyclomaticComplexity:i,maxCognitiveComplexity:o,callCount:r,internalCallCount:a,maxCallDepth:c,importSourceCount:s,relativeImportCount:l,externalImportCount:u,exportCount:m,averageFunctionIdentifierOverlap:0===t.length?0:p/t.length,typeAnnotationCount:d,typeAliasCount:f,interfaceCount:h,genericParameterCount:y}}function _(t,e){return!!l.has(t)||!e.includeTests&&u.has(t)}function E(t,e){const i=n.relative(e,t);return""===i||".."!==i&&!i.startsWith(`..${n.sep}`)&&!n.isAbsolute(i)}function M(t,e,i=!1){const o=t.toLowerCase();if((i||!(o.endsWith(".d.ts")||o.endsWith(".d.mts")||o.endsWith(".d.cts")||o.endsWith(".min.js")||o.endsWith(".pnp.cjs")))&&(i||e.includeTests||!m.test(n.basename(t))))return s.get(n.extname(o))}function D(t){if(!/^[1-9]\d*$/u.test(t))throw new i.InvalidArgumentError("Expected a positive integer.");const e=Number(t);if(!Number.isSafeInteger(e)||e<1)throw new i.InvalidArgumentError("Expected a positive integer.");return e}function R(t,e){return n.relative(e,t)||n.basename(t)}function A(t){process.stdout.write(t)}function P(t){process.stderr.write(t)}function T(t){return t instanceof Error?t.message:String(t)}(async function(){const e=(new i.Command).name("code-gauge").description("Measure code metrics and list high-risk findings.").argument("[target]","file or directory to measure",".").option("--config <path>",`config file to use instead of the auto-detected ${r.configFileName}`).option("--file-loc-threshold <number>","minimum file code LOC to report",D).option("--function-loc-threshold <number>","minimum function physical LOC span to report",D).option("--component-loc-threshold <number>","minimum React component physical LOC span to report",D).option("--cognitive-threshold <number>","minimum cognitive complexity to report",D).option("--cyclomatic-threshold <number>","minimum cyclomatic complexity to report",D).option("--call-threshold <number>","minimum function call count to report",D).option("--import-threshold <number>","minimum unique import sources per file to report",D).option("--fan-out-threshold <number>","minimum intra-file fan-out per function to report",D).option("--parameter-threshold <number>","minimum function parameter count to report",D).option("--duplicate-block-threshold <number>","minimum count of duplicated code blocks per file to report",D).option("--transitive-dependency-threshold <number>","minimum transitively reachable local files to report",D).option("--structural-breadth-threshold <number>","minimum structural breadth score to report",D).option("--structural-coordination-threshold <number>","minimum structural coordination score to report",D).option("--state-mutation-threshold <number>","minimum state mutation score to report",D).option("--duplicate-symbol-group-threshold <number>","minimum duplicate symbol group count to report",D).option("--max-findings <number>","maximum number of risk findings to print",D).option("--include-tests","include test files and test directories").option("--tsconfig <path>","TypeScript project file to use instead of auto-detected tsconfig.json").option("--json","print JSON output").option("--fail-on-error","exit with code 1 when files or directories cannot be scanned").option("--fail-on-risk","exit with code 1 when high-risk findings are found");e.action(async(e,i)=>{const a=p(e),s=await r.loadConfig(i.config,await async function(e){try{return(await t.stat(e)).isDirectory()?e:n.dirname(e)}catch{return n.dirname(e)}}(a)),l=r.resolveOptions(i,s),u=await async function(e,i){const o=[],r=[],a=new Set;let c=e;try{c=await t.realpath(e)}catch{}const s=n.dirname(c);let l;try{l=await t.stat(c)}catch(t){const e=`${R(c,s)}: ${T(t)}`;return{displayRoot:s,files:o,errors:[e],fatalError:e}}if(l.isFile()){const t=n.dirname(c),e=M(c,i,!0);if(!e){const e=`${R(c,t)}: unsupported file type`;return{displayRoot:t,files:o,errors:[e],fatalError:e}}return await g(c,e,o,r,a,t,c),{displayRoot:t,files:o,errors:r}}return await f(c,i,o,r,new Set,a,c),{displayRoot:c,files:o,errors:r}}(a,l);await async function(t){if(t.fatalError)return;try{t.architecture=o.measureArchitecture(t.files.map(({file:t,metrics:e})=>({file:t,metrics:e})),t.displayRoot)}catch(e){t.errors.push(`architecture metrics: ${T(e)}`)}}(u),await async function(e,i,o){if(e.fatalError)return;if(0===e.files.length)return;const r=i.tsconfig,a=void 0!==r;if(!a&&!e.files.some(({file:t})=>function(t){return[".cjs",".cts",".js",".jsx",".mjs",".mts",".ts",".tsx"].includes(n.extname(t))}(t)))return;const s=r?p(r):await async function(e){const i=await t.stat(e);let o=i.isDirectory()?e:n.dirname(e);for(;;){const t=n.join(o,"tsconfig.json");if(await d(t))return t;const e=n.dirname(o);if(e===o)return;o=e}}(o);if(!s)return;try{e.typeScriptProject=await c.measureTypeScriptProject(s,e.files.map(({file:t})=>t)),e.componentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.map(t=>v(t.file,t.startLine,t.startColumn))),e.namedComponentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.flatMap(t=>t.name?[w(t.file,t.name,t.startLine)]:[]))}catch(t){a&&e.errors.push(`${R(s,e.displayRoot)}: ${T(t)}`)}}(u,l,a);const m=function(t,e,n,i,o,a){const c=new Map(e?.files.map(t=>[t.file,t])),s=t.flatMap(({file:t,metrics:e})=>{const s=e.functions.some(e=>e.returnsJsx||x(t,e,n,i)),l=r.resolveThresholds(o,e.language,s);return[...C(t,e,c.get(R(t,a)),l,a),...e.functions.flatMap(o=>function(t,e,n,i,o,r,a){const c=n.endLine-n.startLine+1,s=x(t,n,r,a),l=s?"component":"function",u=[];if($(u,"cognitive complexity",n.cognitiveComplexity,i.cognitive),$(u,"cyclomatic complexity",n.cyclomaticComplexity,i.cyclomatic),$(u,s?"component LOC":"function LOC",c,function(t,e){return t?e.componentLoc:e.functionLoc}(s,i)),$(u,"function calls",n.callCount,i.call),$(u,"fan-out",n.fanOut,i.fanOut),$(u,"parameters",n.parameterCount,i.parameter),0===u.length)return[];return[{file:R(t,o),language:e,kind:l,name:n.name??"<anonymous>",startLine:n.startLine,endLine:n.endLine,cyclomaticComplexity:n.cyclomaticComplexity,cognitiveComplexity:n.cognitiveComplexity,triggers:u,score:b(u)}]}(t,e.language,o,l,a,n,i))]});return s.sort(j),s}(u.files,u.architecture,u.componentFunctionKeys,u.namedComponentFunctionKeys,l,u.displayRoot);l.json?function(t,e,n){const i=F(t.files),o=e.slice(0,n.maxFindings);A(JSON.stringify({summary:i,thresholds:n.thresholds,profileThresholds:n.profileThresholds,totalRisks:e.length,truncated:o.length<e.length,architecture:t.architecture,typeScriptProject:t.typeScriptProject,risks:o,errors:t.errors},void 0,2)+"\n")}(u,m,l):function(t,e,n,i){if(e.fatalError)return void P(`Error: ${e.fatalError}\n`);const{thresholds:o}=i,r=F(e.files);A(`Measured ${r.fileCount} files under ${t}\n`),A(`LOC ${r.linesOfCode}, functions ${r.functionCount}, max cyclomatic ${r.maxCyclomaticComplexity}, max cognitive ${r.maxCognitiveComplexity}\n`),A(`Calls ${r.callCount}, internal edges ${r.internalCallCount}, max call depth ${r.maxCallDepth}, imports ${r.importSourceCount}, exports ${r.exportCount}\n`),A(`Type annotations ${r.typeAnnotationCount}, type aliases ${r.typeAliasCount}, interfaces ${r.interfaceCount}, avg cohesion ${r.averageFunctionIdentifierOverlap.toFixed(2)}\n`),e.architecture&&A(`${function(t){const e=Math.max(0,...t.files.map(t=>t.structuralCoordination.stateMutationScore));return`Architecture max reachable files ${t.maxTransitiveLocalDependencyCount}, max structural breadth ${t.maxStructuralBreadthScore}, max structural coordination ${t.maxStructuralCoordinationScore}, max state mutation ${e}, duplicate symbol groups ${t.duplicateSymbolGroups.length}`}(e.architecture)}\n`);e.typeScriptProject&&A(`${function(t){return`TypeScript project root files ${t.rootFileCount}, measured roots ${t.measuredRootFileCount}, semantic diagnostics ${t.semanticDiagnosticCount}, resolved calls ${t.resolvedCallExpressionCount}/${t.callExpressionCount} (${(100*t.resolvedCallExpressionRatio).toFixed(1)}%)`}(e.typeScriptProject)}\n`);A(`Risk thresholds: file LOC >= ${o.fileLoc}, function LOC >= ${o.functionLoc}, component LOC >= ${o.componentLoc}, cognitive >= ${o.cognitive}, cyclomatic >= ${o.cyclomatic}, calls >= ${o.call}, imports >= ${o.import}, fan-out >= ${o.fanOut}, parameters >= ${o.parameter}, duplicated blocks >= ${o.duplicateBlock}\n`);const a=(c=i.profileThresholds,Object.entries(c).map(([t,e])=>`${t} { ${Object.entries(e).map(([t,e])=>`${t} ${e}`).join(", ")} }`).join("; "));var c;a&&A(`Per-language overrides: ${a}\n`);if(0===n.length)A("No high-risk findings found.\n");else{const t=n.slice(0,i.maxFindings),e=n.length>t.length?` of ${n.length}`:"";A(`\nHigh-risk findings (top ${t.length}${e}):\n`);for(const e of t)A(`${L(e)} ${S(e)} ${k(e)}\n`)}if(e.errors.length>0){P(`\nSkipped ${e.errors.length} files or directories:\n`);for(const t of e.errors.slice(0,10))P(`- ${t}\n`);e.errors.length>10&&P(`- ... ${e.errors.length-10} more\n`)}}(a,u,m,l),(u.fatalError||l.failOnError&&u.errors.length>0||l.failOnRisk&&m.length>0)&&(process.exitCode=1)}),await e.parseAsync()})().catch(t=>{P(`Error: ${T(t)}\n`),process.exitCode=1});
3
- //# sourceMappingURL=cli.cjs.map
2
+ "use strict";const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./metrics.cjs"),n=require("./architectureMetrics.cjs"),r=require("./cliConfig.cjs"),i=require("./typescriptProject.cjs");let a=require("node:fs/promises"),o=require("node:os");o=e.__toESM(o,1);let s=require("node:path");s=e.__toESM(s,1);let c=require("commander");const l=new Map([[`.c`,`c`],[`.c++`,`cpp`],[`.cc`,`cpp`],[`.cjs`,`javascript`],[`.cp`,`cpp`],[`.cpp`,`cpp`],[`.tcc`,`cpp`],[`.cts`,`typescript`],[`.cxx`,`cpp`],[`.go`,`go`],[`.h`,`cpp`],[`.hh`,`cpp`],[`.hpp`,`cpp`],[`.hxx`,`cpp`],[`.java`,`java`],[`.js`,`javascript`],[`.jsx`,`jsx`],[`.mjs`,`javascript`],[`.mts`,`typescript`],[`.py`,`python`],[`.rb`,`ruby`],[`.rs`,`rust`],[`.ts`,`typescript`],[`.tsx`,`tsx`]]),u=new Set([`.agents`,`.claude`,`.cursor`,`.git`,`.next`,`.playwright-cli`,`.tox`,`.tmp`,`.turbo`,`.venv`,`.yarn`,`__fixtures__`,`__generated__`,`__pycache__`,`coverage`,`dist`,`fixtures`,`generated`,`node_modules`,`target`,`test-fixtures`,`vendor`,`venv`]),d=new Set([`__tests__`,`test`,`tests`,`spec`]),f=/(?:^test(?:[_-].*)?|\.(?:spec|test)|[_-](?:test|spec))\.[^.]+$/iu,p=/Test\.java$/u;m().catch(e=>{Q(`Error: ${$(e)}\n`),process.exitCode=1});async function m(){let e=new c.Command().name(`code-gauge`).description(`Measure code metrics and list high-risk findings.`).argument(`[target]`,`file or directory to measure`,`.`).option(`--config <path>`,`config file to use instead of the auto-detected ${r.configFileName}`).option(`--file-loc-threshold <number>`,`minimum file code LOC to report`,Y).option(`--function-loc-threshold <number>`,`minimum function physical LOC span to report`,Y).option(`--component-loc-threshold <number>`,`minimum React component physical LOC span to report`,Y).option(`--cognitive-threshold <number>`,`minimum cognitive complexity to report`,Y).option(`--cyclomatic-threshold <number>`,`minimum cyclomatic complexity to report`,Y).option(`--call-threshold <number>`,`minimum function call count to report`,Y).option(`--import-threshold <number>`,`minimum unique import sources per file to report`,Y).option(`--fan-out-threshold <number>`,`minimum intra-file fan-out per function to report`,Y).option(`--parameter-threshold <number>`,`minimum function parameter count to report`,Y).option(`--duplicate-block-threshold <number>`,`minimum count of duplicated code blocks per file to report`,Y).option(`--duplication-ratio-percent-threshold <number>`,`minimum percentage (1-100) of duplicated lines per file to report`,J).option(`--transitive-dependency-threshold <number>`,`minimum transitively reachable local files to report`,Y).option(`--structural-breadth-threshold <number>`,`minimum structural breadth score to report`,Y).option(`--structural-coordination-threshold <number>`,`minimum structural coordination score to report`,Y).option(`--state-mutation-threshold <number>`,`minimum state mutation score to report`,Y).option(`--duplicate-symbol-group-threshold <number>`,`minimum duplicate symbol group count to report`,Y).option(`--max-findings <number>`,`maximum number of risk findings to print`,Y).option(`--largest-files <number>`,`number of largest files by code LOC to list`,Y).option(`--include-tests`,`include test files and test directories`).option(`--tsconfig <path>`,`TypeScript project file to use instead of auto-detected tsconfig.json`).option(`--json`,`print JSON output`).option(`--fail-on-error`,`exit with code 1 when files or directories cannot be scanned`).option(`--fail-on-risk`,`exit with code 1 when high-risk findings are found`);e.action(async(e,t)=>{let n=h(e),i=r.resolveOptions(t,await r.loadConfig(t.config,await g(n))),a=await _(n,i);await y(a),await ee(a,i,n);let o=w(a.files,a.architecture,a.componentFunctionKeys,a.namedComponentFunctionKeys,i,a.displayRoot);i.json?re(a,o,i):F(n,a,o,i),(a.fatalError||i.failOnError&&a.errors.length>0||i.failOnRisk&&o.length>0)&&(process.exitCode=1)}),await e.parseAsync()}function h(e){return e===`~`?o.default.homedir():e.startsWith(`~/`)?s.default.join(o.default.homedir(),e.slice(2)):s.default.resolve(e)}async function g(e){try{return(await(0,a.stat)(e)).isDirectory()?e:s.default.dirname(e)}catch{return s.default.dirname(e)}}async function _(e,t){let n=[],r=[],i=new Set,o=e;try{o=await(0,a.realpath)(e)}catch{}let c=s.default.dirname(o),l;try{l=await(0,a.stat)(o)}catch(e){let t=`${X(o,c)}: ${$(e)}`;return{displayRoot:c,files:n,errors:[t],fatalError:t}}if(l.isFile()){let e=s.default.dirname(o),a=q(o,t,!0);if(!a){let t=`${X(o,e)}: unsupported file type`;return{displayRoot:e,files:n,errors:[t],fatalError:t}}return await C(o,a,n,r,i,e,o),{displayRoot:e,files:n,errors:r}}return await b(o,t,n,r,new Set,i,o),{displayRoot:o,files:n,errors:r}}async function ee(e,t,n){if(e.fatalError||e.files.length===0)return;let r=t.tsconfig,a=r!==void 0;if(!a&&!e.files.some(({file:e})=>te(e)))return;let o=r?h(r):await ne(n);if(o)try{e.typeScriptProject=await i.measureTypeScriptProject(o,e.files.map(({file:e})=>e)),e.componentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.map(e=>j(e.file,e.startLine,e.startColumn))),e.namedComponentFunctionKeys=new Set(e.typeScriptProject.reactComponentFunctions.flatMap(e=>e.name?[M(e.file,e.name,e.startLine)]:[]))}catch(t){a&&e.errors.push(`${X(o,e.displayRoot)}: ${$(t)}`)}}function te(e){return[`.cjs`,`.cts`,`.js`,`.jsx`,`.mjs`,`.mts`,`.ts`,`.tsx`].includes(s.default.extname(e))}async function ne(e){let t=(await(0,a.stat)(e)).isDirectory()?e:s.default.dirname(e);for(;;){let e=s.default.join(t,`tsconfig.json`);if(await v(e))return e;let n=s.default.dirname(t);if(n===t)return;t=n}}async function v(e){try{return(await(0,a.stat)(e)).isFile()}catch{return!1}}async function y(e){if(!e.fatalError)try{e.architecture=n.measureArchitecture(e.files.map(({file:e,metrics:t})=>({file:e,metrics:t})),e.displayRoot)}catch(t){e.errors.push(`architecture metrics: ${$(t)}`)}}async function b(e,t,n,r,i,o,c){let l;try{l=await(0,a.realpath)(e)}catch(t){r.push(`${X(e,c)}: ${$(t)}`);return}if(!K(l,c)||i.has(l))return;i.add(l);let u;try{u=await(0,a.readdir)(e,{withFileTypes:!0})}catch(t){r.push(`${X(e,c)}: ${$(t)}`);return}for(let a of u){let l=s.default.join(e,a.name);if(a.isSymbolicLink()){await x(a.name,l,t,n,r,i,o,c);continue}if(a.isDirectory()){if(G(a.name,t))continue;await b(l,t,n,r,i,o,c);continue}a.isFile()&&await S(l,t,n,r,o,c)}}async function x(e,t,n,r,i,o,c,l){let u;try{u=await(0,a.realpath)(t)}catch(e){i.push(`${X(t,l)}: ${$(e)}`);return}if(!K(u,l))return;let d;try{d=await(0,a.stat)(t)}catch(e){i.push(`${X(t,l)}: ${$(e)}`);return}if(d.isDirectory()){if(G(e,n)||G(s.default.basename(u),n))return;await b(t,n,r,i,o,c,l);return}d.isFile()&&await S(t,n,r,i,c,l,u,u)}async function S(e,t,n,r,i,a,o=e,s){let c=q(o,t);c&&await C(e,c,n,r,i,a,s)}async function C(e,n,r,i,o,s,c){try{let i=c??await(0,a.realpath)(e);if(o.has(i))return;o.add(i);let s=await(0,a.readFile)(e,`utf8`);r.push({file:e,metrics:t.measureCode(s,{language:n})})}catch(t){i.push(`${X(e,s)}: ${$(t)}`)}}function w(e,t,n,i,a,o){let s=new Map(t?.files.map(e=>[e.file,e])),c=e.flatMap(({file:e,metrics:t})=>{let c=t.functions.some(t=>t.returnsJsx||k(e,t,n,i)),l=r.resolveThresholds(a,t.language,c);return[...T(e,t,s.get(X(e,o)),l,o),...t.functions.flatMap(r=>E(e,t.language,r,l,o,n,i))]});return c.sort(P),c}function T(e,t,n,r,i){let a=[],o=X(e,i);D(a,`file LOC`,t.lines.code,r.fileLoc),D(a,`import sources`,t.coupling.importSourceCount,r.import);let s=O(t.duplication.duplicateBlockGroups);return D(a,`duplicated blocks`,t.duplication.duplicateBlockCount,r.duplicateBlock,s),D(a,`duplicated lines (%)`,Math.floor(t.duplication.duplicationRatio*100),r.duplicationRatioPercent,s),n&&((t.lines.code>=100||n.directLocalDependencyCount>=8)&&D(a,`transitive local dependencies`,n.transitiveLocalDependencyCount,r.transitiveDependency),(a.length>0||n.directLocalDependencyCount>=8||n.structuralCoordination.score>=r.structuralCoordination)&&D(a,`structural breadth`,n.structuralBreadthScore,r.structuralBreadth),D(a,`structural coordination`,n.structuralCoordination.score,r.structuralCoordination),D(a,`state mutation`,n.structuralCoordination.stateMutationScore,r.stateMutation),D(a,`duplicate symbol groups`,n.duplicateSymbolGroupCount,r.duplicateSymbolGroup)),a.length===0?[]:[{file:o,language:t.language,kind:`file`,cyclomaticComplexity:t.cyclomaticComplexity,cognitiveComplexity:t.cognitiveComplexity,triggers:a,score:N(a)}]}function E(e,t,n,r,i,a,o){let s=n.endLine-n.startLine+1,c=k(e,n,a,o),l=c?`component`:`function`,u=[];return D(u,`cognitive complexity`,n.cognitiveComplexity,r.cognitive),D(u,`cyclomatic complexity`,n.cyclomaticComplexity,r.cyclomatic),D(u,c?`component LOC`:`function LOC`,s,A(c,r)),D(u,`function calls`,n.callCount,r.call),D(u,`fan-out`,n.fanOut,r.fanOut),D(u,`parameters`,n.parameterCount,r.parameter),u.length===0?[]:[{file:X(e,i),language:t,kind:l,name:n.name??`<anonymous>`,startLine:n.startLine,endLine:n.endLine,cyclomaticComplexity:n.cyclomaticComplexity,cognitiveComplexity:n.cognitiveComplexity,triggers:u,score:N(u)}]}function D(e,t,n,r,i){n<r||e.push({metric:t,value:n,threshold:r,score:n/r,detail:i})}function O(e){if(e.length!==0)return e.map(e=>e.map(({startLine:e,endLine:t})=>`${e}-${t}`).join(` ~ `)).join(`; `)}function k(e,t,n,r){return n?.has(j(e,t.startLine,t.startColumn))||(t.name?r?.has(M(e,t.name,t.startLine)):!1)||!1}function A(e,t){return e?t.componentLoc:t.functionLoc}function j(e,t,n){return`${s.default.resolve(e)}:${t}:${n}`}function M(e,t,n){return`${s.default.resolve(e)}:${t}:${n}`}function N(e){return Math.max(...e.map(e=>e.score))}function P(e,t){return t.score-e.score||e.file.localeCompare(t.file)||(e.startLine??0)-(t.startLine??0)||(e.endLine??0)-(t.endLine??0)||e.kind.localeCompare(t.kind)}function re(e,t,n){let r=W(e.files),i=t.slice(0,n.maxFindings);Z(JSON.stringify({summary:r,thresholds:n.thresholds,profileThresholds:n.profileThresholds,totalRisks:t.length,truncated:i.length<t.length,largestFiles:n.largestFiles>0?I(e.files,n.largestFiles,e.displayRoot):void 0,architecture:e.architecture,typeScriptProject:e.typeScriptProject,risks:i,errors:e.errors},void 0,2)+`
3
+ `)}function F(e,t,n,r){if(t.fatalError){Q(`Error: ${t.fatalError}\n`);return}let{thresholds:i}=r,a=W(t.files);Z(`Measured ${a.fileCount} files under ${e}\n`),Z(`LOC ${a.linesOfCode}, functions ${a.functionCount}, max cyclomatic ${a.maxCyclomaticComplexity}, max cognitive ${a.maxCognitiveComplexity}\n`),Z(`Calls ${a.callCount}, internal edges ${a.internalCallCount}, max call depth ${a.maxCallDepth}, imports ${a.importSourceCount}, exports ${a.exportCount}\n`),Z(`Type annotations ${a.typeAnnotationCount}, type aliases ${a.typeAliasCount}, interfaces ${a.interfaceCount}, avg cohesion ${a.averageFunctionIdentifierOverlap.toFixed(2)}\n`),t.architecture&&Z(`${H(t.architecture)}\n`),t.typeScriptProject&&Z(`${U(t.typeScriptProject)}\n`),Z(`Risk thresholds: file LOC >= ${i.fileLoc}, function LOC >= ${i.functionLoc}, component LOC >= ${i.componentLoc}, cognitive >= ${i.cognitive}, cyclomatic >= ${i.cyclomatic}, calls >= ${i.call}, imports >= ${i.import}, fan-out >= ${i.fanOut}, parameters >= ${i.parameter}, duplicated blocks >= ${i.duplicateBlock}, duplicated lines (%) >= ${i.duplicationRatioPercent}\n`);let o=L(r.profileThresholds);if(o&&Z(`Per-language overrides: ${o}\n`),n.length===0)Z(`No high-risk findings found.
4
+ `);else{let e=n.slice(0,r.maxFindings),t=n.length>e.length?` of ${n.length}`:``;Z(`\nHigh-risk findings (top ${e.length}${t}):\n`);for(let t of e)Z(`${R(t)} ${z(t)} ${B(t)}\n`)}let s=t.architecture?.duplicateSymbolGroups??[];if(s.length>0){let e=s.toSorted((e,t)=>t.files.length-e.files.length||e.name.localeCompare(t.name)).slice(0,10),t=s.length>e.length?` of ${s.length}`:``;Z(`\nDuplicate symbols (top ${e.length}${t}):\n`);for(let t of e)Z(`${t.name}: ${t.declarations.map(e=>`${e.file}:${e.line}`).join(`, `)}\n`)}if(r.largestFiles>0){let e=I(t.files,r.largestFiles,t.displayRoot);Z(`\nLargest files by code LOC (top ${e.length}):\n`);for(let{file:t,codeLoc:n}of e)Z(`${t} (code LOC ${n})\n`)}if(t.errors.length>0){Q(`\nSkipped ${t.errors.length} files or directories:\n`);for(let e of t.errors.slice(0,10))Q(`- ${e}\n`);t.errors.length>10&&Q(`- ... ${t.errors.length-10} more\n`)}}function I(e,t,n){return e.map(({file:e,metrics:t})=>({file:X(e,n),codeLoc:t.lines.code})).toSorted((e,t)=>t.codeLoc-e.codeLoc||e.file.localeCompare(t.file)).slice(0,t)}function L(e){return Object.entries(e).map(([e,t])=>`${e} { ${Object.entries(t).map(([e,t])=>`${e} ${t}`).join(`, `)} }`).join(`; `)}function R(e){return e.startLine===void 0||e.endLine===void 0?e.file:`${e.file}:${e.startLine}-${e.endLine}`}function z(e){return e.name?`${e.kind} ${e.name}`:e.kind}function B(e){return`(${e.triggers.map(e=>`${e.metric} ${V(e.value)} >= ${V(e.threshold)}${e.detail?` [${e.detail}]`:``}`).join(`, `)}; cyclomatic ${e.cyclomaticComplexity}, cognitive ${e.cognitiveComplexity})`}function V(e){return Number.isInteger(e)?String(e):e.toFixed(2)}function H(e){let t=Math.max(0,...e.files.map(e=>e.structuralCoordination.stateMutationScore));return`Architecture max reachable files ${e.maxTransitiveLocalDependencyCount}, max structural breadth ${e.maxStructuralBreadthScore}, max structural coordination ${e.maxStructuralCoordinationScore}, max state mutation ${t}, duplicate symbol groups ${e.duplicateSymbolGroups.length}`}function U(e){return`TypeScript project root files ${e.rootFileCount}, measured roots ${e.measuredRootFileCount}, semantic diagnostics ${e.semanticDiagnosticCount}, resolved calls ${e.resolvedCallExpressionCount}/${e.callExpressionCount} (${(e.resolvedCallExpressionRatio*100).toFixed(1)}%)`}function W(e){let t=0,n=0,r=0,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0,f=0,p=0,m=0,h=0,g=0;for(let _ of e)t+=_.metrics.functionCount,n+=_.metrics.lines.code,r=Math.max(r,_.metrics.maxCyclomaticComplexity),i=Math.max(i,_.metrics.maxCognitiveComplexity),a+=_.metrics.callGraph.callCount,o+=_.metrics.callGraph.internalCallCount,s=Math.max(s,_.metrics.callGraph.maxCallDepth),c+=_.metrics.coupling.importSourceCount,l+=_.metrics.coupling.relativeImportCount,u+=_.metrics.coupling.externalImportCount,d+=_.metrics.coupling.exportCount,f+=_.metrics.cohesion.averageFunctionIdentifierOverlap,p+=_.metrics.typeComplexity.typeAnnotationCount,m+=_.metrics.typeComplexity.typeAliasCount,h+=_.metrics.typeComplexity.interfaceCount,g+=_.metrics.typeComplexity.genericParameterCount;return{fileCount:e.length,functionCount:t,linesOfCode:n,maxCyclomaticComplexity:r,maxCognitiveComplexity:i,callCount:a,internalCallCount:o,maxCallDepth:s,importSourceCount:c,relativeImportCount:l,externalImportCount:u,exportCount:d,averageFunctionIdentifierOverlap:e.length===0?0:f/e.length,typeAnnotationCount:p,typeAliasCount:m,interfaceCount:h,genericParameterCount:g}}function G(e,t){return u.has(e)?!0:t.includeTests?!1:d.has(e)}function K(e,t){let n=s.default.relative(t,e);return n===``||n!==`..`&&!n.startsWith(`..${s.default.sep}`)&&!s.default.isAbsolute(n)}function q(e,t,n=!1){let r=e.toLowerCase();if(!(!n&&(r.endsWith(`.d.ts`)||r.endsWith(`.d.mts`)||r.endsWith(`.d.cts`)||r.endsWith(`.min.js`)||r.endsWith(`.pnp.cjs`)))&&!(!n&&!t.includeTests&&(f.test(s.default.basename(e))||p.test(s.default.basename(e)))))return s.default.extname(e)===`.C`?`cpp`:l.get(s.default.extname(r))}function J(e){let t=Y(e);if(t>100)throw new c.InvalidArgumentError(`Expected an integer between 1 and 100.`);return t}function Y(e){if(!/^[1-9]\d*$/u.test(e))throw new c.InvalidArgumentError(`Expected a positive integer.`);let t=Number(e);if(!Number.isSafeInteger(t)||t<1)throw new c.InvalidArgumentError(`Expected a positive integer.`);return t}function X(e,t){return s.default.relative(t,e)||s.default.basename(e)}function Z(e){process.stdout.write(e)}function Q(e){process.stderr.write(e)}function $(e){return e instanceof Error?e.message:String(e)}
5
+ //# sourceMappingURL=cli.cjs.map