@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,70 @@
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
+ discoverAllFiles: function() {
13
+ return discoverAllFiles;
14
+ },
15
+ discoverDirectiveFiles: function() {
16
+ return discoverDirectiveFiles;
17
+ },
18
+ findPackageName: function() {
19
+ return findPackageName;
20
+ }
21
+ });
22
+ const _nodefs = require("node:fs");
23
+ const _promises = require("node:fs/promises");
24
+ const _nodepath = require("node:path");
25
+ const USE_NO_MEMO_RE = /['(]use no memo[')]/;
26
+ async function findPackageName(startDir) {
27
+ let dir = (0, _nodepath.resolve)(startDir);
28
+ const root = (0, _nodepath.resolve)('/');
29
+ while(dir !== root){
30
+ const pkgJsonPath = (0, _nodepath.join)(dir, 'package.json');
31
+ if ((0, _nodefs.existsSync)(pkgJsonPath)) try {
32
+ const content = await (0, _promises.readFile)(pkgJsonPath, 'utf-8');
33
+ const pkg = JSON.parse(content);
34
+ if (typeof pkg.name === 'string') return pkg.name;
35
+ } catch {
36
+ // ignore parse errors, keep walking
37
+ }
38
+ dir = (0, _nodepath.dirname)(dir);
39
+ }
40
+ return (0, _nodepath.basename)(startDir);
41
+ }
42
+ async function discoverDirectiveFiles(scanDir, packageName, exclude, verbose) {
43
+ const files = [];
44
+ const tsFiles = (0, _nodefs.globSync)('**/*.{ts,tsx}', {
45
+ cwd: scanDir,
46
+ exclude
47
+ }).map((relative)=>(0, _nodepath.join)(scanDir, relative));
48
+ for (const filePath of tsFiles){
49
+ const content = await (0, _promises.readFile)(filePath, 'utf-8');
50
+ if (USE_NO_MEMO_RE.test(content)) files.push({
51
+ filePath,
52
+ packageName
53
+ });
54
+ }
55
+ if (verbose && files.length === 0) console.log(` No 'use no memo' files found in ${scanDir}`);
56
+ return files;
57
+ }
58
+ async function discoverAllFiles(scanDir, packageName, exclude, verbose) {
59
+ const tsFiles = (0, _nodefs.globSync)('**/*.{ts,tsx}', {
60
+ cwd: scanDir,
61
+ exclude
62
+ }).map((relative)=>(0, _nodepath.join)(scanDir, relative));
63
+ if (verbose) console.log(` Found ${tsFiles.length} TypeScript files in ${scanDir}`);
64
+ return tsFiles.map((filePath)=>({
65
+ filePath,
66
+ packageName
67
+ }));
68
+ }
69
+
70
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/discovery.ts"],"sourcesContent":["import { existsSync, globSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { basename, dirname, join, resolve } from 'node:path';\n\nimport type { FileEntry } from './types';\n\nconst USE_NO_MEMO_RE = /['(]use no memo[')]/;\n\n/**\n * Walk up from `startDir` to find the nearest package.json and return its `name` field.\n * Falls back to the basename of `startDir`.\n */\nexport async function findPackageName(startDir: string): Promise<string> {\n let dir = resolve(startDir);\n const root = resolve('/');\n\n while (dir !== root) {\n const pkgJsonPath = join(dir, 'package.json');\n if (existsSync(pkgJsonPath)) {\n try {\n const content = await readFile(pkgJsonPath, 'utf-8');\n const pkg = JSON.parse(content);\n if (typeof pkg.name === 'string') {\n return pkg.name;\n }\n } catch {\n // ignore parse errors, keep walking\n }\n }\n dir = dirname(dir);\n }\n\n return basename(startDir);\n}\n\n/**\n * Discover files containing 'use no memo' in the given directory.\n */\nexport async function discoverDirectiveFiles(\n scanDir: string,\n packageName: string,\n exclude: string[],\n verbose: boolean,\n): Promise<FileEntry[]> {\n const files: FileEntry[] = [];\n\n const tsFiles = globSync('**/*.{ts,tsx}', {\n cwd: scanDir,\n exclude,\n }).map(relative => join(scanDir, relative));\n\n for (const filePath of tsFiles) {\n const content = await readFile(filePath, 'utf-8');\n if (USE_NO_MEMO_RE.test(content)) {\n files.push({ filePath, packageName });\n }\n }\n\n if (verbose && files.length === 0) {\n console.log(` No 'use no memo' files found in ${scanDir}`);\n }\n\n return files;\n}\n\n/**\n * Discover all TypeScript files in the given directory (for coverage analysis).\n */\nexport async function discoverAllFiles(\n scanDir: string,\n packageName: string,\n exclude: string[],\n verbose: boolean,\n): Promise<FileEntry[]> {\n const tsFiles = globSync('**/*.{ts,tsx}', {\n cwd: scanDir,\n exclude,\n }).map(relative => join(scanDir, relative));\n\n if (verbose) {\n console.log(` Found ${tsFiles.length} TypeScript files in ${scanDir}`);\n }\n\n return tsFiles.map(filePath => ({ filePath, packageName }));\n}\n"],"names":["discoverAllFiles","discoverDirectiveFiles","findPackageName","USE_NO_MEMO_RE","startDir","dir","resolve","root","pkgJsonPath","join","existsSync","content","readFile","pkg","JSON","parse","name","dirname","basename","scanDir","packageName","exclude","verbose","files","tsFiles","globSync","cwd","map","relative","filePath","test","push","length","console","log"],"mappings":";;;;;;;;;;;IAoEsBA,gBAAgB;eAAhBA;;IA9BAC,sBAAsB;eAAtBA;;IA1BAC,eAAe;eAAfA;;;wBAZe;0BACZ;0BACwB;AAIjD,MAAMC,iBAAiB;AAMhB,eAAeD,gBAAgBE,QAAgB;IACpD,IAAIC,MAAMC,IAAAA,iBAAO,EAACF;IAClB,MAAMG,OAAOD,IAAAA,iBAAO,EAAC;IAErB,MAAOD,QAAQE,KAAM;QACnB,MAAMC,cAAcC,IAAAA,cAAI,EAACJ,KAAK;QAC9B,IAAIK,IAAAA,kBAAU,EAACF,cACb,IAAI;YACF,MAAMG,UAAU,MAAMC,IAAAA,kBAAQ,EAACJ,aAAa;YAC5C,MAAMK,MAAMC,KAAKC,KAAK,CAACJ;YACvB,IAAI,OAAOE,IAAIG,IAAI,KAAK,UACtB,OAAOH,IAAIG,IAAI;QAEnB,EAAE,OAAM;QACN,oCAAoC;QACtC;QAEFX,MAAMY,IAAAA,iBAAO,EAACZ;IAChB;IAEA,OAAOa,IAAAA,kBAAQ,EAACd;AAClB;AAKO,eAAeH,uBACpBkB,OAAe,EACfC,WAAmB,EACnBC,OAAiB,EACjBC,OAAgB;IAEhB,MAAMC,QAAqB,EAAE;IAE7B,MAAMC,UAAUC,IAAAA,gBAAQ,EAAC,iBAAiB;QACxCC,KAAKP;QACLE;IACF,GAAGM,GAAG,CAACC,CAAAA,WAAYnB,IAAAA,cAAI,EAACU,SAASS;IAEjC,KAAK,MAAMC,YAAYL,QAAS;QAC9B,MAAMb,UAAU,MAAMC,IAAAA,kBAAQ,EAACiB,UAAU;QACzC,IAAI1B,eAAe2B,IAAI,CAACnB,UACtBY,MAAMQ,IAAI,CAAC;YAAEF;YAAUT;QAAY;IAEvC;IAEA,IAAIE,WAAWC,MAAMS,MAAM,KAAK,GAC9BC,QAAQC,GAAG,CAAC,CAAC,kCAAkC,EAAEf,SAAS;IAG5D,OAAOI;AACT;AAKO,eAAevB,iBACpBmB,OAAe,EACfC,WAAmB,EACnBC,OAAiB,EACjBC,OAAgB;IAEhB,MAAME,UAAUC,IAAAA,gBAAQ,EAAC,iBAAiB;QACxCC,KAAKP;QACLE;IACF,GAAGM,GAAG,CAACC,CAAAA,WAAYnB,IAAAA,cAAI,EAACU,SAASS;IAEjC,IAAIN,SACFW,QAAQC,GAAG,CAAC,CAAC,QAAQ,EAAEV,QAAQQ,MAAM,CAAC,qBAAqB,EAAEb,SAAS;IAGxE,OAAOK,QAAQG,GAAG,CAACE,CAAAA,WAAa,CAAA;YAAEA;YAAUT;QAAY,CAAA;AAC1D"}
@@ -0,0 +1,9 @@
1
+ import type { DirectiveAnalysis, FixResult } from './types';
2
+ /**
3
+ * Auto-fix directives in source files:
4
+ * - Remove redundant 'use no memo' directives
5
+ * - Annotate active directives with `// justified: <reason>`
6
+ *
7
+ * Processes changes bottom-to-top within each file to avoid offset shifts.
8
+ */
9
+ export declare function applyFixes(results: DirectiveAnalysis[]): Promise<FixResult>;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "applyFixes", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return applyFixes;
9
+ }
10
+ });
11
+ const _promises = require("node:fs/promises");
12
+ async function applyFixes(results) {
13
+ const actionable = results.filter((r)=>r.status === 'redundant' || r.status === 'active');
14
+ if (actionable.length === 0) return {
15
+ filesModified: 0,
16
+ directivesRemoved: 0,
17
+ directivesJustified: 0
18
+ };
19
+ // Group actions by file
20
+ const byFile = new Map();
21
+ for (const r of actionable){
22
+ const actions = byFile.get(r.filePath) ?? [];
23
+ if (r.status === 'redundant') actions.push({
24
+ kind: 'remove',
25
+ line: r.line
26
+ });
27
+ else if (r.status === 'active') actions.push({
28
+ kind: 'justify',
29
+ line: r.line,
30
+ reason: buildJustification(r)
31
+ });
32
+ byFile.set(r.filePath, actions);
33
+ }
34
+ let filesModified = 0;
35
+ let directivesRemoved = 0;
36
+ let directivesJustified = 0;
37
+ for (const [filePath, actions] of byFile){
38
+ const source = await (0, _promises.readFile)(filePath, 'utf-8');
39
+ const lines = source.split('\n');
40
+ // Sort descending so we process from bottom-to-top
41
+ const sorted = [
42
+ ...actions
43
+ ].sort((a, b)=>b.line - a.line);
44
+ for (const action of sorted){
45
+ const idx = action.line - 1; // 0-based index
46
+ if (idx < 0 || idx >= lines.length) continue;
47
+ if (action.kind === 'remove') {
48
+ lines.splice(idx, 1);
49
+ directivesRemoved++;
50
+ } else {
51
+ // Append justification comment to the directive line
52
+ const currentLine = lines[idx];
53
+ // Strip any existing trailing comment (shouldn't have one, but be safe)
54
+ const withoutTrailingComment = currentLine.replace(/\s*\/\/.*$/, '');
55
+ // Ensure semicolon before the comment
56
+ const base = withoutTrailingComment.trimEnd().endsWith(';') ? withoutTrailingComment.trimEnd() : withoutTrailingComment.trimEnd() + ';';
57
+ lines[idx] = `${base} // justified: ${action.reason}`;
58
+ directivesJustified++;
59
+ }
60
+ }
61
+ await (0, _promises.writeFile)(filePath, lines.join('\n'), 'utf-8');
62
+ filesModified++;
63
+ }
64
+ return {
65
+ filesModified,
66
+ directivesRemoved,
67
+ directivesJustified
68
+ };
69
+ }
70
+ /**
71
+ * Build a concise justification string for an active directive.
72
+ * Summarizes why the compiler would optimize the function (and thus
73
+ * why the directive is intentionally keeping it unoptimized).
74
+ */ function buildJustification(r) {
75
+ const fnName = r.functionName ?? 'unknown function';
76
+ return `compiler would optimize ${fnName} — manual opt-out to preserve runtime behavior`;
77
+ }
78
+
79
+ //# sourceMappingURL=fixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fixer.ts"],"sourcesContent":["import { readFile, writeFile } from 'node:fs/promises';\n\nimport type { DirectiveAnalysis, FixResult } from './types';\n\ntype LineAction = { kind: 'remove'; line: number } | { kind: 'justify'; line: number; reason: string };\n\n/**\n * Auto-fix directives in source files:\n * - Remove redundant 'use no memo' directives\n * - Annotate active directives with `// justified: <reason>`\n *\n * Processes changes bottom-to-top within each file to avoid offset shifts.\n */\nexport async function applyFixes(results: DirectiveAnalysis[]): Promise<FixResult> {\n const actionable = results.filter(r => r.status === 'redundant' || r.status === 'active');\n if (actionable.length === 0) {\n return { filesModified: 0, directivesRemoved: 0, directivesJustified: 0 };\n }\n\n // Group actions by file\n const byFile = new Map<string, LineAction[]>();\n for (const r of actionable) {\n const actions = byFile.get(r.filePath) ?? [];\n if (r.status === 'redundant') {\n actions.push({ kind: 'remove', line: r.line });\n } else if (r.status === 'active') {\n actions.push({ kind: 'justify', line: r.line, reason: buildJustification(r) });\n }\n byFile.set(r.filePath, actions);\n }\n\n let filesModified = 0;\n let directivesRemoved = 0;\n let directivesJustified = 0;\n\n for (const [filePath, actions] of byFile) {\n const source = await readFile(filePath, 'utf-8');\n const lines = source.split('\\n');\n\n // Sort descending so we process from bottom-to-top\n const sorted = [...actions].sort((a, b) => b.line - a.line);\n\n for (const action of sorted) {\n const idx = action.line - 1; // 0-based index\n if (idx < 0 || idx >= lines.length) {\n continue;\n }\n\n if (action.kind === 'remove') {\n lines.splice(idx, 1);\n directivesRemoved++;\n } else {\n // Append justification comment to the directive line\n const currentLine = lines[idx];\n // Strip any existing trailing comment (shouldn't have one, but be safe)\n const withoutTrailingComment = currentLine.replace(/\\s*\\/\\/.*$/, '');\n // Ensure semicolon before the comment\n const base = withoutTrailingComment.trimEnd().endsWith(';')\n ? withoutTrailingComment.trimEnd()\n : withoutTrailingComment.trimEnd() + ';';\n lines[idx] = `${base} // justified: ${action.reason}`;\n directivesJustified++;\n }\n }\n\n await writeFile(filePath, lines.join('\\n'), 'utf-8');\n filesModified++;\n }\n\n return { filesModified, directivesRemoved, directivesJustified };\n}\n\n/**\n * Build a concise justification string for an active directive.\n * Summarizes why the compiler would optimize the function (and thus\n * why the directive is intentionally keeping it unoptimized).\n */\nfunction buildJustification(r: DirectiveAnalysis): string {\n const fnName = r.functionName ?? 'unknown function';\n return `compiler would optimize ${fnName} — manual opt-out to preserve runtime behavior`;\n}\n"],"names":["applyFixes","results","actionable","filter","r","status","length","filesModified","directivesRemoved","directivesJustified","byFile","Map","actions","get","filePath","push","kind","line","reason","buildJustification","set","source","readFile","lines","split","sorted","sort","a","b","action","idx","splice","currentLine","withoutTrailingComment","replace","base","trimEnd","endsWith","writeFile","join","fnName","functionName"],"mappings":";;;;+BAasBA;;;eAAAA;;;0BAbc;AAa7B,eAAeA,WAAWC,OAA4B;IAC3D,MAAMC,aAAaD,QAAQE,MAAM,CAACC,CAAAA,IAAKA,EAAEC,MAAM,KAAK,eAAeD,EAAEC,MAAM,KAAK;IAChF,IAAIH,WAAWI,MAAM,KAAK,GACxB,OAAO;QAAEC,eAAe;QAAGC,mBAAmB;QAAGC,qBAAqB;IAAE;IAG1E,wBAAwB;IACxB,MAAMC,SAAS,IAAIC;IACnB,KAAK,MAAMP,KAAKF,WAAY;QAC1B,MAAMU,UAAUF,OAAOG,GAAG,CAACT,EAAEU,QAAQ,KAAK,EAAE;QAC5C,IAAIV,EAAEC,MAAM,KAAK,aACfO,QAAQG,IAAI,CAAC;YAAEC,MAAM;YAAUC,MAAMb,EAAEa,IAAI;QAAC;aACvC,IAAIb,EAAEC,MAAM,KAAK,UACtBO,QAAQG,IAAI,CAAC;YAAEC,MAAM;YAAWC,MAAMb,EAAEa,IAAI;YAAEC,QAAQC,mBAAmBf;QAAG;QAE9EM,OAAOU,GAAG,CAAChB,EAAEU,QAAQ,EAAEF;IACzB;IAEA,IAAIL,gBAAgB;IACpB,IAAIC,oBAAoB;IACxB,IAAIC,sBAAsB;IAE1B,KAAK,MAAM,CAACK,UAAUF,QAAQ,IAAIF,OAAQ;QACxC,MAAMW,SAAS,MAAMC,IAAAA,kBAAQ,EAACR,UAAU;QACxC,MAAMS,QAAQF,OAAOG,KAAK,CAAC;QAE3B,mDAAmD;QACnD,MAAMC,SAAS;eAAIb;SAAQ,CAACc,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEX,IAAI,GAAGU,EAAEV,IAAI;QAE1D,KAAK,MAAMY,UAAUJ,OAAQ;YAC3B,MAAMK,MAAMD,OAAOZ,IAAI,GAAG,GAAG,gBAAgB;YAC7C,IAAIa,MAAM,KAAKA,OAAOP,MAAMjB,MAAM,EAChC;YAGF,IAAIuB,OAAOb,IAAI,KAAK,UAAU;gBAC5BO,MAAMQ,MAAM,CAACD,KAAK;gBAClBtB;YACF,OAAO;gBACL,qDAAqD;gBACrD,MAAMwB,cAAcT,KAAK,CAACO,IAAI;gBAC9B,wEAAwE;gBACxE,MAAMG,yBAAyBD,YAAYE,OAAO,CAAC,cAAc;gBACjE,sCAAsC;gBACtC,MAAMC,OAAOF,uBAAuBG,OAAO,GAAGC,QAAQ,CAAC,OACnDJ,uBAAuBG,OAAO,KAC9BH,uBAAuBG,OAAO,KAAK;gBACvCb,KAAK,CAACO,IAAI,GAAG,GAAGK,KAAK,eAAe,EAAEN,OAAOX,MAAM,EAAE;gBACrDT;YACF;QACF;QAEA,MAAM6B,IAAAA,mBAAS,EAACxB,UAAUS,MAAMgB,IAAI,CAAC,OAAO;QAC5ChC;IACF;IAEA,OAAO;QAAEA;QAAeC;QAAmBC;IAAoB;AACjE;AAEA;;;;CAIC,GACD,SAASU,mBAAmBf,CAAoB;IAC9C,MAAMoC,SAASpC,EAAEqC,YAAY,IAAI;IACjC,OAAO,CAAC,wBAAwB,EAAED,OAAO,8CAA8C,CAAC;AAC1F"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import type { PluginObj } from '@babel/core';
2
+ export interface ManualMemoEntry {
3
+ useMemo: number;
4
+ useCallback: number;
5
+ reactMemo: boolean;
6
+ bodyInsertionLine: number;
7
+ }
8
+ export interface ManualMemoPluginOptions {
9
+ /** Shared map keyed by `line:column` of the enclosing function start */
10
+ results: Map<string, ManualMemoEntry>;
11
+ }
12
+ /**
13
+ * Babel plugin that detects manual memoization (useMemo, useCallback, React.memo)
14
+ * within function bodies. Populates a shared Map in plugin options keyed by function location.
15
+ */
16
+ export declare function manualMemoPlugin(): PluginObj;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "manualMemoPlugin", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return manualMemoPlugin;
9
+ }
10
+ });
11
+ function fnKey(loc) {
12
+ return `${loc.line}:${loc.column}`;
13
+ }
14
+ function getBodyInsertionLine(fnPath) {
15
+ const body = fnPath.node.body;
16
+ if (body.type === 'BlockStatement' && body.loc) // Insert after the opening brace — first statement line, or body start + 1
17
+ return body.loc.start.line + 1;
18
+ // Arrow function with expression body — use the function start line
19
+ if (fnPath.node.loc) return fnPath.node.loc.start.line;
20
+ return 0;
21
+ }
22
+ function hasUseMemoDirective(fnPath) {
23
+ const body = fnPath.node.body;
24
+ if (body.type !== 'BlockStatement') return false;
25
+ for (const directive of body.directives ?? []){
26
+ if (directive.value.value === 'use memo') return true;
27
+ }
28
+ return false;
29
+ }
30
+ function manualMemoPlugin() {
31
+ return {
32
+ name: 'manual-memo-detection',
33
+ visitor: {
34
+ // eslint-disable-next-line @typescript-eslint/naming-convention
35
+ CallExpression (path, state) {
36
+ const opts = state.opts;
37
+ const callee = path.node.callee;
38
+ let hookName = null;
39
+ // Direct call: memo(...), useMemo(...), useCallback(...)
40
+ if (callee.type === 'Identifier') {
41
+ const binding = path.scope.getBinding(callee.name);
42
+ if (binding?.path.parent?.type === 'ImportDeclaration' && binding.path.parent.source.value === 'react') {
43
+ if (callee.name === 'useMemo') hookName = 'useMemo';
44
+ else if (callee.name === 'useCallback') hookName = 'useCallback';
45
+ else if (callee.name === 'memo') hookName = 'reactMemo';
46
+ }
47
+ }
48
+ // Member expression: React.memo(...), React.useMemo(...), React.useCallback(...)
49
+ if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && callee.property.type === 'Identifier') {
50
+ const binding = path.scope.getBinding(callee.object.name);
51
+ if (binding?.path.parent?.type === 'ImportDeclaration' && binding.path.parent.source.value === 'react') {
52
+ if (callee.property.name === 'memo') hookName = 'reactMemo';
53
+ else if (callee.property.name === 'useMemo') hookName = 'useMemo';
54
+ else if (callee.property.name === 'useCallback') hookName = 'useCallback';
55
+ }
56
+ }
57
+ if (!hookName) return;
58
+ // Find the enclosing function
59
+ const fnPath = path.findParent((p)=>p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression());
60
+ if (!fnPath || !fnPath.node.loc) return;
61
+ // Skip functions that already have 'use memo' directive
62
+ if (hasUseMemoDirective(fnPath)) return;
63
+ const key = fnKey(fnPath.node.loc.start);
64
+ let entry = opts.results.get(key);
65
+ if (!entry) {
66
+ entry = {
67
+ useMemo: 0,
68
+ useCallback: 0,
69
+ reactMemo: false,
70
+ bodyInsertionLine: getBodyInsertionLine(fnPath)
71
+ };
72
+ opts.results.set(key, entry);
73
+ }
74
+ if (hookName === 'useMemo') entry.useMemo++;
75
+ else if (hookName === 'useCallback') entry.useCallback++;
76
+ else if (hookName === 'reactMemo') entry.reactMemo = true;
77
+ }
78
+ }
79
+ };
80
+ }
81
+
82
+ //# sourceMappingURL=manual-memo-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/manual-memo-plugin.ts"],"sourcesContent":["import type { PluginObj, NodePath } from '@babel/core';\nimport type { Function as BabelFunction } from '@babel/types';\n\nimport type { ManualMemoization } from './types';\n\nexport interface ManualMemoEntry {\n useMemo: number;\n useCallback: number;\n reactMemo: boolean;\n bodyInsertionLine: number;\n}\n\nexport interface ManualMemoPluginOptions {\n /** Shared map keyed by `line:column` of the enclosing function start */\n results: Map<string, ManualMemoEntry>;\n}\n\nfunction fnKey(loc: { line: number; column: number }): string {\n return `${loc.line}:${loc.column}`;\n}\n\nfunction getBodyInsertionLine(fnPath: NodePath<BabelFunction>): number {\n const body = fnPath.node.body;\n if (body.type === 'BlockStatement' && body.loc) {\n // Insert after the opening brace — first statement line, or body start + 1\n return body.loc.start.line + 1;\n }\n // Arrow function with expression body — use the function start line\n if (fnPath.node.loc) {\n return fnPath.node.loc.start.line;\n }\n return 0;\n}\n\nfunction hasUseMemoDirective(fnPath: NodePath<BabelFunction>): boolean {\n const body = fnPath.node.body;\n if (body.type !== 'BlockStatement') {\n return false;\n }\n for (const directive of body.directives ?? []) {\n if (directive.value.value === 'use memo') {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Babel plugin that detects manual memoization (useMemo, useCallback, React.memo)\n * within function bodies. Populates a shared Map in plugin options keyed by function location.\n */\nexport function manualMemoPlugin(): PluginObj {\n return {\n name: 'manual-memo-detection',\n visitor: {\n // eslint-disable-next-line @typescript-eslint/naming-convention\n CallExpression(path, state) {\n const opts = state.opts as unknown as ManualMemoPluginOptions;\n const callee = path.node.callee;\n\n let hookName: 'useMemo' | 'useCallback' | 'reactMemo' | null = null;\n\n // Direct call: memo(...), useMemo(...), useCallback(...)\n if (callee.type === 'Identifier') {\n const binding = path.scope.getBinding(callee.name);\n if (binding?.path.parent?.type === 'ImportDeclaration' && binding.path.parent.source.value === 'react') {\n if (callee.name === 'useMemo') {\n hookName = 'useMemo';\n } else if (callee.name === 'useCallback') {\n hookName = 'useCallback';\n } else if (callee.name === 'memo') {\n hookName = 'reactMemo';\n }\n }\n }\n\n // Member expression: React.memo(...), React.useMemo(...), React.useCallback(...)\n if (\n callee.type === 'MemberExpression' &&\n callee.object.type === 'Identifier' &&\n callee.property.type === 'Identifier'\n ) {\n const binding = path.scope.getBinding(callee.object.name);\n if (binding?.path.parent?.type === 'ImportDeclaration' && binding.path.parent.source.value === 'react') {\n if (callee.property.name === 'memo') {\n hookName = 'reactMemo';\n } else if (callee.property.name === 'useMemo') {\n hookName = 'useMemo';\n } else if (callee.property.name === 'useCallback') {\n hookName = 'useCallback';\n }\n }\n }\n\n if (!hookName) {\n return;\n }\n\n // Find the enclosing function\n const fnPath = path.findParent(\n p => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression(),\n ) as NodePath<BabelFunction> | null;\n\n if (!fnPath || !fnPath.node.loc) {\n return;\n }\n\n // Skip functions that already have 'use memo' directive\n if (hasUseMemoDirective(fnPath)) {\n return;\n }\n\n const key = fnKey(fnPath.node.loc.start);\n let entry = opts.results.get(key);\n\n if (!entry) {\n entry = {\n useMemo: 0,\n useCallback: 0,\n reactMemo: false,\n bodyInsertionLine: getBodyInsertionLine(fnPath),\n };\n opts.results.set(key, entry);\n }\n\n if (hookName === 'useMemo') {\n entry.useMemo++;\n } else if (hookName === 'useCallback') {\n entry.useCallback++;\n } else if (hookName === 'reactMemo') {\n entry.reactMemo = true;\n }\n },\n },\n };\n}\n"],"names":["manualMemoPlugin","fnKey","loc","line","column","getBodyInsertionLine","fnPath","body","node","type","start","hasUseMemoDirective","directive","directives","value","name","visitor","CallExpression","path","state","opts","callee","hookName","binding","scope","getBinding","parent","source","object","property","findParent","p","isFunctionDeclaration","isFunctionExpression","isArrowFunctionExpression","key","entry","results","get","useMemo","useCallback","reactMemo","bodyInsertionLine","set"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { DirectiveAnalysis } from './types';
2
+ /**
3
+ * Print a markdown-formatted report of all directive analyses, grouped by package and status.
4
+ */
5
+ export declare function printReport(results: DirectiveAnalysis[], workspaceRoot: string, fullReasons: boolean): void;
6
+ /**
7
+ * Print a summary of the analysis results.
8
+ */
9
+ export declare function printSummary(results: DirectiveAnalysis[]): void;
@@ -0,0 +1,117 @@
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
+ printReport: function() {
13
+ return printReport;
14
+ },
15
+ printSummary: function() {
16
+ return printSummary;
17
+ }
18
+ });
19
+ const _nodepath = require("node:path");
20
+ const TABLE_REASON_MAX_LEN = 80;
21
+ function printReport(results, workspaceRoot, fullReasons) {
22
+ if (results.length === 0) {
23
+ console.log("\nNo 'use no memo' directives found.");
24
+ return;
25
+ }
26
+ // Group by package
27
+ const byPackage = new Map();
28
+ for (const r of results){
29
+ const existing = byPackage.get(r.packageName) ?? [];
30
+ existing.push(r);
31
+ byPackage.set(r.packageName, existing);
32
+ }
33
+ // Sort packages alphabetically
34
+ const sortedPackages = [
35
+ ...byPackage.keys()
36
+ ].sort();
37
+ for (const pkg of sortedPackages){
38
+ const pkgResults = byPackage.get(pkg);
39
+ const active = pkgResults.filter((r)=>r.status === 'active');
40
+ const redundant = pkgResults.filter((r)=>r.status === 'redundant');
41
+ const skipped = pkgResults.filter((r)=>r.status === 'skipped');
42
+ console.log(`\n## ${pkg}\n`);
43
+ if (active.length > 0) {
44
+ console.log('### Active (needs `// justified:` comment)\n');
45
+ printTable(active, workspaceRoot, fullReasons);
46
+ }
47
+ if (redundant.length > 0) {
48
+ console.log('### Redundant (removable)\n');
49
+ printTable(redundant, workspaceRoot, fullReasons);
50
+ }
51
+ if (skipped.length > 0) {
52
+ console.log('### Skipped (already justified)\n');
53
+ printTable(skipped, workspaceRoot, fullReasons);
54
+ }
55
+ }
56
+ }
57
+ function printTable(results, workspaceRoot, fullReasons) {
58
+ console.log('| Location | Function | Compiler Event | Reason |');
59
+ console.log('|----------|----------|----------------|--------|');
60
+ for (const r of results){
61
+ const relPath = (0, _nodepath.relative)(workspaceRoot, r.filePath);
62
+ const fn = r.functionName ?? '(unknown)';
63
+ const reason = r.reason ? fullReasons ? escapeTableCell(r.reason) : truncate(r.reason, TABLE_REASON_MAX_LEN) : '';
64
+ console.log(`| ${relPath}:${r.line} | ${fn} | ${r.compilerEvent} | ${reason} |`);
65
+ }
66
+ console.log('');
67
+ if (fullReasons) {
68
+ // Print full reasons as details blocks below the table for readability
69
+ const withReasons = results.filter((r)=>r.reason && r.reason.includes('\n'));
70
+ if (withReasons.length > 0) {
71
+ console.log('<details><summary>Full compiler output</summary>\n');
72
+ for (const r of withReasons){
73
+ const relPath = (0, _nodepath.relative)(workspaceRoot, r.filePath);
74
+ const fn = r.functionName ?? '(unknown)';
75
+ console.log(`#### ${relPath}:${r.line} — ${fn}\n`);
76
+ console.log('```');
77
+ console.log(r.reason);
78
+ console.log('```\n');
79
+ }
80
+ console.log('</details>\n');
81
+ }
82
+ }
83
+ }
84
+ function printSummary(results) {
85
+ const total = results.length;
86
+ const redundant = results.filter((r)=>r.status === 'redundant').length;
87
+ const active = results.filter((r)=>r.status === 'active').length;
88
+ const skipped = results.filter((r)=>r.status === 'skipped').length;
89
+ console.log('## Summary\n');
90
+ console.log(`- **Total directives:** ${total}`);
91
+ console.log(`- **Redundant** (removable): ${redundant}`);
92
+ console.log(`- **Active** (needs \`// justified:\` comment): ${active}`);
93
+ console.log(`- **Skipped** (already justified): ${skipped}`);
94
+ console.log('');
95
+ if (redundant > 0 && active > 0) {
96
+ console.log(`> **${redundant}** redundant directive(s) can be safely removed.`);
97
+ console.log(`> **${active}** active directive(s) need a \`// justified: <reason>\` comment — the compiler would optimize these functions without the directive.`);
98
+ console.log('>');
99
+ console.log('> Run with `--fix` to auto-remove redundant directives and annotate active ones.\n');
100
+ } else if (redundant > 0) {
101
+ console.log(`> **${redundant}** redundant \`'use no memo'\` directive(s) found.`);
102
+ console.log('> Run with `--fix` to auto-remove them.\n');
103
+ } else if (active > 0) {
104
+ console.log(`> **${active}** active directive(s) need a \`// justified: <reason>\` comment.`);
105
+ console.log('> Run with `--fix` to annotate them.\n');
106
+ } else console.log('> All directives are justified. Nothing to do.\n');
107
+ }
108
+ function truncate(str, maxLen) {
109
+ const cleaned = str.replace(/\n/g, ' ');
110
+ return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 3) + '...' : cleaned;
111
+ }
112
+ function escapeTableCell(str) {
113
+ // For table cells, collapse to single line
114
+ return str.replace(/\n/g, ' ').replace(/\|/g, '\\|');
115
+ }
116
+
117
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/reporter.ts"],"sourcesContent":["import { relative } from 'node:path';\n\nimport type { DirectiveAnalysis } from './types';\n\nconst TABLE_REASON_MAX_LEN = 80;\n\n/**\n * Print a markdown-formatted report of all directive analyses, grouped by package and status.\n */\nexport function printReport(results: DirectiveAnalysis[], workspaceRoot: string, fullReasons: boolean): void {\n if (results.length === 0) {\n console.log(\"\\nNo 'use no memo' directives found.\");\n return;\n }\n\n // Group by package\n const byPackage = new Map<string, DirectiveAnalysis[]>();\n for (const r of results) {\n const existing = byPackage.get(r.packageName) ?? [];\n existing.push(r);\n byPackage.set(r.packageName, existing);\n }\n\n // Sort packages alphabetically\n const sortedPackages = [...byPackage.keys()].sort();\n\n for (const pkg of sortedPackages) {\n const pkgResults = byPackage.get(pkg)!;\n const active = pkgResults.filter(r => r.status === 'active');\n const redundant = pkgResults.filter(r => r.status === 'redundant');\n const skipped = pkgResults.filter(r => r.status === 'skipped');\n\n console.log(`\\n## ${pkg}\\n`);\n\n if (active.length > 0) {\n console.log('### Active (needs `// justified:` comment)\\n');\n printTable(active, workspaceRoot, fullReasons);\n }\n\n if (redundant.length > 0) {\n console.log('### Redundant (removable)\\n');\n printTable(redundant, workspaceRoot, fullReasons);\n }\n\n if (skipped.length > 0) {\n console.log('### Skipped (already justified)\\n');\n printTable(skipped, workspaceRoot, fullReasons);\n }\n }\n}\n\nfunction printTable(results: DirectiveAnalysis[], workspaceRoot: string, fullReasons: boolean): void {\n console.log('| Location | Function | Compiler Event | Reason |');\n console.log('|----------|----------|----------------|--------|');\n\n for (const r of results) {\n const relPath = relative(workspaceRoot, r.filePath);\n const fn = r.functionName ?? '(unknown)';\n const reason = r.reason ? (fullReasons ? escapeTableCell(r.reason) : truncate(r.reason, TABLE_REASON_MAX_LEN)) : '';\n console.log(`| ${relPath}:${r.line} | ${fn} | ${r.compilerEvent} | ${reason} |`);\n }\n console.log('');\n\n if (fullReasons) {\n // Print full reasons as details blocks below the table for readability\n const withReasons = results.filter(r => r.reason && r.reason.includes('\\n'));\n if (withReasons.length > 0) {\n console.log('<details><summary>Full compiler output</summary>\\n');\n for (const r of withReasons) {\n const relPath = relative(workspaceRoot, r.filePath);\n const fn = r.functionName ?? '(unknown)';\n console.log(`#### ${relPath}:${r.line} — ${fn}\\n`);\n console.log('```');\n console.log(r.reason);\n console.log('```\\n');\n }\n console.log('</details>\\n');\n }\n }\n}\n\n/**\n * Print a summary of the analysis results.\n */\nexport function printSummary(results: DirectiveAnalysis[]): void {\n const total = results.length;\n const redundant = results.filter(r => r.status === 'redundant').length;\n const active = results.filter(r => r.status === 'active').length;\n const skipped = results.filter(r => r.status === 'skipped').length;\n\n console.log('## Summary\\n');\n console.log(`- **Total directives:** ${total}`);\n console.log(`- **Redundant** (removable): ${redundant}`);\n console.log(`- **Active** (needs \\`// justified:\\` comment): ${active}`);\n console.log(`- **Skipped** (already justified): ${skipped}`);\n console.log('');\n\n if (redundant > 0 && active > 0) {\n console.log(`> **${redundant}** redundant directive(s) can be safely removed.`);\n console.log(\n `> **${active}** active directive(s) need a \\`// justified: <reason>\\` comment — the compiler would optimize these functions without the directive.`,\n );\n console.log('>');\n console.log('> Run with `--fix` to auto-remove redundant directives and annotate active ones.\\n');\n } else if (redundant > 0) {\n console.log(`> **${redundant}** redundant \\`'use no memo'\\` directive(s) found.`);\n console.log('> Run with `--fix` to auto-remove them.\\n');\n } else if (active > 0) {\n console.log(`> **${active}** active directive(s) need a \\`// justified: <reason>\\` comment.`);\n console.log('> Run with `--fix` to annotate them.\\n');\n } else {\n console.log('> All directives are justified. Nothing to do.\\n');\n }\n}\n\nfunction truncate(str: string, maxLen: number): string {\n const cleaned = str.replace(/\\n/g, ' ');\n return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 3) + '...' : cleaned;\n}\n\nfunction escapeTableCell(str: string): string {\n // For table cells, collapse to single line\n return str.replace(/\\n/g, ' ').replace(/\\|/g, '\\\\|');\n}\n"],"names":["printReport","printSummary","TABLE_REASON_MAX_LEN","results","workspaceRoot","fullReasons","length","console","log","byPackage","Map","r","existing","get","packageName","push","set","sortedPackages","keys","sort","pkg","pkgResults","active","filter","status","redundant","skipped","printTable","relPath","relative","filePath","fn","functionName","reason","escapeTableCell","truncate","line","compilerEvent","withReasons","includes","total","str","maxLen","cleaned","replace","slice"],"mappings":";;;;;;;;;;;IASgBA,WAAW;eAAXA;;IA2EAC,YAAY;eAAZA;;;0BApFS;AAIzB,MAAMC,uBAAuB;AAKtB,SAASF,YAAYG,OAA4B,EAAEC,aAAqB,EAAEC,WAAoB;IACnG,IAAIF,QAAQG,MAAM,KAAK,GAAG;QACxBC,QAAQC,GAAG,CAAC;QACZ;IACF;IAEA,mBAAmB;IACnB,MAAMC,YAAY,IAAIC;IACtB,KAAK,MAAMC,KAAKR,QAAS;QACvB,MAAMS,WAAWH,UAAUI,GAAG,CAACF,EAAEG,WAAW,KAAK,EAAE;QACnDF,SAASG,IAAI,CAACJ;QACdF,UAAUO,GAAG,CAACL,EAAEG,WAAW,EAAEF;IAC/B;IAEA,+BAA+B;IAC/B,MAAMK,iBAAiB;WAAIR,UAAUS,IAAI;KAAG,CAACC,IAAI;IAEjD,KAAK,MAAMC,OAAOH,eAAgB;QAChC,MAAMI,aAAaZ,UAAUI,GAAG,CAACO;QACjC,MAAME,SAASD,WAAWE,MAAM,CAACZ,CAAAA,IAAKA,EAAEa,MAAM,KAAK;QACnD,MAAMC,YAAYJ,WAAWE,MAAM,CAACZ,CAAAA,IAAKA,EAAEa,MAAM,KAAK;QACtD,MAAME,UAAUL,WAAWE,MAAM,CAACZ,CAAAA,IAAKA,EAAEa,MAAM,KAAK;QAEpDjB,QAAQC,GAAG,CAAC,CAAC,KAAK,EAAEY,IAAI,EAAE,CAAC;QAE3B,IAAIE,OAAOhB,MAAM,GAAG,GAAG;YACrBC,QAAQC,GAAG,CAAC;YACZmB,WAAWL,QAAQlB,eAAeC;QACpC;QAEA,IAAIoB,UAAUnB,MAAM,GAAG,GAAG;YACxBC,QAAQC,GAAG,CAAC;YACZmB,WAAWF,WAAWrB,eAAeC;QACvC;QAEA,IAAIqB,QAAQpB,MAAM,GAAG,GAAG;YACtBC,QAAQC,GAAG,CAAC;YACZmB,WAAWD,SAAStB,eAAeC;QACrC;IACF;AACF;AAEA,SAASsB,WAAWxB,OAA4B,EAAEC,aAAqB,EAAEC,WAAoB;IAC3FE,QAAQC,GAAG,CAAC;IACZD,QAAQC,GAAG,CAAC;IAEZ,KAAK,MAAMG,KAAKR,QAAS;QACvB,MAAMyB,UAAUC,IAAAA,kBAAQ,EAACzB,eAAeO,EAAEmB,QAAQ;QAClD,MAAMC,KAAKpB,EAAEqB,YAAY,IAAI;QAC7B,MAAMC,SAAStB,EAAEsB,MAAM,GAAI5B,cAAc6B,gBAAgBvB,EAAEsB,MAAM,IAAIE,SAASxB,EAAEsB,MAAM,EAAE/B,wBAAyB;QACjHK,QAAQC,GAAG,CAAC,CAAC,EAAE,EAAEoB,QAAQ,CAAC,EAAEjB,EAAEyB,IAAI,CAAC,GAAG,EAAEL,GAAG,GAAG,EAAEpB,EAAE0B,aAAa,CAAC,GAAG,EAAEJ,OAAO,EAAE,CAAC;IACjF;IACA1B,QAAQC,GAAG,CAAC;IAEZ,IAAIH,aAAa;QACf,uEAAuE;QACvE,MAAMiC,cAAcnC,QAAQoB,MAAM,CAACZ,CAAAA,IAAKA,EAAEsB,MAAM,IAAItB,EAAEsB,MAAM,CAACM,QAAQ,CAAC;QACtE,IAAID,YAAYhC,MAAM,GAAG,GAAG;YAC1BC,QAAQC,GAAG,CAAC;YACZ,KAAK,MAAMG,KAAK2B,YAAa;gBAC3B,MAAMV,UAAUC,IAAAA,kBAAQ,EAACzB,eAAeO,EAAEmB,QAAQ;gBAClD,MAAMC,KAAKpB,EAAEqB,YAAY,IAAI;gBAC7BzB,QAAQC,GAAG,CAAC,CAAC,KAAK,EAAEoB,QAAQ,CAAC,EAAEjB,EAAEyB,IAAI,CAAC,GAAG,EAAEL,GAAG,EAAE,CAAC;gBACjDxB,QAAQC,GAAG,CAAC;gBACZD,QAAQC,GAAG,CAACG,EAAEsB,MAAM;gBACpB1B,QAAQC,GAAG,CAAC;YACd;YACAD,QAAQC,GAAG,CAAC;QACd;IACF;AACF;AAKO,SAASP,aAAaE,OAA4B;IACvD,MAAMqC,QAAQrC,QAAQG,MAAM;IAC5B,MAAMmB,YAAYtB,QAAQoB,MAAM,CAACZ,CAAAA,IAAKA,EAAEa,MAAM,KAAK,aAAalB,MAAM;IACtE,MAAMgB,SAASnB,QAAQoB,MAAM,CAACZ,CAAAA,IAAKA,EAAEa,MAAM,KAAK,UAAUlB,MAAM;IAChE,MAAMoB,UAAUvB,QAAQoB,MAAM,CAACZ,CAAAA,IAAKA,EAAEa,MAAM,KAAK,WAAWlB,MAAM;IAElEC,QAAQC,GAAG,CAAC;IACZD,QAAQC,GAAG,CAAC,CAAC,wBAAwB,EAAEgC,OAAO;IAC9CjC,QAAQC,GAAG,CAAC,CAAC,6BAA6B,EAAEiB,WAAW;IACvDlB,QAAQC,GAAG,CAAC,CAAC,gDAAgD,EAAEc,QAAQ;IACvEf,QAAQC,GAAG,CAAC,CAAC,mCAAmC,EAAEkB,SAAS;IAC3DnB,QAAQC,GAAG,CAAC;IAEZ,IAAIiB,YAAY,KAAKH,SAAS,GAAG;QAC/Bf,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEiB,UAAU,gDAAgD,CAAC;QAC9ElB,QAAQC,GAAG,CACT,CAAC,IAAI,EAAEc,OAAO,qIAAqI,CAAC;QAEtJf,QAAQC,GAAG,CAAC;QACZD,QAAQC,GAAG,CAAC;IACd,OAAO,IAAIiB,YAAY,GAAG;QACxBlB,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEiB,UAAU,kDAAkD,CAAC;QAChFlB,QAAQC,GAAG,CAAC;IACd,OAAO,IAAIc,SAAS,GAAG;QACrBf,QAAQC,GAAG,CAAC,CAAC,IAAI,EAAEc,OAAO,iEAAiE,CAAC;QAC5Ff,QAAQC,GAAG,CAAC;IACd,OACED,QAAQC,GAAG,CAAC;AAEhB;AAEA,SAAS2B,SAASM,GAAW,EAAEC,MAAc;IAC3C,MAAMC,UAAUF,IAAIG,OAAO,CAAC,OAAO;IACnC,OAAOD,QAAQrC,MAAM,GAAGoC,SAASC,QAAQE,KAAK,CAAC,GAAGH,SAAS,KAAK,QAAQC;AAC1E;AAEA,SAAST,gBAAgBO,GAAW;IAClC,2CAA2C;IAC3C,OAAOA,IAAIG,OAAO,CAAC,OAAO,KAAKA,OAAO,CAAC,OAAO;AAChD"}
@@ -0,0 +1,70 @@
1
+ export interface FileEntry {
2
+ filePath: string;
3
+ packageName: string;
4
+ }
5
+ export type DirectiveStatus = 'redundant' | 'active' | 'skipped';
6
+ export interface DirectiveLocation {
7
+ /** 1-based line number in the original source */
8
+ line: number;
9
+ /** The full text of the directive line (for removal) */
10
+ lineText: string;
11
+ /** Whether the directive has a `// justified: <reason>` comment */
12
+ justified: boolean;
13
+ /** The justification reason, if present */
14
+ justification?: string;
15
+ }
16
+ export interface DirectiveAnalysis {
17
+ filePath: string;
18
+ packageName: string;
19
+ line: number;
20
+ functionName: string | null;
21
+ status: DirectiveStatus;
22
+ compilerEvent: 'CompileError' | 'CompileSuccess' | 'PipelineError' | 'none' | 'skipped';
23
+ reason?: string;
24
+ }
25
+ export interface AnalyzerOptions {
26
+ concurrency: number;
27
+ verbose: boolean;
28
+ }
29
+ export interface FixResult {
30
+ filesModified: number;
31
+ directivesRemoved: number;
32
+ directivesJustified: number;
33
+ }
34
+ export type CompilationMode = 'infer' | 'annotation' | 'all';
35
+ export type FunctionStatus = 'compiled' | 'skipped' | 'error';
36
+ export interface MemoStats {
37
+ memoSlots: number;
38
+ memoBlocks: number;
39
+ memoValues: number;
40
+ prunedMemoBlocks: number;
41
+ prunedMemoValues: number;
42
+ }
43
+ export interface ManualMemoization {
44
+ useMemo: number;
45
+ useCallback: number;
46
+ reactMemo: boolean;
47
+ }
48
+ export interface FunctionAnalysis {
49
+ filePath: string;
50
+ packageName: string;
51
+ line: number;
52
+ column: number;
53
+ functionName: string | null;
54
+ status: FunctionStatus;
55
+ compilerEvent: 'CompileSuccess' | 'CompileError' | 'CompileSkip' | 'PipelineError';
56
+ reason?: string;
57
+ memoStats?: MemoStats;
58
+ manualMemo?: ManualMemoization;
59
+ bodyInsertionLine?: number;
60
+ }
61
+ export interface AnnotateResult {
62
+ filesModified: number;
63
+ functionsAnnotated: number;
64
+ }
65
+ export interface CoverageAnalyzerOptions {
66
+ concurrency: number;
67
+ verbose: boolean;
68
+ compilationMode: CompilationMode;
69
+ annotate?: boolean;
70
+ }
@@ -0,0 +1,7 @@
1
+ // ── Shared types ──
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/types.ts"],"sourcesContent":["// ── Shared types ──\n\nexport interface FileEntry {\n filePath: string;\n packageName: string;\n}\n\n// ── Directive analysis types ──\n\nexport type DirectiveStatus = 'redundant' | 'active' | 'skipped';\n\nexport interface DirectiveLocation {\n /** 1-based line number in the original source */\n line: number;\n /** The full text of the directive line (for removal) */\n lineText: string;\n /** Whether the directive has a `// justified: <reason>` comment */\n justified: boolean;\n /** The justification reason, if present */\n justification?: string;\n}\n\nexport interface DirectiveAnalysis {\n filePath: string;\n packageName: string;\n line: number;\n functionName: string | null;\n status: DirectiveStatus;\n compilerEvent: 'CompileError' | 'CompileSuccess' | 'PipelineError' | 'none' | 'skipped';\n reason?: string;\n}\n\nexport interface AnalyzerOptions {\n concurrency: number;\n verbose: boolean;\n}\n\nexport interface FixResult {\n filesModified: number;\n directivesRemoved: number;\n directivesJustified: number;\n}\n\n// ── Coverage analysis types ──\n\nexport type CompilationMode = 'infer' | 'annotation' | 'all';\n\nexport type FunctionStatus = 'compiled' | 'skipped' | 'error';\n\nexport interface MemoStats {\n memoSlots: number;\n memoBlocks: number;\n memoValues: number;\n prunedMemoBlocks: number;\n prunedMemoValues: number;\n}\n\nexport interface ManualMemoization {\n useMemo: number;\n useCallback: number;\n reactMemo: boolean;\n}\n\nexport interface FunctionAnalysis {\n filePath: string;\n packageName: string;\n line: number;\n column: number;\n functionName: string | null;\n status: FunctionStatus;\n compilerEvent: 'CompileSuccess' | 'CompileError' | 'CompileSkip' | 'PipelineError';\n reason?: string;\n memoStats?: MemoStats;\n manualMemo?: ManualMemoization;\n bodyInsertionLine?: number;\n}\n\nexport interface AnnotateResult {\n filesModified: number;\n functionsAnnotated: number;\n}\n\nexport interface CoverageAnalyzerOptions {\n concurrency: number;\n verbose: boolean;\n compilationMode: CompilationMode;\n annotate?: boolean;\n}\n"],"names":[],"mappings":"AAAA,qBAAqB"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@fluentui/react-compiler-analyzer",
3
+ "version": "0.0.0-experimental.rc-analyzer.20260430-fc3bb2e193.0",
4
+ "type": "commonjs",
5
+ "main": "./dist/src/index.js",
6
+ "types": "./dist/src/index.d.ts",
7
+ "files": [
8
+ "bin",
9
+ "*.md",
10
+ "dist",
11
+ "lib",
12
+ "lib-commonjs"
13
+ ],
14
+ "bin": {
15
+ "react-compiler-analyzer": "./bin/react-compiler-analyzer.js"
16
+ },
17
+ "dependencies": {
18
+ "@swc/helpers": "~0.5.1",
19
+ "yargs": "^17.7.2",
20
+ "@babel/core": "^7.28.5",
21
+ "@babel/preset-typescript": "^7.28.5",
22
+ "babel-plugin-react-compiler": "^1.0.0"
23
+ }
24
+ }