Skip to content

Commit 45df665

Browse files
authored
Dedupe cjs export assignments (#4284)
1 parent 4a461b7 commit 45df665

14 files changed

Lines changed: 158 additions & 42 deletions

internal/checker/nodebuilderimpl.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2166,6 +2166,10 @@ func (b *NodeBuilderImpl) indexInfoToIndexSignatureDeclarationHelper(indexInfo *
21662166
return b.f.NewIndexSignatureDeclaration(modifiers, b.f.NewNodeList([]*ast.Node{indexingParameter}), typeNode)
21672167
}
21682168

2169+
func hasTypeAnnotation(declaration *ast.Declaration) bool {
2170+
return declaration != nil && declaration.Type() != nil
2171+
}
2172+
21692173
/**
21702174
* Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag
21712175
* so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym`
@@ -2223,6 +2227,12 @@ func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati
22232227
} else {
22242228
pt = b.pc.GetTypeOfDeclaration(declaration)
22252229
}
2230+
if (pt == nil || pt.Kind == pseudochecker.PseudoTypeKindNoResult) && ast.IsBinaryExpression(declaration) {
2231+
if decl := core.Find(symbol.Declarations, hasTypeAnnotation); decl != nil {
2232+
// Binary expressions have a first-in-wins type annotation system. The first one with an annotation supplies the type for the rest.
2233+
pt = b.pc.GetTypeOfDeclaration(decl)
2234+
}
2235+
}
22262236
reportErrors := !b.ctx.suppressReportInferenceFallback
22272237
if b.pseudoTypeEquivalentToType(pt, t, !requiresAddingUndefined && (ast.IsParameterDeclaration(declaration) || ast.IsPropertySignatureDeclaration(declaration) || ast.IsPropertyDeclaration(declaration)) && isOptionalDeclaration(declaration), reportErrors) {
22282238
// !!! TODO: If annotated type node is a reference with insufficient type arguments, we should still fall back to type serialization

internal/transformers/declarations/transform.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type DeclarationTransformer struct {
5959
enclosingDeclaration *ast.Node
6060
resultHasExternalModuleIndicator bool
6161
suppressNewDiagnosticContexts bool
62+
witnessedCjsExports collections.Set[string]
6263
lateStatementReplacementMap map[ast.NodeId]*ast.Node
6364
expandoHosts map[ast.NodeId]*ast.Node // store the result of transforming expando hosts so they can be inserted later if the host is actually referenced
6465
expandoMembers map[ast.NodeId][]*ast.Node // store any found expando _members_ after transforming them so *if* the host is referenced, they can be emitted alongside it
@@ -269,6 +270,7 @@ func (tx *DeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Nod
269270
tx.rawReferencedFiles = make([]ReferencedFilePair, 0)
270271
tx.rawTypeReferenceDirectives = make([]*ast.FileReference, 0)
271272
tx.rawLibReferenceDirectives = make([]*ast.FileReference, 0)
273+
tx.witnessedCjsExports.Clear()
272274
tx.state.currentSourceFile = node
273275
tx.collectFileReferences(node)
274276
tx.resolver.PrecalculateDeclarationEmitVisibility(node)
@@ -1128,6 +1130,14 @@ func (tx *DeclarationTransformer) transformExportAssignment(input *ast.Node, ass
11281130
}
11291131

11301132
func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name *ast.Node) *ast.Node {
1133+
var nameText string
1134+
if ast.IsIdentifier(name) || ast.IsStringLiteral(name) {
1135+
nameText = name.Text()
1136+
}
1137+
if tx.witnessedCjsExports.Has(nameText) && nameText != "" {
1138+
return nil // Already emitted this export name
1139+
}
1140+
tx.witnessedCjsExports.Add(nameText)
11311141
tx.resultHasExternalModuleIndicator = true
11321142
tx.resultHasScopeMarker = true
11331143
if isCommonJSAliasExport(input) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
file2.js(1,1): error TS2322: Type 'number' is not assignable to type 'string'.
2+
file2.js(5,1): error TS2322: Type 'boolean' is not assignable to type 'string'.
3+
4+
5+
==== file.js (0 errors) ====
6+
exports.foo = 42
7+
exports.foo = "hello"
8+
exports.foo = true
9+
10+
==== file2.js (2 errors) ====
11+
exports.foo = 42
12+
~~~~~~~~~~~
13+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
14+
/** @type {string} */
15+
exports.foo = "hello"
16+
/** @type {boolean} */
17+
exports.foo = true
18+
~~~~~~~~~~~
19+
!!! error TS2322: Type 'boolean' is not assignable to type 'string'.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//// [tests/cases/compiler/cjsExportsDuplication.ts] ////
2+
3+
//// [file.js]
4+
exports.foo = 42
5+
exports.foo = "hello"
6+
exports.foo = true
7+
8+
//// [file2.js]
9+
exports.foo = 42
10+
/** @type {string} */
11+
exports.foo = "hello"
12+
/** @type {boolean} */
13+
exports.foo = true
14+
15+
16+
17+
//// [file.d.ts]
18+
export declare var foo: "hello" | 42 | true;
19+
//// [file2.d.ts]
20+
export declare var foo: string;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//// [tests/cases/compiler/cjsExportsDuplication.ts] ////
2+
3+
=== file.js ===
4+
exports.foo = 42
5+
>exports.foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21))
6+
>exports : Symbol(exports, Decl(file.js, 0, 0))
7+
>foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21))
8+
9+
exports.foo = "hello"
10+
>exports.foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21))
11+
>exports : Symbol(exports, Decl(file.js, 0, 0))
12+
>foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21))
13+
14+
exports.foo = true
15+
>exports.foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21))
16+
>exports : Symbol(exports, Decl(file.js, 0, 0))
17+
>foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21))
18+
19+
=== file2.js ===
20+
exports.foo = 42
21+
>exports.foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21))
22+
>exports : Symbol(exports, Decl(file2.js, 0, 0))
23+
>foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21))
24+
25+
/** @type {string} */
26+
exports.foo = "hello"
27+
>exports.foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21))
28+
>exports : Symbol(exports, Decl(file2.js, 0, 0))
29+
>foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21))
30+
31+
/** @type {boolean} */
32+
exports.foo = true
33+
>exports.foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21))
34+
>exports : Symbol(exports, Decl(file2.js, 0, 0))
35+
>foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21))
36+
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//// [tests/cases/compiler/cjsExportsDuplication.ts] ////
2+
3+
=== file.js ===
4+
exports.foo = 42
5+
>exports.foo = 42 : 42
6+
>exports.foo : "hello" | 42 | true
7+
>exports : typeof import("./file")
8+
>foo : "hello" | 42 | true
9+
>42 : 42
10+
11+
exports.foo = "hello"
12+
>exports.foo = "hello" : "hello"
13+
>exports.foo : "hello" | 42 | true
14+
>exports : typeof import("./file")
15+
>foo : "hello" | 42 | true
16+
>"hello" : "hello"
17+
18+
exports.foo = true
19+
>exports.foo = true : true
20+
>exports.foo : "hello" | 42 | true
21+
>exports : typeof import("./file")
22+
>foo : "hello" | 42 | true
23+
>true : true
24+
25+
=== file2.js ===
26+
exports.foo = 42
27+
>exports.foo = 42 : 42
28+
>exports.foo : string
29+
>exports : typeof import("./file2")
30+
>foo : string
31+
>42 : 42
32+
33+
/** @type {string} */
34+
exports.foo = "hello"
35+
>exports.foo = "hello" : "hello"
36+
>exports.foo : string
37+
>exports : typeof import("./file2")
38+
>foo : string
39+
>"hello" : "hello"
40+
41+
/** @type {boolean} */
42+
exports.foo = true
43+
>exports.foo = true : true
44+
>exports.foo : string
45+
>exports : typeof import("./file2")
46+
>foo : string
47+
>true : true
48+

testdata/baselines/reference/compiler/numericExportNameDeclaration.js

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,3 @@ Object.defineProperty(exports, 1, {});
1111
//// [bug.d.ts]
1212
declare const _exported: any;
1313
export { _exported as "1" };
14-
declare const _exported_1: any;
15-
export { _exported_1 as "1" };
16-
declare const _exported_2: any;
17-
export { _exported_2 as "1" };
18-
19-
20-
//// [DtsFileErrors]
21-
22-
23-
bug.d.ts(2,23): error TS2300: Duplicate identifier '"1"'.
24-
bug.d.ts(4,25): error TS2300: Duplicate identifier '"1"'.
25-
bug.d.ts(6,25): error TS2300: Duplicate identifier '"1"'.
26-
27-
28-
==== bug.d.ts (3 errors) ====
29-
declare const _exported: any;
30-
export { _exported as "1" };
31-
~~~
32-
!!! error TS2300: Duplicate identifier '"1"'.
33-
declare const _exported_1: any;
34-
export { _exported_1 as "1" };
35-
~~~
36-
!!! error TS2300: Duplicate identifier '"1"'.
37-
declare const _exported_2: any;
38-
export { _exported_2 as "1" };
39-
~~~
40-
!!! error TS2300: Duplicate identifier '"1"'.
41-

testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,5 @@ apply();
2828
//// [moduleExportAliasDuplicateAlias.d.ts]
2929
export declare var apply: typeof a;
3030
declare function a(): void;
31-
export declare var apply: typeof a;
3231
//// [test.d.ts]
3332
export {};

testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias2.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,5 @@ apply();
2828
//// [moduleExportAliasDuplicateAlias.d.ts]
2929
export declare var apply: typeof a;
3030
declare function a(): void;
31-
export declare var apply: typeof a;
32-
export declare var apply: typeof a;
3331
//// [test.d.ts]
3432
export {};

testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias3.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ const result = apply.toFixed();
3333

3434
//// [moduleExportAliasDuplicateAlias.d.ts]
3535
export declare var apply: "ok" | 1 | typeof a | undefined;
36-
export declare var apply: "ok" | 1 | typeof a | undefined;
3736
declare function a(): void;
38-
export declare var apply: "ok" | 1 | typeof a | undefined;
39-
export declare var apply: "ok" | 1 | typeof a | undefined;
40-
export declare var apply: "ok" | 1 | typeof a | undefined;
4137
//// [test.d.ts]
4238
export {};

0 commit comments

Comments
 (0)