Skip to content

Commit a123b83

Browse files
authored
fix: ensure deterministic and valid namespace import identifiers across files (#180)
1 parent 3a934e9 commit a123b83

File tree

18 files changed

+220
-63
lines changed

18 files changed

+220
-63
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
},
7676
"dependencies": {
7777
"@babel/generator": "8.0.0-rc.1",
78+
"@babel/helper-validator-identifier": "8.0.0-rc.1",
7879
"@babel/parser": "8.0.0-rc.1",
7980
"@babel/types": "8.0.0-rc.1",
8081
"ast-kit": "^3.0.0-beta.1",

pnpm-lock.yaml

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

src/fake-js.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { generate } from '@babel/generator'
2+
import { isIdentifierName } from '@babel/helper-validator-identifier'
23
import { parse } from '@babel/parser'
34
import * as t from '@babel/types'
45
import {
@@ -66,7 +67,6 @@ export function createFakeJsPlugin({
6667
sideEffects,
6768
}: Pick<OptionsResolved, 'sourcemap' | 'cjsDefault' | 'sideEffects'>): Plugin {
6869
let declarationIdx = 0
69-
const identifierMap: Record<string, number> = Object.create(null)
7070
const declarationMap = new Map<number /* declaration id */, DeclarationInfo>()
7171
const commentsMap = new Map<string /* filename */, t.Comment[]>()
7272
const typeOnlyMap = new Map<string /* filename */, string[]>()
@@ -150,6 +150,7 @@ export function createFakeJsPlugin({
150150
})
151151
const { program, comments } = file
152152
const typeOnlyIds: string[] = []
153+
const identifierMap: Record<string, number> = Object.create(null)
153154

154155
if (comments) {
155156
const directives = collectReferenceDirectives(comments)
@@ -225,7 +226,7 @@ export function createFakeJsPlugin({
225226
}
226227

227228
binding = sideEffect
228-
? t.identifier(`_${getIdentifierIndex('')}`)
229+
? t.identifier(`_${getIdentifierIndex(identifierMap, '')}`)
229230
: binding
230231

231232
if (binding.type !== 'Identifier') {
@@ -243,7 +244,12 @@ export function createFakeJsPlugin({
243244
const params: TypeParams = collectParams(decl)
244245

245246
const childrenSet = new Set<t.Node>()
246-
const deps = collectDependencies(decl, namespaceStmts, childrenSet)
247+
const deps = collectDependencies(
248+
decl,
249+
namespaceStmts,
250+
childrenSet,
251+
identifierMap,
252+
)
247253
const children = Array.from(childrenSet).filter((child) =>
248254
bindings.every((b) => child !== b),
249255
)
@@ -490,9 +496,13 @@ export function createFakeJsPlugin({
490496
}
491497
}
492498

493-
function getIdentifierIndex(name: string) {
499+
// eslint-disable-next-line unicorn/consistent-function-scoping
500+
function getIdentifierIndex(
501+
identifierMap: Record<string, number>,
502+
name: string,
503+
): number {
494504
if (name in identifierMap) {
495-
return identifierMap[name]++
505+
return ++identifierMap[name]
496506
}
497507
return (identifierMap[name] = 0)
498508
}
@@ -551,6 +561,7 @@ export function createFakeJsPlugin({
551561
node: t.Node,
552562
namespaceStmts: NamespaceMap,
553563
children: Set<t.Node>,
564+
identifierMap: Record<string, number>,
554565
): Dep[] {
555566
const deps = new Set<Dep>()
556567
const seen = new Set<t.Node>()
@@ -638,6 +649,7 @@ export function createFakeJsPlugin({
638649
qualifier,
639650
source,
640651
namespaceStmts,
652+
identifierMap,
641653
)
642654
addDependency(dep)
643655
break
@@ -662,11 +674,14 @@ export function createFakeJsPlugin({
662674
imported: t.TSEntityName | null | undefined,
663675
source: t.StringLiteral,
664676
namespaceStmts: NamespaceMap,
677+
identifierMap: Record<string, number>,
665678
): Dep {
666679
const sourceText = source.value.replaceAll(/\W/g, '_')
667-
let local: t.Identifier | t.TSQualifiedName = t.identifier(
668-
`${sourceText}${getIdentifierIndex(sourceText)}`,
669-
)
680+
// Use original source if it's already a valid identifier, otherwise use formatted text with index
681+
const localName = isIdentifierName(source.value)
682+
? source.value
683+
: `${sourceText}${getIdentifierIndex(identifierMap, sourceText)}`
684+
let local: t.Identifier | t.TSQualifiedName = t.identifier(localName)
670685

671686
if (namespaceStmts.has(source.value)) {
672687
local = namespaceStmts.get(source.value)!.local

tests/__snapshots__/index.test.ts.snap

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,78 @@ declare module "virtual" {
112112
}"
113113
`;
114114
115+
exports[`deterministic namespace import index 1`] = `
116+
"// a.d.ts
117+
import * as stub_lib from "stub_lib";
118+
119+
//#region a.d.ts
120+
type MyTypeA = stub_lib.LibType;
121+
//#endregion
122+
export { MyTypeA };
123+
// b.d.ts
124+
import * as stub_lib from "stub_lib";
125+
126+
//#region b.d.ts
127+
type MyTypeB = stub_lib.LibType;
128+
//#endregion
129+
export { MyTypeB };
130+
// c.d.ts
131+
import * as stub_lib from "stub_lib";
132+
133+
//#region c.d.ts
134+
type MyTypeC = stub_lib.LibType;
135+
//#endregion
136+
export { MyTypeC };"
137+
`;
138+
139+
exports[`deterministic namespace import index 2`] = `
140+
"// a.d.ts
141+
import * as stub_lib from "stub_lib";
142+
143+
//#region a.d.ts
144+
type MyTypeA = stub_lib.LibType;
145+
//#endregion
146+
export { MyTypeA };
147+
// b.d.ts
148+
import * as stub_lib from "stub_lib";
149+
150+
//#region b.d.ts
151+
type MyTypeB = stub_lib.LibType;
152+
//#endregion
153+
export { MyTypeB };
154+
// c.d.ts
155+
import * as stub_lib from "stub_lib";
156+
157+
//#region c.d.ts
158+
type MyTypeC = stub_lib.LibType;
159+
//#endregion
160+
export { MyTypeC };"
161+
`;
162+
163+
exports[`deterministic namespace import index 3`] = `
164+
"// a.d.ts
165+
import * as stub_lib from "stub_lib";
166+
167+
//#region a.d.ts
168+
type MyTypeA = stub_lib.LibType;
169+
//#endregion
170+
export { MyTypeA };
171+
// b.d.ts
172+
import * as stub_lib from "stub_lib";
173+
174+
//#region b.d.ts
175+
type MyTypeB = stub_lib.LibType;
176+
//#endregion
177+
export { MyTypeB };
178+
// c.d.ts
179+
import * as stub_lib from "stub_lib";
180+
181+
//#region c.d.ts
182+
type MyTypeC = stub_lib.LibType;
183+
//#endregion
184+
export { MyTypeC };"
185+
`;
186+
115187
exports[`dts input > custom chunk name 2`] = `
116188
"// chunks/BCXvBysl-types.d.ts
117189
//#region tests/fixtures/dts-multi-input/types.d.ts

tests/__snapshots__/tsc.test.ts.snap

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -484,22 +484,22 @@ export { _default as App };"
484484
485485
exports[`tsc > vue-sfc w/ ts-compiler w/ vueCompilerOptions in tsconfig 1`] = `
486486
"// main.d.ts
487-
import * as vue0 from "vue";
487+
import * as vue from "vue";
488488
489489
//#region tests/fixtures/vue-sfc-fallthrough/App.vue.d.ts
490-
declare const __VLS_export: vue0.DefineComponent<{
490+
declare const __VLS_export: vue.DefineComponent<{
491491
foo: string;
492492
bar?: string;
493493
innerHTML?: string | undefined;
494494
class?: any;
495-
style?: vue0.StyleValue | undefined;
495+
style?: vue.StyleValue | undefined;
496496
accesskey?: string | undefined;
497497
contenteditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
498498
contextmenu?: string | undefined;
499499
dir?: string | undefined;
500500
draggable?: (boolean | "true" | "false") | undefined;
501501
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
502-
enterKeyHint?: vue0.HTMLAttributes["enterkeyhint"];
502+
enterKeyHint?: vue.HTMLAttributes["enterkeyhint"];
503503
hidden?: (boolean | "true" | "false") | "" | "hidden" | "until-found" | undefined;
504504
id?: string | undefined;
505505
inert?: (boolean | "true" | "false") | undefined;
@@ -678,28 +678,28 @@ declare const __VLS_export: vue0.DefineComponent<{
678678
onTransitionrun?: (payload: TransitionEvent) => void;
679679
onTransitionstart?: (payload: TransitionEvent) => void;
680680
key?: PropertyKey | undefined;
681-
ref?: vue0.VNodeRef | undefined;
681+
ref?: vue.VNodeRef | undefined;
682682
ref_for?: boolean | undefined;
683683
ref_key?: string | undefined;
684-
onVnodeBeforeMount?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
685-
onVnodeMounted?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
686-
onVnodeBeforeUpdate?: ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void) | ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void)[];
687-
onVnodeUpdated?: ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void) | ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void)[];
688-
onVnodeBeforeUnmount?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
689-
onVnodeUnmounted?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
690-
}, {}, {}, {}, {}, vue0.ComponentOptionsMixin, vue0.ComponentOptionsMixin, {}, string, vue0.PublicProps, Readonly<{
684+
onVnodeBeforeMount?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
685+
onVnodeMounted?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
686+
onVnodeBeforeUpdate?: ((vnode: vue.VNode, oldVNode: vue.VNode) => void) | ((vnode: vue.VNode, oldVNode: vue.VNode) => void)[];
687+
onVnodeUpdated?: ((vnode: vue.VNode, oldVNode: vue.VNode) => void) | ((vnode: vue.VNode, oldVNode: vue.VNode) => void)[];
688+
onVnodeBeforeUnmount?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
689+
onVnodeUnmounted?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
690+
}, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{
691691
foo: string;
692692
bar?: string;
693693
innerHTML?: string | undefined;
694694
class?: any;
695-
style?: vue0.StyleValue | undefined;
695+
style?: vue.StyleValue | undefined;
696696
accesskey?: string | undefined;
697697
contenteditable?: (boolean | "true" | "false") | "inherit" | "plaintext-only" | undefined;
698698
contextmenu?: string | undefined;
699699
dir?: string | undefined;
700700
draggable?: (boolean | "true" | "false") | undefined;
701701
enterkeyhint?: "enter" | "done" | "go" | "next" | "previous" | "search" | "send" | undefined;
702-
enterKeyHint?: vue0.HTMLAttributes["enterkeyhint"];
702+
enterKeyHint?: vue.HTMLAttributes["enterkeyhint"];
703703
hidden?: (boolean | "true" | "false") | "" | "hidden" | "until-found" | undefined;
704704
id?: string | undefined;
705705
inert?: (boolean | "true" | "false") | undefined;
@@ -878,24 +878,24 @@ declare const __VLS_export: vue0.DefineComponent<{
878878
onTransitionrun?: (payload: TransitionEvent) => void;
879879
onTransitionstart?: (payload: TransitionEvent) => void;
880880
key?: PropertyKey | undefined;
881-
ref?: vue0.VNodeRef | undefined;
881+
ref?: vue.VNodeRef | undefined;
882882
ref_for?: boolean | undefined;
883883
ref_key?: string | undefined;
884-
onVnodeBeforeMount?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
885-
onVnodeMounted?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
886-
onVnodeBeforeUpdate?: ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void) | ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void)[];
887-
onVnodeUpdated?: ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void) | ((vnode: vue0.VNode, oldVNode: vue0.VNode) => void)[];
888-
onVnodeBeforeUnmount?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
889-
onVnodeUnmounted?: ((vnode: vue0.VNode) => void) | ((vnode: vue0.VNode) => void)[];
890-
}> & Readonly<{}>, {}, {}, {}, {}, string, vue0.ComponentProvideOptions, false, {}, any>;
884+
onVnodeBeforeMount?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
885+
onVnodeMounted?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
886+
onVnodeBeforeUpdate?: ((vnode: vue.VNode, oldVNode: vue.VNode) => void) | ((vnode: vue.VNode, oldVNode: vue.VNode) => void)[];
887+
onVnodeUpdated?: ((vnode: vue.VNode, oldVNode: vue.VNode) => void) | ((vnode: vue.VNode, oldVNode: vue.VNode) => void)[];
888+
onVnodeBeforeUnmount?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
889+
onVnodeUnmounted?: ((vnode: vue.VNode) => void) | ((vnode: vue.VNode) => void)[];
890+
}> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, false, {}, any>;
891891
declare const _default: typeof __VLS_export;
892892
//#endregion
893893
export { _default as App };"
894894
`;
895895
896896
exports[`tsc > vue-sfc w/ ts-macro w/ ts-compiler 1`] = `
897897
"// main.d.ts
898-
import * as vue0 from "vue";
898+
import * as vue from "vue";
899899
900900
//#region tests/fixtures/vue-sfc-with-ts-macro/App.vue.d.ts
901901
type __VLS_Props = {
@@ -906,7 +906,7 @@ declare global {
906906
foo: string;
907907
}
908908
}
909-
declare const __VLS_export: vue0.DefineComponent<__VLS_Props, {}, {}, {}, {}, vue0.ComponentOptionsMixin, vue0.ComponentOptionsMixin, {}, string, vue0.PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, vue0.ComponentProvideOptions, false, {}, any>;
909+
declare const __VLS_export: vue.DefineComponent<__VLS_Props, {}, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, false, {}, any>;
910910
declare const _default: typeof __VLS_export;
911911
//#endregion
912912
//#region tests/fixtures/vue-sfc-with-ts-macro/main.d.ts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type MyTypeA = import('stub_lib').LibType
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type MyTypeB = import('stub_lib').LibType
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type MyTypeC = import('stub_lib').LibType
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"noEmit": true,
5+
"typeRoots": ["./typings"]
6+
}
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface LibType {
2+
name: string;
3+
}

0 commit comments

Comments
 (0)