import type { TSESLint , TSESTree } from '@typescript-eslint/experimental-utils' ; import { areEquivalent } from '../utils/equivalence' ; import { isExpressionStatement , isMemberExpression , isAssignmentExpression , isLiteral , isIdentifier , isCallExpression , } from '../utils/nodes' ; import { report , issueLocation } from '../utils/locations' ; import docsUrl from '../utils/docs-url' ; const message = 'Verify this is the index that was intended; "{{index}}" was already set on line {{line}}.' ; const rule = { meta : { messages : { verifyIntendedIndex : message , sonarRuntime : '{{sonarRuntimeData}}' , } , type : 'problem' , docs : { description : 'Collection elements should not be replaced unconditionally' , recommended : 'error' , url : docsUrl ( __filename ) , } , schema : [ { enum : [ 'sonar-runtime' ] , } , ] , } , create ( context ) { return { SwitchCase ( node ) { const switchCase = node as TSESTree . SwitchCase ; checkStatements ( switchCase . consequent ) ; } , BlockStatement ( node ) { const block = node as TSESTree . BlockStatement ; checkStatements ( block . body ) ; } , Program ( node ) { const program = node as TSESTree . Program ; checkStatements ( program . body ) ; } , } ; function checkStatements ( statements ) { const usedKeys = new Map ( ) ; let collection ; statements . forEach ( statement => { const keyWriteUsage = getKeyWriteUsage ( statement ) ; if ( keyWriteUsage ) { if ( collection && ! areEquivalent ( keyWriteUsage . collectionNode , collection , context . getSourceCode ( ) ) ) { usedKeys . clear ( ) ; } const sameKeyWriteUsage = usedKeys . get ( keyWriteUsage . indexOrKey ) ; if ( sameKeyWriteUsage && sameKeyWriteUsage . node . loc ) { const sameKeyWriteUsageLoc = sameKeyWriteUsage . node . loc ; const secondaryLocations = [ issueLocation ( sameKeyWriteUsageLoc , sameKeyWriteUsageLoc , 'Original value' ) , ] ; report ( context , { node : keyWriteUsage . node , messageId : 'verifyIntendedIndex' , data : { index : keyWriteUsage . indexOrKey , line : sameKeyWriteUsage . node . loc . start . line , } , } , secondaryLocations , message ) ; } usedKeys . set ( keyWriteUsage . indexOrKey , keyWriteUsage ) ; collection = keyWriteUsage . collectionNode ; } else { usedKeys . clear ( ) ; } } ) ; } function getKeyWriteUsage ( node ) { if ( isExpressionStatement ( node ) ) { return arrayKeyWriteUsage ( node . expression ) || mapOrSetKeyWriteUsage ( node . expression ) ; } return undefined ; } function arrayKeyWriteUsage ( node ) { if ( isSimpleAssignment ( node ) && isMemberExpression ( node . left ) && node . left . computed ) { const { left , right } = node ; const index = extractIndex ( left . property ) ; if ( index !== undefined && ! isUsed ( left . object , right ) ) { return { collectionNode : left . object , indexOrKey : index , node , } ; } } return undefined ; } function mapOrSetKeyWriteUsage ( node ) { if ( isCallExpression ( node ) && isMemberExpression ( node . callee ) ) { const propertyAccess = node . callee ; if ( isIdentifier ( propertyAccess . property ) ) { const methodName = propertyAccess . property . name ; const addMethod = methodName === 'add' && node . arguments . length === 1 ; const setMethod = methodName === 'set' && node . arguments . length === 2 ; if ( addMethod || setMethod ) { const key = extractIndex ( node . arguments [ 0 ] ) ; if ( key ) { return { collectionNode : propertyAccess . object , indexOrKey : key , node , } ; } } } } return undefined ; } function extractIndex ( node ) { if ( isLiteral ( node ) ) { const { value } = node ; return typeof value === 'number' || typeof value === 'string' ? String ( value ) : undefined ; } else if ( isIdentifier ( node ) ) { return node . name ; } return undefined ; } function isUsed ( value , expression ) { const valueTokens = context . getSourceCode ( ) . getTokens ( value ) ; const expressionTokens = context . getSourceCode ( ) . getTokens ( expression ) ; const foundUsage = expressionTokens . find ( ( token , index ) => { if ( eq ( token , valueTokens [ 0 ] ) ) { for ( let expressionIndex = index , valueIndex = 0 ; expressionIndex < expressionTokens . length && valueIndex < valueTokens . length ; expressionIndex ++ , valueIndex ++ ) { if ( ! eq ( expressionTokens [ expressionIndex ] , valueTokens [ valueIndex ] ) ) { break ; } else if ( valueIndex === valueTokens . length - 1 ) { return true ; } } } return false ; } ) ; return foundUsage !== undefined ; } } , } ; function eq ( token1 , token2 ) { return token1 . value === token2 . value ; } function isSimpleAssignment ( node ) { return isAssignmentExpression ( node ) && node . operator === '=' ; } interface KeyWriteCollectionUsage { collectionNode : TSESTree . Node ; indexOrKey : string ; node : TSESTree . Node ; } export = rule ;