eslint-plugin-ember
Version:
ESLint plugin for Ember.js apps
146 lines (134 loc) • 3.67 kB
JavaScript
// Comprehensive Ember event handler names
// Note: mouseMove, mouseEnter, and mouseLeave are intentionally excluded —
// they are native DOM events that do not have corresponding Ember
// classic-event aliases on components.
const EMBER_EVENTS = new Set([
'touchStart',
'touchMove',
'touchEnd',
'touchCancel',
'keyDown',
'keyUp',
'keyPress',
'mouseDown',
'mouseUp',
'contextMenu',
'click',
'doubleClick',
'focusIn',
'focusOut',
'submit',
'change',
'input',
'dragStart',
'drag',
'dragEnter',
'dragLeave',
'dragOver',
'dragEnd',
'drop',
]);
function isEventName(name) {
return EMBER_EVENTS.has(name);
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow passing event handlers directly as component arguments',
category: 'Best Practices',
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-passed-in-event-handlers.md',
templateMode: 'both',
},
fixable: null,
schema: [
{
type: 'object',
properties: {
ignore: {
type: 'object',
additionalProperties: {
type: 'array',
items: { type: 'string' },
},
},
},
additionalProperties: false,
},
],
messages: {
unexpected:
'Event handler "@{{name}}" should not be passed as a component argument. Use the `on` modifier instead.',
},
originallyFrom: {
name: 'ember-template-lint',
rule: 'lib/rules/no-passed-in-event-handlers.js',
docs: 'docs/rule/no-passed-in-event-handlers.md',
tests: 'test/unit/rules/no-passed-in-event-handlers-test.js',
},
},
create(context) {
const options = context.options[0] || {};
const ignoreConfig = options.ignore || {};
return {
GlimmerElementNode(node) {
// Only check component invocations (PascalCase)
if (!/^[A-Z]/.test(node.tag)) {
return;
}
// Skip built-in Input/Textarea
if (node.tag === 'Input' || node.tag === 'Textarea') {
return;
}
if (!node.attributes) {
return;
}
const ignoredAttrs = ignoreConfig[node.tag] || [];
for (const attr of node.attributes) {
if (!attr.name || !attr.name.startsWith('@')) {
continue;
}
const argName = attr.name.slice(1);
if (ignoredAttrs.includes(argName)) {
continue;
}
if (isEventName(argName)) {
context.report({
node: attr,
messageId: 'unexpected',
data: { name: argName },
});
}
}
},
GlimmerMustacheStatement(node) {
const path = node.path;
if (!path || path.type !== 'GlimmerPathExpression') {
return;
}
// Skip built-in input/textarea
if (path.original === 'input' || path.original === 'textarea') {
return;
}
// Check hash pairs for event handler names
if (!node.hash || !node.hash.pairs) {
return;
}
const ignoredAttrs = ignoreConfig[path.original] || [];
for (const pair of node.hash.pairs) {
if (ignoredAttrs.includes(pair.key)) {
continue;
}
if (isEventName(pair.key)) {
context.report({
node: pair,
messageId: 'unexpected',
data: { name: pair.key },
});
}
}
},
};
},
};