Skip to content

Commit

Permalink
fix(findImports): support string capture in import source
Browse files Browse the repository at this point in the history
  • Loading branch information
jedwards1211 committed Mar 30, 2024
1 parent 7dea20c commit 0e61bde
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 3 deletions.
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ Super powerful structural search and replace for JavaScript and TypeScript to au
- [`FindOptions`](#findoptions)
- [`FindOptions.where` (`{ [captureName: string]: (path: Astx) => boolean }`)](#findoptionswhere--capturename-string-path-astx--boolean-)
- [`.find(...).replace(...)` (`void`)](#findreplace-void)
- [`.findImports(...)` (`Astx`)](#findimports-astx)
- [`.addImports(...)` (`Astx`)](#addimports-astx)
- [`.removeImports(...)` (`boolean`)](#removeimports-boolean)
- [`.replaceImport(...).with(...)` (`boolean`)](#replaceimportwith-boolean)
- [`.remove()` (`void`)](#remove-void)
- [`.matched` (`this | null`)](#matched-this--null)
- [`.size()` (`number`)](#size-number)
Expand Down Expand Up @@ -598,6 +602,79 @@ astx
.replace(({ captures: { $fn } }) => `${$fn.name.toUpperCase()}()`)
```

### `.findImports(...)` (`Astx`)

A convenience version of `.find()` for finding imports that tolerates extra specifiers,
matches value imports of the same name if type imports were requested, etc.

For example `` .findImports`import $a from 'a'` `` would match `import A, { b, c } from 'a'`
or `import { default as a } from 'a'`, capturing `$a`, whereas `` .find`import $a from 'a'` ``
would not match either of these cases.

The pattern must contain only import statements.

### `.addImports(...)` (`Astx`)

Like `.findImports()`, but adds any imports that were not found. For example given the
source code:

```ts
import { foo, type bar as qux } from 'foo'
import 'g'
```

And the operation

```ts
const { $bar } = astx.addImports`
import type { bar as $bar } from 'foo'
import FooDefault from 'foo'
import * as g from 'g'
`
```

The output would be

```ts
import FooDefault, { foo, type bar as qux } from 'foo'
import * as g from 'g'
```

With `$bar` capturing the identifier `qux`.

### `.removeImports(...)` (`boolean`)

Takes import statements in the same format as `.findImports()` but removes all given specifiers.

### `.replaceImport(...).with(...)` (`boolean`)

Replaces a single import specifier with another. For example given the input

```ts
import { Match, Route, Location } from 'react-router-dom'
import type { History } from 'history'
```

And operation

```ts
astx.replaceImport`
import { Location } from 'react-router-dom'
`.with`
import type { Location } from 'history'
`
```

The output would be

```ts
import { Match, Route } from 'react-router-dom'
import type { History, Location } from 'history'
```

The find and replace patterns must both contain a single import statement
with a single specifier.

### `.remove()` (`void`)

Removes the matches from `.find()` or focused capture(s) in this `Astx` instance.
Expand Down
14 changes: 11 additions & 3 deletions src/util/findImports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getImported,
stripImportKind,
} from './imports'
import compileMatcher from '../compileMatcher'

export default function findImports(
astx: Astx,
Expand Down Expand Up @@ -36,13 +37,20 @@ export default function findImports(
const restSpecifier = () =>
t.importSpecifier(t.identifier('$$$'), t.identifier('$$$'))

for (const { node } of pattern) {
for (const path of pattern) {
const { node } = path
const decl: ImportDeclaration = node as any
const sourceMatcher = compileMatcher(path.get('source'), {
backend: astx.backend,
})
// filter down to only declarations where the source matches to speed up
// going through the various patterns for each import specifier in the pattern
// const existing = allExisting.filter(
// (a) => (a.node as ImportDeclaration).source.value === decl.source.value
// ).matched
const existing = allExisting.filter(
(a) => (a.node as ImportDeclaration).source.value === decl.source.value
).matched
(a) => sourceMatcher.match(a.path.get('source'), null) != null
)
if (!existing) return new Astx(astx.context, [])

if (!decl.specifiers?.length) {
Expand Down
19 changes: 19 additions & 0 deletions test/astx/findImports-source-placeholder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { TransformOptions } from '../../src'
import { astxTestcase } from '../astxTestcase'
import dedent from 'dedent-js'

astxTestcase({
file: __filename,
input: dedent`
import { foo as bar, qux } from 'foo'
import { foo as baz } from 'baz'
`,
astx: ({ astx, report }: TransformOptions): void => {
const result = astx.findImports`import { foo as $foo } from '$x'`
for (const { $foo, $x } of result) report([$foo.code, $x.stringValue])
},
expectedReports: [
['bar', 'foo'],
['baz', 'baz'],
],
})

0 comments on commit 0e61bde

Please sign in to comment.