From 64be2a8d163cbb6a99ceb59442e79833f19dddd2 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Mon, 9 Nov 2020 11:34:41 -0800 Subject: [PATCH] Revert "Revert "feat(40197): handle uncalled function checks in binary expressions (#40260)"" (#41462) This reverts commit cf3e28ea66bdfb4e438f86bd7d7860b66dae0ed8. --- src/compiler/binder.ts | 4 +- src/compiler/checker.ts | 53 +-- src/compiler/emitter.ts | 6 +- ...uthinessCallExpressionCoercion2.errors.txt | 108 ++++++ .../truthinessCallExpressionCoercion2.js | 146 ++++++++ .../truthinessCallExpressionCoercion2.symbols | 226 +++++++++++++ .../truthinessCallExpressionCoercion2.types | 318 ++++++++++++++++++ .../truthinessCallExpressionCoercion2.ts | 83 +++++ .../codeFixMissingCallParentheses11.ts | 6 + .../codeFixMissingCallParentheses12.ts | 19 ++ .../codeFixMissingCallParentheses13.ts | 21 ++ .../codeFixMissingCallParentheses14.ts | 29 ++ .../codeFixMissingCallParentheses15.ts | 30 ++ .../codeFixMissingCallParentheses16.ts | 25 ++ .../codeFixMissingCallParentheses17.ts | 19 ++ 15 files changed, 1069 insertions(+), 24 deletions(-) create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion2.js create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion2.symbols create mode 100644 tests/baselines/reference/truthinessCallExpressionCoercion2.types create mode 100644 tests/cases/compiler/truthinessCallExpressionCoercion2.ts create mode 100644 tests/cases/fourslash/codeFixMissingCallParentheses12.ts create mode 100644 tests/cases/fourslash/codeFixMissingCallParentheses13.ts create mode 100644 tests/cases/fourslash/codeFixMissingCallParentheses14.ts create mode 100644 tests/cases/fourslash/codeFixMissingCallParentheses15.ts create mode 100644 tests/cases/fourslash/codeFixMissingCallParentheses16.ts create mode 100644 tests/cases/fourslash/codeFixMissingCallParentheses17.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 6b28594a5e..8929ec075f 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -670,7 +670,7 @@ namespace ts { } // We create a return control flow graph for IIFEs and constructors. For constructors // we use the return control flow graph in strict property initialization checks. - currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || (isInJSFile && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; + currentReturnTarget = isIIFE || node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression)) ? createBranchLabel() : undefined; currentExceptionTarget = undefined; currentBreakTarget = undefined; currentContinueTarget = undefined; @@ -691,7 +691,7 @@ namespace ts { if (currentReturnTarget) { addAntecedent(currentReturnTarget, currentFlow); currentFlow = finishFlowLabel(currentReturnTarget); - if (node.kind === SyntaxKind.Constructor || (isInJSFile && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { + if (node.kind === SyntaxKind.Constructor || (isInJSFile(node) && (node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression))) { (node).returnFlowNode = currentFlow; } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 83bbb549a5..9a71a6a027 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30156,6 +30156,9 @@ namespace ts { workStacks.leftType[stackIndex] = leftType; const operator = node.operatorToken.kind; if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) { + if (operator === SyntaxKind.AmpersandAmpersandToken) { + checkTestingKnownTruthyCallableType(node.left, leftType); + } checkTruthinessOfType(leftType, node.left); } advanceState(CheckBinaryExpressionState.FinishCheck); @@ -30689,7 +30692,7 @@ namespace ts { function checkConditionalExpression(node: ConditionalExpression, checkMode?: CheckMode): Type { const type = checkTruthinessExpression(node.condition); - checkTestingKnownTruthyCallableType(node.condition, node.whenTrue, type); + checkTestingKnownTruthyCallableType(node.condition, type, node.whenTrue); const type1 = checkExpression(node.whenTrue, checkMode); const type2 = checkExpression(node.whenFalse, checkMode); return getUnionType([type1, type2], UnionReduction.Subtype); @@ -33926,7 +33929,7 @@ namespace ts { // Grammar checking checkGrammarStatementInAmbientContext(node); const type = checkTruthinessExpression(node.expression); - checkTestingKnownTruthyCallableType(node.expression, node.thenStatement, type); + checkTestingKnownTruthyCallableType(node.expression, type, node.thenStatement); checkSourceElement(node.thenStatement); if (node.thenStatement.kind === SyntaxKind.EmptyStatement) { @@ -33936,16 +33939,16 @@ namespace ts { checkSourceElement(node.elseStatement); } - function checkTestingKnownTruthyCallableType(condExpr: Expression, body: Statement | Expression, type: Type) { + function checkTestingKnownTruthyCallableType(condExpr: Expression, type: Type, body?: Statement | Expression) { if (!strictNullChecks) { return; } - const testedNode = isIdentifier(condExpr) - ? condExpr - : isPropertyAccessExpression(condExpr) - ? condExpr.name - : undefined; + const location = isBinaryExpression(condExpr) ? condExpr.right : condExpr; + const testedNode = isIdentifier(location) ? location + : isPropertyAccessExpression(location) ? location.name + : isBinaryExpression(location) && isIdentifier(location.right) ? location.right + : undefined; if (!testedNode) { return; @@ -33966,27 +33969,34 @@ namespace ts { return; } - const testedFunctionSymbol = getSymbolAtLocation(testedNode); - if (!testedFunctionSymbol) { + const testedSymbol = getSymbolAtLocation(testedNode); + if (!testedSymbol) { return; } - const functionIsUsedInBody = forEachChild(body, function check(childNode): boolean | undefined { + const isUsed = isBinaryExpression(condExpr.parent) ? isFunctionUsedInBinaryExpressionChain(condExpr.parent, testedSymbol) + : body ? isFunctionUsedInConditionBody(condExpr, body, testedNode, testedSymbol) + : false; + if (!isUsed) { + error(location, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); + } + } + + function isFunctionUsedInConditionBody(expr: Expression, body: Statement | Expression, testedNode: Node, testedSymbol: Symbol): boolean { + return !!forEachChild(body, function check(childNode): boolean | undefined { if (isIdentifier(childNode)) { const childSymbol = getSymbolAtLocation(childNode); - if (childSymbol && childSymbol === testedFunctionSymbol) { + if (childSymbol && childSymbol === testedSymbol) { // If the test was a simple identifier, the above check is sufficient - if (isIdentifier(condExpr)) { + if (isIdentifier(expr)) { return true; } // Otherwise we need to ensure the symbol is called on the same target let testedExpression = testedNode.parent; let childExpression = childNode.parent; while (testedExpression && childExpression) { - if (isIdentifier(testedExpression) && isIdentifier(childExpression) || - testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword - ) { + testedExpression.kind === SyntaxKind.ThisKeyword && childExpression.kind === SyntaxKind.ThisKeyword) { return getSymbolAtLocation(testedExpression) === getSymbolAtLocation(childExpression); } @@ -34003,13 +34013,18 @@ namespace ts { } } } - return forEachChild(childNode, check); }); + } - if (!functionIsUsedInBody) { - error(condExpr, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead); + function isFunctionUsedInBinaryExpressionChain(node: Node, testedSymbol: Symbol): boolean { + while (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) { + if (isCallExpression(node.right) && testedSymbol === getSymbolAtLocation(node.right.expression)) { + return true; + } + node = node.parent; } + return false; } function checkDoStatement(node: DoStatement) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index ebfb7ff3e4..c3363eb750 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2812,7 +2812,7 @@ namespace ts { if (isSimilarNode && currentSourceFile) { pos = skipTrivia(currentSourceFile.text, pos); } - if (emitLeadingCommentsOfPosition && isSimilarNode && contextNode.pos !== startPos) { + if (isSimilarNode && contextNode.pos !== startPos) { const needsIndent = indentLeading && currentSourceFile && !positionsAreOnSameLine(startPos, pos, currentSourceFile); if (needsIndent) { increaseIndent(); @@ -2823,7 +2823,7 @@ namespace ts { } } pos = writeTokenText(token, writer, pos); - if (emitTrailingCommentsOfPosition && isSimilarNode && contextNode.end !== pos) { + if (isSimilarNode && contextNode.end !== pos) { emitTrailingCommentsOfPosition(pos, /*prefixSpace*/ true); } return pos; @@ -3469,7 +3469,7 @@ namespace ts { // "comment1" is not considered to be leading comment for node.initializer // but rather a trailing comment on the previous node. const initializer = node.initializer; - if (emitTrailingCommentsOfPosition && (getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { + if ((getEmitFlags(initializer) & EmitFlags.NoLeadingComments) === 0) { const commentRange = getCommentRange(initializer); emitTrailingCommentsOfPosition(commentRange.pos); } diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt new file mode 100644 index 0000000000..f5dd6ad304 --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.errors.txt @@ -0,0 +1,108 @@ +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(3,5): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(6,10): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(30,18): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(36,46): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(47,5): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(50,10): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(66,9): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? +tests/cases/compiler/truthinessCallExpressionCoercion2.ts(69,14): error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + + +==== tests/cases/compiler/truthinessCallExpressionCoercion2.ts (8 errors) ==== + function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) { + // error + required1 && console.log('required'); + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + + // error + 1 && required1 && console.log('required'); + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + + // ok + required1 && required1(); + + // ok + required1 && 1 && required1(); + + // ok + optional && console.log('optional'); + + // ok + 1 && optional && console.log('optional'); + + // ok + !!required1 && console.log('not required'); + + // ok + required1() && console.log('required call'); + + // ok + required1 && required2 && required1() && required2(); + + // error + required1 && required2 && required1() && console.log('foo'); + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + } + + function checksConsole() { + // error + typeof window !== 'undefined' && window.console && + ((window.console as any).firebug || (window.console.exception && window.console.table)); + ~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + } + + function checksPropertyAccess() { + const x = { + foo: { + bar() { return true; } + } + } + + // error + x.foo.bar && console.log('x.foo.bar'); + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + + // error + 1 && x.foo.bar && console.log('x.foo.bar'); + ~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + + // ok + x.foo.bar && x.foo.bar(); + + // ok + x.foo.bar && 1 && x.foo.bar(); + } + + class Foo { + optional?: () => boolean; + required() { + return true; + } + test() { + // error + this.required && console.log('required'); + ~~~~~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + + // error + 1 && this.required && console.log('required'); + ~~~~~~~~~~~~~ +!!! error TS2774: This condition will always return true since the function is always defined. Did you mean to call it instead? + + // ok + this.required && this.required(); + + // ok + this.required && 1 && this.required(); + + // ok + 1 && this.optional && console.log('optional'); + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.js b/tests/baselines/reference/truthinessCallExpressionCoercion2.js new file mode 100644 index 0000000000..d2e5cee217 --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.js @@ -0,0 +1,146 @@ +//// [truthinessCallExpressionCoercion2.ts] +function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) { + // error + required1 && console.log('required'); + + // error + 1 && required1 && console.log('required'); + + // ok + required1 && required1(); + + // ok + required1 && 1 && required1(); + + // ok + optional && console.log('optional'); + + // ok + 1 && optional && console.log('optional'); + + // ok + !!required1 && console.log('not required'); + + // ok + required1() && console.log('required call'); + + // ok + required1 && required2 && required1() && required2(); + + // error + required1 && required2 && required1() && console.log('foo'); +} + +function checksConsole() { + // error + typeof window !== 'undefined' && window.console && + ((window.console as any).firebug || (window.console.exception && window.console.table)); +} + +function checksPropertyAccess() { + const x = { + foo: { + bar() { return true; } + } + } + + // error + x.foo.bar && console.log('x.foo.bar'); + + // error + 1 && x.foo.bar && console.log('x.foo.bar'); + + // ok + x.foo.bar && x.foo.bar(); + + // ok + x.foo.bar && 1 && x.foo.bar(); +} + +class Foo { + optional?: () => boolean; + required() { + return true; + } + test() { + // error + this.required && console.log('required'); + + // error + 1 && this.required && console.log('required'); + + // ok + this.required && this.required(); + + // ok + this.required && 1 && this.required(); + + // ok + 1 && this.optional && console.log('optional'); + } +} + + +//// [truthinessCallExpressionCoercion2.js] +function test(required1, required2, optional) { + // error + required1 && console.log('required'); + // error + 1 && required1 && console.log('required'); + // ok + required1 && required1(); + // ok + required1 && 1 && required1(); + // ok + optional && console.log('optional'); + // ok + 1 && optional && console.log('optional'); + // ok + !!required1 && console.log('not required'); + // ok + required1() && console.log('required call'); + // ok + required1 && required2 && required1() && required2(); + // error + required1 && required2 && required1() && console.log('foo'); +} +function checksConsole() { + // error + typeof window !== 'undefined' && window.console && + (window.console.firebug || (window.console.exception && window.console.table)); +} +function checksPropertyAccess() { + var x = { + foo: { + bar: function () { return true; } + } + }; + // error + x.foo.bar && console.log('x.foo.bar'); + // error + 1 && x.foo.bar && console.log('x.foo.bar'); + // ok + x.foo.bar && x.foo.bar(); + // ok + x.foo.bar && 1 && x.foo.bar(); +} +var Foo = /** @class */ (function () { + function Foo() { + } + Foo.prototype.required = function () { + return true; + }; + Foo.prototype.test = function () { + // error + this.required && console.log('required'); + // error + 1 && this.required && console.log('required'); + // ok + this.required && this.required(); + // ok + this.required && 1 && this.required(); + // ok + 1 && this.optional && console.log('optional'); + }; + return Foo; +}()); diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.symbols b/tests/baselines/reference/truthinessCallExpressionCoercion2.symbols new file mode 100644 index 0000000000..67dce3ad8b --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.symbols @@ -0,0 +1,226 @@ +=== tests/cases/compiler/truthinessCallExpressionCoercion2.ts === +function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) { +>test : Symbol(test, Decl(truthinessCallExpressionCoercion2.ts, 0, 0)) +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39)) +>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion2.ts, 0, 65)) + + // error + required1 && console.log('required'); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // error + 1 && required1 && console.log('required'); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // ok + required1 && required1(); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) + + // ok + required1 && 1 && required1(); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) + + // ok + optional && console.log('optional'); +>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion2.ts, 0, 65)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // ok + 1 && optional && console.log('optional'); +>optional : Symbol(optional, Decl(truthinessCallExpressionCoercion2.ts, 0, 65)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // ok + !!required1 && console.log('not required'); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // ok + required1() && console.log('required call'); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // ok + required1 && required2 && required1() && required2(); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39)) +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39)) + + // error + required1 && required2 && required1() && console.log('foo'); +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>required2 : Symbol(required2, Decl(truthinessCallExpressionCoercion2.ts, 0, 39)) +>required1 : Symbol(required1, Decl(truthinessCallExpressionCoercion2.ts, 0, 14)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} + +function checksConsole() { +>checksConsole : Symbol(checksConsole, Decl(truthinessCallExpressionCoercion2.ts, 30, 1)) + + // error + typeof window !== 'undefined' && window.console && +>window : Symbol(window, Decl(lib.dom.d.ts, --, --)) +>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>window : Symbol(window, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) + + ((window.console as any).firebug || (window.console.exception && window.console.table)); +>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>window : Symbol(window, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>window.console.exception : Symbol(Console.exception, Decl(lib.dom.d.ts, --, --)) +>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>window : Symbol(window, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>exception : Symbol(Console.exception, Decl(lib.dom.d.ts, --, --)) +>window.console.table : Symbol(Console.table, Decl(lib.dom.d.ts, --, --)) +>window.console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>window : Symbol(window, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>table : Symbol(Console.table, Decl(lib.dom.d.ts, --, --)) +} + +function checksPropertyAccess() { +>checksPropertyAccess : Symbol(checksPropertyAccess, Decl(truthinessCallExpressionCoercion2.ts, 36, 1)) + + const x = { +>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9)) + + foo: { +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) + + bar() { return true; } +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) + } + } + + // error + x.foo.bar && console.log('x.foo.bar'); +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // error + 1 && x.foo.bar && console.log('x.foo.bar'); +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // ok + x.foo.bar && x.foo.bar(); +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) + + // ok + x.foo.bar && 1 && x.foo.bar(); +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo.bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +>x.foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>x : Symbol(x, Decl(truthinessCallExpressionCoercion2.ts, 39, 9)) +>foo : Symbol(foo, Decl(truthinessCallExpressionCoercion2.ts, 39, 15)) +>bar : Symbol(bar, Decl(truthinessCallExpressionCoercion2.ts, 40, 14)) +} + +class Foo { +>Foo : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) + + optional?: () => boolean; +>optional : Symbol(Foo.optional, Decl(truthinessCallExpressionCoercion2.ts, 58, 11)) + + required() { +>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) + + return true; + } + test() { +>test : Symbol(Foo.test, Decl(truthinessCallExpressionCoercion2.ts, 62, 5)) + + // error + this.required && console.log('required'); +>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) +>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // error + 1 && this.required && console.log('required'); +>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) +>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + + // ok + this.required && this.required(); +>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) +>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) +>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) + + // ok + this.required && 1 && this.required(); +>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) +>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this.required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) +>required : Symbol(Foo.required, Decl(truthinessCallExpressionCoercion2.ts, 59, 29)) + + // ok + 1 && this.optional && console.log('optional'); +>this.optional : Symbol(Foo.optional, Decl(truthinessCallExpressionCoercion2.ts, 58, 11)) +>this : Symbol(Foo, Decl(truthinessCallExpressionCoercion2.ts, 56, 1)) +>optional : Symbol(Foo.optional, Decl(truthinessCallExpressionCoercion2.ts, 58, 11)) +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) + } +} + diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.types b/tests/baselines/reference/truthinessCallExpressionCoercion2.types new file mode 100644 index 0000000000..f93ffcc0b3 --- /dev/null +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.types @@ -0,0 +1,318 @@ +=== tests/cases/compiler/truthinessCallExpressionCoercion2.ts === +function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) { +>test : (required1: () => boolean, required2: () => boolean, optional?: (() => boolean) | undefined) => void +>required1 : () => boolean +>required2 : () => boolean +>optional : (() => boolean) | undefined + + // error + required1 && console.log('required'); +>required1 && console.log('required') : void +>required1 : () => boolean +>console.log('required') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'required' : "required" + + // error + 1 && required1 && console.log('required'); +>1 && required1 && console.log('required') : void +>1 && required1 : () => boolean +>1 : 1 +>required1 : () => boolean +>console.log('required') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'required' : "required" + + // ok + required1 && required1(); +>required1 && required1() : boolean +>required1 : () => boolean +>required1() : boolean +>required1 : () => boolean + + // ok + required1 && 1 && required1(); +>required1 && 1 && required1() : boolean +>required1 && 1 : 1 +>required1 : () => boolean +>1 : 1 +>required1() : boolean +>required1 : () => boolean + + // ok + optional && console.log('optional'); +>optional && console.log('optional') : void | undefined +>optional : (() => boolean) | undefined +>console.log('optional') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'optional' : "optional" + + // ok + 1 && optional && console.log('optional'); +>1 && optional && console.log('optional') : void | undefined +>1 && optional : (() => boolean) | undefined +>1 : 1 +>optional : (() => boolean) | undefined +>console.log('optional') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'optional' : "optional" + + // ok + !!required1 && console.log('not required'); +>!!required1 && console.log('not required') : void +>!!required1 : true +>!required1 : false +>required1 : () => boolean +>console.log('not required') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'not required' : "not required" + + // ok + required1() && console.log('required call'); +>required1() && console.log('required call') : false | void +>required1() : boolean +>required1 : () => boolean +>console.log('required call') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'required call' : "required call" + + // ok + required1 && required2 && required1() && required2(); +>required1 && required2 && required1() && required2() : boolean +>required1 && required2 && required1() : boolean +>required1 && required2 : () => boolean +>required1 : () => boolean +>required2 : () => boolean +>required1() : boolean +>required1 : () => boolean +>required2() : boolean +>required2 : () => boolean + + // error + required1 && required2 && required1() && console.log('foo'); +>required1 && required2 && required1() && console.log('foo') : false | void +>required1 && required2 && required1() : boolean +>required1 && required2 : () => boolean +>required1 : () => boolean +>required2 : () => boolean +>required1() : boolean +>required1 : () => boolean +>console.log('foo') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'foo' : "foo" +} + +function checksConsole() { +>checksConsole : () => void + + // error + typeof window !== 'undefined' && window.console && +>typeof window !== 'undefined' && window.console && ((window.console as any).firebug || (window.console.exception && window.console.table)) : any +>typeof window !== 'undefined' && window.console : false | Console +>typeof window !== 'undefined' : boolean +>typeof window : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>window : Window & typeof globalThis +>'undefined' : "undefined" +>window.console : Console +>window : Window & typeof globalThis +>console : Console + + ((window.console as any).firebug || (window.console.exception && window.console.table)); +>((window.console as any).firebug || (window.console.exception && window.console.table)) : any +>(window.console as any).firebug || (window.console.exception && window.console.table) : any +>(window.console as any).firebug : any +>(window.console as any) : any +>window.console as any : any +>window.console : Console +>window : Window & typeof globalThis +>console : Console +>firebug : any +>(window.console.exception && window.console.table) : (tabularData?: any, properties?: string[] | undefined) => void +>window.console.exception && window.console.table : (tabularData?: any, properties?: string[] | undefined) => void +>window.console.exception : (message?: string | undefined, ...optionalParams: any[]) => void +>window.console : Console +>window : Window & typeof globalThis +>console : Console +>exception : (message?: string | undefined, ...optionalParams: any[]) => void +>window.console.table : (tabularData?: any, properties?: string[] | undefined) => void +>window.console : Console +>window : Window & typeof globalThis +>console : Console +>table : (tabularData?: any, properties?: string[] | undefined) => void +} + +function checksPropertyAccess() { +>checksPropertyAccess : () => void + + const x = { +>x : { foo: { bar(): boolean; }; } +>{ foo: { bar() { return true; } } } : { foo: { bar(): boolean; }; } + + foo: { +>foo : { bar(): boolean; } +>{ bar() { return true; } } : { bar(): boolean; } + + bar() { return true; } +>bar : () => boolean +>true : true + } + } + + // error + x.foo.bar && console.log('x.foo.bar'); +>x.foo.bar && console.log('x.foo.bar') : void +>x.foo.bar : () => boolean +>x.foo : { bar(): boolean; } +>x : { foo: { bar(): boolean; }; } +>foo : { bar(): boolean; } +>bar : () => boolean +>console.log('x.foo.bar') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'x.foo.bar' : "x.foo.bar" + + // error + 1 && x.foo.bar && console.log('x.foo.bar'); +>1 && x.foo.bar && console.log('x.foo.bar') : void +>1 && x.foo.bar : () => boolean +>1 : 1 +>x.foo.bar : () => boolean +>x.foo : { bar(): boolean; } +>x : { foo: { bar(): boolean; }; } +>foo : { bar(): boolean; } +>bar : () => boolean +>console.log('x.foo.bar') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'x.foo.bar' : "x.foo.bar" + + // ok + x.foo.bar && x.foo.bar(); +>x.foo.bar && x.foo.bar() : boolean +>x.foo.bar : () => boolean +>x.foo : { bar(): boolean; } +>x : { foo: { bar(): boolean; }; } +>foo : { bar(): boolean; } +>bar : () => boolean +>x.foo.bar() : boolean +>x.foo.bar : () => boolean +>x.foo : { bar(): boolean; } +>x : { foo: { bar(): boolean; }; } +>foo : { bar(): boolean; } +>bar : () => boolean + + // ok + x.foo.bar && 1 && x.foo.bar(); +>x.foo.bar && 1 && x.foo.bar() : boolean +>x.foo.bar && 1 : 1 +>x.foo.bar : () => boolean +>x.foo : { bar(): boolean; } +>x : { foo: { bar(): boolean; }; } +>foo : { bar(): boolean; } +>bar : () => boolean +>1 : 1 +>x.foo.bar() : boolean +>x.foo.bar : () => boolean +>x.foo : { bar(): boolean; } +>x : { foo: { bar(): boolean; }; } +>foo : { bar(): boolean; } +>bar : () => boolean +} + +class Foo { +>Foo : Foo + + optional?: () => boolean; +>optional : (() => boolean) | undefined + + required() { +>required : () => boolean + + return true; +>true : true + } + test() { +>test : () => void + + // error + this.required && console.log('required'); +>this.required && console.log('required') : void +>this.required : () => boolean +>this : this +>required : () => boolean +>console.log('required') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'required' : "required" + + // error + 1 && this.required && console.log('required'); +>1 && this.required && console.log('required') : void +>1 && this.required : () => boolean +>1 : 1 +>this.required : () => boolean +>this : this +>required : () => boolean +>console.log('required') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'required' : "required" + + // ok + this.required && this.required(); +>this.required && this.required() : boolean +>this.required : () => boolean +>this : this +>required : () => boolean +>this.required() : boolean +>this.required : () => boolean +>this : this +>required : () => boolean + + // ok + this.required && 1 && this.required(); +>this.required && 1 && this.required() : boolean +>this.required && 1 : 1 +>this.required : () => boolean +>this : this +>required : () => boolean +>1 : 1 +>this.required() : boolean +>this.required : () => boolean +>this : this +>required : () => boolean + + // ok + 1 && this.optional && console.log('optional'); +>1 && this.optional && console.log('optional') : void | undefined +>1 && this.optional : (() => boolean) | undefined +>1 : 1 +>this.optional : (() => boolean) | undefined +>this : this +>optional : (() => boolean) | undefined +>console.log('optional') : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>'optional' : "optional" + } +} + diff --git a/tests/cases/compiler/truthinessCallExpressionCoercion2.ts b/tests/cases/compiler/truthinessCallExpressionCoercion2.ts new file mode 100644 index 0000000000..60d3b9b969 --- /dev/null +++ b/tests/cases/compiler/truthinessCallExpressionCoercion2.ts @@ -0,0 +1,83 @@ +// @strictNullChecks: true +// @lib: esnext,dom + +function test(required1: () => boolean, required2: () => boolean, optional?: () => boolean) { + // error + required1 && console.log('required'); + + // error + 1 && required1 && console.log('required'); + + // ok + required1 && required1(); + + // ok + required1 && 1 && required1(); + + // ok + optional && console.log('optional'); + + // ok + 1 && optional && console.log('optional'); + + // ok + !!required1 && console.log('not required'); + + // ok + required1() && console.log('required call'); + + // ok + required1 && required2 && required1() && required2(); + + // error + required1 && required2 && required1() && console.log('foo'); +} + +function checksConsole() { + // error + typeof window !== 'undefined' && window.console && + ((window.console as any).firebug || (window.console.exception && window.console.table)); +} + +function checksPropertyAccess() { + const x = { + foo: { + bar() { return true; } + } + } + + // error + x.foo.bar && console.log('x.foo.bar'); + + // error + 1 && x.foo.bar && console.log('x.foo.bar'); + + // ok + x.foo.bar && x.foo.bar(); + + // ok + x.foo.bar && 1 && x.foo.bar(); +} + +class Foo { + optional?: () => boolean; + required() { + return true; + } + test() { + // error + this.required && console.log('required'); + + // error + 1 && this.required && console.log('required'); + + // ok + this.required && this.required(); + + // ok + this.required && 1 && this.required(); + + // ok + 1 && this.optional && console.log('optional'); + } +} diff --git a/tests/cases/fourslash/codeFixMissingCallParentheses11.ts b/tests/cases/fourslash/codeFixMissingCallParentheses11.ts index 74d8b5af2d..76e4b2a9a4 100644 --- a/tests/cases/fourslash/codeFixMissingCallParentheses11.ts +++ b/tests/cases/fourslash/codeFixMissingCallParentheses11.ts @@ -7,12 +7,14 @@ //// if (this.#test) { //// console.log('test') //// } +//// this.#test && console.log('test'); //// } ////} //// ////function foo() { //// function test() { return Math.random() > 0.5; } //// test ? console.log('test') : undefined; +//// test && console.log('test'); ////} //// ////function foo() { @@ -23,6 +25,7 @@ //// } //// x.foo.bar ? console.log('test') : undefined; //// if (x.foo.bar) {} +//// x.foo.bar && console.log('test'); ////} verify.codeFixAll({ @@ -35,12 +38,14 @@ verify.codeFixAll({ if (this.#test()) { console.log('test') } + this.#test() && console.log('test'); } } function foo() { function test() { return Math.random() > 0.5; } test() ? console.log('test') : undefined; + test() && console.log('test'); } function foo() { @@ -51,5 +56,6 @@ function foo() { } x.foo.bar() ? console.log('test') : undefined; if (x.foo.bar()) {} + x.foo.bar() && console.log('test'); }`, }); diff --git a/tests/cases/fourslash/codeFixMissingCallParentheses12.ts b/tests/cases/fourslash/codeFixMissingCallParentheses12.ts new file mode 100644 index 0000000000..096b38030d --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingCallParentheses12.ts @@ -0,0 +1,19 @@ +/// + +// @strictNullChecks: true +////function foo(fn: () => boolean) { +//// fn/**/ && console.log('test'); +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_missing_call_parentheses.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_missing_call_parentheses.message, + index: 0, + newFileContent: +`function foo(fn: () => boolean) { + fn() && console.log('test'); +}`, +}); diff --git a/tests/cases/fourslash/codeFixMissingCallParentheses13.ts b/tests/cases/fourslash/codeFixMissingCallParentheses13.ts new file mode 100644 index 0000000000..b6f41e6f3b --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingCallParentheses13.ts @@ -0,0 +1,21 @@ +/// + +// @strictNullChecks: true +////function foo() { +//// function test() { return Math.random() > 0.5; } +//// test/**/ && console.log('test'); +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_missing_call_parentheses.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_missing_call_parentheses.message, + index: 0, + newFileContent: +`function foo() { + function test() { return Math.random() > 0.5; } + test() && console.log('test'); +}`, +}); diff --git a/tests/cases/fourslash/codeFixMissingCallParentheses14.ts b/tests/cases/fourslash/codeFixMissingCallParentheses14.ts new file mode 100644 index 0000000000..e6a9668330 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingCallParentheses14.ts @@ -0,0 +1,29 @@ +/// + +// @strictNullChecks: true +////function foo() { +//// const x = { +//// foo: { +//// bar() { return true; } +//// } +//// } +//// x.foo.bar/**/ && console.log('test'); +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_missing_call_parentheses.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_missing_call_parentheses.message, + index: 0, + newFileContent: +`function foo() { + const x = { + foo: { + bar() { return true; } + } + } + x.foo.bar() && console.log('test'); +}`, +}); diff --git a/tests/cases/fourslash/codeFixMissingCallParentheses15.ts b/tests/cases/fourslash/codeFixMissingCallParentheses15.ts new file mode 100644 index 0000000000..d0f64a0343 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingCallParentheses15.ts @@ -0,0 +1,30 @@ +/// + +// @strictNullChecks: true +////class Foo { +//// test() { +//// return true; +//// } +//// run() { +//// this.test/**/ && console.log('test'); +//// } +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_missing_call_parentheses.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_missing_call_parentheses.message, + index: 0, + newFileContent: +`class Foo { + test() { + return true; + } + run() { + this.test() && console.log('test'); + } +}`, +}); + diff --git a/tests/cases/fourslash/codeFixMissingCallParentheses16.ts b/tests/cases/fourslash/codeFixMissingCallParentheses16.ts new file mode 100644 index 0000000000..dca4f385f2 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingCallParentheses16.ts @@ -0,0 +1,25 @@ +/// + +// @strictNullChecks: true +////class Foo { +//// #test = () => true; +//// run() { +//// this.#test/**/ && console.log('test'); +//// } +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_missing_call_parentheses.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_missing_call_parentheses.message, + index: 0, + newFileContent: +`class Foo { + #test = () => true; + run() { + this.#test() && console.log('test'); + } +}`, +}); diff --git a/tests/cases/fourslash/codeFixMissingCallParentheses17.ts b/tests/cases/fourslash/codeFixMissingCallParentheses17.ts new file mode 100644 index 0000000000..2eced6dcb4 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingCallParentheses17.ts @@ -0,0 +1,19 @@ +/// + +// @strictNullChecks: true +////function foo(fn: () => boolean) { +//// 1 && fn/**/ && console.log('test'); +////} + +verify.codeFixAvailable([ + { description: ts.Diagnostics.Add_missing_call_parentheses.message } +]); + +verify.codeFix({ + description: ts.Diagnostics.Add_missing_call_parentheses.message, + index: 0, + newFileContent: +`function foo(fn: () => boolean) { + 1 && fn() && console.log('test'); +}`, +});