| name | naidan-named-args-lint |
| description | Use this when fixing Naidan require-named-args lint errors. Prefer named args or verifiable external TypeScript types over eslint-disable comments. Includes safe patterns for Promise callbacks, DOM callbacks, platform-owned callback and mock contracts, assignment RHS callbacks, EventTarget adapters, Comlink boundaries, runtime-only external contracts, and external interface contracts. |
Naidan named-args lint fixes
Use this skill when local-rules-named-args/require-named-args reports an error.
The goal is not only to silence lint. The goal is to preserve Naidan's named-args API style while keeping true external API contracts positional.
Core rule
Naidan-owned callables should use one destructured object parameter.
function run({ value }: { value: string }) {}
Not:
function run(value: string) {}
Also prefer destructuring inline object parameters.
async function run({ signal }: { signal?: AbortSignal }) {}
Not:
async function run(params: { signal?: AbortSignal }) {}
Do not add eslint-disable as the first response. First try to make the callable either:
- a Naidan named-args callable, or
- a positional callable whose external contract is verifiable from TypeScript types.
Only use eslint-disable for true external, deprecated, or runtime-only contracts.
Never suppress by name
Never allow or suppress a callable only because of its parameter, property, or method name.
These names are hints for investigation, not proof of an external contract:
resolve
reject
event
listener
callback
handler
start
write
set
mounted
Also do not suppress only because a parameter type is external.
type NaidanHandler = (event: Event) => void;
Event is external, but NaidanHandler is Naidan-owned. Convert it to named args.
type NaidanHandler = ({ event }: { event: Event }) => void;
Preferred fix order
- Convert Naidan-owned functions and callbacks to one destructured object parameter.
- For inline object parameters like
params: { ... }, destructure the parameter instead of keeping params.
- If the callable is storing or adapting an external callback, reference the external type instead of rewriting the function type by hand.
- If the callable is only a short adapter to an external positional callback, keep the adapter inline and call a named-args function inside it.
- If the callable mirrors a true external, deprecated, or runtime-only contract and cannot be expressed by external types, use a focused
eslint-disable comment with a precise reason.
Inline object parameters
Convert inline object parameters to destructured parameters.
async function listModels(params: { signal?: AbortSignal }) {
const { signal } = params;
}
Fix:
async function listModels({ signal }: { signal?: AbortSignal }) {
}
This rule is based on the shape, not on the identifier name. These are all candidates:
params: { id: string }
options: { buffer: Uint8Array, offset?: number }
config: { endpoint: string, headers?: [string, string][] }
request: { url: string, signal?: AbortSignal }
When the original function stores the whole object, preserve behavior by rebuilding the object.
constructor({ endpoint, headers }: { endpoint: string, headers?: [string, string][] }) {
this.config = { endpoint, headers };
}
Alias-typed parameters
Alias-typed parameters need judgment.
function run(params: RunParams) {}
Prefer destructuring when the callable is Naidan-owned and the object is not intentionally passed through as a cohesive object.
function run({ id, title }: RunParams) {}
A destructured alias is already named-args compatible.
function run({ id, title }: RunParams) {}
Do not expand alias types mechanically unless doing so improves the code. Shared option types, worker request types, payload types, and cohesive context objects may remain aliases.
Naidan callback and signature types
Naidan-owned callback types should also use one object parameter.
type ProgressListener = (status: string, progress: number) => void;
Fix:
type ProgressListener = ({ status, progress }: { status: string, progress: number }) => void;
Do not suppress a callback type just because the parameter type is external.
type ResizeHandler = (event: UIEvent) => void;
Fix:
type ResizeHandler = ({ event }: { event: UIEvent }) => void;
Promise resolver and rejecter callbacks
Do not suppress based on the names resolve or reject.
For stored Promise callbacks, use native Promise types.
type PromiseResolve<T> =
ReturnType<typeof Promise.withResolvers<T>>['resolve'];
type PromiseReject<T> =
ReturnType<typeof Promise.withResolvers<T>>['reject'];
For both callbacks:
type PromiseCallbacks<T> = Pick<
ReturnType<typeof Promise.withResolvers<T>>,
'resolve' | 'reject'
>;
Example:
let resolvePromise:
| ReturnType<typeof Promise.withResolvers<boolean>>['resolve']
| undefined;
Instead of:
let resolvePromise: ((value: boolean) => void) | undefined;
For deferred objects:
type Deferred<T> = ReturnType<typeof Promise.withResolvers<T>>;
Or with extra fields:
type PendingRequest = Pick<
ReturnType<typeof Promise.withResolvers<PrivacyFetchResponse>>,
'resolve' | 'reject'
> & {
cleanup: () => void,
};
DOM callback properties
Do not rewrite DOM callback types by hand.
Prefer DOM-owned property types.
private readonly storageHandler: NonNullable<Window['onstorage']> = (event) => {
};
Instead of:
private readonly storageHandler = (event: StorageEvent) => {
};
For message handlers:
const onMessage: NonNullable<Window['onmessage']> = (event) => {
};
For element handlers:
const onImageError: NonNullable<HTMLImageElement['onerror']> = (event) => {
};
requestIdleCallback and requestAnimationFrame
Avoid handwritten callback signatures in local object types.
Prefer DOM-owned types.
const requestIdleCallback:
Window['requestIdleCallback'] =
window.requestIdleCallback.bind(window);
For animation frames:
const requestAnimationFrame:
Window['requestAnimationFrame'] =
window.requestAnimationFrame.bind(window);
If a polyfill or test shim is needed, type it through the DOM property when possible.
const requestIdle:
Window['requestIdleCallback'] =
(callback) => window.setTimeout(() => callback({ didTimeout: false, timeRemaining: () => 0 }), 0);
Platform-owned callback and mock contracts
When a positional callback or mock mirrors a platform API, prefer typing it with the platform-owned contract instead of adding a disable comment.
For Node HTTP server callbacks, use the Node-owned listener type.
import type { RequestListener } from 'node:http';
const handler: RequestListener = (req, res) => {
};
For global fetch adapters, use the global fetch type.
const interceptedFetch: typeof self.fetch = async (input, init) => {
return fetch(input, init);
};
For DOM function shims, use the DOM-owned function type.
const mockScrollTo: typeof window.scrollTo = (_options, _left) => {
};
For test doubles that mirror platform objects, implement the platform interface when TypeScript can verify it.
class MockFileSystemHandle implements FileSystemHandle {
readonly kind: FileSystemHandleKind;
readonly name: string;
async isSameEntry(other: FileSystemHandle): Promise<boolean> {
return this === other;
}
}
Do not assume that implementing an external interface covers local constructors. Constructors are not part of TypeScript interfaces, so mock constructors remain Naidan-owned and should use named args unless they are intentionally preserving a deprecated or runtime-only positional contract.
class MockFileSystemDirectoryHandle implements FileSystemDirectoryHandle {
constructor({ name }: { name: string }) {
this.name = name;
}
}
After adding implements for an external interface, run typecheck and add any required platform members to the mock. For example, FileSystemDirectoryHandle requires async iteration support.
async *[Symbol.asyncIterator](): AsyncIterableIterator<[
string,
FileSystemHandle,
]> {
for (const entry of this.entries.values()) {
yield [entry.name, entry];
}
}
Assignment RHS callbacks
Do not allow assignments just because they are assignments.
This is allowed only if the assignment target has an external callback type.
window.onresize = (event) => {
};
This is also good:
let onStorage: NonNullable<Window['onstorage']>;
onStorage = (event) => {
};
This should still be fixed as Naidan-owned:
type NaidanListener = (event: Event) => void;
let listener: NaidanListener;
listener = (event) => {
};
Fix:
type NaidanListener = ({ event }: { event: Event }) => void;
let listener: NaidanListener;
listener = ({ event }) => {
};
When the lint error says the assignment target needs an external callback type, do not disable the rule first. Type the assignment target with a verifiable external callback type.
EventTarget listener adapters
useEventTargetListener intentionally mirrors addEventListener / removeEventListener and remains positional.
For short adapters, prefer inline positional adapter functions.
useEventTargetListener(window, 'keydown', (event) => {
handleKeyDown({ event });
});
Avoid defining a separate positional function only to call a named-args function.
function handleWindowKeyDown(event: KeyboardEvent) {
handleKeyDown({ event });
}
useEventTargetListener(window, 'keydown', handleWindowKeyDown);
Keep the real logic in named-args functions.
function handleKeyDown({ event }: { event: KeyboardEvent }) {
}
useEventTargetListener(window, 'keydown', (event) => {
handleKeyDown({ event });
});
If the same function identity is required for both add and remove, use an external callback type.
const onStorage: NonNullable<Window['onstorage']> = (event) => {
};
window.addEventListener('storage', onStorage);
window.removeEventListener('storage', onStorage);
Interface extends external contracts
If an interface extends an external interface and redeclares the same method, prefer relying on the external base method when possible.
Allowed pattern:
interface LocalEventTarget extends EventTarget {
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
): void,
}
This is only safe when the method name exists on the external base type.
Do not use this as a blanket exception for new Naidan methods.
interface LocalEventTarget extends EventTarget {
naidanMethod(value: string): void,
}
Fix:
interface LocalEventTarget extends EventTarget {
naidanMethod({ value }: { value: string }): void,
}
Runtime-only external methods
If a method exists at runtime but not in the package's public TypeScript declarations, TypeScript cannot verify it as an external signature.
In that case, keep a precise disable comment.
interface JSZipObjectWithInternalStream extends JSZip.JSZipObject {
internalStream(type: 'uint8array'): JSZip.JSZipStreamHelper<Uint8Array>,
}
Do not use vague comments such as:
Kept positional because this is external.
Comlink boundaries
Keep Comlink boundary methods positional when top-level arguments or proxied callbacks are required.
This applies to interfaces used with Comlink.wrap<RemoteInterface>(...) or objects exposed through Comlink.expose(...).
Do not move Comlink.proxy(...) callbacks inside named-args objects.
Good boundary:
interface WorkerApi {
run(
request: RunRequest,
progressCallback: (progress: ProgressInfo) => void,
): Promise<void>,
}
Good Naidan-facing facade above it:
async function run({
request,
onProgress,
}: {
request: RunRequest,
onProgress: ({ progress }: { progress: ProgressInfo }) => void,
}) {
await remote.run(
request,
Comlink.proxy((progress) => {
onProgress({ progress });
}),
);
}
Existing focused exceptions
If a positional callable already has a precise local exception comment, preserve that reasoning when it is still accurate.
Do not broaden a local exception into a reusable category unless the same contract is verifiable from TypeScript types or from a documented runtime boundary.
Prefer converting the callable or proving the external contract over adding a new exception category.
Disable comment requirements
Only use eslint-disable-next-line local-rules-named-args/require-named-args when the positional callable is one of:
- true external API contract
- Comlink boundary that requires top-level arguments
- deprecated positional overload retained for compatibility
- runtime-only external API not represented in public TypeScript declarations
- intentionally external-compatible helper with a documented local reason
The reason must be specific.
Good:
Good:
Good:
Bad:
Bad:
Checklist before finishing
Before returning a patch:
- Search for new
require-named-args disables.
- Confirm no disable was added only because of a name like
resolve, reject, event, callback, start, write, or set.
- Convert inline object parameters like
params: { ... } to destructured parameters.
- Prefer
ReturnType<typeof Promise.withResolvers<T>>[...] for Promise callbacks.
- Prefer DOM-owned types like
Window['onstorage'], Window['onmessage'], HTMLImageElement['onerror'], or Window['requestIdleCallback'].
- Prefer platform-owned function types like
RequestListener, typeof self.fetch, or typeof window.scrollTo for platform callback shims.
- Prefer
implements for external platform object mocks, then typecheck and add missing required members.
- Keep local mock constructors named args unless they intentionally preserve a deprecated or runtime-only positional contract.
- Keep Comlink boundary methods positional, but keep Naidan-facing facades named args.
- Keep runtime-only external exceptions narrow and explicit.
- Confirm no
TODO(named-args-audit): mechanically suppressed remains.
- Confirm
TODO(named-args-design) is rare and genuinely needs human design judgment.
- Run the named-args rule tests and lint before finalizing.