Files
FrenoCorp/node_modules/eslint-plugin-solid/dist/rules/event-handlers.js
Michael Freno 7c684a42cc FRE-600: Fix code review blockers
- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-25 00:08:01 -04:00

237 lines
9.9 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@typescript-eslint/utils");
const utils_2 = require("../utils");
const createRule = utils_1.ESLintUtils.RuleCreator.withoutDocs;
const { getStaticValue } = utils_1.ASTUtils;
const COMMON_EVENTS = [
"onAnimationEnd",
"onAnimationIteration",
"onAnimationStart",
"onBeforeInput",
"onBlur",
"onChange",
"onClick",
"onContextMenu",
"onCopy",
"onCut",
"onDblClick",
"onDrag",
"onDragEnd",
"onDragEnter",
"onDragExit",
"onDragLeave",
"onDragOver",
"onDragStart",
"onDrop",
"onError",
"onFocus",
"onFocusIn",
"onFocusOut",
"onGotPointerCapture",
"onInput",
"onInvalid",
"onKeyDown",
"onKeyPress",
"onKeyUp",
"onLoad",
"onLostPointerCapture",
"onMouseDown",
"onMouseEnter",
"onMouseLeave",
"onMouseMove",
"onMouseOut",
"onMouseOver",
"onMouseUp",
"onPaste",
"onPointerCancel",
"onPointerDown",
"onPointerEnter",
"onPointerLeave",
"onPointerMove",
"onPointerOut",
"onPointerOver",
"onPointerUp",
"onReset",
"onScroll",
"onSelect",
"onSubmit",
"onToggle",
"onTouchCancel",
"onTouchEnd",
"onTouchMove",
"onTouchStart",
"onTransitionEnd",
"onWheel",
];
const COMMON_EVENTS_MAP = new Map((function* () {
for (const event of COMMON_EVENTS) {
yield [event.toLowerCase(), event];
}
})());
const NONSTANDARD_EVENTS_MAP = {
ondoubleclick: "onDblClick",
};
const isCommonHandlerName = (lowercaseHandlerName) => COMMON_EVENTS_MAP.has(lowercaseHandlerName);
const getCommonEventHandlerName = (lowercaseHandlerName) => COMMON_EVENTS_MAP.get(lowercaseHandlerName);
const isNonstandardEventName = (lowercaseEventName) => Boolean(NONSTANDARD_EVENTS_MAP[lowercaseEventName]);
const getStandardEventHandlerName = (lowercaseEventName) => NONSTANDARD_EVENTS_MAP[lowercaseEventName];
exports.default = createRule({
meta: {
type: "problem",
docs: {
description: "Enforce naming DOM element event handlers consistently and prevent Solid's analysis from misunderstanding whether a prop should be an event handler.",
url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/docs/event-handlers.md",
},
fixable: "code",
hasSuggestions: true,
schema: [
{
type: "object",
properties: {
ignoreCase: {
type: "boolean",
description: "if true, don't warn on ambiguously named event handlers like `onclick` or `onchange`",
default: false,
},
warnOnSpread: {
type: "boolean",
description: "if true, warn when spreading event handlers onto JSX. Enable for Solid < v1.6.",
default: false,
},
},
additionalProperties: false,
},
],
messages: {
"detected-attr": 'The {{name}} prop is named as an event handler (starts with "on"), but Solid knows its value ({{staticValue}}) is a string or number, so it will be treated as an attribute. If this is intentional, name this prop attr:{{name}}.',
naming: "The {{name}} prop is ambiguous. If it is an event handler, change it to {{handlerName}}. If it is an attribute, change it to {{attrName}}.",
capitalization: "The {{name}} prop should be renamed to {{fixedName}} for readability.",
nonstandard: "The {{name}} prop should be renamed to {{fixedName}}, because it's not a standard event handler.",
"make-handler": "Change the {{name}} prop to {{handlerName}}.",
"make-attr": "Change the {{name}} prop to {{attrName}}.",
"spread-handler": "The {{name}} prop should be added as a JSX attribute, not spread in. Solid doesn't add listeners when spreading into JSX.",
},
},
defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode();
return {
JSXAttribute(node) {
const openingElement = node.parent;
if (openingElement.name.type !== "JSXIdentifier" ||
!(0, utils_2.isDOMElementName)(openingElement.name.name)) {
return;
}
if (node.name.type === "JSXNamespacedName") {
return;
}
const { name } = node.name;
if (!/^on[a-zA-Z]/.test(name)) {
return;
}
let staticValue = null;
if (node.value?.type === "JSXExpressionContainer" &&
node.value.expression.type !== "JSXEmptyExpression" &&
node.value.expression.type !== "ArrayExpression" &&
(staticValue = getStaticValue(node.value.expression, context.getScope())) !== null &&
(typeof staticValue.value === "string" || typeof staticValue.value === "number")) {
context.report({
node,
messageId: "detected-attr",
data: {
name,
staticValue: staticValue.value,
},
});
}
else if (node.value === null || node.value?.type === "Literal") {
context.report({
node,
messageId: "detected-attr",
data: {
name,
staticValue: node.value !== null ? node.value.value : true,
},
});
}
else if (!context.options[0]?.ignoreCase) {
const lowercaseHandlerName = name.toLowerCase();
if (isNonstandardEventName(lowercaseHandlerName)) {
const fixedName = getStandardEventHandlerName(lowercaseHandlerName);
context.report({
node: node.name,
messageId: "nonstandard",
data: { name, fixedName },
fix: (fixer) => fixer.replaceText(node.name, fixedName),
});
}
else if (isCommonHandlerName(lowercaseHandlerName)) {
const fixedName = getCommonEventHandlerName(lowercaseHandlerName);
if (fixedName !== name) {
context.report({
node: node.name,
messageId: "capitalization",
data: { name, fixedName },
fix: (fixer) => fixer.replaceText(node.name, fixedName),
});
}
}
else if (name[2] === name[2].toLowerCase()) {
const handlerName = `on${name[2].toUpperCase()}${name.slice(3)}`;
const attrName = `attr:${name}`;
context.report({
node: node.name,
messageId: "naming",
data: { name, attrName, handlerName },
suggest: [
{
messageId: "make-handler",
data: { name, handlerName },
fix: (fixer) => fixer.replaceText(node.name, handlerName),
},
{
messageId: "make-attr",
data: { name, attrName },
fix: (fixer) => fixer.replaceText(node.name, attrName),
},
],
});
}
}
},
Property(node) {
if (context.options[0]?.warnOnSpread &&
node.parent?.type === "ObjectExpression" &&
node.parent.parent?.type === "JSXSpreadAttribute" &&
node.parent.parent.parent?.type === "JSXOpeningElement") {
const openingElement = node.parent.parent.parent;
if (openingElement.name.type === "JSXIdentifier" &&
(0, utils_2.isDOMElementName)(openingElement.name.name)) {
if (node.key.type === "Identifier" && /^on/.test(node.key.name)) {
const handlerName = node.key.name;
context.report({
node,
messageId: "spread-handler",
data: {
name: node.key.name,
},
*fix(fixer) {
const commaAfter = sourceCode.getTokenAfter(node);
yield fixer.remove(node.parent.properties.length === 1
? node.parent.parent
: node);
if (commaAfter?.value === ",") {
yield fixer.remove(commaAfter);
}
yield fixer.insertTextAfter(node.parent.parent, ` ${handlerName}={${sourceCode.getText(node.value)}}`);
},
});
}
}
}
},
};
},
});