;
77 | ```
78 |
79 | ### Valid Examples
80 |
81 | These snippets don't cause lint errors.
82 |
83 | ```js
84 | let el = (
85 |
Hello" + "
world!
"} />;
99 |
100 | let el =
;
101 | ```
102 |
103 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/docs/prefer-show.md:
--------------------------------------------------------------------------------
1 |
2 | # solid/prefer-show
3 | Enforce using Solid's `
` component for conditionally showing content. Solid's compiler covers this case, so it's a stylistic rule only.
4 | This rule is **off** by default.
5 |
6 | [View source](../src/rules/prefer-show.ts) · [View tests](../test/rules/prefer-show.test.ts)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## Tests
15 |
16 | ### Invalid Examples
17 |
18 | These snippets cause lint errors, and all of them can be auto-fixed.
19 |
20 | ```js
21 | function Component(props) {
22 | return
{props.cond && Content}
;
23 | }
24 | // after eslint --fix:
25 | function Component(props) {
26 | return (
27 |
28 |
29 | Content
30 |
31 |
32 | );
33 | }
34 |
35 | function Component(props) {
36 | return <>{props.cond &&
Content}>;
37 | }
38 | // after eslint --fix:
39 | function Component(props) {
40 | return (
41 | <>
42 |
43 | Content
44 |
45 | >
46 | );
47 | }
48 |
49 | function Component(props) {
50 | return
{props.cond ? Content : Fallback}
;
51 | }
52 | // after eslint --fix:
53 | function Component(props) {
54 | return (
55 |
56 | Fallback}>
57 | Content
58 |
59 |
60 | );
61 | }
62 |
63 | function Component(props) {
64 | return
{(listItem) => listItem.cond && Content};
65 | }
66 | // after eslint --fix:
67 | function Component(props) {
68 | return (
69 |
70 | {(listItem) => (
71 |
72 | Content
73 |
74 | )}
75 |
76 | );
77 | }
78 |
79 | function Component(props) {
80 | return (
81 |
82 | {(listItem) => (listItem.cond ? Content : Fallback)}
83 |
84 | );
85 | }
86 | // after eslint --fix:
87 | function Component(props) {
88 | return (
89 |
90 | {(listItem) => (
91 | Fallback}>
92 | Content
93 |
94 | )}
95 |
96 | );
97 | }
98 | ```
99 |
100 | ### Valid Examples
101 |
102 | These snippets don't cause lint errors.
103 |
104 | ```js
105 | function Component(props) {
106 | return
Content;
107 | }
108 |
109 | function Component(props) {
110 | return (
111 |
112 | Content
113 |
114 | );
115 | }
116 | ```
117 |
118 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/test/rules/no-react-deps.test.ts:
--------------------------------------------------------------------------------
1 | import { run } from "../ruleTester";
2 | import rule from "../../src/rules/no-react-deps";
3 |
4 | export const cases = run("no-react-deps", rule, {
5 | valid: [
6 | `createEffect(() => {
7 | console.log(signal());
8 | });`,
9 | `createEffect((prev) => {
10 | console.log(signal());
11 | return prev + 1;
12 | }, 0);`,
13 | `createEffect((prev) => {
14 | console.log(signal());
15 | return (prev || 0) + 1;
16 | });`,
17 | `createEffect((prev) => {
18 | console.log(signal());
19 | return prev ? prev + 1 : 1;
20 | }, undefined);`,
21 | `const value = createMemo(() => computeExpensiveValue(a(), b()));`,
22 | `const sum = createMemo((prev) => input() + prev, 0);`,
23 | `const args = [() => { console.log(signal()); }, [signal()]];
24 | createEffect(...args);`,
25 | ],
26 | invalid: [
27 | {
28 | code: `createEffect(() => {
29 | console.log(signal());
30 | }, [signal()]);`,
31 | errors: [{ messageId: "noUselessDep", data: { name: "createEffect" } }],
32 | output: `createEffect(() => {
33 | console.log(signal());
34 | }, );`,
35 | },
36 | {
37 | code: `createEffect(() => {
38 | console.log(signal());
39 | }, [signal]);`,
40 | errors: [{ messageId: "noUselessDep", data: { name: "createEffect" } }],
41 | output: `createEffect(() => {
42 | console.log(signal());
43 | }, );`,
44 | },
45 | {
46 | code: `const deps = [signal];
47 | createEffect(() => {
48 | console.log(signal());
49 | }, deps);`,
50 | errors: [{ messageId: "noUselessDep", data: { name: "createEffect" } }],
51 | // no `output`
52 | },
53 | {
54 | code: `const value = createMemo(() => computeExpensiveValue(a(), b()), [a(), b()]);`,
55 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }],
56 | output: `const value = createMemo(() => computeExpensiveValue(a(), b()), );`,
57 | },
58 | {
59 | code: `const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b]);`,
60 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }],
61 | output: `const value = createMemo(() => computeExpensiveValue(a(), b()), );`,
62 | },
63 | {
64 | code: `const value = createMemo(() => computeExpensiveValue(a(), b()), [a, b()]);`,
65 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }],
66 | output: `const value = createMemo(() => computeExpensiveValue(a(), b()), );`,
67 | },
68 | {
69 | code: `const deps = [a, b];
70 | const value = createMemo(() => computeExpensiveValue(a(), b()), deps);`,
71 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }],
72 | // no `output`
73 | },
74 | {
75 | code: `const deps = [a, b];
76 | const memoFn = () => computeExpensiveValue(a(), b());
77 | const value = createMemo(memoFn, deps);`,
78 | errors: [{ messageId: "noUselessDep", data: { name: "createMemo" } }],
79 | // no `output`
80 | },
81 | ],
82 | });
83 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/docs/no-react-specific-props.md:
--------------------------------------------------------------------------------
1 |
2 | # solid/no-react-specific-props
3 | Disallow usage of React-specific `className`/`htmlFor` props, which were deprecated in v1.4.0.
4 | This rule is **a warning** by default.
5 |
6 | [View source](../src/rules/no-react-specific-props.ts) · [View tests](../test/rules/no-react-specific-props.test.ts)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## Tests
15 |
16 | ### Invalid Examples
17 |
18 | These snippets cause lint errors, and all of them can be auto-fixed.
19 |
20 | ```js
21 | let el =
Hello world!
;
22 | // after eslint --fix:
23 | let el =
Hello world!
;
24 |
25 | let el =
Hello world!
;
26 | // after eslint --fix:
27 | let el =
Hello world!
;
28 |
29 | let el =
;
30 | // after eslint --fix:
31 | let el =
;
32 |
33 | let el = (
34 |
35 | Hello world!
36 |
37 | );
38 | // after eslint --fix:
39 | let el = (
40 |
41 | Hello world!
42 |
43 | );
44 |
45 | let el =
Hello world!;
46 | // after eslint --fix:
47 | let el =
Hello world!;
48 |
49 | let el =
;
50 | // after eslint --fix:
51 | let el =
;
52 |
53 | let el =
;
54 | // after eslint --fix:
55 | let el =
;
56 |
57 | let el = (
58 |
61 | );
62 | // after eslint --fix:
63 | let el = (
64 |
67 | );
68 |
69 | let el =
Hello world!;
70 | // after eslint --fix:
71 | let el =
Hello world!;
72 |
73 | let el =
;
74 | // after eslint --fix:
75 | let el =
;
76 | ```
77 |
78 | ### Valid Examples
79 |
80 | These snippets don't cause lint errors.
81 |
82 | ```js
83 | let el =
Hello world!
;
84 |
85 | let el =
Hello world!
;
86 |
87 | let el =
Hello world!
;
88 |
89 | let el = (
90 |
91 | Hello world!
92 |
93 | );
94 |
95 | let el =
;
96 |
97 | let el =
;
98 |
99 | let el =
;
100 |
101 | let el = (
102 |
105 | );
106 |
107 | let el =
;
108 |
109 | let el =
;
110 | ```
111 |
112 |
--------------------------------------------------------------------------------
/packages/eslint-solid-standalone/rollup-plugin-replace.mjs:
--------------------------------------------------------------------------------
1 | // lifted from @typescript-eslint/website-eslint/rollup-plugin/replace.js
2 | import path from "path";
3 | import Module from "module";
4 | import { createFilter } from "@rollup/pluginutils";
5 | import MagicString from "magic-string";
6 | import { createRequire } from "node:module";
7 |
8 | const require = createRequire(import.meta.url);
9 |
10 | function toAbsolute(id) {
11 | return id.startsWith("./") ? path.resolve(id) : require.resolve(id);
12 | }
13 |
14 | function log(opts, message, type = "info") {
15 | if (opts.verbose) {
16 | console.log("rollup-plugin-replace > [" + type + "]", message);
17 | }
18 | }
19 |
20 | function createMatcher(it) {
21 | if (typeof it === "function") {
22 | return it;
23 | } else {
24 | return createFilter(it);
25 | }
26 | }
27 |
28 | export default (options = {}) => {
29 | const aliasesCache = new Map();
30 | const aliases = (options.alias || []).map((item) => {
31 | return {
32 | match: item.match,
33 | matcher: createMatcher(item.match),
34 | target: item.target,
35 | absoluteTarget: toAbsolute(item.target),
36 | };
37 | });
38 | const replaces = (options.replace || []).map((item) => {
39 | return {
40 | match: item.match,
41 | test: item.test,
42 | replace: typeof item.replace === "string" ? () => item.replace : item.replace,
43 |
44 | matcher: createMatcher(item.match),
45 | };
46 | });
47 |
48 | return {
49 | name: "rollup-plugin-replace",
50 | resolveId(id, importerPath) {
51 | const importeePath =
52 | id.startsWith("./") || id.startsWith("../")
53 | ? Module.createRequire(importerPath).resolve(id)
54 | : id;
55 |
56 | let result = aliasesCache.get(importeePath);
57 | if (result) {
58 | return result;
59 | }
60 |
61 | result = aliases.find((item) => item.matcher(importeePath));
62 | if (result) {
63 | aliasesCache.set(importeePath, result.absoluteTarget);
64 | log(options, `${importeePath} as ${result.target}`, "resolve");
65 | return result.absoluteTarget;
66 | }
67 |
68 | return null;
69 | },
70 | transform(code, id) {
71 | let hasReplacements = false;
72 | let magicString = new MagicString(code);
73 |
74 | replaces.forEach((item) => {
75 | if (item.matcher && !item.matcher(id)) {
76 | return;
77 | }
78 |
79 | let match = item.test.exec(code);
80 | let start, end;
81 | while (match) {
82 | hasReplacements = true;
83 | start = match.index;
84 | end = start + match[0].length;
85 | magicString.overwrite(start, end, item.replace(match));
86 | match = item.test.global ? item.test.exec(code) : null;
87 | }
88 | });
89 |
90 | if (!hasReplacements) {
91 | return;
92 | }
93 | log(options, id, "replace");
94 |
95 | const map = magicString.generateMap();
96 | return { code: magicString.toString(), map: map.toString() };
97 | },
98 | };
99 | };
100 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/docs/prefer-classlist.md:
--------------------------------------------------------------------------------
1 |
2 | # solid/prefer-classlist
3 | Enforce using the classlist prop over importing a classnames helper. The classlist prop accepts an object `{ [class: string]: boolean }` just like classnames.
4 | This rule is **deprecated** and **off** by default.
5 |
6 | [View source](../src/rules/prefer-classlist.ts) · [View tests](../test/rules/prefer-classlist.test.ts)
7 |
8 |
9 |
10 | ## Rule Options
11 |
12 | Options shown here are the defaults. Manually configuring an array will *replace* the defaults.
13 |
14 | ```js
15 | {
16 | "solid/prefer-classlist": ["off", {
17 | // An array of names to treat as `classnames` functions
18 | classnames: ["cn","clsx","classnames"], // Array
19 | }]
20 | }
21 | ```
22 |
23 |
24 |
25 | ## Tests
26 |
27 | ### Invalid Examples
28 |
29 | These snippets cause lint errors, and all of them can be auto-fixed.
30 |
31 | ```js
32 | let el = Hello, world!
;
33 | // after eslint --fix:
34 | let el = Hello, world!
;
35 |
36 | let el = Hello, world!
;
37 | // after eslint --fix:
38 | let el = Hello, world!
;
39 |
40 | let el = Hello, world!
;
41 | // after eslint --fix:
42 | let el = Hello, world!
;
43 |
44 | /* eslint solid/prefer-classlist: ["error", { "classnames": ["x", "y", "z"] }] */
45 | let el = Hello, world!
;
46 | // after eslint --fix:
47 | let el = Hello, world!
;
48 |
49 | let el = Hello, world!
;
50 | // after eslint --fix:
51 | let el = Hello, world!
;
52 |
53 | let el = 2 })}>Hello, world!
;
54 | // after eslint --fix:
55 | let el = 2 }}>Hello, world!
;
56 | ```
57 |
58 | ### Valid Examples
59 |
60 | These snippets don't cause lint errors.
61 |
62 | ```js
63 | let el = Hello, world!
;
64 |
65 | let el = Hello, world!
;
66 |
67 | let el = Hello, world!
;
68 |
69 | let el = Hello, world!
;
70 |
71 | let el = Hello, world!
;
72 |
73 | let el = Hello, world!
;
74 |
75 | let el = Hello, world!
;
76 |
77 | let el = Hello, world!
;
78 |
79 | let el = Hello, world!
;
80 |
81 | let el = (
82 |
83 | Hello, world!
84 |
85 | );
86 |
87 | /* eslint solid/prefer-classlist: ["error", { "classnames": ["x", "y", "z"] }] */
88 | let el = Hello, world!
;
89 | ```
90 |
91 |
--------------------------------------------------------------------------------
/packages/eslint-solid-standalone/test.mjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | import path from "node:path";
3 | import fs from "node:fs";
4 | import assert from "node:assert";
5 | import vm from "node:vm";
6 | import typescript from "typescript";
7 |
8 | /**
9 | * Test that dist.js can be run in a clean environment without Node or browser APIs, that it won't
10 | * crash, and that it will produce expected results. Code in, lints/fixes out is all it needs to do.
11 | */
12 |
13 | // inject assert and a hidden _TYPESCRIPT_GLOBAL into global scope
14 | const context = vm.createContext({
15 | assert,
16 | structuredClone,
17 | _TYPESCRIPT_GLOBAL: typescript,
18 | });
19 |
20 | // create a module with the standalone build
21 | const code = fs.readFileSync(path.resolve("dist.js"), "utf-8");
22 | const dist = new vm.SourceTextModule(code, { identifier: "dist.js", context });
23 |
24 | // create a module reexporting typescript, a peer dependency of the standalone build
25 | const ts = new vm.SourceTextModule("export default _TYPESCRIPT_GLOBAL", {
26 | identifier: "typescript",
27 | context,
28 | });
29 |
30 | // create a module that tests the build with `assert`
31 | const test = new vm.SourceTextModule(
32 | `
33 | import { plugin, pluginVersion, eslintVersion, verify, verifyAndFix } from "dist.js";
34 |
35 | // check no Node APIs are present, except injected 'assert' and '_TYPESCRIPT_GLOBAL'
36 | assert.equal(Object.keys(globalThis).length, 3);
37 | assert.equal(typeof assert, 'function');
38 | assert.equal(typeof process, 'undefined');
39 | assert.equal(typeof __dirname, 'undefined');
40 |
41 | // check for presence of exported variables
42 | assert.equal(typeof plugin, "object");
43 | assert.equal(typeof pluginVersion, "string");
44 | assert.equal(typeof eslintVersion, "string");
45 | assert.equal(eslintVersion[0], '8')
46 | assert.equal(typeof verify, "function");
47 | assert.equal(typeof verifyAndFix, "function");
48 |
49 | // ensure that the standalone runs without crashing and returns results
50 | assert.deepStrictEqual(
51 | verify('let el = ', { 'solid/no-react-specific-props': 2 }),
52 | [{
53 | ruleId: "solid/no-react-specific-props",
54 | severity: 2,
55 | message: "Prefer the \`class\` prop over the deprecated \`className\` prop.",
56 | line: 1,
57 | column: 15,
58 | nodeType: "JSXAttribute",
59 | messageId: "prefer",
60 | endLine: 1,
61 | endColumn: 30,
62 | fix: { range: [14, 23], text: "class" },
63 | }],
64 | );
65 | assert.deepStrictEqual(verifyAndFix('let el = '), {
66 | fixed: true,
67 | messages: [],
68 | output: 'let el = ',
69 | });
70 | `,
71 | { identifier: "test.mjs", context }
72 | );
73 |
74 | // resolve imports to created modules, disallow any other attempts to import
75 | const linker = (specifier) => {
76 | const mod = {
77 | typescript: ts,
78 | "dist.js": dist,
79 | }[specifier];
80 | if (!mod) {
81 | throw new Error(`can't import other modules: ${specifier}`);
82 | }
83 | return mod;
84 | };
85 | await Promise.all([dist.link(linker), test.link(linker), ts.link(linker)]);
86 |
87 | // run the test module
88 | await test.evaluate({ timeout: 10 * 1000 });
89 | assert.equal(test.status, "evaluated");
90 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/test/rules/prefer-show.test.ts:
--------------------------------------------------------------------------------
1 | import { run } from "../ruleTester";
2 | import rule from "../../src/rules/prefer-show";
3 |
4 | export const cases = run("prefer-show", rule, {
5 | valid: [
6 | `function Component(props) {
7 | return Content;
8 | }`,
9 | `function Component(props) {
10 | return Content;
11 | }`,
12 | ],
13 | invalid: [
14 | {
15 | code: `
16 | function Component(props) {
17 | return {props.cond && Content}
;
18 | }`,
19 | errors: [{ messageId: "preferShowAnd" }],
20 | output: `
21 | function Component(props) {
22 | return Content
;
23 | }`,
24 | },
25 | {
26 | code: `
27 | function Component(props) {
28 | return <>{props.cond && Content}>;
29 | }`,
30 | errors: [{ messageId: "preferShowAnd" }],
31 | output: `
32 | function Component(props) {
33 | return <>Content>;
34 | }`,
35 | },
36 | {
37 | code: `
38 | function Component(props) {
39 | return (
40 |
41 | {props.cond ? (
42 | Content
43 | ) : (
44 | Fallback
45 | )}
46 |
47 | );
48 | }`,
49 | errors: [{ messageId: "preferShowTernary" }],
50 | output: `
51 | function Component(props) {
52 | return (
53 |
54 | Fallback}>Content
55 |
56 | );
57 | }`,
58 | },
59 | // Check that it also works with control flow function children
60 | {
61 | code: `
62 | function Component(props) {
63 | return (
64 |
65 | {(listItem) => listItem.cond && Content}
66 |
67 | );
68 | }`,
69 | errors: [{ messageId: "preferShowAnd" }],
70 | output: `
71 | function Component(props) {
72 | return (
73 |
74 | {(listItem) => Content}
75 |
76 | );
77 | }`,
78 | },
79 | {
80 | code: `
81 | function Component(props) {
82 | return (
83 |
84 | {(listItem) => (listItem.cond ? (
85 | Content
86 | ) : (
87 | Fallback
88 | ))}
89 |
90 | );
91 | }`,
92 | errors: [{ messageId: "preferShowTernary" }],
93 | output: `
94 | function Component(props) {
95 | return (
96 |
97 | {(listItem) => (Fallback}>Content)}
98 |
99 | );
100 | }`,
101 | },
102 | ],
103 | });
104 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-plugin-solid",
3 | "version": "0.14.5",
4 | "description": "Solid-specific linting rules for ESLint.",
5 | "keywords": [
6 | "eslint",
7 | "eslintplugin",
8 | "solid",
9 | "solidjs",
10 | "reactivity"
11 | ],
12 | "repository": "https://github.com/solidjs-community/eslint-plugin-solid",
13 | "license": "MIT",
14 | "author": "Josh Wilson ",
15 | "exports": {
16 | ".": {
17 | "types": {
18 | "import": "./dist/index.d.mts",
19 | "require": "./dist/index.d.ts"
20 | },
21 | "import": "./dist/index.mjs",
22 | "require": "./dist/index.js"
23 | },
24 | "./configs/recommended": {
25 | "types": {
26 | "import": "./dist/configs/recommended.d.mts",
27 | "require": "./dist/configs/recommended.d.ts"
28 | },
29 | "import": "./dist/configs/recommended.mjs",
30 | "require": "./dist/configs/recommended.js"
31 | },
32 | "./configs/typescript": {
33 | "types": {
34 | "import": "./dist/configs/typescript.d.mts",
35 | "require": "./dist/configs/typescript.d.ts"
36 | },
37 | "import": "./dist/configs/typescript.mjs",
38 | "require": "./dist/configs/typescript.js"
39 | },
40 | "./package.json": "./package.json"
41 | },
42 | "main": "dist/index.js",
43 | "types": "dist/index.d.ts",
44 | "files": [
45 | "src",
46 | "dist",
47 | "README.md"
48 | ],
49 | "scripts": {
50 | "build": "tsup",
51 | "test": "vitest --run",
52 | "test:all": "PARSER=all vitest --run",
53 | "test:babel": "PARSER=babel vitest --run",
54 | "test:ts": "PARSER=ts vitest --run",
55 | "test:v6": "PARSER=v6 vitest --run",
56 | "test:v7": "PARSER=v7 vitest --run",
57 | "test:watch": "vitest",
58 | "turbo:build": "tsup",
59 | "turbo:docs": "PARSER=none tsx scripts/docs.ts",
60 | "turbo:test": "vitest --run"
61 | },
62 | "dependencies": {
63 | "@typescript-eslint/utils": "^7.13.1 || ^8.0.0",
64 | "estraverse": "^5.3.0",
65 | "is-html": "^2.0.0",
66 | "kebab-case": "^1.0.2",
67 | "known-css-properties": "^0.30.0",
68 | "style-to-object": "^1.0.6"
69 | },
70 | "devDependencies": {
71 | "@babel/core": "^7.24.4",
72 | "@babel/eslint-parser": "^7.24.7",
73 | "@microsoft/api-extractor": "^7.47.6",
74 | "@types/eslint": "^8.56.10",
75 | "@types/eslint-v6": "npm:@types/eslint@6",
76 | "@types/eslint-v7": "npm:@types/eslint@7",
77 | "@types/eslint-v8": "npm:@types/eslint@8",
78 | "@types/eslint__js": "^8.42.3",
79 | "@types/estraverse": "^5.1.7",
80 | "@types/is-html": "^2.0.2",
81 | "@typescript-eslint/eslint-plugin": "^8.0.0",
82 | "@typescript-eslint/parser": "^8.0.0",
83 | "eslint": "^9.5.0",
84 | "eslint-v6": "npm:eslint@6",
85 | "eslint-v7": "npm:eslint@7",
86 | "eslint-v8": "npm:eslint@8",
87 | "markdown-magic": "^3.3.0",
88 | "prettier": "^2.8.8",
89 | "tsup": "^8.2.4",
90 | "tsx": "^4.17.0",
91 | "vitest": "^1.5.2"
92 | },
93 | "peerDependencies": {
94 | "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
95 | "typescript": ">=4.8.4"
96 | },
97 | "engines": {
98 | "node": ">=18.0.0"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/test/rules/self-closing-comp.test.ts:
--------------------------------------------------------------------------------
1 | import { run } from "../ruleTester";
2 | import rule from "../../src/rules/self-closing-comp";
3 |
4 | export const cases = run("self-closing-comp", rule, {
5 | valid: [
6 | `let el = ;`,
7 | `let el = ;`,
8 | `let el =
;`,
9 | `let el =
;`,
10 | `let el =
11 |
12 | ;`,
13 | `let el =
14 |
15 | `,
16 | `let el = ;`,
17 | `let el = ;`,
18 |
19 | `let el = ;`,
20 | `let el =
`,
21 | `let el = {' '}
`,
22 | {
23 | code: `let el = ;`,
24 | options: [{ html: "none" }],
25 | },
26 | {
27 | code: `let el =
;`,
28 | options: [{ html: "none" }],
29 | },
30 | {
31 | code: `let el = ;`,
32 | options: [{ html: "void" }],
33 | },
34 | {
35 | code: `let el = (
36 |
37 |
38 | )`,
39 | options: [{ html: "none" }],
40 | },
41 | {
42 | code: `let el = `,
43 | options: [{ component: "none" }],
44 | },
45 | ],
46 | invalid: [
47 | {
48 | code: `let el = ;`,
49 | errors: [{ messageId: "selfClose" }],
50 | output: `let el = ;`,
51 | },
52 | {
53 | code: `let el =
;`,
54 | errors: [{ messageId: "selfClose" }],
55 | output: `let el =
;`,
56 | },
57 | {
58 | code: `let el = ;`,
59 | options: [{ html: "void" }],
60 | errors: [{ messageId: "dontSelfClose" }],
61 | output: `let el = ;`,
62 | },
63 | {
64 | code: `let el = ;`,
65 | options: [{ html: "void" }],
66 | errors: [{ messageId: "dontSelfClose" }],
67 | output: `let el = ;`,
68 | },
69 | {
70 | code: `let el =
;`,
71 | options: [{ html: "none" }],
72 | errors: [{ messageId: "dontSelfClose" }],
73 | output: `let el =
;`,
74 | },
75 | {
76 | code: `let el =
;`,
77 | options: [{ html: "none" }],
78 | errors: [{ messageId: "dontSelfClose" }],
79 | output: `let el =
;`,
80 | },
81 | {
82 | code: `let el = (
83 |
84 |
85 | );`,
86 | errors: [{ messageId: "selfClose" }],
87 | output: `let el = (
88 |
89 | );`,
90 | },
91 | {
92 | code: `let el = (
93 |
94 |
95 | );`,
96 | errors: [{ messageId: "selfClose" }],
97 | output: `let el = (
98 |
99 | );`,
100 | },
101 | {
102 | code: `let el = ;`,
103 | options: [{ component: "none" }],
104 | errors: [{ messageId: "dontSelfClose" }],
105 | output: `let el = ;`,
106 | },
107 | ],
108 | });
109 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/test/rules/no-react-specific-props.test.ts:
--------------------------------------------------------------------------------
1 | import { run } from "../ruleTester";
2 | import rule from "../../src/rules/no-react-specific-props";
3 |
4 | export const cases = run("no-react-specific-props", rule, {
5 | valid: [
6 | `let el = Hello world!
;`,
7 | `let el = Hello world!
;`,
8 | `let el = Hello world!
;`,
9 | `let el = Hello world!
;`,
10 | `let el = ;`,
11 | `let el = `,
12 | `let el = `,
13 | `let el = `,
14 | `let el = `,
15 | `let el = `,
16 | ],
17 | invalid: [
18 | {
19 | code: `let el = Hello world!
`,
20 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }],
21 | output: `let el = Hello world!
`,
22 | },
23 | {
24 | code: `let el = Hello world!
`,
25 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }],
26 | output: `let el = Hello world!
`,
27 | },
28 | {
29 | code: `let el = `,
30 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }],
31 | output: `let el = `,
32 | },
33 | {
34 | code: `let el = Hello world!
`,
35 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }],
36 | output: `let el = Hello world!
`,
37 | },
38 | {
39 | code: `let el = Hello world!`,
40 | errors: [{ messageId: "prefer", data: { from: "className", to: "class" } }],
41 | output: `let el = Hello world!`,
42 | },
43 | {
44 | code: `let el = `,
45 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }],
46 | output: `let el = `,
47 | },
48 | {
49 | code: `let el = `,
50 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }],
51 | output: `let el = `,
52 | },
53 | {
54 | code: `let el = `,
55 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }],
56 | output: `let el = `,
57 | },
58 | {
59 | code: `let el = Hello world!`,
60 | errors: [{ messageId: "prefer", data: { from: "htmlFor", to: "for" } }],
61 | output: `let el = Hello world!`,
62 | },
63 | {
64 | code: `let el = `,
65 | errors: [{ messageId: "noUselessKey" }],
66 | output: `let el = `,
67 | },
68 | ],
69 | });
70 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/src/rules/prefer-classlist.ts:
--------------------------------------------------------------------------------
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, TSESTree as T } from "@typescript-eslint/utils";
10 | import { jsxHasProp, jsxPropName } from "../utils";
11 |
12 | const createRule = ESLintUtils.RuleCreator.withoutDocs;
13 |
14 | type MessageIds = "preferClasslist";
15 | type Options = [{ classnames?: Array }?];
16 |
17 | export default createRule({
18 | meta: {
19 | type: "problem",
20 | docs: {
21 | description:
22 | "Enforce using the classlist prop over importing a classnames helper. The classlist prop accepts an object `{ [class: string]: boolean }` just like classnames.",
23 | url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/prefer-classlist.md",
24 | },
25 | fixable: "code",
26 | deprecated: true,
27 | schema: [
28 | {
29 | type: "object",
30 | properties: {
31 | classnames: {
32 | type: "array",
33 | description: "An array of names to treat as `classnames` functions",
34 | default: ["cn", "clsx", "classnames"],
35 | items: {
36 | type: "string",
37 | },
38 | minItems: 1,
39 | uniqueItems: true,
40 | },
41 | },
42 | additionalProperties: false,
43 | },
44 | ],
45 | messages: {
46 | preferClasslist:
47 | "The classlist prop should be used instead of {{ classnames }} to efficiently set classes based on an object.",
48 | },
49 | },
50 | defaultOptions: [],
51 | create(context) {
52 | const classnames = context.options[0]?.classnames ?? ["cn", "clsx", "classnames"];
53 | return {
54 | JSXAttribute(node) {
55 | if (
56 | ["class", "className"].indexOf(jsxPropName(node)) === -1 ||
57 | jsxHasProp(
58 | (node.parent as T.JSXOpeningElement | undefined)?.attributes ?? [],
59 | "classlist"
60 | )
61 | ) {
62 | return;
63 | }
64 | if (node.value?.type === "JSXExpressionContainer") {
65 | const expr = node.value.expression;
66 | if (
67 | expr.type === "CallExpression" &&
68 | expr.callee.type === "Identifier" &&
69 | classnames.indexOf(expr.callee.name) !== -1 &&
70 | expr.arguments.length === 1 &&
71 | expr.arguments[0].type === "ObjectExpression"
72 | ) {
73 | context.report({
74 | node,
75 | messageId: "preferClasslist",
76 | data: {
77 | classnames: expr.callee.name,
78 | },
79 | fix: (fixer) => {
80 | const attrRange = node.range;
81 | const objectRange = expr.arguments[0].range;
82 | return [
83 | fixer.replaceTextRange([attrRange[0], objectRange[0]], "classlist={"),
84 | fixer.replaceTextRange([objectRange[1], attrRange[1]], "}"),
85 | ];
86 | },
87 | });
88 | }
89 | }
90 | },
91 | };
92 | },
93 | });
94 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/docs/self-closing-comp.md:
--------------------------------------------------------------------------------
1 |
2 | # solid/self-closing-comp
3 | Disallow extra closing tags for components without children.
4 | This rule is **a warning** by default.
5 |
6 | [View source](../src/rules/self-closing-comp.ts) · [View tests](../test/rules/self-closing-comp.test.ts)
7 |
8 |
9 |
10 | ## Rule Options
11 |
12 | Options shown here are the defaults.
13 |
14 | ```js
15 | {
16 | "solid/self-closing-comp": ["warn", {
17 | // which Solid components should be self-closing when possible
18 | component: "all", // "all" | "none"
19 | // which native elements should be self-closing when possible
20 | html: "all", // "all" | "void" | "none"
21 | }]
22 | }
23 | ```
24 |
25 |
26 |
27 | ## Tests
28 |
29 | ### Invalid Examples
30 |
31 | These snippets cause lint errors, and all of them can be auto-fixed.
32 |
33 | ```js
34 | let el = ;
35 | // after eslint --fix:
36 | let el = ;
37 |
38 | let el =
;
39 | // after eslint --fix:
40 | let el =
;
41 |
42 | /* eslint solid/self-closing-comp: ["error", { "html": "void" }] */
43 | let el = ;
44 | // after eslint --fix:
45 | let el = ;
46 |
47 | /* eslint solid/self-closing-comp: ["error", { "html": "void" }] */
48 | let el = ;
49 | // after eslint --fix:
50 | let el = ;
51 |
52 | /* eslint solid/self-closing-comp: ["error", { "html": "none" }] */
53 | let el =
;
54 | // after eslint --fix:
55 | let el =
;
56 |
57 | /* eslint solid/self-closing-comp: ["error", { "html": "none" }] */
58 | let el =
;
59 | // after eslint --fix:
60 | let el =
;
61 |
62 | let el = ;
63 | // after eslint --fix:
64 | let el = ;
65 |
66 | let el = ;
67 | // after eslint --fix:
68 | let el = ;
69 |
70 | /* eslint solid/self-closing-comp: ["error", { "component": "none" }] */
71 | let el = ;
72 | // after eslint --fix:
73 | let el = ;
74 | ```
75 |
76 | ### Valid Examples
77 |
78 | These snippets don't cause lint errors.
79 |
80 | ```js
81 | let el = ;
82 |
83 | let el = ;
84 |
85 | let el = (
86 |
87 |
88 |
89 | );
90 |
91 | let el = (
92 |
93 |
94 |
95 | );
96 |
97 | let el = (
98 |
99 |
100 |
101 | );
102 |
103 | let el = (
104 |
105 |
106 |
107 | );
108 |
109 | let el = ;
110 |
111 | let el = ;
112 |
113 | let el = ;
114 |
115 | let el =
;
116 |
117 | let el =
;
118 |
119 | /* eslint solid/self-closing-comp: ["error", { "html": "none" }] */
120 | let el = ;
121 |
122 | /* eslint solid/self-closing-comp: ["error", { "html": "none" }] */
123 | let el =
;
124 |
125 | /* eslint solid/self-closing-comp: ["error", { "html": "void" }] */
126 | let el = ;
127 |
128 | /* eslint solid/self-closing-comp: ["error", { "html": "none" }] */
129 | let el = ;
130 |
131 | /* eslint solid/self-closing-comp: ["error", { "component": "none" }] */
132 | let el = ;
133 | ```
134 |
135 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/test/rules/no-innerhtml.test.ts:
--------------------------------------------------------------------------------
1 | import { AST_NODE_TYPES as T } from "@typescript-eslint/utils";
2 | import { run } from "../ruleTester";
3 | import rule from "../../src/rules/no-innerhtml";
4 |
5 | export const cases = run("no-innerhtml", rule, {
6 | valid: [
7 | `let el = Hello world!
`,
8 | `let el = Hello world!`,
9 | `let el = `,
10 | `let el = Hello" + "
world!
"} />`,
11 | `let el =
`,
12 | ],
13 | invalid: [
14 | {
15 | code: `let el =
`,
16 | options: [{ allowStatic: false }],
17 | errors: [{ messageId: "dangerous" }],
18 | },
19 | {
20 | code: `let el =
Hello
world!
"} />`,
21 | options: [{ allowStatic: false }],
22 | errors: [{ messageId: "dangerous" }],
23 | },
24 | {
25 | code: `let el =
Hello" + "
world!
"} />`,
26 | options: [{ allowStatic: false }],
27 | errors: [{ messageId: "dangerous" }],
28 | },
29 | {
30 | code: `let el =
`,
31 | errors: [{ messageId: "dangerous" }],
32 | },
33 | {
34 | code: `let el =
`,
35 | errors: [
36 | {
37 | messageId: "notHtml",
38 | suggestions: [
39 | {
40 | messageId: "useInnerText",
41 | output: `let el =
`,
42 | },
43 | ],
44 | },
45 | ],
46 | },
47 | {
48 | code: `
49 | let el = (
50 |
51 |
Child element content
52 |
53 | );
54 | `,
55 | errors: [{ messageId: "conflict", type: T.JSXElement }],
56 | },
57 | {
58 | code: `
59 | let el = (
60 |
61 |
Child element content 1
62 |
Child element context 2
63 |
64 | );
65 | `,
66 | errors: [{ messageId: "conflict", type: T.JSXElement }],
67 | },
68 | {
69 | code: `
70 | let el = (
71 |
72 | {"Child text content"}
73 |
74 | );
75 | `,
76 | errors: [{ messageId: "conflict", type: T.JSXElement }],
77 | },
78 | {
79 | code: `
80 | let el = (
81 |
82 | {identifier}
83 |
84 | );
85 | `,
86 | errors: [{ messageId: "conflict", type: T.JSXElement }],
87 | },
88 | {
89 | code: `let el =
Hello
world!
" }} />`,
90 | errors: [{ messageId: "dangerouslySetInnerHTML" }],
91 | output: `let el =
Hello
world!
"} />`,
92 | },
93 | {
94 | code: `let el =
`,
95 | errors: [{ messageId: "dangerouslySetInnerHTML" }],
96 | },
97 | {
98 | code: `let el =
`,
99 | errors: [{ messageId: "dangerouslySetInnerHTML" }],
100 | },
101 | ],
102 | });
103 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/src/rules/jsx-no-duplicate-props.ts:
--------------------------------------------------------------------------------
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 { jsxGetAllProps } from "../utils";
11 |
12 | const createRule = ESLintUtils.RuleCreator.withoutDocs;
13 |
14 | /*
15 | * This rule is adapted from eslint-plugin-react's jsx-no-duplicate-props rule under
16 | * the MIT license, with some enhancements. Thank you for your work!
17 | */
18 |
19 | type MessageIds = "noDuplicateProps" | "noDuplicateClass" | "noDuplicateChildren";
20 | type Options = [{ ignoreCase?: boolean }?];
21 |
22 | export default createRule
({
23 | meta: {
24 | type: "problem",
25 | docs: {
26 | description: "Disallow passing the same prop twice in JSX.",
27 | url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-duplicate-props.md",
28 | },
29 | schema: [
30 | {
31 | type: "object",
32 | properties: {
33 | ignoreCase: {
34 | type: "boolean",
35 | description: "Consider two prop names differing only by case to be the same.",
36 | default: false,
37 | },
38 | },
39 | additionalProperties: false,
40 | },
41 | ],
42 | messages: {
43 | noDuplicateProps: "Duplicate props are not allowed.",
44 | noDuplicateClass:
45 | "Duplicate `class` props are not allowed; while it might seem to work, it can break unexpectedly. Use `classList` instead.",
46 | noDuplicateChildren: "Using {{used}} at the same time is not allowed.",
47 | },
48 | },
49 | defaultOptions: [],
50 | create(context) {
51 | return {
52 | JSXOpeningElement(node) {
53 | const ignoreCase = context.options[0]?.ignoreCase ?? false;
54 | const props = new Set();
55 | const checkPropName = (name: string, node: T.Node) => {
56 | if (ignoreCase || name.startsWith("on")) {
57 | name = name
58 | .toLowerCase()
59 | .replace(/^on(?:capture)?:/, "on")
60 | .replace(/^(?:attr|prop):/, "");
61 | }
62 | if (props.has(name)) {
63 | context.report({
64 | node,
65 | messageId: name === "class" ? "noDuplicateClass" : "noDuplicateProps",
66 | });
67 | }
68 | props.add(name);
69 | };
70 |
71 | for (const [name, propNode] of jsxGetAllProps(node.attributes)) {
72 | checkPropName(name, propNode);
73 | }
74 |
75 | const hasChildrenProp = props.has("children");
76 | const hasChildren = (node.parent as T.JSXElement | T.JSXFragment).children.length > 0;
77 | const hasInnerHTML = props.has("innerHTML") || props.has("innerhtml");
78 | const hasTextContent = props.has("textContent") || props.has("textContent");
79 | const used = [
80 | hasChildrenProp && "`props.children`",
81 | hasChildren && "JSX children",
82 | hasInnerHTML && "`props.innerHTML`",
83 | hasTextContent && "`props.textContent`",
84 | ].filter(Boolean);
85 | if (used.length > 1) {
86 | context.report({
87 | node,
88 | messageId: "noDuplicateChildren",
89 | data: {
90 | used: used.join(", "),
91 | },
92 | });
93 | }
94 | },
95 | };
96 | },
97 | });
98 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/test/rules/imports.test.ts:
--------------------------------------------------------------------------------
1 | import { run, tsOnly } from "../ruleTester";
2 | import rule from "../../src/rules/imports";
3 |
4 | export const cases = run("imports", rule, {
5 | valid: [
6 | `import { createSignal, mergeProps as merge } from "solid-js";`,
7 | `import { createSignal, mergeProps as merge } from 'solid-js';`,
8 | `import { render, hydrate } from "solid-js/web";`,
9 | `import { createStore, produce } from "solid-js/store";`,
10 | `import { createSignal } from "solid-js";
11 | import { render } from "solid-js/web";
12 | import { something } from "somewhere/else";
13 | import { createStore } from "solid-js/store";`,
14 | `import * as Solid from "solid-js"; Solid.render();`,
15 | {
16 | code: `import type { Component, JSX } from "solid-js";
17 | import type { Store } from "solid-js/store";`,
18 | [tsOnly]: true,
19 | },
20 | ],
21 | invalid: [
22 | {
23 | code: `import { createEffect } from "solid-js/web";`,
24 | errors: [
25 | {
26 | messageId: "prefer-source",
27 | data: { name: "createEffect", source: "solid-js" },
28 | },
29 | ],
30 | output: `import { createEffect } from "solid-js";
31 | `,
32 | },
33 | {
34 | code: `import { createEffect } from "solid-js/web";
35 | import { createSignal } from "solid-js";`,
36 | errors: [
37 | {
38 | messageId: "prefer-source",
39 | data: { name: "createEffect", source: "solid-js" },
40 | },
41 | ],
42 | output: `
43 | import { createSignal, createEffect } from "solid-js";`,
44 | },
45 |
46 | {
47 | code: `import type { Component } from "solid-js/store";
48 | import { createSignal } from "solid-js";
49 | console.log('hi');`,
50 | errors: [
51 | {
52 | messageId: "prefer-source",
53 | data: { name: "Component", source: "solid-js" },
54 | },
55 | ],
56 | output: `
57 | import { createSignal, Component } from "solid-js";
58 | console.log('hi');`,
59 | [tsOnly]: true,
60 | },
61 | {
62 | code: `import { createSignal } from "solid-js/web";
63 | import "solid-js";`,
64 | errors: [
65 | {
66 | messageId: "prefer-source",
67 | data: { name: "createSignal", source: "solid-js" },
68 | },
69 | ],
70 | output: `
71 | import { createSignal } from "solid-js";`,
72 | },
73 | {
74 | code: `import { createSignal } from "solid-js/web";
75 | import {} from "solid-js";`,
76 | errors: [
77 | {
78 | messageId: "prefer-source",
79 | data: { name: "createSignal", source: "solid-js" },
80 | },
81 | ],
82 | output: `
83 | import { createSignal } from "solid-js";`,
84 | },
85 | // Two-part fix, output here is first pass...
86 | {
87 | code: `import { createEffect } from "solid-js/web";
88 | import { render } from "solid-js";`,
89 | errors: [
90 | {
91 | messageId: "prefer-source",
92 | data: { name: "createEffect", source: "solid-js" },
93 | },
94 | {
95 | messageId: "prefer-source",
96 | data: { name: "render", source: "solid-js/web" },
97 | },
98 | ],
99 | output: `
100 | import { render, createEffect } from "solid-js";`,
101 | },
102 | // ...and output here is second pass
103 | {
104 | code: `
105 | import { render, createEffect } from "solid-js";`,
106 | errors: [
107 | {
108 | messageId: "prefer-source",
109 | data: { name: "render", source: "solid-js/web" },
110 | },
111 | ],
112 | output: `
113 | import { render } from "solid-js/web";
114 | import { createEffect } from "solid-js";`,
115 | },
116 | ],
117 | });
118 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/src/rules/no-proxy-apis.ts:
--------------------------------------------------------------------------------
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, 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();
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 | });
97 |
--------------------------------------------------------------------------------
/packages/eslint-plugin-solid/src/rules/prefer-for.ts:
--------------------------------------------------------------------------------
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, ASTUtils } from "@typescript-eslint/utils";
10 | import { isFunctionNode, isJSXElementOrFragment } from "../utils";
11 |
12 | const createRule = ESLintUtils.RuleCreator.withoutDocs;
13 | const { getPropertyName } = ASTUtils;
14 |
15 | export default createRule({
16 | meta: {
17 | type: "problem",
18 | docs: {
19 | description:
20 | "Enforce using Solid's `` component for mapping an array to JSX elements.",
21 | url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/prefer-for.md",
22 | },
23 | fixable: "code",
24 | schema: [],
25 | messages: {
26 | preferFor:
27 | "Use Solid's `` component for efficiently rendering lists. Array#map causes DOM elements to be recreated.",
28 | preferForOrIndex:
29 | "Use Solid's `` component or `` component for rendering lists. Array#map causes DOM elements to be recreated.",
30 | },
31 | },
32 | defaultOptions: [],
33 | create(context) {
34 | const reportPreferFor = (node: T.CallExpression) => {
35 | const jsxExpressionContainerNode = node.parent as T.JSXExpressionContainer;
36 | const arrayNode = (node.callee as T.MemberExpression).object;
37 | const mapFnNode = node.arguments[0];
38 | context.report({
39 | node,
40 | messageId: "preferFor",
41 | fix: (fixer) => {
42 | const beforeArray: [number, number] = [
43 | jsxExpressionContainerNode.range[0],
44 | arrayNode.range[0],
45 | ];
46 | const betweenArrayAndMapFn: [number, number] = [arrayNode.range[1], mapFnNode.range[0]];
47 | const afterMapFn: [number, number] = [
48 | mapFnNode.range[1],
49 | jsxExpressionContainerNode.range[1],
50 | ];
51 | // We can insert the component
52 | return [
53 | fixer.replaceTextRange(beforeArray, "{"),
55 | fixer.replaceTextRange(afterMapFn, "}"),
56 | ];
57 | },
58 | });
59 | };
60 |
61 | return {
62 | CallExpression(node) {
63 | const callOrChain = node.parent?.type === "ChainExpression" ? node.parent : node;
64 | if (
65 | callOrChain.parent?.type === "JSXExpressionContainer" &&
66 | isJSXElementOrFragment(callOrChain.parent.parent)
67 | ) {
68 | // check for Array.prototype.map in JSX
69 | if (
70 | node.callee.type === "MemberExpression" &&
71 | getPropertyName(node.callee) === "map" &&
72 | node.arguments.length === 1 && // passing thisArg to Array.prototype.map is rare, deopt in that case
73 | isFunctionNode(node.arguments[0])
74 | ) {
75 | const mapFnNode = node.arguments[0];
76 | if (mapFnNode.params.length === 1 && mapFnNode.params[0].type !== "RestElement") {
77 | // The map fn doesn't take an index param, so it can't possibly be an index-keyed list. Use .
78 | // The returned JSX, if it's coming from React, will have an unnecessary `key` prop to be removed in
79 | // the useless-keys rule.
80 | reportPreferFor(node);
81 | } else {
82 | // Too many possible solutions to make a suggestion or fix
83 | context.report({
84 | node,
85 | messageId: "preferForOrIndex",
86 | });
87 | }
88 | }
89 | }
90 | },
91 | };
92 | },
93 | });
94 |
--------------------------------------------------------------------------------
/test/valid/examples/css-animations.jsx:
--------------------------------------------------------------------------------
1 | import { createSignal, For, Match, Switch } from "solid-js";
2 | import { render } from "solid-js/web";
3 | import { Transition, TransitionGroup } from "solid-transition-group";
4 | import "./styles.css";
5 |
6 | function shuffle(array) {
7 | return array.sort(() => Math.random() - 0.5);
8 | }
9 | let nextId = 10;
10 |
11 | const App = () => {
12 | const [show, toggleShow] = createSignal(true),
13 | [select, setSelect] = createSignal(0),
14 | [numList, setNumList] = createSignal([1, 2, 3, 4, 5, 6, 7, 8, 9]),
15 | randomIndex = () => Math.floor(Math.random() * numList().length);
16 |
17 | return (
18 | <>
19 |
20 |
21 | Transition:
22 |
23 | {show() && (
24 |
25 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero,
26 | at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.
27 |
28 | )}
29 |
30 |
31 | Animation:
32 |
33 | {show() && (
34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero,
36 | at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.
37 |
38 | )}
39 |
40 |
41 | Custom JS:
42 | {
44 | const a = el.animate([{ opacity: 0 }, { opacity: 1 }], {
45 | duration: 600,
46 | });
47 | a.finished.then(done);
48 | }}
49 | onExit={(el, done) => {
50 | const a = el.animate([{ opacity: 1 }, { opacity: 0 }], {
51 | duration: 600,
52 | });
53 | a.finished.then(done);
54 | }}
55 | >
56 | {show() && (
57 |
58 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis enim libero,
59 | at lacinia diam fermentum id. Pellentesque habitant morbi tristique senectus et netus.
60 |
61 | )}
62 |
63 |
64 | Switch OutIn
65 |
66 |
67 |
68 |
69 |
70 | The First
71 |
72 |
73 | The Second
74 |
75 |
76 | The Third
77 |
78 |
79 |
80 | Group
81 |
82 |
91 |
100 |
108 |
109 |
110 | {(r) => {r}}
111 |
112 | >
113 | );
114 | };
115 |
116 | render(App, document.getElementById("app"));
117 |
--------------------------------------------------------------------------------