Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1/**
2 * Codemod to replace namespaced React calls with named imports
3 *
4 * Before:
5 * import React from 'react'
6 * React.useEffect(() => {}, [])
7 *
8 * After:
9 * import { useEffect } from 'react'
10 * useEffect(() => {}, [])
11 *
12 * Usage: jscodeshift -t .jscodeshift/react-import.js <file-path>
13 * Example: jscodeshift -t .jscodeshift/react-import.js src/App.native.tsx
14 */
15
16/* eslint-disable */
17
18export const parser = 'tsx'
19
20export default function transformer(file, api) {
21 const j = api.jscodeshift
22 const root = j(file.source)
23
24 // Find the React import
25 let reactImportPath = null
26 const reactMembers = new Set()
27
28 root.find(j.ImportDeclaration).forEach(path => {
29 const node = path.value
30 if (node.source.value === 'react') {
31 node.specifiers.forEach(spec => {
32 // Check if this is a default import of React
33 if (
34 spec.type === 'ImportDefaultSpecifier' &&
35 spec.local.name === 'React'
36 ) {
37 reactImportPath = path
38 }
39 })
40 }
41 })
42
43 if (!reactImportPath) {
44 // No React import found, nothing to do
45 return file.source
46 }
47
48 // Find all React.* member expressions
49 root
50 .find(j.MemberExpression)
51 .filter(path => {
52 const node = path.value
53 return (
54 node.object.type === 'Identifier' &&
55 node.object.name === 'React' &&
56 node.property.type === 'Identifier'
57 )
58 })
59 .forEach(path => {
60 const propertyName = path.value.property.name
61 reactMembers.add(propertyName)
62 })
63
64 // Find all React.* JSX member expressions (e.g., <React.Fragment>)
65 root
66 .find(j.JSXMemberExpression)
67 .filter(path => {
68 const node = path.value
69 return node.object.name === 'React' && node.property.name
70 })
71 .forEach(path => {
72 const propertyName = path.value.property.name
73 reactMembers.add(propertyName)
74 })
75
76 // If no React members are used, remove the import
77 if (reactMembers.size === 0) {
78 reactImportPath.prune()
79 return root.toSource()
80 }
81
82 // Sort the members for consistent output
83 const sortedMembers = Array.from(reactMembers).sort()
84
85 // Create new import specifiers
86 const newSpecifiers = sortedMembers.map(name =>
87 j.importSpecifier(j.identifier(name), j.identifier(name)),
88 )
89
90 // Get the existing import specifiers
91 const sortedImports = Array.from(reactImportPath.value.specifiers).sort()
92 const existingSpecifiers = sortedImports.filter(
93 specifier => specifier.type !== 'ImportDefaultSpecifier',
94 )
95
96 const allSpecifiers = [
97 ...new Map(
98 [...existingSpecifiers, ...newSpecifiers].map(item => [
99 item.imported.name,
100 item,
101 ]),
102 ).values(),
103 ]
104
105 // Update the import declaration
106 reactImportPath.value.specifiers = allSpecifiers
107
108 // Replace all React.* member expressions with just the identifier
109 root
110 .find(j.MemberExpression)
111 .filter(path => {
112 const node = path.value
113 return (
114 node.object.type === 'Identifier' &&
115 node.object.name === 'React' &&
116 node.property.type === 'Identifier'
117 )
118 })
119 .replaceWith(path => {
120 return j.identifier(path.value.property.name)
121 })
122
123 // Replace all React.* JSX member expressions with just the identifier
124 root
125 .find(j.JSXMemberExpression)
126 .filter(path => {
127 const node = path.value
128 return node.object.name === 'React' && node.property.name
129 })
130 .replaceWith(path => {
131 return j.jsxIdentifier(path.value.property.name)
132 })
133
134 return root.toSource()
135}