import type { Rule } from 'eslint' ; import type { AttributeToken , Loc , TagToken , Token } from 'pug-lexer' ; import { processRule } from '../utils' ; import { getAttributeTokens , getChildTags } from '../utils/pug-utils' ; import { isCustomComponent } from '../utils/vue' ; export default { meta : { type : 'problem' , docs : { description : 'require `v-bind:key` with `v-for` directives' , categories : [ 'vue3-essential' , 'essential' ] , url : 'https://eslint.vuejs.org/rules/require-v-for-key.html' } , fixable : undefined , schema : [ ] } , create ( context ) { return processRule ( context , ( ) => { let lastTagTokenIndex ; let lastStartAttributesTokenIndex ; return { tag ( _ , { index } ) { lastTagTokenIndex = index ; } , 'start-attributes' ( _ , { index } ) { lastStartAttributesTokenIndex = index ; } , attribute ( token , { index , tokens } ) { if ( token . name !== 'v-for' ) { 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 [ ] ; if ( attributeTokens . some ( ( attr ) => /^(v-bind)?:key$/ . test ( attr . name ) ) ) { return ; } const lastTagToken = tokens [ lastTagTokenIndex ! ] as TagToken | undefined ; if ( lastTagToken && ( lastTagToken . val === 'template' || lastTagToken . val === 'slot' ) ) { const childTagTokens = getChildTags ( lastTagToken , tokens ) ; if ( childTagTokens . every ( ( tag ) => tag . val === 'template' || tag . val === 'slot' ) ) { return ; } let foundChildTagWithKey = false ; for ( const childTag of childTagTokens ) { const attributeTokens = getAttributeTokens ( childTag , tokens ) ; if ( attributeTokens . some ( ( attr ) => /^(v-bind)?:key$/ . test ( attr . name ) ) ) { foundChildTagWithKey = true ; break ; } } if ( foundChildTagWithKey ) { return ; } } if ( lastTagToken && isCustomComponent ( lastTagToken , tokens ) ) { return ; } const loc = token . loc ; const columnStart = loc . start . column - 1 ; const columnEnd = columnStart + 'v-for' . length ; 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 : "Elements in iteration expect to have 'v-bind:key' directives." } ) ; } } ; } ) ; } } as Rule . RuleModule ;