Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better support class instances assigned to the module object for JS declarations #40037

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4208,6 +4208,10 @@ namespace ts {
return flags & TypeFormatFlags.NodeBuilderFlagsMask;
}

function isClassInstanceSide(type: Type) {
return !!type.symbol && !!(type.symbol.flags & SymbolFlags.Class) && (type === getDeclaredTypeOfClassOrInterface(type.symbol) || !!(getObjectFlags(type) & ObjectFlags.IsClassInstanceClone));
}

function createNodeBuilder() {
return {
typeToTypeNode: (type: Type, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) =>
Expand Down Expand Up @@ -4505,16 +4509,16 @@ namespace ts {
const typeId = type.id;
const symbol = type.symbol;
if (symbol) {
const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value;
if (isJSConstructor(symbol.valueDeclaration)) {
// Instance and static types share the same symbol; only add 'typeof' for the static side.
const isInstanceType = type === getDeclaredTypeOfClassOrInterface(symbol) ? SymbolFlags.Type : SymbolFlags.Value;
return symbolToTypeNode(symbol, context, isInstanceType);
}
// Always use 'typeof T' for type of class, enum, and module objects
else if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) && !(symbol.valueDeclaration.kind === SyntaxKind.ClassExpression && context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral) ||
symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule) ||
shouldWriteTypeOfFunctionSymbol()) {
return symbolToTypeNode(symbol, context, SymbolFlags.Value);
return symbolToTypeNode(symbol, context, isInstanceType);
}
else if (context.visitedTypes?.has(typeId)) {
// If type is an anonymous type literal in a type alias declaration, use type alias name
Expand Down Expand Up @@ -6541,7 +6545,7 @@ namespace ts {

function isNamespaceMember(p: Symbol) {
return !!(p.flags & (SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias)) ||
!(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && isClassLike(p.valueDeclaration.parent));
!(p.flags & SymbolFlags.Prototype || p.escapedName === "prototype" || p.valueDeclaration && getEffectiveModifierFlags(p.valueDeclaration) & ModifierFlags.Static && isClassLike(p.valueDeclaration.parent));
}

function serializeAsClass(symbol: Symbol, localName: string, modifierFlags: ModifierFlags) {
Expand Down Expand Up @@ -6843,7 +6847,8 @@ namespace ts {
return getObjectFlags(typeToSerialize) & (ObjectFlags.Anonymous | ObjectFlags.Mapped) &&
!getIndexInfoOfType(typeToSerialize, IndexKind.String) &&
!getIndexInfoOfType(typeToSerialize, IndexKind.Number) &&
!!(length(getPropertiesOfType(typeToSerialize)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) &&
!isClassInstanceSide(typeToSerialize) && // While a class instance is potentially representable as a NS, prefer printing a reference to the instance type and serializing the class
!!(length(filter(getPropertiesOfType(typeToSerialize), isNamespaceMember)) || length(getSignaturesOfType(typeToSerialize, SignatureKind.Call))) &&
!length(getSignaturesOfType(typeToSerialize, SignatureKind.Construct)) && // TODO: could probably serialize as function + ns + class, now that that's OK
!getDeclarationWithTypeAnnotation(hostSymbol, enclosingDeclaration) &&
!(typeToSerialize.symbol && some(typeToSerialize.symbol.declarations, d => getSourceFileOfNode(d) !== ctxSrc)) &&
Expand Down Expand Up @@ -8096,6 +8101,7 @@ namespace ts {
const exportedType = resolveStructuredTypeMembers(type as ObjectType);
const members = createSymbolTable();
copyEntries(exportedType.members, members);
const initialSize = members.size;
if (resolvedSymbol && !resolvedSymbol.exports) {
resolvedSymbol.exports = createSymbolTable();
}
Expand Down Expand Up @@ -8138,13 +8144,16 @@ namespace ts {
}
});
const result = createAnonymousType(
exportedType.symbol,
initialSize !== members.size ? undefined : exportedType.symbol, // Only set the type's symbol if it looks to be the same as the original type
members,
exportedType.callSignatures,
exportedType.constructSignatures,
exportedType.stringIndexInfo,
exportedType.numberIndexInfo);
result.objectFlags |= (getObjectFlags(type) & ObjectFlags.JSLiteral); // Propagate JSLiteral flag
if (result.symbol && result.symbol.flags & SymbolFlags.Class && type === getDeclaredTypeOfClassOrInterface(result.symbol)) {
result.objectFlags |= ObjectFlags.IsClassInstanceClone; // Propagate the knowledge that this type is equivalent to the symbol's class instance type
}
return result;
}
if (isEmptyArrayLiteralType(type)) {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5016,6 +5016,8 @@ namespace ts {
IsNeverIntersectionComputed = 1 << 28, // IsNeverLike flag has been computed
/* @internal */
IsNeverIntersection = 1 << 29, // Intersection reduces to never
/* @internal */
IsClassInstanceClone = 1 << 30, // Type is a clone of a class instance type
ClassOrInterface = Class | Interface,
/* @internal */
RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//// [index.js]
class Foo {}

module.exports = new Foo();

//// [index.js]
var Foo = /** @class */ (function () {
function Foo() {
}
return Foo;
}());
module.exports = new Foo();


//// [index.d.ts]
declare const _exports: Foo;
export = _exports;
declare class Foo {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
class Foo {}
>Foo : Symbol(Foo, Decl(index.js, 0, 0))

module.exports = new Foo();
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>module : Symbol(export=, Decl(index.js, 0, 12))
>exports : Symbol(export=, Decl(index.js, 0, 12))
>Foo : Symbol(Foo, Decl(index.js, 0, 0))

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
class Foo {}
>Foo : Foo

module.exports = new Foo();
>module.exports = new Foo() : Foo
>module.exports : Foo
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": Foo; }
>exports : Foo
>new Foo() : Foo
>Foo : typeof Foo

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//// [index.js]
class Foo {
static stat = 10;
member = 10;
}

module.exports = new Foo();

//// [index.js]
var Foo = /** @class */ (function () {
function Foo() {
this.member = 10;
}
Foo.stat = 10;
return Foo;
}());
module.exports = new Foo();


//// [index.d.ts]
declare const _exports: Foo;
export = _exports;
declare class Foo {
static stat: number;
member: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
class Foo {
>Foo : Symbol(Foo, Decl(index.js, 0, 0))

static stat = 10;
>stat : Symbol(Foo.stat, Decl(index.js, 0, 11))

member = 10;
>member : Symbol(Foo.member, Decl(index.js, 1, 21))
}

module.exports = new Foo();
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>module : Symbol(export=, Decl(index.js, 3, 1))
>exports : Symbol(export=, Decl(index.js, 3, 1))
>Foo : Symbol(Foo, Decl(index.js, 0, 0))

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
class Foo {
>Foo : Foo

static stat = 10;
>stat : number
>10 : 10

member = 10;
>member : number
>10 : 10
}

module.exports = new Foo();
>module.exports = new Foo() : Foo
>module.exports : Foo
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": Foo; }
>exports : Foo
>new Foo() : Foo
>Foo : typeof Foo

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [index.js]
class Foo {
static stat = 10;
member = 10;
}

module.exports = new Foo();

module.exports.additional = 20;

//// [index.js]
var Foo = /** @class */ (function () {
function Foo() {
this.member = 10;
}
Foo.stat = 10;
return Foo;
}());
module.exports = new Foo();
module.exports.additional = 20;


//// [index.d.ts]
export const member: number;
export const additional: number;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
class Foo {
>Foo : Symbol(Foo, Decl(index.js, 0, 0))

static stat = 10;
>stat : Symbol(Foo.stat, Decl(index.js, 0, 11))

member = 10;
>member : Symbol(Foo.member, Decl(index.js, 1, 21))
}

module.exports = new Foo();
>module.exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>module : Symbol(export=, Decl(index.js, 3, 1))
>exports : Symbol(export=, Decl(index.js, 3, 1))
>Foo : Symbol(Foo, Decl(index.js, 0, 0))

module.exports.additional = 20;
>module.exports.additional : Symbol(additional, Decl(index.js, 5, 27))
>module.exports : Symbol(additional, Decl(index.js, 5, 27))
>module : Symbol(module, Decl(index.js, 3, 1))
>exports : Symbol("tests/cases/conformance/jsdoc/declarations/index", Decl(index.js, 0, 0))
>additional : Symbol(additional, Decl(index.js, 5, 27))

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
=== tests/cases/conformance/jsdoc/declarations/index.js ===
class Foo {
>Foo : Foo

static stat = 10;
>stat : number
>10 : 10

member = 10;
>member : number
>10 : 10
}

module.exports = new Foo();
>module.exports = new Foo() : { member: number; additional: number; }
>module.exports : { member: number; additional: number; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": { member: number; additional: number; }; }
>exports : { member: number; additional: number; }
>new Foo() : Foo
>Foo : typeof Foo

module.exports.additional = 20;
>module.exports.additional = 20 : 20
>module.exports.additional : number
>module.exports : { member: number; additional: number; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/index\"": { member: number; additional: number; }; }
>exports : { member: number; additional: number; }
>additional : number
>20 : 20

Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
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.
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.


==== tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub.js (1 errors) ====
==== tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub.js (2 errors) ====
/**
* @param {number} p
*/
module.exports = function (p) {
~~~~~~
!!! error TS9005: Declaration emit for this file requires using private name 'Sub'. An explicit type annotation may unblock declaration emit.
~~~~~~
!!! error TS9005: Declaration emit for this file requires using private name 'exports'. An explicit type annotation may unblock declaration emit.
this.t = 12 + p;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
* @param {number} p
*/
module.exports = function (p) {
>module.exports = function (p) { this.t = 12 + p;} : typeof exports
>module.exports : typeof exports
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
>exports : typeof exports
>module.exports = function (p) { this.t = 12 + p;} : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>function (p) { this.t = 12 + p;} : typeof exports
>p : number

Expand All @@ -22,9 +22,9 @@ module.exports = function (p) {
module.exports.Sub = function() {
>module.exports.Sub = function() { this.instance = new module.exports(10);} : typeof Sub
>module.exports.Sub : typeof Sub
>module.exports : typeof exports
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
>exports : typeof exports
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>Sub : typeof Sub
>function() { this.instance = new module.exports(10);} : typeof Sub

Expand All @@ -34,18 +34,18 @@ module.exports.Sub = function() {
>this : this
>instance : any
>new module.exports(10) : exports
>module.exports : typeof exports
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
>exports : typeof exports
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>10 : 10
}
module.exports.Sub.prototype = { }
>module.exports.Sub.prototype = { } : {}
>module.exports.Sub.prototype : {}
>module.exports.Sub : typeof Sub
>module.exports : typeof exports
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": typeof exports; }
>exports : typeof exports
>module.exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/jsDeclarationsExportAssignedConstructorFunctionWithSub\"": { (p: number): void; new (p: number): exports; Sub: typeof Sub; }; }
>exports : { (p: number): void; new (p: number): exports; Sub: typeof Sub; }
>Sub : typeof Sub
>prototype : {}
>{ } : {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,9 @@ Context.prototype = {
}
}
module.exports = Context;
>module.exports = Context : typeof Context
>module.exports : typeof Context
>module : { "\"tests/cases/conformance/jsdoc/declarations/context\"": typeof Context; }
>exports : typeof Context
>module.exports = Context : { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }
>module.exports : { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }
>module : { "\"tests/cases/conformance/jsdoc/declarations/context\"": { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }; }
>exports : { (input: Input): Context; new (input: Input): Context; prototype: { construct(input: Input, handle?: (arg: Context) => void): State; }; }
>Context : typeof Context

Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ const MW = require("./MW");

/** @class */
module.exports = function MC() {
>module.exports = function MC() { /** @type {any} */ var x = {} return new MW(x);} : typeof MC
>module.exports : typeof MC
>module : { "\"tests/cases/conformance/jsdoc/MC\"": typeof MC; }
>exports : typeof MC
>module.exports = function MC() { /** @type {any} */ var x = {} return new MW(x);} : { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }
>module.exports : { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }
>module : { "\"tests/cases/conformance/jsdoc/MC\"": { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }; }
>exports : { (): import("tests/cases/conformance/jsdoc/MW"); new (): MC; }
>function MC() { /** @type {any} */ var x = {} return new MW(x);} : typeof MC
>MC : typeof MC

Expand All @@ -38,14 +38,14 @@ class MW {
* @param {MC} compiler the compiler
*/
constructor(compiler) {
>compiler : typeof MC
>compiler : { (): MW; new (): MC; }

this.compiler = compiler;
>this.compiler = compiler : typeof MC
>this.compiler = compiler : { (): MW; new (): MC; }
>this.compiler : any
>this : this
>compiler : any
>compiler : typeof MC
>compiler : { (): MW; new (): MC; }
}
}

Expand Down
Loading