diff --git a/packages/plugins/eslint-plugin-react-x/package.json b/packages/plugins/eslint-plugin-react-x/package.json
index 4ddac4636..12427f6a4 100644
--- a/packages/plugins/eslint-plugin-react-x/package.json
+++ b/packages/plugins/eslint-plugin-react-x/package.json
@@ -59,6 +59,7 @@
"@typescript-eslint/type-utils": "^8.15.0",
"@typescript-eslint/types": "^8.15.0",
"@typescript-eslint/utils": "^8.15.0",
+ "compare-versions": "^6.1.1",
"is-immutable-type": "5.0.0",
"ts-pattern": "^5.5.0"
},
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.spec.ts
index 77c953da5..ad7a6b8f7 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.spec.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.spec.ts
@@ -13,6 +13,11 @@ ruleTesterWithTypes.run(RULE_NAME, rule, {
errors: [
{ messageId: "noLeakedConditionalRendering" },
],
+ settings: {
+ "react-x": {
+ version: "17.0.0",
+ },
+ },
},
{
code: /* tsx */ `
@@ -25,6 +30,28 @@ ruleTesterWithTypes.run(RULE_NAME, rule, {
errors: [
{ messageId: "noLeakedConditionalRendering" },
],
+ settings: {
+ "react-x": {
+ version: "17.0.0",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ ///
+ ///
+
+ const anyString = Math.random() > 0.5 ? "" : "foo";
+ const a = <>{anyString && }>;
+ `,
+ errors: [
+ { messageId: "noLeakedConditionalRendering" },
+ ],
+ settings: {
+ "react-x": {
+ version: "17.0.0",
+ },
+ },
},
{
code: /* tsx */ `
@@ -683,5 +710,33 @@ ruleTesterWithTypes.run(RULE_NAME, rule, {
);
};
`,
+ {
+ code: /* tsx */ `
+ ///
+ ///
+
+ const someString = "";
+ const a = <>{someString && }>;
+ `,
+ settings: {
+ "react-x": {
+ version: "18.0.0",
+ },
+ },
+ },
+ {
+ code: /* tsx */ `
+ ///
+ ///
+
+ const anyString = Math.random() > 0.5 ? "" : "foo";
+ const a = <>{anyString && }>;
+ `,
+ settings: {
+ "react-x": {
+ version: "18.0.0",
+ },
+ },
+ },
],
});
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.ts
index ebf352c63..0fa8f394a 100644
--- a/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.ts
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-leaked-conditional-rendering.ts
@@ -1,4 +1,5 @@
import * as AST from "@eslint-react/ast";
+import { decodeSettings, normalizeSettings } from "@eslint-react/shared";
import { F, O } from "@eslint-react/tools";
import * as VAR from "@eslint-react/var";
import type { Variable } from "@typescript-eslint/scope-manager";
@@ -8,6 +9,7 @@ import { AST_NODE_TYPES } from "@typescript-eslint/types";
import { ESLintUtils } from "@typescript-eslint/utils";
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
import type { ReportDescriptor } from "@typescript-eslint/utils/ts-eslint";
+import { compare } from "compare-versions";
import type { CamelCase } from "string-ts";
import { isFalseLiteralType, isTrueLiteralType, isTypeFlagSet, unionTypeParts } from "ts-api-utils";
import { isMatching, match, P } from "ts-pattern";
@@ -48,20 +50,6 @@ type VariantType =
| "truthy string";
/* eslint-enable perfectionist/sort-union-types */
-// Allowed left node type variants
-const allowedVariants = [
- "any",
- "boolean",
- "nullish",
- "object",
- "string",
- "falsy boolean",
- "truthy bigint",
- "truthy boolean",
- "truthy number",
- "truthy string",
-] as const satisfies VariantType[];
-
// #endregion
// #region Helpers
@@ -223,6 +211,25 @@ export default createRule<[], MessageID>({
name: RULE_NAME,
create(context) {
if (!context.sourceCode.text.includes("&&") && !context.sourceCode.text.includes("?")) return {};
+
+ const { version } = normalizeSettings(decodeSettings(context.settings));
+
+ // Allowed left node type variants
+ const allowedVariants = [
+ "any",
+ "boolean",
+ "nullish",
+ "object",
+ "falsy boolean",
+ "truthy bigint",
+ "truthy boolean",
+ "truthy number",
+ "truthy string",
+ ...compare(version, "18.0.0", "<")
+ ? []
+ : ["string", "falsy string"] as const,
+ ] as const satisfies VariantType[];
+
const services = ESLintUtils.getParserServices(context, false);
function getReportDescriptor(node: TSESTree.Expression): O.Option> {
return match>>(node)
diff --git a/packages/shared/package.json b/packages/shared/package.json
index fc5176aa4..d95ca114f 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -44,7 +44,9 @@
"dependencies": {
"@eslint-react/tools": "workspace:*",
"@typescript-eslint/utils": "^8.15.0",
- "picomatch": "^4.0.2"
+ "local-pkg": "^0.5.1",
+ "picomatch": "^4.0.2",
+ "ts-pattern": "^5.5.0"
},
"devDependencies": {
"@types/picomatch": "^3.0.1",
diff --git a/packages/shared/src/schemas.ts b/packages/shared/src/schemas.ts
index b89cc9b62..09adb49e3 100644
--- a/packages/shared/src/schemas.ts
+++ b/packages/shared/src/schemas.ts
@@ -195,4 +195,5 @@ export type ESLintSettings = InferOutput;
export interface ESLintReactSettingsNormalized extends ESLintReactSettings {
additionalComponents: CustomComponentNormalized[];
components: Map;
+ version: string;
}
diff --git a/packages/shared/src/settings.ts b/packages/shared/src/settings.ts
index f5c4d97f1..98423c2ab 100644
--- a/packages/shared/src/settings.ts
+++ b/packages/shared/src/settings.ts
@@ -1,7 +1,9 @@
import { Data, F } from "@eslint-react/tools";
import { shallowEqual } from "fast-equals";
+import { getPackageInfoSync } from "local-pkg";
import memoize from "micro-memoize";
import pm from "picomatch";
+import { match, P } from "ts-pattern";
import type { PartialDeep } from "type-fest";
import { parse } from "valibot";
@@ -72,6 +74,9 @@ export const normalizeSettings = memoize((settings: ESLintReactSettings) => {
if (!/^[\w-]+$/u.test(name)) return acc;
return acc.set(name, as);
}, new Map()),
+ version: match(settings.version)
+ .with(P.union(P.nullish, "", "detect"), () => getPackageInfoSync("react")?.version)
+ .otherwise(F.identity) ?? "18.3.1",
});
}, { isEqual: shallowEqual });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8a0be0234..024f800a7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -246,7 +246,7 @@ importers:
version: 9.15.0(jiti@2.3.3)
eslint-plugin-react-hooks:
specifier: rc
- version: 5.1.0-rc-69d4b800-20241021(eslint@9.15.0(jiti@2.3.3))
+ version: 5.1.0-rc.1(eslint@9.15.0(jiti@2.3.3))
eslint-plugin-react-refresh:
specifier: ^0.4.14
version: 0.4.14(eslint@9.15.0(jiti@2.3.3))
@@ -350,7 +350,7 @@ importers:
version: 9.15.0(jiti@2.3.3)
eslint-plugin-react-hooks:
specifier: rc
- version: 5.1.0-rc-69d4b800-20241021(eslint@9.15.0(jiti@2.3.3))
+ version: 5.1.0-rc.1(eslint@9.15.0(jiti@2.3.3))
eslint-plugin-react-refresh:
specifier: ^0.4.14
version: 0.4.14(eslint@9.15.0(jiti@2.3.3))
@@ -417,7 +417,7 @@ importers:
version: 9.15.0(jiti@2.3.3)
eslint-plugin-react-hooks:
specifier: rc
- version: 5.1.0-rc-69d4b800-20241021(eslint@9.15.0(jiti@2.3.3))
+ version: 5.1.0-rc.1(eslint@9.15.0(jiti@2.3.3))
eslint-plugin-react-refresh:
specifier: ^0.4.14
version: 0.4.14(eslint@9.15.0(jiti@2.3.3))
@@ -869,6 +869,9 @@ importers:
'@typescript-eslint/utils':
specifier: ^8.15.0
version: 8.15.0(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3)
+ compare-versions:
+ specifier: ^6.1.1
+ version: 6.1.1
eslint:
specifier: ^8.57.0 || ^9.0.0
version: 9.15.0(jiti@2.3.3)
@@ -906,9 +909,15 @@ importers:
'@typescript-eslint/utils':
specifier: ^8.15.0
version: 8.15.0(eslint@9.15.0(jiti@2.3.3))(typescript@5.6.3)
+ local-pkg:
+ specifier: ^0.5.1
+ version: 0.5.1
picomatch:
specifier: ^4.0.2
version: 4.0.2
+ ts-pattern:
+ specifier: ^5.5.0
+ version: 5.5.0
devDependencies:
'@types/picomatch':
specifier: ^3.0.1
@@ -4408,6 +4417,9 @@ packages:
resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
engines: {node: '>= 12.0.0'}
+ compare-versions@6.1.1:
+ resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
+
compressible@2.0.18:
resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
engines: {node: '>= 0.6'}
@@ -5106,12 +5118,6 @@ packages:
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
- eslint-plugin-react-hooks@5.1.0-rc-69d4b800-20241021:
- resolution: {integrity: sha512-BlHwxPe59W888jvcwa2dRJXzJf2DWJm9ZMwlpaeobPzW1gaghptM+ISk/E2UK/PTVuDmdkuqZ4je/9v3QE99Gg==}
- engines: {node: '>=10'}
- peerDependencies:
- eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
-
eslint-plugin-react-hooks@5.1.0-rc.1:
resolution: {integrity: sha512-nAD017D/00XFwjP4F7cXaIbCxQ9A4pGaqjLs5347px37w/WclOtPqz8bBiTQFoj+teVQei6Ahr1h1aZiuaXMSw==}
engines: {node: '>=10'}
@@ -6159,8 +6165,8 @@ packages:
resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==}
engines: {node: '>=8.9.0'}
- local-pkg@0.5.0:
- resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
+ local-pkg@0.5.1:
+ resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
engines: {node: '>=14'}
locate-path@5.0.0:
@@ -6546,6 +6552,9 @@ packages:
mlly@1.7.2:
resolution: {integrity: sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==}
+ mlly@1.7.3:
+ resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
+
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -10105,7 +10114,7 @@ snapshots:
'@iconify/types': 2.0.0
debug: 4.3.7
kolorist: 1.8.0
- local-pkg: 0.5.0
+ local-pkg: 0.5.1
mlly: 1.7.2
transitivePeerDependencies:
- supports-color
@@ -11900,6 +11909,8 @@ snapshots:
comment-parser@1.4.1: {}
+ compare-versions@6.1.1: {}
+
compressible@2.0.18:
dependencies:
mime-db: 1.53.0
@@ -12784,10 +12795,6 @@ snapshots:
dependencies:
eslint: 9.15.0(jiti@2.3.3)
- eslint-plugin-react-hooks@5.1.0-rc-69d4b800-20241021(eslint@9.15.0(jiti@2.3.3)):
- dependencies:
- eslint: 9.15.0(jiti@2.3.3)
-
eslint-plugin-react-hooks@5.1.0-rc.1(eslint@9.15.0(jiti@2.3.3)):
dependencies:
eslint: 9.15.0(jiti@2.3.3)
@@ -13939,9 +13946,9 @@ snapshots:
emojis-list: 3.0.0
json5: 2.2.3
- local-pkg@0.5.0:
+ local-pkg@0.5.1:
dependencies:
- mlly: 1.7.2
+ mlly: 1.7.3
pkg-types: 1.2.1
locate-path@5.0.0:
@@ -14633,6 +14640,13 @@ snapshots:
pkg-types: 1.2.1
ufo: 1.5.4
+ mlly@1.7.3:
+ dependencies:
+ acorn: 8.14.0
+ pathe: 1.1.2
+ pkg-types: 1.2.1
+ ufo: 1.5.4
+
mri@1.2.0: {}
mrmime@2.0.0: {}
@@ -15091,7 +15105,7 @@ snapshots:
pkg-types@1.2.1:
dependencies:
confbox: 0.1.8
- mlly: 1.7.2
+ mlly: 1.7.3
pathe: 1.1.2
please-upgrade-node@3.2.0: