types-react-codemod
Version:
Collection of transforms for [jscodeshift](https://github.com/facebook/jscodeshift) related to `@types/react`.
89 lines (77 loc) • 2.43 kB
JavaScript
const parseSync = require("./utils/parseSync");
const {
addReactTypeImport,
findReactImportForNewImports,
} = require("./utils/newReactImports");
const traverse = require("@babel/traverse").default;
/**
* @param {import('jscodeshift').FileInfo} file
* @param {import('jscodeshift').API} api
*/
const scopedJsxTransform = (file, api) => {
const j = api.jscodeshift;
const ast = parseSync(file);
let hasChanges = false;
let hasGlobalNamespaceReferences = false;
// ast.get("program").value is sufficient for unit tests but not actually running it on files
// TODO: How to test?
const traverseRoot = ast.paths()[0].value;
traverse(traverseRoot, {
TSTypeReference({ node: typeReference }) {
const { typeName } = typeReference;
if (typeName.type === "TSQualifiedName") {
if (
typeName.left.type === "Identifier" &&
typeName.left.name === "JSX" &&
typeName.right.type === "Identifier"
) {
hasGlobalNamespaceReferences = true;
}
}
},
});
if (hasGlobalNamespaceReferences && !isJsxAlreadyImported(j, ast)) {
const reactImport = findReactImportForNewImports(j, ast);
const jsxImportSpecifier = reactImport.find(j.ImportSpecifier, {
imported: { name: "JSX" },
});
if (jsxImportSpecifier.length === 0) {
hasChanges = true;
const hasExistingReactImport = reactImport.length > 0;
if (hasExistingReactImport) {
addReactTypeImport(j, reactImport);
} else {
const importSpecifier = j.importSpecifier(j.identifier("JSX"));
const jsxNamespaceImport = j.importDeclaration(
[importSpecifier],
j.stringLiteral("react"),
// Not using inline type import because it violates https://typescript-eslint.io/rules/no-import-type-side-effects/
"type",
);
const lastImport = ast.find(j.ImportDeclaration).at(-1);
if (lastImport.length > 0) {
lastImport.insertAfter(jsxNamespaceImport);
} else {
// TODO: Intuitively I wanted to do `ast.insertBefore` but that crashes
ast.get("program").get("body").value.unshift(jsxNamespaceImport);
}
}
}
}
if (hasChanges) {
return ast.toSource();
}
return file.source;
};
/**
* @param {import('jscodeshift').API['jscodeshift']} j
* @param {import('jscodeshift').Collection} ast
*/
function isJsxAlreadyImported(j, ast) {
return (
ast.find(j.ImportSpecifier, {
local: { name: "JSX" },
}).length > 0
);
}
module.exports = scopedJsxTransform;