import * as ts from 'typescript' ; import * as Lint from 'tslint' ; import { ErrorTolerantWalker } from './utils/ErrorTolerantWalker' ; import { ExtendedMetadata } from './utils/ExtendedMetadata' ; /**
 * Implementation of the prefer-dry-conditionals rule.
 */ export class Rule extends Lint . Rules . AbstractRule { public static metadata : ExtendedMetadata = { ruleName : 'prefer-dry-conditionals' , type : 'maintainability' , description : "Don't-Repeat-Yourself in if statement conditionals, instead use Switch statements." , options : null , optionsDescription : '' , optionExamples : [ ] , typescriptOnly : false , issueClass : 'Non-SDL' , issueType : 'Warning' , severity : 'Low' , level : 'Opportunity for Excellence' , group : 'Clarity' , commonWeaknessEnumeration : '' , } ; public static FAILURE_STRING ( switchExpression , caseExpressions ) { const cases = caseExpressions . map ( text => `    case ${ text }:\n      // ...\n      break;` ) ; return ( "Don't Repeat Yourself in If statements. Try using a Switch statement instead:\n" + `  switch (${ switchExpression }) {\n${ cases . join ( '\n' ) }\n    default:\n      // ...\n}` ) ; } public apply ( sourceFile ) { return this . applyWithWalker ( new PreferDryConditionalsRuleWalker ( sourceFile , this . getOptions ( ) ) ) ; } } class PreferDryConditionalsRuleWalker extends ErrorTolerantWalker { private threshold : number = 1 ; constructor ( sourceFile , options ) { super ( sourceFile , options ) ; this . parseOptions ( ) ; } protected visitIfStatement ( node ) { this . checkAndReport ( node ) ; super . visitIfStatement ( node ) ; } private checkAndReport ( node ) { const { expression , parent } = node ; if ( ! ts . isIfStatement ( parent ) && ts . isBinaryExpression ( expression ) ) { const ifStatements = this . allNestedIfStatements ( node ) ; const exceedsThreshold = ifStatements . length > this . threshold ; if ( ! exceedsThreshold ) { return ; } const areAllBinaryExpressions = ifStatements . every ( statement => ts . isBinaryExpression ( statement . expression ) ) ; if ( areAllBinaryExpressions ) { const binaryExpressions = ifStatements . map ( statement => < ts . BinaryExpression > statement . expression ) ; this . checkBinaryExpressions ( binaryExpressions ) ; } } } private allNestedIfStatements ( node ) { const ifStatements = [ node ] ; let curr = node ; while ( ts . isIfStatement ( curr . elseStatement ) ) { ifStatements . push ( curr . elseStatement ) ; curr = curr . elseStatement ; } return ifStatements ; } private checkBinaryExpressions ( expressions ) { if ( expressions . length <= 1 ) { return ; } const firstExpression = expressions [ 0 ] ; const expectedOperatorToken = firstExpression . operatorToken ; const isEqualityCheck = expectedOperatorToken . kind === ts . SyntaxKind . EqualsEqualsToken || expectedOperatorToken . kind === ts . SyntaxKind . EqualsEqualsEqualsToken ; if ( ! isEqualityCheck ) { return ; } const expectedLeft = firstExpression . left ; const expectedRight = firstExpression . right ; const hasSameOperator = expressions . every ( expression => expression . operatorToken . kind === expectedOperatorToken . kind ) ; if ( ! hasSameOperator ) { return ; } const leftExpressions = expressions . map ( expression => expression . left ) ; const rightExpressions = expressions . map ( expression => expression . right ) ; const hasSameLeft = leftExpressions . every ( expression => expression . getText ( ) === expectedLeft . getText ( ) ) ; const hasSameRight = rightExpressions . every ( expression => expression . getText ( ) === expectedRight . getText ( ) ) ; if ( hasSameLeft ) { this . addFailureAtNode ( firstExpression . parent , Rule . FAILURE_STRING ( expectedLeft . getText ( ) , rightExpressions . map ( expression => expression . getText ( ) ) ) ) ; } else if ( hasSameRight ) { this . addFailureAtNode ( firstExpression . parent , Rule . FAILURE_STRING ( expectedRight . getText ( ) , leftExpressions . map ( expression => expression . getText ( ) ) ) ) ; } } private parseOptions ( ) { this . getOptions ( ) . forEach ( ( opt ) => { if ( typeof opt === 'boolean' ) { return ; } if ( typeof opt === 'number' ) { this . threshold = Math . max ( 1 , opt ) ; return ; } } ) ; } }