Skip to content

Commit 7b1d315

Browse files
committed
fix: improve specifier merging
1 parent 6769389 commit 7b1d315

File tree

2 files changed

+49
-25
lines changed

2 files changed

+49
-25
lines changed

index.js

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ const j = require('jscodeshift').withParser('babylon')
22
const findImports = require('jscodeshift-find-imports')
33
const traverse = require('@babel/traverse').default
44

5+
const firstPath = c => c.at(0).paths()[0]
6+
const lastPath = c => c.at(-1).paths()[0]
7+
const firstNode = c => c.at(0).nodes()[0]
8+
const lastNode = c => c.at(-1).nodes()[0]
9+
510
module.exports = function addImports(root, _statements) {
611
const found = findImports(root, _statements)
712

813
let babelScope
9-
let astTypeScope = root
10-
.find(j.Program)
11-
.at(0)
12-
.paths()[0]
13-
.scope.getGlobalScope()
14+
let astTypeScope = firstPath(root.find(j.Program)).scope.getGlobalScope()
1415
try {
15-
traverse(root.at(0).nodes()[0], {
16+
traverse(firstNode(root), {
1617
Program(path) {
1718
babelScope = path.scope
1819
},
@@ -42,7 +43,7 @@ module.exports = function addImports(root, _statements) {
4243
if (statement.type === 'ImportDeclaration') {
4344
const { importKind } = statement
4445
const source = { value: statement.source.value }
45-
let existing = root.find(j.ImportDeclaration, { importKind, source })
46+
let existing = root.find(j.ImportDeclaration, { source })
4647
for (let specifier of statement.specifiers) {
4748
if (found[specifier.local.name]) continue
4849
const name = preventNameConflict(specifier.local.name)
@@ -57,8 +58,19 @@ module.exports = function addImports(root, _statements) {
5758
specifier.local = j.identifier(name)
5859
}
5960
if (existing.size()) {
60-
const last = existing.paths()[existing.size() - 1].node
61-
last.specifiers.push(specifier)
61+
const decl = lastNode(existing)
62+
const specifierImportKind =
63+
specifier.importKind || importKind || 'value'
64+
if ((decl.importKind || 'value') !== specifierImportKind) {
65+
if (decl.importKind.startsWith('type')) {
66+
for (let existingSpecifier of decl.specifiers) {
67+
existingSpecifier.importKind = decl.importKind
68+
}
69+
decl.importKind = null
70+
}
71+
specifier.importKind = specifierImportKind
72+
}
73+
decl.specifiers.push(specifier)
6274
} else {
6375
const newDeclaration = j.importDeclaration(
6476
[specifier],
@@ -67,13 +79,11 @@ module.exports = function addImports(root, _statements) {
6779
)
6880
const allImports = root.find(j.ImportDeclaration)
6981
if (allImports.size()) {
70-
allImports
71-
.paths()
72-
[allImports.size() - 1].insertAfter(newDeclaration)
82+
lastPath(allImports).insertAfter(newDeclaration)
7383
} else {
7484
insertProgramStatement(root, newDeclaration)
7585
}
76-
existing = root.find(j.ImportDeclaration, { importKind, source })
86+
existing = root.find(j.ImportDeclaration, { source })
7787
}
7888
}
7989
} else if (statement.type === 'VariableDeclaration') {
@@ -102,17 +112,14 @@ module.exports = function addImports(root, _statements) {
102112
)
103113
}
104114
if (existing.size()) {
105-
const last = existing.paths()[existing.size() - 1].node
106-
last.id.properties.push(prop)
115+
lastNode(existing).id.properties.push(prop)
107116
} else {
108117
const newDeclaration = j.variableDeclaration('const', [
109118
j.variableDeclarator(j.objectPattern([prop]), declarator.init),
110119
])
111120
const allImports = root.find(j.ImportDeclaration)
112121
if (allImports.size()) {
113-
allImports
114-
.paths()
115-
[allImports.size() - 1].insertAfter(newDeclaration)
122+
lastPath(allImports).insertAfter(newDeclaration)
116123
} else {
117124
insertProgramStatement(root, newDeclaration)
118125
}
@@ -128,9 +135,7 @@ module.exports = function addImports(root, _statements) {
128135
])
129136
const allImports = root.find(j.ImportDeclaration)
130137
if (allImports.size()) {
131-
allImports
132-
.paths()
133-
[allImports.size() - 1].insertAfter(newDeclaration)
138+
lastPath(allImports).insertAfter(newDeclaration)
134139
} else {
135140
insertProgramStatement(root, newDeclaration)
136141
}

test/index.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ const {
5757
const {
5858
foo: qux
5959
} = require('baz');`)
60+
})
61+
it(`merges destructuring`, function() {
62+
const code = `const {foo} = require('bar')`
63+
const root = j(code)
64+
addImports(root, statement`const {bar} = require('bar')`)
65+
expect(root.toSource()).to.equal(`const {
66+
foo,
67+
bar
68+
} = require('bar')`)
6069
})
6170
it(`leaves existing non-default requires without alias untouched`, function() {
6271
const code = `const {foo} = require('baz')`
@@ -221,8 +230,19 @@ import {foo as bar} from 'baz'
221230
import type { glab as qlob } from "qlob";`
222231
const root = j(code)
223232
addImports(root, statement`import type {foo as qux} from 'baz'`)
224-
expect(root.toSource()).to.equal(`${code}
225-
import type { foo as qux } from "baz";`)
233+
expect(root.toSource()).to.equal(`
234+
import { foo as bar, type foo as qux } from 'baz';
235+
import type { glab as qlob } from "qlob";`)
236+
})
237+
it(`converts import type {} to import {type} if necessary`, function() {
238+
const code = `
239+
import type {foo as bar} from 'baz'
240+
`
241+
const root = j(code)
242+
addImports(root, statement`import {foo as qux} from 'baz'`)
243+
expect(root.toSource()).to.equal(`
244+
import { type foo as bar, foo as qux } from 'baz';
245+
`)
226246
})
227247
it(`leaves existing non-default import specifiers without aliases untouched`, function() {
228248
const code = `import {foo} from 'baz'`
@@ -370,9 +390,8 @@ import baz from 'baz'
370390
qux: 'qux',
371391
})
372392
expect(root.toSource()).to.equal(`// @flow
373-
import {foo, type bar} from 'foo'
393+
import { foo, type bar, type baz as baz1 } from 'foo';
374394
import baz from 'baz'
375-
import type { baz as baz1 } from "foo";
376395
import blah, { type qux } from "qux";
377396
`)
378397
})

0 commit comments

Comments
 (0)