import type { TSESTree , TSESLint } from '@typescript-eslint/experimental-utils' ; import { isFunctionExpression , isArrowFunctionExpression , isBlockStatement } from '../utils/nodes' ; import docsUrl from '../utils/docs-url' ; function isReturnValueUsed ( callExpr ) { const { parent } = callExpr ; if ( ! parent ) { return false ; } if ( parent . type === 'LogicalExpression' ) { return parent . left === callExpr ; } if ( parent . type === 'SequenceExpression' ) { return parent . expressions [ parent . expressions . length - 1 ] === callExpr ; } if ( parent . type === 'ConditionalExpression' ) { return parent . test === callExpr ; } return ( parent . type !== 'ExpressionStatement' && parent . type !== 'ArrowFunctionExpression' && parent . type !== 'UnaryExpression' && parent . type !== 'AwaitExpression' && parent . type !== 'ReturnStatement' && parent . type !== 'ThrowStatement' ) ; } const rule = { meta : { messages : { removeUseOfOutput : 'Remove this use of the output from "{{name}}"; "{{name}}" doesn\'t return anything.' , } , schema : [ ] , type : 'problem' , docs : { description : "The output of functions that don't return anything should not be used" , recommended : 'error' , url : docsUrl ( __filename ) , } , } , create ( context ) { const callExpressionsToCheck = new Map ( ) ; const functionsWithReturnValue = new Set ( ) ; return { CallExpression ( node ) { const callExpr = node as TSESTree . CallExpression ; if ( ! isReturnValueUsed ( callExpr ) ) { return ; } const scope = context . getScope ( ) ; const reference = scope . references . find ( ref => ref . identifier === callExpr . callee ) ; if ( reference && reference . resolved ) { const variable = reference . resolved ; if ( variable . defs . length === 1 ) { const definition = variable . defs [ 0 ] ; if ( definition . type === 'FunctionName' ) { callExpressionsToCheck . set ( reference . identifier , definition . node ) ; } else if ( definition . type === 'Variable' ) { const { init } = definition . node ; if ( init && ( isFunctionExpression ( init ) || isArrowFunctionExpression ( init ) ) ) { callExpressionsToCheck . set ( reference . identifier , init ) ; } } } } } , ReturnStatement ( node ) { const returnStmt = node as TSESTree . ReturnStatement ; if ( returnStmt . argument ) { const ancestors = [ ... context . getAncestors ( ) ] . reverse ( ) ; const functionNode = ancestors . find ( node => node . type === 'FunctionExpression' || node . type === 'FunctionDeclaration' || node . type === 'ArrowFunctionExpression' ) ; functionsWithReturnValue . add ( functionNode as TSESTree . FunctionLike ) ; } } , ArrowFunctionExpression ( node ) { const arrowFunc = node as TSESTree . ArrowFunctionExpression ; if ( arrowFunc . expression ) { functionsWithReturnValue . add ( arrowFunc ) ; } } , ':function' ( node ) { const func = node as TSESTree . FunctionExpression | TSESTree . FunctionDeclaration | TSESTree . ArrowFunctionExpression ; if ( func . async || func . generator || ( isBlockStatement ( func . body ) && func . body . body . length === 0 ) ) { functionsWithReturnValue . add ( func ) ; } } , 'Program:exit' ( ) { callExpressionsToCheck . forEach ( ( functionDeclaration , callee ) => { if ( ! functionsWithReturnValue . has ( functionDeclaration ) ) { context . report ( { messageId : 'removeUseOfOutput' , node : callee , data : { name : callee . name } , } ) ; } } ) ; } , } ; } , } ; export = rule ;