Skip to content

Commit 112e793

Browse files
authored
Skip unstable value checks when 'use memo'; is present (#1441)
1 parent 4b18845 commit 112e793

6 files changed

Lines changed: 61 additions & 1 deletion

File tree

packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,20 @@ ruleTester.run(RULE_NAME, rule, {
185185
return <Context.Provider value={foo}></Context.Provider>
186186
}
187187
`,
188+
tsx`
189+
function App() {
190+
"use memo";
191+
const foo = {}
192+
return <Context.Provider value={foo}></Context.Provider>;
193+
}
194+
`,
195+
tsx`
196+
"use memo";
197+
function App() {
198+
const foo = {}
199+
return <Context.Provider value={foo}></Context.Provider>;
200+
}
201+
`,
188202
tsx`
189203
function App() {
190204
const foo = useMemo(() => [], [])

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export default createRule<[], MessageID>({
3535
});
3636

3737
export function create(context: RuleContext<MessageID, []>): RuleListener {
38+
// If "use memo" directive is present in the file, skip analysis
39+
if (AST.getProgramDirectives(context.sourceCode.ast).some((d) => d.value === "use memo")) return {};
40+
3841
const { version } = getSettingsFromContext(context);
3942
const isReact18OrBelow = compare(version, "19.0.0", "<");
4043
const { ctx, visitor } = useComponentCollector(context);
@@ -69,8 +72,9 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
6972
getOrElseUpdate(constructions, functionEntry.node, () => []).push(construction);
7073
},
7174
"Program:exit"(program) {
72-
for (const { node: component } of ctx.getAllComponents(program)) {
75+
for (const { node: component, directives } of ctx.getAllComponents(program)) {
7376
for (const construction of constructions.get(component) ?? []) {
77+
if (directives.some((d) => d.value === "use memo")) return;
7478
const { kind, node: constructionNode } = construction;
7579
const suggestion = kind === "function"
7680
? "Consider wrapping it in a useCallback hook."

packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-default-props.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,24 @@ ruleTester.run(RULE_NAME, rule, {
219219
],
220220
valid: [
221221
...allValid,
222+
tsx`
223+
"use memo";
224+
const App = ({
225+
a = {},
226+
b = ['one', 'two'],
227+
c = /regex/i,
228+
d = () => {},
229+
e = function() {},
230+
f = class {},
231+
g = new Thing(),
232+
h = <Thing />,
233+
i = Symbol('foo'),
234+
j = unknownFunction(),
235+
k = window.name
236+
}) => {
237+
return null
238+
}
239+
`,
222240
{
223241
code: tsx`
224242
function MyComponent({ position = new Vector3(0, 0, 0) }) {

packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-default-props.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ function extractIdentifier(node: TSESTree.Node): string | null {
7676
}
7777

7878
export function create(context: RuleContext<MessageID, Options>, [options]: Options): RuleListener {
79+
// If "use memo" directive is present in the file, skip analysis
80+
if (AST.getProgramDirectives(context.sourceCode.ast).some((d) => d.value === "use memo")) return {};
81+
7982
const { ctx, visitor } = useComponentCollector(context);
8083
const declarators = new WeakMap<AST.TSESTreeFunction, AST.ObjectDestructuringVariableDeclarator[]>();
8184
const { safeDefaultProps = [] } = options;

packages/utilities/ast/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from "./is";
1313
export * from "./literal";
1414
export * from "./misc";
1515
export * from "./process-env-node-env";
16+
export * from "./program-directives";
1617
export * from "./property-name";
1718
export * from "./selectors";
1819
export * from "./traverse";
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { AST_NODE_TYPES as T, type TSESTree } from "@typescript-eslint/types";
2+
3+
import { getUnderlyingExpression } from "./expression-base";
4+
import { isLiteral } from "./literal";
5+
6+
/**
7+
* Get all directive string literals from a program node
8+
* @param node The program AST node
9+
* @returns The array of directive string literals (e.g., "use strict")
10+
*/
11+
export function getProgramDirectives(node: TSESTree.Program): TSESTree.StringLiteral[] {
12+
const directives: TSESTree.StringLiteral[] = [];
13+
for (const stmt of node.body) {
14+
if (stmt.type !== T.ExpressionStatement) continue;
15+
const expr = getUnderlyingExpression(stmt.expression);
16+
if (!isLiteral(expr, "string")) continue;
17+
directives.push(expr);
18+
}
19+
return directives;
20+
}

0 commit comments

Comments
 (0)