123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- /**
- * @fileoverview enforce usage of `exact` modifier on `v-on`.
- * @author Armano
- */
- 'use strict'
- // ------------------------------------------------------------------------------
- // Requirements
- // ------------------------------------------------------------------------------
- const utils = require('../utils')
- const SYSTEM_MODIFIERS = new Set(['ctrl', 'shift', 'alt', 'meta'])
- // ------------------------------------------------------------------------------
- // Helpers
- // ------------------------------------------------------------------------------
- /**
- * Finds and returns all keys for event directives
- *
- * @param {array} attributes Element attributes
- * @returns {array[object]} [{ name, node, modifiers }]
- */
- function getEventDirectives (attributes) {
- return attributes
- .filter(attribute =>
- attribute.directive &&
- attribute.key.name === 'on'
- )
- .map(attribute => ({
- name: attribute.key.argument,
- node: attribute.key,
- modifiers: attribute.key.modifiers
- }))
- }
- /**
- * Checks whether given modifier is system one
- *
- * @param {string} modifier
- * @returns {boolean}
- */
- function isSystemModifier (modifier) {
- return SYSTEM_MODIFIERS.has(modifier)
- }
- /**
- * Checks whether given any of provided modifiers
- * has system modifier
- *
- * @param {array} modifiers
- * @returns {boolean}
- */
- function hasSystemModifier (modifiers) {
- return modifiers.some(isSystemModifier)
- }
- /**
- * Groups all events in object,
- * with keys represinting each event name
- *
- * @param {array} events
- * @returns {object} { click: [], keypress: [] }
- */
- function groupEvents (events) {
- return events.reduce((acc, event) => {
- if (acc[event.name]) {
- acc[event.name].push(event)
- } else {
- acc[event.name] = [event]
- }
- return acc
- }, {})
- }
- /**
- * Creates alphabetically sorted string with system modifiers
- *
- * @param {array[string]} modifiers
- * @returns {string} e.g. "alt,ctrl,del,shift"
- */
- function getSystemModifiersString (modifiers) {
- return modifiers.filter(isSystemModifier).sort().join(',')
- }
- /**
- * Compares two events based on their modifiers
- * to detect possible event leakeage
- *
- * @param {object} baseEvent
- * @param {object} event
- * @returns {boolean}
- */
- function hasConflictedModifiers (baseEvent, event) {
- if (
- event.node === baseEvent.node ||
- event.modifiers.includes('exact')
- ) return false
- const eventModifiers = getSystemModifiersString(event.modifiers)
- const baseEventModifiers = getSystemModifiersString(baseEvent.modifiers)
- return (
- baseEvent.modifiers.length >= 1 &&
- baseEventModifiers !== eventModifiers &&
- baseEventModifiers.indexOf(eventModifiers) > -1
- )
- }
- /**
- * Searches for events that might conflict with each other
- *
- * @param {array} events
- * @returns {array} conflicted events, without duplicates
- */
- function findConflictedEvents (events) {
- return events.reduce((acc, event) => {
- return [
- ...acc,
- ...events
- .filter(evt => !acc.find(e => evt === e)) // No duplicates
- .filter(hasConflictedModifiers.bind(null, event))
- ]
- }, [])
- }
- // ------------------------------------------------------------------------------
- // Rule details
- // ------------------------------------------------------------------------------
- module.exports = {
- meta: {
- type: 'suggestion',
- docs: {
- description: 'enforce usage of `exact` modifier on `v-on`',
- category: 'essential',
- url: 'https://eslint.vuejs.org/rules/use-v-on-exact.html'
- },
- fixable: null,
- schema: []
- },
- /**
- * Creates AST event handlers for use-v-on-exact.
- *
- * @param {RuleContext} context - The rule context.
- * @returns {Object} AST event handlers.
- */
- create (context) {
- return utils.defineTemplateBodyVisitor(context, {
- VStartTag (node) {
- if (node.attributes.length === 0) return
- const isCustomComponent = utils.isCustomComponent(node.parent)
- let events = getEventDirectives(node.attributes)
- if (isCustomComponent) {
- // For components consider only events with `native` modifier
- events = events.filter(event => event.modifiers.includes('native'))
- }
- const grouppedEvents = groupEvents(events)
- Object.keys(grouppedEvents).forEach(eventName => {
- const eventsInGroup = grouppedEvents[eventName]
- const hasEventWithKeyModifier = eventsInGroup.some(event =>
- hasSystemModifier(event.modifiers)
- )
- if (!hasEventWithKeyModifier) return
- const conflictedEvents = findConflictedEvents(eventsInGroup)
- conflictedEvents.forEach(e => {
- context.report({
- node: e.node,
- loc: e.node.loc,
- message: "Consider to use '.exact' modifier."
- })
- })
- })
- }
- })
- }
- }
|