import type { TSESLint , TSESTree } from '@typescript-eslint/experimental-utils' ; import { isReturnStatement , isThrowStatement , isIdentifier , isVariableDeclaration , } from '../utils/nodes' ; import docsUrl from '../utils/docs-url' ; const rule = { meta : { messages : { doImmediateAction : 'Immediately {{action}} this expression instead of assigning it to the temporary variable "{{variable}}".' , } , schema : [ ] , type : 'suggestion' , docs : { description : 'Local variables should not be declared and then immediately returned or thrown' , recommended : 'error' , url : docsUrl ( __filename ) , } , fixable : 'code' , } , create ( context ) { return { BlockStatement ( node ) { processStatements ( ( node as TSESTree . BlockStatement ) . body ) ; } , SwitchCase ( node ) { processStatements ( ( node as TSESTree . SwitchCase ) . consequent ) ; } , } ; function processStatements ( statements ) { if ( statements . length > 1 ) { const last = statements [ statements . length - 1 ] ; const returnedIdentifier = getOnlyReturnedVariable ( last ) ; const lastButOne = statements [ statements . length - 2 ] ; const declaredIdentifier = getOnlyDeclaredVariable ( lastButOne ) ; if ( returnedIdentifier && declaredIdentifier ) { const sameVariable = getVariables ( context ) . find ( variable => { return ( variable . references . find ( ref => ref . identifier === returnedIdentifier ) !== undefined && variable . references . find ( ref => ref . identifier === declaredIdentifier . id ) !== undefined ) ; } ) ; if ( sameVariable && sameVariable . references . filter ( ref => ref . isRead ( ) ) . length === 1 ) { context . report ( { messageId : 'doImmediateAction' , data : { action : isReturnStatement ( last ) ? 'return' : 'throw' , variable : returnedIdentifier . name , } , node : declaredIdentifier . init , fix : fixer => fix ( fixer , last , lastButOne , declaredIdentifier . init , returnedIdentifier ) , } ) ; } } } } function fix ( fixer , last , lastButOne , expressionToReturn , returnedExpression ) { const expressionText = context . getSourceCode ( ) . getText ( expressionToReturn ) ; const rangeToRemoveStart = lastButOne . range [ 0 ] ; const commentsBetweenStatements = context . getSourceCode ( ) . getCommentsAfter ( lastButOne ) ; const rangeToRemoveEnd = commentsBetweenStatements . length > 0 ? commentsBetweenStatements [ 0 ] . range [ 0 ] : last . range [ 0 ] ; return [ fixer . removeRange ( [ rangeToRemoveStart , rangeToRemoveEnd ] ) , fixer . replaceText ( returnedExpression , expressionText ) , ] ; } function getOnlyReturnedVariable ( node ) { return ( isReturnStatement ( node ) || isThrowStatement ( node ) ) && node . argument && isIdentifier ( node . argument ) ? node . argument : undefined ; } function getOnlyDeclaredVariable ( node ) { if ( isVariableDeclaration ( node ) && node . declarations . length === 1 ) { const { id , init } = node . declarations [ 0 ] ; if ( isIdentifier ( id ) && init && ! id . typeAnnotation ) { return { id , init } ; } } return undefined ; } function getVariables ( context ) { const { variableScope , variables : currentScopeVariables } = context . getScope ( ) ; if ( variableScope === context . getScope ( ) ) { return currentScopeVariables ; } else { return currentScopeVariables . concat ( variableScope . variables ) ; } } } , } ; export = rule ;