diff --git a/eslint.config.ts b/eslint.config.ts index 14f4ad9ce7..01298ef059 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -75,6 +75,7 @@ const enableTypeCheckedRules = { ...eslintPluginSafeTypeScript.configs.recommended.rules, "@susisu/safe-typescript/no-unsafe-object-property-check": "off", "@susisu/safe-typescript/no-unsafe-object-property-overwrite": "off", + "@typescript-eslint/consistent-type-exports": "error", "@typescript-eslint/strict-boolean-expressions": ["warn", { allowNullableBoolean: true, allowNullableString: true }], } as const; diff --git a/packages/core/docs/variables/ERComponentHint.md b/packages/core/docs/variables/ERComponentHint.md index cb9cac4775..f670299053 100644 --- a/packages/core/docs/variables/ERComponentHint.md +++ b/packages/core/docs/variables/ERComponentHint.md @@ -16,6 +16,10 @@ hints for component collector > `readonly` **None**: `0n` +### SkipBigIntLiteral + +> `readonly` **SkipBigIntLiteral**: `bigint` + ### SkipBooleanLiteral > `readonly` **SkipBooleanLiteral**: `bigint` @@ -64,9 +68,9 @@ hints for component collector > `readonly` **SkipStringLiteral**: `bigint` -### SkipUndefinedLiteral +### SkipUndefined -> `readonly` **SkipUndefinedLiteral**: `bigint` +> `readonly` **SkipUndefined**: `bigint` ### StrictArray diff --git a/packages/core/src/component/component-collector-hint.ts b/packages/core/src/component/component-collector-hint.ts index ffd62c7776..4513023db6 100644 --- a/packages/core/src/component/component-collector-hint.ts +++ b/packages/core/src/component/component-collector-hint.ts @@ -29,7 +29,7 @@ export const DEFAULT_COMPONENT_HINT = 0n | ERComponentHint.SkipMapCallback | ERComponentHint.SkipNumberLiteral | ERComponentHint.SkipStringLiteral - | ERComponentHint.SkipUndefinedLiteral + | ERComponentHint.SkipUndefined | ERComponentHint.SkipEmptyArray | ERComponentHint.StrictArray | ERComponentHint.StrictConditional diff --git a/packages/core/src/render-prop/is.ts b/packages/core/src/render-prop/is.ts index c9fc35c239..49585ab546 100644 --- a/packages/core/src/render-prop/is.ts +++ b/packages/core/src/render-prop/is.ts @@ -31,7 +31,7 @@ export function isRenderFunctionLoose(node: AST.TSESTreeFunction, context: RuleC getScope: (node: TSESTree.Node) => context.sourceCode.getScope(node), }, JSX.JSXValueHint.SkipNullLiteral - | JSX.JSXValueHint.SkipUndefinedLiteral + | JSX.JSXValueHint.SkipUndefined | JSX.JSXValueHint.StrictLogical | JSX.JSXValueHint.StrictConditional, ); diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-components.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-components.ts index 3a91b8d9f5..79ded05b70 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-components.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-components.ts @@ -44,7 +44,7 @@ export default createRule<[], MessageID>({ create(context) { const hint = ERComponentHint.SkipMapCallback | ERComponentHint.SkipNullLiteral - | ERComponentHint.SkipUndefinedLiteral + | ERComponentHint.SkipUndefined | ERComponentHint.SkipBooleanLiteral | ERComponentHint.SkipStringLiteral | ERComponentHint.SkipNumberLiteral diff --git a/packages/types/docs/functions/typeOf.md b/packages/types/docs/functions/typeOf.md index 5307562352..77b6614a05 100644 --- a/packages/types/docs/functions/typeOf.md +++ b/packages/types/docs/functions/typeOf.md @@ -6,7 +6,7 @@ # Function: typeOf() -> **typeOf**(`t`): `"object"` \| `string` & `object` \| `"array"` +> **typeOf**(`t`): `"object"` \| `"array"` \| `string` & `object` This is an enhanced version of the typeof operator to check the type of more complex values. In this case we just mind about arrays and objects. We can add more on demand. @@ -21,6 +21,6 @@ the value to be checked ## Returns -`"object"` \| `string` & `object` \| `"array"` +`"object"` \| `"array"` \| `string` & `object` the type of the value diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 56e86b5ee0..078d5d437b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,5 +1,5 @@ -export * from "./helpers"; export type * from "./rule"; export type * from "./rule-feature"; export type * from "./rule-name"; export type * from "./rule-namespace"; +export * from "./utils"; diff --git a/packages/types/src/helpers.ts b/packages/types/src/utils.ts similarity index 100% rename from packages/types/src/helpers.ts rename to packages/types/src/utils.ts diff --git a/packages/utilities/ast/src/index.ts b/packages/utilities/ast/src/index.ts index ff40e0976a..59116515f3 100644 --- a/packages/utilities/ast/src/index.ts +++ b/packages/utilities/ast/src/index.ts @@ -27,5 +27,5 @@ export * from "./is-string-literal"; export * from "./is-this-expression"; export * from "./to-readable-node-name"; export * from "./to-readable-node-type"; -export * from "./types"; +export type * from "./types"; export * from "./unwrap-type-expression"; diff --git a/packages/utilities/jsx/src/is-jsx-value.ts b/packages/utilities/jsx/src/is-jsx-value.ts index b2b42fb227..5029751e8d 100644 --- a/packages/utilities/jsx/src/is-jsx-value.ts +++ b/packages/utilities/jsx/src/is-jsx-value.ts @@ -4,7 +4,6 @@ import * as VAR from "@eslint-react/var"; import type { Scope } from "@typescript-eslint/scope-manager"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import type { TSESTree } from "@typescript-eslint/utils"; -import { match, P } from "ts-pattern"; // type ReactNode = // | ReactElement @@ -22,21 +21,22 @@ import { match, P } from "ts-pattern"; /* eslint-disable perfectionist/sort-objects */ export const JSXValueHint = { None: 0n, - SkipNullLiteral: 1n << 0n, - SkipUndefinedLiteral: 1n << 1n, + SkipUndefined: 1n << 0n, + SkipNullLiteral: 1n << 1n, SkipBooleanLiteral: 1n << 2n, SkipStringLiteral: 1n << 3n, SkipNumberLiteral: 1n << 4n, - SkipCreateElement: 1n << 5n, + SkipBigIntLiteral: 1n << 5n, SkipEmptyArray: 1n << 6n, - StrictArray: 1n << 7n, - StrictLogical: 1n << 8n, - StrictConditional: 1n << 9n, + SkipCreateElement: 1n << 7n, + StrictArray: 1n << 8n, + StrictLogical: 1n << 9n, + StrictConditional: 1n << 10n, } as const; /* eslint-enable perfectionist/sort-objects */ export const DEFAULT_JSX_VALUE_HINT = 0n - | JSXValueHint.SkipUndefinedLiteral + | JSXValueHint.SkipUndefined | JSXValueHint.SkipBooleanLiteral; /** @@ -52,30 +52,45 @@ export function isJSXValue( jsxCtx: { getScope: (node: TSESTree.Node) => Scope }, hint: bigint = DEFAULT_JSX_VALUE_HINT, ): boolean { - if (!node) { - return false; - } - return match(node) - .with({ type: T.JSXElement }, F.constTrue) - .with({ type: T.JSXFragment }, F.constTrue) - .with({ type: T.JSXMemberExpression }, F.constTrue) - .with({ type: T.JSXNamespacedName }, F.constTrue) - .with({ type: T.Literal }, (node) => { - return match(node.value) - .with(null, () => !(hint & JSXValueHint.SkipNullLiteral)) - .with(P.boolean, () => !(hint & JSXValueHint.SkipBooleanLiteral)) - .with(P.string, () => !(hint & JSXValueHint.SkipStringLiteral)) - .with(P.number, () => !(hint & JSXValueHint.SkipNumberLiteral)) - .otherwise(F.constFalse); - }) - .with({ type: T.TemplateLiteral }, () => !(hint & JSXValueHint.SkipStringLiteral)) - .with({ type: T.ArrayExpression }, (node) => { + switch (node?.type) { + case T.JSXElement: + case T.JSXFragment: + case T.JSXMemberExpression: + case T.JSXNamespacedName: { + return true; + } + case T.Literal: { + switch (typeof node.value) { + case "boolean": + return !(hint & JSXValueHint.SkipBooleanLiteral); + case "string": + return !(hint & JSXValueHint.SkipStringLiteral); + case "number": + return !(hint & JSXValueHint.SkipNumberLiteral); + case "bigint": + return !(hint & JSXValueHint.SkipBigIntLiteral); + } + if (node.value === null) { + return !(hint & JSXValueHint.SkipNullLiteral); + } + return false; + } + case T.TemplateLiteral: { + return !(hint & JSXValueHint.SkipStringLiteral); + } + case T.ArrayExpression: { if (hint & JSXValueHint.StrictArray) { return node.elements.every((n) => isJSXValue(n, jsxCtx, hint)); } return node.elements.some((n) => isJSXValue(n, jsxCtx, hint)); - }) - .with({ type: T.ConditionalExpression }, (node) => { + } + case T.LogicalExpression: { + if (hint & JSXValueHint.StrictLogical) { + return isJSXValue(node.left, jsxCtx, hint) && isJSXValue(node.right, jsxCtx, hint); + } + return isJSXValue(node.left, jsxCtx, hint) || isJSXValue(node.right, jsxCtx, hint); + } + case T.ConditionalExpression: { function leftHasJSX(node: TSESTree.ConditionalExpression) { if (Array.isArray(node.consequent)) { if (node.consequent.length === 0) { @@ -95,40 +110,37 @@ export function isJSXValue( return leftHasJSX(node) && rightHasJSX(node); } return leftHasJSX(node) || rightHasJSX(node); - }) - .with({ type: T.LogicalExpression }, (node) => { - if (hint & JSXValueHint.StrictLogical) { - return isJSXValue(node.left, jsxCtx, hint) && isJSXValue(node.right, jsxCtx, hint); - } - return isJSXValue(node.left, jsxCtx, hint) || isJSXValue(node.right, jsxCtx, hint); - }) - .with({ type: T.SequenceExpression }, (node) => { + } + case T.SequenceExpression: { const exp = node.expressions.at(-1); return isJSXValue(exp, jsxCtx, hint); - }) - .with({ type: T.CallExpression }, (node) => { + } + case T.CallExpression: { if (hint & JSXValueHint.SkipCreateElement) { return false; } - return match(node.callee) - .with({ type: T.Identifier, name: "createElement" }, F.constTrue) - .with({ type: T.MemberExpression, property: { name: "createElement" } }, F.constTrue) - .otherwise(F.constFalse); - }) - .with({ type: T.Identifier }, (node) => { + switch (node.callee.type) { + case T.Identifier: + return node.callee.name === "createElement"; + case T.MemberExpression: + return node.callee.property.type === T.Identifier && node.callee.property.name === "createElement"; + } + return false; + } + case T.Identifier: { const { name } = node; if (name === "undefined") { - return !(hint & JSXValueHint.SkipUndefinedLiteral); + return !(hint & JSXValueHint.SkipUndefined); } if (AST.isJSXTagNameExpression(node)) { return true; } - const initialScope = jsxCtx.getScope(node); return F.pipe( - VAR.findVariable(name, initialScope), + VAR.findVariable(name, jsxCtx.getScope(node)), O.flatMap(VAR.getVariableNode(0)), O.exists(n => isJSXValue(n, jsxCtx, hint)), ); - }) - .otherwise(F.constFalse); + } + } + return false; }