Skip to content

Commit 89df5d2

Browse files
committed
refactor: minor improvements (#1061)
1 parent 204e5cd commit 89df5d2

File tree

10 files changed

+78
-52
lines changed

10 files changed

+78
-52
lines changed

apps/website/content/docs/faq.mdx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
88

99
<Accordion title="Why?">
1010

11-
**ESLint React addresses critical gaps of the existing `eslint-plugin-react` in modern React ecosystems**. The current plugin assumes a DOM-centric model, which creates friction when working with alternative renderers like React Native, React Three Fiber, or custom renderers.
11+
**ESLint React addresses critical gaps of the existing `eslint-plugin-react` in modern React ecosystems**. While named "react", the current plugin implementation specifically targets React DOM and maintains DOM-centric assumptions, creating friction when used with alternative renderers like React Native, React Three Fiber, or custom renderers.
1212

13-
Key goals of ESLint React include:
13+
Our solution treating DOM as one of many supported targets rather than the default assumption. This paradigm shift enables:
1414

15-
- **Context-aware linting**: It adapts to different runtime environments, ensuring that linting rules are relevant to the specific platform you’re targeting.
16-
- **Future-proof architecture**: It is designed to be extensible and adaptable, allowing for easy integration of new features and support for emerging React patterns.
17-
- **Unified code quality standards**: It enables consistent linting and code quality enforcement across various React-based projects, regardless of the rendering target.
18-
19-
This makes ESLint React a more flexible and modern alternative for working across diverse React ecosystems.
15+
- **Context-aware linting**: Adapting to different runtime environments
16+
- **Future-proof architecture**: Compatibility with emerging React platforms
17+
- **Unified code quality standards**: Consistent linting across diverse projects
2018

2119
</Accordion>
2220

packages/plugins/eslint-plugin-react-dom/src/rules/no-dangerously-set-innerhtml-with-children.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ export default createRule<[], MessageID>({
3232
defaultOptions: [],
3333
});
3434

35+
const dangerouslySetInnerHTML = "dangerouslySetInnerHTML";
36+
3537
export function create(context: RuleContext<MessageID, []>): RuleListener {
36-
if (!context.sourceCode.text.includes("dangerouslySetInnerHTML")) return {};
38+
if (!context.sourceCode.text.includes(dangerouslySetInnerHTML)) return {};
3739
return {
3840
JSXElement(node) {
3941
const attributes = node.openingElement.attributes;
4042
const initialScope = context.sourceCode.getScope(node);
4143
const hasChildren = hasChildrenWithin(node) || ER.hasAttribute(context, "children", attributes, initialScope);
42-
if (hasChildren && ER.hasAttribute(context, "dangerouslySetInnerHTML", attributes, initialScope)) {
44+
if (hasChildren && ER.hasAttribute(context, dangerouslySetInnerHTML, attributes, initialScope)) {
4345
context.report({
4446
messageId: "noDangerouslySetInnerhtmlWithChildren",
4547
node,

packages/plugins/eslint-plugin-react-dom/src/rules/no-dangerously-set-innerhtml.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ export default createRule<[], MessageID>({
2929
defaultOptions: [],
3030
});
3131

32+
const dangerouslySetInnerHTML = "dangerouslySetInnerHTML";
33+
3234
export function create(context: RuleContext<MessageID, []>): RuleListener {
33-
if (!context.sourceCode.text.includes("dangerouslySetInnerHTML")) return {};
35+
if (!context.sourceCode.text.includes(dangerouslySetInnerHTML)) return {};
3436
return {
3537
JSXElement(node) {
36-
const attributes = node.openingElement.attributes;
3738
const attribute = ER.getAttribute(
3839
context,
39-
"dangerouslySetInnerHTML",
40-
attributes,
40+
dangerouslySetInnerHTML,
41+
node.openingElement.attributes,
4142
context.sourceCode.getScope(node),
4243
);
4344
if (attribute == null) return;

packages/plugins/eslint-plugin-react-dom/src/rules/no-find-dom-node.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,21 @@ export default createRule<[], MessageID>({
2828
defaultOptions: [],
2929
});
3030

31+
const findDOMNode = "findDOMNode";
32+
3133
export function create(context: RuleContext<MessageID, []>): RuleListener {
32-
if (!context.sourceCode.text.includes("findDOMNode")) return {};
34+
if (!context.sourceCode.text.includes(findDOMNode)) return {};
3335
return {
3436
CallExpression(node) {
3537
const { callee } = node;
3638
switch (callee.type) {
3739
case T.Identifier:
38-
if (callee.name === "findDOMNode") {
40+
if (callee.name === findDOMNode) {
3941
context.report({ messageId: "noFindDomNode", node });
4042
}
4143
return;
4244
case T.MemberExpression:
43-
if (callee.property.type === T.Identifier && callee.property.name === "findDOMNode") {
45+
if (callee.property.type === T.Identifier && callee.property.name === findDOMNode) {
4446
context.report({ messageId: "noFindDomNode", node });
4547
}
4648
return;

packages/plugins/eslint-plugin-react-dom/src/rules/no-flush-sync.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,21 @@ export default createRule<[], MessageID>({
2828
defaultOptions: [],
2929
});
3030

31+
const flushSync = "flushSync";
32+
3133
export function create(context: RuleContext<MessageID, []>): RuleListener {
32-
if (!context.sourceCode.text.includes("flushSync")) return {};
34+
if (!context.sourceCode.text.includes(flushSync)) return {};
3335
return {
3436
CallExpression(node) {
3537
const { callee } = node;
3638
switch (callee.type) {
3739
case T.Identifier:
38-
if (callee.name === "flushSync") {
40+
if (callee.name === flushSync) {
3941
context.report({ messageId: "noFlushSync", node });
4042
}
4143
return;
4244
case T.MemberExpression:
43-
if (callee.property.type === T.Identifier && callee.property.name === "flushSync") {
45+
if (callee.property.type === T.Identifier && callee.property.name === flushSync) {
4446
context.report({ messageId: "noFlushSync", node });
4547
}
4648
return;

packages/plugins/eslint-plugin-react-dom/src/rules/no-hydrate.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ export default createRule<[], MessageID>({
3434
defaultOptions: [],
3535
});
3636

37+
const hydrate = "hydrate";
38+
3739
export function create(context: RuleContext<MessageID, []>): RuleListener {
38-
if (!context.sourceCode.text.includes("hydrate")) return {};
40+
if (!context.sourceCode.text.includes(hydrate)) return {};
3941
const settings = getSettingsFromContext(context);
4042
if (compare(settings.version, "18.0.0", "<")) return {};
4143

@@ -56,7 +58,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
5658
case node.callee.type === T.MemberExpression
5759
&& node.callee.object.type === T.Identifier
5860
&& node.callee.property.type === T.Identifier
59-
&& node.callee.property.name === "hydrate"
61+
&& node.callee.property.name === hydrate
6062
&& reactDomNames.has(node.callee.object.name):
6163
context.report({
6264
messageId: "noHydrate",
@@ -73,7 +75,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
7375
switch (specifier.type) {
7476
case T.ImportSpecifier:
7577
if (specifier.imported.type !== T.Identifier) continue;
76-
if (specifier.imported.name === "hydrate") {
78+
if (specifier.imported.name === hydrate) {
7779
hydrateNames.add(specifier.local.name);
7880
}
7981
continue;
Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { RuleContext, RuleFeature } from "@eslint-react/kit";
22
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
33
import type { CamelCase } from "string-ts";
4-
import * as AST from "@eslint-react/ast";
54
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
65

76
import { createRule } from "../utils";
@@ -18,7 +17,7 @@ const banParentTypes = [
1817
T.ReturnStatement,
1918
T.ArrowFunctionExpression,
2019
T.AssignmentExpression,
21-
] as const;
20+
];
2221

2322
export default createRule<[], MessageID>({
2423
meta: {
@@ -28,7 +27,7 @@ export default createRule<[], MessageID>({
2827
[Symbol.for("rule_features")]: RULE_FEATURES,
2928
},
3029
messages: {
31-
noRenderReturnValue: "Do not depend on the return value from '{{objectName}}.render'.",
30+
noRenderReturnValue: "Do not depend on the return value from 'ReactDOM.render'.",
3231
},
3332
schema: [],
3433
},
@@ -38,34 +37,50 @@ export default createRule<[], MessageID>({
3837
});
3938

4039
export function create(context: RuleContext<MessageID, []>): RuleListener {
40+
const reactDomNames = new Set<string>(["ReactDOM", "ReactDom"]);
41+
const renderNames = new Set<string>();
42+
4143
return {
4244
CallExpression(node) {
43-
const { callee, parent } = node;
44-
if (callee.type !== T.MemberExpression) {
45-
return;
46-
}
47-
if (callee.object.type !== T.Identifier) {
48-
return;
45+
switch (true) {
46+
case node.callee.type === T.Identifier
47+
&& renderNames.has(node.callee.name)
48+
&& banParentTypes.includes(node.parent.type):
49+
context.report({
50+
messageId: "noRenderReturnValue",
51+
node,
52+
});
53+
return;
54+
case node.callee.type === T.MemberExpression
55+
&& node.callee.object.type === T.Identifier
56+
&& node.callee.property.type === T.Identifier
57+
&& node.callee.property.name === "render"
58+
&& reactDomNames.has(node.callee.object.name)
59+
&& banParentTypes.includes(node.parent.type):
60+
context.report({
61+
messageId: "noRenderReturnValue",
62+
node,
63+
});
64+
return;
4965
}
50-
if (!("name" in callee.object)) {
51-
return;
52-
}
53-
const objectName = callee.object.name;
54-
if (
55-
objectName.toLowerCase() !== "reactdom"
56-
|| callee.property.type !== T.Identifier
57-
|| callee.property.name !== "render"
58-
|| !AST.isOneOf(banParentTypes)(parent)
59-
) {
60-
return;
66+
},
67+
ImportDeclaration(node) {
68+
const [baseSource] = node.source.value.split("/");
69+
if (baseSource !== "react-dom") return;
70+
for (const specifier of node.specifiers) {
71+
switch (specifier.type) {
72+
case T.ImportSpecifier:
73+
if (specifier.imported.type !== T.Identifier) continue;
74+
if (specifier.imported.name === "render") {
75+
renderNames.add(specifier.local.name);
76+
}
77+
continue;
78+
case T.ImportDefaultSpecifier:
79+
case T.ImportNamespaceSpecifier:
80+
reactDomNames.add(specifier.local.name);
81+
continue;
82+
}
6183
}
62-
context.report({
63-
messageId: "noRenderReturnValue",
64-
node,
65-
data: {
66-
objectName,
67-
},
68-
});
6984
},
7085
};
7186
}

packages/plugins/eslint-plugin-react-dom/src/rules/no-render.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
3939
const settings = getSettingsFromContext(context);
4040
if (compare(settings.version, "18.0.0", "<")) return {};
4141

42-
const reactDomNames = new Set<string>();
42+
const reactDomNames = new Set<string>(["ReactDOM", "ReactDom"]);
4343
const renderNames = new Set<string>();
4444

4545
return {

packages/plugins/eslint-plugin-react-dom/src/rules/no-script-url.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
4040
if (node.name.type !== T.JSXIdentifier || node.value == null) {
4141
return;
4242
}
43-
const attributeName = ER.getAttributeName(context, node);
44-
const attributeValue = ER.getAttributeValue(context, node, attributeName);
43+
const attributeValue = ER.getAttributeValue(context, node, ER.getAttributeName(context, node));
4544
if (attributeValue.kind === "none" || typeof attributeValue.value !== "string") return;
4645
if (RE.JAVASCRIPT_PROTOCOL.test(attributeValue.value)) {
4746
context.report({

packages/utilities/kit/src/RE.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Regular expressions for matching a HTML tag name
3+
*/
4+
export const HTML_TAG = /^[a-z][^-]*$/u;
5+
16
/**
27
* Regular expression for matching a TypeScript file extension.
38
*/

0 commit comments

Comments
 (0)