Skip to content

Commit 01370b2

Browse files
committed
refactor: introduce new utility functions for JSX attribute and element name handling
1 parent 5ddf3e0 commit 01370b2

37 files changed

+213
-146
lines changed

packages/core/docs/functions/useComponentCollector.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,34 @@
88

99
> **useComponentCollector**(`context`, `hint`, `options`): `object`
1010
11+
Get a ctx and listeners for the rule to collect function components
12+
1113
## Parameters
1214

1315
### context
1416

1517
`Readonly`\<`RuleContext`\<`string`, readonly `unknown`[]\>\>
1618

19+
The ESLint rule context
20+
1721
### hint
1822

1923
`bigint` = `DEFAULT_COMPONENT_HINT`
2024

25+
The hint to use
26+
2127
### options
2228

2329
[`ComponentCollectorOptions`](../interfaces/ComponentCollectorOptions.md) = `{}`
2430

31+
The options to use
32+
2533
## Returns
2634

2735
`object`
2836

37+
The component collector
38+
2939
### ctx
3040

3141
> **ctx**: `object`

packages/core/docs/functions/useComponentCollectorLegacy.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88

99
> **useComponentCollectorLegacy**(): `object`
1010
11+
Get a ctx and listeners for the rule to collect class components
12+
1113
## Returns
1214

1315
`object`
1416

17+
The context and listeners for the rule
18+
1519
### ctx
1620

1721
> **ctx**: `object`

packages/core/src/component/component-collector-legacy.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { ERClassComponentFlag } from "./component-flag";
77
import type { ERClassComponent } from "./component-semantic-node";
88
import { isClassComponent, isPureComponent } from "./is";
99

10+
/**
11+
* Get a ctx and listeners for the rule to collect class components
12+
* @returns The context and listeners for the rule
13+
*/
1014
export function useComponentCollectorLegacy() {
1115
const components = new Map<string, ERClassComponent>();
1216

packages/core/src/component/component-collector.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ export interface ComponentCollectorOptions {
6565
// dprint-ignore
6666
const displayNameAssignmentSelector = "AssignmentExpression[type][operator='='][left.type='MemberExpression'][left.property.name='displayName']";
6767

68+
/**
69+
* Get a ctx and listeners for the rule to collect function components
70+
* @param context The ESLint rule context
71+
* @param hint The hint to use
72+
* @param options The options to use
73+
* @returns The component collector
74+
*/
6875
export function useComponentCollector(
6976
context: RuleContext,
7077
hint = DEFAULT_COMPONENT_HINT,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default createRule<[], MessageID>({
3131
return {
3232
JSXElement(node) {
3333
const attributes = node.openingElement.attributes;
34-
const attribute = JSX.getAttributeNode(
34+
const attribute = JSX.getAttribute(
3535
"dangerouslySetInnerHTML",
3636
context.sourceCode.getScope(node),
3737
attributes,

packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-button-type.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default createRule<[], MessageID>({
3434
return {
3535
JSXElement(node) {
3636
const [elementNameOnJsx, elementNameOnDom] = getElementNameOnJsxAndDom(
37-
node.openingElement,
37+
node,
3838
context,
3939
polymorphicPropName,
4040
additionalComponents,
@@ -46,16 +46,15 @@ export default createRule<[], MessageID>({
4646
const customComponent = findCustomComponent(elementNameOnJsx, additionalComponents);
4747
const customComponentProp = findCustomComponentProp("type", customComponent?.attributes ?? []);
4848
const propNameOnJsx = customComponentProp?.name ?? "type";
49-
const attributeNode = JSX.getAttributeNode(
49+
const attributeNode = JSX.getAttribute(
5050
propNameOnJsx,
5151
elementScope,
5252
node.openingElement.attributes,
5353
);
5454
if (attributeNode != null) {
5555
const attributeScope = context.sourceCode.getScope(attributeNode);
56-
const attributeStaticValue = JSX.getAttributeStaticValue(attributeNode, attributeScope);
57-
const attributeStringValue = JSX.toResolvedAttributeValue(propNameOnJsx, attributeStaticValue);
58-
if (typeof attributeStringValue !== "string") {
56+
const attributeValue = JSX.getAttributeValue(propNameOnJsx, attributeNode, attributeScope);
57+
if (attributeValue.kind === "some" && typeof attributeValue.value !== "string") {
5958
context.report({
6059
messageId: "noMissingButtonType",
6160
node: attributeNode,

packages/plugins/eslint-plugin-react-dom/src/rules/no-missing-iframe-sandbox.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { _ } from "@eslint-react/eff";
21
import * as JSX from "@eslint-react/jsx";
32
import type { RuleFeature } from "@eslint-react/shared";
43
import { getSettingsFromContext } from "@eslint-react/shared";
@@ -33,7 +32,7 @@ const validTypes = [
3332
"allow-top-navigation-to-custom-protocols",
3433
] as const;
3534

36-
function hasValidSandBox(value: string | _) {
35+
function hasValidSandBox(value: unknown) {
3736
return typeof value === "string"
3837
&& value
3938
.split(" ")
@@ -60,7 +59,7 @@ export default createRule<[], MessageID>({
6059
return {
6160
JSXElement(node) {
6261
const [elementNameOnJsx, elementNameOnDom] = getElementNameOnJsxAndDom(
63-
node.openingElement,
62+
node,
6463
context,
6564
polymorphicPropName,
6665
additionalComponents,
@@ -72,16 +71,15 @@ export default createRule<[], MessageID>({
7271
const customComponent = findCustomComponent(elementNameOnJsx, additionalComponents);
7372
const customComponentProp = findCustomComponentProp("sandbox", customComponent?.attributes ?? []);
7473
const propNameOnJsx = customComponentProp?.name ?? "sandbox";
75-
const attributeNode = JSX.getAttributeNode(
74+
const attributeNode = JSX.getAttribute(
7675
propNameOnJsx,
7776
elementScope,
7877
node.openingElement.attributes,
7978
);
8079
if (attributeNode != null) {
8180
const attributeScope = context.sourceCode.getScope(attributeNode);
82-
const attributeStaticValue = JSX.getAttributeStaticValue(attributeNode, attributeScope);
83-
const attributeStringValue = JSX.toResolvedAttributeValue(propNameOnJsx, attributeStaticValue);
84-
if (hasValidSandBox(attributeStringValue)) return;
81+
const attributeValue = JSX.getAttributeValue(propNameOnJsx, attributeNode, attributeScope);
82+
if (attributeValue.kind === "some" && hasValidSandBox(attributeValue.value)) return;
8583
context.report({
8684
messageId: "noMissingIframeSandbox",
8785
node: attributeNode,

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ export default createRule<[], MessageID>({
2727
name: RULE_NAME,
2828
create(context) {
2929
return {
30-
JSXOpeningElement(node) {
30+
JSXElement(node) {
3131
const name = JSX.getElementName(node);
3232
if (typeof name !== "string" || !name.includes(":")) {
3333
return;
3434
}
3535
context.report({
3636
messageId: "noNamespace",
37-
node,
37+
node: node.openingElement.name,
3838
data: {
3939
name,
4040
},

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as JSX from "@eslint-react/jsx";
22
import type { RuleFeature } from "@eslint-react/shared";
33
import { RE_JAVASCRIPT_PROTOCOL } from "@eslint-react/shared";
4-
import * as VAR from "@eslint-react/var";
54
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
65
import type { CamelCase } from "string-ts";
76

@@ -39,10 +38,9 @@ export default createRule<[], MessageID>({
3938
return;
4039
}
4140
const attributeScope = context.sourceCode.getScope(node);
42-
const attributeValue = JSX.getAttributeStaticValue(node, attributeScope);
43-
const attributeValueResolved = VAR.toResolved(attributeValue).value;
44-
if (typeof attributeValueResolved !== "string") return;
45-
if (RE_JAVASCRIPT_PROTOCOL.test(attributeValueResolved)) {
41+
const attributeValue = JSX.getAttributeValue(JSX.toString(node.name), node, attributeScope);
42+
if (attributeValue.kind === "none" || typeof attributeValue.value !== "string") return;
43+
if (RE_JAVASCRIPT_PROTOCOL.test(attributeValue.value)) {
4644
context.report({
4745
messageId: "noScriptUrl",
4846
node: node.value,

packages/plugins/eslint-plugin-react-dom/src/rules/no-unsafe-iframe-sandbox.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { _ } from "@eslint-react/eff";
21
import * as JSX from "@eslint-react/jsx";
32
import type { RuleFeature } from "@eslint-react/shared";
43
import { getSettingsFromContext } from "@eslint-react/shared";
@@ -18,8 +17,8 @@ const unsafeSandboxValues = [
1817
["allow-scripts", "allow-same-origin"],
1918
] as const;
2019

21-
function hasNoneOrSafeSandbox(value: string | _) {
22-
if (value == null) return true;
20+
function hasSafeSandbox(value: unknown) {
21+
if (typeof value !== "string") return false;
2322
return !unsafeSandboxValues.some((values) => {
2423
return values.every((v) => value.includes(v));
2524
});
@@ -45,7 +44,7 @@ export default createRule<[], MessageID>({
4544
return {
4645
JSXElement(node) {
4746
const [elementNameOnJsx, elementNameOnDom] = getElementNameOnJsxAndDom(
48-
node.openingElement,
47+
node,
4948
context,
5049
polymorphicPropName,
5150
additionalComponents,
@@ -57,23 +56,24 @@ export default createRule<[], MessageID>({
5756
const customComponent = findCustomComponent(elementNameOnJsx, additionalComponents);
5857
const customComponentProp = findCustomComponentProp("sandbox", customComponent?.attributes ?? []);
5958
const propNameOnJsx = customComponentProp?.name ?? "sandbox";
60-
const attributeNode = JSX.getAttributeNode(
59+
const attributeNode = JSX.getAttribute(
6160
propNameOnJsx,
6261
elementScope,
6362
node.openingElement.attributes,
6463
);
6564
if (attributeNode != null) {
6665
const attributeScope = context.sourceCode.getScope(attributeNode);
67-
const attributeStaticValue = JSX.getAttributeStaticValue(attributeNode, attributeScope);
68-
const attributeStringValue = JSX.toResolvedAttributeValue(propNameOnJsx, attributeStaticValue);
69-
if (hasNoneOrSafeSandbox(attributeStringValue)) return;
70-
context.report({
71-
messageId: "noUnsafeIframeSandbox",
72-
node: attributeNode,
73-
});
74-
return;
66+
const attributeValue = JSX.getAttributeValue(propNameOnJsx, attributeNode, attributeScope);
67+
if (attributeValue.kind === "some" && !hasSafeSandbox(attributeValue.value)) {
68+
context.report({
69+
messageId: "noUnsafeIframeSandbox",
70+
node: attributeNode,
71+
});
72+
return;
73+
}
7574
}
76-
if (!hasNoneOrSafeSandbox(customComponentProp?.defaultValue)) {
75+
if (customComponentProp?.defaultValue == null) return;
76+
if (!hasSafeSandbox(customComponentProp.defaultValue)) {
7777
context.report({
7878
messageId: "noUnsafeIframeSandbox",
7979
node,

0 commit comments

Comments
 (0)