Skip to content
This repository has been archived by the owner on Mar 25, 2021. It is now read-only.

Used checker.getTypeArguments in return-undefined rule when possible #4866

Merged
merged 5 commits into from
Oct 5, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 78 additions & 58 deletions src/rules/returnUndefinedRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker) {
return;
}

const returnKindFromType = getReturnKindFromFunction(functionReturningFrom, checker);
const returnKindFromType = getReturnKindFromFunction(functionReturningFrom);
if (returnKindFromType !== undefined && returnKindFromType !== actualReturnKind) {
ctx.addFailureAtNode(
node,
Expand All @@ -82,6 +82,83 @@ function walk(ctx: Lint.WalkContext, checker: ts.TypeChecker) {
);
}
}

function getReturnKindFromFunction(node: FunctionLike): ReturnKind | undefined {
switch (node.kind) {
case ts.SyntaxKind.Constructor:
case ts.SyntaxKind.SetAccessor:
return ReturnKind.Void;
case ts.SyntaxKind.GetAccessor:
return ReturnKind.Value;
}

// Handle generator functions/methods:
if (node.asteriskToken !== undefined) {
return ReturnKind.Void;
}

const contextual =
isFunctionExpressionLike(node) && node.type === undefined
? tryGetReturnType(checker.getContextualType(node), checker)
: undefined;
const returnType =
contextual !== undefined
? contextual
: tryGetReturnType(checker.getTypeAtLocation(node), checker);

if (returnType === undefined || isTypeFlagSet(returnType, ts.TypeFlags.Any)) {
return undefined;
}

const effectivelyVoidChecker = hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword)
? isEffectivelyVoidPromise
: isEffectivelyVoid;

if (effectivelyVoidChecker(returnType)) {
return ReturnKind.Void;
}

return ReturnKind.Value;
}

/** True for `void`, `undefined`, Promise<void>, or `void | undefined | Promise<void>`. */
function isEffectivelyVoidPromise(type: ts.Type): boolean {
// Would need access to `checker.getPromisedTypeOfPromise` to do this properly.
// Assume that the return type is the global Promise (since this is an async function) and get its type argument.

if (
// tslint:disable-next-line:no-bitwise
isTypeFlagSet(type, ts.TypeFlags.Void | ts.TypeFlags.Undefined) ||
(isUnionType(type) && type.types.every(isEffectivelyVoidPromise))
) {
return true;
}

const typeArguments = getTypeArgumentsOfType(type);

return (
typeArguments !== undefined &&
typeArguments.length === 1 &&
isEffectivelyVoidPromise(typeArguments[0])
);
}

function getTypeArgumentsOfType(type: ts.Type) {
if (!isTypeReference(type)) {
return undefined;
}

// tslint:disable:no-unsafe-any
// Fixes for https://github.com/palantir/tslint/issues/4863
// type.typeArguments was replaced with checker.getTypeArguments:
// https://github.com/microsoft/TypeScript/commit/250d5a8229e17342f36fe52545bb68140db96a2e
if ((checker as any).getTypeArguments) {
return (checker as any).getTypeArguments(type) as ts.Type[] | undefined;
}

return (type as any).typeArguments as ts.Type[] | undefined;
// tslint:enable:no-unsafe-any
}
}

function getReturnKindFromReturnStatement(node: ts.ReturnStatement): ReturnKind | undefined {
Expand All @@ -108,63 +185,6 @@ type FunctionLike =
| ts.GetAccessorDeclaration
| ts.SetAccessorDeclaration;

function getReturnKindFromFunction(
node: FunctionLike,
checker: ts.TypeChecker,
): ReturnKind | undefined {
switch (node.kind) {
case ts.SyntaxKind.Constructor:
case ts.SyntaxKind.SetAccessor:
return ReturnKind.Void;
case ts.SyntaxKind.GetAccessor:
return ReturnKind.Value;
}

// Handle generator functions/methods:
if (node.asteriskToken !== undefined) {
return ReturnKind.Void;
}

const contextual =
isFunctionExpressionLike(node) && node.type === undefined
? tryGetReturnType(checker.getContextualType(node), checker)
: undefined;
const returnType =
contextual !== undefined
? contextual
: tryGetReturnType(checker.getTypeAtLocation(node), checker);

if (returnType === undefined || isTypeFlagSet(returnType, ts.TypeFlags.Any)) {
return undefined;
}

const effectivelyVoidChecker = hasModifier(node.modifiers, ts.SyntaxKind.AsyncKeyword)
? isEffectivelyVoidPromise
: isEffectivelyVoid;

if (effectivelyVoidChecker(returnType)) {
return ReturnKind.Void;
}

return ReturnKind.Value;
}

/** True for `void`, `undefined`, Promise<void>, or `void | undefined | Promise<void>`. */
function isEffectivelyVoidPromise(type: ts.Type): boolean {
// Would need access to `checker.getPromisedTypeOfPromise` to do this properly.
// Assume that the return type is the global Promise (since this is an async function) and get its type argument.

return (
// tslint:disable-next-line:no-bitwise
isTypeFlagSet(type, ts.TypeFlags.Void | ts.TypeFlags.Undefined) ||
(isUnionType(type) && type.types.every(isEffectivelyVoidPromise)) ||
(isTypeReference(type) &&
type.typeArguments !== undefined &&
type.typeArguments.length === 1 &&
isEffectivelyVoidPromise(type.typeArguments[0]))
);
}

/** True for `void`, `undefined`, or `void | undefined`. */
function isEffectivelyVoid(type: ts.Type): boolean {
return (
Expand Down