code-gauge 1.9.0 → 1.11.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.
Files changed (44) hide show
  1. package/README.md +38 -23
  2. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  3. package/dist/architectureMetrics.cjs +2 -2
  4. package/dist/architectureMetrics.cjs.map +1 -1
  5. package/dist/architectureMetrics.js +2 -2
  6. package/dist/architectureMetrics.js.map +1 -1
  7. package/dist/cli.cjs +4 -2
  8. package/dist/cli.cjs.map +1 -1
  9. package/dist/cli.js +4 -2
  10. package/dist/cli.js.map +1 -1
  11. package/dist/cliConfig.cjs +2 -2
  12. package/dist/cliConfig.cjs.map +1 -1
  13. package/dist/cliConfig.d.ts +4 -1
  14. package/dist/cliConfig.js +2 -2
  15. package/dist/cliConfig.js.map +1 -1
  16. package/dist/duplication.cjs +2 -0
  17. package/dist/duplication.cjs.map +1 -0
  18. package/dist/duplication.d.ts +11 -0
  19. package/dist/duplication.js +2 -0
  20. package/dist/duplication.js.map +1 -0
  21. package/dist/index.cjs +1 -2
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -2
  24. package/dist/languages.cjs +2 -2
  25. package/dist/languages.cjs.map +1 -1
  26. package/dist/languages.js +2 -2
  27. package/dist/languages.js.map +1 -1
  28. package/dist/metrics.cjs +3 -2
  29. package/dist/metrics.cjs.map +1 -1
  30. package/dist/metrics.js +3 -2
  31. package/dist/metrics.js.map +1 -1
  32. package/dist/nativeMetrics.cjs +2 -0
  33. package/dist/nativeMetrics.cjs.map +1 -0
  34. package/dist/nativeMetrics.d.ts +24 -0
  35. package/dist/nativeMetrics.js +2 -0
  36. package/dist/nativeMetrics.js.map +1 -0
  37. package/dist/types.d.ts +17 -7
  38. package/dist/typescriptProject.cjs +3 -2
  39. package/dist/typescriptProject.cjs.map +1 -1
  40. package/dist/typescriptProject.js +3 -2
  41. package/dist/typescriptProject.js.map +1 -1
  42. package/package.json +17 -10
  43. package/dist/index.cjs.map +0 -1
  44. package/dist/index.js.map +0 -1
@@ -0,0 +1,2 @@
1
+ "use strict";const e=require("./languages.cjs");let t=require("node:module");const n=new Map(e.defaultLanguages.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{i=(0,t.createRequire)(require("url").pathToFileURL(__filename).href)(`../native/code-gauge.node`)}catch{}return i}exports.isNativeBackendAvailable=o,exports.measureWithNativeBackend=a;
2
+ //# sourceMappingURL=nativeMetrics.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeMetrics.cjs","names":["defaultLanguages"],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, difficulty, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'maintainabilityIndex' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n}\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n cachedBinding = requireNative('../native/code-gauge.node') as NativeBinding;\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"6EAyBA,MAAM,EAAwB,IAAI,IAAIA,EAAAA,iBAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,GAAA,EAAA,EAAA,cAAA,CAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAA4B,CAAC,CAAC,2BAA2B,CAC3D,MAAQ,CAER,CACA,OAAO,CACT"}
@@ -0,0 +1,24 @@
1
+ import type { CodeMetrics, LanguageDefinition } from './types.js';
2
+ /**
3
+ * Halstead counts measured natively; the derived float metrics (volume, difficulty, ...) are
4
+ * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and
5
+ * the native backend must be bit-identical to the TypeScript one.
6
+ */
7
+ export interface NativeHalsteadCounts {
8
+ distinctOperators: number;
9
+ distinctOperands: number;
10
+ totalOperators: number;
11
+ totalOperands: number;
12
+ }
13
+ export interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'maintainabilityIndex' | 'syntaxTree'> {
14
+ halsteadCounts: NativeHalsteadCounts;
15
+ syntaxTree?: string;
16
+ }
17
+ /**
18
+ * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller
19
+ * falls back to the TypeScript implementation. Custom-registered languages always fall back: the
20
+ * addon only embeds the built-in grammars.
21
+ */
22
+ export declare function measureWithNativeBackend(code: string, language: LanguageDefinition, includeSyntaxTree: boolean): NativeMetricsPayload | undefined;
23
+ /** Whether measureCode currently uses the native backend for built-in languages. */
24
+ export declare function isNativeBackendAvailable(): boolean;
@@ -0,0 +1,2 @@
1
+ import{defaultLanguages as e}from"./languages.js";import{createRequire as t}from"node:module";const n=new Map(e.map(e=>[e.name,e]));let r=!1,i;function a(e,t,r){if(!s()||n.get(t.name)!==t||!e.isWellFormed())return;let i=c();if(i)try{return JSON.parse(i.measureCodeNative(e,t.name,r))}catch(e){if(process.env.CODE_GAUGE_NATIVE_STRICT===`1`)throw e;return}}function o(){return s()&&c()!==void 0}function s(){return process.env.CODE_GAUGE_NATIVE!==`0`}function c(){if(r)return i;r=!0;try{i=t(import.meta.url)(`../native/code-gauge.node`)}catch{}return i}export{o as isNativeBackendAvailable,a as measureWithNativeBackend};
2
+ //# sourceMappingURL=nativeMetrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nativeMetrics.js","names":[],"sources":["../src/nativeMetrics.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { defaultLanguages } from './languages.js';\nimport type { CodeMetrics, LanguageDefinition } from './types.js';\n\n/**\n * Halstead counts measured natively; the derived float metrics (volume, difficulty, ...) are\n * computed in TypeScript because V8 and Rust disagree on the last bit of log/log2 results, and\n * the native backend must be bit-identical to the TypeScript one.\n */\nexport interface NativeHalsteadCounts {\n distinctOperators: number;\n distinctOperands: number;\n totalOperators: number;\n totalOperands: number;\n}\n\nexport interface NativeMetricsPayload extends Omit<CodeMetrics, 'halstead' | 'maintainabilityIndex' | 'syntaxTree'> {\n halsteadCounts: NativeHalsteadCounts;\n syntaxTree?: string;\n}\n\ninterface NativeBinding {\n measureCodeNative(code: string, language: string, includeSyntaxTree: boolean): string;\n}\n\nconst defaultLanguageByName = new Map(defaultLanguages.map((language) => [language.name, language]));\n\nlet bindingLoadAttempted = false;\nlet cachedBinding: NativeBinding | undefined;\n\n/**\n * Measures via the Rust addon when it is built and applicable, or returns undefined so the caller\n * falls back to the TypeScript implementation. Custom-registered languages always fall back: the\n * addon only embeds the built-in grammars.\n */\nexport function measureWithNativeBackend(\n code: string,\n language: LanguageDefinition,\n includeSyntaxTree: boolean\n): NativeMetricsPayload | undefined {\n if (!isNativeBackendEnabled() || defaultLanguageByName.get(language.name) !== language) {\n return undefined;\n }\n\n // Lone surrogates cannot cross the N-API boundary losslessly (they become U+FFFD), so\n // ill-formed strings measure through the TypeScript backend, which sees them as-is.\n if (!code.isWellFormed()) {\n return undefined;\n }\n\n const binding = loadBinding();\n if (!binding) {\n return undefined;\n }\n\n try {\n return JSON.parse(binding.measureCodeNative(code, language.name, includeSyntaxTree)) as NativeMetricsPayload;\n } catch (error) {\n // Parity tests set the strict flag: without it, a binding that starts throwing would silently\n // degrade the \"native\" side of every comparison into a TypeScript-vs-TypeScript check.\n if (process.env.CODE_GAUGE_NATIVE_STRICT === '1') {\n throw error;\n }\n // A native failure (e.g. the tree-depth guard on pathological input) falls back to the\n // TypeScript backend instead of turning measureCode into a throwing API.\n return undefined;\n }\n}\n\n/** Whether measureCode currently uses the native backend for built-in languages. */\nexport function isNativeBackendAvailable(): boolean {\n return isNativeBackendEnabled() && loadBinding() !== undefined;\n}\n\n/** Checked per call (not cached) so tests can flip backends within one process. */\nfunction isNativeBackendEnabled(): boolean {\n return process.env.CODE_GAUGE_NATIVE !== '0';\n}\n\nfunction loadBinding(): NativeBinding | undefined {\n if (bindingLoadAttempted) {\n return cachedBinding;\n }\n bindingLoadAttempted = true;\n\n try {\n // Resolved relative to this file, so both src/ (tests) and dist/ (build) find native/.\n const requireNative = createRequire(import.meta.url);\n cachedBinding = requireNative('../native/code-gauge.node') as NativeBinding;\n } catch {\n // The addon has not been built (or this platform/module format cannot load it).\n }\n return cachedBinding;\n}\n"],"mappings":"8FAyBA,MAAM,EAAwB,IAAI,IAAI,EAAiB,IAAK,GAAa,CAAC,EAAS,KAAM,CAAQ,CAAC,CAAC,EAEnG,IAAI,EAAuB,GACvB,EAOJ,SAAgB,EACd,EACA,EACA,EACkC,CAOlC,GANI,CAAC,EAAuB,GAAK,EAAsB,IAAI,EAAS,IAAI,IAAM,GAM1E,CAAC,EAAK,aAAa,EACrB,OAGF,IAAM,EAAU,EAAY,EACvB,KAIL,GAAI,CACF,OAAO,KAAK,MAAM,EAAQ,kBAAkB,EAAM,EAAS,KAAM,CAAiB,CAAC,CACrF,OAAS,EAAO,CAGd,GAAI,QAAQ,IAAI,2BAA6B,IAC3C,MAAM,EAIR,MACF,CACF,CAGA,SAAgB,GAAoC,CAClD,OAAO,EAAuB,GAAK,EAAY,IAAM,IAAA,EACvD,CAGA,SAAS,GAAkC,CACzC,OAAO,QAAQ,IAAI,oBAAsB,GAC3C,CAEA,SAAS,GAAyC,CAChD,GAAI,EACF,OAAO,EAET,EAAuB,GAEvB,GAAI,CAGF,EADsB,EAAc,OAAO,KAAK,GACpB,CAAC,CAAC,2BAA2B,CAC3D,MAAQ,CAER,CACA,OAAO,CACT"}
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type SupportedLanguage = 'go' | 'javascript' | 'jsx' | 'python' | 'rust' | 'typescript' | 'tsx';
1
+ export type SupportedLanguage = 'c' | 'cpp' | 'go' | 'java' | 'javascript' | 'jsx' | 'python' | 'ruby' | 'rust' | 'typescript' | 'tsx';
2
2
  export type LanguageName = SupportedLanguage | (string & {});
3
3
  export type ParserLanguage = unknown;
4
4
  export interface LanguageDefinition {
@@ -41,6 +41,7 @@ export interface FunctionMetrics {
41
41
  returnsJsx: boolean;
42
42
  cyclomaticComplexity: number;
43
43
  cognitiveComplexity: number;
44
+ nestingDepth: number;
44
45
  callCount: number;
45
46
  uniqueCalleeCount: number;
46
47
  fanIn: number;
@@ -89,17 +90,26 @@ export interface SyntaxFeatureMetrics {
89
90
  tryStatementCount: number;
90
91
  }
91
92
  /**
92
- * Within-file structural duplication: copy-pasted code blocks whose syntax shape (node types,
93
- * ignoring identifiers and literals) repeats. Distinct from cross-file duplicate symbol names.
93
+ * Within-file structural duplication: copy-pasted regions whose normalized token sequence repeats.
94
+ * Identifiers are anonymized consistently by first-occurrence order and literals by kind, so
95
+ * consistently renamed copies match. Distinct from cross-file duplicate symbol names.
94
96
  */
95
97
  export interface DuplicationMetrics {
96
- /** Number of redundant (extra) copies of duplicated blocks, i.e. sum of (groupSize - 1). */
98
+ /** Number of redundant (extra) copies of duplicated regions, i.e. sum of (groupSize - 1). */
97
99
  duplicateBlockCount: number;
98
- /** Number of distinct shapes that appear more than once. */
100
+ /** Number of distinct normalized token sequences that appear more than once. */
99
101
  duplicateBlockGroupCount: number;
100
- /** 1-based line ranges of every counted copy, grouped by shared shape. */
102
+ /** 1-based line ranges of every counted copy, grouped by shared normalized token sequence. */
101
103
  duplicateBlockGroups: DuplicateBlockOccurrence[][];
102
- /** Node count of the largest duplicated block, indicating how big the copied region is. */
104
+ /** Number of distinct lines covered by any counted duplicate occurrence (originals included). */
105
+ duplicateLineCount: number;
106
+ /**
107
+ * duplicateLineCount / code lines (0 when the file has no code). Code lines are the denominator
108
+ * because duplicated lines are counted from matched tokens, which only ever land on code lines;
109
+ * dividing by total lines would let comment density deflate the ratio.
110
+ */
111
+ duplicationRatio: number;
112
+ /** Normalized token count of the largest duplicated region, indicating how big the copied region is. */
103
113
  maxDuplicateBlockSize: number;
104
114
  }
105
115
  export interface DuplicateBlockOccurrence {
@@ -1,2 +1,3 @@
1
- "use strict";var n=require("node:fs/promises"),t=require("node:path"),e=require("@typescript/native-preview/unstable/async"),i=require("@typescript/native-preview/unstable/ast");async function o(n,e,i,o,a){for(const r of function(n,e,i,o){const a=new Set([n,e]);e.startsWith(`${o}${t.sep}`)&&a.add(t.join(i,t.relative(o,e)));return[...a]}(e,i,o,a)){const t=await n.program.getSourceFile(r);if(t)return t}}async function a(t){try{return await n.realpath(t)}catch{return t}}async function r(n,t){const e=[],o=function(n){const t=[];return v(n,n=>{(f(n)||function(n){if(n.kind!==i.SyntaxKind.VariableDeclaration)return!1;const t=S(n,"initializer"),e=t?C(t):void 0;return!!e&&(f(e)||e.kind===i.SyntaxKind.CallExpression)}(n))&&t.push(n)}),t}(n),a=(r=n.text,/\bfrom\s+['"]react['"]|\bimport\s+['"]react['"]|\brequire\(\s*['"]react['"]\s*\)/u.test(r));var r;for(const i of o){if(!await s(i,t,a))continue;const o=y(i),r=p(i),c=m(n.text,r,o,n),u=h(n.text,c);e.push({implementationKey:`${r.pos}:${r.end}`,name:o?x(o):void 0,startColumn:u.column,startLine:u.line})}return function(n){const t=new Map;for(const e of n){const n=t.get(e.implementationKey);(!n||!n.name&&e.name)&&t.set(e.implementationKey,e)}return[...t.values()].map(n=>({name:n.name,startColumn:n.startColumn,startLine:n.startLine}))}(e)}async function s(n,t,e){const i=await t.getTypeAtLocation(n);if(!i)return!1;const o=y(n),a=o?x(o):void 0;if(e&&await u(i,n,t))return!0;const r=o?await t.getTypeAtLocation(o):void 0;return!!(e&&r&&await u(r,n,t))||!!function(n){return void 0!==n&&/^[A-Z]/u.test(n)}(a)&&(await c(i,n,t)||Boolean(r&&await c(r,n,t)))}async function c(n,t,i){const o=await i.getSignaturesOfType(n,e.SignatureKind.Call);for(const n of o){const e=await i.getReturnTypeOfSignature(n);if(e&&await l(e,t,i))return!0}return!1}async function u(n,t,e){const i=await e.typeToString(n,t);return/\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\b/u.test(i)}async function l(n,t,e){const i=await e.typeToString(n,t);return/\b(?:JSX\.Element|React\.JSX\.Element|ReactElement|ReactNode)\b/u.test(i)}function f(n){return!(n.kind===i.SyntaxKind.FunctionDeclaration&&!S(n,"body"))&&(n.kind===i.SyntaxKind.FunctionDeclaration||n.kind===i.SyntaxKind.FunctionExpression||n.kind===i.SyntaxKind.ArrowFunction)}function p(n){if(f(n))return n;const t=S(n,"initializer"),e=t?C(t):void 0;return e?f(e)?e:d(e)?g(e)??n:n:n}function d(n){if(n.kind!==i.SyntaxKind.CallExpression)return!1;const t=S(n,"expression"),e=t?function(n){const t=S(n,"name");return x(t||n)}(t):void 0;return"memo"===e||"forwardRef"===e}function g(n){let t;return n.forEachChild(n=>{const e=C(n);return f(e)?t=e:d(e)&&(t=g(e)),t}),t}function m(n,t,e,o){const a=i.getTokenPosOfNode(t,o);if(t.kind!==i.SyntaxKind.FunctionDeclaration||!e)return a;const r=x(e),s=r?n.indexOf(r,a):-1,c=s>=0?n.lastIndexOf("function",s):-1;if(c<a)return a;const u=n.slice(a,c),l=/\basync\s*$/u.exec(u);return l?a+l.index:c}function x(n){return"escapedText"in n?String(n.escapedText):"text"in n&&"string"==typeof n.text?n.text:void 0}function y(n){const t=S(n,"name");if(t)return t;let e=n;for(;e;){const n=S(e,"parent");if(!n)return;if(n.kind===i.SyntaxKind.VariableDeclaration)return S(n,"name");if(!w(n))return;e=n}}function C(n){let t=n;for(;w(t);){const n=S(t,"expression");if(!n)return t;t=n}return t}function w(n){return n.kind===i.SyntaxKind.ParenthesizedExpression||n.kind===i.SyntaxKind.AsExpression||n.kind===i.SyntaxKind.TypeAssertionExpression||n.kind===i.SyntaxKind.SatisfiesExpression}function S(n,t){const e=n[t];return function(n){return"object"==typeof n&&null!==n&&"kind"in n&&"pos"in n&&"end"in n}(e)?e:void 0}function h(n,t){const e=n.slice(0,t).split("\n");return{column:e.at(-1)?.length??0,line:e.length}}function E(n){const t=[];return v(n,n=>{n.kind===i.SyntaxKind.CallExpression&&t.push(n)}),t}function v(n,t){t(n),n.forEachChild(n=>{v(n,t)})}function F(n,t){for(const e of t)e.fileName&&n.add(e.fileName)}exports.measureTypeScriptProject=async function(n,i=[]){const s=new e.API({cwd:process.cwd()});try{const e=(await s.updateSnapshot({openProject:n})).getProjects(),c=await async function(n){const t=new Map;for(const e of n)t.set(await a(e),e);return t}(i),u=new Set(c.keys()),l=new Set,f={callExpressionCount:0,configDiagnosticCount:0,declarationDiagnosticCount:0,measuredRootFileCount:0,resolvedCallExpressionCount:0,rootFileCount:0,semanticDiagnosticCount:0,suggestionDiagnosticCount:0,syntacticDiagnosticCount:0},p=[],d=new Set;for(const n of e){const e=await Promise.all(n.rootFiles.map(async n=>({canonicalFile:await a(n),file:n})));f.rootFileCount+=e.length,f.measuredRootFileCount+=e.filter(({canonicalFile:n})=>u.has(n)).length;const i=await n.program.getSyntacticDiagnostics(),s=await n.program.getSemanticDiagnostics(),g=await n.program.getSuggestionDiagnostics(),m=await n.program.getDeclarationDiagnostics(),x=await n.program.getConfigFileParsingDiagnostics();f.syntacticDiagnosticCount+=i.length,f.semanticDiagnosticCount+=s.length,f.suggestionDiagnosticCount+=g.length,f.declarationDiagnosticCount+=m.length,f.configDiagnosticCount+=x.length,F(l,[...i,...s,...g,...m,...x]);const y=0===u.size?e:[...c.entries()].map(([n,t])=>({canonicalFile:n,file:t})),C=t.dirname(n.configFileName),w=await a(C);for(const{canonicalFile:t,file:e}of y){if(d.has(t))continue;const i=await o(n,e,t,C,w);if(!i)continue;d.add(t);const a=E(i);f.callExpressionCount+=a.length;const s=await r(i,n.checker);p.push(...s.map(n=>({...n,file:c.get(t)??e})));for(const t of a)await n.checker.getResolvedSignature(t)&&(f.resolvedCallExpressionCount+=1)}}const g=f.callExpressionCount-f.resolvedCallExpressionCount;return{...f,configFile:n,diagnosticFileCount:l.size,projectCount:e.length,reactComponentFunctions:p,unresolvedCallExpressionCount:g,resolvedCallExpressionRatio:0===f.callExpressionCount?0:f.resolvedCallExpressionCount/f.callExpressionCount}}finally{await s.close()}};
2
- //# sourceMappingURL=typescriptProject.cjs.map
1
+ "use strict";const e=require("./_virtual/_rolldown/runtime.cjs");let t=require("node:fs/promises"),n=require("node:path");n=e.__toESM(n,1);let r=require("typescript/unstable/async"),i=require("typescript/unstable/ast");async function a(e,t=[]){let i=new r.API({cwd:process.cwd()});try{let r=(await i.updateSnapshot({openProject:e})).getProjects(),a=await c(t),s=new Set(a.keys()),d=new Set,f={callExpressionCount:0,configDiagnosticCount:0,declarationDiagnosticCount:0,measuredRootFileCount:0,resolvedCallExpressionCount:0,rootFileCount:0,semanticDiagnosticCount:0,suggestionDiagnosticCount:0,syntacticDiagnosticCount:0},p=[],m=new Set;for(let e of r){let t=await Promise.all(e.rootFiles.map(async e=>({canonicalFile:await l(e),file:e})));f.rootFileCount+=t.length,f.measuredRootFileCount+=t.filter(({canonicalFile:e})=>s.has(e)).length;let r=await e.program.getSyntacticDiagnostics(),i=await e.program.getSemanticDiagnostics(),c=await e.program.getSuggestionDiagnostics(),h=await e.program.getDeclarationDiagnostics(),g=await e.program.getConfigFileParsingDiagnostics();f.syntacticDiagnosticCount+=r.length,f.semanticDiagnosticCount+=i.length,f.suggestionDiagnosticCount+=c.length,f.declarationDiagnosticCount+=h.length,f.configDiagnosticCount+=g.length,F(d,[...r,...i,...c,...h,...g]);let _=s.size===0?t:[...a.entries()].map(([e,t])=>({canonicalFile:e,file:t})),v=n.default.dirname(e.configFileName),y=await l(v);for(let{canonicalFile:t,file:n}of _){if(m.has(t))continue;let r=await o(e,n,t,v,y);if(!r)continue;m.add(t);let i=N(r);f.callExpressionCount+=i.length;let s=await u(r,e.checker);p.push(...s.map(e=>({...e,file:a.get(t)??n})));for(let t of i)await e.checker.getResolvedSignature(t)&&(f.resolvedCallExpressionCount+=1)}}let h=f.callExpressionCount-f.resolvedCallExpressionCount;return{...f,configFile:e,diagnosticFileCount:d.size,projectCount:r.length,reactComponentFunctions:p,unresolvedCallExpressionCount:h,resolvedCallExpressionRatio:f.callExpressionCount===0?0:f.resolvedCallExpressionCount/f.callExpressionCount}}finally{await i.close()}}async function o(e,t,n,r,i){for(let a of s(t,n,r,i)){let t=await e.program.getSourceFile(a);if(t)return t}}function s(e,t,r,i){let a=new Set([e,t]);return t.startsWith(`${i}${n.default.sep}`)&&a.add(n.default.join(r,n.default.relative(i,t))),[...a]}async function c(e){let t=new Map;for(let n of e)t.set(await l(n),n);return t}async function l(e){try{return await(0,t.realpath)(e)}catch{return e}}async function u(e,t){let n=[],r=d(e),i=p(e.text);for(let a of r){if(!await f(a,t,i))continue;let r=D(a),o=y(a),s=C(e.text,o,r,e),c=M(e.text,s);n.push({implementationKey:`${o.pos}:${o.end}`,name:r?T(r):void 0,startColumn:c.column,startLine:c.line})}return w(n)}function d(e){let t=[];return P(e,e=>{(_(e)||v(e))&&t.push(e)}),t}async function f(e,t,n){let r=await t.getTypeAtLocation(e);if(!r)return!1;let i=D(e),a=i?T(i):void 0;if(n&&await h(r,e,t))return!0;let o=i?await t.getTypeAtLocation(i):void 0;return n&&o&&await h(o,e,t)?!0:E(a)?await m(r,e,t)||!!(o&&await m(o,e,t)):!1}function p(e){return/\bfrom\s+['"]react['"]|\bimport\s+['"]react['"]|\brequire\(\s*['"]react['"]\s*\)/u.test(e)}async function m(e,t,n){let i=await n.getSignaturesOfType(e,r.SignatureKind.Call);for(let e of i){let r=await n.getReturnTypeOfSignature(e);if(r&&await g(r,t,n))return!0}return!1}async function h(e,t,n){let r=await n.typeToString(e,t);return/\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\b/u.test(r)}async function g(e,t,n){let r=await n.typeToString(e,t);return/\b(?:JSX\.Element|React\.JSX\.Element|ReactElement|ReactNode)\b/u.test(r)}function _(e){return e.kind===i.SyntaxKind.FunctionDeclaration&&!A(e,`body`)?!1:e.kind===i.SyntaxKind.FunctionDeclaration||e.kind===i.SyntaxKind.FunctionExpression||e.kind===i.SyntaxKind.ArrowFunction}function v(e){if(e.kind!==i.SyntaxKind.VariableDeclaration)return!1;let t=A(e,`initializer`),n=t?O(t):void 0;return n?_(n)||n.kind===i.SyntaxKind.CallExpression:!1}function y(e){if(_(e))return e;let t=A(e,`initializer`),n=t?O(t):void 0;return n?_(n)?n:b(n)?S(n)??e:e:e}function b(e){if(e.kind!==i.SyntaxKind.CallExpression)return!1;let t=A(e,`expression`),n=t?x(t):void 0;return n===`memo`||n===`forwardRef`}function x(e){return T(A(e,`name`)||e)}function S(e){let t;return e.forEachChild(e=>{let n=O(e);return _(n)?t=n:b(n)&&(t=S(n)),t}),t}function C(e,t,n,r){let a=(0,i.getTokenPosOfNode)(t,r);if(t.kind!==i.SyntaxKind.FunctionDeclaration||!n)return a;let o=T(n),s=o?e.indexOf(o,a):-1,c=s>=0?e.lastIndexOf(`function`,s):-1;if(c<a)return a;let l=e.slice(a,c),u=/\basync\s*$/u.exec(l);return u?a+u.index:c}function w(e){let t=new Map;for(let n of e){let e=t.get(n.implementationKey);(!e||!e.name&&n.name)&&t.set(n.implementationKey,n)}return[...t.values()].map(e=>({name:e.name,startColumn:e.startColumn,startLine:e.startLine}))}function T(e){if(`escapedText`in e)return String(e.escapedText);if(`text`in e&&typeof e.text==`string`)return e.text}function E(e){return e!==void 0&&/^[A-Z]/u.test(e)}function D(e){let t=A(e,`name`);if(t)return t;let n=e;for(;n;){let e=A(n,`parent`);if(!e)return;if(e.kind===i.SyntaxKind.VariableDeclaration)return A(e,`name`);if(!k(e))return;n=e}}function O(e){let t=e;for(;k(t);){let e=A(t,`expression`);if(!e)return t;t=e}return t}function k(e){return e.kind===i.SyntaxKind.ParenthesizedExpression||e.kind===i.SyntaxKind.AsExpression||e.kind===i.SyntaxKind.TypeAssertionExpression||e.kind===i.SyntaxKind.SatisfiesExpression}function A(e,t){let n=e[t];return j(n)?n:void 0}function j(e){return typeof e==`object`&&!!e&&`kind`in e&&`pos`in e&&`end`in e}function M(e,t){let n=e.slice(0,t).split(`
2
+ `);return{column:n.at(-1)?.length??0,line:n.length}}function N(e){let t=[];return P(e,e=>{e.kind===i.SyntaxKind.CallExpression&&t.push(e)}),t}function P(e,t){t(e),e.forEachChild(e=>{P(e,t)})}function F(e,t){for(let n of t)n.fileName&&e.add(n.fileName)}exports.measureTypeScriptProject=a;
3
+ //# sourceMappingURL=typescriptProject.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"typescriptProject.cjs","sources":["../src/typescriptProject.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { API, SignatureKind, type Checker, type Type } from '@typescript/native-preview/unstable/async';\nimport { getTokenPosOfNode, SyntaxKind, type Node, type SourceFile } from '@typescript/native-preview/unstable/ast';\n\nexport interface ReactComponentFunctionMetric {\n file: string;\n name?: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface TypeScriptProjectMetrics {\n callExpressionCount: number;\n configDiagnosticCount: number;\n configFile: string;\n declarationDiagnosticCount: number;\n diagnosticFileCount: number;\n measuredRootFileCount: number;\n projectCount: number;\n reactComponentFunctions: ReactComponentFunctionMetric[];\n resolvedCallExpressionCount: number;\n resolvedCallExpressionRatio: number;\n rootFileCount: number;\n semanticDiagnosticCount: number;\n suggestionDiagnosticCount: number;\n syntacticDiagnosticCount: number;\n unresolvedCallExpressionCount: number;\n}\n\ninterface DiagnosticLike {\n fileName?: string;\n}\n\ninterface ReactComponentCandidateMetric extends Omit<ReactComponentFunctionMetric, 'file'> {\n implementationKey: string;\n}\n\nexport async function measureTypeScriptProject(\n configFile: string,\n measuredFiles: readonly string[] = []\n): Promise<TypeScriptProjectMetrics> {\n const api = new API({ cwd: process.cwd() });\n try {\n const snapshot = await api.updateSnapshot({ openProject: configFile });\n const projects = snapshot.getProjects();\n const measuredFileByCanonicalFile = await mapMeasuredFilesByCanonicalFile(measuredFiles);\n const measuredFileSet = new Set(measuredFileByCanonicalFile.keys());\n const diagnosticFiles = new Set<string>();\n const totals = {\n callExpressionCount: 0,\n configDiagnosticCount: 0,\n declarationDiagnosticCount: 0,\n measuredRootFileCount: 0,\n resolvedCallExpressionCount: 0,\n rootFileCount: 0,\n semanticDiagnosticCount: 0,\n suggestionDiagnosticCount: 0,\n syntacticDiagnosticCount: 0,\n };\n const reactComponentFunctions: ReactComponentFunctionMetric[] = [];\n const analyzedFiles = new Set<string>();\n\n for (const project of projects) {\n const projectRootFiles = await Promise.all(\n project.rootFiles.map(async (file) => ({ canonicalFile: await canonicalizeFile(file), file }))\n );\n totals.rootFileCount += projectRootFiles.length;\n totals.measuredRootFileCount += projectRootFiles.filter(({ canonicalFile }) =>\n measuredFileSet.has(canonicalFile)\n ).length;\n\n const syntacticDiagnostics = await project.program.getSyntacticDiagnostics();\n const semanticDiagnostics = await project.program.getSemanticDiagnostics();\n const suggestionDiagnostics = await project.program.getSuggestionDiagnostics();\n const declarationDiagnostics = await project.program.getDeclarationDiagnostics();\n const configDiagnostics = await project.program.getConfigFileParsingDiagnostics();\n totals.syntacticDiagnosticCount += syntacticDiagnostics.length;\n totals.semanticDiagnosticCount += semanticDiagnostics.length;\n totals.suggestionDiagnosticCount += suggestionDiagnostics.length;\n totals.declarationDiagnosticCount += declarationDiagnostics.length;\n totals.configDiagnosticCount += configDiagnostics.length;\n addDiagnosticFiles(diagnosticFiles, [\n ...syntacticDiagnostics,\n ...semanticDiagnostics,\n ...suggestionDiagnostics,\n ...declarationDiagnostics,\n ...configDiagnostics,\n ]);\n\n const projectAnalysisFiles =\n measuredFileSet.size === 0\n ? projectRootFiles\n : [...measuredFileByCanonicalFile.entries()].map(([canonicalFile, file]) => ({ canonicalFile, file }));\n const configDirectory = path.dirname(project.configFileName);\n const canonicalConfigDirectory = await canonicalizeFile(configDirectory);\n for (const { canonicalFile, file } of projectAnalysisFiles) {\n if (analyzedFiles.has(canonicalFile)) {\n continue;\n }\n const sourceFile = await getProjectSourceFile(\n project,\n file,\n canonicalFile,\n configDirectory,\n canonicalConfigDirectory\n );\n if (!sourceFile) {\n continue;\n }\n analyzedFiles.add(canonicalFile);\n const callExpressions = collectCallExpressions(sourceFile);\n totals.callExpressionCount += callExpressions.length;\n const fileReactComponentFunctions = await collectReactComponentFunctions(sourceFile, project.checker);\n reactComponentFunctions.push(\n ...fileReactComponentFunctions.map((component) => ({\n ...component,\n file: measuredFileByCanonicalFile.get(canonicalFile) ?? file,\n }))\n );\n for (const callExpression of callExpressions) {\n if (await project.checker.getResolvedSignature(callExpression)) {\n totals.resolvedCallExpressionCount += 1;\n }\n }\n }\n }\n\n const unresolvedCallExpressionCount = totals.callExpressionCount - totals.resolvedCallExpressionCount;\n return {\n ...totals,\n configFile,\n diagnosticFileCount: diagnosticFiles.size,\n projectCount: projects.length,\n reactComponentFunctions,\n unresolvedCallExpressionCount,\n resolvedCallExpressionRatio:\n totals.callExpressionCount === 0 ? 0 : totals.resolvedCallExpressionCount / totals.callExpressionCount,\n };\n } finally {\n await api.close();\n }\n}\n\nasync function getProjectSourceFile(\n project: { configFileName: string; program: { getSourceFile: (file: string) => Promise<SourceFile | undefined> } },\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): Promise<SourceFile | undefined> {\n for (const candidate of getProjectFileCandidates(file, canonicalFile, configDirectory, canonicalConfigDirectory)) {\n const sourceFile = await project.program.getSourceFile(candidate);\n if (sourceFile) {\n return sourceFile;\n }\n }\n return undefined;\n}\n\nfunction getProjectFileCandidates(\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): string[] {\n const candidates = new Set([file, canonicalFile]);\n if (canonicalFile.startsWith(`${canonicalConfigDirectory}${path.sep}`)) {\n candidates.add(path.join(configDirectory, path.relative(canonicalConfigDirectory, canonicalFile)));\n }\n return [...candidates];\n}\n\nasync function mapMeasuredFilesByCanonicalFile(files: readonly string[]): Promise<Map<string, string>> {\n const measuredFileByCanonicalFile = new Map<string, string>();\n for (const file of files) {\n measuredFileByCanonicalFile.set(await canonicalizeFile(file), file);\n }\n return measuredFileByCanonicalFile;\n}\n\nasync function canonicalizeFile(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch {\n return file;\n }\n}\n\nasync function collectReactComponentFunctions(\n sourceFile: SourceFile,\n checker: Checker\n): Promise<Omit<ReactComponentFunctionMetric, 'file'>[]> {\n const components: ReactComponentCandidateMetric[] = [];\n const candidates = collectReactComponentCandidates(sourceFile);\n const hasReactReference = containsReactModuleReference(sourceFile.text);\n for (const candidate of candidates) {\n if (!(await isReactComponentCandidate(candidate, checker, hasReactReference))) {\n continue;\n }\n const name = findNameNode(candidate);\n const functionNode = findComponentFunctionNode(candidate);\n const startOffset = findFunctionStartPosition(sourceFile.text, functionNode, name, sourceFile);\n const startPosition = positionToLineColumn(sourceFile.text, startOffset);\n components.push({\n implementationKey: `${functionNode.pos}:${functionNode.end}`,\n name: name ? findCandidateName(name) : undefined,\n startColumn: startPosition.column,\n startLine: startPosition.line,\n });\n }\n return dedupeReactComponentFunctions(components);\n}\n\nfunction collectReactComponentCandidates(root: Node): Node[] {\n const candidates: Node[] = [];\n visitNode(root, (node) => {\n if (isFunctionLikeCandidate(node) || isVariableFunctionCandidate(node)) {\n candidates.push(node);\n }\n });\n return candidates;\n}\n\nasync function isReactComponentCandidate(node: Node, checker: Checker, hasReactReference: boolean): Promise<boolean> {\n const type = await checker.getTypeAtLocation(node);\n if (!type) {\n return false;\n }\n\n const name = findNameNode(node);\n const componentName = name ? findCandidateName(name) : undefined;\n if (hasReactReference && (await hasReactComponentTypeName(type, node, checker))) {\n return true;\n }\n\n const namedType = name ? await checker.getTypeAtLocation(name) : undefined;\n if (hasReactReference && namedType && (await hasReactComponentTypeName(namedType, node, checker))) {\n return true;\n }\n\n if (!isUppercaseComponentName(componentName)) {\n return false;\n }\n\n return (\n (await hasReactRenderableReturnType(type, node, checker)) ||\n Boolean(namedType && (await hasReactRenderableReturnType(namedType, node, checker)))\n );\n}\n\nfunction containsReactModuleReference(text: string): boolean {\n return /\\bfrom\\s+['\"]react['\"]|\\bimport\\s+['\"]react['\"]|\\brequire\\(\\s*['\"]react['\"]\\s*\\)/u.test(text);\n}\n\nasync function hasReactRenderableReturnType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const signatures = await checker.getSignaturesOfType(type, SignatureKind.Call);\n for (const signature of signatures) {\n const returnType = await checker.getReturnTypeOfSignature(signature);\n if (returnType && (await isReactRenderableType(returnType, node, checker))) {\n return true;\n }\n }\n return false;\n}\n\nasync function hasReactComponentTypeName(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\\b/u.test(\n typeName\n );\n}\n\nasync function isReactRenderableType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:JSX\\.Element|React\\.JSX\\.Element|ReactElement|ReactNode)\\b/u.test(typeName);\n}\n\nfunction isFunctionLikeCandidate(node: Node): boolean {\n if (node.kind === SyntaxKind.FunctionDeclaration && !getNodeProperty(node, 'body')) {\n return false;\n }\n\n return (\n node.kind === SyntaxKind.FunctionDeclaration ||\n node.kind === SyntaxKind.FunctionExpression ||\n node.kind === SyntaxKind.ArrowFunction\n );\n}\n\nfunction isVariableFunctionCandidate(node: Node): boolean {\n if (node.kind !== SyntaxKind.VariableDeclaration) {\n return false;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n return unwrappedInitializer\n ? isFunctionLikeCandidate(unwrappedInitializer) || unwrappedInitializer.kind === SyntaxKind.CallExpression\n : false;\n}\n\nfunction findComponentFunctionNode(node: Node): Node {\n if (isFunctionLikeCandidate(node)) {\n return node;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n if (!unwrappedInitializer) {\n return node;\n }\n if (isFunctionLikeCandidate(unwrappedInitializer)) {\n return unwrappedInitializer;\n }\n if (isComponentImplementationWrapperCall(unwrappedInitializer)) {\n return findComponentImplementationWrapperFunction(unwrappedInitializer) ?? node;\n }\n return node;\n}\n\nfunction isComponentImplementationWrapperCall(node: Node): boolean {\n if (node.kind !== SyntaxKind.CallExpression) {\n return false;\n }\n\n const expression = getNodeProperty(node, 'expression');\n const expressionName = expression ? findCallExpressionName(expression) : undefined;\n return expressionName === 'memo' || expressionName === 'forwardRef';\n}\n\nfunction findCallExpressionName(expression: Node): string | undefined {\n const name = getNodeProperty(expression, 'name');\n return name ? findCandidateName(name) : findCandidateName(expression);\n}\n\nfunction findComponentImplementationWrapperFunction(node: Node): Node | undefined {\n let functionNode: Node | undefined;\n node.forEachChild((child) => {\n const unwrappedChild = unwrapExpression(child);\n if (isFunctionLikeCandidate(unwrappedChild)) {\n functionNode = unwrappedChild;\n } else if (isComponentImplementationWrapperCall(unwrappedChild)) {\n functionNode = findComponentImplementationWrapperFunction(unwrappedChild);\n }\n return functionNode;\n });\n return functionNode;\n}\n\nfunction findFunctionStartPosition(\n text: string,\n functionNode: Node,\n name: Node | undefined,\n sourceFile: SourceFile\n): number {\n const tokenPosition = getTokenPosOfNode(functionNode, sourceFile);\n if (functionNode.kind !== SyntaxKind.FunctionDeclaration || !name) {\n return tokenPosition;\n }\n\n const functionName = findCandidateName(name);\n const namePosition = functionName ? text.indexOf(functionName, tokenPosition) : -1;\n const functionPosition = namePosition >= 0 ? text.lastIndexOf('function', namePosition) : -1;\n if (functionPosition < tokenPosition) {\n return tokenPosition;\n }\n\n const modifierText = text.slice(tokenPosition, functionPosition);\n const asyncMatch = /\\basync\\s*$/u.exec(modifierText);\n return asyncMatch ? tokenPosition + asyncMatch.index : functionPosition;\n}\n\nfunction dedupeReactComponentFunctions(\n components: readonly ReactComponentCandidateMetric[]\n): Omit<ReactComponentFunctionMetric, 'file'>[] {\n const componentByKey = new Map<string, ReactComponentCandidateMetric>();\n for (const component of components) {\n const existingComponent = componentByKey.get(component.implementationKey);\n if (!existingComponent || (!existingComponent.name && component.name)) {\n componentByKey.set(component.implementationKey, component);\n }\n }\n return [...componentByKey.values()].map((component) => ({\n name: component.name,\n startColumn: component.startColumn,\n startLine: component.startLine,\n }));\n}\n\nfunction findCandidateName(name: Node): string | undefined {\n if ('escapedText' in name) {\n return String(name.escapedText);\n }\n if ('text' in name && typeof name.text === 'string') {\n return name.text;\n }\n return undefined;\n}\n\nfunction isUppercaseComponentName(name: string | undefined): boolean {\n return name !== undefined && /^[A-Z]/u.test(name);\n}\n\nfunction findNameNode(node: Node): Node | undefined {\n const name = getNodeProperty(node, 'name');\n if (name) {\n return name;\n }\n\n let currentNode: Node | undefined = node;\n while (currentNode) {\n const parent = getNodeProperty(currentNode, 'parent');\n if (!parent) {\n return undefined;\n }\n if (parent.kind === SyntaxKind.VariableDeclaration) {\n return getNodeProperty(parent, 'name');\n }\n if (!isExpressionWrapperNode(parent)) {\n return undefined;\n }\n currentNode = parent;\n }\n return undefined;\n}\n\nfunction unwrapExpression(node: Node): Node {\n let currentNode = node;\n while (isExpressionWrapperNode(currentNode)) {\n const expression = getNodeProperty(currentNode, 'expression');\n if (!expression) {\n return currentNode;\n }\n currentNode = expression;\n }\n return currentNode;\n}\n\nfunction isExpressionWrapperNode(node: Node): boolean {\n return (\n node.kind === SyntaxKind.ParenthesizedExpression ||\n node.kind === SyntaxKind.AsExpression ||\n node.kind === SyntaxKind.TypeAssertionExpression ||\n node.kind === SyntaxKind.SatisfiesExpression\n );\n}\n\nfunction getNodeProperty(\n node: Node,\n property: 'body' | 'expression' | 'initializer' | 'name' | 'parent'\n): Node | undefined {\n const value = (node as Partial<Record<typeof property, Node>>)[property];\n return isNode(value) ? value : undefined;\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && 'kind' in value && 'pos' in value && 'end' in value;\n}\n\nfunction positionToLineColumn(text: string, position: number): { column: number; line: number } {\n const lines = text.slice(0, position).split('\\n');\n return { column: lines.at(-1)?.length ?? 0, line: lines.length };\n}\n\nfunction collectCallExpressions(root: Node): Node[] {\n const calls: Node[] = [];\n visitNode(root, (node) => {\n if (node.kind === SyntaxKind.CallExpression) {\n calls.push(node);\n }\n });\n return calls;\n}\n\nfunction visitNode(node: Node, visitor: (node: Node) => void): void {\n visitor(node);\n node.forEachChild((child) => {\n visitNode(child, visitor);\n return;\n });\n}\n\nfunction addDiagnosticFiles(files: Set<string>, diagnostics: readonly DiagnosticLike[]): void {\n for (const diagnostic of diagnostics) {\n if (diagnostic.fileName) {\n files.add(diagnostic.fileName);\n }\n }\n}\n"],"names":["async","getProjectSourceFile","project","file","canonicalFile","configDirectory","canonicalConfigDirectory","candidate","candidates","Set","startsWith","path","sep","add","join","relative","getProjectFileCandidates","sourceFile","program","getSourceFile","canonicalizeFile","realpath","collectReactComponentFunctions","checker","components","root","visitNode","node","isFunctionLikeCandidate","kind","SyntaxKind","VariableDeclaration","initializer","getNodeProperty","unwrappedInitializer","unwrapExpression","undefined","CallExpression","isVariableFunctionCandidate","push","collectReactComponentCandidates","hasReactReference","text","test","isReactComponentCandidate","name","findNameNode","functionNode","findComponentFunctionNode","startOffset","findFunctionStartPosition","startPosition","positionToLineColumn","implementationKey","pos","end","findCandidateName","startColumn","column","startLine","line","componentByKey","Map","component","existingComponent","get","set","values","map","dedupeReactComponentFunctions","type","getTypeAtLocation","componentName","hasReactComponentTypeName","namedType","isUppercaseComponentName","hasReactRenderableReturnType","Boolean","signatures","getSignaturesOfType","SignatureKind","Call","signature","returnType","getReturnTypeOfSignature","isReactRenderableType","typeName","typeToString","FunctionDeclaration","FunctionExpression","ArrowFunction","isComponentImplementationWrapperCall","findComponentImplementationWrapperFunction","expression","expressionName","findCallExpressionName","forEachChild","child","unwrappedChild","tokenPosition","getTokenPosOfNode","functionName","namePosition","indexOf","functionPosition","lastIndexOf","modifierText","slice","asyncMatch","exec","index","String","escapedText","currentNode","parent","isExpressionWrapperNode","ParenthesizedExpression","AsExpression","TypeAssertionExpression","SatisfiesExpression","property","value","isNode","position","lines","split","at","length","collectCallExpressions","calls","visitor","addDiagnosticFiles","files","diagnostics","diagnostic","fileName","configFile","measuredFiles","api","API","cwd","process","projects","updateSnapshot","openProject","getProjects","measuredFileByCanonicalFile","mapMeasuredFilesByCanonicalFile","measuredFileSet","keys","diagnosticFiles","totals","callExpressionCount","configDiagnosticCount","declarationDiagnosticCount","measuredRootFileCount","resolvedCallExpressionCount","rootFileCount","semanticDiagnosticCount","suggestionDiagnosticCount","syntacticDiagnosticCount","reactComponentFunctions","analyzedFiles","projectRootFiles","Promise","all","rootFiles","filter","has","syntacticDiagnostics","getSyntacticDiagnostics","semanticDiagnostics","getSemanticDiagnostics","suggestionDiagnostics","getSuggestionDiagnostics","declarationDiagnostics","getDeclarationDiagnostics","configDiagnostics","getConfigFileParsingDiagnostics","projectAnalysisFiles","size","entries","dirname","configFileName","callExpressions","fileReactComponentFunctions","callExpression","getResolvedSignature","unresolvedCallExpressionCount","diagnosticFileCount","projectCount","resolvedCallExpressionRatio","close"],"mappings":"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"}
1
+ {"version":3,"file":"typescriptProject.cjs","names":["API","path","SignatureKind","SyntaxKind"],"sources":["../src/typescriptProject.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { API, SignatureKind, type Checker, type Type } from 'typescript/unstable/async';\nimport { getTokenPosOfNode, SyntaxKind, type Node, type SourceFile } from 'typescript/unstable/ast';\n\nexport interface ReactComponentFunctionMetric {\n file: string;\n name?: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface TypeScriptProjectMetrics {\n callExpressionCount: number;\n configDiagnosticCount: number;\n configFile: string;\n declarationDiagnosticCount: number;\n diagnosticFileCount: number;\n measuredRootFileCount: number;\n projectCount: number;\n reactComponentFunctions: ReactComponentFunctionMetric[];\n resolvedCallExpressionCount: number;\n resolvedCallExpressionRatio: number;\n rootFileCount: number;\n semanticDiagnosticCount: number;\n suggestionDiagnosticCount: number;\n syntacticDiagnosticCount: number;\n unresolvedCallExpressionCount: number;\n}\n\ninterface DiagnosticLike {\n fileName?: string;\n}\n\ninterface ReactComponentCandidateMetric extends Omit<ReactComponentFunctionMetric, 'file'> {\n implementationKey: string;\n}\n\nexport async function measureTypeScriptProject(\n configFile: string,\n measuredFiles: readonly string[] = []\n): Promise<TypeScriptProjectMetrics> {\n const api = new API({ cwd: process.cwd() });\n try {\n const snapshot = await api.updateSnapshot({ openProject: configFile });\n const projects = snapshot.getProjects();\n const measuredFileByCanonicalFile = await mapMeasuredFilesByCanonicalFile(measuredFiles);\n const measuredFileSet = new Set(measuredFileByCanonicalFile.keys());\n const diagnosticFiles = new Set<string>();\n const totals = {\n callExpressionCount: 0,\n configDiagnosticCount: 0,\n declarationDiagnosticCount: 0,\n measuredRootFileCount: 0,\n resolvedCallExpressionCount: 0,\n rootFileCount: 0,\n semanticDiagnosticCount: 0,\n suggestionDiagnosticCount: 0,\n syntacticDiagnosticCount: 0,\n };\n const reactComponentFunctions: ReactComponentFunctionMetric[] = [];\n const analyzedFiles = new Set<string>();\n\n for (const project of projects) {\n const projectRootFiles = await Promise.all(\n project.rootFiles.map(async (file) => ({ canonicalFile: await canonicalizeFile(file), file }))\n );\n totals.rootFileCount += projectRootFiles.length;\n totals.measuredRootFileCount += projectRootFiles.filter(({ canonicalFile }) =>\n measuredFileSet.has(canonicalFile)\n ).length;\n\n const syntacticDiagnostics = await project.program.getSyntacticDiagnostics();\n const semanticDiagnostics = await project.program.getSemanticDiagnostics();\n const suggestionDiagnostics = await project.program.getSuggestionDiagnostics();\n const declarationDiagnostics = await project.program.getDeclarationDiagnostics();\n const configDiagnostics = await project.program.getConfigFileParsingDiagnostics();\n totals.syntacticDiagnosticCount += syntacticDiagnostics.length;\n totals.semanticDiagnosticCount += semanticDiagnostics.length;\n totals.suggestionDiagnosticCount += suggestionDiagnostics.length;\n totals.declarationDiagnosticCount += declarationDiagnostics.length;\n totals.configDiagnosticCount += configDiagnostics.length;\n addDiagnosticFiles(diagnosticFiles, [\n ...syntacticDiagnostics,\n ...semanticDiagnostics,\n ...suggestionDiagnostics,\n ...declarationDiagnostics,\n ...configDiagnostics,\n ]);\n\n const projectAnalysisFiles =\n measuredFileSet.size === 0\n ? projectRootFiles\n : [...measuredFileByCanonicalFile.entries()].map(([canonicalFile, file]) => ({ canonicalFile, file }));\n const configDirectory = path.dirname(project.configFileName);\n const canonicalConfigDirectory = await canonicalizeFile(configDirectory);\n for (const { canonicalFile, file } of projectAnalysisFiles) {\n if (analyzedFiles.has(canonicalFile)) {\n continue;\n }\n const sourceFile = await getProjectSourceFile(\n project,\n file,\n canonicalFile,\n configDirectory,\n canonicalConfigDirectory\n );\n if (!sourceFile) {\n continue;\n }\n analyzedFiles.add(canonicalFile);\n const callExpressions = collectCallExpressions(sourceFile);\n totals.callExpressionCount += callExpressions.length;\n const fileReactComponentFunctions = await collectReactComponentFunctions(sourceFile, project.checker);\n reactComponentFunctions.push(\n ...fileReactComponentFunctions.map((component) => ({\n ...component,\n file: measuredFileByCanonicalFile.get(canonicalFile) ?? file,\n }))\n );\n for (const callExpression of callExpressions) {\n if (await project.checker.getResolvedSignature(callExpression)) {\n totals.resolvedCallExpressionCount += 1;\n }\n }\n }\n }\n\n const unresolvedCallExpressionCount = totals.callExpressionCount - totals.resolvedCallExpressionCount;\n return {\n ...totals,\n configFile,\n diagnosticFileCount: diagnosticFiles.size,\n projectCount: projects.length,\n reactComponentFunctions,\n unresolvedCallExpressionCount,\n resolvedCallExpressionRatio:\n totals.callExpressionCount === 0 ? 0 : totals.resolvedCallExpressionCount / totals.callExpressionCount,\n };\n } finally {\n await api.close();\n }\n}\n\nasync function getProjectSourceFile(\n project: { configFileName: string; program: { getSourceFile: (file: string) => Promise<SourceFile | undefined> } },\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): Promise<SourceFile | undefined> {\n for (const candidate of getProjectFileCandidates(file, canonicalFile, configDirectory, canonicalConfigDirectory)) {\n const sourceFile = await project.program.getSourceFile(candidate);\n if (sourceFile) {\n return sourceFile;\n }\n }\n return undefined;\n}\n\nfunction getProjectFileCandidates(\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): string[] {\n const candidates = new Set([file, canonicalFile]);\n if (canonicalFile.startsWith(`${canonicalConfigDirectory}${path.sep}`)) {\n candidates.add(path.join(configDirectory, path.relative(canonicalConfigDirectory, canonicalFile)));\n }\n return [...candidates];\n}\n\nasync function mapMeasuredFilesByCanonicalFile(files: readonly string[]): Promise<Map<string, string>> {\n const measuredFileByCanonicalFile = new Map<string, string>();\n for (const file of files) {\n measuredFileByCanonicalFile.set(await canonicalizeFile(file), file);\n }\n return measuredFileByCanonicalFile;\n}\n\nasync function canonicalizeFile(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch {\n return file;\n }\n}\n\nasync function collectReactComponentFunctions(\n sourceFile: SourceFile,\n checker: Checker\n): Promise<Omit<ReactComponentFunctionMetric, 'file'>[]> {\n const components: ReactComponentCandidateMetric[] = [];\n const candidates = collectReactComponentCandidates(sourceFile);\n const hasReactReference = containsReactModuleReference(sourceFile.text);\n for (const candidate of candidates) {\n if (!(await isReactComponentCandidate(candidate, checker, hasReactReference))) {\n continue;\n }\n const name = findNameNode(candidate);\n const functionNode = findComponentFunctionNode(candidate);\n const startOffset = findFunctionStartPosition(sourceFile.text, functionNode, name, sourceFile);\n const startPosition = positionToLineColumn(sourceFile.text, startOffset);\n components.push({\n implementationKey: `${functionNode.pos}:${functionNode.end}`,\n name: name ? findCandidateName(name) : undefined,\n startColumn: startPosition.column,\n startLine: startPosition.line,\n });\n }\n return dedupeReactComponentFunctions(components);\n}\n\nfunction collectReactComponentCandidates(root: Node): Node[] {\n const candidates: Node[] = [];\n visitNode(root, (node) => {\n if (isFunctionLikeCandidate(node) || isVariableFunctionCandidate(node)) {\n candidates.push(node);\n }\n });\n return candidates;\n}\n\nasync function isReactComponentCandidate(node: Node, checker: Checker, hasReactReference: boolean): Promise<boolean> {\n const type = await checker.getTypeAtLocation(node);\n if (!type) {\n return false;\n }\n\n const name = findNameNode(node);\n const componentName = name ? findCandidateName(name) : undefined;\n if (hasReactReference && (await hasReactComponentTypeName(type, node, checker))) {\n return true;\n }\n\n const namedType = name ? await checker.getTypeAtLocation(name) : undefined;\n if (hasReactReference && namedType && (await hasReactComponentTypeName(namedType, node, checker))) {\n return true;\n }\n\n if (!isUppercaseComponentName(componentName)) {\n return false;\n }\n\n return (\n (await hasReactRenderableReturnType(type, node, checker)) ||\n Boolean(namedType && (await hasReactRenderableReturnType(namedType, node, checker)))\n );\n}\n\nfunction containsReactModuleReference(text: string): boolean {\n return /\\bfrom\\s+['\"]react['\"]|\\bimport\\s+['\"]react['\"]|\\brequire\\(\\s*['\"]react['\"]\\s*\\)/u.test(text);\n}\n\nasync function hasReactRenderableReturnType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const signatures = await checker.getSignaturesOfType(type, SignatureKind.Call);\n for (const signature of signatures) {\n const returnType = await checker.getReturnTypeOfSignature(signature);\n if (returnType && (await isReactRenderableType(returnType, node, checker))) {\n return true;\n }\n }\n return false;\n}\n\nasync function hasReactComponentTypeName(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\\b/u.test(\n typeName\n );\n}\n\nasync function isReactRenderableType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:JSX\\.Element|React\\.JSX\\.Element|ReactElement|ReactNode)\\b/u.test(typeName);\n}\n\nfunction isFunctionLikeCandidate(node: Node): boolean {\n if (node.kind === SyntaxKind.FunctionDeclaration && !getNodeProperty(node, 'body')) {\n return false;\n }\n\n return (\n node.kind === SyntaxKind.FunctionDeclaration ||\n node.kind === SyntaxKind.FunctionExpression ||\n node.kind === SyntaxKind.ArrowFunction\n );\n}\n\nfunction isVariableFunctionCandidate(node: Node): boolean {\n if (node.kind !== SyntaxKind.VariableDeclaration) {\n return false;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n return unwrappedInitializer\n ? isFunctionLikeCandidate(unwrappedInitializer) || unwrappedInitializer.kind === SyntaxKind.CallExpression\n : false;\n}\n\nfunction findComponentFunctionNode(node: Node): Node {\n if (isFunctionLikeCandidate(node)) {\n return node;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n if (!unwrappedInitializer) {\n return node;\n }\n if (isFunctionLikeCandidate(unwrappedInitializer)) {\n return unwrappedInitializer;\n }\n if (isComponentImplementationWrapperCall(unwrappedInitializer)) {\n return findComponentImplementationWrapperFunction(unwrappedInitializer) ?? node;\n }\n return node;\n}\n\nfunction isComponentImplementationWrapperCall(node: Node): boolean {\n if (node.kind !== SyntaxKind.CallExpression) {\n return false;\n }\n\n const expression = getNodeProperty(node, 'expression');\n const expressionName = expression ? findCallExpressionName(expression) : undefined;\n return expressionName === 'memo' || expressionName === 'forwardRef';\n}\n\nfunction findCallExpressionName(expression: Node): string | undefined {\n const name = getNodeProperty(expression, 'name');\n return name ? findCandidateName(name) : findCandidateName(expression);\n}\n\nfunction findComponentImplementationWrapperFunction(node: Node): Node | undefined {\n let functionNode: Node | undefined;\n node.forEachChild((child) => {\n const unwrappedChild = unwrapExpression(child);\n if (isFunctionLikeCandidate(unwrappedChild)) {\n functionNode = unwrappedChild;\n } else if (isComponentImplementationWrapperCall(unwrappedChild)) {\n functionNode = findComponentImplementationWrapperFunction(unwrappedChild);\n }\n return functionNode;\n });\n return functionNode;\n}\n\nfunction findFunctionStartPosition(\n text: string,\n functionNode: Node,\n name: Node | undefined,\n sourceFile: SourceFile\n): number {\n const tokenPosition = getTokenPosOfNode(functionNode, sourceFile);\n if (functionNode.kind !== SyntaxKind.FunctionDeclaration || !name) {\n return tokenPosition;\n }\n\n const functionName = findCandidateName(name);\n const namePosition = functionName ? text.indexOf(functionName, tokenPosition) : -1;\n const functionPosition = namePosition >= 0 ? text.lastIndexOf('function', namePosition) : -1;\n if (functionPosition < tokenPosition) {\n return tokenPosition;\n }\n\n const modifierText = text.slice(tokenPosition, functionPosition);\n const asyncMatch = /\\basync\\s*$/u.exec(modifierText);\n return asyncMatch ? tokenPosition + asyncMatch.index : functionPosition;\n}\n\nfunction dedupeReactComponentFunctions(\n components: readonly ReactComponentCandidateMetric[]\n): Omit<ReactComponentFunctionMetric, 'file'>[] {\n const componentByKey = new Map<string, ReactComponentCandidateMetric>();\n for (const component of components) {\n const existingComponent = componentByKey.get(component.implementationKey);\n if (!existingComponent || (!existingComponent.name && component.name)) {\n componentByKey.set(component.implementationKey, component);\n }\n }\n return [...componentByKey.values()].map((component) => ({\n name: component.name,\n startColumn: component.startColumn,\n startLine: component.startLine,\n }));\n}\n\nfunction findCandidateName(name: Node): string | undefined {\n if ('escapedText' in name) {\n return String(name.escapedText);\n }\n if ('text' in name && typeof name.text === 'string') {\n return name.text;\n }\n return undefined;\n}\n\nfunction isUppercaseComponentName(name: string | undefined): boolean {\n return name !== undefined && /^[A-Z]/u.test(name);\n}\n\nfunction findNameNode(node: Node): Node | undefined {\n const name = getNodeProperty(node, 'name');\n if (name) {\n return name;\n }\n\n let currentNode: Node | undefined = node;\n while (currentNode) {\n const parent = getNodeProperty(currentNode, 'parent');\n if (!parent) {\n return undefined;\n }\n if (parent.kind === SyntaxKind.VariableDeclaration) {\n return getNodeProperty(parent, 'name');\n }\n if (!isExpressionWrapperNode(parent)) {\n return undefined;\n }\n currentNode = parent;\n }\n return undefined;\n}\n\nfunction unwrapExpression(node: Node): Node {\n let currentNode = node;\n while (isExpressionWrapperNode(currentNode)) {\n const expression = getNodeProperty(currentNode, 'expression');\n if (!expression) {\n return currentNode;\n }\n currentNode = expression;\n }\n return currentNode;\n}\n\nfunction isExpressionWrapperNode(node: Node): boolean {\n return (\n node.kind === SyntaxKind.ParenthesizedExpression ||\n node.kind === SyntaxKind.AsExpression ||\n node.kind === SyntaxKind.TypeAssertionExpression ||\n node.kind === SyntaxKind.SatisfiesExpression\n );\n}\n\nfunction getNodeProperty(\n node: Node,\n property: 'body' | 'expression' | 'initializer' | 'name' | 'parent'\n): Node | undefined {\n const value = (node as Partial<Record<typeof property, Node>>)[property];\n return isNode(value) ? value : undefined;\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && 'kind' in value && 'pos' in value && 'end' in value;\n}\n\nfunction positionToLineColumn(text: string, position: number): { column: number; line: number } {\n const lines = text.slice(0, position).split('\\n');\n return { column: lines.at(-1)?.length ?? 0, line: lines.length };\n}\n\nfunction collectCallExpressions(root: Node): Node[] {\n const calls: Node[] = [];\n visitNode(root, (node) => {\n if (node.kind === SyntaxKind.CallExpression) {\n calls.push(node);\n }\n });\n return calls;\n}\n\nfunction visitNode(node: Node, visitor: (node: Node) => void): void {\n visitor(node);\n node.forEachChild((child) => {\n visitNode(child, visitor);\n return;\n });\n}\n\nfunction addDiagnosticFiles(files: Set<string>, diagnostics: readonly DiagnosticLike[]): void {\n for (const diagnostic of diagnostics) {\n if (diagnostic.fileName) {\n files.add(diagnostic.fileName);\n }\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,3 @@
1
- import{realpath as n}from"node:fs/promises";import t from"node:path";import{API as e,SignatureKind as o}from"@typescript/native-preview/unstable/async";import{SyntaxKind as i,getTokenPosOfNode as a}from"@typescript/native-preview/unstable/ast";async function r(n,o=[]){const i=new e({cwd:process.cwd()});try{const e=(await i.updateSnapshot({openProject:n})).getProjects(),a=await async function(n){const t=new Map;for(const e of n)t.set(await c(e),e);return t}(o),r=new Set(a.keys()),l=new Set,f={callExpressionCount:0,configDiagnosticCount:0,declarationDiagnosticCount:0,measuredRootFileCount:0,resolvedCallExpressionCount:0,rootFileCount:0,semanticDiagnosticCount:0,suggestionDiagnosticCount:0,syntacticDiagnosticCount:0},p=[],g=new Set;for(const n of e){const e=await Promise.all(n.rootFiles.map(async n=>({canonicalFile:await c(n),file:n})));f.rootFileCount+=e.length,f.measuredRootFileCount+=e.filter(({canonicalFile:n})=>r.has(n)).length;const o=await n.program.getSyntacticDiagnostics(),i=await n.program.getSemanticDiagnostics(),d=await n.program.getSuggestionDiagnostics(),m=await n.program.getDeclarationDiagnostics(),C=await n.program.getConfigFileParsingDiagnostics();f.syntacticDiagnosticCount+=o.length,f.semanticDiagnosticCount+=i.length,f.suggestionDiagnosticCount+=d.length,f.declarationDiagnosticCount+=m.length,f.configDiagnosticCount+=C.length,b(l,[...o,...i,...d,...m,...C]);const w=0===r.size?e:[...a.entries()].map(([n,t])=>({canonicalFile:n,file:t})),x=t.dirname(n.configFileName),y=await c(x);for(const{canonicalFile:t,file:e}of w){if(g.has(t))continue;const o=await s(n,e,t,x,y);if(!o)continue;g.add(t);const i=k(o);f.callExpressionCount+=i.length;const r=await u(o,n.checker);p.push(...r.map(n=>({...n,file:a.get(t)??e})));for(const t of i)await n.checker.getResolvedSignature(t)&&(f.resolvedCallExpressionCount+=1)}}const d=f.callExpressionCount-f.resolvedCallExpressionCount;return{...f,configFile:n,diagnosticFileCount:l.size,projectCount:e.length,reactComponentFunctions:p,unresolvedCallExpressionCount:d,resolvedCallExpressionRatio:0===f.callExpressionCount?0:f.resolvedCallExpressionCount/f.callExpressionCount}}finally{await i.close()}}async function s(n,e,o,i,a){for(const r of function(n,e,o,i){const a=new Set([n,e]);e.startsWith(`${i}${t.sep}`)&&a.add(t.join(o,t.relative(i,e)));return[...a]}(e,o,i,a)){const t=await n.program.getSourceFile(r);if(t)return t}}async function c(t){try{return await n(t)}catch{return t}}async function u(n,t){const e=[],o=function(n){const t=[];return S(n,n=>{(d(n)||function(n){if(n.kind!==i.VariableDeclaration)return!1;const t=F(n,"initializer"),e=t?E(t):void 0;return!!e&&(d(e)||e.kind===i.CallExpression)}(n))&&t.push(n)}),t}(n),a=(r=n.text,/\bfrom\s+['"]react['"]|\bimport\s+['"]react['"]|\brequire\(\s*['"]react['"]\s*\)/u.test(r));var r;for(const i of o){if(!await l(i,t,a))continue;const o=h(i),r=m(i),s=x(n.text,r,o,n),c=D(n.text,s);e.push({implementationKey:`${r.pos}:${r.end}`,name:o?y(o):void 0,startColumn:c.column,startLine:c.line})}return function(n){const t=new Map;for(const e of n){const n=t.get(e.implementationKey);(!n||!n.name&&e.name)&&t.set(e.implementationKey,e)}return[...t.values()].map(n=>({name:n.name,startColumn:n.startColumn,startLine:n.startLine}))}(e)}async function l(n,t,e){const o=await t.getTypeAtLocation(n);if(!o)return!1;const i=h(n),a=i?y(i):void 0;if(e&&await p(o,n,t))return!0;const r=i?await t.getTypeAtLocation(i):void 0;return!!(e&&r&&await p(r,n,t))||!!function(n){return void 0!==n&&/^[A-Z]/u.test(n)}(a)&&(await f(o,n,t)||Boolean(r&&await f(r,n,t)))}async function f(n,t,e){const i=await e.getSignaturesOfType(n,o.Call);for(const n of i){const o=await e.getReturnTypeOfSignature(n);if(o&&await g(o,t,e))return!0}return!1}async function p(n,t,e){const o=await e.typeToString(n,t);return/\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\b/u.test(o)}async function g(n,t,e){const o=await e.typeToString(n,t);return/\b(?:JSX\.Element|React\.JSX\.Element|ReactElement|ReactNode)\b/u.test(o)}function d(n){return!(n.kind===i.FunctionDeclaration&&!F(n,"body"))&&(n.kind===i.FunctionDeclaration||n.kind===i.FunctionExpression||n.kind===i.ArrowFunction)}function m(n){if(d(n))return n;const t=F(n,"initializer"),e=t?E(t):void 0;return e?d(e)?e:C(e)?w(e)??n:n:n}function C(n){if(n.kind!==i.CallExpression)return!1;const t=F(n,"expression"),e=t?function(n){const t=F(n,"name");return y(t||n)}(t):void 0;return"memo"===e||"forwardRef"===e}function w(n){let t;return n.forEachChild(n=>{const e=E(n);return d(e)?t=e:C(e)&&(t=w(e)),t}),t}function x(n,t,e,o){const r=a(t,o);if(t.kind!==i.FunctionDeclaration||!e)return r;const s=y(e),c=s?n.indexOf(s,r):-1,u=c>=0?n.lastIndexOf("function",c):-1;if(u<r)return r;const l=n.slice(r,u),f=/\basync\s*$/u.exec(l);return f?r+f.index:u}function y(n){return"escapedText"in n?String(n.escapedText):"text"in n&&"string"==typeof n.text?n.text:void 0}function h(n){const t=F(n,"name");if(t)return t;let e=n;for(;e;){const n=F(e,"parent");if(!n)return;if(n.kind===i.VariableDeclaration)return F(n,"name");if(!v(n))return;e=n}}function E(n){let t=n;for(;v(t);){const n=F(t,"expression");if(!n)return t;t=n}return t}function v(n){return n.kind===i.ParenthesizedExpression||n.kind===i.AsExpression||n.kind===i.TypeAssertionExpression||n.kind===i.SatisfiesExpression}function F(n,t){const e=n[t];return function(n){return"object"==typeof n&&null!==n&&"kind"in n&&"pos"in n&&"end"in n}(e)?e:void 0}function D(n,t){const e=n.slice(0,t).split("\n");return{column:e.at(-1)?.length??0,line:e.length}}function k(n){const t=[];return S(n,n=>{n.kind===i.CallExpression&&t.push(n)}),t}function S(n,t){t(n),n.forEachChild(n=>{S(n,t)})}function b(n,t){for(const e of t)e.fileName&&n.add(e.fileName)}export{r as measureTypeScriptProject};
2
- //# sourceMappingURL=typescriptProject.js.map
1
+ import{realpath as e}from"node:fs/promises";import t from"node:path";import{API as n,SignatureKind as r}from"typescript/unstable/async";import{SyntaxKind as i,getTokenPosOfNode as a}from"typescript/unstable/ast";async function o(e,r=[]){let i=new n({cwd:process.cwd()});try{let n=(await i.updateSnapshot({openProject:e})).getProjects(),a=await l(r),o=new Set(a.keys()),c=new Set,f={callExpressionCount:0,configDiagnosticCount:0,declarationDiagnosticCount:0,measuredRootFileCount:0,resolvedCallExpressionCount:0,rootFileCount:0,semanticDiagnosticCount:0,suggestionDiagnosticCount:0,syntacticDiagnosticCount:0},p=[],m=new Set;for(let e of n){let n=await Promise.all(e.rootFiles.map(async e=>({canonicalFile:await u(e),file:e})));f.rootFileCount+=n.length,f.measuredRootFileCount+=n.filter(({canonicalFile:e})=>o.has(e)).length;let r=await e.program.getSyntacticDiagnostics(),i=await e.program.getSemanticDiagnostics(),l=await e.program.getSuggestionDiagnostics(),h=await e.program.getDeclarationDiagnostics(),g=await e.program.getConfigFileParsingDiagnostics();f.syntacticDiagnosticCount+=r.length,f.semanticDiagnosticCount+=i.length,f.suggestionDiagnosticCount+=l.length,f.declarationDiagnosticCount+=h.length,f.configDiagnosticCount+=g.length,I(c,[...r,...i,...l,...h,...g]);let _=o.size===0?n:[...a.entries()].map(([e,t])=>({canonicalFile:e,file:t})),v=t.dirname(e.configFileName),y=await u(v);for(let{canonicalFile:t,file:n}of _){if(m.has(t))continue;let r=await s(e,n,t,v,y);if(!r)continue;m.add(t);let i=P(r);f.callExpressionCount+=i.length;let o=await d(r,e.checker);p.push(...o.map(e=>({...e,file:a.get(t)??n})));for(let t of i)await e.checker.getResolvedSignature(t)&&(f.resolvedCallExpressionCount+=1)}}let h=f.callExpressionCount-f.resolvedCallExpressionCount;return{...f,configFile:e,diagnosticFileCount:c.size,projectCount:n.length,reactComponentFunctions:p,unresolvedCallExpressionCount:h,resolvedCallExpressionRatio:f.callExpressionCount===0?0:f.resolvedCallExpressionCount/f.callExpressionCount}}finally{await i.close()}}async function s(e,t,n,r,i){for(let a of c(t,n,r,i)){let t=await e.program.getSourceFile(a);if(t)return t}}function c(e,n,r,i){let a=new Set([e,n]);return n.startsWith(`${i}${t.sep}`)&&a.add(t.join(r,t.relative(i,n))),[...a]}async function l(e){let t=new Map;for(let n of e)t.set(await u(n),n);return t}async function u(t){try{return await e(t)}catch{return t}}async function d(e,t){let n=[],r=f(e),i=m(e.text);for(let a of r){if(!await p(a,t,i))continue;let r=O(a),o=b(a),s=w(e.text,o,r,e),c=N(e.text,s);n.push({implementationKey:`${o.pos}:${o.end}`,name:r?E(r):void 0,startColumn:c.column,startLine:c.line})}return T(n)}function f(e){let t=[];return F(e,e=>{(v(e)||y(e))&&t.push(e)}),t}async function p(e,t,n){let r=await t.getTypeAtLocation(e);if(!r)return!1;let i=O(e),a=i?E(i):void 0;if(n&&await g(r,e,t))return!0;let o=i?await t.getTypeAtLocation(i):void 0;return n&&o&&await g(o,e,t)?!0:D(a)?await h(r,e,t)||!!(o&&await h(o,e,t)):!1}function m(e){return/\bfrom\s+['"]react['"]|\bimport\s+['"]react['"]|\brequire\(\s*['"]react['"]\s*\)/u.test(e)}async function h(e,t,n){let i=await n.getSignaturesOfType(e,r.Call);for(let e of i){let r=await n.getReturnTypeOfSignature(e);if(r&&await _(r,t,n))return!0}return!1}async function g(e,t,n){let r=await n.typeToString(e,t);return/\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\b/u.test(r)}async function _(e,t,n){let r=await n.typeToString(e,t);return/\b(?:JSX\.Element|React\.JSX\.Element|ReactElement|ReactNode)\b/u.test(r)}function v(e){return e.kind===i.FunctionDeclaration&&!j(e,`body`)?!1:e.kind===i.FunctionDeclaration||e.kind===i.FunctionExpression||e.kind===i.ArrowFunction}function y(e){if(e.kind!==i.VariableDeclaration)return!1;let t=j(e,`initializer`),n=t?k(t):void 0;return n?v(n)||n.kind===i.CallExpression:!1}function b(e){if(v(e))return e;let t=j(e,`initializer`),n=t?k(t):void 0;return n?v(n)?n:x(n)?C(n)??e:e:e}function x(e){if(e.kind!==i.CallExpression)return!1;let t=j(e,`expression`),n=t?S(t):void 0;return n===`memo`||n===`forwardRef`}function S(e){return E(j(e,`name`)||e)}function C(e){let t;return e.forEachChild(e=>{let n=k(e);return v(n)?t=n:x(n)&&(t=C(n)),t}),t}function w(e,t,n,r){let o=a(t,r);if(t.kind!==i.FunctionDeclaration||!n)return o;let s=E(n),c=s?e.indexOf(s,o):-1,l=c>=0?e.lastIndexOf(`function`,c):-1;if(l<o)return o;let u=e.slice(o,l),d=/\basync\s*$/u.exec(u);return d?o+d.index:l}function T(e){let t=new Map;for(let n of e){let e=t.get(n.implementationKey);(!e||!e.name&&n.name)&&t.set(n.implementationKey,n)}return[...t.values()].map(e=>({name:e.name,startColumn:e.startColumn,startLine:e.startLine}))}function E(e){if(`escapedText`in e)return String(e.escapedText);if(`text`in e&&typeof e.text==`string`)return e.text}function D(e){return e!==void 0&&/^[A-Z]/u.test(e)}function O(e){let t=j(e,`name`);if(t)return t;let n=e;for(;n;){let e=j(n,`parent`);if(!e)return;if(e.kind===i.VariableDeclaration)return j(e,`name`);if(!A(e))return;n=e}}function k(e){let t=e;for(;A(t);){let e=j(t,`expression`);if(!e)return t;t=e}return t}function A(e){return e.kind===i.ParenthesizedExpression||e.kind===i.AsExpression||e.kind===i.TypeAssertionExpression||e.kind===i.SatisfiesExpression}function j(e,t){let n=e[t];return M(n)?n:void 0}function M(e){return typeof e==`object`&&!!e&&`kind`in e&&`pos`in e&&`end`in e}function N(e,t){let n=e.slice(0,t).split(`
2
+ `);return{column:n.at(-1)?.length??0,line:n.length}}function P(e){let t=[];return F(e,e=>{e.kind===i.CallExpression&&t.push(e)}),t}function F(e,t){t(e),e.forEachChild(e=>{F(e,t)})}function I(e,t){for(let n of t)n.fileName&&e.add(n.fileName)}export{o as measureTypeScriptProject};
3
+ //# sourceMappingURL=typescriptProject.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"typescriptProject.js","sources":["../src/typescriptProject.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { API, SignatureKind, type Checker, type Type } from '@typescript/native-preview/unstable/async';\nimport { getTokenPosOfNode, SyntaxKind, type Node, type SourceFile } from '@typescript/native-preview/unstable/ast';\n\nexport interface ReactComponentFunctionMetric {\n file: string;\n name?: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface TypeScriptProjectMetrics {\n callExpressionCount: number;\n configDiagnosticCount: number;\n configFile: string;\n declarationDiagnosticCount: number;\n diagnosticFileCount: number;\n measuredRootFileCount: number;\n projectCount: number;\n reactComponentFunctions: ReactComponentFunctionMetric[];\n resolvedCallExpressionCount: number;\n resolvedCallExpressionRatio: number;\n rootFileCount: number;\n semanticDiagnosticCount: number;\n suggestionDiagnosticCount: number;\n syntacticDiagnosticCount: number;\n unresolvedCallExpressionCount: number;\n}\n\ninterface DiagnosticLike {\n fileName?: string;\n}\n\ninterface ReactComponentCandidateMetric extends Omit<ReactComponentFunctionMetric, 'file'> {\n implementationKey: string;\n}\n\nexport async function measureTypeScriptProject(\n configFile: string,\n measuredFiles: readonly string[] = []\n): Promise<TypeScriptProjectMetrics> {\n const api = new API({ cwd: process.cwd() });\n try {\n const snapshot = await api.updateSnapshot({ openProject: configFile });\n const projects = snapshot.getProjects();\n const measuredFileByCanonicalFile = await mapMeasuredFilesByCanonicalFile(measuredFiles);\n const measuredFileSet = new Set(measuredFileByCanonicalFile.keys());\n const diagnosticFiles = new Set<string>();\n const totals = {\n callExpressionCount: 0,\n configDiagnosticCount: 0,\n declarationDiagnosticCount: 0,\n measuredRootFileCount: 0,\n resolvedCallExpressionCount: 0,\n rootFileCount: 0,\n semanticDiagnosticCount: 0,\n suggestionDiagnosticCount: 0,\n syntacticDiagnosticCount: 0,\n };\n const reactComponentFunctions: ReactComponentFunctionMetric[] = [];\n const analyzedFiles = new Set<string>();\n\n for (const project of projects) {\n const projectRootFiles = await Promise.all(\n project.rootFiles.map(async (file) => ({ canonicalFile: await canonicalizeFile(file), file }))\n );\n totals.rootFileCount += projectRootFiles.length;\n totals.measuredRootFileCount += projectRootFiles.filter(({ canonicalFile }) =>\n measuredFileSet.has(canonicalFile)\n ).length;\n\n const syntacticDiagnostics = await project.program.getSyntacticDiagnostics();\n const semanticDiagnostics = await project.program.getSemanticDiagnostics();\n const suggestionDiagnostics = await project.program.getSuggestionDiagnostics();\n const declarationDiagnostics = await project.program.getDeclarationDiagnostics();\n const configDiagnostics = await project.program.getConfigFileParsingDiagnostics();\n totals.syntacticDiagnosticCount += syntacticDiagnostics.length;\n totals.semanticDiagnosticCount += semanticDiagnostics.length;\n totals.suggestionDiagnosticCount += suggestionDiagnostics.length;\n totals.declarationDiagnosticCount += declarationDiagnostics.length;\n totals.configDiagnosticCount += configDiagnostics.length;\n addDiagnosticFiles(diagnosticFiles, [\n ...syntacticDiagnostics,\n ...semanticDiagnostics,\n ...suggestionDiagnostics,\n ...declarationDiagnostics,\n ...configDiagnostics,\n ]);\n\n const projectAnalysisFiles =\n measuredFileSet.size === 0\n ? projectRootFiles\n : [...measuredFileByCanonicalFile.entries()].map(([canonicalFile, file]) => ({ canonicalFile, file }));\n const configDirectory = path.dirname(project.configFileName);\n const canonicalConfigDirectory = await canonicalizeFile(configDirectory);\n for (const { canonicalFile, file } of projectAnalysisFiles) {\n if (analyzedFiles.has(canonicalFile)) {\n continue;\n }\n const sourceFile = await getProjectSourceFile(\n project,\n file,\n canonicalFile,\n configDirectory,\n canonicalConfigDirectory\n );\n if (!sourceFile) {\n continue;\n }\n analyzedFiles.add(canonicalFile);\n const callExpressions = collectCallExpressions(sourceFile);\n totals.callExpressionCount += callExpressions.length;\n const fileReactComponentFunctions = await collectReactComponentFunctions(sourceFile, project.checker);\n reactComponentFunctions.push(\n ...fileReactComponentFunctions.map((component) => ({\n ...component,\n file: measuredFileByCanonicalFile.get(canonicalFile) ?? file,\n }))\n );\n for (const callExpression of callExpressions) {\n if (await project.checker.getResolvedSignature(callExpression)) {\n totals.resolvedCallExpressionCount += 1;\n }\n }\n }\n }\n\n const unresolvedCallExpressionCount = totals.callExpressionCount - totals.resolvedCallExpressionCount;\n return {\n ...totals,\n configFile,\n diagnosticFileCount: diagnosticFiles.size,\n projectCount: projects.length,\n reactComponentFunctions,\n unresolvedCallExpressionCount,\n resolvedCallExpressionRatio:\n totals.callExpressionCount === 0 ? 0 : totals.resolvedCallExpressionCount / totals.callExpressionCount,\n };\n } finally {\n await api.close();\n }\n}\n\nasync function getProjectSourceFile(\n project: { configFileName: string; program: { getSourceFile: (file: string) => Promise<SourceFile | undefined> } },\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): Promise<SourceFile | undefined> {\n for (const candidate of getProjectFileCandidates(file, canonicalFile, configDirectory, canonicalConfigDirectory)) {\n const sourceFile = await project.program.getSourceFile(candidate);\n if (sourceFile) {\n return sourceFile;\n }\n }\n return undefined;\n}\n\nfunction getProjectFileCandidates(\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): string[] {\n const candidates = new Set([file, canonicalFile]);\n if (canonicalFile.startsWith(`${canonicalConfigDirectory}${path.sep}`)) {\n candidates.add(path.join(configDirectory, path.relative(canonicalConfigDirectory, canonicalFile)));\n }\n return [...candidates];\n}\n\nasync function mapMeasuredFilesByCanonicalFile(files: readonly string[]): Promise<Map<string, string>> {\n const measuredFileByCanonicalFile = new Map<string, string>();\n for (const file of files) {\n measuredFileByCanonicalFile.set(await canonicalizeFile(file), file);\n }\n return measuredFileByCanonicalFile;\n}\n\nasync function canonicalizeFile(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch {\n return file;\n }\n}\n\nasync function collectReactComponentFunctions(\n sourceFile: SourceFile,\n checker: Checker\n): Promise<Omit<ReactComponentFunctionMetric, 'file'>[]> {\n const components: ReactComponentCandidateMetric[] = [];\n const candidates = collectReactComponentCandidates(sourceFile);\n const hasReactReference = containsReactModuleReference(sourceFile.text);\n for (const candidate of candidates) {\n if (!(await isReactComponentCandidate(candidate, checker, hasReactReference))) {\n continue;\n }\n const name = findNameNode(candidate);\n const functionNode = findComponentFunctionNode(candidate);\n const startOffset = findFunctionStartPosition(sourceFile.text, functionNode, name, sourceFile);\n const startPosition = positionToLineColumn(sourceFile.text, startOffset);\n components.push({\n implementationKey: `${functionNode.pos}:${functionNode.end}`,\n name: name ? findCandidateName(name) : undefined,\n startColumn: startPosition.column,\n startLine: startPosition.line,\n });\n }\n return dedupeReactComponentFunctions(components);\n}\n\nfunction collectReactComponentCandidates(root: Node): Node[] {\n const candidates: Node[] = [];\n visitNode(root, (node) => {\n if (isFunctionLikeCandidate(node) || isVariableFunctionCandidate(node)) {\n candidates.push(node);\n }\n });\n return candidates;\n}\n\nasync function isReactComponentCandidate(node: Node, checker: Checker, hasReactReference: boolean): Promise<boolean> {\n const type = await checker.getTypeAtLocation(node);\n if (!type) {\n return false;\n }\n\n const name = findNameNode(node);\n const componentName = name ? findCandidateName(name) : undefined;\n if (hasReactReference && (await hasReactComponentTypeName(type, node, checker))) {\n return true;\n }\n\n const namedType = name ? await checker.getTypeAtLocation(name) : undefined;\n if (hasReactReference && namedType && (await hasReactComponentTypeName(namedType, node, checker))) {\n return true;\n }\n\n if (!isUppercaseComponentName(componentName)) {\n return false;\n }\n\n return (\n (await hasReactRenderableReturnType(type, node, checker)) ||\n Boolean(namedType && (await hasReactRenderableReturnType(namedType, node, checker)))\n );\n}\n\nfunction containsReactModuleReference(text: string): boolean {\n return /\\bfrom\\s+['\"]react['\"]|\\bimport\\s+['\"]react['\"]|\\brequire\\(\\s*['\"]react['\"]\\s*\\)/u.test(text);\n}\n\nasync function hasReactRenderableReturnType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const signatures = await checker.getSignaturesOfType(type, SignatureKind.Call);\n for (const signature of signatures) {\n const returnType = await checker.getReturnTypeOfSignature(signature);\n if (returnType && (await isReactRenderableType(returnType, node, checker))) {\n return true;\n }\n }\n return false;\n}\n\nasync function hasReactComponentTypeName(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\\b/u.test(\n typeName\n );\n}\n\nasync function isReactRenderableType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:JSX\\.Element|React\\.JSX\\.Element|ReactElement|ReactNode)\\b/u.test(typeName);\n}\n\nfunction isFunctionLikeCandidate(node: Node): boolean {\n if (node.kind === SyntaxKind.FunctionDeclaration && !getNodeProperty(node, 'body')) {\n return false;\n }\n\n return (\n node.kind === SyntaxKind.FunctionDeclaration ||\n node.kind === SyntaxKind.FunctionExpression ||\n node.kind === SyntaxKind.ArrowFunction\n );\n}\n\nfunction isVariableFunctionCandidate(node: Node): boolean {\n if (node.kind !== SyntaxKind.VariableDeclaration) {\n return false;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n return unwrappedInitializer\n ? isFunctionLikeCandidate(unwrappedInitializer) || unwrappedInitializer.kind === SyntaxKind.CallExpression\n : false;\n}\n\nfunction findComponentFunctionNode(node: Node): Node {\n if (isFunctionLikeCandidate(node)) {\n return node;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n if (!unwrappedInitializer) {\n return node;\n }\n if (isFunctionLikeCandidate(unwrappedInitializer)) {\n return unwrappedInitializer;\n }\n if (isComponentImplementationWrapperCall(unwrappedInitializer)) {\n return findComponentImplementationWrapperFunction(unwrappedInitializer) ?? node;\n }\n return node;\n}\n\nfunction isComponentImplementationWrapperCall(node: Node): boolean {\n if (node.kind !== SyntaxKind.CallExpression) {\n return false;\n }\n\n const expression = getNodeProperty(node, 'expression');\n const expressionName = expression ? findCallExpressionName(expression) : undefined;\n return expressionName === 'memo' || expressionName === 'forwardRef';\n}\n\nfunction findCallExpressionName(expression: Node): string | undefined {\n const name = getNodeProperty(expression, 'name');\n return name ? findCandidateName(name) : findCandidateName(expression);\n}\n\nfunction findComponentImplementationWrapperFunction(node: Node): Node | undefined {\n let functionNode: Node | undefined;\n node.forEachChild((child) => {\n const unwrappedChild = unwrapExpression(child);\n if (isFunctionLikeCandidate(unwrappedChild)) {\n functionNode = unwrappedChild;\n } else if (isComponentImplementationWrapperCall(unwrappedChild)) {\n functionNode = findComponentImplementationWrapperFunction(unwrappedChild);\n }\n return functionNode;\n });\n return functionNode;\n}\n\nfunction findFunctionStartPosition(\n text: string,\n functionNode: Node,\n name: Node | undefined,\n sourceFile: SourceFile\n): number {\n const tokenPosition = getTokenPosOfNode(functionNode, sourceFile);\n if (functionNode.kind !== SyntaxKind.FunctionDeclaration || !name) {\n return tokenPosition;\n }\n\n const functionName = findCandidateName(name);\n const namePosition = functionName ? text.indexOf(functionName, tokenPosition) : -1;\n const functionPosition = namePosition >= 0 ? text.lastIndexOf('function', namePosition) : -1;\n if (functionPosition < tokenPosition) {\n return tokenPosition;\n }\n\n const modifierText = text.slice(tokenPosition, functionPosition);\n const asyncMatch = /\\basync\\s*$/u.exec(modifierText);\n return asyncMatch ? tokenPosition + asyncMatch.index : functionPosition;\n}\n\nfunction dedupeReactComponentFunctions(\n components: readonly ReactComponentCandidateMetric[]\n): Omit<ReactComponentFunctionMetric, 'file'>[] {\n const componentByKey = new Map<string, ReactComponentCandidateMetric>();\n for (const component of components) {\n const existingComponent = componentByKey.get(component.implementationKey);\n if (!existingComponent || (!existingComponent.name && component.name)) {\n componentByKey.set(component.implementationKey, component);\n }\n }\n return [...componentByKey.values()].map((component) => ({\n name: component.name,\n startColumn: component.startColumn,\n startLine: component.startLine,\n }));\n}\n\nfunction findCandidateName(name: Node): string | undefined {\n if ('escapedText' in name) {\n return String(name.escapedText);\n }\n if ('text' in name && typeof name.text === 'string') {\n return name.text;\n }\n return undefined;\n}\n\nfunction isUppercaseComponentName(name: string | undefined): boolean {\n return name !== undefined && /^[A-Z]/u.test(name);\n}\n\nfunction findNameNode(node: Node): Node | undefined {\n const name = getNodeProperty(node, 'name');\n if (name) {\n return name;\n }\n\n let currentNode: Node | undefined = node;\n while (currentNode) {\n const parent = getNodeProperty(currentNode, 'parent');\n if (!parent) {\n return undefined;\n }\n if (parent.kind === SyntaxKind.VariableDeclaration) {\n return getNodeProperty(parent, 'name');\n }\n if (!isExpressionWrapperNode(parent)) {\n return undefined;\n }\n currentNode = parent;\n }\n return undefined;\n}\n\nfunction unwrapExpression(node: Node): Node {\n let currentNode = node;\n while (isExpressionWrapperNode(currentNode)) {\n const expression = getNodeProperty(currentNode, 'expression');\n if (!expression) {\n return currentNode;\n }\n currentNode = expression;\n }\n return currentNode;\n}\n\nfunction isExpressionWrapperNode(node: Node): boolean {\n return (\n node.kind === SyntaxKind.ParenthesizedExpression ||\n node.kind === SyntaxKind.AsExpression ||\n node.kind === SyntaxKind.TypeAssertionExpression ||\n node.kind === SyntaxKind.SatisfiesExpression\n );\n}\n\nfunction getNodeProperty(\n node: Node,\n property: 'body' | 'expression' | 'initializer' | 'name' | 'parent'\n): Node | undefined {\n const value = (node as Partial<Record<typeof property, Node>>)[property];\n return isNode(value) ? value : undefined;\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && 'kind' in value && 'pos' in value && 'end' in value;\n}\n\nfunction positionToLineColumn(text: string, position: number): { column: number; line: number } {\n const lines = text.slice(0, position).split('\\n');\n return { column: lines.at(-1)?.length ?? 0, line: lines.length };\n}\n\nfunction collectCallExpressions(root: Node): Node[] {\n const calls: Node[] = [];\n visitNode(root, (node) => {\n if (node.kind === SyntaxKind.CallExpression) {\n calls.push(node);\n }\n });\n return calls;\n}\n\nfunction visitNode(node: Node, visitor: (node: Node) => void): void {\n visitor(node);\n node.forEachChild((child) => {\n visitNode(child, visitor);\n return;\n });\n}\n\nfunction addDiagnosticFiles(files: Set<string>, diagnostics: readonly DiagnosticLike[]): void {\n for (const diagnostic of diagnostics) {\n if (diagnostic.fileName) {\n files.add(diagnostic.fileName);\n }\n }\n}\n"],"names":["async","measureTypeScriptProject","configFile","measuredFiles","api","API","cwd","process","projects","updateSnapshot","openProject","getProjects","measuredFileByCanonicalFile","files","Map","file","set","canonicalizeFile","mapMeasuredFilesByCanonicalFile","measuredFileSet","Set","keys","diagnosticFiles","totals","callExpressionCount","configDiagnosticCount","declarationDiagnosticCount","measuredRootFileCount","resolvedCallExpressionCount","rootFileCount","semanticDiagnosticCount","suggestionDiagnosticCount","syntacticDiagnosticCount","reactComponentFunctions","analyzedFiles","project","projectRootFiles","Promise","all","rootFiles","map","canonicalFile","length","filter","has","syntacticDiagnostics","program","getSyntacticDiagnostics","semanticDiagnostics","getSemanticDiagnostics","suggestionDiagnostics","getSuggestionDiagnostics","declarationDiagnostics","getDeclarationDiagnostics","configDiagnostics","getConfigFileParsingDiagnostics","addDiagnosticFiles","projectAnalysisFiles","size","entries","configDirectory","path","dirname","configFileName","canonicalConfigDirectory","sourceFile","getProjectSourceFile","add","callExpressions","collectCallExpressions","fileReactComponentFunctions","collectReactComponentFunctions","checker","push","component","get","callExpression","getResolvedSignature","unresolvedCallExpressionCount","diagnosticFileCount","projectCount","resolvedCallExpressionRatio","close","candidate","candidates","startsWith","sep","join","relative","getProjectFileCandidates","getSourceFile","realpath","components","root","visitNode","node","isFunctionLikeCandidate","kind","SyntaxKind","VariableDeclaration","initializer","getNodeProperty","unwrappedInitializer","unwrapExpression","undefined","CallExpression","isVariableFunctionCandidate","collectReactComponentCandidates","hasReactReference","text","test","isReactComponentCandidate","name","findNameNode","functionNode","findComponentFunctionNode","startOffset","findFunctionStartPosition","startPosition","positionToLineColumn","implementationKey","pos","end","findCandidateName","startColumn","column","startLine","line","componentByKey","existingComponent","values","dedupeReactComponentFunctions","type","getTypeAtLocation","componentName","hasReactComponentTypeName","namedType","isUppercaseComponentName","hasReactRenderableReturnType","Boolean","signatures","getSignaturesOfType","SignatureKind","Call","signature","returnType","getReturnTypeOfSignature","isReactRenderableType","typeName","typeToString","FunctionDeclaration","FunctionExpression","ArrowFunction","isComponentImplementationWrapperCall","findComponentImplementationWrapperFunction","expression","expressionName","findCallExpressionName","forEachChild","child","unwrappedChild","tokenPosition","getTokenPosOfNode","functionName","namePosition","indexOf","functionPosition","lastIndexOf","modifierText","slice","asyncMatch","exec","index","String","escapedText","currentNode","parent","isExpressionWrapperNode","ParenthesizedExpression","AsExpression","TypeAssertionExpression","SatisfiesExpression","property","value","isNode","position","lines","split","at","calls","visitor","diagnostics","diagnostic","fileName"],"mappings":"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"}
1
+ {"version":3,"file":"typescriptProject.js","names":[],"sources":["../src/typescriptProject.ts"],"sourcesContent":["import { realpath } from 'node:fs/promises';\nimport path from 'node:path';\nimport { API, SignatureKind, type Checker, type Type } from 'typescript/unstable/async';\nimport { getTokenPosOfNode, SyntaxKind, type Node, type SourceFile } from 'typescript/unstable/ast';\n\nexport interface ReactComponentFunctionMetric {\n file: string;\n name?: string;\n startLine: number;\n startColumn: number;\n}\n\nexport interface TypeScriptProjectMetrics {\n callExpressionCount: number;\n configDiagnosticCount: number;\n configFile: string;\n declarationDiagnosticCount: number;\n diagnosticFileCount: number;\n measuredRootFileCount: number;\n projectCount: number;\n reactComponentFunctions: ReactComponentFunctionMetric[];\n resolvedCallExpressionCount: number;\n resolvedCallExpressionRatio: number;\n rootFileCount: number;\n semanticDiagnosticCount: number;\n suggestionDiagnosticCount: number;\n syntacticDiagnosticCount: number;\n unresolvedCallExpressionCount: number;\n}\n\ninterface DiagnosticLike {\n fileName?: string;\n}\n\ninterface ReactComponentCandidateMetric extends Omit<ReactComponentFunctionMetric, 'file'> {\n implementationKey: string;\n}\n\nexport async function measureTypeScriptProject(\n configFile: string,\n measuredFiles: readonly string[] = []\n): Promise<TypeScriptProjectMetrics> {\n const api = new API({ cwd: process.cwd() });\n try {\n const snapshot = await api.updateSnapshot({ openProject: configFile });\n const projects = snapshot.getProjects();\n const measuredFileByCanonicalFile = await mapMeasuredFilesByCanonicalFile(measuredFiles);\n const measuredFileSet = new Set(measuredFileByCanonicalFile.keys());\n const diagnosticFiles = new Set<string>();\n const totals = {\n callExpressionCount: 0,\n configDiagnosticCount: 0,\n declarationDiagnosticCount: 0,\n measuredRootFileCount: 0,\n resolvedCallExpressionCount: 0,\n rootFileCount: 0,\n semanticDiagnosticCount: 0,\n suggestionDiagnosticCount: 0,\n syntacticDiagnosticCount: 0,\n };\n const reactComponentFunctions: ReactComponentFunctionMetric[] = [];\n const analyzedFiles = new Set<string>();\n\n for (const project of projects) {\n const projectRootFiles = await Promise.all(\n project.rootFiles.map(async (file) => ({ canonicalFile: await canonicalizeFile(file), file }))\n );\n totals.rootFileCount += projectRootFiles.length;\n totals.measuredRootFileCount += projectRootFiles.filter(({ canonicalFile }) =>\n measuredFileSet.has(canonicalFile)\n ).length;\n\n const syntacticDiagnostics = await project.program.getSyntacticDiagnostics();\n const semanticDiagnostics = await project.program.getSemanticDiagnostics();\n const suggestionDiagnostics = await project.program.getSuggestionDiagnostics();\n const declarationDiagnostics = await project.program.getDeclarationDiagnostics();\n const configDiagnostics = await project.program.getConfigFileParsingDiagnostics();\n totals.syntacticDiagnosticCount += syntacticDiagnostics.length;\n totals.semanticDiagnosticCount += semanticDiagnostics.length;\n totals.suggestionDiagnosticCount += suggestionDiagnostics.length;\n totals.declarationDiagnosticCount += declarationDiagnostics.length;\n totals.configDiagnosticCount += configDiagnostics.length;\n addDiagnosticFiles(diagnosticFiles, [\n ...syntacticDiagnostics,\n ...semanticDiagnostics,\n ...suggestionDiagnostics,\n ...declarationDiagnostics,\n ...configDiagnostics,\n ]);\n\n const projectAnalysisFiles =\n measuredFileSet.size === 0\n ? projectRootFiles\n : [...measuredFileByCanonicalFile.entries()].map(([canonicalFile, file]) => ({ canonicalFile, file }));\n const configDirectory = path.dirname(project.configFileName);\n const canonicalConfigDirectory = await canonicalizeFile(configDirectory);\n for (const { canonicalFile, file } of projectAnalysisFiles) {\n if (analyzedFiles.has(canonicalFile)) {\n continue;\n }\n const sourceFile = await getProjectSourceFile(\n project,\n file,\n canonicalFile,\n configDirectory,\n canonicalConfigDirectory\n );\n if (!sourceFile) {\n continue;\n }\n analyzedFiles.add(canonicalFile);\n const callExpressions = collectCallExpressions(sourceFile);\n totals.callExpressionCount += callExpressions.length;\n const fileReactComponentFunctions = await collectReactComponentFunctions(sourceFile, project.checker);\n reactComponentFunctions.push(\n ...fileReactComponentFunctions.map((component) => ({\n ...component,\n file: measuredFileByCanonicalFile.get(canonicalFile) ?? file,\n }))\n );\n for (const callExpression of callExpressions) {\n if (await project.checker.getResolvedSignature(callExpression)) {\n totals.resolvedCallExpressionCount += 1;\n }\n }\n }\n }\n\n const unresolvedCallExpressionCount = totals.callExpressionCount - totals.resolvedCallExpressionCount;\n return {\n ...totals,\n configFile,\n diagnosticFileCount: diagnosticFiles.size,\n projectCount: projects.length,\n reactComponentFunctions,\n unresolvedCallExpressionCount,\n resolvedCallExpressionRatio:\n totals.callExpressionCount === 0 ? 0 : totals.resolvedCallExpressionCount / totals.callExpressionCount,\n };\n } finally {\n await api.close();\n }\n}\n\nasync function getProjectSourceFile(\n project: { configFileName: string; program: { getSourceFile: (file: string) => Promise<SourceFile | undefined> } },\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): Promise<SourceFile | undefined> {\n for (const candidate of getProjectFileCandidates(file, canonicalFile, configDirectory, canonicalConfigDirectory)) {\n const sourceFile = await project.program.getSourceFile(candidate);\n if (sourceFile) {\n return sourceFile;\n }\n }\n return undefined;\n}\n\nfunction getProjectFileCandidates(\n file: string,\n canonicalFile: string,\n configDirectory: string,\n canonicalConfigDirectory: string\n): string[] {\n const candidates = new Set([file, canonicalFile]);\n if (canonicalFile.startsWith(`${canonicalConfigDirectory}${path.sep}`)) {\n candidates.add(path.join(configDirectory, path.relative(canonicalConfigDirectory, canonicalFile)));\n }\n return [...candidates];\n}\n\nasync function mapMeasuredFilesByCanonicalFile(files: readonly string[]): Promise<Map<string, string>> {\n const measuredFileByCanonicalFile = new Map<string, string>();\n for (const file of files) {\n measuredFileByCanonicalFile.set(await canonicalizeFile(file), file);\n }\n return measuredFileByCanonicalFile;\n}\n\nasync function canonicalizeFile(file: string): Promise<string> {\n try {\n return await realpath(file);\n } catch {\n return file;\n }\n}\n\nasync function collectReactComponentFunctions(\n sourceFile: SourceFile,\n checker: Checker\n): Promise<Omit<ReactComponentFunctionMetric, 'file'>[]> {\n const components: ReactComponentCandidateMetric[] = [];\n const candidates = collectReactComponentCandidates(sourceFile);\n const hasReactReference = containsReactModuleReference(sourceFile.text);\n for (const candidate of candidates) {\n if (!(await isReactComponentCandidate(candidate, checker, hasReactReference))) {\n continue;\n }\n const name = findNameNode(candidate);\n const functionNode = findComponentFunctionNode(candidate);\n const startOffset = findFunctionStartPosition(sourceFile.text, functionNode, name, sourceFile);\n const startPosition = positionToLineColumn(sourceFile.text, startOffset);\n components.push({\n implementationKey: `${functionNode.pos}:${functionNode.end}`,\n name: name ? findCandidateName(name) : undefined,\n startColumn: startPosition.column,\n startLine: startPosition.line,\n });\n }\n return dedupeReactComponentFunctions(components);\n}\n\nfunction collectReactComponentCandidates(root: Node): Node[] {\n const candidates: Node[] = [];\n visitNode(root, (node) => {\n if (isFunctionLikeCandidate(node) || isVariableFunctionCandidate(node)) {\n candidates.push(node);\n }\n });\n return candidates;\n}\n\nasync function isReactComponentCandidate(node: Node, checker: Checker, hasReactReference: boolean): Promise<boolean> {\n const type = await checker.getTypeAtLocation(node);\n if (!type) {\n return false;\n }\n\n const name = findNameNode(node);\n const componentName = name ? findCandidateName(name) : undefined;\n if (hasReactReference && (await hasReactComponentTypeName(type, node, checker))) {\n return true;\n }\n\n const namedType = name ? await checker.getTypeAtLocation(name) : undefined;\n if (hasReactReference && namedType && (await hasReactComponentTypeName(namedType, node, checker))) {\n return true;\n }\n\n if (!isUppercaseComponentName(componentName)) {\n return false;\n }\n\n return (\n (await hasReactRenderableReturnType(type, node, checker)) ||\n Boolean(namedType && (await hasReactRenderableReturnType(namedType, node, checker)))\n );\n}\n\nfunction containsReactModuleReference(text: string): boolean {\n return /\\bfrom\\s+['\"]react['\"]|\\bimport\\s+['\"]react['\"]|\\brequire\\(\\s*['\"]react['\"]\\s*\\)/u.test(text);\n}\n\nasync function hasReactRenderableReturnType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const signatures = await checker.getSignaturesOfType(type, SignatureKind.Call);\n for (const signature of signatures) {\n const returnType = await checker.getReturnTypeOfSignature(signature);\n if (returnType && (await isReactRenderableType(returnType, node, checker))) {\n return true;\n }\n }\n return false;\n}\n\nasync function hasReactComponentTypeName(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:FC|FunctionComponent|ComponentType|MemoExoticComponent|ForwardRefExoticComponent|LazyExoticComponent)\\b/u.test(\n typeName\n );\n}\n\nasync function isReactRenderableType(type: Type, node: Node, checker: Checker): Promise<boolean> {\n const typeName = await checker.typeToString(type, node);\n return /\\b(?:JSX\\.Element|React\\.JSX\\.Element|ReactElement|ReactNode)\\b/u.test(typeName);\n}\n\nfunction isFunctionLikeCandidate(node: Node): boolean {\n if (node.kind === SyntaxKind.FunctionDeclaration && !getNodeProperty(node, 'body')) {\n return false;\n }\n\n return (\n node.kind === SyntaxKind.FunctionDeclaration ||\n node.kind === SyntaxKind.FunctionExpression ||\n node.kind === SyntaxKind.ArrowFunction\n );\n}\n\nfunction isVariableFunctionCandidate(node: Node): boolean {\n if (node.kind !== SyntaxKind.VariableDeclaration) {\n return false;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n return unwrappedInitializer\n ? isFunctionLikeCandidate(unwrappedInitializer) || unwrappedInitializer.kind === SyntaxKind.CallExpression\n : false;\n}\n\nfunction findComponentFunctionNode(node: Node): Node {\n if (isFunctionLikeCandidate(node)) {\n return node;\n }\n\n const initializer = getNodeProperty(node, 'initializer');\n const unwrappedInitializer = initializer ? unwrapExpression(initializer) : undefined;\n if (!unwrappedInitializer) {\n return node;\n }\n if (isFunctionLikeCandidate(unwrappedInitializer)) {\n return unwrappedInitializer;\n }\n if (isComponentImplementationWrapperCall(unwrappedInitializer)) {\n return findComponentImplementationWrapperFunction(unwrappedInitializer) ?? node;\n }\n return node;\n}\n\nfunction isComponentImplementationWrapperCall(node: Node): boolean {\n if (node.kind !== SyntaxKind.CallExpression) {\n return false;\n }\n\n const expression = getNodeProperty(node, 'expression');\n const expressionName = expression ? findCallExpressionName(expression) : undefined;\n return expressionName === 'memo' || expressionName === 'forwardRef';\n}\n\nfunction findCallExpressionName(expression: Node): string | undefined {\n const name = getNodeProperty(expression, 'name');\n return name ? findCandidateName(name) : findCandidateName(expression);\n}\n\nfunction findComponentImplementationWrapperFunction(node: Node): Node | undefined {\n let functionNode: Node | undefined;\n node.forEachChild((child) => {\n const unwrappedChild = unwrapExpression(child);\n if (isFunctionLikeCandidate(unwrappedChild)) {\n functionNode = unwrappedChild;\n } else if (isComponentImplementationWrapperCall(unwrappedChild)) {\n functionNode = findComponentImplementationWrapperFunction(unwrappedChild);\n }\n return functionNode;\n });\n return functionNode;\n}\n\nfunction findFunctionStartPosition(\n text: string,\n functionNode: Node,\n name: Node | undefined,\n sourceFile: SourceFile\n): number {\n const tokenPosition = getTokenPosOfNode(functionNode, sourceFile);\n if (functionNode.kind !== SyntaxKind.FunctionDeclaration || !name) {\n return tokenPosition;\n }\n\n const functionName = findCandidateName(name);\n const namePosition = functionName ? text.indexOf(functionName, tokenPosition) : -1;\n const functionPosition = namePosition >= 0 ? text.lastIndexOf('function', namePosition) : -1;\n if (functionPosition < tokenPosition) {\n return tokenPosition;\n }\n\n const modifierText = text.slice(tokenPosition, functionPosition);\n const asyncMatch = /\\basync\\s*$/u.exec(modifierText);\n return asyncMatch ? tokenPosition + asyncMatch.index : functionPosition;\n}\n\nfunction dedupeReactComponentFunctions(\n components: readonly ReactComponentCandidateMetric[]\n): Omit<ReactComponentFunctionMetric, 'file'>[] {\n const componentByKey = new Map<string, ReactComponentCandidateMetric>();\n for (const component of components) {\n const existingComponent = componentByKey.get(component.implementationKey);\n if (!existingComponent || (!existingComponent.name && component.name)) {\n componentByKey.set(component.implementationKey, component);\n }\n }\n return [...componentByKey.values()].map((component) => ({\n name: component.name,\n startColumn: component.startColumn,\n startLine: component.startLine,\n }));\n}\n\nfunction findCandidateName(name: Node): string | undefined {\n if ('escapedText' in name) {\n return String(name.escapedText);\n }\n if ('text' in name && typeof name.text === 'string') {\n return name.text;\n }\n return undefined;\n}\n\nfunction isUppercaseComponentName(name: string | undefined): boolean {\n return name !== undefined && /^[A-Z]/u.test(name);\n}\n\nfunction findNameNode(node: Node): Node | undefined {\n const name = getNodeProperty(node, 'name');\n if (name) {\n return name;\n }\n\n let currentNode: Node | undefined = node;\n while (currentNode) {\n const parent = getNodeProperty(currentNode, 'parent');\n if (!parent) {\n return undefined;\n }\n if (parent.kind === SyntaxKind.VariableDeclaration) {\n return getNodeProperty(parent, 'name');\n }\n if (!isExpressionWrapperNode(parent)) {\n return undefined;\n }\n currentNode = parent;\n }\n return undefined;\n}\n\nfunction unwrapExpression(node: Node): Node {\n let currentNode = node;\n while (isExpressionWrapperNode(currentNode)) {\n const expression = getNodeProperty(currentNode, 'expression');\n if (!expression) {\n return currentNode;\n }\n currentNode = expression;\n }\n return currentNode;\n}\n\nfunction isExpressionWrapperNode(node: Node): boolean {\n return (\n node.kind === SyntaxKind.ParenthesizedExpression ||\n node.kind === SyntaxKind.AsExpression ||\n node.kind === SyntaxKind.TypeAssertionExpression ||\n node.kind === SyntaxKind.SatisfiesExpression\n );\n}\n\nfunction getNodeProperty(\n node: Node,\n property: 'body' | 'expression' | 'initializer' | 'name' | 'parent'\n): Node | undefined {\n const value = (node as Partial<Record<typeof property, Node>>)[property];\n return isNode(value) ? value : undefined;\n}\n\nfunction isNode(value: unknown): value is Node {\n return typeof value === 'object' && value !== null && 'kind' in value && 'pos' in value && 'end' in value;\n}\n\nfunction positionToLineColumn(text: string, position: number): { column: number; line: number } {\n const lines = text.slice(0, position).split('\\n');\n return { column: lines.at(-1)?.length ?? 0, line: lines.length };\n}\n\nfunction collectCallExpressions(root: Node): Node[] {\n const calls: Node[] = [];\n visitNode(root, (node) => {\n if (node.kind === SyntaxKind.CallExpression) {\n calls.push(node);\n }\n });\n return calls;\n}\n\nfunction visitNode(node: Node, visitor: (node: Node) => void): void {\n visitor(node);\n node.forEachChild((child) => {\n visitNode(child, visitor);\n return;\n });\n}\n\nfunction addDiagnosticFiles(files: Set<string>, diagnostics: readonly DiagnosticLike[]): void {\n for (const diagnostic of diagnostics) {\n if (diagnostic.fileName) {\n files.add(diagnostic.fileName);\n }\n }\n}\n"],"mappings":"oNAsCA,eAAsB,EACpB,EACA,EAAmC,CAAC,EACD,CACnC,IAAM,EAAM,IAAI,EAAI,CAAE,IAAK,QAAQ,IAAI,CAAE,CAAC,EAC1C,GAAI,CAEF,IAAM,GAAW,MADM,EAAI,eAAe,CAAE,YAAa,CAAW,CAAC,EAAA,CAC3C,YAAY,EAChC,EAA8B,MAAM,EAAgC,CAAa,EACjF,EAAkB,IAAI,IAAI,EAA4B,KAAK,CAAC,EAC5D,EAAkB,IAAI,IACtB,EAAS,CACb,oBAAqB,EACrB,sBAAuB,EACvB,2BAA4B,EAC5B,sBAAuB,EACvB,4BAA6B,EAC7B,cAAe,EACf,wBAAyB,EACzB,0BAA2B,EAC3B,yBAA0B,CAC5B,EACM,EAA0D,CAAC,EAC3D,EAAgB,IAAI,IAE1B,IAAK,IAAM,KAAW,EAAU,CAC9B,IAAM,EAAmB,MAAM,QAAQ,IACrC,EAAQ,UAAU,IAAI,KAAO,KAAU,CAAE,cAAe,MAAM,EAAiB,CAAI,EAAG,MAAK,EAAE,CAC/F,EACA,EAAO,eAAiB,EAAiB,OACzC,EAAO,uBAAyB,EAAiB,QAAQ,CAAE,mBACzD,EAAgB,IAAI,CAAa,CACnC,CAAC,CAAC,OAEF,IAAM,EAAuB,MAAM,EAAQ,QAAQ,wBAAwB,EACrE,EAAsB,MAAM,EAAQ,QAAQ,uBAAuB,EACnE,EAAwB,MAAM,EAAQ,QAAQ,yBAAyB,EACvE,EAAyB,MAAM,EAAQ,QAAQ,0BAA0B,EACzE,EAAoB,MAAM,EAAQ,QAAQ,gCAAgC,EAChF,EAAO,0BAA4B,EAAqB,OACxD,EAAO,yBAA2B,EAAoB,OACtD,EAAO,2BAA6B,EAAsB,OAC1D,EAAO,4BAA8B,EAAuB,OAC5D,EAAO,uBAAyB,EAAkB,OAClD,EAAmB,EAAiB,CAClC,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACL,CAAC,EAED,IAAM,EACJ,EAAgB,OAAS,EACrB,EACA,CAAC,GAAG,EAA4B,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAe,MAAW,CAAE,gBAAe,MAAK,EAAE,EACnG,EAAkB,EAAK,QAAQ,EAAQ,cAAc,EACrD,EAA2B,MAAM,EAAiB,CAAe,EACvE,IAAK,GAAM,CAAE,gBAAe,UAAU,EAAsB,CAC1D,GAAI,EAAc,IAAI,CAAa,EACjC,SAEF,IAAM,EAAa,MAAM,EACvB,EACA,EACA,EACA,EACA,CACF,EACA,GAAI,CAAC,EACH,SAEF,EAAc,IAAI,CAAa,EAC/B,IAAM,EAAkB,EAAuB,CAAU,EACzD,EAAO,qBAAuB,EAAgB,OAC9C,IAAM,EAA8B,MAAM,EAA+B,EAAY,EAAQ,OAAO,EACpG,EAAwB,KACtB,GAAG,EAA4B,IAAK,IAAe,CACjD,GAAG,EACH,KAAM,EAA4B,IAAI,CAAa,GAAK,CAC1D,EAAE,CACJ,EACA,IAAK,IAAM,KAAkB,EACvB,MAAM,EAAQ,QAAQ,qBAAqB,CAAc,IAC3D,EAAO,6BAA+B,EAG5C,CACF,CAEA,IAAM,EAAgC,EAAO,oBAAsB,EAAO,4BAC1E,MAAO,CACL,GAAG,EACH,aACA,oBAAqB,EAAgB,KACrC,aAAc,EAAS,OACvB,0BACA,gCACA,4BACE,EAAO,sBAAwB,EAAI,EAAI,EAAO,4BAA8B,EAAO,mBACvF,CACF,QAAU,CACR,MAAM,EAAI,MAAM,CAClB,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,EACA,EACiC,CACjC,IAAK,IAAM,KAAa,EAAyB,EAAM,EAAe,EAAiB,CAAwB,EAAG,CAChH,IAAM,EAAa,MAAM,EAAQ,QAAQ,cAAc,CAAS,EAChE,GAAI,EACF,OAAO,CAEX,CAEF,CAEA,SAAS,EACP,EACA,EACA,EACA,EACU,CACV,IAAM,EAAa,IAAI,IAAI,CAAC,EAAM,CAAa,CAAC,EAIhD,OAHI,EAAc,WAAW,GAAG,IAA2B,EAAK,KAAK,GACnE,EAAW,IAAI,EAAK,KAAK,EAAiB,EAAK,SAAS,EAA0B,CAAa,CAAC,CAAC,EAE5F,CAAC,GAAG,CAAU,CACvB,CAEA,eAAe,EAAgC,EAAwD,CACrG,IAAM,EAA8B,IAAI,IACxC,IAAK,IAAM,KAAQ,EACjB,EAA4B,IAAI,MAAM,EAAiB,CAAI,EAAG,CAAI,EAEpE,OAAO,CACT,CAEA,eAAe,EAAiB,EAA+B,CAC7D,GAAI,CACF,OAAO,MAAM,EAAS,CAAI,CAC5B,MAAQ,CACN,OAAO,CACT,CACF,CAEA,eAAe,EACb,EACA,EACuD,CACvD,IAAM,EAA8C,CAAC,EAC/C,EAAa,EAAgC,CAAU,EACvD,EAAoB,EAA6B,EAAW,IAAI,EACtE,IAAK,IAAM,KAAa,EAAY,CAClC,GAAI,CAAE,MAAM,EAA0B,EAAW,EAAS,CAAiB,EACzE,SAEF,IAAM,EAAO,EAAa,CAAS,EAC7B,EAAe,EAA0B,CAAS,EAClD,EAAc,EAA0B,EAAW,KAAM,EAAc,EAAM,CAAU,EACvF,EAAgB,EAAqB,EAAW,KAAM,CAAW,EACvE,EAAW,KAAK,CACd,kBAAmB,GAAG,EAAa,IAAI,GAAG,EAAa,MACvD,KAAM,EAAO,EAAkB,CAAI,EAAI,IAAA,GACvC,YAAa,EAAc,OAC3B,UAAW,EAAc,IAC3B,CAAC,CACH,CACA,OAAO,EAA8B,CAAU,CACjD,CAEA,SAAS,EAAgC,EAAoB,CAC3D,IAAM,EAAqB,CAAC,EAM5B,OALA,EAAU,EAAO,GAAS,EACpB,EAAwB,CAAI,GAAK,EAA4B,CAAI,IACnE,EAAW,KAAK,CAAI,CAExB,CAAC,EACM,CACT,CAEA,eAAe,EAA0B,EAAY,EAAkB,EAA8C,CACnH,IAAM,EAAO,MAAM,EAAQ,kBAAkB,CAAI,EACjD,GAAI,CAAC,EACH,MAAO,GAGT,IAAM,EAAO,EAAa,CAAI,EACxB,EAAgB,EAAO,EAAkB,CAAI,EAAI,IAAA,GACvD,GAAI,GAAsB,MAAM,EAA0B,EAAM,EAAM,CAAO,EAC3E,MAAO,GAGT,IAAM,EAAY,EAAO,MAAM,EAAQ,kBAAkB,CAAI,EAAI,IAAA,GASjE,OARI,GAAqB,GAAc,MAAM,EAA0B,EAAW,EAAM,CAAO,EACtF,GAGJ,EAAyB,CAAa,EAKxC,MAAM,EAA6B,EAAM,EAAM,CAAO,GACvD,GAAQ,GAAc,MAAM,EAA6B,EAAW,EAAM,CAAO,GAL1E,EAOX,CAEA,SAAS,EAA6B,EAAuB,CAC3D,MAAO,oFAAoF,KAAK,CAAI,CACtG,CAEA,eAAe,EAA6B,EAAY,EAAY,EAAoC,CACtG,IAAM,EAAa,MAAM,EAAQ,oBAAoB,EAAM,EAAc,IAAI,EAC7E,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAa,MAAM,EAAQ,yBAAyB,CAAS,EACnE,GAAI,GAAe,MAAM,EAAsB,EAAY,EAAM,CAAO,EACtE,MAAO,EAEX,CACA,MAAO,EACT,CAEA,eAAe,EAA0B,EAAY,EAAY,EAAoC,CACnG,IAAM,EAAW,MAAM,EAAQ,aAAa,EAAM,CAAI,EACtD,MAAO,gHAAgH,KACrH,CACF,CACF,CAEA,eAAe,EAAsB,EAAY,EAAY,EAAoC,CAC/F,IAAM,EAAW,MAAM,EAAQ,aAAa,EAAM,CAAI,EACtD,MAAO,mEAAmE,KAAK,CAAQ,CACzF,CAEA,SAAS,EAAwB,EAAqB,CAKpD,OAJI,EAAK,OAAS,EAAW,qBAAuB,CAAC,EAAgB,EAAM,MAAM,EACxE,GAIP,EAAK,OAAS,EAAW,qBACzB,EAAK,OAAS,EAAW,oBACzB,EAAK,OAAS,EAAW,aAE7B,CAEA,SAAS,EAA4B,EAAqB,CACxD,GAAI,EAAK,OAAS,EAAW,oBAC3B,MAAO,GAGT,IAAM,EAAc,EAAgB,EAAM,aAAa,EACjD,EAAuB,EAAc,EAAiB,CAAW,EAAI,IAAA,GAC3E,OAAO,EACH,EAAwB,CAAoB,GAAK,EAAqB,OAAS,EAAW,eAC1F,EACN,CAEA,SAAS,EAA0B,EAAkB,CACnD,GAAI,EAAwB,CAAI,EAC9B,OAAO,EAGT,IAAM,EAAc,EAAgB,EAAM,aAAa,EACjD,EAAuB,EAAc,EAAiB,CAAW,EAAI,IAAA,GAU3E,OATK,EAGD,EAAwB,CAAoB,EACvC,EAEL,EAAqC,CAAoB,EACpD,EAA2C,CAAoB,GAAK,EAEtE,EARE,CASX,CAEA,SAAS,EAAqC,EAAqB,CACjE,GAAI,EAAK,OAAS,EAAW,eAC3B,MAAO,GAGT,IAAM,EAAa,EAAgB,EAAM,YAAY,EAC/C,EAAiB,EAAa,EAAuB,CAAU,EAAI,IAAA,GACzE,OAAO,IAAmB,QAAU,IAAmB,YACzD,CAEA,SAAS,EAAuB,EAAsC,CAEpE,OAAc,EADD,EAAgB,EAAY,MAClC,GAAmD,CAAU,CACtE,CAEA,SAAS,EAA2C,EAA8B,CAChF,IAAI,EAUJ,OATA,EAAK,aAAc,GAAU,CAC3B,IAAM,EAAiB,EAAiB,CAAK,EAM7C,OALI,EAAwB,CAAc,EACxC,EAAe,EACN,EAAqC,CAAc,IAC5D,EAAe,EAA2C,CAAc,GAEnE,CACT,CAAC,EACM,CACT,CAEA,SAAS,EACP,EACA,EACA,EACA,EACQ,CACR,IAAM,EAAgB,EAAkB,EAAc,CAAU,EAChE,GAAI,EAAa,OAAS,EAAW,qBAAuB,CAAC,EAC3D,OAAO,EAGT,IAAM,EAAe,EAAkB,CAAI,EACrC,EAAe,EAAe,EAAK,QAAQ,EAAc,CAAa,EAAI,GAC1E,EAAmB,GAAgB,EAAI,EAAK,YAAY,WAAY,CAAY,EAAI,GAC1F,GAAI,EAAmB,EACrB,OAAO,EAGT,IAAM,EAAe,EAAK,MAAM,EAAe,CAAgB,EACzD,EAAa,eAAe,KAAK,CAAY,EACnD,OAAO,EAAa,EAAgB,EAAW,MAAQ,CACzD,CAEA,SAAS,EACP,EAC8C,CAC9C,IAAM,EAAiB,IAAI,IAC3B,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAoB,EAAe,IAAI,EAAU,iBAAiB,GACpE,CAAC,GAAsB,CAAC,EAAkB,MAAQ,EAAU,OAC9D,EAAe,IAAI,EAAU,kBAAmB,CAAS,CAE7D,CACA,MAAO,CAAC,GAAG,EAAe,OAAO,CAAC,CAAC,CAAC,IAAK,IAAe,CACtD,KAAM,EAAU,KAChB,YAAa,EAAU,YACvB,UAAW,EAAU,SACvB,EAAE,CACJ,CAEA,SAAS,EAAkB,EAAgC,CACzD,GAAI,gBAAiB,EACnB,OAAO,OAAO,EAAK,WAAW,EAEhC,GAAI,SAAU,GAAQ,OAAO,EAAK,MAAS,SACzC,OAAO,EAAK,IAGhB,CAEA,SAAS,EAAyB,EAAmC,CACnE,OAAO,IAAS,IAAA,IAAa,UAAU,KAAK,CAAI,CAClD,CAEA,SAAS,EAAa,EAA8B,CAClD,IAAM,EAAO,EAAgB,EAAM,MAAM,EACzC,GAAI,EACF,OAAO,EAGT,IAAI,EAAgC,EACpC,KAAO,GAAa,CAClB,IAAM,EAAS,EAAgB,EAAa,QAAQ,EACpD,GAAI,CAAC,EACH,OAEF,GAAI,EAAO,OAAS,EAAW,oBAC7B,OAAO,EAAgB,EAAQ,MAAM,EAEvC,GAAI,CAAC,EAAwB,CAAM,EACjC,OAEF,EAAc,CAChB,CAEF,CAEA,SAAS,EAAiB,EAAkB,CAC1C,IAAI,EAAc,EAClB,KAAO,EAAwB,CAAW,GAAG,CAC3C,IAAM,EAAa,EAAgB,EAAa,YAAY,EAC5D,GAAI,CAAC,EACH,OAAO,EAET,EAAc,CAChB,CACA,OAAO,CACT,CAEA,SAAS,EAAwB,EAAqB,CACpD,OACE,EAAK,OAAS,EAAW,yBACzB,EAAK,OAAS,EAAW,cACzB,EAAK,OAAS,EAAW,yBACzB,EAAK,OAAS,EAAW,mBAE7B,CAEA,SAAS,EACP,EACA,EACkB,CAClB,IAAM,EAAS,EAAgD,GAC/D,OAAO,EAAO,CAAK,EAAI,EAAQ,IAAA,EACjC,CAEA,SAAS,EAAO,EAA+B,CAC7C,OAAO,OAAO,GAAU,YAAY,GAAkB,SAAU,GAAS,QAAS,GAAS,QAAS,CACtG,CAEA,SAAS,EAAqB,EAAc,EAAoD,CAC9F,IAAM,EAAQ,EAAK,MAAM,EAAG,CAAQ,CAAC,CAAC,MAAM;CAAI,EAChD,MAAO,CAAE,OAAQ,EAAM,GAAG,EAAE,CAAC,EAAE,QAAU,EAAG,KAAM,EAAM,MAAO,CACjE,CAEA,SAAS,EAAuB,EAAoB,CAClD,IAAM,EAAgB,CAAC,EAMvB,OALA,EAAU,EAAO,GAAS,CACpB,EAAK,OAAS,EAAW,gBAC3B,EAAM,KAAK,CAAI,CAEnB,CAAC,EACM,CACT,CAEA,SAAS,EAAU,EAAY,EAAqC,CAClE,EAAQ,CAAI,EACZ,EAAK,aAAc,GAAU,CAC3B,EAAU,EAAO,CAAO,CAE1B,CAAC,CACH,CAEA,SAAS,EAAmB,EAAoB,EAA8C,CAC5F,IAAK,IAAM,KAAc,EACnB,EAAW,UACb,EAAM,IAAI,EAAW,QAAQ,CAGnC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "code-gauge",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Measure code metrics with tree-sitter.",
5
5
  "keywords": [
6
6
  "cli",
@@ -32,7 +32,9 @@
32
32
  "dist/"
33
33
  ],
34
34
  "scripts": {
35
+ "benchmark": "node scripts/benchmark.mjs",
35
36
  "build": "build-ts lib -i src/index.ts -i src/cli.ts",
37
+ "build-native": "node scripts/buildNative.mjs",
36
38
  "cleanup": "yarn format && yarn lint-fix",
37
39
  "format": "sort-package-json && yarn format-code",
38
40
  "format-code": "oxfmt --write --no-error-on-unmatched-pattern . '!**/package.json'",
@@ -40,19 +42,24 @@
40
42
  "lint-fix": "yarn lint --fix",
41
43
  "prepare": "lefthook install || true",
42
44
  "test": "vitest",
45
+ "test/ci": "yarn build-native && CODE_GAUGE_EXPECT_NATIVE=1 vitest",
43
46
  "typecheck": "wb typecheck",
44
47
  "verify": "wb verify",
45
- "verify-full": "wb verify --full"
48
+ "verify-full": "wb verify --full && yarn vitest run test/e2e"
46
49
  },
47
50
  "dependencies": {
48
- "@typescript/native-preview": "7.0.0-dev.20260615.1",
49
51
  "commander": "15.0.0",
50
52
  "tree-sitter": "^0.21.1",
53
+ "tree-sitter-c": "^0.21.0",
54
+ "tree-sitter-cpp": "^0.22.0",
51
55
  "tree-sitter-go": "^0.21.2",
56
+ "tree-sitter-java": "^0.21.0",
52
57
  "tree-sitter-javascript": "^0.21.4",
53
58
  "tree-sitter-python": "^0.21.0",
59
+ "tree-sitter-ruby": "^0.21.0",
54
60
  "tree-sitter-rust": "^0.21.0",
55
- "tree-sitter-typescript": "^0.21.2"
61
+ "tree-sitter-typescript": "^0.21.2",
62
+ "typescript": "7.0.2"
56
63
  },
57
64
  "devDependencies": {
58
65
  "@tsconfig/node-lts": "24.0.0",
@@ -60,18 +67,18 @@
60
67
  "@types/node": "25.9.4",
61
68
  "@willbooster/oxfmt-config": "1.2.2",
62
69
  "@willbooster/oxlint-config": "1.4.8",
63
- "@willbooster/wb": "13.22.18",
64
- "build-ts": "17.1.27",
70
+ "@willbooster/wb": "13.25.0",
71
+ "build-ts": "17.1.31",
65
72
  "conventional-changelog-conventionalcommits": "9.3.1",
66
73
  "lefthook": "2.1.9",
67
- "oxfmt": "0.56.0",
68
- "oxlint": "1.71.0",
69
- "oxlint-tsgolint": "0.23.0",
74
+ "oxfmt": "0.58.0",
75
+ "oxlint": "1.73.0",
76
+ "oxlint-tsgolint": "0.24.0",
70
77
  "semantic-release": "25.0.5",
71
78
  "sort-package-json": "4.0.0",
72
79
  "vitest": "4.1.9"
73
80
  },
74
- "packageManager": "yarn@4.17.0",
81
+ "packageManager": "yarn@4.17.1",
75
82
  "engines": {
76
83
  "node": ">=24"
77
84
  },
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}