mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 05:43:12 +00:00
95 lines
2.9 KiB
JavaScript
95 lines
2.9 KiB
JavaScript
import ts from "typescript";
|
|
|
|
/**
|
|
* Local type-aware rule: the left operand of `&&` in JSX must be entirely
|
|
* boolean.
|
|
*
|
|
* `{value && <Element />}` renders its left operand when it is falsy and
|
|
* non-boolean (notably `{count && <X />}` renders `0`), so the left operand
|
|
* must be `boolean` (or boolean literals) across the whole union. The rule
|
|
* asks the TypeScript checker for the type of the left operand and reports
|
|
* unless every union constituent is boolean-like.
|
|
*
|
|
* Type-aware by design: a selector that cannot see operand types would give
|
|
* false confidence rather than enforcement. If type services are unexpectedly
|
|
* unavailable in a governed TSX file, the rule fails closed (reports) instead
|
|
* of silently claiming the expression is safe.
|
|
*/
|
|
const booleanLikeFlags = ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral;
|
|
|
|
function isBooleanLikeType(type) {
|
|
return (type.flags & booleanLikeFlags) !== 0;
|
|
}
|
|
|
|
function isEntirelyBoolean(type) {
|
|
if (type.isUnion()) {
|
|
return type.types.every((constituent) => isBooleanLikeType(constituent));
|
|
}
|
|
return isBooleanLikeType(type);
|
|
}
|
|
|
|
const transparentExpressionTypes = new Set([
|
|
"ChainExpression",
|
|
"ParenthesizedExpression",
|
|
"TSAsExpression",
|
|
"TSNonNullExpression",
|
|
"TSTypeAssertion",
|
|
]);
|
|
|
|
function isInRenderedPosition(node) {
|
|
let current = node;
|
|
while (current.parent) {
|
|
const parent = current.parent;
|
|
if (parent.type === "JSXExpressionContainer") {
|
|
return parent.parent?.type !== "JSXAttribute";
|
|
}
|
|
if (parent.type === "LogicalExpression" || transparentExpressionTypes.has(parent.type)) {
|
|
current = parent;
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const rule = {
|
|
meta: {
|
|
type: "problem",
|
|
docs: {
|
|
description: "Require the left operand of `&&` in JSX to be entirely boolean",
|
|
},
|
|
schema: [],
|
|
messages: {
|
|
nonBooleanLeft:
|
|
'The left operand of `&&` in JSX must be boolean. Non-boolean falsy operands (e.g. `0`, `""`) render into the DOM; coerce with `Boolean(...)` or `!!` before `&&`.',
|
|
typeServicesUnavailable:
|
|
"Type-aware boolean-safety check is unavailable for this JSX expression. This file must be part of a TypeScript project with type information so the rule can enforce safely.",
|
|
},
|
|
},
|
|
create(context) {
|
|
const services = context.sourceCode?.parserServices ?? context.parserServices;
|
|
|
|
return {
|
|
LogicalExpression(node) {
|
|
if (node.operator !== "&&") {
|
|
return;
|
|
}
|
|
if (!isInRenderedPosition(node)) {
|
|
return;
|
|
}
|
|
|
|
if (!services || services.program === null) {
|
|
context.report({ node, messageId: "typeServicesUnavailable" });
|
|
return;
|
|
}
|
|
|
|
const leftType = services.getTypeAtLocation(node.left);
|
|
if (!isEntirelyBoolean(leftType)) {
|
|
context.report({ node: node.left, messageId: "nonBooleanLeft" });
|
|
}
|
|
},
|
|
};
|
|
},
|
|
};
|
|
|
|
export default rule;
|