@fluentui/react-compiler-analyzer 0.0.0-experimental.rc-analyzer.20260430-fc3bb2e193.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 (51) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +185 -0
  3. package/bin/react-compiler-analyzer.js +10 -0
  4. package/dist/README.md +185 -0
  5. package/dist/package.json +24 -0
  6. package/dist/src/analyzer.d.ts +9 -0
  7. package/dist/src/analyzer.js +271 -0
  8. package/dist/src/analyzer.js.map +1 -0
  9. package/dist/src/cli.d.ts +1 -0
  10. package/dist/src/cli.js +19 -0
  11. package/dist/src/cli.js.map +1 -0
  12. package/dist/src/commands/coverage.d.ts +13 -0
  13. package/dist/src/commands/coverage.js +65 -0
  14. package/dist/src/commands/coverage.js.map +1 -0
  15. package/dist/src/commands/directives.d.ts +11 -0
  16. package/dist/src/commands/directives.js +60 -0
  17. package/dist/src/commands/directives.js.map +1 -0
  18. package/dist/src/commands/shared.d.ts +18 -0
  19. package/dist/src/commands/shared.js +79 -0
  20. package/dist/src/commands/shared.js.map +1 -0
  21. package/dist/src/compiler-utils.d.ts +29 -0
  22. package/dist/src/compiler-utils.js +29 -0
  23. package/dist/src/compiler-utils.js.map +1 -0
  24. package/dist/src/coverage-analyzer.d.ts +9 -0
  25. package/dist/src/coverage-analyzer.js +177 -0
  26. package/dist/src/coverage-analyzer.js.map +1 -0
  27. package/dist/src/coverage-fixer.d.ts +12 -0
  28. package/dist/src/coverage-fixer.js +71 -0
  29. package/dist/src/coverage-fixer.js.map +1 -0
  30. package/dist/src/coverage-reporter.d.ts +16 -0
  31. package/dist/src/coverage-reporter.js +167 -0
  32. package/dist/src/coverage-reporter.js.map +1 -0
  33. package/dist/src/discovery.d.ts +14 -0
  34. package/dist/src/discovery.js +70 -0
  35. package/dist/src/discovery.js.map +1 -0
  36. package/dist/src/fixer.d.ts +9 -0
  37. package/dist/src/fixer.js +79 -0
  38. package/dist/src/fixer.js.map +1 -0
  39. package/dist/src/index.d.ts +1 -0
  40. package/dist/src/index.js +6 -0
  41. package/dist/src/index.js.map +1 -0
  42. package/dist/src/manual-memo-plugin.d.ts +16 -0
  43. package/dist/src/manual-memo-plugin.js +82 -0
  44. package/dist/src/manual-memo-plugin.js.map +1 -0
  45. package/dist/src/reporter.d.ts +9 -0
  46. package/dist/src/reporter.js +117 -0
  47. package/dist/src/reporter.js.map +1 -0
  48. package/dist/src/types.d.ts +70 -0
  49. package/dist/src/types.js +7 -0
  50. package/dist/src/types.js.map +1 -0
  51. package/package.json +24 -0
@@ -0,0 +1,29 @@
1
+ export interface CompilerEvent {
2
+ kind: 'CompileSuccess' | 'CompileError' | 'CompileSkip' | 'PipelineError' | string;
3
+ fnLoc: {
4
+ start: {
5
+ line: number;
6
+ column: number;
7
+ };
8
+ end: {
9
+ line: number;
10
+ column: number;
11
+ };
12
+ } | null;
13
+ fnName?: string | null;
14
+ reason?: string;
15
+ detail?: unknown;
16
+ data?: string;
17
+ memoSlots?: number;
18
+ memoBlocks?: number;
19
+ memoValues?: number;
20
+ prunedMemoBlocks?: number;
21
+ prunedMemoValues?: number;
22
+ }
23
+ /**
24
+ * Extract a human-readable reason string from a CompileError detail object.
25
+ * The `detail` can be a CompilerErrorDetail or CompilerDiagnostic from the
26
+ * React Compiler — both carry a `.reason` and optional `.description`.
27
+ * Falls back to `String(detail)` and then to `Error.toString()`.
28
+ */
29
+ export declare function extractDetailReason(detail: unknown): string;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "extractDetailReason", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return extractDetailReason;
9
+ }
10
+ });
11
+ function extractDetailReason(detail) {
12
+ if (detail === null || detail === undefined) return '';
13
+ // CompilerErrorDetail / CompilerDiagnostic expose .reason + .description
14
+ if (typeof detail === 'object') {
15
+ const compilerDetail = detail;
16
+ const parts = [];
17
+ if (typeof compilerDetail.reason === 'string') parts.push(compilerDetail.reason);
18
+ if (typeof compilerDetail.description === 'string') parts.push(compilerDetail.description);
19
+ // Some details nest a loc with line/column info
20
+ if (compilerDetail.loc && typeof compilerDetail.loc === 'object' && compilerDetail.loc.start) {
21
+ const start = compilerDetail.loc.start;
22
+ parts.push(`(${start.line ?? '?'}:${start.column ?? '?'})`);
23
+ }
24
+ if (parts.length > 0) return parts.join(' ');
25
+ }
26
+ return String(detail);
27
+ }
28
+
29
+ //# sourceMappingURL=compiler-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/compiler-utils.ts"],"sourcesContent":["export interface CompilerEvent {\n kind: 'CompileSuccess' | 'CompileError' | 'CompileSkip' | 'PipelineError' | string;\n fnLoc: { start: { line: number; column: number }; end: { line: number; column: number } } | null;\n fnName?: string | null;\n reason?: string;\n detail?: unknown;\n data?: string;\n memoSlots?: number;\n memoBlocks?: number;\n memoValues?: number;\n prunedMemoBlocks?: number;\n prunedMemoValues?: number;\n}\n\ninterface CompilerDetailLike {\n reason?: string;\n description?: string;\n loc?: {\n start?: { line?: number; column?: number };\n };\n}\n\n/**\n * Extract a human-readable reason string from a CompileError detail object.\n * The `detail` can be a CompilerErrorDetail or CompilerDiagnostic from the\n * React Compiler — both carry a `.reason` and optional `.description`.\n * Falls back to `String(detail)` and then to `Error.toString()`.\n */\nexport function extractDetailReason(detail: unknown): string {\n if (detail === null || detail === undefined) {\n return '';\n }\n // CompilerErrorDetail / CompilerDiagnostic expose .reason + .description\n if (typeof detail === 'object') {\n const compilerDetail = detail as CompilerDetailLike;\n const parts: string[] = [];\n\n if (typeof compilerDetail.reason === 'string') {\n parts.push(compilerDetail.reason);\n }\n if (typeof compilerDetail.description === 'string') {\n parts.push(compilerDetail.description);\n }\n // Some details nest a loc with line/column info\n if (compilerDetail.loc && typeof compilerDetail.loc === 'object' && compilerDetail.loc.start) {\n const start = compilerDetail.loc.start;\n parts.push(`(${start.line ?? '?'}:${start.column ?? '?'})`);\n }\n if (parts.length > 0) {\n return parts.join(' ');\n }\n }\n return String(detail);\n}\n"],"names":["extractDetailReason","detail","undefined","compilerDetail","parts","reason","push","description","loc","start","line","column","length","join","String"],"mappings":";;;;+BA4BgBA;;;eAAAA;;;AAAT,SAASA,oBAAoBC,MAAe;IACjD,IAAIA,WAAW,QAAQA,WAAWC,WAChC,OAAO;IAET,yEAAyE;IACzE,IAAI,OAAOD,WAAW,UAAU;QAC9B,MAAME,iBAAiBF;QACvB,MAAMG,QAAkB,EAAE;QAE1B,IAAI,OAAOD,eAAeE,MAAM,KAAK,UACnCD,MAAME,IAAI,CAACH,eAAeE,MAAM;QAElC,IAAI,OAAOF,eAAeI,WAAW,KAAK,UACxCH,MAAME,IAAI,CAACH,eAAeI,WAAW;QAEvC,gDAAgD;QAChD,IAAIJ,eAAeK,GAAG,IAAI,OAAOL,eAAeK,GAAG,KAAK,YAAYL,eAAeK,GAAG,CAACC,KAAK,EAAE;YAC5F,MAAMA,QAAQN,eAAeK,GAAG,CAACC,KAAK;YACtCL,MAAME,IAAI,CAAC,CAAC,CAAC,EAAEG,MAAMC,IAAI,IAAI,IAAI,CAAC,EAAED,MAAME,MAAM,IAAI,IAAI,CAAC,CAAC;QAC5D;QACA,IAAIP,MAAMQ,MAAM,GAAG,GACjB,OAAOR,MAAMS,IAAI,CAAC;IAEtB;IACA,OAAOC,OAAOb;AAChB"}
@@ -0,0 +1,9 @@
1
+ import type { FunctionAnalysis, FileEntry, CoverageAnalyzerOptions, CompilationMode } from './types';
2
+ /**
3
+ * Analyze a single file to determine which functions the React Compiler would memoize.
4
+ */
5
+ export declare function analyzeFileForCoverage(filePath: string, packageName: string, compilationMode: CompilationMode, verbose: boolean): Promise<FunctionAnalysis[]>;
6
+ /**
7
+ * Analyze multiple files for coverage with concurrency-limited parallelism.
8
+ */
9
+ export declare function analyzeFilesForCoverage(files: FileEntry[], options: CoverageAnalyzerOptions): Promise<FunctionAnalysis[]>;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ analyzeFileForCoverage: function() {
13
+ return analyzeFileForCoverage;
14
+ },
15
+ analyzeFilesForCoverage: function() {
16
+ return analyzeFilesForCoverage;
17
+ }
18
+ });
19
+ const _promises = require("node:fs/promises");
20
+ const _nodepath = require("node:path");
21
+ const _core = require("@babel/core");
22
+ const _compilerutils = require("./compiler-utils");
23
+ const _manualmemoplugin = require("./manual-memo-plugin");
24
+ async function analyzeFileForCoverage(filePath, packageName, compilationMode, verbose) {
25
+ const source = await (0, _promises.readFile)(filePath, 'utf-8');
26
+ const results = [];
27
+ // Collect compiler events
28
+ const events = [];
29
+ const logger = {
30
+ logEvent: (_filename, event)=>{
31
+ events.push(event);
32
+ }
33
+ };
34
+ const ext = (0, _nodepath.extname)(filePath);
35
+ const isTSX = ext === '.tsx';
36
+ // Collect manual memoization data
37
+ const manualMemoResults = new Map();
38
+ try {
39
+ await (0, _core.transformAsync)(source, {
40
+ filename: filePath,
41
+ ast: false,
42
+ code: false,
43
+ babelrc: false,
44
+ configFile: false,
45
+ presets: [
46
+ [
47
+ require.resolve('@babel/preset-typescript'),
48
+ {
49
+ isTSX: isTSX || ext === '.ts',
50
+ allExtensions: true
51
+ }
52
+ ]
53
+ ],
54
+ plugins: [
55
+ [
56
+ _manualmemoplugin.manualMemoPlugin,
57
+ {
58
+ results: manualMemoResults
59
+ }
60
+ ],
61
+ [
62
+ require.resolve('babel-plugin-react-compiler'),
63
+ {
64
+ noEmit: true,
65
+ panicThreshold: 'none',
66
+ compilationMode,
67
+ logger
68
+ }
69
+ ]
70
+ ]
71
+ });
72
+ } catch (err) {
73
+ if (verbose) console.error(` babel error in ${filePath}: ${err.message}`);
74
+ // Nothing to report — file-level parse errors don't produce function-level results
75
+ return results;
76
+ }
77
+ if (verbose) for (const ev of events){
78
+ const loc = ev.fnLoc ? `${ev.fnLoc.start.line}:${ev.fnLoc.start.column}` : '?';
79
+ const name = ev.fnName ?? '';
80
+ console.log(` [${ev.kind}] ${filePath} fn@${loc} ${name}`);
81
+ }
82
+ for (const event of events){
83
+ // Skip non-analysis events (Timing, AutoDeps*, etc.)
84
+ if (![
85
+ 'CompileSuccess',
86
+ 'CompileError',
87
+ 'CompileSkip',
88
+ 'PipelineError'
89
+ ].includes(event.kind)) continue;
90
+ const line = event.fnLoc?.start.line ?? 0;
91
+ const column = event.fnLoc?.start.column ?? 0;
92
+ const functionName = event.fnName ?? null;
93
+ if (event.kind === 'CompileSuccess') {
94
+ const memoStats = {
95
+ memoSlots: event.memoSlots ?? 0,
96
+ memoBlocks: event.memoBlocks ?? 0,
97
+ memoValues: event.memoValues ?? 0,
98
+ prunedMemoBlocks: event.prunedMemoBlocks ?? 0,
99
+ prunedMemoValues: event.prunedMemoValues ?? 0
100
+ };
101
+ results.push({
102
+ filePath,
103
+ packageName,
104
+ line,
105
+ column,
106
+ functionName,
107
+ status: 'compiled',
108
+ compilerEvent: 'CompileSuccess',
109
+ memoStats
110
+ });
111
+ } else if (event.kind === 'CompileSkip') results.push({
112
+ filePath,
113
+ packageName,
114
+ line,
115
+ column,
116
+ functionName,
117
+ status: 'skipped',
118
+ compilerEvent: 'CompileSkip',
119
+ reason: event.reason
120
+ });
121
+ else if (event.kind === 'CompileError') results.push({
122
+ filePath,
123
+ packageName,
124
+ line,
125
+ column,
126
+ functionName,
127
+ status: 'error',
128
+ compilerEvent: 'CompileError',
129
+ reason: (0, _compilerutils.extractDetailReason)(event.detail)
130
+ });
131
+ else if (event.kind === 'PipelineError') results.push({
132
+ filePath,
133
+ packageName,
134
+ line,
135
+ column,
136
+ functionName,
137
+ status: 'error',
138
+ compilerEvent: 'PipelineError',
139
+ reason: event.data ?? ''
140
+ });
141
+ }
142
+ // Merge manual memoization data into results by matching function location
143
+ for (const result of results){
144
+ const key = `${result.line}:${result.column}`;
145
+ const memoEntry = manualMemoResults.get(key);
146
+ if (memoEntry) {
147
+ result.manualMemo = {
148
+ useMemo: memoEntry.useMemo,
149
+ useCallback: memoEntry.useCallback,
150
+ reactMemo: memoEntry.reactMemo
151
+ };
152
+ result.bodyInsertionLine = memoEntry.bodyInsertionLine;
153
+ }
154
+ }
155
+ return results;
156
+ }
157
+ async function analyzeFilesForCoverage(files, options) {
158
+ const allResults = [];
159
+ const { concurrency, verbose, compilationMode } = options;
160
+ let index = 0;
161
+ async function worker() {
162
+ while(index < files.length){
163
+ const current = index++;
164
+ const entry = files[current];
165
+ if (verbose) console.log(`Analyzing: ${entry.filePath}`);
166
+ const results = await analyzeFileForCoverage(entry.filePath, entry.packageName, compilationMode, verbose);
167
+ allResults.push(...results);
168
+ }
169
+ }
170
+ const workers = Array.from({
171
+ length: Math.min(concurrency, files.length)
172
+ }, ()=>worker());
173
+ await Promise.all(workers);
174
+ return allResults;
175
+ }
176
+
177
+ //# sourceMappingURL=coverage-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/coverage-analyzer.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { extname } from 'node:path';\n\nimport { transformAsync } from '@babel/core';\n\nimport type {\n FunctionAnalysis,\n FileEntry,\n CoverageAnalyzerOptions,\n CompilationMode,\n MemoStats,\n ManualMemoization,\n} from './types';\nimport { extractDetailReason } from './compiler-utils';\nimport type { CompilerEvent } from './compiler-utils';\nimport { manualMemoPlugin, ManualMemoEntry, ManualMemoPluginOptions } from './manual-memo-plugin';\n\n/**\n * Analyze a single file to determine which functions the React Compiler would memoize.\n */\nexport async function analyzeFileForCoverage(\n filePath: string,\n packageName: string,\n compilationMode: CompilationMode,\n verbose: boolean,\n): Promise<FunctionAnalysis[]> {\n const source = await readFile(filePath, 'utf-8');\n const results: FunctionAnalysis[] = [];\n\n // Collect compiler events\n const events: CompilerEvent[] = [];\n const logger = {\n logEvent: (_filename: string | null, event: CompilerEvent) => {\n events.push(event);\n },\n };\n\n const ext = extname(filePath);\n const isTSX = ext === '.tsx';\n\n // Collect manual memoization data\n const manualMemoResults = new Map<string, ManualMemoEntry>();\n\n try {\n await transformAsync(source, {\n filename: filePath,\n ast: false,\n code: false,\n babelrc: false,\n configFile: false,\n presets: [\n [\n require.resolve('@babel/preset-typescript'),\n {\n isTSX: isTSX || ext === '.ts',\n allExtensions: true,\n },\n ],\n ],\n plugins: [\n [manualMemoPlugin, { results: manualMemoResults } as ManualMemoPluginOptions],\n [\n require.resolve('babel-plugin-react-compiler'),\n {\n noEmit: true,\n panicThreshold: 'none',\n compilationMode,\n logger,\n },\n ],\n ],\n });\n } catch (err) {\n if (verbose) {\n console.error(` babel error in ${filePath}: ${(err as Error).message}`);\n }\n // Nothing to report — file-level parse errors don't produce function-level results\n return results;\n }\n\n if (verbose) {\n for (const ev of events) {\n const loc = ev.fnLoc ? `${ev.fnLoc.start.line}:${ev.fnLoc.start.column}` : '?';\n const name = ev.fnName ?? '';\n console.log(` [${ev.kind}] ${filePath} fn@${loc} ${name}`);\n }\n }\n\n for (const event of events) {\n // Skip non-analysis events (Timing, AutoDeps*, etc.)\n if (!['CompileSuccess', 'CompileError', 'CompileSkip', 'PipelineError'].includes(event.kind)) {\n continue;\n }\n\n const line = event.fnLoc?.start.line ?? 0;\n const column = event.fnLoc?.start.column ?? 0;\n const functionName = event.fnName ?? null;\n\n if (event.kind === 'CompileSuccess') {\n const memoStats: MemoStats = {\n memoSlots: event.memoSlots ?? 0,\n memoBlocks: event.memoBlocks ?? 0,\n memoValues: event.memoValues ?? 0,\n prunedMemoBlocks: event.prunedMemoBlocks ?? 0,\n prunedMemoValues: event.prunedMemoValues ?? 0,\n };\n\n results.push({\n filePath,\n packageName,\n line,\n column,\n functionName,\n status: 'compiled',\n compilerEvent: 'CompileSuccess',\n memoStats,\n });\n } else if (event.kind === 'CompileSkip') {\n results.push({\n filePath,\n packageName,\n line,\n column,\n functionName,\n status: 'skipped',\n compilerEvent: 'CompileSkip',\n reason: event.reason,\n });\n } else if (event.kind === 'CompileError') {\n results.push({\n filePath,\n packageName,\n line,\n column,\n functionName,\n status: 'error',\n compilerEvent: 'CompileError',\n reason: extractDetailReason(event.detail),\n });\n } else if (event.kind === 'PipelineError') {\n results.push({\n filePath,\n packageName,\n line,\n column,\n functionName,\n status: 'error',\n compilerEvent: 'PipelineError',\n reason: event.data ?? '',\n });\n }\n }\n\n // Merge manual memoization data into results by matching function location\n for (const result of results) {\n const key = `${result.line}:${result.column}`;\n const memoEntry = manualMemoResults.get(key);\n if (memoEntry) {\n result.manualMemo = {\n useMemo: memoEntry.useMemo,\n useCallback: memoEntry.useCallback,\n reactMemo: memoEntry.reactMemo,\n };\n result.bodyInsertionLine = memoEntry.bodyInsertionLine;\n }\n }\n\n return results;\n}\n\n/**\n * Analyze multiple files for coverage with concurrency-limited parallelism.\n */\nexport async function analyzeFilesForCoverage(\n files: FileEntry[],\n options: CoverageAnalyzerOptions,\n): Promise<FunctionAnalysis[]> {\n const allResults: FunctionAnalysis[] = [];\n const { concurrency, verbose, compilationMode } = options;\n\n let index = 0;\n\n async function worker(): Promise<void> {\n while (index < files.length) {\n const current = index++;\n const entry = files[current];\n\n if (verbose) {\n console.log(`Analyzing: ${entry.filePath}`);\n }\n\n const results = await analyzeFileForCoverage(entry.filePath, entry.packageName, compilationMode, verbose);\n allResults.push(...results);\n }\n }\n\n const workers = Array.from({ length: Math.min(concurrency, files.length) }, () => worker());\n await Promise.all(workers);\n\n return allResults;\n}\n"],"names":["analyzeFileForCoverage","analyzeFilesForCoverage","filePath","packageName","compilationMode","verbose","source","readFile","results","events","logger","logEvent","_filename","event","push","ext","extname","isTSX","manualMemoResults","Map","transformAsync","filename","ast","code","babelrc","configFile","presets","require","resolve","allExtensions","plugins","manualMemoPlugin","noEmit","panicThreshold","err","console","error","message","ev","loc","fnLoc","start","line","column","name","fnName","log","kind","includes","functionName","memoStats","memoSlots","memoBlocks","memoValues","prunedMemoBlocks","prunedMemoValues","status","compilerEvent","reason","extractDetailReason","detail","data","result","key","memoEntry","get","manualMemo","useMemo","useCallback","reactMemo","bodyInsertionLine","files","options","allResults","concurrency","index","worker","length","current","entry","workers","Array","from","Math","min","Promise","all"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { FunctionAnalysis, AnnotateResult } from './types';
2
+ /**
3
+ * Apply 'use memo' annotations to functions that are migration candidates.
4
+ * A migration candidate is a function that:
5
+ * - Has CompileSuccess status (compiler can optimize it)
6
+ * - Has manual memoization (useMemo/useCallback/React.memo)
7
+ * - Has a valid bodyInsertionLine
8
+ *
9
+ * Inserts 'use memo'; directive at the top of the function body.
10
+ * Processes insertions bottom-to-top within each file to preserve line numbers.
11
+ */
12
+ export declare function applyAnnotations(results: FunctionAnalysis[]): Promise<AnnotateResult>;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "applyAnnotations", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return applyAnnotations;
9
+ }
10
+ });
11
+ const _promises = require("node:fs/promises");
12
+ async function applyAnnotations(results) {
13
+ // Filter to migration candidates
14
+ const candidates = results.filter((r)=>r.status === 'compiled' && r.manualMemo && r.bodyInsertionLine && r.bodyInsertionLine > 0);
15
+ if (candidates.length === 0) return {
16
+ filesModified: 0,
17
+ functionsAnnotated: 0
18
+ };
19
+ // Group by file
20
+ const byFile = new Map();
21
+ for (const c of candidates){
22
+ const existing = byFile.get(c.filePath) ?? [];
23
+ existing.push(c);
24
+ byFile.set(c.filePath, existing);
25
+ }
26
+ let filesModified = 0;
27
+ let functionsAnnotated = 0;
28
+ for (const [filePath, fileCandidates] of byFile){
29
+ const source = await (0, _promises.readFile)(filePath, 'utf-8');
30
+ const lines = source.split('\n');
31
+ // Sort by bodyInsertionLine descending to insert bottom-to-top
32
+ const sorted = [
33
+ ...fileCandidates
34
+ ].sort((a, b)=>b.bodyInsertionLine - a.bodyInsertionLine);
35
+ let modified = false;
36
+ for (const candidate of sorted){
37
+ const insertLine = candidate.bodyInsertionLine;
38
+ // insertLine is 1-based, array is 0-based
39
+ const insertIndex = insertLine - 1;
40
+ if (insertIndex < 0 || insertIndex > lines.length) continue;
41
+ // Detect indentation from the line at insertion point (or next non-empty line)
42
+ let indent = ' ';
43
+ if (insertIndex < lines.length) {
44
+ const match = lines[insertIndex].match(/^(\s+)/);
45
+ if (match) indent = match[1];
46
+ }
47
+ // Check if 'use memo' is already present nearby (idempotent)
48
+ const surroundingStart = Math.max(0, insertIndex - 1);
49
+ const surroundingEnd = Math.min(lines.length - 1, insertIndex + 1);
50
+ let alreadyPresent = false;
51
+ for(let i = surroundingStart; i <= surroundingEnd; i++)if (lines[i].includes("'use memo'")) {
52
+ alreadyPresent = true;
53
+ break;
54
+ }
55
+ if (alreadyPresent) continue;
56
+ lines.splice(insertIndex, 0, `${indent}'use memo';`);
57
+ modified = true;
58
+ functionsAnnotated++;
59
+ }
60
+ if (modified) {
61
+ await (0, _promises.writeFile)(filePath, lines.join('\n'), 'utf-8');
62
+ filesModified++;
63
+ }
64
+ }
65
+ return {
66
+ filesModified,
67
+ functionsAnnotated
68
+ };
69
+ }
70
+
71
+ //# sourceMappingURL=coverage-fixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/coverage-fixer.ts"],"sourcesContent":["import { readFile, writeFile } from 'node:fs/promises';\n\nimport type { FunctionAnalysis, AnnotateResult } from './types';\n\n/**\n * Apply 'use memo' annotations to functions that are migration candidates.\n * A migration candidate is a function that:\n * - Has CompileSuccess status (compiler can optimize it)\n * - Has manual memoization (useMemo/useCallback/React.memo)\n * - Has a valid bodyInsertionLine\n *\n * Inserts 'use memo'; directive at the top of the function body.\n * Processes insertions bottom-to-top within each file to preserve line numbers.\n */\nexport async function applyAnnotations(results: FunctionAnalysis[]): Promise<AnnotateResult> {\n // Filter to migration candidates\n const candidates = results.filter(\n r => r.status === 'compiled' && r.manualMemo && r.bodyInsertionLine && r.bodyInsertionLine > 0,\n );\n\n if (candidates.length === 0) {\n return { filesModified: 0, functionsAnnotated: 0 };\n }\n\n // Group by file\n const byFile = new Map<string, FunctionAnalysis[]>();\n for (const c of candidates) {\n const existing = byFile.get(c.filePath) ?? [];\n existing.push(c);\n byFile.set(c.filePath, existing);\n }\n\n let filesModified = 0;\n let functionsAnnotated = 0;\n\n for (const [filePath, fileCandidates] of byFile) {\n const source = await readFile(filePath, 'utf-8');\n const lines = source.split('\\n');\n\n // Sort by bodyInsertionLine descending to insert bottom-to-top\n const sorted = [...fileCandidates].sort((a, b) => b.bodyInsertionLine! - a.bodyInsertionLine!);\n\n let modified = false;\n for (const candidate of sorted) {\n const insertLine = candidate.bodyInsertionLine!;\n // insertLine is 1-based, array is 0-based\n const insertIndex = insertLine - 1;\n\n if (insertIndex < 0 || insertIndex > lines.length) {\n continue;\n }\n\n // Detect indentation from the line at insertion point (or next non-empty line)\n let indent = ' ';\n if (insertIndex < lines.length) {\n const match = lines[insertIndex].match(/^(\\s+)/);\n if (match) {\n indent = match[1];\n }\n }\n\n // Check if 'use memo' is already present nearby (idempotent)\n const surroundingStart = Math.max(0, insertIndex - 1);\n const surroundingEnd = Math.min(lines.length - 1, insertIndex + 1);\n let alreadyPresent = false;\n for (let i = surroundingStart; i <= surroundingEnd; i++) {\n if (lines[i].includes(\"'use memo'\")) {\n alreadyPresent = true;\n break;\n }\n }\n\n if (alreadyPresent) {\n continue;\n }\n\n lines.splice(insertIndex, 0, `${indent}'use memo';`);\n modified = true;\n functionsAnnotated++;\n }\n\n if (modified) {\n await writeFile(filePath, lines.join('\\n'), 'utf-8');\n filesModified++;\n }\n }\n\n return { filesModified, functionsAnnotated };\n}\n"],"names":["applyAnnotations","results","candidates","filter","r","status","manualMemo","bodyInsertionLine","length","filesModified","functionsAnnotated","byFile","Map","c","existing","get","filePath","push","set","fileCandidates","source","readFile","lines","split","sorted","sort","a","b","modified","candidate","insertLine","insertIndex","indent","match","surroundingStart","Math","max","surroundingEnd","min","alreadyPresent","i","includes","splice","writeFile","join"],"mappings":";;;;+BAcsBA;;;eAAAA;;;0BAdc;AAc7B,eAAeA,iBAAiBC,OAA2B;IAChE,iCAAiC;IACjC,MAAMC,aAAaD,QAAQE,MAAM,CAC/BC,CAAAA,IAAKA,EAAEC,MAAM,KAAK,cAAcD,EAAEE,UAAU,IAAIF,EAAEG,iBAAiB,IAAIH,EAAEG,iBAAiB,GAAG;IAG/F,IAAIL,WAAWM,MAAM,KAAK,GACxB,OAAO;QAAEC,eAAe;QAAGC,oBAAoB;IAAE;IAGnD,gBAAgB;IAChB,MAAMC,SAAS,IAAIC;IACnB,KAAK,MAAMC,KAAKX,WAAY;QAC1B,MAAMY,WAAWH,OAAOI,GAAG,CAACF,EAAEG,QAAQ,KAAK,EAAE;QAC7CF,SAASG,IAAI,CAACJ;QACdF,OAAOO,GAAG,CAACL,EAAEG,QAAQ,EAAEF;IACzB;IAEA,IAAIL,gBAAgB;IACpB,IAAIC,qBAAqB;IAEzB,KAAK,MAAM,CAACM,UAAUG,eAAe,IAAIR,OAAQ;QAC/C,MAAMS,SAAS,MAAMC,IAAAA,kBAAQ,EAACL,UAAU;QACxC,MAAMM,QAAQF,OAAOG,KAAK,CAAC;QAE3B,+DAA+D;QAC/D,MAAMC,SAAS;eAAIL;SAAe,CAACM,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEpB,iBAAiB,GAAImB,EAAEnB,iBAAiB;QAE5F,IAAIqB,WAAW;QACf,KAAK,MAAMC,aAAaL,OAAQ;YAC9B,MAAMM,aAAaD,UAAUtB,iBAAiB;YAC9C,0CAA0C;YAC1C,MAAMwB,cAAcD,aAAa;YAEjC,IAAIC,cAAc,KAAKA,cAAcT,MAAMd,MAAM,EAC/C;YAGF,+EAA+E;YAC/E,IAAIwB,SAAS;YACb,IAAID,cAAcT,MAAMd,MAAM,EAAE;gBAC9B,MAAMyB,QAAQX,KAAK,CAACS,YAAY,CAACE,KAAK,CAAC;gBACvC,IAAIA,OACFD,SAASC,KAAK,CAAC,EAAE;YAErB;YAEA,6DAA6D;YAC7D,MAAMC,mBAAmBC,KAAKC,GAAG,CAAC,GAAGL,cAAc;YACnD,MAAMM,iBAAiBF,KAAKG,GAAG,CAAChB,MAAMd,MAAM,GAAG,GAAGuB,cAAc;YAChE,IAAIQ,iBAAiB;YACrB,IAAK,IAAIC,IAAIN,kBAAkBM,KAAKH,gBAAgBG,IAClD,IAAIlB,KAAK,CAACkB,EAAE,CAACC,QAAQ,CAAC,eAAe;gBACnCF,iBAAiB;gBACjB;YACF;YAGF,IAAIA,gBACF;YAGFjB,MAAMoB,MAAM,CAACX,aAAa,GAAG,GAAGC,OAAO,WAAW,CAAC;YACnDJ,WAAW;YACXlB;QACF;QAEA,IAAIkB,UAAU;YACZ,MAAMe,IAAAA,mBAAS,EAAC3B,UAAUM,MAAMsB,IAAI,CAAC,OAAO;YAC5CnC;QACF;IACF;IAEA,OAAO;QAAEA;QAAeC;IAAmB;AAC7C"}
@@ -0,0 +1,16 @@
1
+ import type { FunctionAnalysis } from './types';
2
+ /**
3
+ * Print a coverage report of all function analyses, grouped by package.
4
+ * Always prints a summary table. With `verbose`, also prints a per-function table.
5
+ */
6
+ export declare function printCoverageReport(results: FunctionAnalysis[], workspaceRoot: string, verbose: boolean, fullReasons: boolean): void;
7
+ /**
8
+ * Print an overall coverage summary.
9
+ */
10
+ export declare function printCoverageSummary(results: FunctionAnalysis[], verbose: boolean): void;
11
+ /**
12
+ * Print a "Migration Candidates" section — functions that compile successfully
13
+ * and also use manual memoization (useMemo, useCallback, React.memo).
14
+ * These are candidates for adding 'use memo' and removing manual hooks.
15
+ */
16
+ export declare function printMigrationCandidates(results: FunctionAnalysis[], workspaceRoot: string): void;
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: all[name]
9
+ });
10
+ }
11
+ _export(exports, {
12
+ printCoverageReport: function() {
13
+ return printCoverageReport;
14
+ },
15
+ printCoverageSummary: function() {
16
+ return printCoverageSummary;
17
+ },
18
+ printMigrationCandidates: function() {
19
+ return printMigrationCandidates;
20
+ }
21
+ });
22
+ const _nodepath = require("node:path");
23
+ const TABLE_REASON_MAX_LEN = 80;
24
+ function printCoverageReport(results, workspaceRoot, verbose, fullReasons) {
25
+ if (results.length === 0) {
26
+ console.log('\nNo functions analyzed by the compiler.');
27
+ return;
28
+ }
29
+ // Group by package
30
+ const byPackage = new Map();
31
+ for (const r of results){
32
+ const existing = byPackage.get(r.packageName) ?? [];
33
+ existing.push(r);
34
+ byPackage.set(r.packageName, existing);
35
+ }
36
+ const sortedPackages = [
37
+ ...byPackage.keys()
38
+ ].sort();
39
+ for (const pkg of sortedPackages){
40
+ const pkgResults = byPackage.get(pkg);
41
+ console.log(`\n## ${pkg}\n`);
42
+ printPackageSummaryTable(pkgResults);
43
+ if (verbose) {
44
+ const compiled = pkgResults.filter((r)=>r.status === 'compiled');
45
+ const skipped = pkgResults.filter((r)=>r.status === 'skipped');
46
+ const errored = pkgResults.filter((r)=>r.status === 'error');
47
+ if (compiled.length > 0) {
48
+ console.log('### Compiled (will be memoized)\n');
49
+ printFunctionTable(compiled, workspaceRoot, fullReasons, true);
50
+ }
51
+ if (skipped.length > 0) {
52
+ console.log('### Skipped (not a component/hook)\n');
53
+ printFunctionTable(skipped, workspaceRoot, fullReasons, false);
54
+ }
55
+ if (errored.length > 0) {
56
+ console.log('### Errors (compiler bailout)\n');
57
+ printFunctionTable(errored, workspaceRoot, fullReasons, false);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ function printPackageSummaryTable(results) {
63
+ const total = results.length;
64
+ const compiled = results.filter((r)=>r.status === 'compiled').length;
65
+ const skipped = results.filter((r)=>r.status === 'skipped').length;
66
+ const errored = results.filter((r)=>r.status === 'error').length;
67
+ console.log('| Status | Count | Percentage |');
68
+ console.log('|--------|-------|------------|');
69
+ console.log(`| Compiled | ${compiled} | ${pct(compiled, total)} |`);
70
+ console.log(`| Skipped | ${skipped} | ${pct(skipped, total)} |`);
71
+ console.log(`| Errors | ${errored} | ${pct(errored, total)} |`);
72
+ console.log(`| **Total** | **${total}** | |`);
73
+ console.log('');
74
+ }
75
+ function printFunctionTable(results, workspaceRoot, fullReasons, showMemoStats) {
76
+ if (showMemoStats) {
77
+ console.log('| Location | Function | Memo Slots | Memo Blocks | Memo Values |');
78
+ console.log('|----------|----------|------------|-------------|-------------|');
79
+ for (const r of results){
80
+ const relPath = (0, _nodepath.relative)(workspaceRoot, r.filePath);
81
+ const fn = r.functionName ?? '(anonymous)';
82
+ const stats = r.memoStats;
83
+ console.log(`| ${relPath}:${r.line} | ${fn} | ${stats?.memoSlots ?? 0} | ${stats?.memoBlocks ?? 0} | ${stats?.memoValues ?? 0} |`);
84
+ }
85
+ } else {
86
+ console.log('| Location | Function | Compiler Event | Reason |');
87
+ console.log('|----------|----------|----------------|--------|');
88
+ for (const r of results){
89
+ const relPath = (0, _nodepath.relative)(workspaceRoot, r.filePath);
90
+ const fn = r.functionName ?? '(anonymous)';
91
+ const reason = r.reason ? fullReasons ? escapeTableCell(r.reason) : truncate(r.reason, TABLE_REASON_MAX_LEN) : '';
92
+ console.log(`| ${relPath}:${r.line} | ${fn} | ${r.compilerEvent} | ${reason} |`);
93
+ }
94
+ }
95
+ console.log('');
96
+ if (fullReasons) {
97
+ const withReasons = results.filter((r)=>r.reason && r.reason.includes('\n'));
98
+ if (withReasons.length > 0) {
99
+ console.log('<details><summary>Full compiler output</summary>\n');
100
+ for (const r of withReasons){
101
+ const relPath = (0, _nodepath.relative)(workspaceRoot, r.filePath);
102
+ const fn = r.functionName ?? '(anonymous)';
103
+ console.log(`#### ${relPath}:${r.line} — ${fn}\n`);
104
+ console.log('```');
105
+ console.log(r.reason);
106
+ console.log('```\n');
107
+ }
108
+ console.log('</details>\n');
109
+ }
110
+ }
111
+ }
112
+ function printCoverageSummary(results, verbose) {
113
+ const total = results.length;
114
+ const compiled = results.filter((r)=>r.status === 'compiled').length;
115
+ const skipped = results.filter((r)=>r.status === 'skipped').length;
116
+ const errored = results.filter((r)=>r.status === 'error').length;
117
+ console.log('## Summary\n');
118
+ console.log(`- **Total functions analyzed:** ${total}`);
119
+ console.log(`- **Compiled** (will be memoized): ${compiled} (${pct(compiled, total)})`);
120
+ console.log(`- **Skipped** (not a component/hook): ${skipped} (${pct(skipped, total)})`);
121
+ console.log(`- **Errors** (compiler bailout): ${errored} (${pct(errored, total)})`);
122
+ console.log('');
123
+ if (total === 0) console.log('> No functions were analyzed. The directory may not contain React components or hooks.\n');
124
+ else if (errored > 0) {
125
+ console.log(`> **${errored}** function(s) caused compiler errors — these won't be optimized until the patterns are refactored.`);
126
+ console.log('> Run with `--verbose` to see per-function details.\n');
127
+ } else console.log('> All recognized functions compile successfully.\n');
128
+ if (verbose) {
129
+ console.log('### Legend\n');
130
+ console.log('| Term | Meaning |');
131
+ console.log('|------|---------|');
132
+ console.log('| **Memo Slots** | Total number of cache slots the compiler allocates for a function. Each memoized value or block occupies one slot. |');
133
+ console.log('| **Memo Blocks** | Number of memoized code blocks (JSX elements, conditional branches, etc.) that the compiler wraps with cache checks. |');
134
+ console.log('| **Memo Values** | Number of individual memoized values (variables, expressions, hook results) that the compiler caches between renders. |');
135
+ console.log('');
136
+ }
137
+ }
138
+ function printMigrationCandidates(results, workspaceRoot) {
139
+ const candidates = results.filter((r)=>r.status === 'compiled' && r.manualMemo);
140
+ if (candidates.length === 0) return;
141
+ console.log('## Migration Candidates\n');
142
+ console.log("Functions that compile successfully and contain manual memoization. These can safely use `'use memo'` and may have their manual hooks removed.\n");
143
+ console.log('| Location | Function | useMemo | useCallback | React.memo | Memo Slots |');
144
+ console.log('|----------|----------|---------|-------------|------------|------------|');
145
+ for (const r of candidates){
146
+ const relPath = (0, _nodepath.relative)(workspaceRoot, r.filePath);
147
+ const fn = r.functionName ?? '(anonymous)';
148
+ const memo = r.manualMemo;
149
+ const slots = r.memoStats?.memoSlots ?? 0;
150
+ console.log(`| ${relPath}:${r.line} | ${fn} | ${memo.useMemo} | ${memo.useCallback} | ${memo.reactMemo ? 'yes' : 'no'} | ${slots} |`);
151
+ }
152
+ console.log('');
153
+ console.log(`> **${candidates.length}** migration candidate(s) found.\n`);
154
+ }
155
+ function pct(count, total) {
156
+ if (total === 0) return '0%';
157
+ return `${(count / total * 100).toFixed(1)}%`;
158
+ }
159
+ function truncate(str, maxLen) {
160
+ const cleaned = str.replace(/\n/g, ' ');
161
+ return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 3) + '...' : cleaned;
162
+ }
163
+ function escapeTableCell(str) {
164
+ return str.replace(/\n/g, ' ').replace(/\|/g, '\\|');
165
+ }
166
+
167
+ //# sourceMappingURL=coverage-reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/coverage-reporter.ts"],"sourcesContent":["import { relative } from 'node:path';\n\nimport type { FunctionAnalysis } from './types';\n\nconst TABLE_REASON_MAX_LEN = 80;\n\n/**\n * Print a coverage report of all function analyses, grouped by package.\n * Always prints a summary table. With `verbose`, also prints a per-function table.\n */\nexport function printCoverageReport(\n results: FunctionAnalysis[],\n workspaceRoot: string,\n verbose: boolean,\n fullReasons: boolean,\n): void {\n if (results.length === 0) {\n console.log('\\nNo functions analyzed by the compiler.');\n return;\n }\n\n // Group by package\n const byPackage = new Map<string, FunctionAnalysis[]>();\n for (const r of results) {\n const existing = byPackage.get(r.packageName) ?? [];\n existing.push(r);\n byPackage.set(r.packageName, existing);\n }\n\n const sortedPackages = [...byPackage.keys()].sort();\n\n for (const pkg of sortedPackages) {\n const pkgResults = byPackage.get(pkg)!;\n console.log(`\\n## ${pkg}\\n`);\n printPackageSummaryTable(pkgResults);\n\n if (verbose) {\n const compiled = pkgResults.filter(r => r.status === 'compiled');\n const skipped = pkgResults.filter(r => r.status === 'skipped');\n const errored = pkgResults.filter(r => r.status === 'error');\n\n if (compiled.length > 0) {\n console.log('### Compiled (will be memoized)\\n');\n printFunctionTable(compiled, workspaceRoot, fullReasons, true);\n }\n if (skipped.length > 0) {\n console.log('### Skipped (not a component/hook)\\n');\n printFunctionTable(skipped, workspaceRoot, fullReasons, false);\n }\n if (errored.length > 0) {\n console.log('### Errors (compiler bailout)\\n');\n printFunctionTable(errored, workspaceRoot, fullReasons, false);\n }\n }\n }\n}\n\nfunction printPackageSummaryTable(results: FunctionAnalysis[]): void {\n const total = results.length;\n const compiled = results.filter(r => r.status === 'compiled').length;\n const skipped = results.filter(r => r.status === 'skipped').length;\n const errored = results.filter(r => r.status === 'error').length;\n\n console.log('| Status | Count | Percentage |');\n console.log('|--------|-------|------------|');\n console.log(`| Compiled | ${compiled} | ${pct(compiled, total)} |`);\n console.log(`| Skipped | ${skipped} | ${pct(skipped, total)} |`);\n console.log(`| Errors | ${errored} | ${pct(errored, total)} |`);\n console.log(`| **Total** | **${total}** | |`);\n console.log('');\n}\n\nfunction printFunctionTable(\n results: FunctionAnalysis[],\n workspaceRoot: string,\n fullReasons: boolean,\n showMemoStats: boolean,\n): void {\n if (showMemoStats) {\n console.log('| Location | Function | Memo Slots | Memo Blocks | Memo Values |');\n console.log('|----------|----------|------------|-------------|-------------|');\n for (const r of results) {\n const relPath = relative(workspaceRoot, r.filePath);\n const fn = r.functionName ?? '(anonymous)';\n const stats = r.memoStats;\n console.log(\n `| ${relPath}:${r.line} | ${fn} | ${stats?.memoSlots ?? 0} | ${stats?.memoBlocks ?? 0} | ${\n stats?.memoValues ?? 0\n } |`,\n );\n }\n } else {\n console.log('| Location | Function | Compiler Event | Reason |');\n console.log('|----------|----------|----------------|--------|');\n for (const r of results) {\n const relPath = relative(workspaceRoot, r.filePath);\n const fn = r.functionName ?? '(anonymous)';\n const reason = r.reason\n ? fullReasons\n ? escapeTableCell(r.reason)\n : truncate(r.reason, TABLE_REASON_MAX_LEN)\n : '';\n console.log(`| ${relPath}:${r.line} | ${fn} | ${r.compilerEvent} | ${reason} |`);\n }\n }\n console.log('');\n\n if (fullReasons) {\n const withReasons = results.filter(r => r.reason && r.reason.includes('\\n'));\n if (withReasons.length > 0) {\n console.log('<details><summary>Full compiler output</summary>\\n');\n for (const r of withReasons) {\n const relPath = relative(workspaceRoot, r.filePath);\n const fn = r.functionName ?? '(anonymous)';\n console.log(`#### ${relPath}:${r.line} — ${fn}\\n`);\n console.log('```');\n console.log(r.reason);\n console.log('```\\n');\n }\n console.log('</details>\\n');\n }\n }\n}\n\n/**\n * Print an overall coverage summary.\n */\nexport function printCoverageSummary(results: FunctionAnalysis[], verbose: boolean): void {\n const total = results.length;\n const compiled = results.filter(r => r.status === 'compiled').length;\n const skipped = results.filter(r => r.status === 'skipped').length;\n const errored = results.filter(r => r.status === 'error').length;\n\n console.log('## Summary\\n');\n console.log(`- **Total functions analyzed:** ${total}`);\n console.log(`- **Compiled** (will be memoized): ${compiled} (${pct(compiled, total)})`);\n console.log(`- **Skipped** (not a component/hook): ${skipped} (${pct(skipped, total)})`);\n console.log(`- **Errors** (compiler bailout): ${errored} (${pct(errored, total)})`);\n console.log('');\n\n if (total === 0) {\n console.log('> No functions were analyzed. The directory may not contain React components or hooks.\\n');\n } else if (errored > 0) {\n console.log(\n `> **${errored}** function(s) caused compiler errors — these won't be optimized until the patterns are refactored.`,\n );\n console.log('> Run with `--verbose` to see per-function details.\\n');\n } else {\n console.log('> All recognized functions compile successfully.\\n');\n }\n\n if (verbose) {\n console.log('### Legend\\n');\n console.log('| Term | Meaning |');\n console.log('|------|---------|');\n console.log(\n '| **Memo Slots** | Total number of cache slots the compiler allocates for a function. Each memoized value or block occupies one slot. |',\n );\n console.log(\n '| **Memo Blocks** | Number of memoized code blocks (JSX elements, conditional branches, etc.) that the compiler wraps with cache checks. |',\n );\n console.log(\n '| **Memo Values** | Number of individual memoized values (variables, expressions, hook results) that the compiler caches between renders. |',\n );\n console.log('');\n }\n}\n\n/**\n * Print a \"Migration Candidates\" section — functions that compile successfully\n * and also use manual memoization (useMemo, useCallback, React.memo).\n * These are candidates for adding 'use memo' and removing manual hooks.\n */\nexport function printMigrationCandidates(results: FunctionAnalysis[], workspaceRoot: string): void {\n const candidates = results.filter(r => r.status === 'compiled' && r.manualMemo);\n\n if (candidates.length === 0) {\n return;\n }\n\n console.log('## Migration Candidates\\n');\n console.log(\n 'Functions that compile successfully and contain manual memoization. ' +\n \"These can safely use `'use memo'` and may have their manual hooks removed.\\n\",\n );\n\n console.log('| Location | Function | useMemo | useCallback | React.memo | Memo Slots |');\n console.log('|----------|----------|---------|-------------|------------|------------|');\n\n for (const r of candidates) {\n const relPath = relative(workspaceRoot, r.filePath);\n const fn = r.functionName ?? '(anonymous)';\n const memo = r.manualMemo!;\n const slots = r.memoStats?.memoSlots ?? 0;\n console.log(\n `| ${relPath}:${r.line} | ${fn} | ${memo.useMemo} | ${memo.useCallback} | ${\n memo.reactMemo ? 'yes' : 'no'\n } | ${slots} |`,\n );\n }\n\n console.log('');\n console.log(`> **${candidates.length}** migration candidate(s) found.\\n`);\n}\n\nfunction pct(count: number, total: number): string {\n if (total === 0) {\n return '0%';\n }\n return `${((count / total) * 100).toFixed(1)}%`;\n}\n\nfunction truncate(str: string, maxLen: number): string {\n const cleaned = str.replace(/\\n/g, ' ');\n return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 3) + '...' : cleaned;\n}\n\nfunction escapeTableCell(str: string): string {\n return str.replace(/\\n/g, ' ').replace(/\\|/g, '\\\\|');\n}\n"],"names":["printCoverageReport","printCoverageSummary","printMigrationCandidates","TABLE_REASON_MAX_LEN","results","workspaceRoot","verbose","fullReasons","length","console","log","byPackage","Map","r","existing","get","packageName","push","set","sortedPackages","keys","sort","pkg","pkgResults","printPackageSummaryTable","compiled","filter","status","skipped","errored","printFunctionTable","total","pct","showMemoStats","relPath","relative","filePath","fn","functionName","stats","memoStats","line","memoSlots","memoBlocks","memoValues","reason","escapeTableCell","truncate","compilerEvent","withReasons","includes","candidates","manualMemo","memo","slots","useMemo","useCallback","reactMemo","count","toFixed","str","maxLen","cleaned","replace","slice"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { FileEntry } from './types';
2
+ /**
3
+ * Walk up from `startDir` to find the nearest package.json and return its `name` field.
4
+ * Falls back to the basename of `startDir`.
5
+ */
6
+ export declare function findPackageName(startDir: string): Promise<string>;
7
+ /**
8
+ * Discover files containing 'use no memo' in the given directory.
9
+ */
10
+ export declare function discoverDirectiveFiles(scanDir: string, packageName: string, exclude: string[], verbose: boolean): Promise<FileEntry[]>;
11
+ /**
12
+ * Discover all TypeScript files in the given directory (for coverage analysis).
13
+ */
14
+ export declare function discoverAllFiles(scanDir: string, packageName: string, exclude: string[], verbose: boolean): Promise<FileEntry[]>;