Skip to content

Commit 115884a

Browse files
committed
Follow symbol through commonjs require for inferred class type
1 parent f1fb1b9 commit 115884a

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
@@ -16073,8 +16073,8 @@ namespace ts {
1607316073
* Indicates whether a declaration can be treated as a constructor in a JavaScript
1607416074
* file.
1607516075
*/
16076-
function isJavaScriptConstructor(node: Declaration): boolean {
16077-
if (isInJavaScriptFile(node)) {
16076+
function isJavaScriptConstructor(node: Declaration | undefined): boolean {
16077+
if (node && isInJavaScriptFile(node)) {
1607816078
// If the node has a @class tag, treat it like a constructor.
1607916079
if (getJSDocClassTag(node)) return true;
1608016080

@@ -16089,6 +16089,21 @@ namespace ts {
1608916089
return false;
1609016090
}
1609116091

16092+
function getJavaScriptClassType(symbol: Symbol): Type | undefined {
16093+
if (symbol && isDeclarationOfFunctionOrClassExpression(symbol)) {
16094+
symbol = getSymbolOfNode((<VariableDeclaration>symbol.valueDeclaration).initializer);
16095+
}
16096+
if (isJavaScriptConstructor(symbol.valueDeclaration)) {
16097+
return getInferredClassType(symbol);
16098+
}
16099+
if (symbol.flags & SymbolFlags.Variable) {
16100+
const valueType = getTypeOfSymbol(symbol);
16101+
if (valueType.symbol && !isInferredClassType(valueType) && isJavaScriptConstructor(valueType.symbol.valueDeclaration)) {
16102+
return getInferredClassType(valueType.symbol);
16103+
}
16104+
}
16105+
}
16106+
1609216107
function getInferredClassType(symbol: Symbol) {
1609316108
const links = getSymbolLinks(symbol);
1609416109
if (!links.inferredClassType) {
@@ -16132,16 +16147,14 @@ namespace ts {
1613216147
// in a JS file
1613316148
// Note:JS inferred classes might come from a variable declaration instead of a function declaration.
1613416149
// In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration.
16135-
let funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
16150+
const funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
1613616151
getResolvedSymbol(node.expression as Identifier) :
1613716152
checkExpression(node.expression).symbol;
16138-
if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
16139-
funcSymbol = getSymbolOfNode((<VariableDeclaration>funcSymbol.valueDeclaration).initializer);
16140-
}
16141-
if (funcSymbol && funcSymbol.flags & SymbolFlags.Function && (funcSymbol.members || getJSDocClassTag(funcSymbol.valueDeclaration))) {
16142-
return getInferredClassType(funcSymbol);
16153+
const type = funcSymbol && getJavaScriptClassType(funcSymbol);
16154+
if (type) {
16155+
return type;
1614316156
}
16144-
else if (noImplicitAny) {
16157+
if (noImplicitAny) {
1614516158
error(node, Diagnostics.new_expression_whose_target_lacks_a_construct_signature_implicitly_has_an_any_type);
1614616159
}
1614716160
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)