@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,271 @@
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
+ analyzeFile: function() {
13
+ return analyzeFile;
14
+ },
15
+ analyzeFiles: function() {
16
+ return analyzeFiles;
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
+ // Regex matching the ESLint rule's justification pattern
24
+ const JUSTIFIED_RE = /^\s*justified:/;
25
+ // Match 'use no memo' as either a plain expression statement or parenthesized
26
+ const USE_NO_MEMO_RE = /^\s*(?:\(?'use no memo'\)?;?\s*)(\/\/.*)?$/;
27
+ /**
28
+ * Parse source text to find all 'use no memo' directive locations.
29
+ */ function findDirectiveLocations(source) {
30
+ const lines = source.split('\n');
31
+ const locations = [];
32
+ for(let i = 0; i < lines.length; i++){
33
+ const line = lines[i];
34
+ if (!USE_NO_MEMO_RE.test(line)) continue;
35
+ // Check for inline justification comment on the same line
36
+ const commentMatch = line.match(/\/\/(.*)$/);
37
+ let justified = false;
38
+ let justification;
39
+ if (commentMatch) {
40
+ const commentText = commentMatch[1];
41
+ if (JUSTIFIED_RE.test(commentText)) {
42
+ justified = true;
43
+ justification = commentText.trim();
44
+ }
45
+ }
46
+ locations.push({
47
+ line: i + 1,
48
+ lineText: line,
49
+ justified,
50
+ justification
51
+ });
52
+ }
53
+ return locations;
54
+ }
55
+ /**
56
+ * Create a modified source with non-justified 'use no memo' directives stripped.
57
+ * Returns the modified source and a line-number mapping (original → new).
58
+ */ function stripDirectives(source, directives) {
59
+ const removedLines = new Set();
60
+ for (const directive of directives)if (!directive.justified) removedLines.add(directive.line);
61
+ if (removedLines.size === 0) return {
62
+ modifiedSource: source,
63
+ removedLines
64
+ };
65
+ const lines = source.split('\n');
66
+ const kept = [];
67
+ for(let i = 0; i < lines.length; i++)if (!removedLines.has(i + 1)) kept.push(lines[i]);
68
+ return {
69
+ modifiedSource: kept.join('\n'),
70
+ removedLines
71
+ };
72
+ }
73
+ /**
74
+ * Build a mapping from modified-source line numbers back to original line numbers.
75
+ * This lets us correlate compiler events (which reference modified-source locations)
76
+ * back to the original source lines.
77
+ */ function buildLineMapping(totalOriginalLines, removedLines) {
78
+ const map = new Map();
79
+ let newLine = 1;
80
+ for(let origLine = 1; origLine <= totalOriginalLines; origLine++)if (!removedLines.has(origLine)) {
81
+ map.set(newLine, origLine);
82
+ newLine++;
83
+ }
84
+ return map;
85
+ }
86
+ /**
87
+ * Try to determine the enclosing function name for a directive at a given line.
88
+ * Uses a simple heuristic: scan backwards for function/arrow/const declarations.
89
+ */ function findEnclosingFunctionName(source, directiveLine) {
90
+ const lines = source.split('\n');
91
+ // Scan backwards from the directive line
92
+ for(let i = directiveLine - 2; i >= 0; i--){
93
+ const line = lines[i];
94
+ // Named function declaration: function useFoo(
95
+ const fnMatch = line.match(/function\s+(\w+)/);
96
+ if (fnMatch) return fnMatch[1];
97
+ // Const/let/var arrow or function expression: const useFoo = (
98
+ const constMatch = line.match(/(?:const|let|var)\s+(\w+)\s*=/);
99
+ if (constMatch) return constMatch[1];
100
+ // Method: useFoo(
101
+ const methodMatch = line.match(/^\s+(\w+)\s*\(/);
102
+ if (methodMatch && !line.includes('if') && !line.includes('for') && !line.includes('while')) return methodMatch[1];
103
+ // Stop at top-level boundaries
104
+ if (line.match(/^(import |export |type |interface )/)) break;
105
+ }
106
+ return null;
107
+ }
108
+ /**
109
+ * For a given compiler event location (in modified source), find which original
110
+ * directive's enclosing function contains this event.
111
+ */ function matchEventToDirective(event, directives, lineMapping, removedLines) {
112
+ if (!event.fnLoc) return null;
113
+ // Map the event's function start/end lines back to original source lines
114
+ const origStartLine = lineMapping.get(event.fnLoc.start.line);
115
+ const origEndLine = lineMapping.get(event.fnLoc.end.line);
116
+ if (origStartLine === undefined || origEndLine === undefined) return null;
117
+ // Find which directive falls within this function's range.
118
+ // The directive was removed, so its original line should be between the function start
119
+ // and the first kept line after function start.
120
+ for (const dir of directives){
121
+ if (dir.justified) continue;
122
+ // The directive was originally between origStartLine and origEndLine
123
+ // Since we removed it, its original line should be >= function start and <= function end
124
+ // Account for the shift: the directive was on dir.line in original, which is now missing
125
+ if (dir.line >= origStartLine && dir.line <= origEndLine + removedLines.size) return dir;
126
+ }
127
+ return null;
128
+ }
129
+ async function analyzeFile(filePath, packageName, verbose) {
130
+ const source = await (0, _promises.readFile)(filePath, 'utf-8');
131
+ const directives = findDirectiveLocations(source);
132
+ if (directives.length === 0) return [];
133
+ const results = [];
134
+ // Handle justified directives (always skipped)
135
+ const justifiedDirs = directives.filter((d)=>d.justified);
136
+ for (const dir of justifiedDirs)results.push({
137
+ filePath,
138
+ packageName,
139
+ line: dir.line,
140
+ functionName: findEnclosingFunctionName(source, dir.line),
141
+ status: 'skipped',
142
+ compilerEvent: 'skipped',
143
+ reason: dir.justification
144
+ });
145
+ const nonJustifiedDirs = directives.filter((d)=>!d.justified);
146
+ if (nonJustifiedDirs.length === 0) return results;
147
+ // Strip non-justified directives and run the compiler
148
+ const { modifiedSource, removedLines } = stripDirectives(source, directives);
149
+ const totalOriginalLines = source.split('\n').length;
150
+ const lineMapping = buildLineMapping(totalOriginalLines, removedLines);
151
+ // Collect compiler events
152
+ const events = [];
153
+ const logger = {
154
+ logEvent: (_filename, event)=>{
155
+ events.push(event);
156
+ }
157
+ };
158
+ const ext = (0, _nodepath.extname)(filePath);
159
+ const isTSX = ext === '.tsx';
160
+ try {
161
+ await (0, _core.transformAsync)(modifiedSource, {
162
+ filename: filePath,
163
+ ast: false,
164
+ code: false,
165
+ babelrc: false,
166
+ configFile: false,
167
+ presets: [
168
+ [
169
+ require.resolve('@babel/preset-typescript'),
170
+ {
171
+ isTSX: isTSX || ext === '.ts',
172
+ allExtensions: true
173
+ }
174
+ ]
175
+ ],
176
+ plugins: [
177
+ [
178
+ require.resolve('babel-plugin-react-compiler'),
179
+ {
180
+ noEmit: true,
181
+ panicThreshold: 'none',
182
+ logger
183
+ }
184
+ ]
185
+ ]
186
+ });
187
+ } catch (err) {
188
+ // If babel itself fails (e.g. syntax error), mark all directives in this file as redundant
189
+ // since we can't determine compiler behavior
190
+ if (verbose) console.error(` babel error in ${filePath}: ${err.message}`);
191
+ const fullTrace = err.stack ?? err.message;
192
+ for (const dir of nonJustifiedDirs)results.push({
193
+ filePath,
194
+ packageName,
195
+ line: dir.line,
196
+ functionName: findEnclosingFunctionName(source, dir.line),
197
+ status: 'redundant',
198
+ compilerEvent: 'none',
199
+ reason: `babel parse error:\n${fullTrace}`
200
+ });
201
+ return results;
202
+ }
203
+ if (verbose) for (const ev of events){
204
+ const loc = ev.fnLoc ? `${ev.fnLoc.start.line}:${ev.fnLoc.start.column}` : '?';
205
+ const name = ev.fnName ?? '';
206
+ console.log(` [${ev.kind}] ${filePath} fn@${loc} ${name}`);
207
+ }
208
+ // Match each non-justified directive to compiler events
209
+ const matched = new Set();
210
+ for (const event of events){
211
+ if (!event.fnLoc) continue;
212
+ const dir = matchEventToDirective(event, nonJustifiedDirs, lineMapping, removedLines);
213
+ if (!dir || matched.has(dir)) continue;
214
+ matched.add(dir);
215
+ if (event.kind === 'CompileSuccess') // The compiler CAN compile this function → the directive was actively preventing optimization
216
+ results.push({
217
+ filePath,
218
+ packageName,
219
+ line: dir.line,
220
+ functionName: event.fnName ?? findEnclosingFunctionName(source, dir.line),
221
+ status: 'active',
222
+ compilerEvent: 'CompileSuccess'
223
+ });
224
+ else if (event.kind === 'CompileError' || event.kind === 'PipelineError') {
225
+ // The compiler bails on this function anyway → the directive is redundant
226
+ const reason = event.kind === 'PipelineError' ? event.data ?? '' : (0, _compilerutils.extractDetailReason)(event.detail);
227
+ results.push({
228
+ filePath,
229
+ packageName,
230
+ line: dir.line,
231
+ functionName: findEnclosingFunctionName(source, dir.line),
232
+ status: 'redundant',
233
+ compilerEvent: event.kind,
234
+ reason
235
+ });
236
+ }
237
+ }
238
+ // Any non-justified directives not matched to any event → dead directive (redundant)
239
+ for (const dir of nonJustifiedDirs)if (!matched.has(dir)) results.push({
240
+ filePath,
241
+ packageName,
242
+ line: dir.line,
243
+ functionName: findEnclosingFunctionName(source, dir.line),
244
+ status: 'redundant',
245
+ compilerEvent: 'none',
246
+ reason: 'no compiler event — function not recognized as React component/hook'
247
+ });
248
+ return results;
249
+ }
250
+ async function analyzeFiles(files, options) {
251
+ const allResults = [];
252
+ const { concurrency, verbose } = options;
253
+ // Simple concurrency pool
254
+ let index = 0;
255
+ async function worker() {
256
+ while(index < files.length){
257
+ const current = index++;
258
+ const entry = files[current];
259
+ if (verbose) console.log(`Analyzing: ${entry.filePath}`);
260
+ const results = await analyzeFile(entry.filePath, entry.packageName, verbose);
261
+ allResults.push(...results);
262
+ }
263
+ }
264
+ const workers = Array.from({
265
+ length: Math.min(concurrency, files.length)
266
+ }, ()=>worker());
267
+ await Promise.all(workers);
268
+ return allResults;
269
+ }
270
+
271
+ //# sourceMappingURL=analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/analyzer.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { extname } from 'node:path';\n\nimport { transformAsync } from '@babel/core';\n\nimport type { DirectiveAnalysis, DirectiveLocation, FileEntry, AnalyzerOptions } from './types';\nimport { extractDetailReason } from './compiler-utils';\nimport type { CompilerEvent } from './compiler-utils';\n\n// Regex matching the ESLint rule's justification pattern\nconst JUSTIFIED_RE = /^\\s*justified:/;\n\n// Match 'use no memo' as either a plain expression statement or parenthesized\nconst USE_NO_MEMO_RE = /^\\s*(?:\\(?'use no memo'\\)?;?\\s*)(\\/\\/.*)?$/;\n\n/**\n * Parse source text to find all 'use no memo' directive locations.\n */\nfunction findDirectiveLocations(source: string): DirectiveLocation[] {\n const lines = source.split('\\n');\n const locations: DirectiveLocation[] = [];\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (!USE_NO_MEMO_RE.test(line)) {\n continue;\n }\n // Check for inline justification comment on the same line\n const commentMatch = line.match(/\\/\\/(.*)$/);\n let justified = false;\n let justification: string | undefined;\n if (commentMatch) {\n const commentText = commentMatch[1];\n if (JUSTIFIED_RE.test(commentText)) {\n justified = true;\n justification = commentText.trim();\n }\n }\n\n locations.push({\n line: i + 1, // 1-based\n lineText: line,\n justified,\n justification,\n });\n }\n\n return locations;\n}\n\n/**\n * Create a modified source with non-justified 'use no memo' directives stripped.\n * Returns the modified source and a line-number mapping (original → new).\n */\nfunction stripDirectives(\n source: string,\n directives: DirectiveLocation[],\n): { modifiedSource: string; removedLines: Set<number> } {\n const removedLines = new Set<number>();\n for (const directive of directives) {\n if (!directive.justified) {\n removedLines.add(directive.line);\n }\n }\n\n if (removedLines.size === 0) {\n return { modifiedSource: source, removedLines };\n }\n\n const lines = source.split('\\n');\n const kept: string[] = [];\n for (let i = 0; i < lines.length; i++) {\n if (!removedLines.has(i + 1)) {\n kept.push(lines[i]);\n }\n }\n\n return { modifiedSource: kept.join('\\n'), removedLines };\n}\n\n/**\n * Build a mapping from modified-source line numbers back to original line numbers.\n * This lets us correlate compiler events (which reference modified-source locations)\n * back to the original source lines.\n */\nfunction buildLineMapping(totalOriginalLines: number, removedLines: Set<number>): Map<number, number> {\n const map = new Map<number, number>();\n let newLine = 1;\n for (let origLine = 1; origLine <= totalOriginalLines; origLine++) {\n if (!removedLines.has(origLine)) {\n map.set(newLine, origLine);\n newLine++;\n }\n }\n return map;\n}\n\n/**\n * Try to determine the enclosing function name for a directive at a given line.\n * Uses a simple heuristic: scan backwards for function/arrow/const declarations.\n */\nfunction findEnclosingFunctionName(source: string, directiveLine: number): string | null {\n const lines = source.split('\\n');\n // Scan backwards from the directive line\n for (let i = directiveLine - 2; i >= 0; i--) {\n const line = lines[i];\n // Named function declaration: function useFoo(\n const fnMatch = line.match(/function\\s+(\\w+)/);\n if (fnMatch) {\n return fnMatch[1];\n }\n // Const/let/var arrow or function expression: const useFoo = (\n const constMatch = line.match(/(?:const|let|var)\\s+(\\w+)\\s*=/);\n if (constMatch) {\n return constMatch[1];\n }\n // Method: useFoo(\n const methodMatch = line.match(/^\\s+(\\w+)\\s*\\(/);\n if (methodMatch && !line.includes('if') && !line.includes('for') && !line.includes('while')) {\n return methodMatch[1];\n }\n // Stop at top-level boundaries\n if (line.match(/^(import |export |type |interface )/)) {\n break;\n }\n }\n return null;\n}\n\n/**\n * For a given compiler event location (in modified source), find which original\n * directive's enclosing function contains this event.\n */\nfunction matchEventToDirective(\n event: CompilerEvent,\n directives: DirectiveLocation[],\n lineMapping: Map<number, number>,\n removedLines: Set<number>,\n): DirectiveLocation | null {\n if (!event.fnLoc) {\n return null;\n }\n\n // Map the event's function start/end lines back to original source lines\n const origStartLine = lineMapping.get(event.fnLoc.start.line);\n const origEndLine = lineMapping.get(event.fnLoc.end.line);\n\n if (origStartLine === undefined || origEndLine === undefined) {\n return null;\n }\n\n // Find which directive falls within this function's range.\n // The directive was removed, so its original line should be between the function start\n // and the first kept line after function start.\n for (const dir of directives) {\n if (dir.justified) {\n continue;\n }\n // The directive was originally between origStartLine and origEndLine\n // Since we removed it, its original line should be >= function start and <= function end\n // Account for the shift: the directive was on dir.line in original, which is now missing\n if (dir.line >= origStartLine && dir.line <= origEndLine + removedLines.size) {\n return dir;\n }\n }\n return null;\n}\n\n/**\n * Analyze a single file for redundant 'use no memo' directives.\n */\nexport async function analyzeFile(\n filePath: string,\n packageName: string,\n verbose: boolean,\n): Promise<DirectiveAnalysis[]> {\n const source = await readFile(filePath, 'utf-8');\n const directives = findDirectiveLocations(source);\n\n if (directives.length === 0) {\n return [];\n }\n\n const results: DirectiveAnalysis[] = [];\n\n // Handle justified directives (always skipped)\n const justifiedDirs = directives.filter(d => d.justified);\n for (const dir of justifiedDirs) {\n results.push({\n filePath,\n packageName,\n line: dir.line,\n functionName: findEnclosingFunctionName(source, dir.line),\n status: 'skipped',\n compilerEvent: 'skipped',\n reason: dir.justification,\n });\n }\n\n const nonJustifiedDirs = directives.filter(d => !d.justified);\n if (nonJustifiedDirs.length === 0) {\n return results;\n }\n\n // Strip non-justified directives and run the compiler\n const { modifiedSource, removedLines } = stripDirectives(source, directives);\n const totalOriginalLines = source.split('\\n').length;\n const lineMapping = buildLineMapping(totalOriginalLines, removedLines);\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 try {\n await transformAsync(modifiedSource, {\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', // enable JSX parsing for .ts too (some .ts files use JSX pragma)\n allExtensions: true,\n },\n ],\n ],\n plugins: [\n [\n require.resolve('babel-plugin-react-compiler'),\n {\n noEmit: true,\n panicThreshold: 'none',\n logger,\n },\n ],\n ],\n });\n } catch (err) {\n // If babel itself fails (e.g. syntax error), mark all directives in this file as redundant\n // since we can't determine compiler behavior\n if (verbose) {\n console.error(` babel error in ${filePath}: ${(err as Error).message}`);\n }\n const fullTrace = (err as Error).stack ?? (err as Error).message;\n for (const dir of nonJustifiedDirs) {\n results.push({\n filePath,\n packageName,\n line: dir.line,\n functionName: findEnclosingFunctionName(source, dir.line),\n status: 'redundant',\n compilerEvent: 'none',\n reason: `babel parse error:\\n${fullTrace}`,\n });\n }\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 as { fnName?: string }).fnName ?? '';\n console.log(` [${ev.kind}] ${filePath} fn@${loc} ${name}`);\n }\n }\n\n // Match each non-justified directive to compiler events\n const matched = new Set<DirectiveLocation>();\n\n for (const event of events) {\n if (!event.fnLoc) {\n continue;\n }\n\n const dir = matchEventToDirective(event, nonJustifiedDirs, lineMapping, removedLines);\n if (!dir || matched.has(dir)) {\n continue;\n }\n\n matched.add(dir);\n\n if (event.kind === 'CompileSuccess') {\n // The compiler CAN compile this function → the directive was actively preventing optimization\n results.push({\n filePath,\n packageName,\n line: dir.line,\n functionName: (event as { fnName?: string | null }).fnName ?? findEnclosingFunctionName(source, dir.line),\n status: 'active',\n compilerEvent: 'CompileSuccess',\n });\n } else if (event.kind === 'CompileError' || event.kind === 'PipelineError') {\n // The compiler bails on this function anyway → the directive is redundant\n const reason =\n event.kind === 'PipelineError' ? (event as { data?: string }).data ?? '' : extractDetailReason(event.detail);\n results.push({\n filePath,\n packageName,\n line: dir.line,\n functionName: findEnclosingFunctionName(source, dir.line),\n status: 'redundant',\n compilerEvent: event.kind as 'CompileError' | 'PipelineError',\n reason,\n });\n }\n }\n\n // Any non-justified directives not matched to any event → dead directive (redundant)\n for (const dir of nonJustifiedDirs) {\n if (!matched.has(dir)) {\n results.push({\n filePath,\n packageName,\n line: dir.line,\n functionName: findEnclosingFunctionName(source, dir.line),\n status: 'redundant',\n compilerEvent: 'none',\n reason: 'no compiler event — function not recognized as React component/hook',\n });\n }\n }\n\n return results;\n}\n\n/**\n * Analyze multiple files with concurrency-limited parallelism.\n */\nexport async function analyzeFiles(files: FileEntry[], options: AnalyzerOptions): Promise<DirectiveAnalysis[]> {\n const allResults: DirectiveAnalysis[] = [];\n const { concurrency, verbose } = options;\n\n // Simple concurrency pool\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 analyzeFile(entry.filePath, entry.packageName, 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":["analyzeFile","analyzeFiles","JUSTIFIED_RE","USE_NO_MEMO_RE","findDirectiveLocations","source","lines","split","locations","i","length","line","test","commentMatch","match","justified","justification","commentText","trim","push","lineText","stripDirectives","directives","removedLines","Set","directive","add","size","modifiedSource","kept","has","join","buildLineMapping","totalOriginalLines","map","Map","newLine","origLine","set","findEnclosingFunctionName","directiveLine","fnMatch","constMatch","methodMatch","includes","matchEventToDirective","event","lineMapping","fnLoc","origStartLine","get","start","origEndLine","end","undefined","dir","filePath","packageName","verbose","readFile","results","justifiedDirs","filter","d","functionName","status","compilerEvent","reason","nonJustifiedDirs","events","logger","logEvent","_filename","ext","extname","isTSX","transformAsync","filename","ast","code","babelrc","configFile","presets","require","resolve","allExtensions","plugins","noEmit","panicThreshold","err","console","error","message","fullTrace","stack","ev","loc","column","name","fnName","log","kind","matched","data","extractDetailReason","detail","files","options","allResults","concurrency","index","worker","current","entry","workers","Array","from","Math","min","Promise","all"],"mappings":";;;;;;;;;;;IA2KsBA,WAAW;eAAXA;;IAuKAC,YAAY;eAAZA;;;0BAlVG;0BACD;sBAEO;+BAGK;AAGpC,yDAAyD;AACzD,MAAMC,eAAe;AAErB,8EAA8E;AAC9E,MAAMC,iBAAiB;AAEvB;;CAEC,GACD,SAASC,uBAAuBC,MAAc;IAC5C,MAAMC,QAAQD,OAAOE,KAAK,CAAC;IAC3B,MAAMC,YAAiC,EAAE;IAEzC,IAAK,IAAIC,IAAI,GAAGA,IAAIH,MAAMI,MAAM,EAAED,IAAK;QACrC,MAAME,OAAOL,KAAK,CAACG,EAAE;QACrB,IAAI,CAACN,eAAeS,IAAI,CAACD,OACvB;QAEF,0DAA0D;QAC1D,MAAME,eAAeF,KAAKG,KAAK,CAAC;QAChC,IAAIC,YAAY;QAChB,IAAIC;QACJ,IAAIH,cAAc;YAChB,MAAMI,cAAcJ,YAAY,CAAC,EAAE;YACnC,IAAIX,aAAaU,IAAI,CAACK,cAAc;gBAClCF,YAAY;gBACZC,gBAAgBC,YAAYC,IAAI;YAClC;QACF;QAEAV,UAAUW,IAAI,CAAC;YACbR,MAAMF,IAAI;YACVW,UAAUT;YACVI;YACAC;QACF;IACF;IAEA,OAAOR;AACT;AAEA;;;CAGC,GACD,SAASa,gBACPhB,MAAc,EACdiB,UAA+B;IAE/B,MAAMC,eAAe,IAAIC;IACzB,KAAK,MAAMC,aAAaH,WACtB,IAAI,CAACG,UAAUV,SAAS,EACtBQ,aAAaG,GAAG,CAACD,UAAUd,IAAI;IAInC,IAAIY,aAAaI,IAAI,KAAK,GACxB,OAAO;QAAEC,gBAAgBvB;QAAQkB;IAAa;IAGhD,MAAMjB,QAAQD,OAAOE,KAAK,CAAC;IAC3B,MAAMsB,OAAiB,EAAE;IACzB,IAAK,IAAIpB,IAAI,GAAGA,IAAIH,MAAMI,MAAM,EAAED,IAChC,IAAI,CAACc,aAAaO,GAAG,CAACrB,IAAI,IACxBoB,KAAKV,IAAI,CAACb,KAAK,CAACG,EAAE;IAItB,OAAO;QAAEmB,gBAAgBC,KAAKE,IAAI,CAAC;QAAOR;IAAa;AACzD;AAEA;;;;CAIC,GACD,SAASS,iBAAiBC,kBAA0B,EAAEV,YAAyB;IAC7E,MAAMW,MAAM,IAAIC;IAChB,IAAIC,UAAU;IACd,IAAK,IAAIC,WAAW,GAAGA,YAAYJ,oBAAoBI,WACrD,IAAI,CAACd,aAAaO,GAAG,CAACO,WAAW;QAC/BH,IAAII,GAAG,CAACF,SAASC;QACjBD;IACF;IAEF,OAAOF;AACT;AAEA;;;CAGC,GACD,SAASK,0BAA0BlC,MAAc,EAAEmC,aAAqB;IACtE,MAAMlC,QAAQD,OAAOE,KAAK,CAAC;IAC3B,yCAAyC;IACzC,IAAK,IAAIE,IAAI+B,gBAAgB,GAAG/B,KAAK,GAAGA,IAAK;QAC3C,MAAME,OAAOL,KAAK,CAACG,EAAE;QACrB,+CAA+C;QAC/C,MAAMgC,UAAU9B,KAAKG,KAAK,CAAC;QAC3B,IAAI2B,SACF,OAAOA,OAAO,CAAC,EAAE;QAEnB,+DAA+D;QAC/D,MAAMC,aAAa/B,KAAKG,KAAK,CAAC;QAC9B,IAAI4B,YACF,OAAOA,UAAU,CAAC,EAAE;QAEtB,kBAAkB;QAClB,MAAMC,cAAchC,KAAKG,KAAK,CAAC;QAC/B,IAAI6B,eAAe,CAAChC,KAAKiC,QAAQ,CAAC,SAAS,CAACjC,KAAKiC,QAAQ,CAAC,UAAU,CAACjC,KAAKiC,QAAQ,CAAC,UACjF,OAAOD,WAAW,CAAC,EAAE;QAEvB,+BAA+B;QAC/B,IAAIhC,KAAKG,KAAK,CAAC,wCACb;IAEJ;IACA,OAAO;AACT;AAEA;;;CAGC,GACD,SAAS+B,sBACPC,KAAoB,EACpBxB,UAA+B,EAC/ByB,WAAgC,EAChCxB,YAAyB;IAEzB,IAAI,CAACuB,MAAME,KAAK,EACd,OAAO;IAGT,yEAAyE;IACzE,MAAMC,gBAAgBF,YAAYG,GAAG,CAACJ,MAAME,KAAK,CAACG,KAAK,CAACxC,IAAI;IAC5D,MAAMyC,cAAcL,YAAYG,GAAG,CAACJ,MAAME,KAAK,CAACK,GAAG,CAAC1C,IAAI;IAExD,IAAIsC,kBAAkBK,aAAaF,gBAAgBE,WACjD,OAAO;IAGT,2DAA2D;IAC3D,uFAAuF;IACvF,gDAAgD;IAChD,KAAK,MAAMC,OAAOjC,WAAY;QAC5B,IAAIiC,IAAIxC,SAAS,EACf;QAEF,qEAAqE;QACrE,yFAAyF;QACzF,yFAAyF;QACzF,IAAIwC,IAAI5C,IAAI,IAAIsC,iBAAiBM,IAAI5C,IAAI,IAAIyC,cAAc7B,aAAaI,IAAI,EAC1E,OAAO4B;IAEX;IACA,OAAO;AACT;AAKO,eAAevD,YACpBwD,QAAgB,EAChBC,WAAmB,EACnBC,OAAgB;IAEhB,MAAMrD,SAAS,MAAMsD,IAAAA,kBAAQ,EAACH,UAAU;IACxC,MAAMlC,aAAalB,uBAAuBC;IAE1C,IAAIiB,WAAWZ,MAAM,KAAK,GACxB,OAAO,EAAE;IAGX,MAAMkD,UAA+B,EAAE;IAEvC,+CAA+C;IAC/C,MAAMC,gBAAgBvC,WAAWwC,MAAM,CAACC,CAAAA,IAAKA,EAAEhD,SAAS;IACxD,KAAK,MAAMwC,OAAOM,cAChBD,QAAQzC,IAAI,CAAC;QACXqC;QACAC;QACA9C,MAAM4C,IAAI5C,IAAI;QACdqD,cAAczB,0BAA0BlC,QAAQkD,IAAI5C,IAAI;QACxDsD,QAAQ;QACRC,eAAe;QACfC,QAAQZ,IAAIvC,aAAa;IAC3B;IAGF,MAAMoD,mBAAmB9C,WAAWwC,MAAM,CAACC,CAAAA,IAAK,CAACA,EAAEhD,SAAS;IAC5D,IAAIqD,iBAAiB1D,MAAM,KAAK,GAC9B,OAAOkD;IAGT,sDAAsD;IACtD,MAAM,EAAEhC,cAAc,EAAEL,YAAY,EAAE,GAAGF,gBAAgBhB,QAAQiB;IACjE,MAAMW,qBAAqB5B,OAAOE,KAAK,CAAC,MAAMG,MAAM;IACpD,MAAMqC,cAAcf,iBAAiBC,oBAAoBV;IAEzD,0BAA0B;IAC1B,MAAM8C,SAA0B,EAAE;IAClC,MAAMC,SAAS;QACbC,UAAU,CAACC,WAA0B1B;YACnCuB,OAAOlD,IAAI,CAAC2B;QACd;IACF;IAEA,MAAM2B,MAAMC,IAAAA,iBAAO,EAAClB;IACpB,MAAMmB,QAAQF,QAAQ;IAEtB,IAAI;QACF,MAAMG,IAAAA,oBAAc,EAAChD,gBAAgB;YACnCiD,UAAUrB;YACVsB,KAAK;YACLC,MAAM;YACNC,SAAS;YACTC,YAAY;YACZC,SAAS;gBACP;oBACEC,QAAQC,OAAO,CAAC;oBAChB;wBACET,OAAOA,SAASF,QAAQ;wBACxBY,eAAe;oBACjB;iBACD;aACF;YACDC,SAAS;gBACP;oBACEH,QAAQC,OAAO,CAAC;oBAChB;wBACEG,QAAQ;wBACRC,gBAAgB;wBAChBlB;oBACF;iBACD;aACF;QACH;IACF,EAAE,OAAOmB,KAAK;QACZ,2FAA2F;QAC3F,6CAA6C;QAC7C,IAAI/B,SACFgC,QAAQC,KAAK,CAAC,CAAC,iBAAiB,EAAEnC,SAAS,EAAE,EAAE,AAACiC,IAAcG,OAAO,EAAE;QAEzE,MAAMC,YAAY,AAACJ,IAAcK,KAAK,IAAI,AAACL,IAAcG,OAAO;QAChE,KAAK,MAAMrC,OAAOa,iBAChBR,QAAQzC,IAAI,CAAC;YACXqC;YACAC;YACA9C,MAAM4C,IAAI5C,IAAI;YACdqD,cAAczB,0BAA0BlC,QAAQkD,IAAI5C,IAAI;YACxDsD,QAAQ;YACRC,eAAe;YACfC,QAAQ,CAAC,oBAAoB,EAAE0B,WAAW;QAC5C;QAEF,OAAOjC;IACT;IAEA,IAAIF,SACF,KAAK,MAAMqC,MAAM1B,OAAQ;QACvB,MAAM2B,MAAMD,GAAG/C,KAAK,GAAG,GAAG+C,GAAG/C,KAAK,CAACG,KAAK,CAACxC,IAAI,CAAC,CAAC,EAAEoF,GAAG/C,KAAK,CAACG,KAAK,CAAC8C,MAAM,EAAE,GAAG;QAC3E,MAAMC,OAAO,AAACH,GAA2BI,MAAM,IAAI;QACnDT,QAAQU,GAAG,CAAC,CAAC,GAAG,EAAEL,GAAGM,IAAI,CAAC,EAAE,EAAE7C,SAAS,IAAI,EAAEwC,IAAI,CAAC,EAAEE,MAAM;IAC5D;IAGF,wDAAwD;IACxD,MAAMI,UAAU,IAAI9E;IAEpB,KAAK,MAAMsB,SAASuB,OAAQ;QAC1B,IAAI,CAACvB,MAAME,KAAK,EACd;QAGF,MAAMO,MAAMV,sBAAsBC,OAAOsB,kBAAkBrB,aAAaxB;QACxE,IAAI,CAACgC,OAAO+C,QAAQxE,GAAG,CAACyB,MACtB;QAGF+C,QAAQ5E,GAAG,CAAC6B;QAEZ,IAAIT,MAAMuD,IAAI,KAAK,kBACjB,8FAA8F;QAC9FzC,QAAQzC,IAAI,CAAC;YACXqC;YACAC;YACA9C,MAAM4C,IAAI5C,IAAI;YACdqD,cAAc,AAAClB,MAAqCqD,MAAM,IAAI5D,0BAA0BlC,QAAQkD,IAAI5C,IAAI;YACxGsD,QAAQ;YACRC,eAAe;QACjB;aACK,IAAIpB,MAAMuD,IAAI,KAAK,kBAAkBvD,MAAMuD,IAAI,KAAK,iBAAiB;YAC1E,0EAA0E;YAC1E,MAAMlC,SACJrB,MAAMuD,IAAI,KAAK,kBAAkB,AAACvD,MAA4ByD,IAAI,IAAI,KAAKC,IAAAA,kCAAmB,EAAC1D,MAAM2D,MAAM;YAC7G7C,QAAQzC,IAAI,CAAC;gBACXqC;gBACAC;gBACA9C,MAAM4C,IAAI5C,IAAI;gBACdqD,cAAczB,0BAA0BlC,QAAQkD,IAAI5C,IAAI;gBACxDsD,QAAQ;gBACRC,eAAepB,MAAMuD,IAAI;gBACzBlC;YACF;QACF;IACF;IAEA,qFAAqF;IACrF,KAAK,MAAMZ,OAAOa,iBAChB,IAAI,CAACkC,QAAQxE,GAAG,CAACyB,MACfK,QAAQzC,IAAI,CAAC;QACXqC;QACAC;QACA9C,MAAM4C,IAAI5C,IAAI;QACdqD,cAAczB,0BAA0BlC,QAAQkD,IAAI5C,IAAI;QACxDsD,QAAQ;QACRC,eAAe;QACfC,QAAQ;IACV;IAIJ,OAAOP;AACT;AAKO,eAAe3D,aAAayG,KAAkB,EAAEC,OAAwB;IAC7E,MAAMC,aAAkC,EAAE;IAC1C,MAAM,EAAEC,WAAW,EAAEnD,OAAO,EAAE,GAAGiD;IAEjC,0BAA0B;IAC1B,IAAIG,QAAQ;IAEZ,eAAeC;QACb,MAAOD,QAAQJ,MAAMhG,MAAM,CAAE;YAC3B,MAAMsG,UAAUF;YAChB,MAAMG,QAAQP,KAAK,CAACM,QAAQ;YAE5B,IAAItD,SACFgC,QAAQU,GAAG,CAAC,CAAC,WAAW,EAAEa,MAAMzD,QAAQ,EAAE;YAG5C,MAAMI,UAAU,MAAM5D,YAAYiH,MAAMzD,QAAQ,EAAEyD,MAAMxD,WAAW,EAAEC;YACrEkD,WAAWzF,IAAI,IAAIyC;QACrB;IACF;IAEA,MAAMsD,UAAUC,MAAMC,IAAI,CAAC;QAAE1G,QAAQ2G,KAAKC,GAAG,CAACT,aAAaH,MAAMhG,MAAM;IAAE,GAAG,IAAMqG;IAClF,MAAMQ,QAAQC,GAAG,CAACN;IAElB,OAAON;AACT"}
@@ -0,0 +1 @@
1
+ export declare function cli(): Promise<void>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "cli", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return cli;
9
+ }
10
+ });
11
+ const _interop_require_default = require("@swc/helpers/_/_interop_require_default");
12
+ const _yargs = /*#__PURE__*/ _interop_require_default._(require("yargs"));
13
+ const _coverage = require("./commands/coverage");
14
+ const _directives = require("./commands/directives");
15
+ async function cli() {
16
+ await (0, _yargs.default)(process.argv.slice(2)).scriptName('react-compiler-analyzer').usage('Analyze React Compiler behavior on TypeScript source files.\n\nUsage: $0 <command> <path>').command(_directives.directivesCommand).command(_coverage.coverageCommand).demandCommand(1, 'You must specify a command. Use --help to see available commands.').strict().help().parse();
17
+ }
18
+
19
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli.ts"],"sourcesContent":["import yargs from 'yargs';\n\nimport { coverageCommand } from './commands/coverage';\nimport { directivesCommand } from './commands/directives';\n\nexport async function cli(): Promise<void> {\n await yargs(process.argv.slice(2))\n .scriptName('react-compiler-analyzer')\n .usage('Analyze React Compiler behavior on TypeScript source files.\\n\\nUsage: $0 <command> <path>')\n .command(directivesCommand)\n .command(coverageCommand)\n .demandCommand(1, 'You must specify a command. Use --help to see available commands.')\n .strict()\n .help()\n .parse();\n}\n"],"names":["cli","yargs","process","argv","slice","scriptName","usage","command","directivesCommand","coverageCommand","demandCommand","strict","help","parse"],"mappings":";;;;+BAKsBA;;;eAAAA;;;;gEALJ;0BAEc;4BACE;AAE3B,eAAeA;IACpB,MAAMC,IAAAA,cAAK,EAACC,QAAQC,IAAI,CAACC,KAAK,CAAC,IAC5BC,UAAU,CAAC,2BACXC,KAAK,CAAC,6FACNC,OAAO,CAACC,6BAAiB,EACzBD,OAAO,CAACE,yBAAe,EACvBC,aAAa,CAAC,GAAG,qEACjBC,MAAM,GACNC,IAAI,GACJC,KAAK;AACV"}
@@ -0,0 +1,13 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import type { CompilationMode } from '../types';
3
+ type CoverageArgv = {
4
+ path: string;
5
+ verbose: boolean;
6
+ concurrency: number;
7
+ 'full-reasons': boolean;
8
+ exclude: string[];
9
+ mode: CompilationMode;
10
+ annotate: boolean;
11
+ };
12
+ export declare const coverageCommand: CommandModule<{}, CoverageArgv>;
13
+ export {};
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "coverageCommand", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return coverageCommand;
9
+ }
10
+ });
11
+ const _coverageanalyzer = require("../coverage-analyzer");
12
+ const _coveragefixer = require("../coverage-fixer");
13
+ const _coveragereporter = require("../coverage-reporter");
14
+ const _discovery = require("../discovery");
15
+ const _shared = require("./shared");
16
+ const coverageCommand = {
17
+ command: 'coverage <path>',
18
+ describe: 'Analyze which functions the React Compiler will memoize',
19
+ builder: (yarg)=>(0, _shared.sharedOptions)(yarg).option('mode', {
20
+ type: 'string',
21
+ describe: 'React Compiler compilation mode',
22
+ choices: [
23
+ 'infer',
24
+ 'annotation',
25
+ 'all'
26
+ ],
27
+ default: 'infer'
28
+ }).option('annotate', {
29
+ type: 'boolean',
30
+ describe: "Insert 'use memo' into compilable functions that use manual memoization",
31
+ default: false
32
+ }),
33
+ handler: async (argv)=>{
34
+ const resolvedPath = (0, _shared.validatePath)(argv.path);
35
+ (0, _shared.validateConcurrency)(argv.concurrency);
36
+ console.log('━━ React Compiler Coverage Analyzer ━━\n');
37
+ const packageName = await (0, _discovery.findPackageName)(resolvedPath);
38
+ console.log(`## Scanning: ${resolvedPath}`);
39
+ console.log(` Package: ${packageName}`);
40
+ console.log(` Mode: ${argv.mode}\n`);
41
+ const files = await (0, _discovery.discoverAllFiles)(resolvedPath, packageName, argv.exclude, argv.verbose);
42
+ if (files.length === 0) {
43
+ console.log('No TypeScript files found.');
44
+ process.exit(0);
45
+ }
46
+ console.log(`Files to analyze: ${files.length}\n`);
47
+ const results = await (0, _coverageanalyzer.analyzeFilesForCoverage)(files, {
48
+ concurrency: argv.concurrency,
49
+ verbose: argv.verbose,
50
+ compilationMode: argv.mode
51
+ });
52
+ const workspaceRoot = process.cwd();
53
+ (0, _coveragereporter.printCoverageReport)(results, workspaceRoot, argv.verbose, argv['full-reasons']);
54
+ (0, _coveragereporter.printMigrationCandidates)(results, workspaceRoot);
55
+ (0, _coveragereporter.printCoverageSummary)(results, argv.verbose);
56
+ if (argv.annotate) {
57
+ const outcome = await (0, _coveragefixer.applyAnnotations)(results);
58
+ if (outcome.functionsAnnotated > 0) console.log(`\n✓ Annotated ${outcome.functionsAnnotated} function(s) in ${outcome.filesModified} file(s) with 'use memo'.`);
59
+ else console.log('\nNo functions to annotate.');
60
+ }
61
+ process.exit(0);
62
+ }
63
+ };
64
+
65
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/commands/coverage.ts"],"sourcesContent":["import type { CommandModule } from 'yargs';\n\nimport { analyzeFilesForCoverage } from '../coverage-analyzer';\nimport { applyAnnotations } from '../coverage-fixer';\nimport { printCoverageReport, printCoverageSummary, printMigrationCandidates } from '../coverage-reporter';\nimport { discoverAllFiles, findPackageName } from '../discovery';\nimport type { CompilationMode } from '../types';\nimport { sharedOptions, validateConcurrency, validatePath } from './shared';\n\ntype CoverageArgv = {\n path: string;\n verbose: boolean;\n concurrency: number;\n 'full-reasons': boolean;\n exclude: string[];\n mode: CompilationMode;\n annotate: boolean;\n};\n\nexport const coverageCommand: CommandModule<{}, CoverageArgv> = {\n command: 'coverage <path>',\n describe: 'Analyze which functions the React Compiler will memoize',\n builder: yarg =>\n sharedOptions(yarg)\n .option('mode', {\n type: 'string' as const,\n describe: 'React Compiler compilation mode',\n choices: ['infer', 'annotation', 'all'] as const,\n default: 'infer' as CompilationMode,\n })\n .option('annotate', {\n type: 'boolean' as const,\n describe: \"Insert 'use memo' into compilable functions that use manual memoization\",\n default: false,\n }),\n handler: async argv => {\n const resolvedPath = validatePath(argv.path);\n validateConcurrency(argv.concurrency);\n\n console.log('━━ React Compiler Coverage Analyzer ━━\\n');\n\n const packageName = await findPackageName(resolvedPath);\n\n console.log(`## Scanning: ${resolvedPath}`);\n console.log(` Package: ${packageName}`);\n console.log(` Mode: ${argv.mode}\\n`);\n\n const files = await discoverAllFiles(resolvedPath, packageName, argv.exclude, argv.verbose);\n\n if (files.length === 0) {\n console.log('No TypeScript files found.');\n process.exit(0);\n }\n\n console.log(`Files to analyze: ${files.length}\\n`);\n\n const results = await analyzeFilesForCoverage(files, {\n concurrency: argv.concurrency,\n verbose: argv.verbose,\n compilationMode: argv.mode,\n });\n\n const workspaceRoot = process.cwd();\n printCoverageReport(results, workspaceRoot, argv.verbose, argv['full-reasons']);\n printMigrationCandidates(results, workspaceRoot);\n printCoverageSummary(results, argv.verbose);\n\n if (argv.annotate) {\n const outcome = await applyAnnotations(results);\n if (outcome.functionsAnnotated > 0) {\n console.log(\n `\\n✓ Annotated ${outcome.functionsAnnotated} function(s) in ${outcome.filesModified} file(s) with 'use memo'.`,\n );\n } else {\n console.log('\\nNo functions to annotate.');\n }\n }\n\n process.exit(0);\n },\n};\n"],"names":["coverageCommand","command","describe","builder","yarg","sharedOptions","option","type","choices","default","handler","argv","resolvedPath","validatePath","path","validateConcurrency","concurrency","console","log","packageName","findPackageName","mode","files","discoverAllFiles","exclude","verbose","length","process","exit","results","analyzeFilesForCoverage","compilationMode","workspaceRoot","cwd","printCoverageReport","printMigrationCandidates","printCoverageSummary","annotate","outcome","applyAnnotations","functionsAnnotated","filesModified"],"mappings":";;;;+BAmBaA;;;eAAAA;;;kCAjB2B;+BACP;kCACmD;2BAClC;wBAEe;AAY1D,MAAMA,kBAAmD;IAC9DC,SAAS;IACTC,UAAU;IACVC,SAASC,CAAAA,OACPC,IAAAA,qBAAa,EAACD,MACXE,MAAM,CAAC,QAAQ;YACdC,MAAM;YACNL,UAAU;YACVM,SAAS;gBAAC;gBAAS;gBAAc;aAAM;YACvCC,SAAS;QACX,GACCH,MAAM,CAAC,YAAY;YAClBC,MAAM;YACNL,UAAU;YACVO,SAAS;QACX;IACJC,SAAS,OAAMC;QACb,MAAMC,eAAeC,IAAAA,oBAAY,EAACF,KAAKG,IAAI;QAC3CC,IAAAA,2BAAmB,EAACJ,KAAKK,WAAW;QAEpCC,QAAQC,GAAG,CAAC;QAEZ,MAAMC,cAAc,MAAMC,IAAAA,0BAAe,EAACR;QAE1CK,QAAQC,GAAG,CAAC,CAAC,aAAa,EAAEN,cAAc;QAC1CK,QAAQC,GAAG,CAAC,CAAC,YAAY,EAAEC,aAAa;QACxCF,QAAQC,GAAG,CAAC,CAAC,SAAS,EAAEP,KAAKU,IAAI,CAAC,EAAE,CAAC;QAErC,MAAMC,QAAQ,MAAMC,IAAAA,2BAAgB,EAACX,cAAcO,aAAaR,KAAKa,OAAO,EAAEb,KAAKc,OAAO;QAE1F,IAAIH,MAAMI,MAAM,KAAK,GAAG;YACtBT,QAAQC,GAAG,CAAC;YACZS,QAAQC,IAAI,CAAC;QACf;QAEAX,QAAQC,GAAG,CAAC,CAAC,kBAAkB,EAAEI,MAAMI,MAAM,CAAC,EAAE,CAAC;QAEjD,MAAMG,UAAU,MAAMC,IAAAA,yCAAuB,EAACR,OAAO;YACnDN,aAAaL,KAAKK,WAAW;YAC7BS,SAASd,KAAKc,OAAO;YACrBM,iBAAiBpB,KAAKU,IAAI;QAC5B;QAEA,MAAMW,gBAAgBL,QAAQM,GAAG;QACjCC,IAAAA,qCAAmB,EAACL,SAASG,eAAerB,KAAKc,OAAO,EAAEd,IAAI,CAAC,eAAe;QAC9EwB,IAAAA,0CAAwB,EAACN,SAASG;QAClCI,IAAAA,sCAAoB,EAACP,SAASlB,KAAKc,OAAO;QAE1C,IAAId,KAAK0B,QAAQ,EAAE;YACjB,MAAMC,UAAU,MAAMC,IAAAA,+BAAgB,EAACV;YACvC,IAAIS,QAAQE,kBAAkB,GAAG,GAC/BvB,QAAQC,GAAG,CACT,CAAC,cAAc,EAAEoB,QAAQE,kBAAkB,CAAC,gBAAgB,EAAEF,QAAQG,aAAa,CAAC,yBAAyB,CAAC;iBAGhHxB,QAAQC,GAAG,CAAC;QAEhB;QAEAS,QAAQC,IAAI,CAAC;IACf;AACF"}
@@ -0,0 +1,11 @@
1
+ import type { CommandModule } from 'yargs';
2
+ type DirectivesArgv = {
3
+ path: string;
4
+ verbose: boolean;
5
+ concurrency: number;
6
+ 'full-reasons': boolean;
7
+ exclude: string[];
8
+ fix: boolean;
9
+ };
10
+ export declare const directivesCommand: CommandModule<{}, DirectivesArgv>;
11
+ export {};
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "directivesCommand", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return directivesCommand;
9
+ }
10
+ });
11
+ const _analyzer = require("../analyzer");
12
+ const _discovery = require("../discovery");
13
+ const _fixer = require("../fixer");
14
+ const _reporter = require("../reporter");
15
+ const _shared = require("./shared");
16
+ const directivesCommand = {
17
+ command: 'directives <path>',
18
+ describe: "Analyze and detect redundant 'use no memo' directives",
19
+ builder: (yarg)=>(0, _shared.sharedOptions)(yarg).option('fix', {
20
+ type: 'boolean',
21
+ describe: "Auto-remove redundant 'use no memo' directives from source files",
22
+ default: false
23
+ }),
24
+ handler: async (argv)=>{
25
+ const resolvedPath = (0, _shared.validatePath)(argv.path);
26
+ (0, _shared.validateConcurrency)(argv.concurrency);
27
+ console.log('━━ React Compiler No-Memo Analyzer ━━\n');
28
+ const packageName = await (0, _discovery.findPackageName)(resolvedPath);
29
+ console.log(`## Scanning: ${resolvedPath}`);
30
+ console.log(` Package: ${packageName}\n`);
31
+ const files = await (0, _discovery.discoverDirectiveFiles)(resolvedPath, packageName, argv.exclude, argv.verbose);
32
+ if (files.length === 0) {
33
+ console.log("No files with 'use no memo' directives found.");
34
+ process.exit(0);
35
+ }
36
+ console.log(`Files with 'use no memo': ${files.length}\n`);
37
+ const results = await (0, _analyzer.analyzeFiles)(files, {
38
+ concurrency: argv.concurrency,
39
+ verbose: argv.verbose
40
+ });
41
+ const workspaceRoot = process.cwd();
42
+ (0, _reporter.printReport)(results, workspaceRoot, argv['full-reasons']);
43
+ (0, _reporter.printSummary)(results);
44
+ if (argv.fix) {
45
+ const fixable = results.filter((r)=>r.status === 'redundant' || r.status === 'active');
46
+ if (fixable.length > 0) {
47
+ console.log('Applying fixes...');
48
+ const fixResult = await (0, _fixer.applyFixes)(results);
49
+ const parts = [];
50
+ if (fixResult.directivesRemoved > 0) parts.push(`${fixResult.directivesRemoved} redundant directive(s) removed`);
51
+ if (fixResult.directivesJustified > 0) parts.push(`${fixResult.directivesJustified} active directive(s) annotated with // justified:`);
52
+ console.log(`Fixed: ${parts.join(', ')} across ${fixResult.filesModified} file(s).\n`);
53
+ } else console.log('Nothing to fix.\n');
54
+ }
55
+ const hasRedundant = results.some((r)=>r.status === 'redundant');
56
+ process.exit(hasRedundant && !argv.fix ? 1 : 0);
57
+ }
58
+ };
59
+
60
+ //# sourceMappingURL=directives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/commands/directives.ts"],"sourcesContent":["import type { CommandModule } from 'yargs';\n\nimport { analyzeFiles } from '../analyzer';\nimport { discoverDirectiveFiles, findPackageName } from '../discovery';\nimport { applyFixes } from '../fixer';\nimport { printReport, printSummary } from '../reporter';\nimport { sharedOptions, validateConcurrency, validatePath } from './shared';\n\ntype DirectivesArgv = {\n path: string;\n verbose: boolean;\n concurrency: number;\n 'full-reasons': boolean;\n exclude: string[];\n fix: boolean;\n};\n\nexport const directivesCommand: CommandModule<{}, DirectivesArgv> = {\n command: 'directives <path>',\n describe: \"Analyze and detect redundant 'use no memo' directives\",\n builder: yarg =>\n sharedOptions(yarg).option('fix', {\n type: 'boolean' as const,\n describe: \"Auto-remove redundant 'use no memo' directives from source files\",\n default: false,\n }),\n handler: async argv => {\n const resolvedPath = validatePath(argv.path);\n validateConcurrency(argv.concurrency);\n\n console.log('━━ React Compiler No-Memo Analyzer ━━\\n');\n\n const packageName = await findPackageName(resolvedPath);\n\n console.log(`## Scanning: ${resolvedPath}`);\n console.log(` Package: ${packageName}\\n`);\n\n const files = await discoverDirectiveFiles(resolvedPath, packageName, argv.exclude, argv.verbose);\n\n if (files.length === 0) {\n console.log(\"No files with 'use no memo' directives found.\");\n process.exit(0);\n }\n\n console.log(`Files with 'use no memo': ${files.length}\\n`);\n\n const results = await analyzeFiles(files, {\n concurrency: argv.concurrency,\n verbose: argv.verbose,\n });\n\n const workspaceRoot = process.cwd();\n printReport(results, workspaceRoot, argv['full-reasons']);\n printSummary(results);\n\n if (argv.fix) {\n const fixable = results.filter(r => r.status === 'redundant' || r.status === 'active');\n if (fixable.length > 0) {\n console.log('Applying fixes...');\n const fixResult = await applyFixes(results);\n const parts: string[] = [];\n if (fixResult.directivesRemoved > 0) {\n parts.push(`${fixResult.directivesRemoved} redundant directive(s) removed`);\n }\n if (fixResult.directivesJustified > 0) {\n parts.push(`${fixResult.directivesJustified} active directive(s) annotated with // justified:`);\n }\n console.log(`Fixed: ${parts.join(', ')} across ${fixResult.filesModified} file(s).\\n`);\n } else {\n console.log('Nothing to fix.\\n');\n }\n }\n\n const hasRedundant = results.some(r => r.status === 'redundant');\n process.exit(hasRedundant && !argv.fix ? 1 : 0);\n },\n};\n"],"names":["directivesCommand","command","describe","builder","yarg","sharedOptions","option","type","default","handler","argv","resolvedPath","validatePath","path","validateConcurrency","concurrency","console","log","packageName","findPackageName","files","discoverDirectiveFiles","exclude","verbose","length","process","exit","results","analyzeFiles","workspaceRoot","cwd","printReport","printSummary","fix","fixable","filter","r","status","fixResult","applyFixes","parts","directivesRemoved","push","directivesJustified","join","filesModified","hasRedundant","some"],"mappings":";;;;+BAiBaA;;;eAAAA;;;0BAfgB;2BAC2B;uBAC7B;0BACe;wBACuB;AAW1D,MAAMA,oBAAuD;IAClEC,SAAS;IACTC,UAAU;IACVC,SAASC,CAAAA,OACPC,IAAAA,qBAAa,EAACD,MAAME,MAAM,CAAC,OAAO;YAChCC,MAAM;YACNL,UAAU;YACVM,SAAS;QACX;IACFC,SAAS,OAAMC;QACb,MAAMC,eAAeC,IAAAA,oBAAY,EAACF,KAAKG,IAAI;QAC3CC,IAAAA,2BAAmB,EAACJ,KAAKK,WAAW;QAEpCC,QAAQC,GAAG,CAAC;QAEZ,MAAMC,cAAc,MAAMC,IAAAA,0BAAe,EAACR;QAE1CK,QAAQC,GAAG,CAAC,CAAC,aAAa,EAAEN,cAAc;QAC1CK,QAAQC,GAAG,CAAC,CAAC,YAAY,EAAEC,YAAY,EAAE,CAAC;QAE1C,MAAME,QAAQ,MAAMC,IAAAA,iCAAsB,EAACV,cAAcO,aAAaR,KAAKY,OAAO,EAAEZ,KAAKa,OAAO;QAEhG,IAAIH,MAAMI,MAAM,KAAK,GAAG;YACtBR,QAAQC,GAAG,CAAC;YACZQ,QAAQC,IAAI,CAAC;QACf;QAEAV,QAAQC,GAAG,CAAC,CAAC,0BAA0B,EAAEG,MAAMI,MAAM,CAAC,EAAE,CAAC;QAEzD,MAAMG,UAAU,MAAMC,IAAAA,sBAAY,EAACR,OAAO;YACxCL,aAAaL,KAAKK,WAAW;YAC7BQ,SAASb,KAAKa,OAAO;QACvB;QAEA,MAAMM,gBAAgBJ,QAAQK,GAAG;QACjCC,IAAAA,qBAAW,EAACJ,SAASE,eAAenB,IAAI,CAAC,eAAe;QACxDsB,IAAAA,sBAAY,EAACL;QAEb,IAAIjB,KAAKuB,GAAG,EAAE;YACZ,MAAMC,UAAUP,QAAQQ,MAAM,CAACC,CAAAA,IAAKA,EAAEC,MAAM,KAAK,eAAeD,EAAEC,MAAM,KAAK;YAC7E,IAAIH,QAAQV,MAAM,GAAG,GAAG;gBACtBR,QAAQC,GAAG,CAAC;gBACZ,MAAMqB,YAAY,MAAMC,IAAAA,iBAAU,EAACZ;gBACnC,MAAMa,QAAkB,EAAE;gBAC1B,IAAIF,UAAUG,iBAAiB,GAAG,GAChCD,MAAME,IAAI,CAAC,GAAGJ,UAAUG,iBAAiB,CAAC,+BAA+B,CAAC;gBAE5E,IAAIH,UAAUK,mBAAmB,GAAG,GAClCH,MAAME,IAAI,CAAC,GAAGJ,UAAUK,mBAAmB,CAAC,iDAAiD,CAAC;gBAEhG3B,QAAQC,GAAG,CAAC,CAAC,OAAO,EAAEuB,MAAMI,IAAI,CAAC,MAAM,QAAQ,EAAEN,UAAUO,aAAa,CAAC,WAAW,CAAC;YACvF,OACE7B,QAAQC,GAAG,CAAC;QAEhB;QAEA,MAAM6B,eAAenB,QAAQoB,IAAI,CAACX,CAAAA,IAAKA,EAAEC,MAAM,KAAK;QACpDZ,QAAQC,IAAI,CAACoB,gBAAgB,CAACpC,KAAKuB,GAAG,GAAG,IAAI;IAC/C;AACF"}
@@ -0,0 +1,18 @@
1
+ import type { Argv } from 'yargs';
2
+ export declare const DEFAULT_EXCLUDE: string[];
3
+ /**
4
+ * Add shared CLI options common to all subcommands.
5
+ */
6
+ export declare function sharedOptions<T>(yarg: Argv<T>): Argv<T & {
7
+ path: string;
8
+ } & {
9
+ verbose: boolean;
10
+ } & {
11
+ concurrency: number;
12
+ } & {
13
+ "full-reasons": boolean;
14
+ } & {
15
+ exclude: string[];
16
+ }>;
17
+ export declare function validatePath(rawPath: string): string;
18
+ export declare function validateConcurrency(concurrency: number): void;
@@ -0,0 +1,79 @@
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
+ DEFAULT_EXCLUDE: function() {
13
+ return DEFAULT_EXCLUDE;
14
+ },
15
+ sharedOptions: function() {
16
+ return sharedOptions;
17
+ },
18
+ validateConcurrency: function() {
19
+ return validateConcurrency;
20
+ },
21
+ validatePath: function() {
22
+ return validatePath;
23
+ }
24
+ });
25
+ const _nodefs = require("node:fs");
26
+ const _nodepath = require("node:path");
27
+ const DEFAULT_EXCLUDE = [
28
+ '**/__tests__/**',
29
+ '**/testing/**',
30
+ '**/__mocks__/**',
31
+ '**/*.spec.*',
32
+ '**/*.test.*',
33
+ '**/*.stories.*',
34
+ '**/*.cy.*'
35
+ ];
36
+ function sharedOptions(yarg) {
37
+ return yarg.positional('path', {
38
+ type: 'string',
39
+ describe: 'Directory to scan for TypeScript files',
40
+ demandOption: true
41
+ }).option('verbose', {
42
+ type: 'boolean',
43
+ describe: 'Show per-function compiler events in the output',
44
+ default: false
45
+ }).option('concurrency', {
46
+ type: 'number',
47
+ describe: 'Max parallel file processing',
48
+ default: 10
49
+ }).option('full-reasons', {
50
+ type: 'boolean',
51
+ describe: 'Show full compiler error reasons instead of truncated summaries',
52
+ default: false
53
+ }).option('exclude', {
54
+ type: 'string',
55
+ array: true,
56
+ describe: 'Glob patterns passed to fs.globSync exclude',
57
+ default: DEFAULT_EXCLUDE
58
+ });
59
+ }
60
+ function validatePath(rawPath) {
61
+ const resolvedPath = (0, _nodepath.resolve)(rawPath);
62
+ if (!(0, _nodefs.existsSync)(resolvedPath)) {
63
+ console.error(`Error: Path does not exist: ${resolvedPath}`);
64
+ process.exit(1);
65
+ }
66
+ if (!(0, _nodefs.statSync)(resolvedPath).isDirectory()) {
67
+ console.error(`Error: Path is not a directory: ${resolvedPath}`);
68
+ process.exit(1);
69
+ }
70
+ return resolvedPath;
71
+ }
72
+ function validateConcurrency(concurrency) {
73
+ if (concurrency < 1) {
74
+ console.error('Error: --concurrency must be >= 1');
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/commands/shared.ts"],"sourcesContent":["import { existsSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport type { Argv } from 'yargs';\n\nexport const DEFAULT_EXCLUDE = [\n '**/__tests__/**',\n '**/testing/**',\n '**/__mocks__/**',\n '**/*.spec.*',\n '**/*.test.*',\n '**/*.stories.*',\n '**/*.cy.*',\n];\n\n/**\n * Add shared CLI options common to all subcommands.\n */\nexport function sharedOptions<T>(yarg: Argv<T>) {\n return yarg\n .positional('path', {\n type: 'string' as const,\n describe: 'Directory to scan for TypeScript files',\n demandOption: true,\n })\n .option('verbose', {\n type: 'boolean' as const,\n describe: 'Show per-function compiler events in the output',\n default: false,\n })\n .option('concurrency', {\n type: 'number' as const,\n describe: 'Max parallel file processing',\n default: 10,\n })\n .option('full-reasons', {\n type: 'boolean' as const,\n describe: 'Show full compiler error reasons instead of truncated summaries',\n default: false,\n })\n .option('exclude', {\n type: 'string' as const,\n array: true as const,\n describe: 'Glob patterns passed to fs.globSync exclude',\n default: DEFAULT_EXCLUDE,\n });\n}\n\nexport function validatePath(rawPath: string): string {\n const resolvedPath = resolve(rawPath);\n\n if (!existsSync(resolvedPath)) {\n console.error(`Error: Path does not exist: ${resolvedPath}`);\n process.exit(1);\n }\n if (!statSync(resolvedPath).isDirectory()) {\n console.error(`Error: Path is not a directory: ${resolvedPath}`);\n process.exit(1);\n }\n\n return resolvedPath;\n}\n\nexport function validateConcurrency(concurrency: number): void {\n if (concurrency < 1) {\n console.error('Error: --concurrency must be >= 1');\n process.exit(1);\n }\n}\n"],"names":["DEFAULT_EXCLUDE","sharedOptions","validateConcurrency","validatePath","yarg","positional","type","describe","demandOption","option","default","array","rawPath","resolvedPath","resolve","existsSync","console","error","process","exit","statSync","isDirectory","concurrency"],"mappings":";;;;;;;;;;;IAKaA,eAAe;eAAfA;;IAaGC,aAAa;eAAbA;;IA6CAC,mBAAmB;eAAnBA;;IAfAC,YAAY;eAAZA;;;wBAhDqB;0BACb;AAIjB,MAAMH,kBAAkB;IAC7B;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAKM,SAASC,cAAiBG,IAAa;IAC5C,OAAOA,KACJC,UAAU,CAAC,QAAQ;QAClBC,MAAM;QACNC,UAAU;QACVC,cAAc;IAChB,GACCC,MAAM,CAAC,WAAW;QACjBH,MAAM;QACNC,UAAU;QACVG,SAAS;IACX,GACCD,MAAM,CAAC,eAAe;QACrBH,MAAM;QACNC,UAAU;QACVG,SAAS;IACX,GACCD,MAAM,CAAC,gBAAgB;QACtBH,MAAM;QACNC,UAAU;QACVG,SAAS;IACX,GACCD,MAAM,CAAC,WAAW;QACjBH,MAAM;QACNK,OAAO;QACPJ,UAAU;QACVG,SAASV;IACX;AACJ;AAEO,SAASG,aAAaS,OAAe;IAC1C,MAAMC,eAAeC,IAAAA,iBAAO,EAACF;IAE7B,IAAI,CAACG,IAAAA,kBAAU,EAACF,eAAe;QAC7BG,QAAQC,KAAK,CAAC,CAAC,4BAA4B,EAAEJ,cAAc;QAC3DK,QAAQC,IAAI,CAAC;IACf;IACA,IAAI,CAACC,IAAAA,gBAAQ,EAACP,cAAcQ,WAAW,IAAI;QACzCL,QAAQC,KAAK,CAAC,CAAC,gCAAgC,EAAEJ,cAAc;QAC/DK,QAAQC,IAAI,CAAC;IACf;IAEA,OAAON;AACT;AAEO,SAASX,oBAAoBoB,WAAmB;IACrD,IAAIA,cAAc,GAAG;QACnBN,QAAQC,KAAK,CAAC;QACdC,QAAQC,IAAI,CAAC;IACf;AACF"}