/
explode-type-and-value-specifiers.ts
62 lines (54 loc) · 2.1 KB
/
explode-type-and-value-specifiers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import { importDeclaration, type ImportSpecifier } from '@babel/types';
import { ExplodeTypeAndValueSpecifiers } from '../types';
/**
* Breaks apart import declarations containing mixed type and value imports into separate declarations.
*
* e.g.
*
* ```diff
* - import foo, { bar, type Baz } from './source';
* + import foo, { bar } from './source';
* + import type { Baz } from './source';
* ```
*/
export const explodeTypeAndValueSpecifiers: ExplodeTypeAndValueSpecifiers = (
nodes,
) => {
const explodedNodes = [];
for (const node of nodes) {
// We don't need to explode type imports, they won't mix type and value
if (node.importKind === 'type') {
explodedNodes.push(node);
continue;
}
// Nothing to do if there's only one specifier
if (node.specifiers.length <= 1) {
explodedNodes.push(node);
continue;
}
// @ts-expect-error TS is not refining correctly, but we're checking the type
const typeImports: ImportSpecifier[] = node.specifiers.filter(
(i) => i.type === 'ImportSpecifier' && i.importKind === 'type',
);
// If we have a mix of type and value imports, we need to 'splode them into two import declarations
if (typeImports.length) {
const valueImports = node.specifiers.filter(
(i) =>
!(i.type === 'ImportSpecifier' && i.importKind === 'type'),
);
const newValueNode = importDeclaration(valueImports, node.source);
explodedNodes.push(newValueNode);
// Change the importKind of the specifiers, to avoid `import type {type Foo} from 'foo'`
typeImports.forEach(
(specifier) => (specifier.importKind = 'value'),
);
const newTypeNode = importDeclaration(typeImports, node.source);
newTypeNode.importKind = 'type';
explodedNodes.push(newTypeNode);
continue;
}
// Just a boring old values-only node
explodedNodes.push(node);
}
return explodedNodes;
};