@thinksharpe/react-compiler-unmemo 0.5.4 → 0.5.5

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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
5
5
  [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square)](https://nodejs.org)
6
- [![Tests](https://img.shields.io/badge/tests-62%20passing-brightgreen?style=flat-square)](./tests)
6
+ [![Tests](https://img.shields.io/badge/tests-69%20passing-brightgreen?style=flat-square)](./tests)
7
7
 
8
8
  A codemod that removes `useMemo` and `useCallback` from your React codebase so you can adopt [React Compiler](https://react.dev/learn/react-compiler) without the manual cleanup.
9
9
 
@@ -84,6 +84,22 @@ export function findClosingParen(content, startIdx) {
84
84
 
85
85
  if (inString || inTemplate) continue;
86
86
 
87
+ // Skip line comments
88
+ if (ch === "/" && content[i + 1] === "/") {
89
+ const eol = content.indexOf("\n", i + 2);
90
+ if (eol === -1) return -1;
91
+ i = eol;
92
+ continue;
93
+ }
94
+
95
+ // Skip block comments
96
+ if (ch === "/" && content[i + 1] === "*") {
97
+ const close = content.indexOf("*/", i + 2);
98
+ if (close === -1) return -1;
99
+ i = close + 1;
100
+ continue;
101
+ }
102
+
87
103
  if (ch === "(") depth++;
88
104
  if (ch === ")") {
89
105
  depth--;
@@ -120,6 +136,22 @@ export function stripDepsArray(inner) {
120
136
  }
121
137
  if (inString) continue;
122
138
 
139
+ // Skip line comments
140
+ if (ch === "/" && inner[i + 1] === "/") {
141
+ const eol = inner.indexOf("\n", i + 2);
142
+ if (eol === -1) break;
143
+ i = eol;
144
+ continue;
145
+ }
146
+
147
+ // Skip block comments
148
+ if (ch === "/" && inner[i + 1] === "*") {
149
+ const close = inner.indexOf("*/", i + 2);
150
+ if (close === -1) break;
151
+ i = close + 1;
152
+ continue;
153
+ }
154
+
123
155
  if (ch === "(" || ch === "[" || ch === "{") depth++;
124
156
  if (ch === ")" || ch === "]" || ch === "}") depth--;
125
157
  if (ch === "," && depth === 0) {
@@ -158,6 +190,22 @@ export function unwrapArrowFn(inner) {
158
190
  }
159
191
  if (inString) continue;
160
192
 
193
+ // Skip line comments
194
+ if (ch === "/" && inner[i + 1] === "/") {
195
+ const eol = inner.indexOf("\n", i + 2);
196
+ if (eol === -1) break;
197
+ i = eol;
198
+ continue;
199
+ }
200
+
201
+ // Skip block comments
202
+ if (ch === "/" && inner[i + 1] === "*") {
203
+ const close = inner.indexOf("*/", i + 2);
204
+ if (close === -1) break;
205
+ i = close + 1;
206
+ continue;
207
+ }
208
+
161
209
  if (ch === "(" || ch === "[" || ch === "{") depth++;
162
210
  if (ch === ")" || ch === "]" || ch === "}") depth--;
163
211
  if (ch === "=" && inner[i + 1] === ">" && depth === 0) {
@@ -434,37 +482,53 @@ export function processFile(filePath, { dryRun = false } = {}) {
434
482
  if (!found) break;
435
483
  }
436
484
 
437
- // Phase 3: Clean up imports
485
+ // Phase 3: Clean up imports (skip matches inside strings/comments)
438
486
  if (changed) {
439
- // Handle: import React, { useMemo, useCallback, ... } from "react"; (single or double quotes, semicolon optional)
440
- content = content.replace(
441
- /import React, \{([^}]*)\} from (["'])react\2[^\S\n]*;?/g,
442
- (match, imports, quote) => {
443
- const semi = match.trimEnd().endsWith(";") ? ";" : "";
444
- const cleaned = imports
445
- .split(",")
446
- .map((s) => s.trim())
447
- .filter((s) => s && s !== "useMemo" && s !== "useCallback")
448
- .join(", ");
449
- if (!cleaned) return `import React from ${quote}react${quote}${semi}`;
450
- return `import React, { ${cleaned} } from ${quote}react${quote}${semi}`;
487
+ const importPatterns = [
488
+ {
489
+ regex: /import React, \{([^}]*)\} from (["'])react\2[^\S\n]*;?/g,
490
+ replace: (match, imports, quote) => {
491
+ const semi = match.trimEnd().endsWith(";") ? ";" : "";
492
+ const cleaned = imports
493
+ .split(",")
494
+ .map((s) => s.trim())
495
+ .filter((s) => s && s !== "useMemo" && s !== "useCallback")
496
+ .join(", ");
497
+ if (!cleaned) return `import React from ${quote}react${quote}${semi}`;
498
+ return `import React, { ${cleaned} } from ${quote}react${quote}${semi}`;
499
+ },
451
500
  },
452
- );
453
-
454
- // Handle: import { useMemo, useCallback, ... } from "react"; (single or double quotes, semicolon optional)
455
- content = content.replace(
456
- /import \{([^}]*)\} from (["'])react\2[^\S\n]*;?/g,
457
- (match, imports, quote) => {
458
- const semi = match.trimEnd().endsWith(";") ? ";" : "";
459
- const cleaned = imports
460
- .split(",")
461
- .map((s) => s.trim())
462
- .filter((s) => s && s !== "useMemo" && s !== "useCallback")
463
- .join(", ");
464
- if (!cleaned) return ""; // Remove empty import entirely
465
- return `import { ${cleaned} } from ${quote}react${quote}${semi}`;
501
+ {
502
+ regex: /import \{([^}]*)\} from (["'])react\2[^\S\n]*;?/g,
503
+ replace: (match, imports, quote) => {
504
+ const semi = match.trimEnd().endsWith(";") ? ";" : "";
505
+ const cleaned = imports
506
+ .split(",")
507
+ .map((s) => s.trim())
508
+ .filter((s) => s && s !== "useMemo" && s !== "useCallback")
509
+ .join(", ");
510
+ if (!cleaned) return ""; // Remove empty import entirely
511
+ return `import { ${cleaned} } from ${quote}react${quote}${semi}`;
512
+ },
466
513
  },
467
- );
514
+ ];
515
+
516
+ for (const { regex, replace } of importPatterns) {
517
+ let result = "";
518
+ let lastIndex = 0;
519
+ let m;
520
+ regex.lastIndex = 0;
521
+ while ((m = regex.exec(content)) !== null) {
522
+ if (isInsideStringOrComment(content, m.index)) {
523
+ continue;
524
+ }
525
+ result += content.slice(lastIndex, m.index);
526
+ result += replace(m[0], m[1], m[2]);
527
+ lastIndex = m.index + m[0].length;
528
+ }
529
+ result += content.slice(lastIndex);
530
+ content = result;
531
+ }
468
532
 
469
533
  // Clean up any resulting blank lines from removed imports
470
534
  content = content.replace(/\n\n\n+/g, "\n\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thinksharpe/react-compiler-unmemo",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "Remove useMemo and useCallback hooks from React codebases to leverage React Compiler automatic optimization",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,13 +23,24 @@
23
23
  * npx react-compiler-unmemo /absolute/path/to/project --files "app/**\/*.tsx"
24
24
  */
25
25
 
26
+ import fs from "fs";
26
27
  import path from "path";
28
+ import { fileURLToPath } from "url";
29
+
27
30
  import { run as fixTypes } from "./helpers/fix-type-annotations.mjs";
28
31
  import { run as removeHooks } from "./helpers/remove-hooks.mjs";
29
32
 
30
33
  // ─── CLI ─────────────────────────────────────────────────────────────────────
31
34
 
32
35
  const args = process.argv.slice(2);
36
+
37
+ if (args.includes("--version") || args.includes("-v")) {
38
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
39
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"));
40
+ console.log(pkg.version);
41
+ process.exit(0);
42
+ }
43
+
33
44
  const write = args.includes("--write");
34
45
  const dryRun = !write;
35
46
  const verbose = args.includes("--verbose");
@@ -52,6 +63,7 @@ if (!targetDir) {
52
63
  console.log(" --verbose Show detailed output per transformation");
53
64
  console.log(" --files <glob> File glob pattern (default: src/**/*.{tsx,ts})");
54
65
  console.log(" --skip-fix Skip the type annotation fix step");
66
+ console.log(" --version, -v Show version number");
55
67
  console.log();
56
68
  console.log("Examples:");
57
69
  console.log(" npx react-compiler-unmemo ./my-react-app # preview (safe default)");