@qq99/eslint-plugin-solid 0.18.0-no-reactive-read-in-effect-callback.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -0
  3. package/dist/chunk-4UZNJ5MS.mjs +32 -0
  4. package/dist/chunk-4UZNJ5MS.mjs.map +1 -0
  5. package/dist/chunk-EBU4C5NV.mjs +77 -0
  6. package/dist/chunk-EBU4C5NV.mjs.map +1 -0
  7. package/dist/chunk-LE6SW2M3.mjs +6231 -0
  8. package/dist/chunk-LE6SW2M3.mjs.map +1 -0
  9. package/dist/chunk-MHDQJMLE.mjs +39 -0
  10. package/dist/chunk-MHDQJMLE.mjs.map +1 -0
  11. package/dist/configs/recommended.d.mts +109 -0
  12. package/dist/configs/recommended.d.ts +109 -0
  13. package/dist/configs/recommended.js +5877 -0
  14. package/dist/configs/recommended.js.map +1 -0
  15. package/dist/configs/recommended.mjs +5 -0
  16. package/dist/configs/recommended.mjs.map +1 -0
  17. package/dist/configs/typescript.d.mts +103 -0
  18. package/dist/configs/typescript.d.ts +103 -0
  19. package/dist/configs/typescript.js +6246 -0
  20. package/dist/configs/typescript.js.map +1 -0
  21. package/dist/configs/typescript.mjs +6 -0
  22. package/dist/configs/typescript.mjs.map +1 -0
  23. package/dist/configs/v2-strict.d.mts +130 -0
  24. package/dist/configs/v2-strict.d.ts +130 -0
  25. package/dist/configs/v2-strict.js +6317 -0
  26. package/dist/configs/v2-strict.js.map +1 -0
  27. package/dist/configs/v2-strict.mjs +7 -0
  28. package/dist/configs/v2-strict.mjs.map +1 -0
  29. package/dist/configs/v2.d.mts +127 -0
  30. package/dist/configs/v2.d.ts +127 -0
  31. package/dist/configs/v2.js +6289 -0
  32. package/dist/configs/v2.js.map +1 -0
  33. package/dist/configs/v2.mjs +6 -0
  34. package/dist/configs/v2.mjs.map +1 -0
  35. package/dist/index.d.mts +740 -0
  36. package/dist/index.d.ts +740 -0
  37. package/dist/index.js +6365 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/index.mjs +42 -0
  40. package/dist/index.mjs.map +1 -0
  41. package/package.json +109 -0
  42. package/src/compat.ts +53 -0
  43. package/src/configs/recommended.ts +48 -0
  44. package/src/configs/typescript.ts +21 -0
  45. package/src/configs/v2-strict.ts +28 -0
  46. package/src/configs/v2.ts +68 -0
  47. package/src/deps.d.ts +1 -0
  48. package/src/index.ts +29 -0
  49. package/src/plugin.ts +92 -0
  50. package/src/rules/components-return-once.ts +209 -0
  51. package/src/rules/event-handlers.ts +397 -0
  52. package/src/rules/imports.ts +338 -0
  53. package/src/rules/jsx-no-duplicate-props.ts +110 -0
  54. package/src/rules/jsx-no-script-url.ts +61 -0
  55. package/src/rules/jsx-no-undef.ts +230 -0
  56. package/src/rules/jsx-uses-vars.ts +66 -0
  57. package/src/rules/no-accessor-as-prop.ts +147 -0
  58. package/src/rules/no-array-handlers.ts +57 -0
  59. package/src/rules/no-boolean-enumerated-attribute.ts +209 -0
  60. package/src/rules/no-browser-globals-in-server-function.ts +108 -0
  61. package/src/rules/no-destructure.ts +245 -0
  62. package/src/rules/no-innerhtml.ts +143 -0
  63. package/src/rules/no-invalid-server-capture.ts +104 -0
  64. package/src/rules/no-module-scope-reactive-primitive.ts +69 -0
  65. package/src/rules/no-proxy-apis.ts +96 -0
  66. package/src/rules/no-react-deps.ts +68 -0
  67. package/src/rules/no-react-specific-props.ts +60 -0
  68. package/src/rules/no-reactive-read-in-effect-callback.ts +570 -0
  69. package/src/rules/no-restated-default-options.ts +96 -0
  70. package/src/rules/no-single-arg-create-effect.ts +56 -0
  71. package/src/rules/no-store-mutation-outside-setter.ts +137 -0
  72. package/src/rules/no-unknown-namespaces.ts +140 -0
  73. package/src/rules/no-unused-signal.ts +119 -0
  74. package/src/rules/no-write-in-pure-computation.ts +231 -0
  75. package/src/rules/prefer-classlist.ts +93 -0
  76. package/src/rules/prefer-for.ts +93 -0
  77. package/src/rules/prefer-onSettled-for-side-effects.ts +101 -0
  78. package/src/rules/prefer-show.ts +102 -0
  79. package/src/rules/prefer-structured-class.ts +117 -0
  80. package/src/rules/reactivity.ts +1730 -0
  81. package/src/rules/removed-api.ts +212 -0
  82. package/src/rules/require-async-server-function.ts +122 -0
  83. package/src/rules/self-closing-comp.ts +150 -0
  84. package/src/rules/style-prop.ts +140 -0
  85. package/src/rules/valid-use-server.ts +192 -0
  86. package/src/rules/validate-jsx-nesting.ts +1 -0
  87. package/src/utils/reactive-imports.ts +382 -0
  88. package/src/utils/reactive-types.ts +272 -0
  89. package/src/utils/reactive-values.ts +162 -0
  90. package/src/utils.ts +442 -0
@@ -0,0 +1,230 @@
1
+ /**
2
+ * FIXME: remove this comments and import when below issue is fixed.
3
+ * This import is necessary for type generation due to a bug in the TypeScript compiler.
4
+ * See: https://github.com/microsoft/TypeScript/issues/42873
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ import type { TSESLint } from "@typescript-eslint/utils";
8
+
9
+ import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
10
+ import { isDOMElementName, formatList, appendImports, insertImports, isSolidV2 } from "../utils";
11
+ import { getScope, getSourceCode } from "../compat";
12
+
13
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
14
+
15
+ // Currently all of the control flow components are from 'solid-js'.
16
+ const AUTO_COMPONENTS = ["Show", "For", "Index", "Switch", "Match"];
17
+ // Solid 2.0 control flow components; `Index`, `ErrorBoundary`, `Suspense`, and
18
+ // `SuspenseList` no longer exist (see solid/removed-api).
19
+ const AUTO_COMPONENTS_V2 = [
20
+ "For",
21
+ "Repeat",
22
+ "Show",
23
+ "Switch",
24
+ "Match",
25
+ "Errored",
26
+ "Loading",
27
+ "Reveal",
28
+ ];
29
+ const SOURCE_MODULE = "solid-js";
30
+
31
+ /*
32
+ * This rule is adapted from eslint-plugin-react's jsx-no-undef rule under
33
+ * the MIT license. Thank you for your work!
34
+ */
35
+ type MessageIds = "undefined" | "customDirectiveUndefined" | "autoImport";
36
+ type Options = [
37
+ {
38
+ allowGlobals?: boolean;
39
+ autoImport?: boolean;
40
+ typescriptEnabled?: boolean;
41
+ }?
42
+ ];
43
+ export default createRule<Options, MessageIds>({
44
+ meta: {
45
+ type: "problem",
46
+ docs: {
47
+ description: "Disallow references to undefined variables in JSX. Handles custom directives.",
48
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-undef.md",
49
+ },
50
+ fixable: "code",
51
+ schema: [
52
+ {
53
+ type: "object",
54
+ properties: {
55
+ allowGlobals: {
56
+ type: "boolean",
57
+ description:
58
+ "When true, the rule will consider the global scope when checking for defined components.",
59
+ default: false,
60
+ },
61
+ autoImport: {
62
+ type: "boolean",
63
+ description:
64
+ 'Automatically import certain components from `"solid-js"` if they are undefined.',
65
+ default: true,
66
+ },
67
+ typescriptEnabled: {
68
+ type: "boolean",
69
+ description: "Adjusts behavior not to conflict with TypeScript's type checking.",
70
+ default: false,
71
+ },
72
+ },
73
+ additionalProperties: false,
74
+ },
75
+ ],
76
+ messages: {
77
+ undefined: "'{{identifier}}' is not defined.",
78
+ customDirectiveUndefined: "Custom directive '{{identifier}}' is not defined.",
79
+ autoImport: "{{imports}} should be imported from '{{source}}'.",
80
+ },
81
+ },
82
+ defaultOptions: [],
83
+ create(context) {
84
+ const allowGlobals = context.options[0]?.allowGlobals ?? false;
85
+ const autoImport = context.options[0]?.autoImport !== false;
86
+ const isTypeScriptEnabled = context.options[0]?.typescriptEnabled ?? false;
87
+ const autoComponents = isSolidV2(context) ? AUTO_COMPONENTS_V2 : AUTO_COMPONENTS;
88
+
89
+ const missingComponentsSet = new Set<string>();
90
+
91
+ /**
92
+ * Compare an identifier with the variables declared in the scope
93
+ * @param {ASTNode} node - Identifier or JSXIdentifier node
94
+ * @returns {void}
95
+ */
96
+ function checkIdentifierInJSX(
97
+ node: T.Identifier | T.JSXIdentifier,
98
+ {
99
+ isComponent,
100
+ isCustomDirective,
101
+ }: { isComponent?: boolean; isCustomDirective?: boolean } = {}
102
+ ) {
103
+ let scope = getScope(context, node);
104
+ const sourceCode = getSourceCode(context);
105
+ const sourceType = sourceCode.ast.sourceType;
106
+ const scopeUpperBound = !allowGlobals && sourceType === "module" ? "module" : "global";
107
+ const variables = [...scope.variables];
108
+
109
+ // Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX)
110
+ if (node.name === "this") {
111
+ return;
112
+ }
113
+
114
+ while (scope.type !== scopeUpperBound && scope.type !== "global" && scope.upper) {
115
+ scope = scope.upper;
116
+ variables.push(...scope.variables);
117
+ }
118
+ if (scope.childScopes.length) {
119
+ variables.push(...scope.childScopes[0].variables);
120
+ // Temporary fix for babel-eslint
121
+ if (scope.childScopes[0].childScopes.length) {
122
+ variables.push(...scope.childScopes[0].childScopes[0].variables);
123
+ }
124
+ }
125
+
126
+ if (variables.find((variable) => variable.name === node.name)) {
127
+ return;
128
+ }
129
+
130
+ if (
131
+ isComponent &&
132
+ autoImport &&
133
+ autoComponents.includes(node.name) &&
134
+ !missingComponentsSet.has(node.name)
135
+ ) {
136
+ // track which names are undefined
137
+ missingComponentsSet.add(node.name);
138
+ } else if (isCustomDirective) {
139
+ context.report({
140
+ node,
141
+ messageId: "customDirectiveUndefined",
142
+ data: {
143
+ identifier: node.name,
144
+ },
145
+ });
146
+ } else if (!isTypeScriptEnabled) {
147
+ context.report({
148
+ node,
149
+ messageId: "undefined",
150
+ data: {
151
+ identifier: node.name,
152
+ },
153
+ });
154
+ }
155
+ }
156
+
157
+ return {
158
+ JSXOpeningElement(node) {
159
+ let n: T.Node | undefined;
160
+ switch (node.name.type) {
161
+ case "JSXIdentifier":
162
+ if (!isDOMElementName(node.name.name)) {
163
+ checkIdentifierInJSX(node.name, { isComponent: true });
164
+ }
165
+ break;
166
+ case "JSXMemberExpression":
167
+ n = node.name;
168
+ do {
169
+ n = (n as any).object;
170
+ } while (n && n.type !== "JSXIdentifier");
171
+ if (n) {
172
+ checkIdentifierInJSX(n);
173
+ }
174
+ break;
175
+ default:
176
+ break;
177
+ }
178
+ },
179
+ "JSXAttribute > JSXNamespacedName": (node: T.JSXNamespacedName) => {
180
+ // <Element use:X /> applies the `X` custom directive to the element, where `X` must be an identifier in scope.
181
+ if (
182
+ node.namespace?.type === "JSXIdentifier" &&
183
+ node.namespace.name === "use" &&
184
+ node.name?.type === "JSXIdentifier"
185
+ ) {
186
+ checkIdentifierInJSX(node.name, { isCustomDirective: true });
187
+ }
188
+ },
189
+ "Program:exit": (programNode: T.Program) => {
190
+ // add in any auto import components used in the program
191
+ const missingComponents = Array.from(missingComponentsSet.values());
192
+ if (autoImport && missingComponents.length) {
193
+ const importNode = programNode.body.find(
194
+ (n) =>
195
+ n.type === "ImportDeclaration" &&
196
+ n.importKind !== "type" &&
197
+ n.source.type === "Literal" &&
198
+ n.source.value === SOURCE_MODULE
199
+ ) as T.ImportDeclaration | undefined;
200
+ if (importNode) {
201
+ context.report({
202
+ node: importNode,
203
+ messageId: "autoImport",
204
+ data: {
205
+ imports: formatList(missingComponents), // "Show, For, and Switch"
206
+ source: SOURCE_MODULE,
207
+ },
208
+ fix: (fixer) => {
209
+ return appendImports(fixer, getSourceCode(context), importNode, missingComponents);
210
+ },
211
+ });
212
+ } else {
213
+ context.report({
214
+ node: programNode,
215
+ messageId: "autoImport",
216
+ data: {
217
+ imports: formatList(missingComponents),
218
+ source: SOURCE_MODULE,
219
+ },
220
+ fix: (fixer) => {
221
+ // insert `import { missing, identifiers } from "solid-js"` at top of module
222
+ return insertImports(fixer, getSourceCode(context), "solid-js", missingComponents);
223
+ },
224
+ });
225
+ }
226
+ }
227
+ },
228
+ };
229
+ },
230
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * FIXME: remove this comments and import when below issue is fixed.
3
+ * This import is necessary for type generation due to a bug in the TypeScript compiler.
4
+ * See: https://github.com/microsoft/TypeScript/issues/42873
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ import type { TSESLint } from "@typescript-eslint/utils";
8
+
9
+ import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
10
+ import { markVariableAsUsed } from "../compat";
11
+
12
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
13
+
14
+ /*
15
+ * This rule is lifted almost verbatim from eslint-plugin-react's
16
+ * jsx-uses-vars rule under the MIT license. Thank you for your work!
17
+ * Solid's custom directives are also handled.
18
+ */
19
+
20
+ export default createRule({
21
+ meta: {
22
+ type: "problem",
23
+ docs: {
24
+ // eslint-disable-next-line eslint-plugin/require-meta-docs-description
25
+ description: "Prevent variables used in JSX from being marked as unused.",
26
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-uses-vars.md",
27
+ },
28
+ schema: [],
29
+ // eslint-disable-next-line eslint-plugin/prefer-message-ids
30
+ messages: {},
31
+ },
32
+ defaultOptions: [],
33
+ create(context) {
34
+ return {
35
+ JSXOpeningElement(node) {
36
+ let parent: T.JSXTagNameExpression;
37
+ switch (node.name.type) {
38
+ case "JSXNamespacedName": // <Foo:Bar>
39
+ return;
40
+ case "JSXIdentifier": // <Foo>
41
+ markVariableAsUsed(context, node.name.name, node.name);
42
+ break;
43
+ case "JSXMemberExpression": // <Foo...Bar>
44
+ parent = node.name.object;
45
+ while (parent?.type === "JSXMemberExpression") {
46
+ parent = parent.object;
47
+ }
48
+ if (parent.type === "JSXIdentifier") {
49
+ markVariableAsUsed(context, parent.name, parent);
50
+ }
51
+ break;
52
+ }
53
+ },
54
+ "JSXAttribute > JSXNamespacedName": (node: T.JSXNamespacedName) => {
55
+ // <Element use:X /> applies the `X` custom directive to the element, where `X` must be an identifier in scope.
56
+ if (
57
+ node.namespace?.type === "JSXIdentifier" &&
58
+ node.namespace.name === "use" &&
59
+ node.name?.type === "JSXIdentifier"
60
+ ) {
61
+ markVariableAsUsed(context, node.name.name, node.name);
62
+ }
63
+ },
64
+ };
65
+ },
66
+ });
@@ -0,0 +1,147 @@
1
+ /**
2
+ * FIXME: remove this comments and import when below issue is fixed.
3
+ * This import is necessary for type generation due to a bug in the TypeScript compiler.
4
+ * See: https://github.com/microsoft/TypeScript/issues/42873
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ import type { TSESLint } from "@typescript-eslint/utils";
8
+
9
+ import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
10
+ import { findVariable } from "../compat";
11
+ import { isDOMElementName, isFunctionNode, getSolidSourceRegex, trackImports } from "../utils";
12
+
13
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
14
+
15
+ type MessageIds = "accessorAsProp" | "functionAsProp";
16
+ type Options = [];
17
+
18
+ // Attributes on DOM elements that legitimately accept a function value.
19
+ const FUNCTION_ATTRIBUTES = new Set(["ref", "children"]);
20
+
21
+ // camelCase `onX` names are event handlers; lowercase `onx` names are literal
22
+ // attributes in Solid 2.0 but are solid/event-handlers' territory — skip both
23
+ // here so a single node never gets two reports.
24
+ const isOnPrefixed = (name: string) => name.length > 2 && name.startsWith("on");
25
+
26
+ /*
27
+ * Passing an uncalled signal accessor as a DOM element attribute
28
+ * (`<div title={count} />`) silently sets the attribute to a stringified
29
+ * function. TypeScript flags this with a cryptic assignability error that
30
+ * many pipelines never run (Vite builds don't typecheck); this rule is the
31
+ * enforcement and translation layer. Unlike solid/reactivity's signal
32
+ * tracing, any expression that resolves to a function fires here.
33
+ */
34
+ export default createRule<Options, MessageIds>({
35
+ meta: {
36
+ type: "problem",
37
+ docs: {
38
+ description:
39
+ "Disallow passing uncalled signal accessors or other functions as value-typed DOM element attributes.",
40
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-accessor-as-prop.md",
41
+ },
42
+ hasSuggestions: true,
43
+ schema: [],
44
+ messages: {
45
+ accessorAsProp:
46
+ "'{{attribute}}' expects a value, but '{{name}}' is a signal accessor — call it: '{{name}}()'.",
47
+ functionAsProp:
48
+ "'{{attribute}}' expects a value, but this is a function; it would render as a stringified function. If it derives a value, call it.",
49
+ },
50
+ },
51
+ defaultOptions: [],
52
+ create(context) {
53
+ const { matchImport, handleImportDeclaration } = trackImports(getSolidSourceRegex(context));
54
+
55
+ /** Is `id` a signal accessor or plain function we can resolve statically? */
56
+ const resolveFunctionKind = (id: T.Identifier): "accessor" | "function" | null => {
57
+ const variable = findVariable(context, id);
58
+ const def = variable?.defs[0];
59
+ if (!def) return null;
60
+
61
+ if (def.type === "FunctionName") return "function";
62
+ if (def.type !== "Variable" || !def.node.init) return null;
63
+
64
+ const { id: declId, init } = def.node;
65
+ // const count = () => ...;
66
+ if (declId.type === "Identifier" && isFunctionNode(init)) return "function";
67
+ if (init.type !== "CallExpression" || init.callee.type !== "Identifier") return null;
68
+
69
+ // const memo = createMemo(...)
70
+ if (
71
+ declId.type === "Identifier" &&
72
+ matchImport(["createMemo", "children"], init.callee.name)
73
+ ) {
74
+ return "accessor";
75
+ }
76
+ // const [count] = createSignal(...); const [value] = createOptimistic(...)
77
+ if (
78
+ declId.type === "ArrayPattern" &&
79
+ declId.elements[0]?.type === "Identifier" &&
80
+ declId.elements[0].name === id.name &&
81
+ matchImport(["createSignal", "createOptimistic"], init.callee.name)
82
+ ) {
83
+ return "accessor";
84
+ }
85
+ return null;
86
+ };
87
+
88
+ return {
89
+ ImportDeclaration: handleImportDeclaration,
90
+ JSXAttribute(node) {
91
+ // Only plain attributes on native DOM elements; namespaced attributes
92
+ // and custom elements can legitimately carry unusual values.
93
+ if (node.name.type !== "JSXIdentifier") return;
94
+ const attribute = node.name.name;
95
+ if (FUNCTION_ATTRIBUTES.has(attribute) || isOnPrefixed(attribute)) return;
96
+
97
+ const element = node.parent as T.JSXOpeningElement;
98
+ if (
99
+ element.name.type !== "JSXIdentifier" ||
100
+ !isDOMElementName(element.name.name) ||
101
+ element.name.name.includes("-")
102
+ ) {
103
+ return;
104
+ }
105
+
106
+ if (node.value?.type !== "JSXExpressionContainer") return;
107
+ const expression = node.value.expression;
108
+
109
+ if (isFunctionNode(expression)) {
110
+ context.report({ node: expression, messageId: "functionAsProp", data: { attribute } });
111
+ return;
112
+ }
113
+
114
+ if (expression.type === "Identifier") {
115
+ const kind = resolveFunctionKind(expression);
116
+ if (kind === "accessor") {
117
+ context.report({
118
+ node: expression,
119
+ messageId: "accessorAsProp",
120
+ data: { attribute, name: expression.name },
121
+ suggest: [
122
+ {
123
+ messageId: "accessorAsProp",
124
+ data: { attribute, name: expression.name },
125
+ fix: (fixer) => fixer.insertTextAfter(expression, "()"),
126
+ },
127
+ ],
128
+ });
129
+ } else if (kind === "function") {
130
+ context.report({
131
+ node: expression,
132
+ messageId: "functionAsProp",
133
+ data: { attribute },
134
+ suggest: [
135
+ {
136
+ messageId: "functionAsProp",
137
+ data: { attribute },
138
+ fix: (fixer) => fixer.insertTextAfter(expression, "()"),
139
+ },
140
+ ],
141
+ });
142
+ }
143
+ }
144
+ },
145
+ };
146
+ },
147
+ });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * FIXME: remove this comments and import when below issue is fixed.
3
+ * This import is necessary for type generation due to a bug in the TypeScript compiler.
4
+ * See: https://github.com/microsoft/TypeScript/issues/42873
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ import type { TSESLint } from "@typescript-eslint/utils";
8
+
9
+ import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
10
+ import { isDOMElementName, trace } from "../utils";
11
+
12
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
13
+
14
+ export default createRule({
15
+ meta: {
16
+ type: "problem",
17
+ docs: {
18
+ description: "Disallow usage of type-unsafe event handlers.",
19
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-array-handlers.md",
20
+ },
21
+ schema: [],
22
+ messages: {
23
+ noArrayHandlers: "Passing an array as an event handler is potentially type-unsafe.",
24
+ },
25
+ },
26
+ defaultOptions: [],
27
+ create(context) {
28
+ return {
29
+ JSXAttribute(node) {
30
+ const openingElement = node.parent as T.JSXOpeningElement;
31
+ if (
32
+ openingElement.name.type !== "JSXIdentifier" ||
33
+ !isDOMElementName(openingElement.name.name)
34
+ ) {
35
+ return; // bail if this is not a DOM/SVG element or web component
36
+ }
37
+
38
+ const isNamespacedHandler =
39
+ node.name.type === "JSXNamespacedName" && node.name.namespace.name === "on";
40
+ const isNormalEventHandler =
41
+ node.name.type === "JSXIdentifier" && /^on[a-zA-Z]/.test(node.name.name);
42
+
43
+ if (
44
+ (isNamespacedHandler || isNormalEventHandler) &&
45
+ node.value?.type === "JSXExpressionContainer" &&
46
+ trace(node.value.expression, context).type === "ArrayExpression"
47
+ ) {
48
+ // Warn if passed an array
49
+ context.report({
50
+ node,
51
+ messageId: "noArrayHandlers",
52
+ });
53
+ }
54
+ },
55
+ };
56
+ },
57
+ });