Skip to content

Commit ec8f5cf

Browse files
committed
Follow symbol through commonjs require for inferred class type
1 parent 13eeb34 commit ec8f5cf

File tree

4 files changed

+136
-9
lines changed

4 files changed

+136
-9
lines changed

src/compiler/checker.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16327,8 +16327,8 @@ namespace ts {
1632716327
* Indicates whether a declaration can be treated as a constructor in a JavaScript
1632816328
* file.
1632916329
*/
16330-
function isJavaScriptConstructor(node: Declaration): boolean {
16331-
if (isInJavaScriptFile(node)) {
16330+
function isJavaScriptConstructor(node: Declaration | undefined): boolean {
16331+
if (node && isInJavaScriptFile(node)) {
1633216332
// If the node has a @class tag, treat it like a constructor.
1633316333
if (getJSDocClassTag(node)) return true;
1633416334

@@ -16343,6 +16343,21 @@ namespace ts {
1634316343
return false;
1634416344
}
1634516345

16346+
function getJavaScriptClassType(symbol: Symbol): Type | undefined {
16347+
if (symbol && isDeclarationOfFunctionOrClassExpression(symbol)) {
16348+
symbol = getSymbolOfNode((<VariableDeclaration>symbol.valueDeclaration).initializer);
16349+
}
16350+
if (isJavaScriptConstructor(symbol.valueDeclaration)) {
16351+
return getInferredClassType(symbol);
16352+
}
16353+
if (symbol.flags & SymbolFlags.Variable) {
16354+
const valueType = getTypeOfSymbol(symbol);
16355+
if (valueType.symbol && !isInferredClassType(valueType) && isJavaScriptConstructor(valueType.symbol.valueDeclaration)) {
16356+
return getInferredClassType(valueType.symbol);
16357+
}
16358+
}
16359+
}
16360+
1634616361
function getInferredClassType(symbol: Symbol) {
1634716362
const links = getSymbolLinks(symbol);
1634816363
if (!links.inferredClassType) {
@@ -16386,16 +16401,14 @@ namespace ts {
1638616401
// in a JS file
1638716402
// Note:JS inferred classes might come from a variable declaration instead of a function declaration.
1638816403
// In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration.
16389-
let funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
16404+
const funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
1639016405
getResolvedSymbol(node.expression as Identifier) :
1639116406
checkExpression(node.expression).symbol;
16392-
if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
16393-
funcSymbol = getSymbolOfNode((<VariableDeclaration>funcSymbol.valueDeclaration).initializer);
16394-
}
16395-
if (funcSymbol && funcSymbol.flags & SymbolFlags.Function && (funcSymbol.members || getJSDocClassTag(funcSymbol.valueDeclaration))) {
16396-
return getInferredClassType(funcSymbol);
16407+
const type = funcSymbol && getJavaScriptClassType(funcSymbol);
16408+
if (type) {
16409+
return type;
1639716410
}
16398-
else if (noImplicitAny) {
16411+
if (noImplicitAny) {
1639916412
error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type);
1640016413
}
1640116414
return anyType;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/conformance/salsa/node.d.ts ===
2+
declare function require(id: string): any;
3+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
4+
>id : Symbol(id, Decl(node.d.ts, 0, 25))
5+
6+
declare var module: any, exports: any;
7+
>module : Symbol(module, Decl(node.d.ts, 1, 11))
8+
>exports : Symbol(exports, Decl(node.d.ts, 1, 24))
9+
10+
=== tests/cases/conformance/salsa/index.js ===
11+
const A = require("./other");
12+
>A : Symbol(A, Decl(index.js, 0, 5))
13+
>require : Symbol(require, Decl(node.d.ts, 0, 0))
14+
>"./other" : Symbol("tests/cases/conformance/salsa/other", Decl(other.js, 0, 0))
15+
16+
const a = new A().id;
17+
>a : Symbol(a, Decl(index.js, 1, 5))
18+
>new A().id : Symbol(A.id, Decl(other.js, 0, 14))
19+
>A : Symbol(A, Decl(index.js, 0, 5))
20+
>id : Symbol(A.id, Decl(other.js, 0, 14))
21+
22+
const B = function() { this.id = 1; }
23+
>B : Symbol(B, Decl(index.js, 3, 5))
24+
>id : Symbol(B.id, Decl(index.js, 3, 22))
25+
26+
const b = new B().id;
27+
>b : Symbol(b, Decl(index.js, 4, 5))
28+
>new B().id : Symbol(B.id, Decl(index.js, 3, 22))
29+
>B : Symbol(B, Decl(index.js, 3, 5))
30+
>id : Symbol(B.id, Decl(index.js, 3, 22))
31+
32+
=== tests/cases/conformance/salsa/other.js ===
33+
function A() { this.id = 1; }
34+
>A : Symbol(A, Decl(other.js, 0, 0))
35+
>id : Symbol(A.id, Decl(other.js, 0, 14))
36+
37+
module.exports = A;
38+
>module : Symbol(export=, Decl(other.js, 0, 29))
39+
>exports : Symbol(export=, Decl(other.js, 0, 29))
40+
>A : Symbol(A, Decl(other.js, 0, 0))
41+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
=== tests/cases/conformance/salsa/node.d.ts ===
2+
declare function require(id: string): any;
3+
>require : (id: string) => any
4+
>id : string
5+
6+
declare var module: any, exports: any;
7+
>module : any
8+
>exports : any
9+
10+
=== tests/cases/conformance/salsa/index.js ===
11+
const A = require("./other");
12+
>A : () => void
13+
>require("./other") : () => void
14+
>require : (id: string) => any
15+
>"./other" : "./other"
16+
17+
const a = new A().id;
18+
>a : number
19+
>new A().id : number
20+
>new A() : { id: number; }
21+
>A : () => void
22+
>id : number
23+
24+
const B = function() { this.id = 1; }
25+
>B : () => void
26+
>function() { this.id = 1; } : () => void
27+
>this.id = 1 : 1
28+
>this.id : any
29+
>this : any
30+
>id : any
31+
>1 : 1
32+
33+
const b = new B().id;
34+
>b : number
35+
>new B().id : number
36+
>new B() : { id: number; }
37+
>B : () => void
38+
>id : number
39+
40+
=== tests/cases/conformance/salsa/other.js ===
41+
function A() { this.id = 1; }
42+
>A : () => void
43+
>this.id = 1 : 1
44+
>this.id : any
45+
>this : any
46+
>id : any
47+
>1 : 1
48+
49+
module.exports = A;
50+
>module.exports = A : () => void
51+
>module.exports : any
52+
>module : any
53+
>exports : any
54+
>A : () => void
55+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @noEmit: true
4+
// @module: commonjs
5+
// @filename: node.d.ts
6+
declare function require(id: string): any;
7+
declare var module: any, exports: any;
8+
9+
// @filename: index.js
10+
const A = require("./other");
11+
const a = new A().id;
12+
13+
const B = function() { this.id = 1; }
14+
const b = new B().id;
15+
16+
// @filename: other.js
17+
function A() { this.id = 1; }
18+
module.exports = A;

0 commit comments

Comments
 (0)