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

Commit

Permalink
fix: Use checker.getTypeArguments in return-undefined rule when possi…
Browse files Browse the repository at this point in the history
…ble (#4866)
  • Loading branch information
Josh Goldberg authored and adidahiya committed Oct 5, 2019
1 parent 88fbdf2 commit f0236ee
Showing 1 changed file with 78 additions and 58 deletions.
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

0 comments on commit f0236ee

Please sign in to comment.