Files
renovate/tools/lint/rules/prefer-nullish-util.ts
Sebastian Poxhofer e44c9a2070 ci(oxlint): move custom oxlint rules to typescript and alternative API (#45337)
* ci: move custom oxlint rules to typescript and alternative API

* ci: add  to test sharding
2026-08-19 05:37:56 +00:00

63 lines
2.1 KiB
TypeScript

import type { ESTree } from '@oxlint/plugins';
import { defineRule } from '@oxlint/plugins';
/**
* Return the method/function name of a non-computed member call like
* `.optional()` or `z.nullable(x)`, or null if the node is not such a call.
*/
function getCalleeName(node: ESTree.Node): string | null {
if (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
!node.callee.computed &&
node.callee.property.type === 'Identifier'
) {
return node.callee.property.name;
}
return null;
}
export default defineRule({
meta: {
type: 'suggestion',
messages: {
noNullish:
'Use `Nullish()` from schema-utils instead of `nullish()` (or `DeepNullish()` for whole schemas).',
noChain:
'Use `Nullish()` from schema-utils instead of combining `{{inner}}()` and `{{outer}}()` (or `DeepNullish()` for whole schemas).',
},
},
createOnce(context) {
return {
CallExpression(node) {
// `.nullish()` and standalone `z.nullish(x)`
const outer = getCalleeName(node);
if (outer === 'nullish') {
context.report({ node, messageId: 'noNullish' });
return;
}
if (outer !== 'optional' && outer !== 'nullable') {
return;
}
const expectedInner = outer === 'optional' ? 'nullable' : 'optional';
// chained: `.nullable().optional()` / `z.nullable(x).optional()`
// a non-null `outer` implies a member callee, but that narrowing
// happens inside `getCalleeName` and is invisible to the compiler
const receiver = (node.callee as ESTree.MemberExpression).object;
// nested standalone: `z.optional(z.nullable(x))`
const nested = node.arguments.length === 1 ? node.arguments[0] : null;
if (
getCalleeName(receiver) === expectedInner ||
(nested && getCalleeName(nested) === expectedInner)
) {
context.report({
node,
messageId: 'noChain',
data: { inner: expectedInner, outer },
});
}
},
};
},
});