mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
Merge pull request #33178 from jwbay/nonNullableCallSignaturesTreeWalk
Flag non-nullable functions in `if` statements as errors (tree walk version)
This commit is contained in:
@@ -28306,7 +28306,8 @@ namespace ts {
|
||||
// Grammar checking
|
||||
checkGrammarStatementInAmbientContext(node);
|
||||
|
||||
checkTruthinessExpression(node.expression);
|
||||
const type = checkTruthinessExpression(node.expression);
|
||||
checkTestingKnownTruthyCallableType(node, type);
|
||||
checkSourceElement(node.thenStatement);
|
||||
|
||||
if (node.thenStatement.kind === SyntaxKind.EmptyStatement) {
|
||||
@@ -28316,6 +28317,57 @@ namespace ts {
|
||||
checkSourceElement(node.elseStatement);
|
||||
}
|
||||
|
||||
function checkTestingKnownTruthyCallableType(ifStatement: IfStatement, type: Type) {
|
||||
if (!strictNullChecks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const testedNode = isIdentifier(ifStatement.expression)
|
||||
? ifStatement.expression
|
||||
: isPropertyAccessExpression(ifStatement.expression)
|
||||
? ifStatement.expression.name
|
||||
: undefined;
|
||||
|
||||
if (!testedNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const possiblyFalsy = getFalsyFlags(type);
|
||||
if (possiblyFalsy) {
|
||||
return;
|
||||
}
|
||||
|
||||
// While it technically should be invalid for any known-truthy value
|
||||
// to be tested, we de-scope to functions unrefenced in the block as a
|
||||
// heuristic to identify the most common bugs. There are too many
|
||||
// false positives for values sourced from type definitions without
|
||||
// strictNullChecks otherwise.
|
||||
const callSignatures = getSignaturesOfType(type, SignatureKind.Call);
|
||||
if (callSignatures.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const testedFunctionSymbol = getSymbolAtLocation(testedNode);
|
||||
if (!testedFunctionSymbol) {
|
||||
return;
|
||||
}
|
||||
|
||||
const functionIsUsedInBody = forEachChild(ifStatement.thenStatement, function check(childNode): boolean | undefined {
|
||||
if (isIdentifier(childNode)) {
|
||||
const childSymbol = getSymbolAtLocation(childNode);
|
||||
if (childSymbol && childSymbol.id === testedFunctionSymbol.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return forEachChild(childNode, check);
|
||||
});
|
||||
|
||||
if (!functionIsUsedInBody) {
|
||||
error(ifStatement.expression, Diagnostics.This_condition_will_always_return_true_since_the_function_is_always_defined_Did_you_mean_to_call_it_instead);
|
||||
}
|
||||
}
|
||||
|
||||
function checkDoStatement(node: DoStatement) {
|
||||
// Grammar checking
|
||||
checkGrammarStatementInAmbientContext(node);
|
||||
|
||||
@@ -1654,7 +1654,7 @@ namespace ts {
|
||||
*/
|
||||
export function compose<T>(...args: ((t: T) => T)[]): (t: T) => T;
|
||||
export function compose<T>(a: (t: T) => T, b: (t: T) => T, c: (t: T) => T, d: (t: T) => T, e: (t: T) => T): (t: T) => T {
|
||||
if (e) {
|
||||
if (!!e) {
|
||||
const args: ((t: T) => T)[] = [];
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
args[i] = arguments[i];
|
||||
|
||||
@@ -2722,7 +2722,10 @@
|
||||
"category": "Error",
|
||||
"code": 2773
|
||||
},
|
||||
|
||||
"This condition will always return true since the function is always defined. Did you mean to call it instead?": {
|
||||
"category": "Error",
|
||||
"code": 2774
|
||||
},
|
||||
"Import declaration '{0}' is using private name '{1}'.": {
|
||||
"category": "Error",
|
||||
"code": 4000
|
||||
|
||||
@@ -1389,18 +1389,16 @@ namespace ts {
|
||||
// try to verify results of module resolution
|
||||
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
|
||||
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory);
|
||||
if (resolveModuleNamesWorker) {
|
||||
const moduleNames = getModuleNames(newSourceFile);
|
||||
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile);
|
||||
// ensure that module resolution results are still correct
|
||||
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
|
||||
if (resolutionsChanged) {
|
||||
oldProgram.structureIsReused = StructureIsReused.SafeModules;
|
||||
newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions);
|
||||
}
|
||||
else {
|
||||
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
|
||||
}
|
||||
const moduleNames = getModuleNames(newSourceFile);
|
||||
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile);
|
||||
// ensure that module resolution results are still correct
|
||||
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
|
||||
if (resolutionsChanged) {
|
||||
oldProgram.structureIsReused = StructureIsReused.SafeModules;
|
||||
newSourceFile.resolvedModules = zipToMap(moduleNames, resolutions);
|
||||
}
|
||||
else {
|
||||
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
|
||||
}
|
||||
if (resolveTypeReferenceDirectiveNamesWorker) {
|
||||
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
|
||||
|
||||
Reference in New Issue
Block a user