Skip to content

Commit 9a891b1

Browse files
Rel1cxCopilot
andauthored
feat: add react-dom/no-string-style-prop rule, closes #1170 (#1171)
Signed-off-by: REL1CX <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 06067ab commit 9a891b1

File tree

8 files changed

+177
-0
lines changed

8 files changed

+177
-0
lines changed

apps/website/content/docs/rules/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"dom-no-render",
7777
"dom-no-render-return-value",
7878
"dom-no-script-url",
79+
"dom-no-string-style-prop",
7980
"dom-no-unknown-property",
8081
"dom-no-unsafe-iframe-sandbox",
8182
"dom-no-unsafe-target-blank",

apps/website/content/docs/rules/overview.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ The `jsx-*` rules check for issues exclusive to JSX syntax, which are absent fro
108108
| [`no-render`](./dom-no-render) | 2️⃣ | `🔄` | Replaces usages of `ReactDom.render()` with `createRoot(node).render()` | >=18.0.0 |
109109
| [`no-render-return-value`](./dom-no-render-return-value) | 2️⃣ | | Disallow the return value of `ReactDOM.render` | |
110110
| [`no-script-url`](./dom-no-script-url) | 1️⃣ | | Disallow `javascript:` URLs as attribute values | |
111+
| [`no-string-style-prop`](./dom-no-string-style-prop) | 2️⃣ | | Disallow string values for the `style` prop | |
111112
| [`no-unknown-property`](./dom-no-unknown-property) | 0️⃣ | `🔧` `⚙️` | Disallow unknown `DOM` property | |
112113
| [`no-unsafe-iframe-sandbox`](./dom-no-unsafe-iframe-sandbox) | 1️⃣ | | Enforces `sandbox` attribute for `iframe` elements is not set to unsafe combinations | |
113114
| [`no-unsafe-target-blank`](./dom-no-unsafe-target-blank) | 1️⃣ | `🔧` | Disallow `target="_blank"` without `rel="noreferrer noopener"` | |

packages/plugins/eslint-plugin-react-dom/src/configs/recommended.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const rules = {
1515
"react-dom/no-render": "error",
1616
"react-dom/no-render-return-value": "error",
1717
"react-dom/no-script-url": "warn",
18+
"react-dom/no-string-style-prop": "error",
1819
"react-dom/no-unsafe-iframe-sandbox": "warn",
1920
"react-dom/no-unsafe-target-blank": "warn",
2021
"react-dom/no-use-form-state": "error",

packages/plugins/eslint-plugin-react-dom/src/plugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import noNamespace from "./rules/no-namespace";
1010
import noRender from "./rules/no-render";
1111
import noRenderReturnValue from "./rules/no-render-return-value";
1212
import noScriptUrl from "./rules/no-script-url";
13+
import noStringStyleProp from "./rules/no-string-style-prop";
1314
import noUnknownProperty from "./rules/no-unknown-property";
1415
import noUnsafeIframeSandbox from "./rules/no-unsafe-iframe-sandbox";
1516
import noUnsafeTargetBlank from "./rules/no-unsafe-target-blank";
@@ -33,6 +34,7 @@ export const plugin = {
3334
"no-render": noRender,
3435
"no-render-return-value": noRenderReturnValue,
3536
"no-script-url": noScriptUrl,
37+
"no-string-style-prop": noStringStyleProp,
3638
"no-unknown-property": noUnknownProperty,
3739
"no-unsafe-iframe-sandbox": noUnsafeIframeSandbox,
3840
"no-unsafe-target-blank": noUnsafeTargetBlank,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
title: no-string-style-prop
3+
---
4+
5+
**Full Name in `eslint-plugin-react-dom`**
6+
7+
```sh copy
8+
react-dom/no-string-style-prop
9+
```
10+
11+
**Full Name in `@eslint-react/eslint-plugin`**
12+
13+
```sh copy
14+
@eslint-react/dom/no-string-style-prop
15+
```
16+
17+
**Presets**
18+
19+
- `dom`
20+
- `recommended`
21+
22+
## Description
23+
24+
Disallow the use of string style prop in JSX. Use an object instead.
25+
26+
## Examples
27+
28+
### Before
29+
30+
```tsx
31+
import React from "react";
32+
33+
function MyComponent() {
34+
return <div style="color: red;" />;
35+
}
36+
```
37+
38+
### After
39+
40+
```tsx
41+
import React from "react";
42+
43+
function MyComponent() {
44+
return <div style={{ color: "red" }} />;
45+
}
46+
```
47+
48+
## Implementation
49+
50+
- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom/src/rules/no-string-style-prop.ts)
51+
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom/src/rules/no-string-style-prop.spec.ts)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import tsx from "dedent";
2+
3+
import { allValid, ruleTester } from "../../../../../test";
4+
import rule, { RULE_NAME } from "./no-string-style-prop";
5+
6+
ruleTester.run(RULE_NAME, rule, {
7+
invalid: [
8+
{
9+
code: tsx`
10+
function Component() {
11+
return <div style="color: red;" />;
12+
}
13+
`,
14+
errors: [
15+
{
16+
messageId: "noStringStyleProp",
17+
},
18+
],
19+
},
20+
{
21+
code: tsx`
22+
function Component() {
23+
return <div style={"color: red;"} />;
24+
}
25+
`,
26+
errors: [
27+
{
28+
messageId: "noStringStyleProp",
29+
},
30+
],
31+
},
32+
{
33+
code: tsx`
34+
function Component() {
35+
return <div style={\`color: red;\`} />;
36+
}
37+
`,
38+
errors: [
39+
{
40+
messageId: "noStringStyleProp",
41+
},
42+
],
43+
},
44+
],
45+
valid: [
46+
...allValid,
47+
tsx`
48+
function Component() {
49+
return <div style={{ color: "red" }} />;
50+
}
51+
`,
52+
tsx`
53+
function Component() {
54+
return <div style={someStyle} />;
55+
}
56+
`,
57+
],
58+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type { RuleContext, RuleFeature } from "@eslint-react/kit";
2+
import type { TSESTree } from "@typescript-eslint/types";
3+
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
4+
import type { CamelCase } from "string-ts";
5+
6+
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
7+
import { createRule } from "../utils";
8+
9+
export const RULE_NAME = "no-string-style-prop";
10+
11+
export const RULE_FEATURES = [] as const satisfies RuleFeature[];
12+
13+
export type MessageID = CamelCase<typeof RULE_NAME>;
14+
15+
export default createRule<[], MessageID>({
16+
meta: {
17+
type: "problem",
18+
docs: {
19+
description: "Disallows the use of string style prop.",
20+
[Symbol.for("rule_features")]: RULE_FEATURES,
21+
},
22+
messages: {
23+
noStringStyleProp: "Do not use string style prop. Use an object instead.",
24+
},
25+
schema: [],
26+
},
27+
name: RULE_NAME,
28+
create,
29+
defaultOptions: [],
30+
});
31+
32+
export function create(context: RuleContext<MessageID, []>): RuleListener {
33+
return {
34+
JSXAttribute(node) {
35+
if (node.name.name !== "style") return;
36+
const styleText = getAttributeValueText(context, node.value);
37+
if (styleText == null) return;
38+
context.report({
39+
messageId: "noStringStyleProp",
40+
node,
41+
});
42+
},
43+
};
44+
}
45+
46+
function getAttributeValueText(context: RuleContext, node: TSESTree.JSXAttribute["value"]) {
47+
if (node == null) return null;
48+
switch (true) {
49+
case node.type === T.Literal
50+
&& typeof node.value === "string":
51+
return context.sourceCode.getText(node);
52+
case node.type === T.JSXExpressionContainer
53+
&& node.expression.type === T.Literal
54+
&& typeof node.expression.value === "string":
55+
return context.sourceCode.getText(node.expression);
56+
case node.type === T.JSXExpressionContainer
57+
&& node.expression.type === T.TemplateLiteral:
58+
return context.sourceCode.getText(node.expression);
59+
default:
60+
return null;
61+
}
62+
}

packages/plugins/eslint-plugin/src/configs/recommended-typescript.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const name = "@eslint-react/recommended-typescript";
66

77
export const rules = {
88
...recommended.rules,
9+
"@eslint-react/dom/no-string-style-prop": "off",
910
"@eslint-react/dom/no-unknown-property": "off",
1011
"@eslint-react/jsx-no-duplicate-props": "off",
1112
"@eslint-react/jsx-uses-react": "off",

0 commit comments

Comments
 (0)