Files
openchamber/tools/oxlint/anti-slop/rules/no-unknown-parameters.ts
T
Bohdan Triapitsyn 51aef5e316 chore(lint): vendor anti-slop oxlint plugin and add batched cleanup pipeline
Vendor the anti-slop Oxlint plugin at tools/oxlint/anti-slop and register it
in oxlint.config.ts, with Oxlint's own rule categories disabled so ESLint
stays the general-purpose linter.

Add scripts/anti-slop.mjs (bun run deslop) mirroring the React Doctor batch
interface: next-batch, check-batch, active, release, top, file. Batch handoff
directories now double as file claims shared across clones via
~/.openchamber/maintenance-claims, so concurrent maintenance batches from
either pipeline never select the same file.

Harden both scheduled maintenance flows: stop on a dirty worktree, stop on
NO BATCH AVAILABLE, validate per package instead of workspace-wide, and pin
react-doctor to 0.9.12. The anti-slop task command documents concrete
good and bad fixes and forbids laundering types to satisfy a rule.
2026-08-16 15:55:08 +03:00

84 lines
2.9 KiB
TypeScript

import { defineRule } from "@oxlint/plugins";
import type { ESTree } from "@oxlint/plugins";
type Parameter = ESTree.ParamPattern;
type ParameterOwner =
| ESTree.ArrowFunctionExpression
| ESTree.Function
| ESTree.TSCallSignatureDeclaration
| ESTree.TSConstructSignatureDeclaration
| ESTree.TSConstructorType
| ESTree.TSFunctionType
| ESTree.TSMethodSignature;
function parameterAnnotation(parameter: Parameter): ESTree.TSTypeAnnotation | null | undefined {
if (parameter.type === "TSParameterProperty") {
return parameterAnnotation(parameter.parameter);
}
if (parameter.type === "RestElement") {
return parameter.typeAnnotation ?? parameterAnnotation(parameter.argument);
}
if (parameter.type === "AssignmentPattern") {
return parameter.typeAnnotation ?? parameter.left.typeAnnotation;
}
return parameter.typeAnnotation;
}
function parameterName(parameter: Parameter, sourceText: string): string {
if (parameter.type === "TSParameterProperty") {
return parameterName(parameter.parameter, sourceText);
}
if (parameter.type === "AssignmentPattern") {
return parameterName(parameter.left, sourceText);
}
if (parameter.type === "RestElement") {
return parameterName(parameter.argument, sourceText);
}
return parameter.type === "Identifier"
? parameter.name
: sourceText.replace(/\s*:\s*unknown\s*$/u, "");
}
/** Disallow unknown inputs except explicitly named error-cause enrichment. */
export const noUnknownParametersRule = defineRule({
meta: {
type: "problem",
docs: {
description:
"Disallow explicitly unknown function parameters except `cause`; decode unknown input at its I/O boundary instead.",
},
messages: {
unknownParameter:
"Parameter `{{parameter}}` leaves input unparsed. Accept a named domain type; run the expected schema or parser at the I/O boundary before calling this function.",
},
},
createOnce(context) {
const checkParameters = (node: ParameterOwner) => {
for (const parameter of node.params) {
const annotation = parameterAnnotation(parameter);
if (annotation?.typeAnnotation.type !== "TSUnknownKeyword") continue;
const name = parameterName(parameter, context.sourceCode.getText(parameter));
if (name === "cause") continue;
context.report({
node: annotation.typeAnnotation,
messageId: "unknownParameter",
data: { parameter: name },
});
}
};
return {
ArrowFunctionExpression: checkParameters,
FunctionDeclaration: checkParameters,
FunctionExpression: checkParameters,
TSCallSignatureDeclaration: checkParameters,
TSConstructSignatureDeclaration: checkParameters,
TSConstructorType: checkParameters,
TSDeclareFunction: checkParameters,
TSEmptyBodyFunctionExpression: checkParameters,
TSFunctionType: checkParameters,
TSMethodSignature: checkParameters,
};
},
});