mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
refactor(lint): enable anti-slop/no-reflect-apply (#20782)
`anti-slop/no-reflect-apply` rejects `Reflect.apply(fn, thisArg, argsArray)`.
It defeats the call-signature checks TypeScript applies to an ordinary call:
the args array is checked as an array, not positionally against the callee's
parameters, so arity and type errors pass silently. Dynamic dispatch belongs
behind a named interface, not behind a reflective call.
Flipped the rule from "off" to "error" and cleared all 17 baseline violations
across `src config tests mobile` (16 sites; one file had two).
Fix pattern: `Reflect.apply(fn, recv, args)` becomes `fn.call(recv, ...args)`,
or a direct method call when the implicit receiver is already the right object.
The receiver is preserved at every site.
Where the callee is a captured built-in whose overloads split on an argument's
shape (`String.prototype.split`, `JSON.stringify`), a call-signature capture no
longer compiles once the args are passed positionally. Those three sites capture
the function through a method-shaped type
(`{ split(separator: unknown, limit?: number): string[] }['split']`), which keeps
the forwarding call checked rather than asserted.
Behaviour notes:
- `diff-section-layout.test.ts` drops a `limit === undefined ? [sep] : [sep, limit]`
conditional. Equivalent: `String.prototype.split` maps an undefined limit to
2^32-1, and the `Symbol.split` path forwards undefined either way.
- `workspace-space-compaction.test.ts` forwards `reduce`'s two arguments unchanged,
so the `arguments.length >= 2` initial-value branch is unaffected.
- `agent-session-history-byte-accounting.test.ts` is the one site where the receiver
is not literally preserved (`JSON` -> undefined). `JSON.stringify` never reads
`this` per spec, and restoring `.call(JSON, ...)` would reintroduce the overload
failure under strictBindCallApply.
No suppression comments added — the rule has zero `oxlint-disable` sites.
`Reflect.apply` still appears at electron.vite.config.ts:159, inside a template
literal of generated bootstrap source. That is string content, not lintable code.
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
"anti-slop/no-module-mocking": "off",
|
||||
"anti-slop/no-object-parameters": "off",
|
||||
"anti-slop/no-reduce-accumulator-copy": "error",
|
||||
"anti-slop/no-reflect-apply": "off",
|
||||
"anti-slop/no-reflect-apply": "error",
|
||||
"anti-slop/no-reflect-get": "off",
|
||||
"anti-slop/no-runtime-typeof": "off",
|
||||
"anti-slop/no-shape-in-symbol-names": "off",
|
||||
|
||||
@@ -12,7 +12,7 @@ export function installMainBlockingProbe() {
|
||||
const epoch = Date.now()
|
||||
let result
|
||||
try {
|
||||
result = Reflect.apply(original, this, args)
|
||||
result = original.call(this, ...args)
|
||||
return result
|
||||
} finally {
|
||||
const durationMs = performance.now() - start
|
||||
|
||||
@@ -21,7 +21,7 @@ export function installPersistenceCallProbe() {
|
||||
const epoch = Date.now()
|
||||
let result
|
||||
try {
|
||||
result = Reflect.apply(original, this, args)
|
||||
result = original.call(this, ...args)
|
||||
return result
|
||||
} finally {
|
||||
const durationMs = performance.now() - start
|
||||
|
||||
@@ -88,7 +88,7 @@ function runWithNativeCallCount(fn) {
|
||||
let calls = 0
|
||||
Buffer.byteLength = (...args) => {
|
||||
calls += 1
|
||||
return Reflect.apply(nativeByteLength, Buffer, args)
|
||||
return nativeByteLength.call(Buffer, ...args)
|
||||
}
|
||||
try {
|
||||
return { output: fn(), calls }
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('gitlab client — MR operations', () => {
|
||||
if (this[0] === 'gitlab.com' && this.every((value) => typeof value === 'string')) {
|
||||
knownHostCacheScans += 1
|
||||
}
|
||||
return Reflect.apply(originalMap, this, [callback, thisArg])
|
||||
return originalMap.call(this, callback, thisArg)
|
||||
})
|
||||
|
||||
try {
|
||||
|
||||
+7
-3
@@ -69,13 +69,17 @@ it.each([1, 100, 200])('serializes each of %i unchanged forward page items once'
|
||||
await appendItems(count, 'x'.repeat(8_000))
|
||||
const snapshot = journal.snapshot()
|
||||
const stringify = JSON.stringify
|
||||
// Method-shaped type: the JSON.stringify overloads split on replacer shape and reject a forwarded one.
|
||||
const forwardStringify: {
|
||||
stringify(value: unknown, replacer?: unknown, space?: unknown): string
|
||||
}['stringify'] = stringify
|
||||
let itemSerializations = 0
|
||||
JSON.stringify = ((value: unknown, ...args: unknown[]) => {
|
||||
JSON.stringify = (value: unknown, replacer?: unknown, space?: unknown): string => {
|
||||
if (value && typeof value === 'object' && 'itemId' in value && 'body' in value) {
|
||||
itemSerializations++
|
||||
}
|
||||
return Reflect.apply(stringify, JSON, [value, ...args])
|
||||
}) as typeof JSON.stringify
|
||||
return forwardStringify(value, replacer, space)
|
||||
}
|
||||
try {
|
||||
const result = readAgentSessionHistory(
|
||||
journal,
|
||||
|
||||
@@ -312,9 +312,7 @@ describe('RuntimeBrowserCommands client-hosted routing', () => {
|
||||
.spyOn(registry, 'publishClientPage')
|
||||
.mockImplementation((input) => {
|
||||
order.push('publish')
|
||||
return Reflect.apply(RuntimeBrowserPageRegistry.prototype.publishClientPage, registry, [
|
||||
input
|
||||
])
|
||||
return RuntimeBrowserPageRegistry.prototype.publishClientPage.call(registry, input)
|
||||
})
|
||||
const notifyHeadlessBrowserSessionTabsChanged = vi.fn(() => order.push('notify'))
|
||||
const issueClientPageCommand = vi.fn(() => {
|
||||
|
||||
@@ -66,7 +66,7 @@ export function installRuntimeLinearCommandSurface(target: object): void {
|
||||
const method = {
|
||||
[name](this: LinearFacadeInstance, ...args: unknown[]): unknown {
|
||||
const commands = this.linearCommands as unknown as LinearMethodBag
|
||||
return Reflect.apply(commands[name], overrideAwareReceiver(this, commands, names), args)
|
||||
return commands[name].call(overrideAwareReceiver(this, commands, names), ...args)
|
||||
}
|
||||
}[name]
|
||||
delegators.add(method)
|
||||
|
||||
@@ -90,7 +90,9 @@ describe('RuntimeFileCommands', () => {
|
||||
submatches: [{ start: 0, end: 6 }]
|
||||
}
|
||||
})
|
||||
const originalSplit = String.prototype.split
|
||||
// Method-shaped type: a call-signature capture would reject `split`'s splitter-object overload.
|
||||
const originalSplit: { split(separator: unknown, limit?: number): string[] }['split'] =
|
||||
String.prototype.split
|
||||
let scanned = 0
|
||||
const spy = vi.spyOn(String.prototype, 'split').mockImplementation(function (
|
||||
this: string,
|
||||
@@ -100,7 +102,7 @@ describe('RuntimeFileCommands', () => {
|
||||
if (separator === '\n') {
|
||||
scanned += this.length
|
||||
}
|
||||
return Reflect.apply(originalSplit, this, [separator, limit])
|
||||
return originalSplit.call(this, separator, limit)
|
||||
})
|
||||
try {
|
||||
for (let offset = 0; offset < line.length; offset += 1024) {
|
||||
|
||||
@@ -83,7 +83,9 @@ describe.each(searchCases)('relay $name line fragments', ({ search, encode }) =>
|
||||
for (let offset = 0; offset < wire.length; offset += 4096) {
|
||||
chunks.push(wire.slice(offset, offset + 4096))
|
||||
}
|
||||
const originalSplit = String.prototype.split
|
||||
// Method-shaped type: a call-signature capture would reject `split`'s splitter-object overload.
|
||||
const originalSplit: { split(separator: unknown, limit?: number): string[] }['split'] =
|
||||
String.prototype.split
|
||||
let scannedCharacters = 0
|
||||
const spy = vi.spyOn(String.prototype, 'split').mockImplementation(function (
|
||||
this: string,
|
||||
@@ -93,7 +95,7 @@ describe.each(searchCases)('relay $name line fragments', ({ search, encode }) =>
|
||||
if (separator === '\n') {
|
||||
scannedCharacters += this.length
|
||||
}
|
||||
return Reflect.apply(originalSplit, this, [separator, limit])
|
||||
return originalSplit.call(this, separator, limit)
|
||||
})
|
||||
let fragmented
|
||||
try {
|
||||
|
||||
+3
-3
@@ -43,8 +43,8 @@ function layoutWithNumericMapSetCount(worktrees: ReturnType<typeof buildChain>)
|
||||
if (typeof key === 'number') {
|
||||
numericMapSets += 1
|
||||
}
|
||||
return Reflect.apply(set, this, [key, value])
|
||||
} as typeof Map.prototype.set
|
||||
return set.call(this, key, value)
|
||||
}
|
||||
try {
|
||||
return { layout: layoutAgentMapWorktreeLineage(worktrees), numericMapSets }
|
||||
} finally {
|
||||
@@ -64,7 +64,7 @@ function layoutWithWorktreePushCount(count: number) {
|
||||
typeof item.id === 'string' &&
|
||||
item.id.startsWith('worktree-')
|
||||
).length
|
||||
return Reflect.apply(push, this, items)
|
||||
return push.call(this, ...items)
|
||||
}
|
||||
try {
|
||||
return {
|
||||
|
||||
@@ -145,8 +145,8 @@ describe('packAgentMapWorktrees', () => {
|
||||
if (typeof key === 'number') {
|
||||
numericMapSets += 1
|
||||
}
|
||||
return Reflect.apply(set, this, [key, value])
|
||||
} as typeof Map.prototype.set
|
||||
return set.call(this, key, value)
|
||||
}
|
||||
try {
|
||||
const packed = packAgentMapWorktrees(
|
||||
Array.from({ length: 5 }, (_, index) => ({
|
||||
|
||||
@@ -110,8 +110,10 @@ describe('diff section layout', () => {
|
||||
})
|
||||
|
||||
it('estimates line-count height without allocating split arrays', () => {
|
||||
const originalSplit = String.prototype.split
|
||||
const patchedSplit = function patchedSplit(
|
||||
// Method-shaped type: a call-signature capture would reject `split`'s splitter-object overload.
|
||||
const originalSplit: { split(separator: unknown, limit?: number): string[] }['split'] =
|
||||
String.prototype.split
|
||||
const patchedSplit: typeof String.prototype.split = function patchedSplit(
|
||||
this: string,
|
||||
separator?: unknown,
|
||||
limit?: number
|
||||
@@ -119,9 +121,8 @@ describe('diff section layout', () => {
|
||||
if (String(this).startsWith('line 0')) {
|
||||
throw new Error('layout should not split full diff content')
|
||||
}
|
||||
const args = limit === undefined ? [separator] : [separator, limit]
|
||||
return Reflect.apply(originalSplit, this, args) as string[]
|
||||
} as typeof String.prototype.split
|
||||
return originalSplit.call(this, separator, limit)
|
||||
}
|
||||
String.prototype.split = patchedSplit
|
||||
|
||||
try {
|
||||
|
||||
@@ -32,7 +32,8 @@ export function createTiptapMarkedFacade(): typeof marked {
|
||||
const lexer = (src: string, options?: MarkedOptions): TokensList =>
|
||||
new RegistryLexer(options).lex(src)
|
||||
const facade = new Proxy(marked, {
|
||||
apply: (_target, _thisArg, args) => Reflect.apply(registry.parse, registry, args),
|
||||
apply: (_target, _thisArg, args: [src: string, options?: MarkedOptions | null]) =>
|
||||
registry.parse(...args),
|
||||
get: (target, property, receiver) => {
|
||||
switch (property) {
|
||||
case 'defaults':
|
||||
|
||||
@@ -33,7 +33,12 @@ it('avoids allocating copies of unaffected pages during an item mutation', () =>
|
||||
if (inputs.has(this)) {
|
||||
allocations++
|
||||
}
|
||||
return Reflect.apply(map, this, [callback, thisArg]) as U[]
|
||||
// Explicit `call` type arguments: inference through `call` erases `map`'s own `U` to `unknown`.
|
||||
return map.call<T[], [(value: T, index: number, array: T[]) => U, unknown], U[]>(
|
||||
this,
|
||||
callback,
|
||||
thisArg
|
||||
)
|
||||
}
|
||||
Array.prototype.slice = function (this: unknown[], ...args: Parameters<typeof slice>) {
|
||||
if (inputs.has(this)) {
|
||||
|
||||
@@ -14,14 +14,16 @@ it('keeps a multiline commit body intact without materializing every message lin
|
||||
'',
|
||||
message
|
||||
].join('\n')
|
||||
const original = String.prototype.split
|
||||
// Method-shaped type: a call-signature capture would reject `split`'s splitter-object overload.
|
||||
const original: { split(separator: unknown, limit?: number): string[] }['split'] =
|
||||
String.prototype.split
|
||||
let allocatedFields = 0
|
||||
const spy = vi.spyOn(String.prototype, 'split').mockImplementation(function (
|
||||
this: string,
|
||||
separator: string | RegExp | { [Symbol.split](value: string, limit?: number): string[] },
|
||||
separator: unknown,
|
||||
limit?: number
|
||||
) {
|
||||
const result = Reflect.apply(original, this, [separator, limit]) as string[]
|
||||
const result = original.call(this, separator, limit)
|
||||
if (separator === '\n' && String(this).includes('body line')) {
|
||||
allocatedFields += result.length
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ it('sums omitted sizes without constructing a replacement object per omitted ite
|
||||
if (initial && typeof initial === 'object' && 'name' in initial && initial.name === 'Other') {
|
||||
objectAccumulators += this.length
|
||||
}
|
||||
return Reflect.apply(original, this, [callback, initial])
|
||||
return original.call(this, callback, initial)
|
||||
})
|
||||
let result: ReturnType<typeof compactWorkspaceSpaceItems>
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user