Skip to content

Commit 155610e

Browse files
authored
Better support class instances assigned to the module object for JS declarations (microsoft#40037)
* Better support class instances assigned to the module object for JS declarations * Extract constant
1 parent 09d68ef commit 155610e

20 files changed

+291
-58
lines changed

src/compiler/checker.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -4250,6 +4250,10 @@ namespace ts {
42504250
return flags & TypeFormatFlags.NodeBuilderFlagsMask;
42514251
}
42524252

4253+
function isClassInstanceSide(type: Type) {
4254+
return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone));
4255+
}
4256+
42534257
function createNodeBuilder() {
42544258
return {
42554259
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
@@ -4547,16 +4551,16 @@ namespace ts {
45474551
const typeId = type.id;
45484552
const symbol = type.symbol;
45494553
if (symbol) {
4554+
const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value;
45504555
if (isJSConstructor(symbol.valueDeclaration)) {
45514556
// Instance and static types share the same symbol; only add 'typeof' for the static side.
4552-
const isInstanceType = type === getDeclaredTypeOfClassOrInterface(symbol) ? SymbolFlags.Type : SymbolFlags.Value;
45534557
return symbolToTypeNode(symbol, context, isInstanceType);
45544558
}
45554559
// Always use 'typeof T' for type of class, enum, and module objects
45564560
else if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) ||
45574561
symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) ||
45584562
shouldWriteTypeOfFunctionSymbol()) {
4559-
return symbolToTypeNode(symbol, context, SymbolFlags.Value);
4563+
return symbolToTypeNode(symbol, context, isInstanceType);
45604564
}
45614565
else if (context.visitedTypes?.has(typeId)) {
45624566
// If type is an anonymous type literal in a type alias declaration, use type alias name
@@ -6600,7 +6604,7 @@ namespace ts {
66006604

66016605
function isNamespaceMember(p: Symbol) {
66026606
return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) ||
6603-
!(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && isClassLike(p.valueDeclaration.parent));
6607+
!(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && getEffectiveModifierFlags(p.valueDeclaration) & ModifierFlags.Static && isClassLike(p.valueDeclaration.parent));
66046608
}
66056609

66066610
function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) {
@@ -6935,7 +6939,8 @@ namespace ts {
69356939
return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) &&
69366940
!getIndexInfoOfType(typeToSerialize, IndexKind.String) &&
69376941
!getIndexInfoOfType(typeToSerialize, IndexKind.Number) &&
6938-
!!(length(getPropertiesOfType(typeToSerialize)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) &&
6942+
!isClassInstanceSide(typeToSerialize) && // While a class instance is potentially representable as a NS, prefer printing a reference to the instance type and serializing the class
6943+
!!(length(filter(getPropertiesOfType(typeToSerialize), isNamespaceMember)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) &&
69396944
!length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK
69406945
!getDeclarationWithTypeAnnotation(hostSymbol, enclosingDeclaration) &&
69416946
!(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) &&
@@ -8188,6 +8193,7 @@ namespace ts {
81888193
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
81898194
const members = createSymbolTable();
81908195
copyEntries(exportedType.members, members);
8196+
const initialSize = members.size;
81918197
if (resolvedSymbol && !resolvedSymbol.exports) {
81928198
resolvedSymbol.exports = createSymbolTable();
81938199
}
@@ -8230,13 +8236,16 @@ namespace ts {
82308236
}
82318237
});
82328238
const result = createAnonymousType(
8233-
exportedType.symbol,
8239+
initialSize !== members.size ? undefined : exportedType.symbol, // Only set the type's symbol if it looks to be the same as the original type
82348240
members,
82358241
exportedType.callSignatures,
82368242
exportedType.constructSignatures,
82378243
exportedType.stringIndexInfo,
82388244
exportedType.numberIndexInfo);
82398245
result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag
8246+
if (result.symbol && result.symbol.flags & SymbolFlags.Class && type === getDeclaredTypeOfClassOrInterface(result.symbol)) {
8247+
result.objectFlags |= ObjectFlags.IsClassInstanceClone; // Propagate the knowledge that this type is equivalent to the symbol's class instance type
8248+
}
82408249
return result;
82418250
}
82428251
if (isEmptyArrayLiteralType(type)) {

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5017,6 +5017,8 @@ namespace ts {
50175017
IsNeverIntersectionComputed = 1 << 28, // IsNeverLike flag has been computed
50185018
/* @internal */
50195019
IsNeverIntersection = 1 << 29, // Intersection reduces to never
5020+
/* @internal */
5021+
IsClassInstanceClone = 1 << 30, // Type is a clone of a class instance type
50205022
ClassOrInterface = Class | Interface,
50215023
/* @internal */
50225024
RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//// [index.js]
2+
class Foo {}
3+
4+
module.exports = new Foo();
5+
6+
//// [index.js]
7+
var Foo = /** @class */ (function () {
8+
function Foo() {
9+
}
10+
return Foo;
11+
}());
12+
module.exports = new Foo();
13+
14+
15+
//// [index.d.ts]
16+
declare const _exports: Foo;
17+
export = _exports;
18+
declare class Foo {
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
class Foo {}
3+
>Foo : Symbol(Foo, Decl(index.js, 0, 0))
4+
5+
module.exports = new Foo();
6+
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
7+
>module : Symbol(export=, Decl(index.js, 0, 12))
8+
>exports : Symbol(export=, Decl(index.js, 0, 12))
9+
>Foo : Symbol(Foo, Decl(index.js, 0, 0))
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
class Foo {}
3+
>Foo : Foo
4+
5+
module.exports = new Foo();
6+
>module.exports = new Foo() : Foo
7+
>module.exports : Foo
8+
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": Foo; }
9+
>exports : Foo
10+
>new Foo() : Foo
11+
>Foo : typeof Foo
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [index.js]
2+
class Foo {
3+
static stat = 10;
4+
member = 10;
5+
}
6+
7+
module.exports = new Foo();
8+
9+
//// [index.js]
10+
var Foo = /** @class */ (function () {
11+
function Foo() {
12+
this.member = 10;
13+
}
14+
Foo.stat = 10;
15+
return Foo;
16+
}());
17+
module.exports = new Foo();
18+
19+
20+
//// [index.d.ts]
21+
declare const _exports: Foo;
22+
export = _exports;
23+
declare class Foo {
24+
static stat: number;
25+
member: number;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
class Foo {
3+
>Foo : Symbol(Foo, Decl(index.js, 0, 0))
4+
5+
static stat = 10;
6+
>stat : Symbol(Foo.stat, Decl(index.js, 0, 11))
7+
8+
member = 10;
9+
>member : Symbol(Foo.member, Decl(index.js, 1, 21))
10+
}
11+
12+
module.exports = new Foo();
13+
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
14+
>module : Symbol(export=, Decl(index.js, 3, 1))
15+
>exports : Symbol(export=, Decl(index.js, 3, 1))
16+
>Foo : Symbol(Foo, Decl(index.js, 0, 0))
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
class Foo {
3+
>Foo : Foo
4+
5+
static stat = 10;
6+
>stat : number
7+
>10 : 10
8+
9+
member = 10;
10+
>member : number
11+
>10 : 10
12+
}
13+
14+
module.exports = new Foo();
15+
>module.exports = new Foo() : Foo
16+
>module.exports : Foo
17+
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": Foo; }
18+
>exports : Foo
19+
>new Foo() : Foo
20+
>Foo : typeof Foo
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [index.js]
2+
class Foo {
3+
static stat = 10;
4+
member = 10;
5+
}
6+
7+
module.exports = new Foo();
8+
9+
module.exports.additional = 20;
10+
11+
//// [index.js]
12+
var Foo = /** @class */ (function () {
13+
function Foo() {
14+
this.member = 10;
15+
}
16+
Foo.stat = 10;
17+
return Foo;
18+
}());
19+
module.exports = new Foo();
20+
module.exports.additional = 20;
21+
22+
23+
//// [index.d.ts]
24+
export const member: number;
25+
export const additional: number;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
class Foo {
3+
>Foo : Symbol(Foo, Decl(index.js, 0, 0))
4+
5+
static stat = 10;
6+
>stat : Symbol(Foo.stat, Decl(index.js, 0, 11))
7+
8+
member = 10;
9+
>member : Symbol(Foo.member, Decl(index.js, 1, 21))
10+
}
11+
12+
module.exports = new Foo();
13+
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
14+
>module : Symbol(export=, Decl(index.js, 3, 1))
15+
>exports : Symbol(export=, Decl(index.js, 3, 1))
16+
>Foo : Symbol(Foo, Decl(index.js, 0, 0))
17+
18+
module.exports.additional = 20;
19+
>module.exports.additional : Symbol(additional, Decl(index.js, 5, 27))
20+
>module.exports : Symbol(additional, Decl(index.js, 5, 27))
21+
>module : Symbol(module, Decl(index.js, 3, 1))
22+
>exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
23+
>additional : Symbol(additional, Decl(index.js, 5, 27))
24+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/conformance/jsdoc/declarations/index.js ===
2+
class Foo {
3+
>Foo : Foo
4+
5+
static stat = 10;
6+
>stat : number
7+
>10 : 10
8+
9+
member = 10;
10+
>member : number
11+
>10 : 10
12+
}
13+
14+
module.exports = new Foo();
15+
>module.exports = new Foo() : { member: number; additional: number; }
16+
>module.exports : { member: number; additional: number; }
17+
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": { member: number; additional: number; }; }
18+
>exports : { member: number; additional: number; }
19+
>new Foo() : Foo
20+
>Foo : typeof Foo
21+
22+
module.exports.additional = 20;
23+
>module.exports.additional = 20 : 20
24+
>module.exports.additional : number
25+
>module.exports : { member: number; additional: number; }
26+
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": { member: number; additional: number; }; }
27+
>exports : { member: number; additional: number; }
28+
>additional : number
29+
>20 : 20
30+

tests/baselines/reference/jsDeclarationsExportAssignedConstructorFunctionWithSub.errors.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub.js(4,1): error TS9005: Declaration emit for this file requires using private name 'Sub'. An explicit type annotation may unblock declaration emit.
12
tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub.js(4,1): error TS9005: Declaration emit for this file requires using private name 'exports'. An explicit type annotation may unblock declaration emit.
23

34

4-
==== tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub.js (1 errors) ====
5+
==== tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub.js (2 errors) ====
56
/**
67
* @param {number} p
78
*/
89
module.exports = function (p) {
910
~~~~~~
11+
!!! error TS9005: Declaration emit for this file requires using private name 'Sub'. An explicit type annotation may unblock declaration emit.
12+
~~~~~~
1013
!!! error TS9005: Declaration emit for this file requires using private name 'exports'. An explicit type annotation may unblock declaration emit.
1114
this.t = 12 + p;
1215
}

tests/baselines/reference/jsDeclarationsExportAssignedConstructorFunctionWithSub.types

+13-13
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
* @param {number} p
44
*/
55
module.exports = function (p) {
6-
>module.exports = function (p) { this.t = 12 + p;} : typeof exports
7-
>module.exports : typeof exports
8-
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
9-
>exports : typeof exports
6+
>module.exports = function (p) { this.t = 12 + p;} : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
7+
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
8+
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
9+
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
1010
>function (p) { this.t = 12 + p;} : typeof exports
1111
>p : number
1212

@@ -22,9 +22,9 @@ module.exports = function (p) {
2222
module.exports.Sub = function() {
2323
>module.exports.Sub = function() { this.instance = new module.exports(10);} : typeof Sub
2424
>module.exports.Sub : typeof Sub
25-
>module.exports : typeof exports
26-
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
27-
>exports : typeof exports
25+
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
26+
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
27+
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
2828
>Sub : typeof Sub
2929
>function() { this.instance = new module.exports(10);} : typeof Sub
3030

@@ -34,18 +34,18 @@ module.exports.Sub = function() {
3434
>this : this
3535
>instance : any
3636
>new module.exports(10) : exports
37-
>module.exports : typeof exports
38-
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
39-
>exports : typeof exports
37+
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
38+
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
39+
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
4040
>10 : 10
4141
}
4242
module.exports.Sub.prototype = { }
4343
>module.exports.Sub.prototype = { } : {}
4444
>module.exports.Sub.prototype : {}
4545
>module.exports.Sub : typeof Sub
46-
>module.exports : typeof exports
47-
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
48-
>exports : typeof exports
46+
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
47+
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
48+
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
4949
>Sub : typeof Sub
5050
>prototype : {}
5151
>{ } : {}

tests/baselines/reference/jsDeclarationsFunctionClassesCjsExportAssignment.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ Context.prototype = {
129129
}
130130
}
131131
module.exports = Context;
132-
>module.exports = Context : typeof Context
133-
>module.exports : typeof Context
134-
>module : { "\"tests/cases/conformance/jsdoc/declarations/context\"": typeof Context; }
135-
>exports : typeof Context
132+
>module.exports = Context : { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }
133+
>module.exports : { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }
134+
>module : { "\"tests/cases/conformance/jsdoc/declarations/context\"": { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }; }
135+
>exports : { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }
136136
>Context : typeof Context
137137

tests/baselines/reference/jsdocTypeReferenceToImportOfFunctionExpression.types

+7-7
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ const MW = require("./MW");
99

1010
/** @class */
1111
module.exports = function MC() {
12-
>module.exports = function MC() { /** @type {any} */ var x = {} return new MW(x);} : typeof MC
13-
>module.exports : typeof MC
14-
>module : { "\"tests/cases/conformance/jsdoc/MC\"": typeof MC; }
15-
>exports : typeof MC
12+
>module.exports = function MC() { /** @type {any} */ var x = {} return new MW(x);} : { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }
13+
>module.exports : { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }
14+
>module : { "\"tests/cases/conformance/jsdoc/MC\"": { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }; }
15+
>exports : { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }
1616
>function MC() { /** @type {any} */ var x = {} return new MW(x);} : typeof MC
1717
>MC : typeof MC
1818

@@ -38,14 +38,14 @@ class MW {
3838
* @param {MC} compiler the compiler
3939
*/
4040
constructor(compiler) {
41-
>compiler : typeof MC
41+
>compiler : { (): MW; new (): MC; }
4242

4343
this.compiler = compiler;
44-
>this.compiler = compiler : typeof MC
44+
>this.compiler = compiler : { (): MW; new (): MC; }
4545
>this.compiler : any
4646
>this : this
4747
>compiler : any
48-
>compiler : typeof MC
48+
>compiler : { (): MW; new (): MC; }
4949
}
5050
}
5151

0 commit comments

Comments
 (0)