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: