fileoverview import * as tsutils from 'tsutils' ; import * as ts from 'typescript' ; import { Checker } from '../checker' ; import { ErrorCode } from '../error_code' ; import { AbstractRule } from '../rule' ; const FAILURE_STRING = 'return value is unused.' + '\n\tSee http://tsetse.info/check-return-value' ; const METHODS_TO_CHECK = new Set < string > ( [ [ 'Array' , 'concat' ] , [ 'Array' , 'filter' ] , [ 'Array' , 'map' ] , [ 'Array' , 'slice' ] , [ 'Function' , 'bind' ] , [ 'Object' , 'create' ] , [ 'string' , 'concat' ] , [ 'string' , 'normalize' ] , [ 'string' , 'padStart' ] , [ 'string' , 'padEnd' ] , [ 'string' , 'repeat' ] , [ 'string' , 'slice' ] , [ 'string' , 'split' ] , [ 'string' , 'substr' ] , [ 'string' , 'substring' ] , [ 'string' , 'toLocaleLowerCase' ] , [ 'string' , 'toLocaleUpperCase' ] , [ 'string' , 'toLowerCase' ] , [ 'string' , 'toUpperCase' ] , [ 'string' , 'trim' ] , ] . map ( list => list . join ( '#' ) ) ) ; /** A rule to ensure required return values from common functions are used. */ export class Rule extends AbstractRule { static readonly RULE_NAME = 'check-return-value' ; readonly ruleName = Rule . RULE_NAME ; readonly code = ErrorCode . CHECK_RETURN_VALUE ; register ( checker ) { checker . on ( ts . SyntaxKind . CallExpression , checkCallExpression , this . code ) ; } } function checkCallExpression ( checker , node ) { if ( tsutils . isExpressionValueUsed ( node ) ) { return ; } const signature = checker . typeChecker . getResolvedSignature ( node ) ; if ( signature !== undefined ) { const returnType = checker . typeChecker . getReturnTypeOfSignature ( signature ) ; if ( ! ! ( returnType . flags & ts . TypeFlags . Void ) ) { return ; } if ( ! isBlackListed ( node , checker . typeChecker ) && ! hasCheckReturnValueJsDoc ( node , checker . typeChecker ) ) { return ; } checker . addFailureAtNode ( node , FAILURE_STRING ) ; } } function isBlackListed ( node , tc ) { type AccessExpression = ts . PropertyAccessExpression | ts . ElementAccessExpression ; switch ( node . expression . kind ) { case ts . SyntaxKind . PropertyAccessExpression : case ts . SyntaxKind . ElementAccessExpression : const nodeExpression = ( node . expression as AccessExpression ) . expression ; const nodeExpressionString = nodeExpression . getText ( ) ; const nodeType = tc . getTypeAtLocation ( nodeExpression ) ; let nodeTypeString = tc . typeToString ( nodeType ) ; if ( nodeTypeString . endsWith ( '[]' ) ) { nodeTypeString = 'Array' ; } if ( nodeTypeString === 'ObjectConstructor' ) { nodeTypeString = 'Object' ; } if ( tsutils . isTypeFlagSet ( nodeType , ts . TypeFlags . StringLiteral ) ) { nodeTypeString = 'string' ; } let nodeFunction = '' ; if ( tsutils . isPropertyAccessExpression ( node . expression ) ) { nodeFunction = node . expression . name . getText ( ) ; } if ( tsutils . isElementAccessExpression ( node . expression ) ) { const argument = node . expression . argumentExpression ; if ( argument !== undefined ) { nodeFunction = argument . getText ( ) ; } } if ( METHODS_TO_CHECK . has ( `${ nodeTypeString }#${ nodeFunction }` ) || METHODS_TO_CHECK . has ( `${ nodeExpressionString }#${ nodeFunction }` ) ) { return true ; } if ( ( `${ nodeTypeString }#${ nodeFunction }` === 'string#replace' ) || ( `${ nodeExpressionString }#${ nodeFunction }` === 'string#replace' ) ) { return node . arguments . length === 2 && ! tsutils . isFunctionWithBody ( node . arguments [ 1 ] ) ; } break ; case ts . SyntaxKind . Identifier : const identifier = node . expression as ts . Identifier ; if ( METHODS_TO_CHECK . has ( identifier . text ) ) { return true ; } break ; default : break ; } return false ; } function hasCheckReturnValueJsDoc ( node , tc ) { let symbol = tc . getSymbolAtLocation ( node . expression ) ; if ( symbol === undefined ) { return false ; } if ( tsutils . isSymbolFlagSet ( symbol , ts . SymbolFlags . Alias ) ) { symbol = tc . getAliasedSymbol ( symbol ) ; } for ( const jsDocTagInfo of symbol . getJsDocTags ( ) ) { if ( jsDocTagInfo . name === 'checkReturnValue' ) { return true ; } } return false ; }