import type { Rule } from 'eslint' ; import type { AttributeToken , Loc , Token } from 'pug-lexer' ; import { processRule } from '../utils' ; param variable param expression returns function usesVariable ( variable , expression ) { if ( new RegExp ( `\\s?${ variable }(?!\\w)` ) . test ( expression ) ) { return true ; } if ( ( variable . startsWith ( '{' ) && variable . endsWith ( '}' ) ) || ( variable . startsWith ( '(' ) && variable . endsWith ( ')' ) ) ) { const variables = variable . slice ( 1 , - 1 ) . split ( ',' ) . map ( ( v ) => v . trim ( ) ) ; return variables . some ( ( variable ) => usesVariable ( variable , expression ) ) ; } return false ; } export default { meta : { type : 'suggestion' , docs : { description : 'disallow use v-if on the same element as v-for' , categories : [ 'vue3-essential' , 'essential' ] , url : 'https://eslint.vuejs.org/rules/no-use-v-if-with-v-for.html' } , fixable : undefined , schema : [ { type : 'object' , properties : { allowUsingIterationVar : { type : 'boolean' } } , additionalProperties : false } ] } , create ( context ) { return processRule ( context , ( ) => { const { allowUsingIterationVar = false } = context . options [ 0 ] ?? { } ; let lastStartAttributesTokenIndex ; return { 'start-attributes' ( _ , { index } ) { lastStartAttributesTokenIndex = index ; } , attribute ( token , { index , tokens } ) { if ( token . name !== 'v-if' ) { return ; } let endAttributesTokenIndex = index ; for ( let index2 = index ; index2 < tokens . length ; index2 ++ ) { endAttributesTokenIndex = index2 ; const element = tokens [ index2 ] ! ; if ( element . type === 'end-attributes' ) { break ; } } const attributeTokens = tokens . slice ( lastStartAttributesTokenIndex ! + 1 , endAttributesTokenIndex ) as AttributeToken [ ] ; const vForAttribute = attributeTokens . find ( ( attr ) => attr . name === 'v-for' ) ; if ( ! vForAttribute || typeof vForAttribute . val !== 'string' ) { return ; } const vForVar = vForAttribute . val . slice ( 1 , - 1 ) ; let [ iterationVariable , iteratorName ] = vForVar . split ( ' in ' ) ; iterationVariable = ( iterationVariable ?? '' ) . trim ( ) ; iteratorName = ( iteratorName ?? '' ) . trim ( ) ; const kind = /^(?!\d)\w+$/ . test ( iteratorName ) ? 'variable' : 'expression' ; let shouldMove = false ; const vIfValue = typeof token . val === 'string' ? token . val . slice ( 1 , - 1 ) . trim ( ) : String ( token . val ) ; if ( allowUsingIterationVar ) { if ( usesVariable ( iterationVariable , vIfValue ) ) { return ; } } else { if ( ! usesVariable ( iterationVariable , vIfValue ) ) { shouldMove = true ; } } const loc = token . loc ; const columnStart = loc . start . column - 1 ; const columnEnd = loc . end . column - 1 ; context . report ( { node : { } as unknown as Rule . Node , loc : { line : loc . start . line , column : loc . start . column - 1 , start : { line : loc . start . line , column : columnStart } , end : { line : loc . end . line , column : columnEnd } } , message : shouldMove ? "This 'v-if' should be moved to the wrapper element." : "The '{{iteratorName}}' {{kind}} inside 'v-for' directive should be replaced with a computed property that returns filtered array instead. You should not mix 'v-for' with 'v-if'." , data : shouldMove ? undefined : { iteratorName , kind } } ) ; } } ; } ) ; } } as Rule . RuleModule ;