mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 02:00:10 +00:00
e44c9a2070
* ci: move custom oxlint rules to typescript and alternative API * ci: add to test sharding
63 lines
2.1 KiB
TypeScript
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 },
|
|
});
|
|
}
|
|
},
|
|
};
|
|
},
|
|
});
|