import type { TSESTree , TSESLint } from '@typescript-eslint/experimental-utils' ; import { Rule } from 'eslint' ; import { isContinueStatement } from '../utils/nodes' ; import docsUrl from '../utils/docs-url' ; import CodePath = Rule . CodePath ; const rule = { meta : { messages : { refactorLoop : 'Refactor this loop to do more than one iteration.' , } , schema : [ ] , type : 'problem' , docs : { description : 'Loops with at most one iteration should be refactored' , recommended : 'error' , url : docsUrl ( __filename ) , } , } , create ( context ) { const loopingNodes = new Set ( ) ; const loops = new Set ( ) ; const loopsAndTheirSegments = [ ] ; const currentCodePaths = [ ] ; return { ForStatement ( node ) { loops . add ( node ) ; } , WhileStatement ( node ) { loops . add ( node ) ; } , DoWhileStatement ( node ) { loops . add ( node ) ; } , onCodePathStart ( codePath ) { currentCodePaths . push ( codePath ) ; } , onCodePathEnd ( ) { currentCodePaths . pop ( ) ; } , 'WhileStatement > *' ( node ) { visitLoopChild ( node . parent as TSESTree . WhileStatement ) ; } , 'ForStatement > *' ( node ) { visitLoopChild ( node . parent as TSESTree . ForStatement ) ; } , onCodePathSegmentLoop ( _ , toSegment , node ) { if ( isContinueStatement ( node ) ) { loopsAndTheirSegments . forEach ( ( { segments , loop } ) => { if ( segments . includes ( toSegment ) ) { loopingNodes . add ( loop ) ; } } ) ; } else { loopingNodes . add ( node ) ; } } , 'Program:exit' ( ) { loops . forEach ( loop => { if ( ! loopingNodes . has ( loop ) ) { context . report ( { messageId : 'refactorLoop' , loc : context . getSourceCode ( ) . getFirstToken ( loop ) ! . loc , } ) ; } } ) ; } , } ; function visitLoopChild ( parent ) { if ( currentCodePaths . length > 0 ) { const currentCodePath = currentCodePaths [ currentCodePaths . length - 1 ] ; loopsAndTheirSegments . push ( { segments : currentCodePath . currentSegments , loop : parent } ) ; } } } , } ; export = rule ;