Skip to content

Commit 6bf74b4

Browse files
committed
feat: resolve relative paths when finding/replacing imports/exports
1 parent 5c26b7a commit 6bf74b4

File tree

10 files changed

+143
-49
lines changed

10 files changed

+143
-49
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@
106106
"ansi-escapes": "^4.3.2",
107107
"ast-types": "^0.14.2",
108108
"babel-parse-wild-code": "^2.1.5",
109-
"callsites": "^4.2.0",
110109
"chalk": "^4.1.0",
111110
"cosmiconfig": "^7.0.1",
112111
"debug": "^4.3.1",

pnpm-lock.yaml

Lines changed: 5 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Astx.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export type FindOptions = {
107107
export type AstxContext = {
108108
backend: Backend
109109
simpleReplacements?: SimpleReplacementInterface
110+
filename?: string
111+
getResolveAgainstDir?: () => string
110112
}
111113

112114
class ExtendableProxy {
@@ -437,7 +439,10 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
437439
| FindPredicate,
438440
...rest: any[]
439441
): Return {
440-
const { backend } = this
442+
const {
443+
backend,
444+
context: { getResolveAgainstDir },
445+
} = this
441446
if (arg0 instanceof Function) {
442447
const predicate = arg0
443448
const match = (path: NodePath): MatchResult => {
@@ -466,6 +471,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
466471
}
467472
const matcher = compileMatcher(pattern[0], {
468473
backend,
474+
getResolveAgainstDir,
469475
})
470476
return exec(matcher.match)
471477
},
@@ -498,6 +504,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
498504
...rest: any[]
499505
): Astx {
500506
const { context } = this
507+
const { filename } = context
501508
return this._execPatternOrPredicate(
502509
'closest',
503510
(matcher: CompiledMatcher['match']): Astx => {
@@ -506,7 +513,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
506513
this.paths.forEach((path) => {
507514
for (let p = path.parentPath; p; p = p.parentPath) {
508515
if (matchedParents.has(p)) return
509-
const match = matcher(p, this.initialMatch, {})
516+
const match = matcher(p, this.initialMatch, { filename })
510517
if (match) {
511518
matchedParents.add(p)
512519
matches.push(createMatch(p, match))
@@ -544,12 +551,13 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
544551
...rest: any[]
545552
): Astx {
546553
const { context } = this
554+
const { filename } = context
547555
return this._execPatternOrPredicate(
548556
'destruct',
549557
(matcher: CompiledMatcher['match']): Astx => {
550558
const matches: Match[] = []
551559
this.paths.forEach((path) => {
552-
const match = matcher(path, this.initialMatch, {})
560+
const match = matcher(path, this.initialMatch, { filename })
553561
if (match) matches.push(createMatch(path, match))
554562
})
555563
return new Astx(context, matches)
@@ -583,6 +591,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
583591
...rest: any[]
584592
): Astx {
585593
const { context, backend } = this
594+
const { filename, getResolveAgainstDir } = context
586595
if (arg0 instanceof Function) {
587596
const predicate = arg0
588597
const matches: Match[] = []
@@ -608,6 +617,8 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
608617
...options,
609618
backend,
610619
matchSoFar: this.initialMatch,
620+
getResolveAgainstDir,
621+
filename,
611622
})
612623
),
613624
arg0,
@@ -690,7 +701,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
690701
return this._execPattern(
691702
'addImports',
692703
(pattern: NodePath<Node, any> | readonly NodePath<Node, any>[]): Astx =>
693-
addImports(this, Array.isArray(pattern) ? pattern : [pattern], {}),
704+
addImports(this, Array.isArray(pattern) ? pattern : [pattern]),
694705
arg0,
695706
...rest
696707
)
@@ -714,7 +725,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
714725
return this._execPattern(
715726
'findImports',
716727
(pattern: NodePath<Node, any> | readonly NodePath<Node, any>[]): Astx =>
717-
findImports(this, Array.isArray(pattern) ? pattern : [pattern], {}),
728+
findImports(this, Array.isArray(pattern) ? pattern : [pattern]),
718729
arg0,
719730
...rest
720731
)
@@ -785,7 +796,7 @@ export default class Astx extends ExtendableProxy implements Iterable<Astx> {
785796
`pattern may not contain more than one import specifier`
786797
)
787798
}
788-
const found = findImports(this, pattern, {})
799+
const found = findImports(this, pattern)
789800
return new ImportReplacer(this, found, decl)
790801
},
791802
arg0,
@@ -823,7 +834,7 @@ class ImportReplacer {
823834
| TemplateStringsArray,
824835
...rest: any[]
825836
): boolean {
826-
const { backend } = this.astx
837+
const { backend, context } = this.astx
827838
const { parsePatternToNodes } = backend
828839

829840
const doReplace = (rawReplacement: any): boolean => {
@@ -842,9 +853,14 @@ class ImportReplacer {
842853
Array.isArray(rawReplacement)
843854
? rawReplacement.map((n) => new backend.t.NodePath(n))
844855
: new backend.t.NodePath(rawReplacement),
845-
{ backend }
856+
context
846857
)
847-
).generate(match)
858+
).generate(
859+
match,
860+
// omit filename for resolving relative imports since
861+
// addImports will handle the relative paths
862+
{}
863+
)
848864

849865
const converted = converter(
850866
Array.isArray(generated) ? generated[0] : generated

src/backend/template.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function statements(
7878
{
7979
backend: this,
8080
}
81-
).generate({ captures, arrayCaptures })
81+
).generate({ captures, arrayCaptures }, {})
8282
return ensureArray(result).map(convertStatementReplacement) as Statement[]
8383
} catch (error) {
8484
if (error instanceof Error) {
@@ -118,7 +118,7 @@ export function expression(
118118
{
119119
backend: this,
120120
}
121-
).generate({ captures, arrayCaptures })
121+
).generate({ captures, arrayCaptures }, {})
122122
let expression
123123
if (Array.isArray(result)) {
124124
if (result.length !== 1) {

src/node/runTransformOnFile.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ export default async function runTransformOnFile({
6565
forWorker,
6666
fs = defaultFs,
6767
}: RunTransformOnFileOptions): Promise<TransformResult> {
68+
// might try using callsites for this in the future in case a transform
69+
// script is broken up into multiple files
70+
const getResolveAgainstDir = () =>
71+
transformFile ? Path.dirname(transformFile) : process.cwd()
72+
6873
const transform: Transform = transformFile
6974
? await importTransformFile(transformFile)
7075
: _transform ??
@@ -170,6 +175,8 @@ export default async function runTransformOnFile({
170175
{
171176
backend,
172177
simpleReplacements,
178+
filename: file,
179+
getResolveAgainstDir,
173180
},
174181
[root]
175182
),

src/util/addImports.ts

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Astx from '../Astx'
22
import { Node, NodePath } from '../types'
3+
import Path from 'path'
34
import * as t from '@babel/types'
45
import findImports from './findImports'
56
import { stripImportKind } from './imports'
@@ -10,9 +11,10 @@ import {
1011

1112
export default function addImports(
1213
astx: Astx,
13-
pattern: readonly NodePath<Node, any>[],
14-
options: { filename?: string }
14+
pattern: readonly NodePath<Node, any>[]
1515
): Astx {
16+
const { getResolveAgainstDir, filename } = astx.context
17+
1618
for (const { node } of pattern) {
1719
if (node.type !== 'ImportDeclaration') {
1820
throw new Error(
@@ -24,6 +26,14 @@ export default function addImports(
2426
}
2527

2628
function addDeclaration(decl: t.ImportDeclaration) {
29+
if (decl.source.value.startsWith('.') && getResolveAgainstDir && filename) {
30+
const absolute = Path.resolve(getResolveAgainstDir(), decl.source.value)
31+
const relative = Path.relative(Path.dirname(filename), absolute)
32+
decl.source = t.stringLiteral(
33+
relative.startsWith('.') ? relative : `./${relative}`
34+
)
35+
}
36+
2737
astx.context.simpleReplacements?.bail()
2838
const before =
2939
astx.find(
@@ -53,11 +63,19 @@ export default function addImports(
5363

5464
if (!decl.specifiers?.length) {
5565
if (
56-
astx.find(
57-
(a) =>
58-
a.node.type === 'ImportDeclaration' &&
59-
a.node.source.value === decl.source.value
60-
).matched
66+
astx.find({
67+
...decl,
68+
specifiers: [
69+
t.importSpecifier(t.identifier('$$'), t.identifier('$$')),
70+
],
71+
}).matched ||
72+
astx.find({
73+
...decl,
74+
importKind: 'value',
75+
specifiers: [
76+
t.importSpecifier(t.identifier('$$'), t.identifier('$$')),
77+
],
78+
}).matched
6179
) {
6280
continue
6381
}
@@ -88,17 +106,23 @@ export default function addImports(
88106
local: t.identifier(unescapeIdentifier(specifier.local.name)),
89107
}
90108
}
91-
const existingImportKind = astx.find(
92-
(a) =>
93-
a.node.type === 'ImportDeclaration' &&
94-
a.node.source.value === decl.source.value &&
95-
(a.node.importKind || 'value') === (decl.importKind || 'value') &&
96-
(specifier.type !== 'ImportNamespaceSpecifier' ||
97-
!a.node.specifiers?.length) &&
98-
!a.node.specifiers?.some(
99-
(s) => s.type === 'ImportNamespaceSpecifier'
100-
)
101-
)
109+
let existingImportKind = astx.find({
110+
...decl,
111+
specifiers:
112+
specifier.type === 'ImportNamespaceSpecifier'
113+
? []
114+
: [t.importSpecifier(t.identifier('$$'), t.identifier('$$'))],
115+
})
116+
if (!existingImportKind.matched) {
117+
existingImportKind = astx.find({
118+
...decl,
119+
importKind: 'value',
120+
specifiers:
121+
specifier.type === 'ImportNamespaceSpecifier'
122+
? []
123+
: [t.importSpecifier(t.identifier('$$'), t.identifier('$$'))],
124+
})
125+
}
102126
if (existingImportKind.matched) {
103127
const existingDecl: t.ImportDeclaration =
104128
existingImportKind.node as any
@@ -111,7 +135,7 @@ export default function addImports(
111135
}
112136
}
113137

114-
return findImports(astx, pattern, options)
138+
return findImports(astx, pattern)
115139
}
116140

117141
function addSpecifierToDeclaration(

src/util/findImports.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import {
1010
stripImportKind,
1111
} from './imports'
1212
import compileMatcher from '../compileMatcher'
13+
import { compileRelativeSourceMatcher } from '../compileMatcher/RelativeSource'
1314

1415
export default function findImports(
1516
astx: Astx,
16-
pattern: readonly NodePath<Node, any>[],
17-
options: { filename?: string }
17+
pattern: readonly NodePath<Node, any>[]
1818
): Astx {
1919
for (const { node } of pattern) {
2020
if (node.type !== 'ImportDeclaration') {
@@ -41,16 +41,20 @@ export default function findImports(
4141
for (const path of pattern) {
4242
const { node } = path
4343
const decl: ImportDeclaration = node as any
44-
const sourceMatcher = compileMatcher(path.get('source'), {
45-
backend: astx.backend,
46-
})
44+
const sourceMatcher =
45+
compileRelativeSourceMatcher(path.get('source'), {
46+
debug: () => {},
47+
backend: astx.backend,
48+
getResolveAgainstDir: astx.context.getResolveAgainstDir,
49+
}) || compileMatcher(path.get('source'), astx.context)
4750
// filter down to only declarations where the source matches to speed up
4851
// going through the various patterns for each import specifier in the pattern
4952
// const existing = allExisting.filter(
5053
// (a) => (a.node as ImportDeclaration).source.value === decl.source.value
5154
// ).matched
5255
const existing = allExisting.filter(
53-
(a) => sourceMatcher.match(a.path.get('source'), null, options) != null
56+
(a) =>
57+
sourceMatcher.match(a.path.get('source'), null, astx.context) != null
5458
)
5559
if (!existing) return new Astx(astx.context, [])
5660

0 commit comments

Comments
 (0)