-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add scoped-jsx transform (#214)
* feat: Add `scoped-jsx` transform * Ensure only a single import is added * Ensure type parameters are preserved * rebase * Remove fsevents-patch * Handle existing React import * Add proper changelog
- Loading branch information
Showing
7 changed files
with
275 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
--- | ||
"types-react-codemod": minor | ||
--- | ||
|
||
Add scoped-jsx transform | ||
|
||
This replaces usage of the deprecated global JSX namespace with usage of the scoped namespace: | ||
|
||
```diff | ||
+import { JSX } from 'react' | ||
|
||
const element: JSX.Element | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
const { describe, expect, test } = require("@jest/globals"); | ||
const dedent = require("dedent"); | ||
const JscodeshiftTestUtils = require("jscodeshift/dist/testUtils"); | ||
const scopedJSXTransform = require("../scoped-jsx"); | ||
|
||
function applyTransform(source, options = {}) { | ||
return JscodeshiftTestUtils.applyTransform(scopedJSXTransform, options, { | ||
path: "test.d.ts", | ||
source: dedent(source), | ||
}); | ||
} | ||
|
||
describe("transform scoped-jsx", () => { | ||
test("not modified", () => { | ||
expect( | ||
applyTransform(` | ||
import * as React from 'react'; | ||
interface Props { | ||
children?: ReactNode; | ||
} | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import * as React from 'react'; | ||
interface Props { | ||
children?: ReactNode; | ||
}" | ||
`); | ||
}); | ||
|
||
test("no import yet", () => { | ||
expect( | ||
applyTransform(` | ||
declare const element: JSX.Element; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import { JSX } from "react"; | ||
declare const element: JSX.Element;" | ||
`); | ||
}); | ||
|
||
test("existing namespace import", () => { | ||
expect( | ||
applyTransform(` | ||
import * as React from 'react'; | ||
declare const element: JSX.Element; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import * as React from 'react'; | ||
declare const element: React.JSX.Element;" | ||
`); | ||
}); | ||
|
||
test("existing type namespace import", () => { | ||
expect( | ||
applyTransform(` | ||
import type * as React from 'react'; | ||
declare const element: JSX.Element; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import type * as React from 'react'; | ||
declare const element: React.JSX.Element;" | ||
`); | ||
}); | ||
|
||
test("existing named import", () => { | ||
expect( | ||
applyTransform(` | ||
import { ReactNode } from 'react'; | ||
declare const element: JSX.Element; | ||
declare const node: ReactNode; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import { ReactNode, JSX } from 'react'; | ||
declare const element: JSX.Element; | ||
declare const node: ReactNode;" | ||
`); | ||
}); | ||
|
||
test("existing named import", () => { | ||
expect( | ||
applyTransform(` | ||
import { JSX } from 'react'; | ||
declare const element: JSX.Element; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import { JSX } from 'react'; | ||
declare const element: JSX.Element;" | ||
`); | ||
}); | ||
|
||
test("existing namespace require", () => { | ||
expect( | ||
applyTransform(` | ||
const React = require('react'); | ||
declare const element: JSX.Element; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import { JSX } from "react"; | ||
const React = require('react'); | ||
declare const element: JSX.Element;" | ||
`); | ||
}); | ||
|
||
test("insert position", () => { | ||
expect( | ||
applyTransform(` | ||
import {} from 'react-dom' | ||
import {} from '@testing-library/react' | ||
declare const element: JSX.Element; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import {} from 'react-dom' | ||
import {} from '@testing-library/react' | ||
import { JSX } from "react"; | ||
declare const element: JSX.Element;" | ||
`); | ||
}); | ||
|
||
test("type parameters are preserved", () => { | ||
expect( | ||
applyTransform(` | ||
import * as React from 'react' | ||
declare const attributes: JSX.LibraryManagedAttributes<A, B>; | ||
`), | ||
).toMatchInlineSnapshot(` | ||
"import * as React from 'react' | ||
declare const attributes: React.JSX.LibraryManagedAttributes<A, B>;" | ||
`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
const parseSync = require("./utils/parseSync"); | ||
|
||
/** | ||
* @type {import('jscodeshift').Transform} | ||
*/ | ||
const deprecatedReactChildTransform = (file, api) => { | ||
const j = api.jscodeshift; | ||
const ast = parseSync(file); | ||
|
||
/** | ||
* @type {string | null} | ||
*/ | ||
let reactNamespaceName = null; | ||
ast.find(j.ImportDeclaration).forEach((importDeclaration) => { | ||
const node = importDeclaration.value; | ||
if ( | ||
node.source.value === "react" && | ||
node.specifiers?.[0]?.type === "ImportNamespaceSpecifier" | ||
) { | ||
reactNamespaceName = node.specifiers[0].local?.name ?? null; | ||
} | ||
}); | ||
|
||
const globalNamespaceReferences = ast.find(j.TSTypeReference, (node) => { | ||
const { typeName } = node; | ||
|
||
if (typeName.type === "TSQualifiedName") { | ||
return ( | ||
typeName.left.type === "Identifier" && | ||
typeName.left.name === "JSX" && | ||
typeName.right.type === "Identifier" | ||
); | ||
} | ||
return false; | ||
}); | ||
|
||
let hasChanges = false; | ||
if (reactNamespaceName !== null && globalNamespaceReferences.length > 0) { | ||
hasChanges = true; | ||
|
||
globalNamespaceReferences.replaceWith((typeReference) => { | ||
const namespaceMember = typeReference | ||
.get("typeName") | ||
.get("right") | ||
.get("name").value; | ||
|
||
return j.tsTypeReference( | ||
j.tsQualifiedName( | ||
j.tsQualifiedName( | ||
j.identifier(/** @type {string} */ (reactNamespaceName)), | ||
j.identifier("JSX"), | ||
), | ||
j.identifier(namespaceMember), | ||
), | ||
typeReference.value.typeParameters, | ||
); | ||
}); | ||
} else if (globalNamespaceReferences.length > 0) { | ||
const reactImport = ast.find(j.ImportDeclaration, { | ||
source: { value: "react" }, | ||
}); | ||
const jsxImportSpecifier = reactImport.find(j.ImportSpecifier, { | ||
imported: { name: "JSX" }, | ||
}); | ||
|
||
if (jsxImportSpecifier.length === 0) { | ||
hasChanges = true; | ||
|
||
const hasExistingReactImport = reactImport.length > 0; | ||
if (hasExistingReactImport) { | ||
reactImport | ||
.get("specifiers") | ||
.value.push(j.importSpecifier(j.identifier("JSX"))); | ||
} else { | ||
const jsxNamespaceImport = j.importDeclaration( | ||
[j.importSpecifier(j.identifier("JSX"))], | ||
j.stringLiteral("react"), | ||
); | ||
|
||
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; | ||
}; | ||
|
||
module.exports = deprecatedReactChildTransform; |