@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,143 @@
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 { ESLintUtils, ASTUtils } from "@typescript-eslint/utils";
10
+ import isHtml from "is-html";
11
+ import { jsxPropName } from "../utils";
12
+
13
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
14
+ const { getStringIfConstant } = ASTUtils;
15
+
16
+ type MessageIds = "dangerous" | "conflict" | "notHtml" | "useInnerText" | "dangerouslySetInnerHTML";
17
+ type Options = [{ allowStatic?: boolean }?];
18
+
19
+ export default createRule<Options, MessageIds>({
20
+ meta: {
21
+ type: "problem",
22
+ docs: {
23
+ description:
24
+ "Disallow usage of the innerHTML attribute, which can often lead to security vulnerabilities.",
25
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-innerhtml.md",
26
+ },
27
+ fixable: "code",
28
+ hasSuggestions: true,
29
+ schema: [
30
+ {
31
+ type: "object",
32
+ properties: {
33
+ allowStatic: {
34
+ description:
35
+ "if the innerHTML value is guaranteed to be a static HTML string (i.e. no user input), allow it",
36
+ type: "boolean",
37
+ default: true,
38
+ },
39
+ },
40
+ additionalProperties: false,
41
+ },
42
+ ],
43
+ messages: {
44
+ dangerous:
45
+ "The innerHTML attribute is dangerous; passing unsanitized input can lead to security vulnerabilities.",
46
+ conflict:
47
+ "The innerHTML attribute should not be used on an element with child elements; they will be overwritten.",
48
+ notHtml: "The string passed to innerHTML does not appear to be valid HTML.",
49
+ useInnerText: "For text content, using innerText is clearer and safer.",
50
+ dangerouslySetInnerHTML:
51
+ "The dangerouslySetInnerHTML prop is not supported; use innerHTML instead.",
52
+ },
53
+ },
54
+ defaultOptions: [{ allowStatic: true }],
55
+ create(context) {
56
+ const allowStatic = Boolean(context.options[0]?.allowStatic ?? true);
57
+ return {
58
+ JSXAttribute(node) {
59
+ if (jsxPropName(node) === "dangerouslySetInnerHTML") {
60
+ if (
61
+ node.value?.type === "JSXExpressionContainer" &&
62
+ node.value.expression.type === "ObjectExpression" &&
63
+ node.value.expression.properties.length === 1
64
+ ) {
65
+ const htmlProp = node.value.expression.properties[0];
66
+ if (
67
+ htmlProp.type === "Property" &&
68
+ htmlProp.key.type === "Identifier" &&
69
+ htmlProp.key.name === "__html"
70
+ ) {
71
+ context.report({
72
+ node,
73
+ messageId: "dangerouslySetInnerHTML",
74
+ fix: (fixer) => {
75
+ const propRange = node.range;
76
+ const valueRange = htmlProp.value.range;
77
+ return [
78
+ fixer.replaceTextRange([propRange[0], valueRange[0]], "innerHTML={"),
79
+ fixer.replaceTextRange([valueRange[1], propRange[1]], "}"),
80
+ ];
81
+ },
82
+ });
83
+ } else {
84
+ context.report({
85
+ node,
86
+ messageId: "dangerouslySetInnerHTML",
87
+ });
88
+ }
89
+ } else {
90
+ context.report({
91
+ node,
92
+ messageId: "dangerouslySetInnerHTML",
93
+ });
94
+ }
95
+ return;
96
+ } else if (jsxPropName(node) !== "innerHTML") {
97
+ return;
98
+ }
99
+
100
+ if (allowStatic) {
101
+ const innerHtmlNode =
102
+ node.value?.type === "JSXExpressionContainer" ? node.value.expression : node.value;
103
+ const innerHtml = innerHtmlNode && getStringIfConstant(innerHtmlNode);
104
+ if (typeof innerHtml === "string") {
105
+ if (isHtml(innerHtml)) {
106
+ // go up to enclosing JSXElement and check if it has children
107
+ if (
108
+ node.parent?.parent?.type === "JSXElement" &&
109
+ node.parent.parent.children?.length
110
+ ) {
111
+ context.report({
112
+ node: node.parent.parent, // report error on JSXElement instead of JSXAttribute
113
+ messageId: "conflict",
114
+ });
115
+ }
116
+ } else {
117
+ context.report({
118
+ node,
119
+ messageId: "notHtml",
120
+ suggest: [
121
+ {
122
+ fix: (fixer) => fixer.replaceText(node.name, "innerText"),
123
+ messageId: "useInnerText",
124
+ },
125
+ ],
126
+ });
127
+ }
128
+ } else {
129
+ context.report({
130
+ node,
131
+ messageId: "dangerous",
132
+ });
133
+ }
134
+ } else {
135
+ context.report({
136
+ node,
137
+ messageId: "dangerous",
138
+ });
139
+ }
140
+ },
141
+ };
142
+ },
143
+ });
@@ -0,0 +1,104 @@
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 {
11
+ isFunctionNode,
12
+ hasUseServerDirective,
13
+ getUseServerEligibility,
14
+ isInsideUseServerFunction,
15
+ programHasUseServerDirective,
16
+ } from "../utils";
17
+ import { getSourceCode } from "../compat";
18
+
19
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
20
+
21
+ type MessageIds = "invalidCapture";
22
+ type Options = [];
23
+
24
+ /*
25
+ * An editor-time mirror of the compiler's closure-capture validation. An
26
+ * extracted server function runs in a different closure environment than the
27
+ * one it was written in: on the server it is hoisted to module top level, on
28
+ * the client it is replaced by a network proxy. Any variable captured from an
29
+ * intermediate scope — declared between module top level and the server
30
+ * function itself — does not exist at runtime. The compiler rejects this at
31
+ * build time; this rule reports the same captures as you type.
32
+ *
33
+ * Module-level directives are unaffected (the whole module runs on the
34
+ * server, so its closures are intact). Eligibility mirrors the transform:
35
+ * functions it never extracts (object/class methods, getters/setters) are
36
+ * not validated, and directives nested inside an already-extracted server
37
+ * function are ignored.
38
+ */
39
+ export default createRule<Options, MessageIds>({
40
+ meta: {
41
+ type: "problem",
42
+ docs: {
43
+ description:
44
+ "Disallow server functions from capturing variables in enclosing non-module scopes, mirroring the compiler's build-time validation.",
45
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-invalid-server-capture.md",
46
+ },
47
+ schema: [],
48
+ messages: {
49
+ invalidCapture:
50
+ "Server functions cannot capture '{{name}}' from an enclosing {{kind}}: on the server the function is hoisted to module level and on the client it becomes a network proxy, so the captured variable does not exist at runtime. Use parameters, module-level bindings, imports, or globals instead.",
51
+ },
52
+ },
53
+ defaultOptions: [],
54
+ create(context) {
55
+ const sourceCode = getSourceCode(context);
56
+ let moduleLevel = false;
57
+
58
+ return {
59
+ Program(node) {
60
+ moduleLevel = programHasUseServerDirective(node);
61
+ },
62
+ ":function"(node: T.Node) {
63
+ if (!isFunctionNode(node)) return;
64
+ if (moduleLevel) return;
65
+ if (!hasUseServerDirective(node)) return;
66
+ if (getUseServerEligibility(node) !== "eligible") return;
67
+ if (isInsideUseServerFunction(node)) return;
68
+
69
+ const scope = sourceCode.scopeManager?.acquire(node);
70
+ if (!scope) return;
71
+ // `through` holds every reference crossing this function's boundary,
72
+ // including those from nested scopes.
73
+ for (const reference of scope.through) {
74
+ const variable = reference.resolved;
75
+ if (!variable) continue; // a true global
76
+ // TS type-only references are erased from the output and never captured
77
+ const typedReference = reference as {
78
+ isTypeReference?: boolean;
79
+ isValueReference?: boolean;
80
+ };
81
+ if (typedReference.isTypeReference && !typedReference.isValueReference) continue;
82
+ const declarationScope = variable.scope;
83
+ if (declarationScope.type === "module" || declarationScope.type === "global") continue;
84
+ // A named function expression referencing itself travels with the
85
+ // extracted function and stays valid.
86
+ if (
87
+ declarationScope.type === "function-expression-name" &&
88
+ declarationScope.block === node
89
+ ) {
90
+ continue;
91
+ }
92
+ context.report({
93
+ node: reference.identifier,
94
+ messageId: "invalidCapture",
95
+ data: {
96
+ name: reference.identifier.name,
97
+ kind: declarationScope.type === "function" ? "function" : "block",
98
+ },
99
+ });
100
+ }
101
+ },
102
+ };
103
+ },
104
+ });
@@ -0,0 +1,69 @@
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 { ESLintUtils } from "@typescript-eslint/utils";
10
+ import { findParent, isFunctionNode, getSolidSourceRegex, trackImports } from "../utils";
11
+
12
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
13
+
14
+ type MessageIds = "moduleScopePrimitive";
15
+ type Options = [];
16
+
17
+ const REACTIVE_PRIMITIVES = [
18
+ "createSignal",
19
+ "createMemo",
20
+ "createStore",
21
+ "createEffect",
22
+ "createRenderEffect",
23
+ "createOptimistic",
24
+ "createOptimisticStore",
25
+ "createProjection",
26
+ ];
27
+
28
+ /*
29
+ * Reactive state created at module scope is shared across every request when
30
+ * server rendering, leaking one user's state into another's response. It is a
31
+ * valid pattern in client-only apps, which is why this rule is only enabled
32
+ * in the strict config. Wrapping in `createRoot` (or any function) is the
33
+ * deliberate escape hatch and is not flagged.
34
+ */
35
+ export default createRule<Options, MessageIds>({
36
+ meta: {
37
+ type: "problem",
38
+ docs: {
39
+ description:
40
+ "Disallow reactive primitives at module scope, where state is shared across SSR requests.",
41
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-module-scope-reactive-primitive.md",
42
+ },
43
+ schema: [],
44
+ messages: {
45
+ moduleScopePrimitive:
46
+ "{{name}} at module scope creates state shared across all SSR requests. Create it inside a component or provider, or wrap it in `createRoot` if module-level state is intentional (client-only).",
47
+ },
48
+ },
49
+ defaultOptions: [],
50
+ create(context) {
51
+ const { matchImport, handleImportDeclaration } = trackImports(getSolidSourceRegex(context));
52
+
53
+ return {
54
+ ImportDeclaration: handleImportDeclaration,
55
+ CallExpression(node) {
56
+ if (node.callee.type !== "Identifier") return;
57
+ const name = matchImport(REACTIVE_PRIMITIVES, node.callee.name);
58
+ if (!name) return;
59
+ if (findParent(node, isFunctionNode) == null) {
60
+ context.report({
61
+ node,
62
+ messageId: "moduleScopePrimitive",
63
+ data: { name: node.callee.name },
64
+ });
65
+ }
66
+ },
67
+ };
68
+ },
69
+ });
@@ -0,0 +1,96 @@
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 { isFunctionNode, getSolidSourceRegex, trackImports, isPropsByName, trace } from "../utils";
11
+
12
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
13
+
14
+ export default createRule({
15
+ meta: {
16
+ type: "problem",
17
+ docs: {
18
+ description:
19
+ "Disallow usage of APIs that use ES6 Proxies, only to target environments that don't support them.",
20
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-proxy-apis.md",
21
+ },
22
+ schema: [],
23
+ messages: {
24
+ noStore: "Solid Store APIs use Proxies, which are incompatible with your target environment.",
25
+ spreadCall:
26
+ "Using a function call in JSX spread makes Solid use Proxies, which are incompatible with your target environment.",
27
+ spreadMember:
28
+ "Using a property access in JSX spread makes Solid use Proxies, which are incompatible with your target environment.",
29
+ proxyLiteral: "Proxies are incompatible with your target environment.",
30
+ mergeProps:
31
+ "If you pass a function to `mergeProps`, it will create a Proxy, which are incompatible with your target environment.",
32
+ },
33
+ },
34
+ defaultOptions: [],
35
+ create(context) {
36
+ const { matchImport, handleImportDeclaration } = trackImports(getSolidSourceRegex(context));
37
+
38
+ return {
39
+ ImportDeclaration(node) {
40
+ handleImportDeclaration(node); // track import aliases
41
+
42
+ const source = node.source.value;
43
+ if (source === "solid-js/store") {
44
+ context.report({
45
+ node,
46
+ messageId: "noStore",
47
+ });
48
+ }
49
+ },
50
+ "JSXSpreadAttribute MemberExpression"(node: T.MemberExpression) {
51
+ context.report({ node, messageId: "spreadMember" });
52
+ },
53
+ "JSXSpreadAttribute CallExpression"(node: T.CallExpression) {
54
+ context.report({ node, messageId: "spreadCall" });
55
+ },
56
+ CallExpression(node) {
57
+ if (node.callee.type === "Identifier") {
58
+ if (matchImport("mergeProps", node.callee.name)) {
59
+ node.arguments
60
+ .filter((arg) => {
61
+ if (arg.type === "SpreadElement") return true;
62
+ const traced = trace(arg, context);
63
+ return (
64
+ (traced.type === "Identifier" && !isPropsByName(traced.name)) ||
65
+ isFunctionNode(traced)
66
+ );
67
+ })
68
+ .forEach((badArg) => {
69
+ context.report({
70
+ node: badArg,
71
+ messageId: "mergeProps",
72
+ });
73
+ });
74
+ }
75
+ } else if (node.callee.type === "MemberExpression") {
76
+ if (
77
+ node.callee.object.type === "Identifier" &&
78
+ node.callee.object.name === "Proxy" &&
79
+ node.callee.property.type === "Identifier" &&
80
+ node.callee.property.name === "revocable"
81
+ ) {
82
+ context.report({
83
+ node,
84
+ messageId: "proxyLiteral",
85
+ });
86
+ }
87
+ }
88
+ },
89
+ NewExpression(node) {
90
+ if (node.callee.type === "Identifier" && node.callee.name === "Proxy") {
91
+ context.report({ node, messageId: "proxyLiteral" });
92
+ }
93
+ },
94
+ };
95
+ },
96
+ });
@@ -0,0 +1,68 @@
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 { ESLintUtils } from "@typescript-eslint/utils";
10
+ import { isFunctionNode, isSolidV2, trace, getSolidSourceRegex, trackImports } 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 dependency arrays in `createEffect` and `createMemo`.",
19
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-react-deps.md",
20
+ },
21
+ fixable: "code",
22
+ schema: [],
23
+ messages: {
24
+ noUselessDep:
25
+ "In Solid, `{{name}}` doesn't accept a dependency array because it automatically tracks its dependencies. If you really need to override the list of dependencies, use `on`.",
26
+ },
27
+ },
28
+ defaultOptions: [],
29
+ create(context) {
30
+ // In Solid 2.0 the second argument of createEffect is the (required) effect
31
+ // function, so a "dependency array" there is a type and runtime error already;
32
+ // this rule's premise only exists in 1.x.
33
+ if (isSolidV2(context)) return {};
34
+
35
+ /** Tracks imports from 'solid-js', handling aliases. */
36
+ const { matchImport, handleImportDeclaration } = trackImports(getSolidSourceRegex(context));
37
+
38
+ return {
39
+ ImportDeclaration: handleImportDeclaration,
40
+ CallExpression(node) {
41
+ if (
42
+ node.callee.type === "Identifier" &&
43
+ matchImport(["createEffect", "createMemo"], node.callee.name) &&
44
+ node.arguments.length === 2 &&
45
+ node.arguments.every((arg) => arg.type !== "SpreadElement")
46
+ ) {
47
+ // grab both arguments, tracing any variables to their actual values if possible
48
+ const [arg0, arg1] = node.arguments.map((arg) => trace(arg, context));
49
+
50
+ if (isFunctionNode(arg0) && arg0.params.length === 0 && arg1.type === "ArrayExpression") {
51
+ // A second argument that looks like a dependency array was passed to
52
+ // createEffect/createMemo, and the inline function doesn't accept a parameter, so it
53
+ // can't just be an initial value.
54
+ context.report({
55
+ node: node.arguments[1], // if this is a variable, highlight the usage, not the initialization
56
+ messageId: "noUselessDep",
57
+ data: {
58
+ name: node.callee.name,
59
+ },
60
+ // remove dep array if it's given inline, otherwise don't fix
61
+ fix: arg1 === node.arguments[1] ? (fixer) => fixer.remove(arg1) : undefined,
62
+ });
63
+ }
64
+ }
65
+ },
66
+ };
67
+ },
68
+ });
@@ -0,0 +1,60 @@
1
+ import { TSESLint, ESLintUtils } from "@typescript-eslint/utils";
2
+ import { isDOMElementName, jsxGetProp, jsxHasProp } from "../utils";
3
+
4
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
5
+
6
+ const reactSpecificProps = [
7
+ { from: "className", to: "class" },
8
+ { from: "htmlFor", to: "for" },
9
+ ];
10
+
11
+ export default createRule({
12
+ meta: {
13
+ type: "problem",
14
+ docs: {
15
+ description:
16
+ "Disallow usage of React-specific `className`/`htmlFor` props, which were deprecated in v1.4.0.",
17
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/no-react-specific-props.md",
18
+ },
19
+ fixable: "code",
20
+ schema: [],
21
+ messages: {
22
+ prefer: "Prefer the `{{ to }}` prop over the deprecated `{{ from }}` prop.",
23
+ noUselessKey: "Elements in a <For> or <Index> list do not need a key prop.",
24
+ },
25
+ },
26
+ defaultOptions: [],
27
+ create(context) {
28
+ return {
29
+ JSXOpeningElement(node) {
30
+ for (const { from, to } of reactSpecificProps) {
31
+ const classNameAttribute = jsxGetProp(node.attributes, from);
32
+ if (classNameAttribute) {
33
+ // only auto-fix if there is no class prop defined
34
+ const fix = !jsxHasProp(node.attributes, to)
35
+ ? (fixer: TSESLint.RuleFixer) => fixer.replaceText(classNameAttribute.name, to)
36
+ : undefined;
37
+
38
+ context.report({
39
+ node: classNameAttribute,
40
+ messageId: "prefer",
41
+ data: { from, to },
42
+ fix,
43
+ });
44
+ }
45
+ }
46
+ if (node.name.type === "JSXIdentifier" && isDOMElementName(node.name.name)) {
47
+ const keyProp = jsxGetProp(node.attributes, "key");
48
+ if (keyProp) {
49
+ // no DOM element has a 'key' prop, so we can assert that this is a holdover from React.
50
+ context.report({
51
+ node: keyProp,
52
+ messageId: "noUselessKey",
53
+ fix: (fixer) => fixer.remove(keyProp),
54
+ });
55
+ }
56
+ }
57
+ },
58
+ };
59
+ },
60
+ });