Skip to content

Commit ce05b44

Browse files
authored
fix: use smaller error marker range to reduce visual noise (#1064)
1 parent e56aa4c commit ce05b44

File tree

12 files changed

+100
-62
lines changed

12 files changed

+100
-62
lines changed

packages/core/src/hook/hook-hierarchy.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ export function isFunctionOfUseEffectSetup(node: TSESTree.Node | _) {
1515

1616
export function isFunctionOfUseEffectCleanup(node: TSESTree.Node | _) {
1717
if (node == null) return false;
18-
const returnStatement = AST.findParentNode(node, AST.is(T.ReturnStatement));
19-
const enclosingFunction = AST.findParentNode(node, AST.isFunction);
20-
const functionOfReturnStatement = AST.findParentNode(returnStatement, AST.isFunction);
21-
return enclosingFunction === functionOfReturnStatement && isFunctionOfUseEffectSetup(enclosingFunction);
18+
const pReturn = AST.findParentNode(node, AST.is(T.ReturnStatement));
19+
const pFaunction = AST.findParentNode(node, AST.isFunction);
20+
const pFunctionOfReturn = AST.findParentNode(pReturn, AST.isFunction);
21+
if (pFaunction !== pFunctionOfReturn) return false;
22+
return isFunctionOfUseEffectSetup(pFaunction);
2223
}

packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-unnecessary-use-callback.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,14 @@ export default createRule<[], MessageID>({
4040
export function create(context: RuleContext<MessageID, []>): RuleListener {
4141
if (!context.sourceCode.text.includes("use")) return {};
4242
const alias = getSettingsFromContext(context).additionalHooks.useCallback ?? [];
43+
const isUseCallbackCall = ER.isReactHookCallWithNameAlias(context, "useCallback", alias);
4344
return {
4445
CallExpression(node) {
4546
if (!ER.isReactHookCall(node)) {
4647
return;
4748
}
4849
const initialScope = context.sourceCode.getScope(node);
49-
if (!ER.isUseCallbackCall(context, node) && !alias.some(ER.isReactHookCallWithNameLoose(node))) {
50+
if (!isUseCallbackCall(node)) {
5051
return;
5152
}
5253
const scope = context.sourceCode.getScope(node);

packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-unnecessary-use-memo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ export default createRule<[], MessageID>({
3939
export function create(context: RuleContext<MessageID, []>): RuleListener {
4040
if (!context.sourceCode.text.includes("use")) return {};
4141
const alias = getSettingsFromContext(context).additionalHooks.useMemo ?? [];
42+
const isUseMemoCall = ER.isReactHookCallWithNameAlias(context, "useMemo", alias);
4243
return {
4344
CallExpression(node) {
4445
if (!ER.isReactHookCall(node)) {
4546
return;
4647
}
4748
const initialScope = context.sourceCode.getScope(node);
48-
if (!ER.isUseMemoCall(context, node) && !alias.some(ER.isReactHookCallWithNameLoose(node))) {
49+
if (!isUseMemoCall(node)) {
4950
return;
5051
}
5152
const scope = context.sourceCode.getScope(node);

packages/plugins/eslint-plugin-react-hooks-extra/src/rules/prefer-use-state-lazy-initialization.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
// Ported from https://github.com/jsx-eslint/eslint-plugin-react/pull/3579/commits/ebb739a0fe99a2ee77055870bfda9f67a2691374
12
import type { RuleContext, RuleFeature } from "@eslint-react/kit";
23
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
34
import type { CamelCase } from "string-ts";
4-
// Ported from https://github.com/jsx-eslint/eslint-plugin-react/pull/3579/commits/ebb739a0fe99a2ee77055870bfda9f67a2691374
55
import * as AST from "@eslint-react/ast";
66
import * as ER from "@eslint-react/core";
77
import { getSettingsFromContext } from "@eslint-react/shared";
@@ -43,14 +43,14 @@ export default createRule<[], MessageID>({
4343
});
4444

4545
export function create(context: RuleContext<MessageID, []>): RuleListener {
46-
if (!context.sourceCode.text.includes("use")) return {};
4746
const alias = getSettingsFromContext(context).additionalHooks.useState ?? [];
47+
const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", alias);
4848
return {
4949
CallExpression(node) {
5050
if (!ER.isReactHookCall(node)) {
5151
return;
5252
}
53-
if (!ER.isUseStateCall(context, node) && !alias.some(ER.isReactHookCallWithNameLoose(node))) {
53+
if (!isUseStateCall(node)) {
5454
return;
5555
}
5656
const [useStateInput] = node.arguments;

packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import type { RuleContext, RuleFeature } from "@eslint-react/kit";
22
import type { TSESTree } from "@typescript-eslint/types";
33
import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
44
import * as ER from "@eslint-react/core";
5+
import { getSettingsFromContext } from "@eslint-react/shared";
56
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
67
import { snakeCase } from "string-ts";
7-
import { match } from "ts-pattern";
88

9+
import { match } from "ts-pattern";
910
import { createRule } from "../utils";
1011

1112
export const RULE_NAME = "use-state";
@@ -35,27 +36,44 @@ export default createRule<[], MessageID>({
3536
});
3637

3738
export function create(context: RuleContext<MessageID, []>): RuleListener {
39+
const alias = getSettingsFromContext(context).additionalHooks.useState ?? [];
40+
const isUseStateCall = ER.isReactHookCallWithNameAlias(context, "useState", alias);
3841
return {
39-
"CallExpression[callee.name='useState']"(node: TSESTree.CallExpression) {
42+
CallExpression(node: TSESTree.CallExpression) {
43+
if (!isUseStateCall(node)) {
44+
return;
45+
}
4046
if (node.parent.type !== T.VariableDeclarator) {
41-
context.report({ messageId: "missingDestructuring", node });
47+
context.report({
48+
messageId: "missingDestructuring",
49+
node,
50+
});
4251
return;
4352
}
4453
const id = ER.getInstanceId(node);
4554
if (id?.type !== T.ArrayPattern) {
46-
context.report({ messageId: "missingDestructuring", node });
55+
context.report({
56+
messageId: "missingDestructuring",
57+
node: id ?? node,
58+
});
4759
return;
4860
}
4961
const [value, setter] = id.elements;
5062
if (value == null || setter == null) {
51-
context.report({ messageId: "missingDestructuring", node });
63+
context.report({
64+
messageId: "missingDestructuring",
65+
node: id,
66+
});
5267
return;
5368
}
5469
const setterName = match(setter)
5570
.with({ type: T.Identifier }, (id) => id.name)
5671
.otherwise(() => null);
5772
if (setterName == null || !setterName.startsWith("set")) {
58-
context.report({ messageId: "invalidSetterNaming", node });
73+
context.report({
74+
messageId: "invalidSetterNaming",
75+
node: setter,
76+
});
5977
return;
6078
}
6179
const valueName = match(value)
@@ -70,8 +88,18 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
7088
return values.join("_");
7189
})
7290
.otherwise(() => null);
73-
if (valueName == null || `set_${valueName}` !== snakeCase(setterName)) {
74-
context.report({ messageId: "invalidSetterNaming", node });
91+
if (valueName == null) {
92+
context.report({
93+
messageId: "invalidSetterNaming",
94+
node: value,
95+
});
96+
return;
97+
}
98+
if (snakeCase(setterName) !== `set_${valueName}`) {
99+
context.report({
100+
messageId: "invalidSetterNaming",
101+
node: setter,
102+
});
75103
return;
76104
}
77105
},

packages/plugins/eslint-plugin-react-x/src/rules/no-forward-ref.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
5050
if (!ER.isForwardRefCall(context, node)) {
5151
return;
5252
}
53+
const id = AST.getFunctionIdentifier(node);
5354
context.report({
5455
messageId: "noForwardRef",
55-
node,
56+
node: id ?? node,
5657
fix: getFix(context, node),
5758
});
5859
},

packages/plugins/eslint-plugin-react-x/src/rules/no-implicit-key.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
3838
return {
3939
JSXOpeningElement(node: TSESTree.JSXOpeningElement) {
4040
const initialScope = context.sourceCode.getScope(node);
41-
const keyPropFound = ER.getAttribute(context, "key", node.attributes, initialScope);
42-
const keyPropOnElement = node.attributes
41+
const keyProp = ER.getAttribute(context, "key", node.attributes, initialScope);
42+
const isKeyPropOnElement = node.attributes
4343
.some((n) =>
4444
n.type === T.JSXAttribute
4545
&& n.name.type === T.JSXIdentifier
4646
&& n.name.name === "key"
4747
);
48-
if (keyPropFound != null && !keyPropOnElement) {
49-
context.report({ messageId: "noImplicitKey", node: keyPropFound });
48+
if (keyProp != null && !isKeyPropOnElement) {
49+
context.report({ messageId: "noImplicitKey", node: keyProp });
5050
}
5151
},
5252
};

packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
5555
continue;
5656
}
5757
if (displayName == null) {
58+
const id = AST.getFunctionIdentifier(node);
5859
context.report({
5960
messageId: "noMissingComponentDisplayName",
60-
node,
61+
node: id ?? node,
6162
});
6263
}
6364
}

packages/plugins/eslint-plugin-react-x/src/rules/no-missing-context-display-name.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
6161
if (!hasDisplayNameAssignment) {
6262
context.report({
6363
messageId: "noMissingContextDisplayName",
64-
node: call,
64+
node: id,
6565
});
6666
}
6767
}

packages/plugins/eslint-plugin-react-x/src/rules/no-use-context.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,17 @@ export default createRule<[], MessageID>({
3939
export function create(context: RuleContext<MessageID, []>): RuleListener {
4040
if (!context.sourceCode.text.includes("useContext")) return {};
4141
const settings = getSettingsFromContext(context);
42-
const useContextAlias = new Set<string>();
4342
if (compare(settings.version, "19.0.0", "<")) {
4443
return {};
4544
}
45+
const useContextNames = new Set<string>();
46+
const hookCalls = new Set<TSESTree.CallExpression>();
4647
return {
4748
CallExpression(node) {
4849
if (!ER.isReactHookCall(node)) {
4950
return;
5051
}
51-
if (!ER.isReactHookCallWithNameAlias(context, "useContext", [...useContextAlias])(node)) {
52-
return;
53-
}
54-
context.report({
55-
messageId: "noUseContext",
56-
node: node.callee,
57-
fix(fixer) {
58-
switch (node.callee.type) {
59-
case T.Identifier:
60-
return fixer.replaceText(node.callee, "use");
61-
case T.MemberExpression:
62-
return fixer.replaceText(node.callee.property, "use");
63-
}
64-
return null;
65-
},
66-
});
52+
hookCalls.add(node);
6753
},
6854
ImportDeclaration(node) {
6955
if (node.source.value !== settings.importSource) {
@@ -78,7 +64,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
7864
// import { useContext as useCtx } from 'react'
7965
if (specifier.local.name !== "useContext") {
8066
// add alias to useContextAlias to keep track of it in future call expressions
81-
useContextAlias.add(specifier.local.name);
67+
useContextNames.add(specifier.local.name);
8268
}
8369
context.report({
8470
messageId: "noUseContext",
@@ -91,7 +77,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
9177
...tokenBefore?.value === ","
9278
? [fixer.replaceTextRange([tokenBefore.range[1], specifier.range[0]], "")]
9379
: [],
94-
...getAssociatedTokens(
80+
...getCorrelativeTokens(
9581
context,
9682
specifier,
9783
).map((token) => fixer.remove(token)),
@@ -103,26 +89,45 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
10389
}
10490
}
10591
},
92+
"Program:exit"() {
93+
const isUseContextCall = ER.isReactHookCallWithNameAlias(context, "useContext", [...useContextNames]);
94+
for (const node of hookCalls) {
95+
if (!isUseContextCall(node)) {
96+
continue;
97+
}
98+
context.report({
99+
messageId: "noUseContext",
100+
node: node.callee,
101+
fix(fixer) {
102+
switch (node.callee.type) {
103+
case T.Identifier:
104+
return fixer.replaceText(node.callee, "use");
105+
case T.MemberExpression:
106+
return fixer.replaceText(node.callee.property, "use");
107+
}
108+
return null;
109+
},
110+
});
111+
}
112+
},
106113
};
107114
}
108115

109-
function getAssociatedTokens(context: RuleContext, node: TSESTree.Node) {
110-
{
111-
const tokenBefore = context.sourceCode.getTokenBefore(node);
112-
const tokenAfter = context.sourceCode.getTokenAfter(node);
113-
const tokens = [];
114-
115-
// If this is not the only entry, then the line above this one
116-
// will become the last line, and should not have a trailing comma.
117-
if (tokenAfter?.value !== "," && tokenBefore?.value === ",") {
118-
tokens.push(tokenBefore);
119-
}
116+
function getCorrelativeTokens(context: RuleContext, node: TSESTree.Node) {
117+
const tokenBefore = context.sourceCode.getTokenBefore(node);
118+
const tokenAfter = context.sourceCode.getTokenAfter(node);
119+
const tokens = [];
120120

121-
// If this is not the last entry, then we need to remove the comma from this line.
122-
if (tokenAfter?.value === ",") {
123-
tokens.push(tokenAfter);
124-
}
121+
// If this is not the only entry, then the line above this one
122+
// will become the last line, and should not have a trailing comma.
123+
if (tokenAfter?.value !== "," && tokenBefore?.value === ",") {
124+
tokens.push(tokenBefore);
125+
}
125126

126-
return tokens;
127+
// If this is not the last entry, then we need to remove the comma from this line.
128+
if (tokenAfter?.value === ",") {
129+
tokens.push(tokenAfter);
127130
}
131+
132+
return tokens;
128133
}

0 commit comments

Comments
 (0)