import type { TSESTree , TSESLint } from '@typescript-eslint/experimental-utils' ; import { isIfStatement , isBlockStatement } from '../utils/nodes' ; import { areEquivalent } from '../utils/equivalence' ; import { collectIfBranches , takeWithoutBreak , collectSwitchBranches } from '../utils/conditions' ; import { report , issueLocation } from '../utils/locations' ; import docsUrl from '../utils/docs-url' ; const message = "This {{type}}'s code block is the same as the block for the {{type}} on line {{line}}." ; const rule = { meta : { messages : { sameConditionalBlock : message , sonarRuntime : '{{sonarRuntimeData}}' , } , type : 'problem' , docs : { description : 'Two branches in a conditional structure should not have exactly the same implementation' , recommended : 'error' , url : docsUrl ( __filename ) , } , schema : [ { enum : [ 'sonar-runtime' ] , } , ] , } , create ( context ) { return { IfStatement ( node ) { visitIfStatement ( node as TSESTree . IfStatement ) ; } , SwitchStatement ( node ) { visitSwitchStatement ( node as TSESTree . SwitchStatement ) ; } , } ; function visitIfStatement ( ifStmt ) { if ( isIfStatement ( ifStmt . parent ) ) { return ; } const { branches , endsWithElse } = collectIfBranches ( ifStmt ) ; if ( allEquivalentWithoutDefault ( branches , endsWithElse ) ) { branches . slice ( 1 ) . forEach ( ( branch , i ) => reportIssue ( branch , branches [ i ] , 'branch' ) ) ; return ; } for ( let i = 1 ; i < branches . length ; i ++ ) { if ( hasRequiredSize ( [ branches [ i ] ] ) ) { for ( let j = 0 ; j < i ; j ++ ) { if ( compareIfBranches ( branches [ i ] , branches [ j ] ) ) { break ; } } } } } function visitSwitchStatement ( switchStmt ) { const { cases } = switchStmt ; const { endsWithDefault } = collectSwitchBranches ( switchStmt ) ; const nonEmptyCases = cases . filter ( c => takeWithoutBreak ( expandSingleBlockStatement ( c . consequent ) ) . length > 0 ) ; const casesWithoutBreak = nonEmptyCases . map ( c => takeWithoutBreak ( expandSingleBlockStatement ( c . consequent ) ) ) ; if ( allEquivalentWithoutDefault ( casesWithoutBreak , endsWithDefault ) ) { nonEmptyCases . slice ( 1 ) . forEach ( ( caseStmt , i ) => reportIssue ( caseStmt , nonEmptyCases [ i ] , 'case' ) ) ; return ; } for ( let i = 1 ; i < cases . length ; i ++ ) { const firstClauseWithoutBreak = takeWithoutBreak ( expandSingleBlockStatement ( cases [ i ] . consequent ) ) ; if ( hasRequiredSize ( firstClauseWithoutBreak ) ) { for ( let j = 0 ; j < i ; j ++ ) { const secondClauseWithoutBreak = takeWithoutBreak ( expandSingleBlockStatement ( cases [ j ] . consequent ) ) ; if ( areEquivalent ( firstClauseWithoutBreak , secondClauseWithoutBreak , context . getSourceCode ( ) ) ) { reportIssue ( cases [ i ] , cases [ j ] , 'case' ) ; break ; } } } } } function hasRequiredSize ( nodes ) { if ( nodes . length > 0 ) { const tokens = [ ... context . getSourceCode ( ) . getTokens ( nodes [ 0 ] ) , ... context . getSourceCode ( ) . getTokens ( nodes [ nodes . length - 1 ] ) , ] . filter ( token => token . value !== '{' && token . value !== '}' ) ; return ( tokens . length > 0 && tokens [ tokens . length - 1 ] . loc . end . line > tokens [ 0 ] . loc . start . line ) ; } return false ; } function compareIfBranches ( a , b ) { const equivalent = areEquivalent ( a , b , context . getSourceCode ( ) ) ; if ( equivalent && b . loc ) { reportIssue ( a , b , 'branch' ) ; } return equivalent ; } function expandSingleBlockStatement ( nodes ) { if ( nodes . length === 1 ) { const node = nodes [ 0 ] ; if ( isBlockStatement ( node ) ) { return node . body ; } } return nodes ; } function allEquivalentWithoutDefault ( branches , endsWithDefault ) { return ( ! endsWithDefault && branches . length > 1 && branches . slice ( 1 ) . every ( ( branch , index ) => areEquivalent ( branch , branches [ index ] , context . getSourceCode ( ) ) ) ) ; } function reportIssue ( node , equivalentNode , type ) { const equivalentNodeLoc = equivalentNode . loc as TSESTree . SourceLocation ; report ( context , { messageId : 'sameConditionalBlock' , data : { type , line : String ( equivalentNode . loc . start . line ) } , node , } , [ issueLocation ( equivalentNodeLoc , equivalentNodeLoc , 'Original' ) ] , message ) ; } } , } ; export = rule ;