Skip to content

Commit 9a0fec3

Browse files
committed
fix: keep bundled identifier name
1 parent a324b07 commit 9a0fec3

File tree

5 files changed

+109
-63
lines changed

5 files changed

+109
-63
lines changed

src/index.ts

Lines changed: 98 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import { walk } from 'estree-walker'
33
import { MagicStringAST } from 'magic-string-ast'
44
import {
55
parseAsync,
6+
type BindingPattern,
67
type Declaration,
8+
type Expression,
79
type Function,
810
type Node,
911
type Span,
12+
type TSModuleDeclarationName,
1013
type TSTypeName,
1114
type TSTypeQuery,
1215
type TSTypeReference,
@@ -17,26 +20,51 @@ import type { Plugin } from 'rolldown'
1720

1821
const RE_TYPE = /\btype\b/
1922

23+
type Range = [start: number, end: number]
24+
2025
export function dts(): Plugin {
2126
let i = 0
22-
const map = new Map<number, [code: string, nameRange: [number, number]]>()
27+
const symbolMap = new Map<
28+
number /* symbol id */,
29+
[code: string, bindingRange: Range[]]
30+
>()
2331

24-
function register(raw: string, idNode: Node & Span, parent: Span) {
25-
const id = i++
26-
let idNodeEnd = idNode.end
27-
if ('typeAnnotation' in idNode && idNode.typeAnnotation) {
28-
idNodeEnd = idNode.typeAnnotation.start
32+
function register(
33+
raw: string,
34+
binding: BindingPattern | TSModuleDeclarationName,
35+
deps: (Node & Span)[],
36+
parent: Span,
37+
) {
38+
const symbolId = i++
39+
let bindingEnd = binding.end
40+
if ('typeAnnotation' in binding && binding.typeAnnotation) {
41+
bindingEnd = binding.typeAnnotation.start
2942
}
30-
const nameRange: [number, number] = [
31-
idNode.start - parent.start,
32-
idNodeEnd - parent.start,
33-
]
34-
map.set(id, [raw, nameRange])
35-
return id
43+
symbolMap.set(symbolId, [
44+
raw,
45+
[
46+
[binding.start - parent.start, bindingEnd - parent.start],
47+
...deps.map(
48+
(d): Range => [d.start - parent.start, d.end - parent.start],
49+
),
50+
],
51+
])
52+
return symbolId
3653
}
37-
function retrieve(id: number, name: string) {
38-
const [code, nameRange] = map.get(id)!
39-
return code.slice(0, nameRange[0]) + name + code.slice(nameRange[1])
54+
55+
function retrieve(s: MagicStringAST, id: number, bindings: Span[]) {
56+
const [code, ranges] = symbolMap.get(id)!
57+
if (!ranges.length) return code
58+
59+
let codeIndex = 0
60+
let result = ''
61+
for (const [start, end] of ranges) {
62+
result += code.slice(codeIndex, start)
63+
result += s.sliceNode(bindings.shift())
64+
codeIndex = end
65+
}
66+
result += code.slice(codeIndex)
67+
return result
4068
}
4169

4270
return {
@@ -60,32 +88,7 @@ export function dts(): Plugin {
6088

6189
const s = new MagicStringAST(code)
6290
for (let node of program.body as (Node & Span)[]) {
63-
// fix:
64-
// - import type { ... } from '...'
65-
// - import { type ... } from '...'
66-
if (node.type === 'ImportDeclaration') {
67-
for (const specifier of node.specifiers) {
68-
if (
69-
specifier.type === 'ImportSpecifier' &&
70-
specifier.importKind === 'type'
71-
) {
72-
s.overwriteNode(
73-
specifier,
74-
s.sliceNode(specifier).replace(RE_TYPE, ''),
75-
)
76-
}
77-
}
78-
79-
const firstSpecifier = node.specifiers[0]
80-
if (node.importKind === 'type' && firstSpecifier) {
81-
s.overwrite(
82-
node.start,
83-
firstSpecifier.start,
84-
s.slice(node.start, firstSpecifier.start).replace(RE_TYPE, ''),
85-
)
86-
}
87-
continue
88-
}
91+
if (rewriteImportType(s, node)) continue
8992

9093
// remove `export` modifier
9194
const isDefaultExport = node.type === 'ExportDefaultDeclaration'
@@ -112,19 +115,18 @@ export function dts(): Plugin {
112115
).id
113116
if (!binding) continue
114117
const original = s.sliceNode(node)
115-
const id = register(original, binding, node)
116-
const deps = collectDependencies(node)
117-
const typeDeps = collectTypeDeps(node)
118-
const depsString = [...deps, ...typeDeps]
118+
const deps = [...collectDependencies(node), ...collectTypeDeps(node)]
119+
const depsString = deps
119120
.map((node) => `() => ${s.sliceNode(node)}`)
120121
.join(', ')
122+
const symbolId = register(original, binding, deps, node)
121123

122-
const runtime = `[${id}, ${depsString}]`
124+
const runtime = `[${symbolId}, ${depsString}]`
123125
s.overwriteNode(
124126
node,
125127
isDefaultExport
126128
? runtime
127-
: `var ${s.sliceNode(binding)} = [${id}, ${depsString}]`,
129+
: `var ${s.sliceNode(binding)} = [${symbolId}, ${depsString}]`,
128130
)
129131
}
130132
}
@@ -137,9 +139,8 @@ export function dts(): Plugin {
137139
const s = new MagicStringAST(code)
138140

139141
for (const node of program.body) {
140-
if (patchImportSource(s, node)) {
141-
continue
142-
}
142+
if (patchImportSource(s, node)) continue
143+
143144
if (
144145
node.type !== 'VariableDeclaration' ||
145146
node.declarations.length !== 1
@@ -153,14 +154,24 @@ export function dts(): Plugin {
153154
continue
154155
}
155156

156-
const idNode = decl.init.elements[0]
157-
if (idNode?.type !== 'Literal' || typeof idNode.value !== 'number') {
157+
const [symbolIdNode, ...depsNodes] = decl.init.elements as Expression[]
158+
if (
159+
symbolIdNode?.type !== 'Literal' ||
160+
typeof symbolIdNode.value !== 'number'
161+
) {
158162
patchVariableDeclarator(s, node, decl)
159163
continue
160164
}
161165

162-
const id = idNode.value
163-
const type = retrieve(id, s.sliceNode(decl.id))
166+
const symbolId = symbolIdNode.value
167+
const type = retrieve(s, symbolId, [
168+
decl.id,
169+
...depsNodes.map((dep) => {
170+
if (dep.type !== 'ArrowFunctionExpression')
171+
throw new Error('Expected ArrowFunctionExpression')
172+
return dep.body
173+
}),
174+
])
164175

165176
s.overwriteNode(node, type)
166177
}
@@ -185,17 +196,18 @@ export function dts(): Plugin {
185196
const [decl] = node.declarations
186197

187198
const raw = s.sliceNode(node)
188-
const id = register(raw, decl.id, node)
189199
const deps = collectTypeDeps(node)
190-
.map((node) => s.sliceNode(node))
200+
const symbolId = register(raw, decl.id, deps, node)
201+
const depsString = collectTypeDeps(node)
202+
.map((node) => `() => ${s.sliceNode(node)}`)
191203
.join(', ')
192-
const runtime = `[${id}, ${deps}]`
204+
const runtime = `[${symbolId}, ${depsString}]`
193205
s.overwriteNode(node, `var ${s.sliceNode(decl.id)} = ${runtime}`)
194206
}
195207
}
196208

197-
function collectDependencies(node: Node) {
198-
const deps = new Set<Node>()
209+
function collectDependencies(node: Node): (Node & Span)[] {
210+
const deps = new Set<Node & Span>()
199211
;(walk as any)(node, {
200212
enter(node: Node) {
201213
if (node.type === 'ClassDeclaration' && node.superClass) {
@@ -234,7 +246,7 @@ function collectTypeDeps(node: Node): TSTypeName[] {
234246
return result
235247
}
236248

237-
// patch let x = 1; to declare let x: typeof 1;
249+
// patch `let x = 1;` to `declare let x: typeof 1;`
238250
function patchVariableDeclarator(
239251
s: MagicStringAST,
240252
node: VariableDeclaration,
@@ -265,3 +277,29 @@ function patchImportSource(s: MagicStringAST, node: Node) {
265277
return true
266278
}
267279
}
280+
281+
// fix:
282+
// - import type { ... } from '...'
283+
// - import { type ... } from '...'
284+
function rewriteImportType(s: MagicStringAST, node: Node) {
285+
if (node.type === 'ImportDeclaration') {
286+
for (const specifier of node.specifiers) {
287+
if (
288+
specifier.type === 'ImportSpecifier' &&
289+
specifier.importKind === 'type'
290+
) {
291+
s.overwriteNode(specifier, s.sliceNode(specifier).replace(RE_TYPE, ''))
292+
}
293+
}
294+
295+
const firstSpecifier = node.specifiers[0]
296+
if (node.importKind === 'type' && firstSpecifier) {
297+
s.overwrite(
298+
node.start,
299+
firstSpecifier.start,
300+
s.slice(node.start, firstSpecifier.start).replace(RE_TYPE, ''),
301+
)
302+
}
303+
return true
304+
}
305+
}

tests/fixtures/basic/foo.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import * as mod from './mod.js'
12
export declare const foo: number
3+
export declare const bar: typeof mod.a
24
type SomeType<T> = T
35
type FooType = string
46
interface Interface {}

tests/fixtures/basic/index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
import type { type foo, type fn, type Cls } from './foo.js'
1+
import type { type foo, type fn, type Cls, bar } from './foo.js'
22

3-
export { foo, fn, Cls }
3+
export { foo, bar, fn, Cls }

tests/fixtures/basic/mod.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export declare const a: string

tests/fixtures/basic/snapshot.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// index.d.ts
22

3+
//#region tests/fixtures/basic/mod.d.ts
4+
declare const a: string
5+
6+
//#endregion
37
//#region tests/fixtures/basic/foo.d.ts
48
declare const foo: number
9+
declare const bar: typeof a
510
type SomeType<T> = T
611
type FooType = string
712
interface Interface {}
@@ -17,4 +22,4 @@ declare class Cls {
1722
}
1823

1924
//#endregion
20-
export { Cls, fn, foo };
25+
export { Cls, bar, fn, foo };

0 commit comments

Comments
 (0)