Skip to content

Commit 4bfc03d

Browse files
committed
feat: add Astx.findImports, Astx.addImports, Astx.removeImports, Astx.replaceImport
1 parent ecb6608 commit 4bfc03d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1457
-16
lines changed

src/Astx.ts

Lines changed: 241 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { Expression, Statement, Node, NodePath } from './types'
1+
import {
2+
Expression,
3+
Statement,
4+
Node,
5+
NodePath,
6+
ImportDeclaration,
7+
} from './types'
28
import { Backend } from './backend/Backend'
39
import find, { Match, convertWithCaptures, createMatch } from './find'
410
import replace from './replace'
@@ -14,6 +20,11 @@ import {
1420
} from './compileMatcher/Placeholder'
1521
import { SimpleReplacementInterface } from './util/SimpleReplacementCollector'
1622
import forEachNode from './util/forEachNode'
23+
import addImports from './util/addImports'
24+
import findImports from './util/findImports'
25+
import removeImports from './util/removeImports'
26+
import createReplacementConverter from './convertReplacement'
27+
import compileReplacement, { CompiledReplacement } from './compileReplacement'
1728

1829
export type TransformOptions = {
1930
/** The absolute path to the current file. */
@@ -254,11 +265,12 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
254265
}
255266

256267
withCaptures(
257-
...captures: (
258-
| Match
259-
| Astx
260-
| { [name: `$${string}` | `$$${string}` | `$$$${string}`]: Astx }
261-
)[]
268+
...captures:
269+
| (
270+
| Match
271+
| Astx
272+
| { [name: `$${string}` | `$$${string}` | `$$$${string}`]: Astx }
273+
)[]
262274
): Astx {
263275
const withCaptures: Match[] = [...this._withCaptures]
264276
for (const item of captures) {
@@ -331,12 +343,12 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
331343
)
332344
}
333345

334-
private _execPattern<Options>(
346+
private _execPattern<Options, Return>(
335347
name: string,
336348
exec: (
337349
pattern: NodePath<Node, any> | readonly NodePath<Node, any>[],
338350
options?: Options
339-
) => Astx,
351+
) => Return,
340352
arg0:
341353
| string
342354
| Node
@@ -346,7 +358,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
346358
| string[]
347359
| TemplateStringsArray,
348360
...rest: any[]
349-
): Astx | ((options?: Options) => Astx) {
361+
): Return | ((options?: Options) => Return) {
350362
const { backend } = this
351363
const { parsePattern } = backend
352364
const { NodePath } = backend.t
@@ -382,9 +394,9 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
382394
}
383395
}
384396

385-
private _execPatternOrPredicate<Options>(
397+
private _execPatternOrPredicate<Options, Return>(
386398
name: string,
387-
exec: (match: CompiledMatcher['match'], options?: Options) => Astx,
399+
exec: (match: CompiledMatcher['match'], options?: Options) => Return,
388400
arg0:
389401
| string
390402
| Node
@@ -395,7 +407,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
395407
| TemplateStringsArray
396408
| FindPredicate,
397409
...rest: any[]
398-
): Astx | ((options?: Options) => Astx) {
410+
): Return | ((options?: Options) => Return) {
399411
const { backend } = this
400412
if (arg0 instanceof Function) {
401413
const predicate = arg0
@@ -647,4 +659,221 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
647659
remove(): void {
648660
this.replace([])
649661
}
662+
663+
addImports(
664+
strings: string[] | TemplateStringsArray,
665+
...quasis: any[]
666+
): () => Astx
667+
addImports(
668+
pattern: string | Node | Node[] | NodePath<any> | NodePath<any>[]
669+
): Astx
670+
addImports(
671+
arg0:
672+
| string
673+
| Node
674+
| Node[]
675+
| NodePath<any>
676+
| NodePath<any>[]
677+
| string[]
678+
| TemplateStringsArray,
679+
...rest: any[]
680+
): Astx | (() => Astx) {
681+
return this._execPattern(
682+
'addImports',
683+
(pattern: NodePath<Node, any> | readonly NodePath<Node, any>[]): Astx =>
684+
addImports(this, Array.isArray(pattern) ? pattern : [pattern]),
685+
arg0,
686+
...rest
687+
)
688+
}
689+
690+
findImports(
691+
strings: string[] | TemplateStringsArray,
692+
...quasis: any[]
693+
): () => Astx
694+
findImports(
695+
pattern: string | Node | Node[] | NodePath<any> | NodePath<any>[]
696+
): Astx
697+
findImports(
698+
arg0:
699+
| string
700+
| Node
701+
| Node[]
702+
| NodePath<any>
703+
| NodePath<any>[]
704+
| string[]
705+
| TemplateStringsArray,
706+
...rest: any[]
707+
): Astx | (() => Astx) {
708+
return this._execPattern(
709+
'findImports',
710+
(pattern: NodePath<Node, any> | readonly NodePath<Node, any>[]): Astx =>
711+
findImports(this, Array.isArray(pattern) ? pattern : [pattern]),
712+
arg0,
713+
...rest
714+
)
715+
}
716+
717+
removeImports(
718+
strings: string[] | TemplateStringsArray,
719+
...quasis: any[]
720+
): () => boolean
721+
removeImports(
722+
pattern: string | Node | Node[] | NodePath<any> | NodePath<any>[]
723+
): boolean
724+
removeImports(
725+
arg0:
726+
| string
727+
| Node
728+
| Node[]
729+
| NodePath<any>
730+
| NodePath<any>[]
731+
| string[]
732+
| TemplateStringsArray,
733+
...rest: any[]
734+
): boolean | (() => boolean) {
735+
return this._execPattern(
736+
'removeImports',
737+
(
738+
pattern: NodePath<Node, any> | readonly NodePath<Node, any>[]
739+
): boolean =>
740+
removeImports(this, Array.isArray(pattern) ? pattern : [pattern]),
741+
arg0,
742+
...rest
743+
)
744+
}
745+
746+
replaceImport(
747+
strings: string[] | TemplateStringsArray,
748+
...quasis: any[]
749+
): () => ImportReplacer
750+
replaceImport(
751+
pattern: string | Node | Node[] | NodePath<any> | NodePath<any>[]
752+
): ImportReplacer
753+
replaceImport(
754+
arg0:
755+
| string
756+
| Node
757+
| Node[]
758+
| NodePath<any>
759+
| NodePath<any>[]
760+
| string[]
761+
| TemplateStringsArray,
762+
...rest: any[]
763+
): ImportReplacer | (() => ImportReplacer) {
764+
return this._execPattern(
765+
'replaceImport',
766+
(
767+
_pattern: NodePath<Node, any> | readonly NodePath<Node, any>[]
768+
): ImportReplacer => {
769+
const pattern = Array.isArray(_pattern) ? _pattern : [_pattern]
770+
if (
771+
pattern.length !== 1 ||
772+
pattern[0].node.type !== 'ImportDeclaration'
773+
) {
774+
throw new Error(`pattern must contain exactly one import declaration`)
775+
}
776+
const decl: ImportDeclaration = pattern[0]?.node as any
777+
if (decl.specifiers && decl.specifiers.length > 1) {
778+
throw new Error(
779+
`pattern may not contain more than one import specifier`
780+
)
781+
}
782+
const found = findImports(this, pattern)
783+
return new ImportReplacer(this, found, pattern)
784+
},
785+
arg0,
786+
...rest
787+
)
788+
}
789+
}
790+
791+
class ImportReplacer {
792+
constructor(
793+
public astx: Astx,
794+
public match: Astx,
795+
public findPattern: readonly NodePath<Node, any>[]
796+
) {}
797+
798+
with(
799+
strings: string[] | TemplateStringsArray,
800+
...quasis: any[]
801+
): () => boolean
802+
with(
803+
pattern:
804+
| string
805+
| Node
806+
| Node[]
807+
| NodePath<any>
808+
| NodePath<any>[]
809+
| GetReplacement
810+
): boolean
811+
with(
812+
arg0:
813+
| string
814+
| Node
815+
| Node[]
816+
| NodePath<any>
817+
| NodePath<any>[]
818+
| GetReplacement
819+
| string[]
820+
| TemplateStringsArray,
821+
...rest: any[]
822+
): boolean | (() => boolean) {
823+
const { backend } = this.astx
824+
const { parsePatternToNodes } = backend
825+
826+
const doReplace = (rawReplacement: any): boolean => {
827+
if (!this.match.matched) return false
828+
const match = this.match.match
829+
const path =
830+
match.path.parentPath?.node?.type === 'ExpressionStatement'
831+
? match.path.parentPath
832+
: match.path
833+
const converter = createReplacementConverter(path)
834+
const generated = (
835+
rawReplacement instanceof Object &&
836+
typeof (rawReplacement as any).generate === 'function'
837+
? (rawReplacement as CompiledReplacement)
838+
: compileReplacement(
839+
Array.isArray(rawReplacement)
840+
? rawReplacement.map((n) => new backend.t.NodePath(n))
841+
: new backend.t.NodePath(rawReplacement),
842+
{ backend }
843+
)
844+
).generate(match)
845+
846+
const converted = converter(
847+
Array.isArray(generated) ? generated[0] : generated
848+
)
849+
850+
removeImports(this.astx, this.findPattern)
851+
this.astx.addImports(converted)
852+
return true
853+
}
854+
855+
try {
856+
if (typeof arg0 === 'function') {
857+
// Always replace in reverse so that if there are matches inside of
858+
// matches, the inner matches get replaced first (since they come
859+
// later in the code)
860+
return doReplace((arg0 as any)(this.match, parsePatternToNodes))
861+
} else if (typeof arg0 === 'string') {
862+
return doReplace(parsePatternToNodes(arg0))
863+
} else if (isNode(arg0) || isNodeArray(arg0)) {
864+
return doReplace(arg0)
865+
} else {
866+
const rawReplacement = parsePatternToNodes(arg0 as any, ...rest)
867+
return () => doReplace(rawReplacement)
868+
}
869+
} catch (error) {
870+
if (error instanceof Error) {
871+
CodeFrameError.rethrow(error, {
872+
filename: 'replace pattern',
873+
source: typeof arg0 === 'string' ? arg0 : undefined,
874+
})
875+
}
876+
throw error
877+
}
878+
}
650879
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ImportDeclaration, NodePath } from '../types'
2+
import { CompiledMatcher, CompileOptions, MatchResult } from '.'
3+
import compileGenericNodeMatcher from './GenericNodeMatcher'
4+
5+
export default function compileImportSpecifierMatcher(
6+
path: NodePath<ImportDeclaration, ImportDeclaration>,
7+
compileOptions: CompileOptions
8+
): CompiledMatcher | void {
9+
const pattern: ImportDeclaration = path.value
10+
11+
const importKind = (pattern as any).importKind || 'value'
12+
13+
return compileGenericNodeMatcher(path, compileOptions, {
14+
keyMatchers: {
15+
importKind: {
16+
pattern: path.get('importKind') as any,
17+
match: (
18+
path: NodePath<any, any>,
19+
matchSoFar: MatchResult
20+
): MatchResult => {
21+
return (path.value || 'value') === importKind
22+
? matchSoFar || {}
23+
: null
24+
},
25+
},
26+
},
27+
})
28+
}

src/compileMatcher/ImportSpecifier.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ImportSpecifier, NodePath } from '../types'
2-
import { CompiledMatcher, CompileOptions } from '.'
2+
import { CompiledMatcher, CompileOptions, MatchResult } from '.'
33
import compilePlaceholderMatcher from './Placeholder'
4+
import compileGenericNodeMatcher from './GenericNodeMatcher'
45

56
export default function compileImportSpecifierMatcher(
67
path: NodePath<ImportSpecifier, ImportSpecifier>,
@@ -9,13 +10,13 @@ export default function compileImportSpecifierMatcher(
910
const n = compileOptions.backend.t.namedTypes
1011
const pattern: ImportSpecifier = path.value
1112

12-
const { importKind } = pattern as any
13+
const importKind = (pattern as any).importKind || 'value'
1314
const { imported, local } = pattern
1415
if (
1516
n.Identifier.check(imported) &&
1617
(!local || local.name === imported.name)
1718
) {
18-
if (importKind == null || importKind === 'value') {
19+
if (importKind === 'value') {
1920
const placeholderMatcher = compilePlaceholderMatcher(
2021
path,
2122
imported.name,
@@ -32,4 +33,20 @@ export default function compileImportSpecifierMatcher(
3233
if (placeholderMatcher) return placeholderMatcher
3334
}
3435
}
36+
37+
return compileGenericNodeMatcher(path, compileOptions, {
38+
keyMatchers: {
39+
importKind: {
40+
pattern: path.get('importKind'),
41+
match: (
42+
path: NodePath<any, any>,
43+
matchSoFar: MatchResult
44+
): MatchResult => {
45+
return (path.value || 'value') === importKind
46+
? matchSoFar || {}
47+
: null
48+
},
49+
},
50+
},
51+
})
3552
}

src/compileMatcher/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import ExpressionStatement from './ExpressionStatement'
1414
import FunctionTypeParam from './FunctionTypeParam'
1515
import GenericTypeAnnotation from './GenericTypeAnnotation'
1616
import Identifier from './Identifier'
17+
import ImportDeclaration from './ImportDeclaration'
1718
import ImportSpecifier from './ImportSpecifier'
1819
import JSXAttribute from './JSXAttribute'
1920
import JSXElement from './JSXElement'
@@ -129,6 +130,7 @@ const nodeMatchers: Record<
129130
FunctionTypeParam,
130131
GenericTypeAnnotation,
131132
Identifier,
133+
ImportDeclaration,
132134
ImportSpecifier,
133135
JSXAttribute,
134136
JSXElement,

0 commit comments

Comments
 (0)