ts-morph-react 1.0.0
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 +270 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# ts-morph-react
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/ts-morph-react)
|
|
4
|
+
[](https://www.npmjs.com/package/ts-morph-react)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
A powerful collection of **AST transformers for ts-morph** to automate React code refactoring. Enforce consistent code patterns, modernize your codebase, and enforce best practices with declarative, composable transformers.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🔄 **Enforce Direct Exports** - Convert separate export statements to direct exports on function declarations
|
|
12
|
+
- 🎯 **Function Components** - Automatically convert function declarations to arrow function components with proper typing
|
|
13
|
+
- 📦 **Named Imports** - Transform default imports to named imports for consistency
|
|
14
|
+
- 🎨 **Code Formatting** - Format code and organize imports according to your style guide
|
|
15
|
+
- ⚡ **Composable** - Mix and match transformers to create your refactoring pipeline
|
|
16
|
+
- 🛡️ **Type-Safe** - Built with TypeScript for a fully typed experience
|
|
17
|
+
- 🎭 **AST-Powered** - Leverage ts-morph for precise, reliable code transformations
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install ts-morph-react
|
|
23
|
+
# or
|
|
24
|
+
pnpm add ts-morph-react
|
|
25
|
+
# or
|
|
26
|
+
yarn add ts-morph-react
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### As a Library
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Project } from 'ts-morph'
|
|
35
|
+
import { transform } from 'ts-morph-react'
|
|
36
|
+
|
|
37
|
+
const project = new Project()
|
|
38
|
+
const sourceFile = project.addSourceFileAtPath('src/Button.tsx')
|
|
39
|
+
|
|
40
|
+
// Run transformers with your configuration
|
|
41
|
+
await transform(sourceFile, {
|
|
42
|
+
enforceDirectExports: true,
|
|
43
|
+
enforceFunctionComponent: true,
|
|
44
|
+
enforceNamedImports: true,
|
|
45
|
+
enforceFormat: true
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Save changes
|
|
49
|
+
await sourceFile.save()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Transformers
|
|
53
|
+
|
|
54
|
+
### enforceDirectExports
|
|
55
|
+
|
|
56
|
+
Converts separate export statement to direct exports.
|
|
57
|
+
|
|
58
|
+
❌ **Before:**
|
|
59
|
+
```tsx
|
|
60
|
+
function Button<ButtonProps>({ label }) {
|
|
61
|
+
return <button>{label}</button>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { Button }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
✅ **After:**
|
|
68
|
+
```tsx
|
|
69
|
+
export function Button(props) {
|
|
70
|
+
return <button>{props.label}</button>
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### enforceFunctionComponent
|
|
75
|
+
|
|
76
|
+
Converts plain function components to properly typed `React.FunctionComponent` components, preserving prop types.
|
|
77
|
+
|
|
78
|
+
❌ **Before:**
|
|
79
|
+
```tsx
|
|
80
|
+
function Button(props: ButtonProps) {
|
|
81
|
+
return <button>{props.label}</button>
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
✅ **After:**
|
|
86
|
+
```tsx
|
|
87
|
+
const Button: React.FunctionComponent<ButtonProps> = (props) => {
|
|
88
|
+
return <button>{props.label}</button>
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### enforceNamedImports
|
|
93
|
+
|
|
94
|
+
Transforms default imports to named imports for better tree-shaking and consistency.
|
|
95
|
+
|
|
96
|
+
❌ **Before:**
|
|
97
|
+
```tsx
|
|
98
|
+
import * as React from 'react'
|
|
99
|
+
|
|
100
|
+
export const Button: React.FunctionComponent<ButtonProps> = ({ label }) => {
|
|
101
|
+
return <button>{label}</button>
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
✅ **After:**
|
|
106
|
+
```tsx
|
|
107
|
+
import { FunctionComponent } from 'react'
|
|
108
|
+
|
|
109
|
+
export const Button: FunctionComponent<ButtonProps> = ({ label }) => {
|
|
110
|
+
return <button>{label}</button>
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### enforceFormat
|
|
115
|
+
|
|
116
|
+
Formats code and organizes imports according to your style guide. Respects all standard TypeScript formatting options.
|
|
117
|
+
|
|
118
|
+
**Usage:**
|
|
119
|
+
```tsx
|
|
120
|
+
import { transform } from 'ts-morph-react'
|
|
121
|
+
|
|
122
|
+
await transform(sourceFile, {
|
|
123
|
+
enforceFormat: true,
|
|
124
|
+
format: {
|
|
125
|
+
indentSize: 2,
|
|
126
|
+
convertTabsToSpaces: true,
|
|
127
|
+
semicolons: ts.SemicolonPreference.Remove
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
❌ **Before:**
|
|
133
|
+
```tsx
|
|
134
|
+
import * as React from 'react';
|
|
135
|
+
|
|
136
|
+
import { Text } from '@/components/Text'
|
|
137
|
+
|
|
138
|
+
export const Button: React.FunctionComponent<ButtonProps> = ({ label }) => {
|
|
139
|
+
return <button><Text>{label}</Text></button>;
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
✅ **After:**
|
|
144
|
+
```tsx
|
|
145
|
+
import * as React from 'react'
|
|
146
|
+
import { Text } from '@/components/Text'
|
|
147
|
+
|
|
148
|
+
export const Button: React.FunctionComponent<ButtonProps> = ({ label }) => {
|
|
149
|
+
return <button><Text>{label}</Text></button>
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## API Reference
|
|
154
|
+
|
|
155
|
+
### `transform(sourceFile: SourceFile, config?: TransformerConfig)`
|
|
156
|
+
|
|
157
|
+
Applies transformers to a source file.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
interface TransformerConfig {
|
|
161
|
+
enforceDirectExports: boolean
|
|
162
|
+
enforceFunctionComponent: boolean
|
|
163
|
+
enforceNamedImports: boolean
|
|
164
|
+
enforceFormat: boolean
|
|
165
|
+
format: {
|
|
166
|
+
baseIndentSize: number
|
|
167
|
+
convertTabsToSpaces: boolean
|
|
168
|
+
ensureNewLineAtEndOfFile: boolean
|
|
169
|
+
indentMultiLineObjectLiteralBeginningOnBlankLine: boolean
|
|
170
|
+
indentSize: number
|
|
171
|
+
indentStyle: IndentStyle
|
|
172
|
+
indentSwitchCase: boolean
|
|
173
|
+
insertSpaceAfterCommaDelimiter: boolean
|
|
174
|
+
insertSpaceAfterConstructor: boolean
|
|
175
|
+
insertSpaceAfterFunctionKeywordForAnonymousFunctions: boolean
|
|
176
|
+
insertSpaceAfterKeywordsInControlFlowStatements: boolean
|
|
177
|
+
insertSpaceAfterOpeningAndBeforeClosingEmptyBraces: boolean
|
|
178
|
+
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: boolean
|
|
179
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: boolean
|
|
180
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: boolean
|
|
181
|
+
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: boolean
|
|
182
|
+
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean
|
|
183
|
+
insertSpaceAfterSemicolonInForStatements: boolean
|
|
184
|
+
insertSpaceAfterTypeAssertion: boolean
|
|
185
|
+
insertSpaceBeforeAndAfterBinaryOperators: boolean
|
|
186
|
+
insertSpaceBeforeFunctionParenthesis: boolean
|
|
187
|
+
insertSpaceBeforeTypeAnnotation: boolean
|
|
188
|
+
newLineCharacter: string
|
|
189
|
+
placeOpenBraceOnNewLineForControlBlocks: boolean
|
|
190
|
+
placeOpenBraceOnNewLineForFunctions: boolean
|
|
191
|
+
semicolons: SemicolonPreference
|
|
192
|
+
tabSize: number
|
|
193
|
+
trimTrailingWhitespace: boolean
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Build the library
|
|
202
|
+
pnpm build
|
|
203
|
+
|
|
204
|
+
# Watch mode during development
|
|
205
|
+
pnpm watch
|
|
206
|
+
|
|
207
|
+
# Run tests
|
|
208
|
+
pnpm test
|
|
209
|
+
|
|
210
|
+
# Watch mode for tests
|
|
211
|
+
pnpm test:watch
|
|
212
|
+
|
|
213
|
+
# Run tests with UI
|
|
214
|
+
pnpm test:ui
|
|
215
|
+
|
|
216
|
+
# Lint and type-check
|
|
217
|
+
pnpm lint
|
|
218
|
+
|
|
219
|
+
# Clean build artifacts
|
|
220
|
+
pnpm clean
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Testing
|
|
224
|
+
|
|
225
|
+
The project uses **vitest** with snapshot testing to ensure transformer behavior is consistent and intentional:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Run tests once
|
|
229
|
+
pnpm test
|
|
230
|
+
|
|
231
|
+
# Run in watch mode
|
|
232
|
+
pnpm test:watch
|
|
233
|
+
|
|
234
|
+
# Update snapshots after intentional changes
|
|
235
|
+
pnpm test -- -u
|
|
236
|
+
|
|
237
|
+
# Run specific test file
|
|
238
|
+
pnpm test enforceFormat
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## When to Use ts-morph-react
|
|
242
|
+
|
|
243
|
+
✅ **Good for:**
|
|
244
|
+
- Enforcing code patterns across your codebase
|
|
245
|
+
- Large-scale refactoring of React components
|
|
246
|
+
- Automating style guide compliance
|
|
247
|
+
- One-time migrations (class → function components, etc.)
|
|
248
|
+
- Building custom code generators and linters
|
|
249
|
+
|
|
250
|
+
❌ **Not ideal for:**
|
|
251
|
+
- Real-time code formatting (use Prettier for that)
|
|
252
|
+
- Rename refactoring with complex scope analysis (use your IDE)
|
|
253
|
+
- Performance-critical transformations of very large codebases
|
|
254
|
+
|
|
255
|
+
## Under the Hood
|
|
256
|
+
|
|
257
|
+
ts-morph-react is built on top of [ts-morph](https://ts-morph.com/), a fantastic library that provides a fluent API for manipulating TypeScript ASTs. If you need lower-level control, you can access the ts-morph APIs directly.
|
|
258
|
+
|
|
259
|
+
## Contributing
|
|
260
|
+
|
|
261
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT © [Tobias Strebitzer](mailto:tobias.strebitzer@atomic.bi)
|
|
266
|
+
|
|
267
|
+
## See Also
|
|
268
|
+
|
|
269
|
+
- [ts-morph](https://ts-morph.com/) - The underlying AST manipulation library
|
|
270
|
+
- [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) - For deeper TypeScript understanding
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FormatCodeSettings, SourceFile, VariableDeclaration, FunctionDeclaration, Node, ts } from 'ts-morph';
|
|
2
|
+
import { PartialDeep } from 'type-fest';
|
|
3
|
+
|
|
4
|
+
interface TransformerConfig {
|
|
5
|
+
enforceDirectExports: boolean;
|
|
6
|
+
enforceFunctionComponent: boolean;
|
|
7
|
+
enforceNamedImports: boolean;
|
|
8
|
+
enforceFormat: boolean;
|
|
9
|
+
format: FormatCodeSettings;
|
|
10
|
+
}
|
|
11
|
+
declare function mergeConfig({ format, ...config }?: PartialDeep<TransformerConfig>): TransformerConfig;
|
|
12
|
+
|
|
13
|
+
interface TransformerParams {
|
|
14
|
+
sourceFile: SourceFile;
|
|
15
|
+
config: TransformerConfig;
|
|
16
|
+
}
|
|
17
|
+
type MatchFn = (params: TransformerParams) => boolean;
|
|
18
|
+
type TransformFn = (params: TransformerParams) => Promise<void>;
|
|
19
|
+
interface Transformer {
|
|
20
|
+
match: MatchFn;
|
|
21
|
+
transform: TransformFn;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare const enforceDirectExports: Transformer;
|
|
25
|
+
|
|
26
|
+
declare const enforceFunctionComponent: Transformer;
|
|
27
|
+
|
|
28
|
+
declare const enforceNamedImports: Transformer;
|
|
29
|
+
|
|
30
|
+
declare function createTempSourceFile(filename: string): Promise<string>;
|
|
31
|
+
|
|
32
|
+
declare const REACT_TYPES: string[];
|
|
33
|
+
declare const REACT_HOOKS: string[];
|
|
34
|
+
declare const REACT_APIS: string[];
|
|
35
|
+
declare function isReactComponent(varDecl: VariableDeclaration): boolean;
|
|
36
|
+
declare function isForwardRefComponent(varDecl: VariableDeclaration): boolean;
|
|
37
|
+
declare function isFunctionComponent(func: FunctionDeclaration): boolean;
|
|
38
|
+
|
|
39
|
+
declare const transformers: Transformer[];
|
|
40
|
+
declare function transform(sourceFile: SourceFile, config?: PartialDeep<TransformerConfig>): Promise<void>;
|
|
41
|
+
|
|
42
|
+
declare enum SeparationIntent {
|
|
43
|
+
IGNORE = 0,
|
|
44
|
+
COMBINE = 1,
|
|
45
|
+
SEPARATE = 2
|
|
46
|
+
}
|
|
47
|
+
interface SeparationEntry {
|
|
48
|
+
range: [number, number];
|
|
49
|
+
intent: SeparationIntent;
|
|
50
|
+
}
|
|
51
|
+
declare function enforeLineSeparation(sourceFile: SourceFile, predicate: (cur: Node<ts.Node>, prev: Node<ts.Node>, triviaWidth: number) => SeparationIntent): void;
|
|
52
|
+
|
|
53
|
+
export { type MatchFn, REACT_APIS, REACT_HOOKS, REACT_TYPES, type SeparationEntry, SeparationIntent, type TransformFn, type Transformer, type TransformerConfig, type TransformerParams, createTempSourceFile, enforceDirectExports, enforceFunctionComponent, enforceNamedImports, enforeLineSeparation, isForwardRefComponent, isFunctionComponent, isReactComponent, mergeConfig, transform, transformers };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {SyntaxKind,Node,StructureKind,VariableDeclarationKind,ts,ImportDeclaration}from'ts-morph';import {mkdtemp}from'fs/promises';import {tmpdir}from'os';import {join}from'path';var y=["ComponentProps","ComponentPropsWithRef","ComponentPropsWithoutRef","FC","FunctionComponent","ReactNode","ReactElement","JSX","RefObject","MutableRefObject","CSSProperties","HTMLAttributes","SVGAttributes","DOMAttributes","PropsWithChildren","PropsWithRef"],N=["useState","useEffect","useCallback","useMemo","useRef","useContext","useReducer","useImperativeHandle","useLayoutEffect","useDebugValue","useTransition","useDeferredValue","useId","useSyncExternalStore"],R=["forwardRef","memo","lazy","createContext","createElement","cloneElement","isValidElement","Children","Fragment","StrictMode","Suspense"];function b(e){try{let t=e.getInitializer?.();if(!t)return !1;let r=t.getDescendantsOfKind?.(SyntaxKind.JsxElement)||[],o=t.getDescendantsOfKind?.(SyntaxKind.JsxSelfClosingElement)||[];if(r.length>0||o.length>0)return !0;if(Node.isArrowFunction(t)){let n=t.getBody?.();if(n){let i=n.getDescendantsOfKind?.(SyntaxKind.JsxElement)||[],a=n.getDescendantsOfKind?.(SyntaxKind.JsxSelfClosingElement)||[];return i.length>0||a.length>0}}return !1}catch{return false}}function I(e){try{let t=e.getInitializer?.();if(!t)return !1;let r=t.getText?.();return !!(r?.includes("forwardRef(")||r?.includes("React.forwardRef("))}catch{return false}}function T(e){try{let t=e.getDescendantsOfKind?.(SyntaxKind.JsxElement)||[],r=e.getDescendantsOfKind?.(SyntaxKind.JsxSelfClosingElement)||[];return t.length>0||r.length>0}catch{return false}}var A={match:({config:e})=>e.enforceDirectExports,transform:async({sourceFile:e})=>{e.getFunctions().forEach(t=>{if(!T(t)||t.isExported()&&t.hasExportKeyword())return;let r=t.getName(),o=false;e.getExportDeclarations().forEach(n=>{n.getNamedExports().some(a=>a.getName()===r)&&(o=true);}),o&&(t.setIsExported(true),e.getExportDeclarations().forEach(n=>{let a=n.getNamedExports().filter(s=>s.getName()===r);a.length>0&&(a.forEach(s=>{s.remove();}),n.getNamedExports().length===0&&n.remove());}));}),e.getVariableDeclarations().forEach(t=>{if(!b(t)||I(t))return;let r=t.getInitializer();if(!r||!Node.isArrowFunction(r))return;let o=t.getName(),n=t.getVariableStatement();if(!n)return;let i=n.hasExportKeyword(),a=false;e.getExportDeclarations().forEach(f=>{f.getNamedExports().some(E=>E.getName()===o)&&(a=true);});let s=i||a,p=r.getParameters().map(f=>f.getText()).join(", "),g=r.getBody();if(!g)return;let x=g.getText(),d,c=x.trim();c.startsWith("(")&&c.endsWith(")")?d=`{
|
|
2
|
+
return ${c.slice(1,-1).trim()}
|
|
3
|
+
}`:c.startsWith("{")?d=x:d=`{
|
|
4
|
+
return ${x}
|
|
5
|
+
}`;let u=`${s?"export ":""}function ${o}(${p}) ${d}`;n.replaceWithText(u),a&&e.getExportDeclarations().forEach(f=>{let E=f.getNamedExports().filter(S=>S.getName()===o);E.length>0&&(E.forEach(S=>{S.remove();}),f.getNamedExports().length===0&&f.remove());});});}};var D={match:({config:e})=>e.enforceFunctionComponent,transform:async({sourceFile:e,config:{enforceDirectExports:t}})=>{e.getFunctions().forEach(r=>{if(!T(r))return;let o=r.getName();if(!o)return;let n=r.isExported(),i=false;e.getExportDeclarations().forEach(c=>{c.getNamedExports().some(f=>f.getName()===o)&&(i=true);});let a=t&&(n||i),m=r.getParameters()[0],g=(m.getTypeNode()??m.getType()).getText();m.removeType();let x=g==="any"?"React.FunctionComponent":`React.FunctionComponent<${g}>`,d=r.getChildIndex();e.insertVariableStatement(d,{declarations:[{name:o,type:x,initializer:c=>{c.write("(").write(r.getParameters().map(C=>C.getText()).join(", ")).write(")");let u=r.getReturnTypeNode()?.getText();u&&c.write(`: ${u}`),c.write(" => ");let f=r.getBody()?.getText()??"{}";c.block(()=>{c.write(f.replace(/^{|}$/g,"").trim());});}}],declarationKind:VariableDeclarationKind.Const,kind:StructureKind.VariableStatement,isExported:a}),r.remove();});}};var P={match:({config:e})=>e.enforceNamedImports,transform:async({sourceFile:e})=>{let t=e.getImportDeclarations().find(i=>i.getModuleSpecifier().getLiteralValue()==="react");if(!t||!t.getNamespaceImport())return;let o=new Set,n=new Set;if(e.forEachDescendant(i=>{if(i.getKind()===SyntaxKind.TypeReference){let a=i.asKind(SyntaxKind.TypeReference);if(!a)return;let s=a.getTypeName();if(Node.isQualifiedName(s)){let m=s.getLeft().getText(),p=s.getRight().getText();m==="React"&&y.includes(p)&&(s.replaceWithText(p),o.add(p));}}if(i.getKind()===SyntaxKind.PropertyAccessExpression){let a=i.asKind(SyntaxKind.PropertyAccessExpression);if(!a)return;let s=a.getExpression(),m=a.getName();s.getText()==="React"&&(N.includes(m)||R.includes(m)||y.includes(m))&&(a.replaceWithText(m),y.includes(m)?o.add(m):(N.includes(m)||R.includes(m))&&n.add(m));}}),o.size>0||n.size>0){t.removeNamespaceImport();let i=Array.from(o).sort();for(let s of i)t.addNamedImport({name:s,isTypeOnly:true});let a=Array.from(n).sort();for(let s of a)t.addNamedImport(s);}}};function O({format:e,...t}={}){return {enforceDirectExports:false,enforceFunctionComponent:false,enforceNamedImports:false,enforceFormat:false,format:{indentSize:2,convertTabsToSpaces:true,indentStyle:ts.IndentStyle.Smart,indentMultiLineObjectLiteralBeginningOnBlankLine:true,ensureNewLineAtEndOfFile:true,semicolons:ts.SemicolonPreference.Remove,trimTrailingWhitespace:true,...e},...t}}async function fe(e){let t=await mkdtemp(join(tmpdir(),"ts-morph-react-"));return join(t,e)}var w=(o=>(o[o.IGNORE=0]="IGNORE",o[o.COMBINE=1]="COMBINE",o[o.SEPARATE=2]="SEPARATE",o))(w||{});function V(e,t){let r=[],o=e.getChildSyntaxListOrThrow();for(let n=o.getChildCount()-1;n>0;n-=1){let i=o.getChildAtIndex(n-1),a=o.getChildAtIndex(n),s=t(a,i,a.getLeadingTriviaWidth());r.push({range:[i.getEnd(),a.getStart()],intent:s});}r.forEach(({range:n,intent:i})=>{i===1?e.replaceText(n,`
|
|
6
|
+
`):i===2&&e.replaceText(n,`
|
|
7
|
+
|
|
8
|
+
`);});}var L={match:({config:e})=>e.enforceFormat,transform:async({sourceFile:e,config:{format:t}})=>{e.formatText(t),V(e,(r,o)=>o instanceof ImportDeclaration&&r instanceof ImportDeclaration?1:2),e.organizeImports(t);}};var _=[A,D,P,L];async function Se(e,t={}){let r={config:O(t),sourceFile:e};for(let o of _)o.match(r)&&await o.transform(r);}export{R as REACT_APIS,N as REACT_HOOKS,y as REACT_TYPES,w as SeparationIntent,fe as createTempSourceFile,A as enforceDirectExports,D as enforceFunctionComponent,P as enforceNamedImports,V as enforeLineSeparation,I as isForwardRefComponent,T as isFunctionComponent,b as isReactComponent,O as mergeConfig,Se as transform,_ as transformers};//# sourceMappingURL=index.js.map
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/react.ts","../src/transformers/enforceDirectExports.ts","../src/transformers/enforceFunctionComponent.ts","../src/transformers/enforceNamedImports.ts","../src/utils/config.ts","../src/utils/file.ts","../src/utils/trivia.ts","../src/transformers/enforceFormat.ts","../src/utils/transform.ts"],"names":["REACT_TYPES","REACT_HOOKS","REACT_APIS","isReactComponent","varDecl","initializer","descendants","SyntaxKind","selfClosing","Node","body","bodyDescendants","bodySelfClosing","isForwardRefComponent","text","isFunctionComponent","func","enforceDirectExports","config","sourceFile","funcName","isExportedViaSeparateStatement","exportDecl","exp","matchingExports","namedExport","varName","varStatement","isExportedViaKeyword","isExported","paramsText","p","bodyText","functionBody","trimmedBody","funcText","enforceFunctionComponent","param","typeText","typeAnnotation","index","writer","returnType","VariableDeclarationKind","StructureKind","enforceNamedImports","reactImport","importDeclaration","typeImportsNeeded","valueImportsNeeded","node","typeRef","typeName","left","right","propAccess","expr","name","typeImportsArray","valueImportsArray","valueName","mergeConfig","format","ts","createTempSourceFile","filename","dir","mkdtemp","join","tmpdir","SeparationIntent","enforeLineSeparation","predicate","instructions","syntaxList","prev","cur","intent","range","enforceFormat","ImportDeclaration","transformers","transform","params","transformer"],"mappings":"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","file":"index.js","sourcesContent":["import { FunctionDeclaration, Node, SyntaxKind, VariableDeclaration } from 'ts-morph'\n\n// React types that should be imported as named imports\nexport const REACT_TYPES = [\n 'ComponentProps',\n 'ComponentPropsWithRef',\n 'ComponentPropsWithoutRef',\n 'FC',\n 'FunctionComponent',\n 'ReactNode',\n 'ReactElement',\n 'JSX',\n 'RefObject',\n 'MutableRefObject',\n 'CSSProperties',\n 'HTMLAttributes',\n 'SVGAttributes',\n 'DOMAttributes',\n 'PropsWithChildren',\n 'PropsWithRef'\n]\n\n// React hooks that should be imported as named imports\nexport const REACT_HOOKS = [\n 'useState',\n 'useEffect',\n 'useCallback',\n 'useMemo',\n 'useRef',\n 'useContext',\n 'useReducer',\n 'useImperativeHandle',\n 'useLayoutEffect',\n 'useDebugValue',\n 'useTransition',\n 'useDeferredValue',\n 'useId',\n 'useSyncExternalStore'\n]\n\n// React APIs that should be imported as named imports\nexport const REACT_APIS = [\n 'forwardRef',\n 'memo',\n 'lazy',\n 'createContext',\n 'createElement',\n 'cloneElement',\n 'isValidElement',\n 'Children',\n 'Fragment',\n 'StrictMode',\n 'Suspense'\n]\n\n// Helper to detect if a variable declaration is a React component\nexport function isReactComponent(varDecl: VariableDeclaration): boolean {\n try {\n const initializer = varDecl.getInitializer?.()\n if (!initializer) return false\n\n // Check if the initializer contains JSX\n const descendants = initializer.getDescendantsOfKind?.(SyntaxKind.JsxElement) || []\n const selfClosing = initializer.getDescendantsOfKind?.(SyntaxKind.JsxSelfClosingElement) || []\n\n if (descendants.length > 0 || selfClosing.length > 0) { return true }\n\n // Check if it's assigned to an arrow function that returns JSX\n if (Node.isArrowFunction(initializer)) {\n const body = initializer.getBody?.()\n if (body) {\n const bodyDescendants = body.getDescendantsOfKind?.(SyntaxKind.JsxElement) || []\n const bodySelfClosing = body.getDescendantsOfKind?.(SyntaxKind.JsxSelfClosingElement) || []\n return bodyDescendants.length > 0 || bodySelfClosing.length > 0\n }\n }\n\n return false\n } catch {\n return false\n }\n}\n\n// Helper to check if a variable is a forwardRef component\nexport function isForwardRefComponent(varDecl: VariableDeclaration): boolean {\n try {\n const initializer = varDecl.getInitializer?.()\n if (!initializer) return false\n\n const text = initializer.getText?.()\n return text?.includes('forwardRef(') || text?.includes('React.forwardRef(') ? true : false\n } catch {\n return false\n }\n}\n\n// Helper to check if a function is a React component (returns JSX)\nexport function isFunctionComponent(func: FunctionDeclaration): boolean {\n try {\n const descendants = func.getDescendantsOfKind?.(SyntaxKind.JsxElement) || []\n const selfClosing = func.getDescendantsOfKind?.(SyntaxKind.JsxSelfClosingElement) || []\n if (descendants.length > 0 || selfClosing.length > 0) { return true }\n return false\n } catch {\n return false\n }\n}\n","import { Node } from 'ts-morph'\nimport { isForwardRefComponent, isFunctionComponent, isReactComponent } from '../utils/react'\nimport { Transformer } from '../utils/transformer'\n\nexport const enforceDirectExports: Transformer = {\n match: ({ config }) => config.enforceDirectExports,\n transform: async ({ sourceFile }) => {\n // Transform function declarations: add direct export if it's a component\n sourceFile.getFunctions().forEach((func) => {\n // Skip if not a component\n if (!isFunctionComponent(func)) { return }\n\n // Skip if already exported directly\n if (func.isExported() && func.hasExportKeyword()) { return }\n\n const funcName = func.getName()\n\n // Check if it's exported via separate export statement\n let isExportedViaSeparateStatement = false\n sourceFile.getExportDeclarations().forEach((exportDecl) => {\n const namedExports = exportDecl.getNamedExports()\n if (namedExports.some((exp) => exp.getName() === funcName)) {\n isExportedViaSeparateStatement = true\n }\n })\n\n // Only convert if it's a component that should be exported\n if (isExportedViaSeparateStatement) {\n // Add direct export modifier\n func.setIsExported(true)\n\n // Remove from separate export statements\n sourceFile.getExportDeclarations().forEach((exportDecl) => {\n const namedExports = exportDecl.getNamedExports()\n const matchingExports = namedExports.filter((namedExport) => namedExport.getName() === funcName)\n if (matchingExports.length > 0) {\n matchingExports.forEach((namedExport) => { namedExport.remove() })\n if (exportDecl.getNamedExports().length === 0) { exportDecl.remove() }\n }\n })\n }\n })\n\n // Transform const arrow functions to function declarations (if they're components and not forwardRef)\n sourceFile.getVariableDeclarations().forEach((varDecl) => {\n // Skip if not a component\n if (!isReactComponent(varDecl)) return\n\n // Skip forwardRef and memo patterns\n if (isForwardRefComponent(varDecl)) return\n\n const initializer = varDecl.getInitializer()\n if (!initializer || !Node.isArrowFunction(initializer)) return\n\n const varName = varDecl.getName()\n const varStatement = varDecl.getVariableStatement()\n if (!varStatement) return\n\n const isExportedViaKeyword = varStatement.hasExportKeyword()\n\n // Check if it's exported via separate export statement\n let isExportedViaSeparateStatement = false\n sourceFile.getExportDeclarations().forEach((exportDecl) => {\n const namedExports = exportDecl.getNamedExports()\n if (namedExports.some((exp) => exp.getName() === varName)) {\n isExportedViaSeparateStatement = true\n }\n })\n\n const isExported = isExportedViaKeyword || isExportedViaSeparateStatement\n\n // Get the parameters\n const params = initializer.getParameters()\n const paramsText = params.map((p) => p.getText()).join(', ')\n\n // Get the body\n const body = initializer.getBody()\n if (!body) return\n\n const bodyText = body.getText()\n\n // Build the function body\n let functionBody: string\n const trimmedBody = bodyText.trim()\n\n if (trimmedBody.startsWith('(') && trimmedBody.endsWith(')')) {\n // Parenthesized expression like: (\n // <button />\n // )\n const innerBody = trimmedBody.slice(1, -1).trim()\n functionBody = `{\\n return ${innerBody}\\n }`\n } else if (trimmedBody.startsWith('{')) {\n // Already a block statement\n functionBody = bodyText\n } else {\n // Simple JSX expression like: <button />\n functionBody = `{\\n return ${bodyText}\\n }`\n }\n\n const funcText = `${isExported ? 'export ' : ''}function ${varName}(${paramsText}) ${functionBody}`\n\n // Replace the variable statement with function declaration\n varStatement.replaceWithText(funcText)\n\n // Remove from separate export statements if it was exported that way\n if (isExportedViaSeparateStatement) {\n sourceFile.getExportDeclarations().forEach((exportDecl) => {\n const namedExports = exportDecl.getNamedExports()\n const matchingExports = namedExports.filter((namedExport) => namedExport.getName() === varName)\n\n if (matchingExports.length > 0) {\n matchingExports.forEach((namedExport) => {\n namedExport.remove()\n })\n\n // Remove the entire export declaration if no exports remain\n if (exportDecl.getNamedExports().length === 0) {\n exportDecl.remove()\n }\n }\n })\n }\n })\n }\n}\n","import { StructureKind, VariableDeclarationKind } from 'ts-morph'\nimport { isFunctionComponent } from '../utils/react'\nimport { Transformer } from '../utils/transformer'\n\nexport const enforceFunctionComponent: Transformer = {\n match: ({ config }) => config.enforceFunctionComponent,\n transform: async ({ sourceFile, config: { enforceDirectExports } }) => {\n sourceFile.getFunctions().forEach((func) => {\n if (!isFunctionComponent(func)) { return }\n\n const funcName = func.getName()\n if (!funcName) { return }\n\n const isExportedViaKeyword = func.isExported()\n let isExportedViaSeparateStatement = false\n\n sourceFile.getExportDeclarations().forEach((exportDecl) => {\n const namedExports = exportDecl.getNamedExports()\n if (namedExports.some((exp) => exp.getName() === funcName)) {\n isExportedViaSeparateStatement = true\n }\n })\n\n const isExported = enforceDirectExports && (isExportedViaKeyword || isExportedViaSeparateStatement)\n\n // Get the parameters with their type annotations\n const params = func.getParameters()\n const param = params[0]\n const type = param.getTypeNode() ?? param.getType()\n const typeText = type.getText()\n param.removeType()\n const typeAnnotation = typeText === 'any' ? 'React.FunctionComponent' : `React.FunctionComponent<${typeText}>`\n\n const index = func.getChildIndex()\n sourceFile.insertVariableStatement(index, {\n declarations: [{\n name: funcName,\n type: typeAnnotation,\n initializer: (writer) => {\n writer.write('(').write(func.getParameters().map((p) => p.getText()).join(', ')).write(')')\n const returnType = func.getReturnTypeNode()?.getText()\n if (returnType) { writer.write(`: ${returnType}`) }\n writer.write(' => ')\n const bodyText = func.getBody()?.getText() ?? '{}'\n writer.block(() => { writer.write(bodyText.replace(/^{|}$/g, '').trim()) })\n }\n }],\n declarationKind: VariableDeclarationKind.Const,\n kind: StructureKind.VariableStatement,\n isExported\n })\n func.remove()\n })\n }\n}\n","import { Node, SyntaxKind } from 'ts-morph'\nimport { REACT_APIS, REACT_HOOKS, REACT_TYPES } from '../utils/react'\nimport { Transformer } from '../utils/transformer'\n\nexport const enforceNamedImports: Transformer = {\n match: ({ config }) => config.enforceNamedImports,\n transform: async ({ sourceFile }) => {\n // Find React import declaration\n const reactImport = sourceFile.getImportDeclarations().find((importDeclaration) => {\n return importDeclaration.getModuleSpecifier().getLiteralValue() === 'react'\n })\n\n // If no React import exists, no transformation needed\n if (!reactImport) { return }\n\n // Check if it's already using named imports only (early exit if enforceNamedImports is disabled)\n const namespaceImport = reactImport.getNamespaceImport()\n if (!namespaceImport) { return }\n\n // Initialize tracking sets\n const typeImportsNeeded = new Set<string>()\n const valueImportsNeeded = new Set<string>()\n\n // ============================================================\n // Phase 2 & 3: Transform Type References and Hook Calls\n // (Single-pass traversal for efficiency)\n // ============================================================\n\n sourceFile.forEachDescendant((node) => {\n // Handle TypeReference nodes (Phase 2)\n if (node.getKind() === SyntaxKind.TypeReference) {\n const typeRef = node.asKind(SyntaxKind.TypeReference)\n if (!typeRef) return\n\n const typeName = typeRef.getTypeName()\n\n // Check if it's a qualified name like React.ComponentProps or React.HTMLAttributes\n if (Node.isQualifiedName(typeName)) {\n const left = typeName.getLeft().getText()\n const right = typeName.getRight().getText()\n\n if (left === 'React') {\n // Check if it's a known React type\n if (REACT_TYPES.includes(right)) {\n typeName.replaceWithText(right)\n typeImportsNeeded.add(right)\n }\n }\n }\n }\n\n // Handle PropertyAccessExpression nodes (Phase 3)\n // This handles both React.Component and React.SomethingRef patterns\n if (node.getKind() === SyntaxKind.PropertyAccessExpression) {\n const propAccess = node.asKind(SyntaxKind.PropertyAccessExpression)\n if (!propAccess) return\n\n const expr = propAccess.getExpression()\n const name = propAccess.getName()\n\n if (expr.getText() === 'React') {\n // Check if it's a known hook or API\n if (REACT_HOOKS.includes(name) || REACT_APIS.includes(name) || REACT_TYPES.includes(name)) {\n propAccess.replaceWithText(name)\n\n // Determine if it's a type or value import\n if (REACT_TYPES.includes(name)) {\n typeImportsNeeded.add(name)\n } else if (REACT_HOOKS.includes(name) || REACT_APIS.includes(name)) {\n valueImportsNeeded.add(name)\n }\n }\n }\n }\n })\n\n // ============================================================\n // Phase 4: Import Management\n // ============================================================\n\n // Only add named imports and remove namespace if we have imports to add\n if (typeImportsNeeded.size > 0 || valueImportsNeeded.size > 0) {\n // Remove namespace import FIRST (before adding named imports)\n reactImport.removeNamespaceImport()\n\n // Add type imports (with 'type' keyword for type-only imports)\n const typeImportsArray = Array.from(typeImportsNeeded).sort()\n for (const typeName of typeImportsArray) {\n reactImport.addNamedImport({\n name: typeName,\n isTypeOnly: true\n })\n }\n\n // Add value imports\n const valueImportsArray = Array.from(valueImportsNeeded).sort()\n for (const valueName of valueImportsArray) {\n reactImport.addNamedImport(valueName)\n }\n }\n }\n}\n","import { FormatCodeSettings, ts } from 'ts-morph'\nimport { PartialDeep } from 'type-fest'\n\nexport interface TransformerConfig {\n enforceDirectExports: boolean\n enforceFunctionComponent: boolean\n enforceNamedImports: boolean\n enforceFormat: boolean\n format: FormatCodeSettings\n\n}\n\nexport function mergeConfig({ format, ...config }: PartialDeep<TransformerConfig> = {}): TransformerConfig {\n return {\n enforceDirectExports: false,\n enforceFunctionComponent: false,\n enforceNamedImports: false,\n enforceFormat: false,\n format: {\n indentSize: 2,\n convertTabsToSpaces: true,\n indentStyle: ts.IndentStyle.Smart,\n indentMultiLineObjectLiteralBeginningOnBlankLine: true,\n ensureNewLineAtEndOfFile: true,\n semicolons: ts.SemicolonPreference.Remove,\n trimTrailingWhitespace: true,\n ...format\n },\n ...config\n }\n}\n","import { mkdtemp } from 'fs/promises'\nimport { tmpdir } from 'os'\nimport { join } from 'path'\n\nexport async function createTempSourceFile(filename: string) {\n const dir = await mkdtemp(join(tmpdir(), 'ts-morph-react-'))\n return join(dir, filename)\n}\n","import { Node, SourceFile, ts } from 'ts-morph'\n\nexport enum SeparationIntent { IGNORE, COMBINE, SEPARATE }\n\nexport interface SeparationEntry {\n range: [number, number]\n intent: SeparationIntent\n}\n\nexport function enforeLineSeparation(sourceFile: SourceFile, predicate: (cur: Node<ts.Node>, prev: Node<ts.Node>, triviaWidth: number) => SeparationIntent) {\n const instructions: SeparationEntry[] = []\n const syntaxList = sourceFile.getChildSyntaxListOrThrow()\n for (let index = syntaxList.getChildCount() - 1; index > 0; index -= 1) {\n const prev = syntaxList.getChildAtIndex(index - 1)\n const cur = syntaxList.getChildAtIndex(index)\n const intent = predicate(cur, prev, cur.getLeadingTriviaWidth())\n instructions.push({ range: [prev.getEnd(), cur.getStart()], intent })\n }\n instructions.forEach(({ range, intent }) => {\n if (intent === SeparationIntent.COMBINE) {\n sourceFile.replaceText(range, '\\n')\n } else if (intent === SeparationIntent.SEPARATE) {\n sourceFile.replaceText(range, '\\n\\n')\n }\n })\n}\n","import { ImportDeclaration } from 'ts-morph'\nimport { Transformer } from '../utils/transformer'\nimport { enforeLineSeparation, SeparationIntent } from '../utils/trivia'\n\nexport const enforceFormat: Transformer = {\n match: ({ config }) => config.enforceFormat,\n transform: async ({ sourceFile, config: { format } }) => {\n sourceFile.formatText(format)\n enforeLineSeparation(sourceFile, (cur, prev) => (prev instanceof ImportDeclaration && cur instanceof ImportDeclaration)\n ? SeparationIntent.COMBINE\n : SeparationIntent.SEPARATE\n )\n sourceFile.organizeImports(format)\n }\n}\n","import { SourceFile } from 'ts-morph'\nimport { PartialDeep } from 'type-fest'\nimport { enforceDirectExports } from '../transformers/enforceDirectExports'\nimport { enforceFormat } from '../transformers/enforceFormat'\nimport { enforceFunctionComponent } from '../transformers/enforceFunctionComponent'\nimport { enforceNamedImports } from '../transformers/enforceNamedImports'\nimport { mergeConfig, TransformerConfig } from './config'\nimport { TransformerParams } from './transformer'\n\nexport const transformers = [enforceDirectExports, enforceFunctionComponent, enforceNamedImports, enforceFormat]\n\nexport async function transform(\n sourceFile: SourceFile,\n config: PartialDeep<TransformerConfig> = {}\n): Promise<void> {\n const params: TransformerParams = { config: mergeConfig(config), sourceFile }\n for (const transformer of transformers) {\n if (transformer.match(params)) {\n await transformer.transform(params)\n }\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ts-morph-react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A collection of transformers for ts-morph to refactor react code",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Tobias Strebitzer",
|
|
8
|
+
"email": "tobias.strebitzer@atomic.bi"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+ssh://git@github.com/atomicbi/ts-morph-react.git"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"bin": "./dist/cli.js",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"ts-morph": "^27.0.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@base-ui/react": "^1.0.0",
|
|
30
|
+
"@eslint/js": "^9.39.2",
|
|
31
|
+
"@stylistic/eslint-plugin": "^5.6.1",
|
|
32
|
+
"@types/node": "^25.0.3",
|
|
33
|
+
"cli-highlight": "^2.1.11",
|
|
34
|
+
"eslint": "^9.39.2",
|
|
35
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
36
|
+
"eslint-plugin-react": "^7.37.5",
|
|
37
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
38
|
+
"react": "^19.2.3",
|
|
39
|
+
"rimraf": "^6.0.1",
|
|
40
|
+
"tsup": "^8.5.0",
|
|
41
|
+
"tsx": "^4.21.0",
|
|
42
|
+
"type-fest": "^4.41.0",
|
|
43
|
+
"typescript": "^5.9.2",
|
|
44
|
+
"typescript-eslint": "^8.50.1",
|
|
45
|
+
"vite-tsconfig-paths": "^6.0.3",
|
|
46
|
+
"vitest": "^4.0.16"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"clean": "rimraf dist",
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"watch": "tsup --watch",
|
|
52
|
+
"lint": "eslint 'src/**/*.ts' && tsc --noEmit",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest --watch",
|
|
55
|
+
"test:ui": "vitest --ui"
|
|
56
|
+
}
|
|
57
|
+
}
|