Skip to content

fix(use-v-on-exact): Reimplement algorithm to catch cases more properly #750

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
]
Loading