import type { Rule } from 'eslint' ; import type { AttributeToken , Loc } from 'pug-lexer' ; import { processRule } from '../utils' ; import { isCustomComponent } from '../utils/vue' ; const SYSTEM_MODIFIERS = [ 'ctrl' , 'shift' , 'alt' , 'meta' ] ; const GLOBAL_MODIFIERS = [ 'stop' , 'prevent' , 'capture' , 'self' , 'once' , 'passive' , 'native' ] ; /**
 * Checks whether given modifier is a key modifier.
 */ function isKeyModifier ( modifier ) { return ! GLOBAL_MODIFIERS . includes ( modifier ) && ! SYSTEM_MODIFIERS . includes ( modifier ) ; } /**
 * Checks whether given modifier is system one.
 */ function isSystemModifier ( modifier ) { return SYSTEM_MODIFIERS . includes ( modifier ) ; } /**
 * Checks whether given any of provided modifiers has system modifier.
 */ function hasSystemModifier ( modifiers ) { return modifiers . some ( isSystemModifier ) ; } function extractName ( event ) { return event . replace ( /^(v-on:|@)/ , '' ) . split ( '.' ) [ 0 ] ?? '' ; } function extractModifiers ( event ) { return event . replace ( /^(v-on:|@)(\w+).?/ , '' ) . split ( '.' ) ; } returns function groupEvents ( events ) { return events . reduce < Record < string , AttributeToken [ ] > > ( ( acc , event ) => { const name = extractName ( event . name ) ; if ( acc [ name ] ) { acc [ name ] ! . push ( event ) ; } else { acc [ name ] = [ event ] ; } return acc ; } , { } ) ; } returns function getSystemModifiersString ( modifiers ) { return modifiers . filter ( isSystemModifier ) . sort ( ) . join ( ',' ) ; } returns function getKeyModifiersString ( modifiers ) { return modifiers . filter ( isKeyModifier ) . sort ( ) . join ( ',' ) ; } /**
 * Compares two events based on their modifiers to detect possible event leakage.
 */ function hasConflictedModifiers ( baseEvent , event ) { if ( event === baseEvent ) { return false ; } const eventModifiers = extractModifiers ( event . name ) ; if ( eventModifiers . includes ( 'exact' ) ) { return false ; } const baseEventModifiers = extractModifiers ( baseEvent . name ) ; const eventKeyModifiers = getKeyModifiersString ( eventModifiers ) ; const baseEventKeyModifiers = getKeyModifiersString ( baseEventModifiers ) ; if ( eventKeyModifiers && baseEventKeyModifiers && eventKeyModifiers !== baseEventKeyModifiers ) { return false ; } const eventSystemModifiers = getSystemModifiersString ( eventModifiers ) ; const baseEventSystemModifiers = getSystemModifiersString ( baseEventModifiers ) ; return ( baseEventModifiers . length >= 1 && baseEventSystemModifiers !== eventSystemModifiers && baseEventSystemModifiers . indexOf ( eventSystemModifiers ) > - 1 ) ; } returns function findConflictedEvents ( events ) { return events . reduce < AttributeToken [ ] > ( ( acc , event ) => { return [ ... acc , ... events . filter ( ( evt ) => ! acc . find ( ( e ) => evt === e ) ) . filter ( hasConflictedModifiers . bind ( null , event ) ) ] ; } , [ ] ) ; } export default { meta : { type : 'suggestion' , docs : { description : 'enforce usage of `exact` modifier on `v-on`' , categories : [ 'vue3-essential' , 'essential' ] , url : 'https://eslint.vuejs.org/rules/use-v-on-exact.html' } , fixable : undefined , schema : [ ] } , create ( context ) { return processRule ( context , ( ) => { let tagIsCustomComponent = false ; let eventAttributes = [ ] ; return { tag ( token , { tokens } ) { tagIsCustomComponent = isCustomComponent ( token , tokens ) ; } , 'start-attributes' ( ) { eventAttributes = [ ] ; } , attribute ( token ) { if ( token . name . startsWith ( 'v-on:' ) || token . name . startsWith ( '@' ) ) { if ( tagIsCustomComponent && ! token . name . includes ( '.native' ) ) { return ; } eventAttributes . push ( token ) ; } } , 'end-attributes' ( ) { if ( eventAttributes . length > 1 ) { const groupedEvents = groupEvents ( eventAttributes ) ; for ( const eventsInGroup of Object . values ( groupedEvents ) ) { const hasEventWithKeyModifiers = eventsInGroup . some ( ( event ) => hasSystemModifier ( extractModifiers ( event . name ) ) ) ; if ( ! hasEventWithKeyModifiers ) { continue ; } const conflictedEvents = findConflictedEvents ( eventsInGroup ) ; for ( const event of conflictedEvents ) { const loc = event . loc ; const columnStart = loc . start . column - 1 ; const columnEnd = columnStart + event . name . 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 : "Consider to use '.exact' modifier." } ) ; } } } } } ; } ) ; } } as Rule . RuleModule ;