From 8cb5333791b386d497da316ea694f9f976fdc403 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 6 Jun 2017 13:33:19 -0700 Subject: [PATCH] Verify JS function is constructor --- Jakefile.js | 7 ++++--- src/compiler/checker.ts | 22 +++++++++++++++++++++- src/compiler/parser.ts | 11 +++++++++++ src/compiler/types.ts | 5 +++++ src/compiler/utilities.ts | 4 ++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Jakefile.js b/Jakefile.js index 439fced0720..274c80cea24 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -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'; @@ -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) { @@ -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); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6188f1aead9..b1a836fe865 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15892,7 +15892,7 @@ namespace ts { const callSignatures = getSignaturesOfType(expressionType, SignatureKind.Call); if (callSignatures.length) { const signature = resolveCall(node, callSignatures, candidatesOutArray); - if (!isInJavaScriptFile(signature.declaration) && 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) { @@ -16119,6 +16119,26 @@ 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) { diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 84306cd81d8..b398ee66e0a 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -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": @@ -6752,6 +6756,13 @@ namespace ts { return finishNode(result); } + function parseClassTag(atToken: AtToken, tagName: Identifier): JSDocClassTag { + const tag = 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(); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1c7f36bb27b..ab04f72faf0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -374,6 +374,7 @@ namespace ts { JSDocComment, JSDocTag, JSDocAugmentsTag, + JSDocClassTag, JSDocParameterTag, JSDocReturnTag, JSDocTypeTag, @@ -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; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 30f7e0a52f9..77f1e43c9cb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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; }