From 7f55deee1724662de655278cf092eb26f5615527 Mon Sep 17 00:00:00 2001 From: rEl1cx Date: Wed, 8 Jan 2025 11:38:52 +0800 Subject: [PATCH 1/2] refactor(utilities/jsx): improve the performance of 'isJSXValue' --- .../core/docs/variables/ERComponentHint.md | 8 +- .../src/component/component-collector-hint.ts | 2 +- packages/core/src/render-prop/is.ts | 2 +- .../src/rules/no-nested-components.ts | 2 +- packages/types/docs/functions/typeOf.md | 4 +- packages/types/src/index.ts | 10 +- packages/types/src/{helpers.ts => utils.ts} | 0 packages/utilities/jsx/src/is-jsx-value.ts | 110 ++++++++++-------- 8 files changed, 77 insertions(+), 61 deletions(-) rename packages/types/src/{helpers.ts => utils.ts} (100%) 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..95d4f9911f 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 "./rule"; +export * from "./rule-feature"; +export * from "./rule-name"; +export * 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/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; } From ff4fa1f27f1b43477b5bf69221fc1e182d14accd Mon Sep 17 00:00:00 2001 From: rEl1cx Date: Wed, 8 Jan 2025 11:44:13 +0800 Subject: [PATCH 2/2] fix(types): enforce consistent type exports in TypeScript files --- eslint.config.ts | 1 + packages/types/src/index.ts | 8 ++++---- packages/utilities/ast/src/index.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) 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/types/src/index.ts b/packages/types/src/index.ts index 95d4f9911f..078d5d437b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,5 +1,5 @@ -export * from "./rule"; -export * from "./rule-feature"; -export * from "./rule-name"; -export * from "./rule-namespace"; +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/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";