@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.
- package/CHANGELOG.md +13 -0
- package/README.md +185 -0
- package/bin/react-compiler-analyzer.js +10 -0
- package/dist/README.md +185 -0
- package/dist/package.json +24 -0
- package/dist/src/analyzer.d.ts +9 -0
- package/dist/src/analyzer.js +271 -0
- package/dist/src/analyzer.js.map +1 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +19 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/coverage.d.ts +13 -0
- package/dist/src/commands/coverage.js +65 -0
- package/dist/src/commands/coverage.js.map +1 -0
- package/dist/src/commands/directives.d.ts +11 -0
- package/dist/src/commands/directives.js +60 -0
- package/dist/src/commands/directives.js.map +1 -0
- package/dist/src/commands/shared.d.ts +18 -0
- package/dist/src/commands/shared.js +79 -0
- package/dist/src/commands/shared.js.map +1 -0
- package/dist/src/compiler-utils.d.ts +29 -0
- package/dist/src/compiler-utils.js +29 -0
- package/dist/src/compiler-utils.js.map +1 -0
- package/dist/src/coverage-analyzer.d.ts +9 -0
- package/dist/src/coverage-analyzer.js +177 -0
- package/dist/src/coverage-analyzer.js.map +1 -0
- package/dist/src/coverage-fixer.d.ts +12 -0
- package/dist/src/coverage-fixer.js +71 -0
- package/dist/src/coverage-fixer.js.map +1 -0
- package/dist/src/coverage-reporter.d.ts +16 -0
- package/dist/src/coverage-reporter.js +167 -0
- package/dist/src/coverage-reporter.js.map +1 -0
- package/dist/src/discovery.d.ts +14 -0
- package/dist/src/discovery.js +70 -0
- package/dist/src/discovery.js.map +1 -0
- package/dist/src/fixer.d.ts +9 -0
- package/dist/src/fixer.js +79 -0
- package/dist/src/fixer.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/manual-memo-plugin.d.ts +16 -0
- package/dist/src/manual-memo-plugin.js +82 -0
- package/dist/src/manual-memo-plugin.js.map +1 -0
- package/dist/src/reporter.d.ts +9 -0
- package/dist/src/reporter.js +117 -0
- package/dist/src/reporter.js.map +1 -0
- package/dist/src/types.d.ts +70 -0
- package/dist/src/types.js +7 -0
- package/dist/src/types.js.map +1 -0
- 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":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cli(): Promise<void>;
|
package/dist/src/cli.js
ADDED
|
@@ -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"}
|