Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
8 changes: 6 additions & 2 deletions packages/core/docs/variables/ERComponentHint.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ hints for component collector

> `readonly` **None**: `0n`

### SkipBigIntLiteral

> `readonly` **SkipBigIntLiteral**: `bigint`

### SkipBooleanLiteral

> `readonly` **SkipBooleanLiteral**: `bigint`
Expand Down Expand Up @@ -64,9 +68,9 @@ hints for component collector

> `readonly` **SkipStringLiteral**: `bigint`

### SkipUndefinedLiteral
### SkipUndefined

> `readonly` **SkipUndefinedLiteral**: `bigint`
> `readonly` **SkipUndefined**: `bigint`

### StrictArray

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/component/component-collector-hint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/render-prop/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/types/docs/functions/typeOf.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -21,6 +21,6 @@ the value to be checked

## Returns

`"object"` \| `string` & `object` \| `"array"`
`"object"` \| `"array"` \| `string` & `object`

the type of the value
2 changes: 1 addition & 1 deletion packages/types/src/index.ts
Original file line number Diff line number Diff line change
@@ -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";
File renamed without changes.
2 changes: 1 addition & 1 deletion packages/utilities/ast/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
110 changes: 61 additions & 49 deletions packages/utilities/jsx/src/is-jsx-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;

/**
Expand All @@ -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<typeof node, boolean>(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) {
Expand All @@ -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;
}
Loading