Skip to content

Commit 7cf22f6

Browse files
authored
Fix typedef binding with CJS exports= (#826)
1 parent 6e4164e commit 7cf22f6

File tree

82 files changed

+819
-1281
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+819
-1281
lines changed

internal/checker/checker.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5539,7 +5539,7 @@ func (c *Checker) checkExternalModuleExports(node *ast.Node) {
55395539
links := c.moduleSymbolLinks.Get(moduleSymbol)
55405540
if !links.exportsChecked {
55415541
exportEqualsSymbol := moduleSymbol.Exports[ast.InternalSymbolNameExportEquals]
5542-
if exportEqualsSymbol != nil && c.hasExportedMembers(moduleSymbol) {
5542+
if exportEqualsSymbol != nil && c.hasExportedMembers(moduleSymbol, exportEqualsSymbol.ValueDeclaration.Kind == ast.KindJSExportAssignment) {
55435543
declaration := core.OrElse(c.getDeclarationOfAliasSymbol(exportEqualsSymbol), exportEqualsSymbol.ValueDeclaration)
55445544
if declaration != nil && !isTopLevelInExternalModuleAugmentation(declaration) {
55455545
c.error(declaration, diagnostics.An_export_assignment_cannot_be_used_in_a_module_with_other_exported_elements)
@@ -5575,10 +5575,17 @@ func (c *Checker) checkExternalModuleExports(node *ast.Node) {
55755575
}
55765576
}
55775577

5578-
func (c *Checker) hasExportedMembers(moduleSymbol *ast.Symbol) bool {
5578+
func (c *Checker) hasExportedMembers(moduleSymbol *ast.Symbol, isCommonJS bool) bool {
55795579
for id := range moduleSymbol.Exports {
55805580
if id != ast.InternalSymbolNameExportEquals {
5581-
return true
5581+
if !isCommonJS {
5582+
return true
5583+
}
5584+
for _, declaration := range moduleSymbol.Exports[id].Declarations {
5585+
if declaration.Kind != ast.KindJSTypeAliasDeclaration {
5586+
return true
5587+
}
5588+
}
55825589
}
55835590
}
55845591
return false
@@ -15333,13 +15340,27 @@ func (c *Checker) resolveEntityName(name *ast.Node, meaning ast.SymbolFlags, ign
1533315340
if resolveLocation == nil {
1533415341
resolveLocation = name
1533515342
}
15336-
symbol = c.getMergedSymbol(c.resolveName(resolveLocation, name.Text(), meaning, message, true /*isUse*/, false /*excludeGlobals*/))
15343+
if meaning == ast.SymbolFlagsNamespace {
15344+
symbol = c.getMergedSymbol(c.resolveName(resolveLocation, name.Text(), meaning, nil, true /*isUse*/, false /*excludeGlobals*/))
15345+
if symbol == nil {
15346+
alias := c.getMergedSymbol(c.resolveName(resolveLocation, name.Text(), ast.SymbolFlagsAlias, nil, true /*isUse*/, false /*excludeGlobals*/))
15347+
if alias != nil && alias.Name == ast.InternalSymbolNameExportEquals {
15348+
// resolve typedefs exported from commonjs, stored on the module symbol
15349+
symbol = alias.Parent
15350+
}
15351+
}
15352+
if symbol == nil && message != nil {
15353+
c.resolveName(resolveLocation, name.Text(), meaning, message, true /*isUse*/, false /*excludeGlobals*/)
15354+
}
15355+
} else {
15356+
symbol = c.getMergedSymbol(c.resolveName(resolveLocation, name.Text(), meaning, message, true /*isUse*/, false /*excludeGlobals*/))
15357+
}
1533715358
case ast.KindQualifiedName:
1533815359
qualified := name.AsQualifiedName()
15339-
symbol = c.resolveQualifiedName(name, qualified.Left, qualified.Right, meaning, ignoreErrors, dontResolveAlias, location)
15360+
symbol = c.resolveQualifiedName(name, qualified.Left, qualified.Right, meaning, ignoreErrors, location)
1534015361
case ast.KindPropertyAccessExpression:
1534115362
access := name.AsPropertyAccessExpression()
15342-
symbol = c.resolveQualifiedName(name, access.Expression, access.Name(), meaning, ignoreErrors, dontResolveAlias, location)
15363+
symbol = c.resolveQualifiedName(name, access.Expression, access.Name(), meaning, ignoreErrors, location)
1534315364
default:
1534415365
panic("Unknown entity name kind")
1534515366
}
@@ -15357,7 +15378,7 @@ func (c *Checker) resolveEntityName(name *ast.Node, meaning ast.SymbolFlags, ign
1535715378
return symbol
1535815379
}
1535915380

15360-
func (c *Checker) resolveQualifiedName(name *ast.Node, left *ast.Node, right *ast.Node, meaning ast.SymbolFlags, ignoreErrors bool, dontResolveAlias bool, location *ast.Node) *ast.Symbol {
15381+
func (c *Checker) resolveQualifiedName(name *ast.Node, left *ast.Node, right *ast.Node, meaning ast.SymbolFlags, ignoreErrors bool, location *ast.Node) *ast.Symbol {
1536115382
namespace := c.resolveEntityName(left, ast.SymbolFlagsNamespace, ignoreErrors, false /*dontResolveAlias*/, location)
1536215383
if namespace == nil || ast.NodeIsMissing(right) {
1536315384
return nil
@@ -15713,12 +15734,26 @@ func (c *Checker) getExportsOfModuleWorker(moduleSymbol *ast.Symbol) (exports as
1571315734
}
1571415735
return symbols
1571515736
}
15737+
var originalModule *ast.Symbol
15738+
if moduleSymbol != nil {
15739+
if c.resolveSymbolEx(moduleSymbol.Exports[ast.InternalSymbolNameExportEquals], false /*dontResolveAlias*/) != nil {
15740+
originalModule = moduleSymbol
15741+
}
15742+
}
1571615743
// A module defined by an 'export=' consists of one export that needs to be resolved
1571715744
moduleSymbol = c.resolveExternalModuleSymbol(moduleSymbol, false /*dontResolveAlias*/)
1571815745
exports = visit(moduleSymbol, nil, false)
1571915746
if exports == nil {
1572015747
exports = make(ast.SymbolTable)
1572115748
}
15749+
// A CommonJS module defined by an 'export=' might also export typedefs, stored on the original module
15750+
if originalModule != nil && len(originalModule.Exports) > 1 {
15751+
for _, symbol := range originalModule.Exports {
15752+
if symbol.Flags&ast.SymbolFlagsType != 0 && symbol.Name != ast.InternalSymbolNameExportEquals && exports[symbol.Name] == nil {
15753+
exports[symbol.Name] = symbol
15754+
}
15755+
}
15756+
}
1572215757
for name := range nonTypeOnlyNames.Keys() {
1572315758
delete(typeOnlyExportStarMap, name)
1572415759
}
@@ -23972,6 +24007,13 @@ func (c *Checker) getTypeFromImportTypeNode(node *ast.Node) *Type {
2397224007
symbolFromVariable = c.getPropertyOfTypeEx(c.getTypeOfSymbol(mergedResolvedSymbol), current.Text(), false /*skipObjectFunctionPropertyAugment*/, true /*includeTypeOnlyMembers*/)
2397324008
} else {
2397424009
symbolFromModule = c.getSymbol(c.getExportsOfSymbol(mergedResolvedSymbol), current.Text(), meaning)
24010+
if symbolFromModule == nil {
24011+
// a CommonJS module might have typedefs exported alongside an export=
24012+
immediateModuleSymbol := c.resolveExternalModuleSymbol(innerModuleSymbol, true /*dontResolveAlias*/)
24013+
if immediateModuleSymbol != nil && core.Some(immediateModuleSymbol.Declarations, func(d *ast.Node) bool { return d.Kind == ast.KindJSExportAssignment }) {
24014+
symbolFromModule = c.getSymbol(c.getExportsOfSymbol(immediateModuleSymbol.Parent), current.Text(), meaning)
24015+
}
24016+
}
2397524017
}
2397624018
next := core.OrElse(symbolFromModule, symbolFromVariable)
2397724019
if next == nil {

internal/fourslash/_scripts/failingTests.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,6 @@ TestIndexerReturnTypes1
326326
TestIndirectClassInstantiation
327327
TestInstanceTypesForGenericType1
328328
TestJavascriptModules20
329-
TestJavascriptModulesTypeImport
330329
TestJsDocAugments
331330
TestJsDocAugmentsAndExtends
332331
TestJsdocCallbackTag

internal/fourslash/tests/gen/javascriptModulesTypeImport_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
func TestJavascriptModulesTypeImport(t *testing.T) {
1212
t.Parallel()
13-
t.Skip()
13+
1414
defer testutil.RecoverAndFail(t, "Panic on fourslash test")
1515
const content = `// @allowJs: true
1616
// @Filename: types.js
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//// [tests/cases/conformance/salsa/typedefModuleExportsIndirect1.ts] ////
2+
3+
//// [typedefModuleExportsIndirect1.js]
4+
/** @typedef {{ a: 1, m: 1 }} C */
5+
const dummy = 0;
6+
module.exports = dummy;
7+
//// [use.js]
8+
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
9+
/** @type {C} */
10+
var c
11+
12+
13+
//// [typedefModuleExportsIndirect1.js]
14+
"use strict";
15+
/** @typedef {{ a: 1, m: 1 }} C */
16+
const dummy = 0;
17+
module.exports = dummy;
18+
//// [use.js]
19+
"use strict";
20+
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
21+
/** @type {C} */
22+
var c;
23+
24+
25+
//// [typedefModuleExportsIndirect1.d.ts]
26+
export type C = {
27+
a: 1;
28+
m: 1;
29+
};
30+
export = dummy;
31+
//// [use.d.ts]
32+
type C = import('./typedefModuleExportsIndirect1').C;
33+
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
34+
/** @type {C} */
35+
declare var c: C;
36+
37+
38+
//// [DtsFileErrors]
39+
40+
41+
dist/typedefModuleExportsIndirect1.d.ts(5,1): error TS2309: An export assignment cannot be used in a module with other exported elements.
42+
dist/typedefModuleExportsIndirect1.d.ts(5,10): error TS2304: Cannot find name 'dummy'.
43+
dist/use.d.ts(1,52): error TS2694: Namespace 'unknown' has no exported member 'C'.
44+
45+
46+
==== dist/typedefModuleExportsIndirect1.d.ts (2 errors) ====
47+
export type C = {
48+
a: 1;
49+
m: 1;
50+
};
51+
export = dummy;
52+
~~~~~~~~~~~~~~~
53+
!!! error TS2309: An export assignment cannot be used in a module with other exported elements.
54+
~~~~~
55+
!!! error TS2304: Cannot find name 'dummy'.
56+
57+
==== dist/use.d.ts (1 errors) ====
58+
type C = import('./typedefModuleExportsIndirect1').C;
59+
~
60+
!!! error TS2694: Namespace 'unknown' has no exported member 'C'.
61+
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
62+
/** @type {C} */
63+
declare var c: C;
64+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [tests/cases/conformance/salsa/typedefModuleExportsIndirect1.ts] ////
2+
3+
=== typedefModuleExportsIndirect1.js ===
4+
/** @typedef {{ a: 1, m: 1 }} C */
5+
const dummy = 0;
6+
>dummy : Symbol(dummy, Decl(typedefModuleExportsIndirect1.js, 1, 5))
7+
8+
module.exports = dummy;
9+
>module.exports : Symbol(dummy, Decl(typedefModuleExportsIndirect1.js, 1, 5))
10+
>module : Symbol("typedefModuleExportsIndirect1", Decl(typedefModuleExportsIndirect1.js, 0, 0))
11+
>exports : Symbol(dummy, Decl(typedefModuleExportsIndirect1.js, 1, 5))
12+
>dummy : Symbol(dummy, Decl(typedefModuleExportsIndirect1.js, 1, 5))
13+
14+
=== use.js ===
15+
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
16+
/** @type {C} */
17+
var c
18+
>c : Symbol(c, Decl(use.js, 2, 3))
19+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [tests/cases/conformance/salsa/typedefModuleExportsIndirect1.ts] ////
2+
3+
=== typedefModuleExportsIndirect1.js ===
4+
/** @typedef {{ a: 1, m: 1 }} C */
5+
const dummy = 0;
6+
>dummy : 0
7+
>0 : 0
8+
9+
module.exports = dummy;
10+
>module.exports = dummy : 0
11+
>module.exports : 0
12+
>module : { readonly dummy: 0; }
13+
>exports : 0
14+
>dummy : 0
15+
16+
=== use.js ===
17+
/** @typedef {import('./typedefModuleExportsIndirect1').C} C */
18+
/** @type {C} */
19+
var c
20+
>c : import("typedefModuleExportsIndirect1").C
21+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//// [tests/cases/conformance/salsa/typedefModuleExportsIndirect2.ts] ////
2+
3+
//// [typedefModuleExportsIndirect2.js]
4+
/** @typedef {{ a: 1, m: 1 }} C */
5+
const f = function() {};
6+
module.exports = f;
7+
//// [use.js]
8+
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
9+
/** @type {C} */
10+
var c
11+
12+
13+
//// [typedefModuleExportsIndirect2.js]
14+
"use strict";
15+
/** @typedef {{ a: 1, m: 1 }} C */
16+
const f = function () { };
17+
module.exports = f;
18+
//// [use.js]
19+
"use strict";
20+
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
21+
/** @type {C} */
22+
var c;
23+
24+
25+
//// [typedefModuleExportsIndirect2.d.ts]
26+
export type C = {
27+
a: 1;
28+
m: 1;
29+
};
30+
export = f;
31+
//// [use.d.ts]
32+
type C = import('./typedefModuleExportsIndirect2').C;
33+
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
34+
/** @type {C} */
35+
declare var c: C;
36+
37+
38+
//// [DtsFileErrors]
39+
40+
41+
dist/typedefModuleExportsIndirect2.d.ts(5,1): error TS2309: An export assignment cannot be used in a module with other exported elements.
42+
dist/typedefModuleExportsIndirect2.d.ts(5,10): error TS2304: Cannot find name 'f'.
43+
dist/use.d.ts(1,52): error TS2694: Namespace 'unknown' has no exported member 'C'.
44+
45+
46+
==== dist/typedefModuleExportsIndirect2.d.ts (2 errors) ====
47+
export type C = {
48+
a: 1;
49+
m: 1;
50+
};
51+
export = f;
52+
~~~~~~~~~~~
53+
!!! error TS2309: An export assignment cannot be used in a module with other exported elements.
54+
~
55+
!!! error TS2304: Cannot find name 'f'.
56+
57+
==== dist/use.d.ts (1 errors) ====
58+
type C = import('./typedefModuleExportsIndirect2').C;
59+
~
60+
!!! error TS2694: Namespace 'unknown' has no exported member 'C'.
61+
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
62+
/** @type {C} */
63+
declare var c: C;
64+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [tests/cases/conformance/salsa/typedefModuleExportsIndirect2.ts] ////
2+
3+
=== typedefModuleExportsIndirect2.js ===
4+
/** @typedef {{ a: 1, m: 1 }} C */
5+
const f = function() {};
6+
>f : Symbol(f, Decl(typedefModuleExportsIndirect2.js, 1, 5))
7+
8+
module.exports = f;
9+
>module.exports : Symbol(f, Decl(typedefModuleExportsIndirect2.js, 1, 5))
10+
>module : Symbol("typedefModuleExportsIndirect2", Decl(typedefModuleExportsIndirect2.js, 0, 0))
11+
>exports : Symbol(f, Decl(typedefModuleExportsIndirect2.js, 1, 5))
12+
>f : Symbol(f, Decl(typedefModuleExportsIndirect2.js, 1, 5))
13+
14+
=== use.js ===
15+
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
16+
/** @type {C} */
17+
var c
18+
>c : Symbol(c, Decl(use.js, 2, 3))
19+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [tests/cases/conformance/salsa/typedefModuleExportsIndirect2.ts] ////
2+
3+
=== typedefModuleExportsIndirect2.js ===
4+
/** @typedef {{ a: 1, m: 1 }} C */
5+
const f = function() {};
6+
>f : () => void
7+
>function() {} : () => void
8+
9+
module.exports = f;
10+
>module.exports = f : () => void
11+
>module.exports : () => void
12+
>module : { readonly f: () => void; }
13+
>exports : () => void
14+
>f : () => void
15+
16+
=== use.js ===
17+
/** @typedef {import('./typedefModuleExportsIndirect2').C} C */
18+
/** @type {C} */
19+
var c
20+
>c : import("typedefModuleExportsIndirect2").C
21+

0 commit comments

Comments
 (0)