import type { TSESTree , TSESLint } from '@typescript-eslint/experimental-utils' ; import { isRequiredParserServices , RequiredParserServices } from '../utils/parser-services' ; import docsUrl from '../utils/docs-url' ; const CollectionLike = [ 'Array' , 'Map' , 'Set' , 'WeakMap' , 'WeakSet' ] ; const CollectionSizeLike = [ 'length' , 'size' ] ; const rule = { meta : { messages : { fixCollectionSizeCheck : 'Fix this expression; {{propertyName}} of "{{objectName}}" is always greater or equal to zero.' , } , schema : [ ] , type : 'problem' , docs : { description : 'Collection sizes and array length comparisons should make sense' , recommended : 'error' , url : docsUrl ( __filename ) , } , } , create ( context ) { const services = context . parserServices ; const isTypeCheckerAvailable = isRequiredParserServices ( services ) ; return { BinaryExpression : ( node ) => { const expr = node as TSESTree . BinaryExpression ; if ( [ '<' , '>=' ] . includes ( expr . operator ) ) { const lhs = expr . left ; const rhs = expr . right ; if ( isZeroLiteral ( rhs ) && lhs . type === 'MemberExpression' ) { const { object , property } = lhs ; if ( property . type === 'Identifier' && CollectionSizeLike . includes ( property . name ) && ( ! isTypeCheckerAvailable || isCollection ( object , services ! ) ) ) { context . report ( { messageId : 'fixCollectionSizeCheck' , data : { propertyName : property . name , objectName : context . getSourceCode ( ) . getText ( object ) , } , node , } ) ; } } } } , } ; } , } ; function isZeroLiteral ( node ) { return node . type === 'Literal' && node . value === 0 ; } function isCollection ( node , services ) { const checker = services . program . getTypeChecker ( ) ; const tp = checker . getTypeAtLocation ( services . esTreeNodeToTSNodeMap . get ( node ) ) ; return ! ! tp . symbol && CollectionLike . includes ( tp . symbol . name ) ; } export = rule ;