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

Allow JS constructor function to return non-void #16196

Merged
merged 4 commits into from
Jun 6, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions Jakefile.js
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,8 @@ function runConsoleTests(defaultReporter, runInParallel) {

var debug = process.env.debug || process.env.d;
var inspect = process.env.inspect;
tests = process.env.test || process.env.tests || process.env.t;
var testTimeout = process.env.timeout || defaultTestTimeout;
var tests = process.env.test || process.env.tests || process.env.t;
var light = process.env.light || false;
var stackTraceLimit = process.env.stackTraceLimit;
var testConfigFile = 'test.config';
Expand All @@ -820,7 +821,7 @@ function runConsoleTests(defaultReporter, runInParallel) {
} while (fs.existsSync(taskConfigsFolder));
fs.mkdirSync(taskConfigsFolder);

workerCount = process.env.workerCount || os.cpus().length;
workerCount = process.env.workerCount || process.env.p || os.cpus().length;
}

if (tests || light || taskConfigsFolder) {
Expand Down Expand Up @@ -925,7 +926,7 @@ function runConsoleTests(defaultReporter, runInParallel) {
}
}

var testTimeout = 20000;
var defaultTestTimeout = 20000;
desc("Runs all the tests in parallel using the built run.js file. Optional arguments are: t[ests]=category1|category2|... d[ebug]=true.");
task("runtests-parallel", ["build-rules", "tests", builtLocalDirectory], function () {
runConsoleTests('min', /*runInParallel*/ true);
Expand Down
26 changes: 23 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15892,7 +15892,7 @@ namespace ts {
const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call);
if (callSignatures.length) {
const signature = resolveCall(node, callSignatures, candidatesOutArray);
if (getReturnTypeOfSignature(signature) !== voidType) {
if (!isJavaScriptConstructor(signature.declaration) && getReturnTypeOfSignature(signature) !== voidType) {
error(node, Diagnostics.Only_a_void_function_can_be_called_with_the_new_keyword);
}
if (getThisTypeOfSignature(signature) === voidType) {
Expand Down Expand Up @@ -16119,10 +16119,30 @@ namespace ts {
return getNodeLinks(node).resolvedSignature === resolvingSignature ? resolvingSignature : getResolvedSignature(node);
}

/**
* Indicates whether a declaration can be treated as a constructor in a JavaScript
* file.
*/
function isJavaScriptConstructor(node: Declaration): boolean {
if (isInJavaScriptFile(node)) {
// If the node has a @class tag, treat it like a constructor.
if (getJSDocClassTag(node)) return true;

// If the symbol of the node has members, treat it like a constructor.
const symbol = isFunctionDeclaration(node) || isFunctionExpression(node) ? getSymbolOfNode(node) :
isVariableDeclaration(node) && isFunctionExpression(node.initializer) ? getSymbolOfNode(node.initializer) :
undefined;

return symbol && symbol.members !== undefined;
}

return false;
}

function getInferredClassType(symbol: Symbol) {
const links = getSymbolLinks(symbol);
if (!links.inferredClassType) {
links.inferredClassType = createAnonymousType(symbol, symbol.members, emptyArray, emptyArray, /*stringIndexType*/ undefined, /*numberIndexType*/ undefined);
links.inferredClassType = createAnonymousType(symbol, symbol.members || emptySymbols, emptyArray, emptyArray, /*stringIndexType*/ undefined, /*numberIndexType*/ undefined);
}
return links.inferredClassType;
}
Expand Down Expand Up @@ -16162,7 +16182,7 @@ namespace ts {
if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
funcSymbol = getSymbolOfNode((<VariableDeclaration>funcSymbol.valueDeclaration).initializer);
}
if (funcSymbol && funcSymbol.members && funcSymbol.flags & SymbolFlags.Function) {
if (funcSymbol && funcSymbol.flags & SymbolFlags.Function && (funcSymbol.members || getJSDocClassTag(funcSymbol.valueDeclaration))) {
return getInferredClassType(funcSymbol);
}
else if (noImplicitAny) {
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6533,6 +6533,10 @@ namespace ts {
case "augments":
tag = parseAugmentsTag(atToken, tagName);
break;
case "class":
case "constructor":
tag = parseClassTag(atToken, tagName);
break;
case "arg":
case "argument":
case "param":
Expand Down Expand Up @@ -6752,6 +6756,13 @@ namespace ts {
return finishNode(result);
}

function parseClassTag(atToken: AtToken, tagName: Identifier): JSDocClassTag {
const tag = <JSDocClassTag>createNode(SyntaxKind.JSDocClassTag, atToken.pos);
tag.atToken = atToken;
tag.tagName = tagName;
return finishNode(tag);
}

function parseTypedefTag(atToken: AtToken, tagName: Identifier): JSDocTypedefTag {
const typeExpression = tryParseTypeExpression();
skipWhitespace();
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ namespace ts {
JSDocComment,
JSDocTag,
JSDocAugmentsTag,
JSDocClassTag,
JSDocParameterTag,
JSDocReturnTag,
JSDocTypeTag,
Expand Down Expand Up @@ -2132,6 +2133,10 @@ namespace ts {
typeExpression: JSDocTypeExpression;
}

export interface JSDocClassTag extends JSDocTag {
kind: SyntaxKind.JSDocClassTag;
}

export interface JSDocTemplateTag extends JSDocTag {
kind: SyntaxKind.JSDocTemplateTag;
typeParameters: NodeArray<TypeParameterDeclaration>;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1562,6 +1562,10 @@ namespace ts {
return getFirstJSDocTag(node, SyntaxKind.JSDocAugmentsTag) as JSDocAugmentsTag;
}

export function getJSDocClassTag(node: Node): JSDocClassTag {
return getFirstJSDocTag(node, SyntaxKind.JSDocClassTag) as JSDocClassTag;
}

export function getJSDocReturnTag(node: Node): JSDocReturnTag {
return getFirstJSDocTag(node, SyntaxKind.JSDocReturnTag) as JSDocReturnTag;
}
Expand Down
76 changes: 76 additions & 0 deletions tests/baselines/reference/constructorFunctions.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
=== tests/cases/conformance/salsa/index.js ===
function C1() {
>C1 : Symbol(C1, Decl(index.js, 0, 0))

if (!(this instanceof C1)) return new C1();
>C1 : Symbol(C1, Decl(index.js, 0, 0))
>C1 : Symbol(C1, Decl(index.js, 0, 0))

this.x = 1;
>x : Symbol(C1.x, Decl(index.js, 1, 47))
}

const c1_v1 = C1();
>c1_v1 : Symbol(c1_v1, Decl(index.js, 5, 5))
>C1 : Symbol(C1, Decl(index.js, 0, 0))

const c1_v2 = new C1();
>c1_v2 : Symbol(c1_v2, Decl(index.js, 6, 5))
>C1 : Symbol(C1, Decl(index.js, 0, 0))

var C2 = function () {
>C2 : Symbol(C2, Decl(index.js, 8, 3))

if (!(this instanceof C2)) return new C2();
>C2 : Symbol(C2, Decl(index.js, 8, 3))
>C2 : Symbol(C2, Decl(index.js, 8, 3))

this.x = 1;
>x : Symbol(C2.x, Decl(index.js, 9, 47))

};

const c2_v1 = C2();
>c2_v1 : Symbol(c2_v1, Decl(index.js, 13, 5))
>C2 : Symbol(C2, Decl(index.js, 8, 3))

const c2_v2 = new C2();
>c2_v2 : Symbol(c2_v2, Decl(index.js, 14, 5))
>C2 : Symbol(C2, Decl(index.js, 8, 3))

/** @class */
function C3() {
>C3 : Symbol(C3, Decl(index.js, 14, 23))

if (!(this instanceof C3)) return new C3();
>C3 : Symbol(C3, Decl(index.js, 14, 23))
>C3 : Symbol(C3, Decl(index.js, 14, 23))

};

const c3_v1 = C3();
>c3_v1 : Symbol(c3_v1, Decl(index.js, 21, 5))
>C3 : Symbol(C3, Decl(index.js, 14, 23))

const c3_v2 = new C3();
>c3_v2 : Symbol(c3_v2, Decl(index.js, 22, 5))
>C3 : Symbol(C3, Decl(index.js, 14, 23))

/** @class */
var C4 = function () {
>C4 : Symbol(C4, Decl(index.js, 25, 3))

if (!(this instanceof C4)) return new C4();
>C4 : Symbol(C4, Decl(index.js, 25, 3))
>C4 : Symbol(C4, Decl(index.js, 25, 3))

};

const c4_v1 = C4();
>c4_v1 : Symbol(c4_v1, Decl(index.js, 29, 5))
>C4 : Symbol(C4, Decl(index.js, 25, 3))

const c4_v2 = new C4();
>c4_v2 : Symbol(c4_v2, Decl(index.js, 30, 5))
>C4 : Symbol(C4, Decl(index.js, 25, 3))

114 changes: 114 additions & 0 deletions tests/baselines/reference/constructorFunctions.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
=== tests/cases/conformance/salsa/index.js ===
function C1() {
>C1 : () => typeof C1

if (!(this instanceof C1)) return new C1();
>!(this instanceof C1) : boolean
>(this instanceof C1) : boolean
>this instanceof C1 : boolean
>this : any
>C1 : () => typeof C1
>new C1() : { x: number; }
>C1 : () => typeof C1

this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : any
>x : any
>1 : 1
}

const c1_v1 = C1();
>c1_v1 : { x: number; }
>C1() : { x: number; }
>C1 : () => typeof C1

const c1_v2 = new C1();
>c1_v2 : { x: number; }
>new C1() : { x: number; }
>C1 : () => typeof C1

var C2 = function () {
>C2 : () => any
>function () { if (!(this instanceof C2)) return new C2(); this.x = 1;} : () => any

if (!(this instanceof C2)) return new C2();
>!(this instanceof C2) : boolean
>(this instanceof C2) : boolean
>this instanceof C2 : boolean
>this : any
>C2 : () => any
>new C2() : { x: number; }
>C2 : () => any

this.x = 1;
>this.x = 1 : 1
>this.x : any
>this : any
>x : any
>1 : 1

};

const c2_v1 = C2();
>c2_v1 : { x: number; }
>C2() : { x: number; }
>C2 : () => any

const c2_v2 = new C2();
>c2_v2 : { x: number; }
>new C2() : { x: number; }
>C2 : () => any

/** @class */
function C3() {
>C3 : () => typeof C3

if (!(this instanceof C3)) return new C3();
>!(this instanceof C3) : boolean
>(this instanceof C3) : boolean
>this instanceof C3 : boolean
>this : any
>C3 : () => typeof C3
>new C3() : {}
>C3 : () => typeof C3

};

const c3_v1 = C3();
>c3_v1 : {}
>C3() : {}
>C3 : () => typeof C3

const c3_v2 = new C3();
>c3_v2 : {}
>new C3() : {}
>C3 : () => typeof C3

/** @class */
var C4 = function () {
>C4 : () => any
>function () { if (!(this instanceof C4)) return new C4();} : () => any

if (!(this instanceof C4)) return new C4();
>!(this instanceof C4) : boolean
>(this instanceof C4) : boolean
>this instanceof C4 : boolean
>this : any
>C4 : () => any
>new C4() : {}
>C4 : () => any

};

const c4_v1 = C4();
>c4_v1 : {}
>C4() : {}
>C4 : () => any

const c4_v2 = new C4();
>c4_v2 : {}
>new C4() : {}
>C4 : () => any

36 changes: 36 additions & 0 deletions tests/cases/conformance/salsa/constructorFunctions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename: index.js

function C1() {
if (!(this instanceof C1)) return new C1();
this.x = 1;
}

const c1_v1 = C1();
const c1_v2 = new C1();

var C2 = function () {
if (!(this instanceof C2)) return new C2();
this.x = 1;
};

const c2_v1 = C2();
const c2_v2 = new C2();

/** @class */
function C3() {
if (!(this instanceof C3)) return new C3();
};

const c3_v1 = C3();
const c3_v2 = new C3();

/** @class */
var C4 = function () {
if (!(this instanceof C4)) return new C4();
};

const c4_v1 = C4();
const c4_v2 = new C4();