Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/leverage-jssg-utils.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
"react-create-element-to-jsx": patch
"react-find-dom-node": patch
"react-19-3-fragment-ref-wrappers": patch
"react-prop-types-typescript": patch
"react-pure-component": patch
"react-pure-render-mixin": patch
"react-dom-to-react-dom-factories": patch
"react-native-view-prop-types": patch
"react-proptypes-to-prop-types": patch
"react-to-react-dom": patch
"react-19-remove-forward-ref": patch
"react-19-remove-legacy-context": patch
"react-19-remove-memoization": patch
"react-19-replace-act-import": patch
"react-19-replace-create-factory": patch
"react-19-replace-reactdom-render": patch
"react-19-replace-string-ref": patch
"react-19-replace-use-form-state": patch
"react-sort-comp": patch
"react-19-3-unprefix-stable-apis": patch
"react-update-react-imports": patch
"react-19-3-use-browser": patch
"react-19-use-context-hook": patch
---

Adopt `@jssg/utils` import helpers (`getImport`, `getAllImports`, `addImport`, `removeImport`, `updateImport`) across React JSSG codemods, bump to `^0.0.10`, and add fixtures covering CJS, aliases, quote/style merges, and multi-specifier import cleanup.
2 changes: 1 addition & 1 deletion codemods/create-element-to-jsx/codemod.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version: "1.0"

name: "react-create-element-to-jsx"
version: "0.1.2"
version: "0.1.3"
description: "Convert React.createElement calls to JSX"
author: "Codemod <hello@codemod.com>"
license: "MIT"
Expand Down
10 changes: 7 additions & 3 deletions codemods/create-element-to-jsx/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
{
"name": "react-create-element-to-jsx",
"version": "0.1.2",
"version": "0.1.3",
"description": "Convert React.createElement calls to JSX",
"type": "module",
"scripts": {
"test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts && node --test ./scripts/error-tests.mjs ./scripts/differential-tests.mjs",
"test": "codemod jssg test -l tsx ./scripts/codemod.ts && node --test ./scripts/error-tests.mjs ./scripts/differential-tests.mjs",
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@codemod.com/jssg-types": "latest",
"@types/node": "latest",
"typescript": "latest"
"typescript": "^7.0.2",
"codemod": "1.18.6"
},
"dependencies": {
"@jssg/utils": "^0.0.10"
}
}
40 changes: 32 additions & 8 deletions codemods/create-element-to-jsx/scripts/codemod.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Transform, Edit, SgNode } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { useMetricAtom } from "codemod:metrics";
import { getAllImports } from "@jssg/utils/javascript/imports";

type Conversion = {
code: string;
Expand Down Expand Up @@ -33,7 +34,21 @@ type ArgumentLayout = {
boundaryComments: SgNode<TSX>[][];
};

const REACT_MODULES = new Set(["React", "react", "react/addons", "react-native"]);
const REACT_MODULES = ["React", "react", "react/addons", "react-native"] as const;
const REACT_MODULE_SET = new Set<string>(REACT_MODULES);

/** Resolved per-transform from import/require bindings (supports `import * as R`). */
let currentReactAliases = new Set<string>(["React"]);

function resolveReactAliases(rootNode: SgNode<TSX, "program">): Set<string> {
const aliases = new Set<string>();
for (const from of REACT_MODULES) {
for (const imp of getAllImports(rootNode, { type: "default", from })) {
aliases.add(imp.alias);
}
}
return aliases.size > 0 ? aliases : new Set(["React"]);
}

function metricFile(filename: string): string {
const cwd = process.cwd() + "/";
Expand Down Expand Up @@ -92,10 +107,6 @@ function stringValue(node: SgNode<TSX>): string {
return evaluated;
}

function isReactModuleName(value: string | null): boolean {
return value !== null && REACT_MODULES.has(value);
}

function stringNodeFrom(node: SgNode<TSX>): SgNode<TSX> | null {
if (node.kind() === "string") {
return node;
Expand Down Expand Up @@ -144,6 +155,10 @@ function hasReact(rootNode: SgNode<TSX>): boolean {
return false;
}

function isReactModuleName(value: string | null): boolean {
return value !== null && REACT_MODULE_SET.has(value);
}

function callArguments(call: SgNode<TSX>): SgNode<TSX>[] {
const args = call.field("arguments");
if (!args) {
Expand All @@ -167,7 +182,7 @@ function isReactCreateElementCall(node: SgNode<TSX>): boolean {
const property = callee.field("property");

return object?.kind() === "identifier" &&
object.text() === "React" &&
currentReactAliases.has(object.text()) &&
property?.kind() === "property_identifier" &&
property.text() === "createElement";
}
Expand Down Expand Up @@ -489,7 +504,7 @@ function propsToDescriptors(node: SgNode<TSX> | null, source: string): {
return { attrs: [], trailingElementComments: [], consumedBoundaryComments: [] };
}

if (isMemberCall(node, "React", "__spread") || isMemberCall(node, "Object", "assign")) {
if (isReactSpreadCall(node) || isMemberCall(node, "Object", "assign")) {
const callee = node.field("function");
const calleeObject = callee?.field("object") ?? null;
const calleeProperty = callee?.field("property") ?? null;
Expand Down Expand Up @@ -745,12 +760,19 @@ function isMemberCall(node: SgNode<TSX>, objectName: string, propertyName: strin
property.text() === propertyName;
}

function isReactSpreadCall(node: SgNode<TSX>): boolean {
for (const alias of currentReactAliases) {
if (isMemberCall(node, alias, "__spread")) return true;
}
return false;
}

function propsToAttributes(node: SgNode<TSX> | null): string[] {
if (!node || node.kind() === "null") {
return [];
}

if (isMemberCall(node, "React", "__spread") || isMemberCall(node, "Object", "assign")) {
if (isReactSpreadCall(node) || isMemberCall(node, "Object", "assign")) {
return callArguments(node).flatMap((arg) => propsToAttributes(arg));
}

Expand Down Expand Up @@ -1214,6 +1236,8 @@ const transform: Transform<TSX> = async (root, options) => {
return null;
}

currentReactAliases = resolveReactAliases(rootNode);

const canConvertCall = (call: SgNode<TSX>): boolean => {
const cached = convertibility.get(call.id());
if (cached !== undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,7 @@ function runJssg(source) {
spawnSync("git", ["add", "input.tsx", ".gitignore"], { cwd: dir });
spawnSync("git", ["commit", "-qm", "init"], { cwd: dir });

const result = spawnSync(
"pnpm",
[
"dlx",
"codemod@latest",
const result = spawnSync("codemod", [
"workflow",
"run",
"-w",
Expand Down
6 changes: 1 addition & 5 deletions codemods/create-element-to-jsx/scripts/error-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ function runCodemod(input) {
writeFileSync(filePath, input, "utf8");

try {
return spawnSync(
"pnpm",
[
"dlx",
"codemod@latest",
return spawnSync("codemod", [
"workflow",
"run",
"-w",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as R from 'React';

var a = <Foo />;
var b = <div />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as R from 'React';

var a = R.createElement(Foo, null);
var b = R.createElement('div', null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"create-element-to-jsx-conversions": [
{
"cardinality": {
"file": "tests/aliased-namespace-react/input.tsx"
},
"count": 2
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import R from 'react';
import React from 'react';

var a = <div />;
var b = <Foo />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import R from 'react';
import React from 'react';

var a = React.createElement('div', null);
var b = R.createElement(Foo, null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"create-element-to-jsx-conversions": [
{
"cardinality": {
"file": "tests/multiple-default-react-aliases/input.tsx"
},
"count": 2
}
]
}
7 changes: 4 additions & 3 deletions codemods/error-boundaries/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
"description": "Rename unstable_handleError to componentDidCatch",
"type": "module",
"scripts": {
"test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts",
"test": "codemod jssg test -l tsx ./scripts/codemod.ts",
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@codemod.com/jssg-types": "latest",
"typescript": "latest",
"@types/node": "latest"
"typescript": "^7.0.2",
"@types/node": "latest",
"codemod": "1.18.6"
}
}
2 changes: 1 addition & 1 deletion codemods/find-dom-node/codemod.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version: "1.0"

name: "react-find-dom-node"
version: "0.1.2"
version: "0.1.3"
description: "Replace getDOMNode() with React.findDOMNode()"
author: "Codemod <hello@codemod.com>"
license: "MIT"
Expand Down
11 changes: 6 additions & 5 deletions codemods/find-dom-node/package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
{
"name": "react-find-dom-node",
"version": "0.1.2",
"version": "0.1.3",
"description": "Replace getDOMNode() with React.findDOMNode()",
"type": "module",
"scripts": {
"test": "pnpm dlx codemod@latest jssg test -l tsx ./scripts/codemod.ts",
"test": "codemod jssg test -l tsx ./scripts/codemod.ts",
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@codemod.com/jssg-types": "latest",
"typescript": "latest",
"@types/node": "latest"
"typescript": "^7.0.2",
"@types/node": "latest",
"codemod": "1.18.6"
},
"dependencies": {
"@jssg/utils": "^0.0.2"
"@jssg/utils": "^0.0.10"
}
}
Loading
Loading