Skip to content

Commit f8c388b

Browse files
committed
fix(imports): fix a bug where registering the same implementation multiple times will generate multiple imports
1 parent 265ac93 commit f8c388b

File tree

2 files changed

+71
-0
lines changed

2 files changed

+71
-0
lines changed

src/transformer/before/before-transformer.ts

+27
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,33 @@ function transformSourceFile(
2121
): TS.SourceFile {
2222
const requiredImportedSymbolSet = new Set<ImportedSymbol>();
2323

24+
/**
25+
* An optimization in which every imported symbol is converted into
26+
* a string that can be matched against directly to guard against
27+
* duplicates
28+
*/
29+
const requiredImportedSymbolSetFlags = new Set<string>();
30+
2431
context.sourceFileToAddTslibDefinition.set(sourceFile.fileName, false);
2532
context.sourceFileToRequiredImportedSymbolSet.set(
2633
sourceFile.fileName,
2734
requiredImportedSymbolSet
2835
);
2936

37+
const computeImportedSymbolFlag = (symbol: ImportedSymbol): string =>
38+
[
39+
"name",
40+
"propertyName",
41+
"moduleSpecifier",
42+
"isNamespaceImport",
43+
"isDefaultImport",
44+
]
45+
.map(
46+
(property) =>
47+
`${property}:${symbol[property as keyof ImportedSymbol] ?? false}`
48+
)
49+
.join("|");
50+
3051
const visitorOptions: Omit<
3152
BeforeVisitorOptions<TS.Node>,
3253
"node" | "sourceFile"
@@ -38,6 +59,12 @@ function transformSourceFile(
3859
},
3960

4061
requireImportedSymbol: (importedSymbol: ImportedSymbol): void => {
62+
// Guard against duplicates and compute a string so we can do
63+
// constant time lookups to compare against existing symbols
64+
const flag = computeImportedSymbolFlag(importedSymbol);
65+
if (requiredImportedSymbolSetFlags.has(flag)) return;
66+
requiredImportedSymbolSetFlags.add(flag);
67+
4168
requiredImportedSymbolSet.add(importedSymbol);
4269
},
4370

test/container.test.ts

+44
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,50 @@ test(
156156
}
157157
);
158158

159+
test(
160+
"Won't include imports multiple times when the same implementation is registered multiple times. #1",
161+
withTypeScript,
162+
(t, { typescript }) => {
163+
const bundle = generateTransformerResult(
164+
[
165+
{
166+
entry: true,
167+
fileName: "index.ts",
168+
text: `
169+
import {DIContainer} from "@wessberg/di";
170+
import {IFoo, Foo} from "./foo";
171+
172+
const container = new DIContainer();
173+
container.registerSingleton<IFoo, Foo>();
174+
container.registerSingleton<IFoo, Foo>();
175+
`,
176+
},
177+
{
178+
entry: false,
179+
fileName: "foo.ts",
180+
text: `
181+
export interface IFoo {}
182+
export class Foo implements IFoo {}
183+
`,
184+
},
185+
],
186+
{ typescript }
187+
);
188+
const file = bundle.find(({ fileName }) => fileName.includes("index.js"))!;
189+
190+
t.deepEqual(
191+
formatCode(file.text),
192+
formatCode(`\
193+
import { Foo } from "./foo";
194+
import { DIContainer } from "@wessberg/di";
195+
const container = new DIContainer();
196+
container.registerSingleton(undefined, { identifier: "IFoo", implementation: Foo });
197+
container.registerSingleton(undefined, { identifier: "IFoo", implementation: Foo });
198+
`)
199+
);
200+
}
201+
);
202+
159203
test(
160204
"Supports custom implementation functions. #1",
161205
withTypeScript,

0 commit comments

Comments
 (0)