@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.
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/chunk-4UZNJ5MS.mjs +32 -0
- package/dist/chunk-4UZNJ5MS.mjs.map +1 -0
- package/dist/chunk-EBU4C5NV.mjs +77 -0
- package/dist/chunk-EBU4C5NV.mjs.map +1 -0
- package/dist/chunk-LE6SW2M3.mjs +6231 -0
- package/dist/chunk-LE6SW2M3.mjs.map +1 -0
- package/dist/chunk-MHDQJMLE.mjs +39 -0
- package/dist/chunk-MHDQJMLE.mjs.map +1 -0
- package/dist/configs/recommended.d.mts +109 -0
- package/dist/configs/recommended.d.ts +109 -0
- package/dist/configs/recommended.js +5877 -0
- package/dist/configs/recommended.js.map +1 -0
- package/dist/configs/recommended.mjs +5 -0
- package/dist/configs/recommended.mjs.map +1 -0
- package/dist/configs/typescript.d.mts +103 -0
- package/dist/configs/typescript.d.ts +103 -0
- package/dist/configs/typescript.js +6246 -0
- package/dist/configs/typescript.js.map +1 -0
- package/dist/configs/typescript.mjs +6 -0
- package/dist/configs/typescript.mjs.map +1 -0
- package/dist/configs/v2-strict.d.mts +130 -0
- package/dist/configs/v2-strict.d.ts +130 -0
- package/dist/configs/v2-strict.js +6317 -0
- package/dist/configs/v2-strict.js.map +1 -0
- package/dist/configs/v2-strict.mjs +7 -0
- package/dist/configs/v2-strict.mjs.map +1 -0
- package/dist/configs/v2.d.mts +127 -0
- package/dist/configs/v2.d.ts +127 -0
- package/dist/configs/v2.js +6289 -0
- package/dist/configs/v2.js.map +1 -0
- package/dist/configs/v2.mjs +6 -0
- package/dist/configs/v2.mjs.map +1 -0
- package/dist/index.d.mts +740 -0
- package/dist/index.d.ts +740 -0
- package/dist/index.js +6365 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +42 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +109 -0
- package/src/compat.ts +53 -0
- package/src/configs/recommended.ts +48 -0
- package/src/configs/typescript.ts +21 -0
- package/src/configs/v2-strict.ts +28 -0
- package/src/configs/v2.ts +68 -0
- package/src/deps.d.ts +1 -0
- package/src/index.ts +29 -0
- package/src/plugin.ts +92 -0
- package/src/rules/components-return-once.ts +209 -0
- package/src/rules/event-handlers.ts +397 -0
- package/src/rules/imports.ts +338 -0
- package/src/rules/jsx-no-duplicate-props.ts +110 -0
- package/src/rules/jsx-no-script-url.ts +61 -0
- package/src/rules/jsx-no-undef.ts +230 -0
- package/src/rules/jsx-uses-vars.ts +66 -0
- package/src/rules/no-accessor-as-prop.ts +147 -0
- package/src/rules/no-array-handlers.ts +57 -0
- package/src/rules/no-boolean-enumerated-attribute.ts +209 -0
- package/src/rules/no-browser-globals-in-server-function.ts +108 -0
- package/src/rules/no-destructure.ts +245 -0
- package/src/rules/no-innerhtml.ts +143 -0
- package/src/rules/no-invalid-server-capture.ts +104 -0
- package/src/rules/no-module-scope-reactive-primitive.ts +69 -0
- package/src/rules/no-proxy-apis.ts +96 -0
- package/src/rules/no-react-deps.ts +68 -0
- package/src/rules/no-react-specific-props.ts +60 -0
- package/src/rules/no-reactive-read-in-effect-callback.ts +570 -0
- package/src/rules/no-restated-default-options.ts +96 -0
- package/src/rules/no-single-arg-create-effect.ts +56 -0
- package/src/rules/no-store-mutation-outside-setter.ts +137 -0
- package/src/rules/no-unknown-namespaces.ts +140 -0
- package/src/rules/no-unused-signal.ts +119 -0
- package/src/rules/no-write-in-pure-computation.ts +231 -0
- package/src/rules/prefer-classlist.ts +93 -0
- package/src/rules/prefer-for.ts +93 -0
- package/src/rules/prefer-onSettled-for-side-effects.ts +101 -0
- package/src/rules/prefer-show.ts +102 -0
- package/src/rules/prefer-structured-class.ts +117 -0
- package/src/rules/reactivity.ts +1730 -0
- package/src/rules/removed-api.ts +212 -0
- package/src/rules/require-async-server-function.ts +122 -0
- package/src/rules/self-closing-comp.ts +150 -0
- package/src/rules/style-prop.ts +140 -0
- package/src/rules/valid-use-server.ts +192 -0
- package/src/rules/validate-jsx-nesting.ts +1 -0
- package/src/utils/reactive-imports.ts +382 -0
- package/src/utils/reactive-types.ts +272 -0
- package/src/utils/reactive-values.ts +162 -0
- 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
|
+
});
|