Skip to content

Commit

Permalink
fix(use-v-on-exact): Reimplement algorithm to catch cases more proper…
Browse files Browse the repository at this point in the history
…ly (#750)

* Reimplement use-v-on-exact rule

* Document methods in use-v-on-exact

* Check only system modifiers
  • Loading branch information
michalsnik committed Jan 5, 2019
1 parent 9bf6098 commit 5dd07bf
Show file tree
Hide file tree
Showing 4 changed files with 426 additions and 101 deletions.
171 changes: 146 additions & 25 deletions lib/rules/use-v-on-exact.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,124 @@

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 Definition
// Rule details
// ------------------------------------------------------------------------------

module.exports = {
Expand All @@ -35,31 +151,36 @@ module.exports = {
create (context) {
return utils.defineTemplateBodyVisitor(context, {
VStartTag (node) {
if (node.attributes.length > 1) {
const groups = node.attributes
.map(item => item.key)
.filter(item => item && item.type === 'VDirectiveKey' && item.name === 'on')
.reduce((rv, item) => {
(rv[item.argument] = rv[item.argument] || []).push(item)
return rv
}, {})

const directives = Object.keys(groups).map(key => groups[key])
// const directives = Object.values(groups) // Uncomment after node 6 is dropped
.filter(item => item.length > 1)
for (const group of directives) {
if (group.some(item => item.modifiers.length > 0)) { // check if there are directives with modifiers
const invalid = group.filter(item => item.modifiers.length === 0)
for (const node of invalid) {
context.report({
node,
loc: node.loc,
message: "Consider to use '.exact' modifier."
})
}
}
}
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."
})
})
})
}
})
}
Expand Down
70 changes: 2 additions & 68 deletions lib/rules/valid-v-on.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// ------------------------------------------------------------------------------

const utils = require('../utils')
const keyAliases = require('../utils/key-aliases.json')

// ------------------------------------------------------------------------------
// Helpers
Expand All @@ -24,74 +25,7 @@ const VERB_MODIFIERS = new Set([
'stop', 'prevent'
])
// https://www.w3.org/TR/uievents-key/
const KEY_ALIASES = new Set([
'unidentified', 'alt', 'alt-graph', 'caps-lock', 'control', 'fn', 'fn-lock',
'meta', 'num-lock', 'scroll-lock', 'shift', 'symbol', 'symbol-lock', 'hyper',
'super', 'enter', 'tab', 'arrow-down', 'arrow-left', 'arrow-right',
'arrow-up', 'end', 'home', 'page-down', 'page-up', 'backspace', 'clear',
'copy', 'cr-sel', 'cut', 'delete', 'erase-eof', 'ex-sel', 'insert', 'paste',
'redo', 'undo', 'accept', 'again', 'attn', 'cancel', 'context-menu', 'escape',
'execute', 'find', 'help', 'pause', 'select', 'zoom-in', 'zoom-out',
'brightness-down', 'brightness-up', 'eject', 'log-off', 'power',
'print-screen', 'hibernate', 'standby', 'wake-up', 'all-candidates',
'alphanumeric', 'code-input', 'compose', 'convert', 'dead', 'final-mode',
'group-first', 'group-last', 'group-next', 'group-previous', 'mode-change',
'next-candidate', 'non-convert', 'previous-candidate', 'process',
'single-candidate', 'hangul-mode', 'hanja-mode', 'junja-mode', 'eisu',
'hankaku', 'hiragana', 'hiragana-katakana', 'kana-mode', 'kanji-mode',
'katakana', 'romaji', 'zenkaku', 'zenkaku-hankaku', 'f1', 'f2', 'f3', 'f4',
'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12', 'soft1', 'soft2', 'soft3',
'soft4', 'channel-down', 'channel-up', 'close', 'mail-forward', 'mail-reply',
'mail-send', 'media-close', 'media-fast-forward', 'media-pause',
'media-play-pause', 'media-record', 'media-rewind', 'media-stop',
'media-track-next', 'media-track-previous', 'new', 'open', 'print', 'save',
'spell-check', 'key11', 'key12', 'audio-balance-left', 'audio-balance-right',
'audio-bass-boost-down', 'audio-bass-boost-toggle', 'audio-bass-boost-up',
'audio-fader-front', 'audio-fader-rear', 'audio-surround-mode-next',
'audio-treble-down', 'audio-treble-up', 'audio-volume-down',
'audio-volume-up', 'audio-volume-mute', 'microphone-toggle',
'microphone-volume-down', 'microphone-volume-up', 'microphone-volume-mute',
'speech-correction-list', 'speech-input-toggle', 'launch-application1',
'launch-application2', 'launch-calendar', 'launch-contacts', 'launch-mail',
'launch-media-player', 'launch-music-player', 'launch-phone',
'launch-screen-saver', 'launch-spreadsheet', 'launch-web-browser',
'launch-web-cam', 'launch-word-processor', 'browser-back',
'browser-favorites', 'browser-forward', 'browser-home', 'browser-refresh',
'browser-search', 'browser-stop', 'app-switch', 'call', 'camera',
'camera-focus', 'end-call', 'go-back', 'go-home', 'headset-hook',
'last-number-redial', 'notification', 'manner-mode', 'voice-dial', 't-v',
't-v3-d-mode', 't-v-antenna-cable', 't-v-audio-description',
't-v-audio-description-mix-down', 't-v-audio-description-mix-up',
't-v-contents-menu', 't-v-data-service', 't-v-input', 't-v-input-component1',
't-v-input-component2', 't-v-input-composite1', 't-v-input-composite2',
't-v-input-h-d-m-i1', 't-v-input-h-d-m-i2', 't-v-input-h-d-m-i3',
't-v-input-h-d-m-i4', 't-v-input-v-g-a1', 't-v-media-context', 't-v-network',
't-v-number-entry', 't-v-power', 't-v-radio-service', 't-v-satellite',
't-v-satellite-b-s', 't-v-satellite-c-s', 't-v-satellite-toggle',
't-v-terrestrial-analog', 't-v-terrestrial-digital', 't-v-timer',
'a-v-r-input', 'a-v-r-power', 'color-f0-red', 'color-f1-green',
'color-f2-yellow', 'color-f3-blue', 'color-f4-grey', 'color-f5-brown',
'closed-caption-toggle', 'dimmer', 'display-swap', 'd-v-r', 'exit',
'favorite-clear0', 'favorite-clear1', 'favorite-clear2', 'favorite-clear3',
'favorite-recall0', 'favorite-recall1', 'favorite-recall2',
'favorite-recall3', 'favorite-store0', 'favorite-store1', 'favorite-store2',
'favorite-store3', 'guide', 'guide-next-day', 'guide-previous-day', 'info',
'instant-replay', 'link', 'list-program', 'live-content', 'lock',
'media-apps', 'media-last', 'media-skip-backward', 'media-skip-forward',
'media-step-backward', 'media-step-forward', 'media-top-menu', 'navigate-in',
'navigate-next', 'navigate-out', 'navigate-previous', 'next-favorite-channel',
'next-user-profile', 'on-demand', 'pairing', 'pin-p-down', 'pin-p-move',
'pin-p-toggle', 'pin-p-up', 'play-speed-down', 'play-speed-reset',
'play-speed-up', 'random-toggle', 'rc-low-battery', 'record-speed-next',
'rf-bypass', 'scan-channels-toggle', 'screen-mode-next', 'settings',
'split-screen-toggle', 's-t-b-input', 's-t-b-power', 'subtitle', 'teletext',
'video-mode-next', 'wink', 'zoom-toggle', 'audio-volume-down',
'audio-volume-up', 'audio-volume-mute', 'browser-back', 'browser-forward',
'channel-down', 'channel-up', 'context-menu', 'eject', 'end', 'enter', 'home',
'media-fast-forward', 'media-play', 'media-play-pause', 'media-record',
'media-rewind', 'media-stop', 'media-next-track', 'media-pause',
'media-previous-track', 'power', 'unidentified'
])
const KEY_ALIASES = new Set(keyAliases)

function isValidModifier (modifier, customModifiers) {
return (
Expand Down
68 changes: 68 additions & 0 deletions lib/utils/key-aliases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
[
"unidentified", "alt", "alt-graph", "caps-lock", "control", "fn", "fn-lock",
"meta", "num-lock", "scroll-lock", "shift", "symbol", "symbol-lock", "hyper",
"super", "enter", "tab", "arrow-down", "arrow-left", "arrow-right",
"arrow-up", "end", "home", "page-down", "page-up", "backspace", "clear",
"copy", "cr-sel", "cut", "delete", "erase-eof", "ex-sel", "insert", "paste",
"redo", "undo", "accept", "again", "attn", "cancel", "context-menu", "escape",
"execute", "find", "help", "pause", "select", "zoom-in", "zoom-out",
"brightness-down", "brightness-up", "eject", "log-off", "power",
"print-screen", "hibernate", "standby", "wake-up", "all-candidates",
"alphanumeric", "code-input", "compose", "convert", "dead", "final-mode",
"group-first", "group-last", "group-next", "group-previous", "mode-change",
"next-candidate", "non-convert", "previous-candidate", "process",
"single-candidate", "hangul-mode", "hanja-mode", "junja-mode", "eisu",
"hankaku", "hiragana", "hiragana-katakana", "kana-mode", "kanji-mode",
"katakana", "romaji", "zenkaku", "zenkaku-hankaku", "f1", "f2", "f3", "f4",
"f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "soft1", "soft2", "soft3",
"soft4", "channel-down", "channel-up", "close", "mail-forward", "mail-reply",
"mail-send", "media-close", "media-fast-forward", "media-pause",
"media-play-pause", "media-record", "media-rewind", "media-stop",
"media-track-next", "media-track-previous", "new", "open", "print", "save",
"spell-check", "key11", "key12", "audio-balance-left", "audio-balance-right",
"audio-bass-boost-down", "audio-bass-boost-toggle", "audio-bass-boost-up",
"audio-fader-front", "audio-fader-rear", "audio-surround-mode-next",
"audio-treble-down", "audio-treble-up", "audio-volume-down",
"audio-volume-up", "audio-volume-mute", "microphone-toggle",
"microphone-volume-down", "microphone-volume-up", "microphone-volume-mute",
"speech-correction-list", "speech-input-toggle", "launch-application1",
"launch-application2", "launch-calendar", "launch-contacts", "launch-mail",
"launch-media-player", "launch-music-player", "launch-phone",
"launch-screen-saver", "launch-spreadsheet", "launch-web-browser",
"launch-web-cam", "launch-word-processor", "browser-back",
"browser-favorites", "browser-forward", "browser-home", "browser-refresh",
"browser-search", "browser-stop", "app-switch", "call", "camera",
"camera-focus", "end-call", "go-back", "go-home", "headset-hook",
"last-number-redial", "notification", "manner-mode", "voice-dial", "t-v",
"t-v3-d-mode", "t-v-antenna-cable", "t-v-audio-description",
"t-v-audio-description-mix-down", "t-v-audio-description-mix-up",
"t-v-contents-menu", "t-v-data-service", "t-v-input", "t-v-input-component1",
"t-v-input-component2", "t-v-input-composite1", "t-v-input-composite2",
"t-v-input-h-d-m-i1", "t-v-input-h-d-m-i2", "t-v-input-h-d-m-i3",
"t-v-input-h-d-m-i4", "t-v-input-v-g-a1", "t-v-media-context", "t-v-network",
"t-v-number-entry", "t-v-power", "t-v-radio-service", "t-v-satellite",
"t-v-satellite-b-s", "t-v-satellite-c-s", "t-v-satellite-toggle",
"t-v-terrestrial-analog", "t-v-terrestrial-digital", "t-v-timer",
"a-v-r-input", "a-v-r-power", "color-f0-red", "color-f1-green",
"color-f2-yellow", "color-f3-blue", "color-f4-grey", "color-f5-brown",
"closed-caption-toggle", "dimmer", "display-swap", "d-v-r", "exit",
"favorite-clear0", "favorite-clear1", "favorite-clear2", "favorite-clear3",
"favorite-recall0", "favorite-recall1", "favorite-recall2",
"favorite-recall3", "favorite-store0", "favorite-store1", "favorite-store2",
"favorite-store3", "guide", "guide-next-day", "guide-previous-day", "info",
"instant-replay", "link", "list-program", "live-content", "lock",
"media-apps", "media-last", "media-skip-backward", "media-skip-forward",
"media-step-backward", "media-step-forward", "media-top-menu", "navigate-in",
"navigate-next", "navigate-out", "navigate-previous", "next-favorite-channel",
"next-user-profile", "on-demand", "pairing", "pin-p-down", "pin-p-move",
"pin-p-toggle", "pin-p-up", "play-speed-down", "play-speed-reset",
"play-speed-up", "random-toggle", "rc-low-battery", "record-speed-next",
"rf-bypass", "scan-channels-toggle", "screen-mode-next", "settings",
"split-screen-toggle", "s-t-b-input", "s-t-b-power", "subtitle", "teletext",
"video-mode-next", "wink", "zoom-toggle", "audio-volume-down",
"audio-volume-up", "audio-volume-mute", "browser-back", "browser-forward",
"channel-down", "channel-up", "context-menu", "eject", "end", "enter", "home",
"media-fast-forward", "media-play", "media-play-pause", "media-record",
"media-rewind", "media-stop", "media-next-track", "media-pause",
"media-previous-track", "power", "unidentified"
]

0 comments on commit 5dd07bf

Please sign in to comment.