Skip to content

Commit 7f380b6

Browse files
committed
refactor: improve react import source detection
1 parent ca768e3 commit 7f380b6

File tree

3 files changed

+256
-4
lines changed

3 files changed

+256
-4
lines changed

packages/core/src/internal/is-from-react.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { RuleContext } from "@eslint-react/types";
44
import { findVariable } from "@eslint-react/var";
55
import type { Scope } from "@typescript-eslint/scope-manager";
66
import type { TSESTree } from "@typescript-eslint/types";
7-
import { Option as O } from "effect";
7+
import { Option as O, Predicate as Pred } from "effect";
88
import { isMatching, match } from "ts-pattern";
99
import { parse } from "valibot";
1010

@@ -13,7 +13,7 @@ export function isInitializedFromReact(
1313
context: RuleContext,
1414
initialScope: Scope,
1515
): boolean {
16-
if (variableName === "React") return true;
16+
if (variableName.toLowerCase() === "react") return true;
1717
const maybeVariable = findVariable(variableName, initialScope);
1818
const maybeLatestDef = O.flatMapNullable(maybeVariable, (v) => v.defs.at(-1));
1919
if (O.isNone(maybeLatestDef)) return false;
@@ -50,8 +50,8 @@ export function isInitializedFromReact(
5050
if (O.isNone(maybeRequireExpression)) return false;
5151
const requireExpression = maybeRequireExpression.value;
5252
const [firstArg] = requireExpression.arguments;
53-
if (firstArg?.type !== NodeType.Literal) return false;
54-
return firstArg.value === importSource;
53+
if (firstArg?.type !== NodeType.Literal || !Pred.isString(firstArg.value)) return false;
54+
return firstArg.value === importSource || firstArg.value.startsWith(`${importSource}/`);
5555
}
5656
// latest definition is an import declaration: import { variable } from 'react'
5757
return isMatching({ type: "ImportDeclaration", source: { value: importSource } }, parent);

packages/plugins/eslint-plugin-react-hooks-extra/src/rules/ensure-use-callback-has-non-empty-deps.spec.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,132 @@ ruleTester.run(RULE_NAME, rule, {
264264
},
265265
},
266266
},
267+
{
268+
code: dedent`
269+
const React = require("roact");
270+
271+
function App({ items }) {
272+
const memoizedValue = React.useCallback(() => [0, 1, 2].sort(), []);
273+
274+
return <div>{count}</div>;
275+
}
276+
`,
277+
errors: [
278+
{
279+
messageId: "ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS",
280+
},
281+
],
282+
settings: {
283+
reactOptions: {
284+
importSource: "roact",
285+
},
286+
},
287+
},
288+
{
289+
code: dedent`
290+
const Roact = require("roact");
291+
292+
function App({ items }) {
293+
const memoizedValue = Roact.useCallback(() => [0, 1, 2].sort(), []);
294+
295+
return <div>{count}</div>;
296+
}
297+
`,
298+
errors: [
299+
{
300+
messageId: "ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS",
301+
},
302+
],
303+
settings: {
304+
reactOptions: {
305+
importSource: "roact",
306+
},
307+
},
308+
},
309+
{
310+
code: dedent`
311+
const { useCallback } = require("roact");
312+
313+
function App({ items }) {
314+
const memoizedValue = useCallback(() => [0, 1, 2].sort(), []);
315+
316+
return <div>{count}</div>;
317+
}
318+
`,
319+
errors: [
320+
{
321+
messageId: "ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS",
322+
},
323+
],
324+
settings: {
325+
reactOptions: {
326+
importSource: "roact",
327+
},
328+
},
329+
},
330+
{
331+
code: dedent`
332+
const React = require("@pika/react");
333+
334+
function App({ items }) {
335+
const memoizedValue = React.useCallback(() => [0, 1, 2].sort(), []);
336+
337+
return <div>{count}</div>;
338+
}
339+
`,
340+
errors: [
341+
{
342+
messageId: "ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS",
343+
},
344+
],
345+
settings: {
346+
reactOptions: {
347+
importSource: "@pika/react",
348+
},
349+
},
350+
},
351+
{
352+
code: dedent`
353+
const Pika = require("@pika/react");
354+
355+
function App({ items }) {
356+
const memoizedValue = Pika.useCallback(() => [0, 1, 2].sort(), []);
357+
358+
return <div>{count}</div>;
359+
}
360+
`,
361+
errors: [
362+
{
363+
messageId: "ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS",
364+
},
365+
],
366+
settings: {
367+
reactOptions: {
368+
importSource: "@pika/react",
369+
},
370+
},
371+
},
372+
{
373+
code: dedent`
374+
const { useCallback } = require("@pika/react");
375+
376+
function App({ items }) {
377+
const memoizedValue = useCallback(() => [0, 1, 2].sort(), []);
378+
379+
return <div>{count}</div>;
380+
}
381+
`,
382+
errors: [
383+
{
384+
messageId: "ENSURE_USE_CALLBACK_HAS_NON_EMPTY_DEPS",
385+
},
386+
],
387+
settings: {
388+
reactOptions: {
389+
importSource: "@pika/react",
390+
},
391+
},
392+
},
267393
{
268394
code: dedent`
269395
import React from "react";

packages/plugins/eslint-plugin-react-hooks-extra/src/rules/ensure-use-memo-has-non-empty-deps.spec.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,132 @@ ruleTester.run(RULE_NAME, rule, {
236236
},
237237
},
238238
},
239+
{
240+
code: dedent`
241+
const React = require("roact");
242+
243+
function App({ items }) {
244+
const memoizedValue = React.useMemo(() => [0, 1, 2].sort(), []);
245+
246+
return <div>{count}</div>;
247+
}
248+
`,
249+
errors: [
250+
{
251+
messageId: "ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS",
252+
},
253+
],
254+
settings: {
255+
reactOptions: {
256+
importSource: "roact",
257+
},
258+
},
259+
},
260+
{
261+
code: dedent`
262+
const Roact = require("roact");
263+
264+
function App({ items }) {
265+
const memoizedValue = Roact.useMemo(() => [0, 1, 2].sort(), []);
266+
267+
return <div>{count}</div>;
268+
}
269+
`,
270+
errors: [
271+
{
272+
messageId: "ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS",
273+
},
274+
],
275+
settings: {
276+
reactOptions: {
277+
importSource: "roact",
278+
},
279+
},
280+
},
281+
{
282+
code: dedent`
283+
const { useMemo } = require("roact");
284+
285+
function App({ items }) {
286+
const memoizedValue = useMemo(() => [0, 1, 2].sort(), []);
287+
288+
return <div>{count}</div>;
289+
}
290+
`,
291+
errors: [
292+
{
293+
messageId: "ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS",
294+
},
295+
],
296+
settings: {
297+
reactOptions: {
298+
importSource: "roact",
299+
},
300+
},
301+
},
302+
{
303+
code: dedent`
304+
const React = require("@pika/react");
305+
306+
function App({ items }) {
307+
const memoizedValue = React.useMemo(() => [0, 1, 2].sort(), []);
308+
309+
return <div>{count}</div>;
310+
}
311+
`,
312+
errors: [
313+
{
314+
messageId: "ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS",
315+
},
316+
],
317+
settings: {
318+
reactOptions: {
319+
importSource: "@pika/react",
320+
},
321+
},
322+
},
323+
{
324+
code: dedent`
325+
const Pika = require("@pika/react");
326+
327+
function App({ items }) {
328+
const memoizedValue = Pika.useMemo(() => [0, 1, 2].sort(), []);
329+
330+
return <div>{count}</div>;
331+
}
332+
`,
333+
errors: [
334+
{
335+
messageId: "ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS",
336+
},
337+
],
338+
settings: {
339+
reactOptions: {
340+
importSource: "@pika/react",
341+
},
342+
},
343+
},
344+
{
345+
code: dedent`
346+
const { useMemo } = require("@pika/react");
347+
348+
function App({ items }) {
349+
const memoizedValue = useMemo(() => [0, 1, 2].sort(), []);
350+
351+
return <div>{count}</div>;
352+
}
353+
`,
354+
errors: [
355+
{
356+
messageId: "ENSURE_USE_MEMO_HAS_NON_EMPTY_DEPS",
357+
},
358+
],
359+
settings: {
360+
reactOptions: {
361+
importSource: "@pika/react",
362+
},
363+
},
364+
},
239365
{
240366
code: dedent`
241367
import React from "react";

0 commit comments

Comments
 (0)