Skip to content

Commit 70c1c57

Browse files
authored
Merge pull request #16196 from Microsoft/fix15959
Allow JS constructor function to return non-void
2 parents 617f60e + 6e87078 commit 70c1c57

File tree

8 files changed

+273
-6
lines changed

8 files changed

+273
-6
lines changed

Jakefile.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,8 @@ function runConsoleTests(defaultReporter, runInParallel) {
802802

803803
var debug = process.env.debug || process.env.d;
804804
var inspect = process.env.inspect;
805-
tests = process.env.test || process.env.tests || process.env.t;
805+
var testTimeout = process.env.timeout || defaultTestTimeout;
806+
var tests = process.env.test || process.env.tests || process.env.t;
806807
var light = process.env.light || false;
807808
var stackTraceLimit = process.env.stackTraceLimit;
808809
var testConfigFile = 'test.config';
@@ -820,7 +821,7 @@ function runConsoleTests(defaultReporter, runInParallel) {
820821
} while (fs.existsSync(taskConfigsFolder));
821822
fs.mkdirSync(taskConfigsFolder);
822823

823-
workerCount = process.env.workerCount || os.cpus().length;
824+
workerCount = process.env.workerCount || process.env.p || os.cpus().length;
824825
}
825826

826827
if (tests || light || taskConfigsFolder) {
@@ -925,7 +926,7 @@ function runConsoleTests(defaultReporter, runInParallel) {
925926
}
926927
}
927928

928-
var testTimeout = 20000;
929+
var defaultTestTimeout = 20000;
929930
desc("Runs all the tests in parallel using the built run.js file. Optional arguments are: t[ests]=category1|category2|... d[ebug]=true.");
930931
task("runtests-parallel", ["build-rules", "tests", builtLocalDirectory], function () {
931932
runConsoleTests('min', /*runInParallel*/ true);

src/compiler/checker.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15890,7 +15890,7 @@ namespace ts {
1589015890
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
1589115891
if (callSignatures.length) {
1589215892
const signature = resolveCall(node, callSignatures, candidatesOutArray);
15893-
if (getReturnTypeOfSignature(signature) !== voidType) {
15893+
if (!isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
1589415894
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
1589515895
}
1589615896
if (getThisTypeOfSignature(signature) === voidType) {
@@ -16117,10 +16117,30 @@ namespace ts {
1611716117
return getNodeLinks(node).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(node);
1611816118
}
1611916119

16120+
/**
16121+
* Indicates whether a declaration can be treated as a constructor in a JavaScript
16122+
* file.
16123+
*/
16124+
function isJavaScriptConstructor(node: Declaration): boolean {
16125+
if (isInJavaScriptFile(node)) {
16126+
// If the node has a @class tag, treat it like a constructor.
16127+
if (getJSDocClassTag(node)) return true;
16128+
16129+
// If the symbol of the node has members, treat it like a constructor.
16130+
const symbol = isFunctionDeclaration(node) || isFunctionExpression(node) ? getSymbolOfNode(node) :
16131+
isVariableDeclaration(node) && isFunctionExpression(node.initializer) ? getSymbolOfNode(node.initializer) :
16132+
undefined;
16133+
16134+
return symbol && symbol.members !== undefined;
16135+
}
16136+
16137+
return false;
16138+
}
16139+
1612016140
function getInferredClassType(symbol: Symbol) {
1612116141
const links = getSymbolLinks(symbol);
1612216142
if (!links.inferredClassType) {
16123-
links.inferredClassType = createAnonymousType(symbol, symbol.members, emptyArray, emptyArray, /*stringIndexType*/ undefined, /*numberIndexType*/ undefined);
16143+
links.inferredClassType = createAnonymousType(symbol, symbol.members || emptySymbols, emptyArray, emptyArray, /*stringIndexType*/ undefined, /*numberIndexType*/ undefined);
1612416144
}
1612516145
return links.inferredClassType;
1612616146
}
@@ -16160,7 +16180,7 @@ namespace ts {
1616016180
if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
1616116181
funcSymbol = getSymbolOfNode((<VariableDeclaration>funcSymbol.valueDeclaration).initializer);
1616216182
}
16163-
if (funcSymbol && funcSymbol.members && funcSymbol.flags & SymbolFlags.Function) {
16183+
if (funcSymbol && funcSymbol.flags & SymbolFlags.Function && (funcSymbol.members || getJSDocClassTag(funcSymbol.valueDeclaration))) {
1616416184
return getInferredClassType(funcSymbol);
1616516185
}
1616616186
else if (noImplicitAny) {

src/compiler/parser.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6533,6 +6533,10 @@ namespace ts {
65336533
case "augments":
65346534
tag = parseAugmentsTag(atToken, tagName);
65356535
break;
6536+
case "class":
6537+
case "constructor":
6538+
tag = parseClassTag(atToken, tagName);
6539+
break;
65366540
case "arg":
65376541
case "argument":
65386542
case "param":
@@ -6752,6 +6756,13 @@ namespace ts {
67526756
return finishNode(result);
67536757
}
67546758

6759+
function parseClassTag(atToken: AtToken, tagName: Identifier): JSDocClassTag {
6760+
const tag = <JSDocClassTag>createNode(SyntaxKind.JSDocClassTag, atToken.pos);
6761+
tag.atToken = atToken;
6762+
tag.tagName = tagName;
6763+
return finishNode(tag);
6764+
}
6765+
67556766
function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag {
67566767
const typeExpression = tryParseTypeExpression();
67576768
skipWhitespace();

src/compiler/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ namespace ts {
374374
JSDocComment,
375375
JSDocTag,
376376
JSDocAugmentsTag,
377+
JSDocClassTag,
377378
JSDocParameterTag,
378379
JSDocReturnTag,
379380
JSDocTypeTag,
@@ -2132,6 +2133,10 @@ namespace ts {
21322133
typeExpression: JSDocTypeExpression;
21332134
}
21342135

2136+
export interface JSDocClassTag extends JSDocTag {
2137+
kind: SyntaxKind.JSDocClassTag;
2138+
}
2139+
21352140
export interface JSDocTemplateTag extends JSDocTag {
21362141
kind: SyntaxKind.JSDocTemplateTag;
21372142
typeParameters: NodeArray<TypeParameterDeclaration>;

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,6 +1562,10 @@ namespace ts {
15621562
return getFirstJSDocTag(node, SyntaxKind.JSDocAugmentsTag) as JSDocAugmentsTag;
15631563
}
15641564

1565+
export function getJSDocClassTag(node: Node): JSDocClassTag {
1566+
return getFirstJSDocTag(node, SyntaxKind.JSDocClassTag) as JSDocClassTag;
1567+
}
1568+
15651569
export function getJSDocReturnTag(node: Node): JSDocReturnTag {
15661570
return getFirstJSDocTag(node, SyntaxKind.JSDocReturnTag) as JSDocReturnTag;
15671571
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
=== tests/cases/conformance/salsa/index.js ===
2+
function C1() {
3+
>C1 : Symbol(C1, Decl(index.js, 0, 0))
4+
5+
if (!(this instanceof C1)) return new C1();
6+
>C1 : Symbol(C1, Decl(index.js, 0, 0))
7+
>C1 : Symbol(C1, Decl(index.js, 0, 0))
8+
9+
this.x = 1;
10+
>x : Symbol(C1.x, Decl(index.js, 1, 47))
11+
}
12+
13+
const c1_v1 = C1();
14+
>c1_v1 : Symbol(c1_v1, Decl(index.js, 5, 5))
15+
>C1 : Symbol(C1, Decl(index.js, 0, 0))
16+
17+
const c1_v2 = new C1();
18+
>c1_v2 : Symbol(c1_v2, Decl(index.js, 6, 5))
19+
>C1 : Symbol(C1, Decl(index.js, 0, 0))
20+
21+
var C2 = function () {
22+
>C2 : Symbol(C2, Decl(index.js, 8, 3))
23+
24+
if (!(this instanceof C2)) return new C2();
25+
>C2 : Symbol(C2, Decl(index.js, 8, 3))
26+
>C2 : Symbol(C2, Decl(index.js, 8, 3))
27+
28+
this.x = 1;
29+
>x : Symbol(C2.x, Decl(index.js, 9, 47))
30+
31+
};
32+
33+
const c2_v1 = C2();
34+
>c2_v1 : Symbol(c2_v1, Decl(index.js, 13, 5))
35+
>C2 : Symbol(C2, Decl(index.js, 8, 3))
36+
37+
const c2_v2 = new C2();
38+
>c2_v2 : Symbol(c2_v2, Decl(index.js, 14, 5))
39+
>C2 : Symbol(C2, Decl(index.js, 8, 3))
40+
41+
/** @class */
42+
function C3() {
43+
>C3 : Symbol(C3, Decl(index.js, 14, 23))
44+
45+
if (!(this instanceof C3)) return new C3();
46+
>C3 : Symbol(C3, Decl(index.js, 14, 23))
47+
>C3 : Symbol(C3, Decl(index.js, 14, 23))
48+
49+
};
50+
51+
const c3_v1 = C3();
52+
>c3_v1 : Symbol(c3_v1, Decl(index.js, 21, 5))
53+
>C3 : Symbol(C3, Decl(index.js, 14, 23))
54+
55+
const c3_v2 = new C3();
56+
>c3_v2 : Symbol(c3_v2, Decl(index.js, 22, 5))
57+
>C3 : Symbol(C3, Decl(index.js, 14, 23))
58+
59+
/** @class */
60+
var C4 = function () {
61+
>C4 : Symbol(C4, Decl(index.js, 25, 3))
62+
63+
if (!(this instanceof C4)) return new C4();
64+
>C4 : Symbol(C4, Decl(index.js, 25, 3))
65+
>C4 : Symbol(C4, Decl(index.js, 25, 3))
66+
67+
};
68+
69+
const c4_v1 = C4();
70+
>c4_v1 : Symbol(c4_v1, Decl(index.js, 29, 5))
71+
>C4 : Symbol(C4, Decl(index.js, 25, 3))
72+
73+
const c4_v2 = new C4();
74+
>c4_v2 : Symbol(c4_v2, Decl(index.js, 30, 5))
75+
>C4 : Symbol(C4, Decl(index.js, 25, 3))
76+
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
=== tests/cases/conformance/salsa/index.js ===
2+
function C1() {
3+
>C1 : () => typeof C1
4+
5+
if (!(this instanceof C1)) return new C1();
6+
>!(this instanceof C1) : boolean
7+
>(this instanceof C1) : boolean
8+
>this instanceof C1 : boolean
9+
>this : any
10+
>C1 : () => typeof C1
11+
>new C1() : { x: number; }
12+
>C1 : () => typeof C1
13+
14+
this.x = 1;
15+
>this.x = 1 : 1
16+
>this.x : any
17+
>this : any
18+
>x : any
19+
>1 : 1
20+
}
21+
22+
const c1_v1 = C1();
23+
>c1_v1 : { x: number; }
24+
>C1() : { x: number; }
25+
>C1 : () => typeof C1
26+
27+
const c1_v2 = new C1();
28+
>c1_v2 : { x: number; }
29+
>new C1() : { x: number; }
30+
>C1 : () => typeof C1
31+
32+
var C2 = function () {
33+
>C2 : () => any
34+
>function () { if (!(this instanceof C2)) return new C2(); this.x = 1;} : () => any
35+
36+
if (!(this instanceof C2)) return new C2();
37+
>!(this instanceof C2) : boolean
38+
>(this instanceof C2) : boolean
39+
>this instanceof C2 : boolean
40+
>this : any
41+
>C2 : () => any
42+
>new C2() : { x: number; }
43+
>C2 : () => any
44+
45+
this.x = 1;
46+
>this.x = 1 : 1
47+
>this.x : any
48+
>this : any
49+
>x : any
50+
>1 : 1
51+
52+
};
53+
54+
const c2_v1 = C2();
55+
>c2_v1 : { x: number; }
56+
>C2() : { x: number; }
57+
>C2 : () => any
58+
59+
const c2_v2 = new C2();
60+
>c2_v2 : { x: number; }
61+
>new C2() : { x: number; }
62+
>C2 : () => any
63+
64+
/** @class */
65+
function C3() {
66+
>C3 : () => typeof C3
67+
68+
if (!(this instanceof C3)) return new C3();
69+
>!(this instanceof C3) : boolean
70+
>(this instanceof C3) : boolean
71+
>this instanceof C3 : boolean
72+
>this : any
73+
>C3 : () => typeof C3
74+
>new C3() : {}
75+
>C3 : () => typeof C3
76+
77+
};
78+
79+
const c3_v1 = C3();
80+
>c3_v1 : {}
81+
>C3() : {}
82+
>C3 : () => typeof C3
83+
84+
const c3_v2 = new C3();
85+
>c3_v2 : {}
86+
>new C3() : {}
87+
>C3 : () => typeof C3
88+
89+
/** @class */
90+
var C4 = function () {
91+
>C4 : () => any
92+
>function () { if (!(this instanceof C4)) return new C4();} : () => any
93+
94+
if (!(this instanceof C4)) return new C4();
95+
>!(this instanceof C4) : boolean
96+
>(this instanceof C4) : boolean
97+
>this instanceof C4 : boolean
98+
>this : any
99+
>C4 : () => any
100+
>new C4() : {}
101+
>C4 : () => any
102+
103+
};
104+
105+
const c4_v1 = C4();
106+
>c4_v1 : {}
107+
>C4() : {}
108+
>C4 : () => any
109+
110+
const c4_v2 = new C4();
111+
>c4_v2 : {}
112+
>new C4() : {}
113+
>C4 : () => any
114+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @noEmit: true
4+
// @filename: index.js
5+
6+
function C1() {
7+
if (!(this instanceof C1)) return new C1();
8+
this.x = 1;
9+
}
10+
11+
const c1_v1 = C1();
12+
const c1_v2 = new C1();
13+
14+
var C2 = function () {
15+
if (!(this instanceof C2)) return new C2();
16+
this.x = 1;
17+
};
18+
19+
const c2_v1 = C2();
20+
const c2_v2 = new C2();
21+
22+
/** @class */
23+
function C3() {
24+
if (!(this instanceof C3)) return new C3();
25+
};
26+
27+
const c3_v1 = C3();
28+
const c3_v2 = new C3();
29+
30+
/** @class */
31+
var C4 = function () {
32+
if (!(this instanceof C4)) return new C4();
33+
};
34+
35+
const c4_v1 = C4();
36+
const c4_v2 = new C4();

0 commit comments

Comments
 (0)