mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix(lint): enable anti-slop/no-reflect-get (#20786)
`anti-slop/no-reflect-get` rejects every call to `Reflect.get`. The
reflective read bypasses ordinary property access and throws away the
type evidence the compiler would otherwise give you: the result is
`any`/`unknown` with no narrowing, so a typo in the key or a shape drift
in the source object is invisible until runtime. The rule's remedy is to
parse dynamic input into a named domain type (or narrow it with `in`)
and then read the field normally.
Baseline: 86 violations across 67 files. Now zero unsuppressed
violations under
`npx oxlint --config config/oxlint-anti-slop.json --ignore-pattern 'config/oxlint-plugins/anti-slop/**' src config tests mobile`.
Fix pattern
-----------
44 of the 86 were rewritten. The dominant shape was an `unknown` value
read through `Reflect.get` right after a `typeof === 'object'` guard;
those became `in`-narrowed property access, which TypeScript checks:
- Reflect.get(value, 'agents')
+ 'agents' in value ? value.agents : null
Two further shapes:
- `Reflect.get(Object(x), 'k')` on a possibly-primitive envelope became a
small named reader that boxes once and indexes a
`Record<string, unknown>` (`settingsField` in
mobile/src/transport/settings-read-operations.ts).
- Tests reaching into private state moved to TypeScript's checked
bracket-index escape hatch (`runtime['layoutQueues']`), or to a
documented read-only accessor on the owning class
(`SearchSubprocessLineAccumulator.retainedCapacityBytes()`,
`CodexSubagentExecutions.retentionSizes()`).
No type assertion was added anywhere: the diff contains zero net-new
`as` casts, `as any`, `as unknown as`, `@ts-ignore`, or
`@ts-expect-error`, so nothing was laundered into the sibling
assertion rules.
Suppressions
------------
42x `// oxlint-disable-next-line anti-slop/no-reflect-get` across 38
files. Every one is the default-forward branch of a `Proxy` `get` trap:
get(target, property, receiver) {
...
return Reflect.get(target, property, receiver)
}
`Reflect.get(target, property, receiver)` is the only construct that
forwards with correct `receiver` semantics; `target[property]` invokes
an accessor with the wrong `this` and silently breaks getters that read
sibling state. There is no typed alternative, so these are suppressed
rather than rewritten.
3x `// oxlint-disable-next-line typescript-eslint/consistent-type-definitions
-- declaration merging requires interface` in
tests/e2e/github-url-smart-input-transition.spec.ts,
tests/e2e/linear-url-workspace-entry.spec.ts, and
tests/e2e/worktree-active-delete-scroll-position.spec.ts. Replacing
`Reflect.get(window, 'x')` with typed `window.x` requires a
`declare global { interface Window }` block, and `interface` is
mandatory for declaration merging. Matches the existing convention at
tests/e2e/helpers/runtime-types.ts:63.
1x `// eslint-disable-next-line no-var -- main-process gate handle for
this spec` in tests/e2e/project-group-creation-visibility.spec.ts, for
the same reason a `var` global is needed to type the handle. Matches
tests/e2e/agent-session-log-tail-stability.spec.ts:24.
Also updates two source-text anchors in mobile's rpc-recording mutation
harness (mobile/src/test-support/rpc-recording/operation-mutations.ts
and recording-runner.test.ts), which pin the exact text of the rewritten
line in settings-read-operations.ts and would otherwise fail with
"Mutant anchor matched 0 sites, expected 1".
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
"anti-slop/no-object-parameters": "off",
|
||||
"anti-slop/no-reduce-accumulator-copy": "error",
|
||||
"anti-slop/no-reflect-apply": "error",
|
||||
"anti-slop/no-reflect-get": "off",
|
||||
"anti-slop/no-reflect-get": "error",
|
||||
"anti-slop/no-runtime-typeof": "off",
|
||||
"anti-slop/no-shape-in-symbol-names": "off",
|
||||
"anti-slop/no-unknown-parameters": "off",
|
||||
|
||||
@@ -36,8 +36,8 @@ export const OPERATION_MUTATIONS = {
|
||||
// Reads the overrides one level above the settings envelope.
|
||||
'bot-overrides-envelope': {
|
||||
file: 'settings-read-operations.ts',
|
||||
before: "settings == null ? undefined : Reflect.get(Object(settings), 'prBotAuthorOverrides')",
|
||||
after: "raw == null ? undefined : Reflect.get(Object(raw), 'prBotAuthorOverrides')"
|
||||
before: "settings == null ? undefined : settingsField(settings, 'prBotAuthorOverrides')",
|
||||
after: "raw == null ? undefined : settingsField(raw, 'prBotAuthorOverrides')"
|
||||
},
|
||||
// Publishes the settings envelope instead of the accepted operation value.
|
||||
'workspace-context-envelope': {
|
||||
|
||||
@@ -445,7 +445,7 @@ describe('recording boundaries', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'rpc-mutant-'))
|
||||
try {
|
||||
const anchor =
|
||||
"const overrides = settings == null ? undefined : Reflect.get(Object(settings), 'prBotAuthorOverrides')"
|
||||
"const overrides = settings == null ? undefined : settingsField(settings, 'prBotAuthorOverrides')"
|
||||
mkdirSync(join(root, 'mod'), { recursive: true })
|
||||
writeFileSync(
|
||||
join(root, 'mod/settings-read-operations.ts'),
|
||||
|
||||
@@ -7,6 +7,12 @@ function settingsMember(raw: unknown): unknown {
|
||||
return boxed!.settings
|
||||
}
|
||||
|
||||
// Box primitives so a non-object settings value reads as absent instead of throwing.
|
||||
function settingsField(settings: unknown, key: string): unknown {
|
||||
const boxed: Record<string, unknown> = Object(settings)
|
||||
return boxed[key]
|
||||
}
|
||||
|
||||
// Settings remain opaque: callers historically retain fields without validating their shapes.
|
||||
const settingsReader: RpcCompatibleReader<unknown, 'settings-member', unknown> = (raw) => ({
|
||||
compatible: true,
|
||||
@@ -27,7 +33,7 @@ const optionalSettingsReader: RpcCompatibleReader<unknown, 'optional-settings-me
|
||||
const botOverridesReader: RpcCompatibleReader<unknown, 'bot-logins', string[]> = (raw) => {
|
||||
const settings = raw == null ? undefined : settingsMember(raw)
|
||||
const overrides: unknown =
|
||||
settings == null ? undefined : Reflect.get(Object(settings), 'prBotAuthorOverrides')
|
||||
settings == null ? undefined : settingsField(settings, 'prBotAuthorOverrides')
|
||||
return {
|
||||
compatible: true,
|
||||
variant: 'bot-logins',
|
||||
@@ -85,7 +91,7 @@ export const newTabSettingsRead = bindDeferredRpcOperation(
|
||||
const copyTrimsGutterReader: RpcCompatibleReader<unknown, 'copy-trims-gutter', boolean> = (raw) => {
|
||||
const settings = raw == null ? undefined : settingsMember(raw)
|
||||
const trims: unknown =
|
||||
settings == null ? undefined : Reflect.get(Object(settings), 'terminalCopyTrimsGutter')
|
||||
settings == null ? undefined : settingsField(settings, 'terminalCopyTrimsGutter')
|
||||
return {
|
||||
compatible: true,
|
||||
variant: 'copy-trims-gutter',
|
||||
|
||||
@@ -53,10 +53,12 @@ export function readManagedHookDetectionResult(value: unknown): {
|
||||
if (value === null || typeof value !== 'object') {
|
||||
return { agents: [], claudeVersion: null }
|
||||
}
|
||||
const agents = detectedManagedHookAgents(Reflect.get(value, 'agents'))
|
||||
const versions = Reflect.get(value, 'versions')
|
||||
const agents = detectedManagedHookAgents('agents' in value ? value.agents : null)
|
||||
const versions = 'versions' in value ? value.versions : null
|
||||
const rawClaudeVersion =
|
||||
versions !== null && typeof versions === 'object' ? Reflect.get(versions, 'claude') : null
|
||||
versions !== null && typeof versions === 'object' && 'claude' in versions
|
||||
? versions.claude
|
||||
: null
|
||||
return {
|
||||
agents,
|
||||
claudeVersion: parseClaudeCliVersion(
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('independent JSON boundary review', () => {
|
||||
expect(await parseHermesSessionDocument(file, bytes(content, 1), 'linux', options)).toEqual(
|
||||
await parseHermesSessionContent(file, content, 'linux', options)
|
||||
)
|
||||
expect(Reflect.get({}, 'polluted')).toBeUndefined()
|
||||
expect('polluted' in {}).toBe(false)
|
||||
})
|
||||
for (const content of [
|
||||
'{"messages":[],}',
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
readQuestionIds,
|
||||
readQuestionOptionAnswers
|
||||
} from './codex-prompt-registry-bounds'
|
||||
import { readRecord, readString as readRecordString } from './codex-item-field-readers'
|
||||
|
||||
export const CODEX_COMMAND_APPROVAL_METHOD = 'item/commandExecution/requestApproval'
|
||||
export const CODEX_FILE_CHANGE_APPROVAL_METHOD = 'item/fileChange/requestApproval'
|
||||
@@ -36,11 +37,7 @@ export type CodexPromptClaim = {
|
||||
}
|
||||
|
||||
function readString(params: unknown, key: string): string | null {
|
||||
if (typeof params !== 'object' || params === null) {
|
||||
return null
|
||||
}
|
||||
const value = Reflect.get(params, key)
|
||||
return typeof value === 'string' && value.length > 0 ? value : null
|
||||
return readRecordString(readRecord(params), key)
|
||||
}
|
||||
|
||||
export function isCodexPromptMethod(method: string): boolean {
|
||||
|
||||
@@ -13,8 +13,9 @@ describe('CodexSubagentExecutions retention and identity', () => {
|
||||
executions.observeTurn(id, id, 'completed')
|
||||
}
|
||||
expect(executions.workingChildren().map((child) => child.agentThreadId)).toEqual(['long-lived'])
|
||||
expect(Reflect.get(executions, 'children').size).toBeLessThanOrEqual(128)
|
||||
expect(Reflect.get(executions, 'settledTurns').size).toBeLessThanOrEqual(256)
|
||||
const { children, settledTurns } = executions.retentionSizes()
|
||||
expect(children).toBeLessThanOrEqual(128)
|
||||
expect(settledTurns).toBeLessThanOrEqual(256)
|
||||
})
|
||||
|
||||
it('retains early live owner events at capacity and makes room only after settlement', () => {
|
||||
@@ -45,7 +46,6 @@ describe('CodexSubagentExecutions retention and identity', () => {
|
||||
executions.observeTurn('child', 'turn', 'failed')
|
||||
expect(executions.workingChildren()[0]?.execution?.turnId).toBe('new-turn')
|
||||
executions.clear()
|
||||
expect(Reflect.get(executions, 'children').size).toBe(0)
|
||||
expect(Reflect.get(executions, 'settledTurns').size).toBe(0)
|
||||
expect(executions.retentionSizes()).toEqual({ children: 0, settledTurns: 0 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -106,6 +106,11 @@ export class CodexSubagentExecutions {
|
||||
this.settledTurns.clear()
|
||||
}
|
||||
|
||||
/** Retention bounds are not observable through the child/turn API, so expose the two counts. */
|
||||
retentionSizes(): { children: number; settledTurns: number } {
|
||||
return { children: this.children.size, settledTurns: this.settledTurns.size }
|
||||
}
|
||||
|
||||
private child(agentThreadId: string): CodexExecutionChild | undefined {
|
||||
const existing = this.children.get(agentThreadId)
|
||||
if (existing) {
|
||||
|
||||
@@ -59,8 +59,11 @@ export function requestDaemonRpc<T>(opts: DaemonRpcRequestOptions): Promise<T> {
|
||||
const createTimeoutError = (): DaemonRequestTimeoutError =>
|
||||
new DaemonRequestTimeoutError(`Request ${type} timed out after ${opts.timeoutMs}ms`)
|
||||
const createSessionId =
|
||||
type === 'createOrAttach' && payload !== null && typeof payload === 'object'
|
||||
? Reflect.get(payload, 'sessionId')
|
||||
type === 'createOrAttach' &&
|
||||
payload !== null &&
|
||||
typeof payload === 'object' &&
|
||||
'sessionId' in payload
|
||||
? payload.sessionId
|
||||
: null
|
||||
const requestPayload =
|
||||
type === 'createOrAttach' && payload !== null && typeof payload === 'object'
|
||||
|
||||
@@ -91,6 +91,7 @@ function flakyClose(journal: AgentSessionJournal, failures: number): AgentSessio
|
||||
return new Proxy(journal, {
|
||||
get(target, property, receiver) {
|
||||
if (property !== 'close') {
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
return async () => {
|
||||
|
||||
@@ -24,6 +24,7 @@ function withSharedProcessSnapshot(provider: IPtyProvider): IPtyProvider {
|
||||
// receiver, a provider whose own method called `this.listProcesses()`
|
||||
// would silently read this sweep's cached snapshot instead of the live
|
||||
// host — the batching must not leak past the calls it was built for.
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
const member: unknown = Reflect.get(target, property)
|
||||
return typeof member === 'function' ? member.bind(target) : member
|
||||
}
|
||||
|
||||
@@ -87,8 +87,24 @@ const store = {
|
||||
}
|
||||
}
|
||||
|
||||
/** Reclaim clears protected retention maps that no public reader exposes. */
|
||||
class ObservableRuntime extends OrcaRuntimeService {
|
||||
get restoreTimers(): typeof this.pendingRestoreTimers {
|
||||
return this.pendingRestoreTimers
|
||||
}
|
||||
get softLeavers(): typeof this.pendingSoftLeavers {
|
||||
return this.pendingSoftLeavers
|
||||
}
|
||||
get fitOverrides(): typeof this.terminalFitOverrides {
|
||||
return this.terminalFitOverrides
|
||||
}
|
||||
get drivers(): typeof this.terminalDrivers {
|
||||
return this.terminalDrivers
|
||||
}
|
||||
}
|
||||
|
||||
function createRuntime() {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
const runtime = new ObservableRuntime(store)
|
||||
const ptySizes = new Map<string, { cols: number; rows: number }>()
|
||||
ptySizes.set('pty-1', { cols: 150, rows: 40 })
|
||||
ptySizes.set('pty-2', { cols: 120, rows: 35 })
|
||||
@@ -865,9 +881,9 @@ describe('mobile subscribe integration', () => {
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-b', { cols: 40, rows: 18 })
|
||||
|
||||
const pendingRestore = Reflect.get(runtime, 'pendingRestoreTimers') as Map<string, unknown>
|
||||
const pendingRestore = runtime.restoreTimers
|
||||
pendingRestore.set('pty-1', { timer: setTimeout(() => {}, 60_000), clientId: 'client-b' })
|
||||
const pendingSoft = Reflect.get(runtime, 'pendingSoftLeavers') as Map<string, unknown>
|
||||
const pendingSoft = runtime.softLeavers
|
||||
expect(pendingSoft.has('pty-1')).toBe(true)
|
||||
await runtime.reclaimTerminalForDesktop('pty-1')
|
||||
expect(pendingRestore.has('pty-1')).toBe(false)
|
||||
@@ -878,10 +894,10 @@ describe('mobile subscribe integration', () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
;(Reflect.get(runtime, 'terminalFitOverrides') as Map<string, unknown>).delete('pty-1')
|
||||
runtime.fitOverrides.delete('pty-1')
|
||||
|
||||
const pendingRestore = Reflect.get(runtime, 'pendingRestoreTimers') as Map<string, unknown>
|
||||
const pendingSoft = Reflect.get(runtime, 'pendingSoftLeavers') as Map<string, unknown>
|
||||
const pendingRestore = runtime.restoreTimers
|
||||
const pendingSoft = runtime.softLeavers
|
||||
await runtime.reclaimTerminalForDesktop('pty-1')
|
||||
expect(pendingRestore.has('pty-1')).toBe(false)
|
||||
expect(pendingSoft.has('pty-1')).toBe(false)
|
||||
@@ -891,15 +907,11 @@ describe('mobile subscribe integration', () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.handleMobileSubscribe('pty-1', 'client-a', { cols: 45, rows: 20 })
|
||||
runtime.handleMobileUnsubscribe('pty-1', 'client-a')
|
||||
;(Reflect.get(runtime, 'terminalFitOverrides') as Map<string, unknown>).delete('pty-1')
|
||||
;(
|
||||
Reflect.get(runtime, 'terminalDrivers') as {
|
||||
set: (ptyId: string, driver: { kind: 'idle' }) => void
|
||||
}
|
||||
).set('pty-1', { kind: 'idle' })
|
||||
runtime.fitOverrides.delete('pty-1')
|
||||
runtime.drivers.set('pty-1', { kind: 'idle' })
|
||||
|
||||
const pendingRestore = Reflect.get(runtime, 'pendingRestoreTimers') as Map<string, unknown>
|
||||
const pendingSoft = Reflect.get(runtime, 'pendingSoftLeavers') as Map<string, unknown>
|
||||
const pendingRestore = runtime.restoreTimers
|
||||
const pendingSoft = runtime.softLeavers
|
||||
expect(await runtime.reclaimTerminalForDesktop('pty-1')).toBe(false)
|
||||
expect(pendingRestore.has('pty-1')).toBe(false)
|
||||
expect(pendingSoft.has('pty-1')).toBe(false)
|
||||
|
||||
@@ -436,10 +436,11 @@ describe('OrcaRuntimeService', () => {
|
||||
throw new Error('onPtyData should use the PTY leaf index')
|
||||
}
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
const value = Reflect.get(target, prop, target)
|
||||
return typeof value === 'function' ? value.bind(target) : value
|
||||
}
|
||||
}) as Map<string, unknown>
|
||||
})
|
||||
|
||||
runtime.onPtyData(`pty-${targetIndex}`, 'hello indexed\n', 123)
|
||||
|
||||
|
||||
@@ -99,10 +99,11 @@ describe('mailbox pointer staging watermark', () => {
|
||||
throw new Error('SQLITE_BUSY')
|
||||
}
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
const value = Reflect.get(target, prop, receiver)
|
||||
return typeof value === 'function' ? value.bind(target) : value
|
||||
}
|
||||
}) as OrchestrationDb
|
||||
})
|
||||
|
||||
const state = new OrchestrationMailboxPointerState()
|
||||
const args = stageArgs(db, state)
|
||||
@@ -178,10 +179,11 @@ describe('mailbox pointer staging watermark', () => {
|
||||
stealNextClaim = false
|
||||
return () => false
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
const value = Reflect.get(target, prop, receiver)
|
||||
return typeof value === 'function' ? value.bind(target) : value
|
||||
}
|
||||
}) as OrchestrationDb
|
||||
})
|
||||
|
||||
const writePty = vi.fn(() => WRITE_ACCEPTED)
|
||||
const delivery = new OrchestrationMailboxPointerDelivery<never>({
|
||||
|
||||
@@ -340,17 +340,18 @@ describe('remote desktop viewer width driver', () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.updateRemoteDesktopViewer('pty-1', 'sub-A', 'viewer-A', 100, 30)
|
||||
await runtime.updateRemoteDesktopViewer('pty-1', 'sub-B', 'viewer-B', 80, 24, false)
|
||||
const layoutQueues = Reflect.get(runtime, 'layoutQueues') as Map<
|
||||
string,
|
||||
{ running: Promise<unknown>; pending: { target: { ownerSubscriptionKey?: string } }[] }
|
||||
>
|
||||
layoutQueues.set('pty-1', { running: new Promise(() => {}), pending: [] })
|
||||
const layoutQueues = runtime['layoutQueues']
|
||||
layoutQueues.set('pty-1', { running: new Promise<never>(() => {}), pending: [] })
|
||||
|
||||
void runtime.updateRemoteDesktopViewer('pty-1', 'sub-A', 'viewer-A', 90, 28)
|
||||
void runtime.claimRemoteDesktopViewer('pty-1', 'sub-B')
|
||||
|
||||
expect(
|
||||
layoutQueues.get('pty-1')?.pending.map(({ target }) => target.ownerSubscriptionKey)
|
||||
layoutQueues
|
||||
.get('pty-1')
|
||||
?.pending.map(({ target }) =>
|
||||
'ownerSubscriptionKey' in target ? target.ownerSubscriptionKey : undefined
|
||||
)
|
||||
).toEqual(['sub-A', 'sub-B'])
|
||||
layoutQueues.delete('pty-1')
|
||||
})
|
||||
@@ -358,11 +359,8 @@ describe('remote desktop viewer width driver', () => {
|
||||
it('makes a host claim join a pending disconnect reclaim', async () => {
|
||||
const { runtime } = createRuntime()
|
||||
await runtime.updateRemoteDesktopViewer('pty-1', 'sub-A', 'viewer-A', 80, 24)
|
||||
const layoutQueues = Reflect.get(runtime, 'layoutQueues') as Map<
|
||||
string,
|
||||
{ running: Promise<unknown>; pending: { waiters: unknown[] }[] }
|
||||
>
|
||||
layoutQueues.set('pty-1', { running: new Promise(() => {}), pending: [] })
|
||||
const layoutQueues = runtime['layoutQueues']
|
||||
layoutQueues.set('pty-1', { running: new Promise<never>(() => {}), pending: [] })
|
||||
|
||||
void runtime.unregisterRemoteDesktopViewer('pty-1', 'sub-A')
|
||||
void runtime.claimRemoteDesktopHost('pty-1', 150, 40)
|
||||
|
||||
@@ -47,6 +47,7 @@ function overrideAwareReceiver(
|
||||
return override.bind(facade)
|
||||
}
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: raw string|symbol pass-through; the receiver stays the target on purpose.
|
||||
return Reflect.get(target, property, proxyReceiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -35,6 +35,7 @@ it('validates large restored MRU lists with linear tab-order reads', () => {
|
||||
if (typeof key === 'string' && /^\d+$/.test(key)) {
|
||||
reads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,7 +5,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { AgentSessionJournalIdentity } from '../../shared/agent-session-journal-types'
|
||||
import { agentSessionJournalCloseRetries } from '../native-chat/agent-session-journal/journal-close-retry'
|
||||
import { createTrackedJournalOpener } from '../native-chat/agent-session-journal/journal-store-test-open'
|
||||
import type { AgentSessionJournal } from '../native-chat/agent-session-journal/journal-store'
|
||||
import type {
|
||||
AgentSessionClaimStatus,
|
||||
AgentSessionExecutionLocation,
|
||||
@@ -346,6 +345,7 @@ describe('a teardown that fails is retried by the next stop', () => {
|
||||
const flaky = new Proxy(real, {
|
||||
get(target, property, receiver) {
|
||||
if (property !== 'close') {
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
return async () => {
|
||||
@@ -356,7 +356,7 @@ describe('a teardown that fails is retried by the next stop', () => {
|
||||
await target.close()
|
||||
}
|
||||
}
|
||||
}) as AgentSessionJournal
|
||||
})
|
||||
await agentSessionJournalCloseRetries.closeOrRetain(flaky)
|
||||
|
||||
// The host's teardown runs the registry retry, so this stop surfaces it.
|
||||
|
||||
@@ -104,6 +104,7 @@ describe('skill bundle installation', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
const value = Reflect.get(target, property, target) as unknown
|
||||
return typeof value === 'function' ? value.bind(target) : value
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ it.each(['skill-install-cancelled', 'skill-install-filesystem-failed'])(
|
||||
if (typeof key === 'string' && /^\d+$/.test(key)) {
|
||||
reads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import type * as NodeFsPromises from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SkillUploadRetainedPaths } from './skill-upload-retained-paths'
|
||||
import { SkillUploadSessionService } from './skill-upload-session-service'
|
||||
|
||||
const roots: string[] = []
|
||||
@@ -30,10 +31,6 @@ vi.mock('node:fs/promises', async (importOriginal) => {
|
||||
}
|
||||
})
|
||||
|
||||
type RetainedPathCleanup = {
|
||||
removeFailedCleanup(path: string): Promise<void>
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
vi.useRealTimers()
|
||||
openGate.release = null
|
||||
@@ -51,8 +48,8 @@ function identity(bytes: Buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
function retainedPathCleanup(service: SkillUploadSessionService): RetainedPathCleanup {
|
||||
return Reflect.get(service, 'retainedPaths') as RetainedPathCleanup
|
||||
function retainedPathCleanup(service: SkillUploadSessionService): SkillUploadRetainedPaths {
|
||||
return service['retainedPaths']
|
||||
}
|
||||
|
||||
async function stagedArchiveCount(uploads: string): Promise<number> {
|
||||
|
||||
@@ -146,6 +146,7 @@ export function createUpdaterMocks(): UpdaterMocks {
|
||||
const loadedGeneration = currentGeneration
|
||||
return new Proxy(autoUpdaterMock, {
|
||||
get(target, property) {
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: raw string|symbol pass-through; the receiver stays the target on purpose.
|
||||
const value = Reflect.get(target, property)
|
||||
if (loadedGeneration === currentGeneration || typeof value !== 'function') {
|
||||
return value
|
||||
@@ -155,7 +156,7 @@ export function createUpdaterMocks(): UpdaterMocks {
|
||||
set(target, property, value) {
|
||||
return loadedGeneration === currentGeneration ? Reflect.set(target, property, value) : true
|
||||
}
|
||||
}) as AutoUpdaterMock
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('summarizeWorkspaceSpaceRows', () => {
|
||||
) {
|
||||
reads[property] += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -75,7 +75,12 @@ export function normalizeRetirableGeneratedName(name: string): string | null {
|
||||
/** A sparse create error carries this marker only when its rollback also failed, leaving the path
|
||||
* occupied even though creation rejected. */
|
||||
export function failedWorktreeCreationNeedsRetirement(error: unknown): boolean {
|
||||
return typeof error === 'object' && error !== null && Reflect.get(error, 'cleanupFailed') === true
|
||||
return (
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
'cleanupFailed' in error &&
|
||||
error.cleanupFailed === true
|
||||
)
|
||||
}
|
||||
|
||||
async function getRetirementProbePath(
|
||||
|
||||
@@ -45,7 +45,9 @@ function readAgents(params: unknown): AgentHookTarget[] {
|
||||
|
||||
function readClaudeVersion(params: unknown): string | undefined {
|
||||
const raw =
|
||||
params !== null && typeof params === 'object' ? Reflect.get(params, 'claudeVersion') : null
|
||||
params !== null && typeof params === 'object' && 'claudeVersion' in params
|
||||
? params.claudeVersion
|
||||
: null
|
||||
return parseClaudeCliVersion(typeof raw === 'string' ? raw : null) ?? undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ function countingRows(rows: ProcessTableRow[]): {
|
||||
if (typeof key === 'string' && /^\d+$/.test(key)) {
|
||||
reads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -104,6 +104,7 @@ describe('RelayPtySourceCreditLedger', () => {
|
||||
if (typeof property === 'string' && /^\d+$/.test(property)) {
|
||||
indexedReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
+1
@@ -109,6 +109,7 @@ describe('buildDashboardSnapshot orchestration routing', () => {
|
||||
if (typeof key === 'string' && Object.hasOwn(target, key)) {
|
||||
runtimeValueReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -113,6 +113,7 @@ describe('useAgentRowConversationName', () => {
|
||||
if (typeof property === 'string' && /^\d+$/.test(property)) {
|
||||
tabReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ export function createTiptapMarkedFacade(): typeof marked {
|
||||
return facade
|
||||
}
|
||||
default:
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ it('selects a primary team without pairwise membership checks or sorting all tea
|
||||
if (typeof key === 'string' && /^\d+$/.test(key)) {
|
||||
reads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ beforeEach(() => {
|
||||
? element.getAttribute('data-state') === 'closed'
|
||||
? 'exit'
|
||||
: 'enter'
|
||||
: Reflect.get(target, property)
|
||||
: // oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: raw string|symbol pass-through; the receiver stays the target on purpose.
|
||||
Reflect.get(target, property)
|
||||
})
|
||||
}
|
||||
return style
|
||||
|
||||
@@ -182,6 +182,7 @@ describe('getActiveChecksStatus caching', () => {
|
||||
{
|
||||
get(target, prop, receiver) {
|
||||
reads.add(prop)
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, prop, receiver)
|
||||
},
|
||||
has(target, prop) {
|
||||
|
||||
+3
-2
@@ -61,9 +61,10 @@ describe('parent PR checks projection selector', () => {
|
||||
const observedCache = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, property, receiver) => {
|
||||
get: (target, property) => {
|
||||
cacheRead(property)
|
||||
return Reflect.get(target, property, receiver)
|
||||
const entries: Record<string | symbol, unknown> = target
|
||||
return entries[property]
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ function trackCacheReads<K extends ReviewCacheName>(
|
||||
): ReviewCacheState[K] {
|
||||
return new Proxy(state[cacheName], {
|
||||
get: (target, property, receiver) => {
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
const value = Reflect.get(target, property, receiver)
|
||||
if (typeof property === 'string') {
|
||||
dependencies.push({ cacheName, key: property, value })
|
||||
@@ -41,7 +42,7 @@ function dependenciesAreCurrent(
|
||||
): boolean {
|
||||
return dependencies.every(
|
||||
({ cacheName, key, value }) =>
|
||||
state[cacheName] === previousState[cacheName] || Reflect.get(state[cacheName], key) === value
|
||||
state[cacheName] === previousState[cacheName] || state[cacheName][key] === value
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -364,6 +364,7 @@ describe('selectRuntimeAgentOrchestrationBatch', () => {
|
||||
if (typeof key === 'string' && Object.hasOwn(target, key)) {
|
||||
runtimeValueReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
})
|
||||
@@ -456,6 +457,7 @@ describe('selectRuntimeAgentOrchestrationBatch', () => {
|
||||
if (typeof key === 'string' && Object.hasOwn(target, key)) {
|
||||
runtimeValueReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
})
|
||||
@@ -636,6 +638,7 @@ describe('selectRuntimeAgentOrchestrationBatch live-map churn', () => {
|
||||
if (typeof key === 'string') {
|
||||
reads.push(key)
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(source, key, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -312,6 +312,7 @@ describe('selectWorktreeAgentOrchestration', () => {
|
||||
if (typeof key === 'string') {
|
||||
onRead()
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(source, key, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -356,6 +356,7 @@ describe('hasUnreadAgentCompletionForTerminalTab', () => {
|
||||
ownKeys,
|
||||
get: (target, property, receiver) => {
|
||||
valueReads += 1
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
+1
@@ -218,6 +218,7 @@ describe('parked terminal watcher synchronization', () => {
|
||||
if (typeof property === 'string') {
|
||||
harness.reconciliationPtyReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -79,6 +79,7 @@ describe('terminal provider snapshot capabilities', () => {
|
||||
if (typeof property === 'string' && /^\d+$/.test(property)) {
|
||||
indexedReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -118,8 +118,8 @@ describe('useIpcEvents rate-limit hydration', () => {
|
||||
const makeEvents = (target: Record<string, unknown> = {}): Record<string, unknown> =>
|
||||
new Proxy(target, {
|
||||
get: (namespace, prop) => {
|
||||
if (prop in namespace) {
|
||||
return Reflect.get(namespace, prop)
|
||||
if (typeof prop === 'string' && prop in namespace) {
|
||||
return namespace[prop]
|
||||
}
|
||||
return () => () => {}
|
||||
}
|
||||
|
||||
@@ -281,10 +281,11 @@ describe('useIpcEvents updater integration', () => {
|
||||
}))
|
||||
vi.doMock('@/lib/zoom-events', () => ({ dispatchZoomLevelChanged: vi.fn() }))
|
||||
|
||||
const makeEvents = (target: Record<string, unknown> = {}): Record<string, unknown> =>
|
||||
const makeEvents = (
|
||||
target: Record<string | symbol, unknown> = {}
|
||||
): Record<string | symbol, unknown> =>
|
||||
new Proxy(target, {
|
||||
get: (namespace, prop) =>
|
||||
prop in namespace ? Reflect.get(namespace, prop) : () => () => {}
|
||||
get: (namespace, prop) => (prop in namespace ? namespace[prop] : () => () => {})
|
||||
})
|
||||
|
||||
vi.stubGlobal('window', {
|
||||
|
||||
@@ -180,8 +180,8 @@ describe('useIpcEvents zoom routing', () => {
|
||||
const makeEvents = (target: Record<string, unknown> = {}): Record<string, unknown> =>
|
||||
new Proxy(target, {
|
||||
get: (namespace, prop) => {
|
||||
if (prop in namespace) {
|
||||
return Reflect.get(namespace, prop)
|
||||
if (typeof prop === 'string' && prop in namespace) {
|
||||
return namespace[prop]
|
||||
}
|
||||
return () => () => {}
|
||||
}
|
||||
@@ -326,8 +326,8 @@ describe('useIpcEvents zoom routing', () => {
|
||||
const makeEvents = (target: Record<string, unknown> = {}): Record<string, unknown> =>
|
||||
new Proxy(target, {
|
||||
get: (namespace, prop) => {
|
||||
if (prop in namespace) {
|
||||
return Reflect.get(namespace, prop)
|
||||
if (typeof prop === 'string' && prop in namespace) {
|
||||
return namespace[prop]
|
||||
}
|
||||
return () => () => {}
|
||||
}
|
||||
|
||||
@@ -367,9 +367,10 @@ describe('resolveCodexPaneSelectionLane', () => {
|
||||
if (property === 'worktreesByRepo') {
|
||||
throw new Error('state read blew up')
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: raw string|symbol pass-through; the receiver stays the target on purpose.
|
||||
return Reflect.get(target, property)
|
||||
}
|
||||
}) as LaneState
|
||||
})
|
||||
// Why: this call sits outside the scan's per-pane failure guard, so a throw
|
||||
// would lose the notice for every pane in the batch, not just this one.
|
||||
expect(
|
||||
|
||||
@@ -111,6 +111,7 @@ export class TerminalLigaturesAddon extends LigaturesAddon {
|
||||
target.refresh(start, end)
|
||||
}
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
const value = Reflect.get(target, property, target) as unknown
|
||||
return typeof value === 'function' ? value.bind(target) : value
|
||||
}
|
||||
|
||||
@@ -210,6 +210,7 @@ describe('session write subscriber allocation', () => {
|
||||
if (typeof property === 'string') {
|
||||
read.add(property)
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -20,8 +20,7 @@ function countCollectionReads<T>(items: readonly T[]): {
|
||||
get(array, property) {
|
||||
if (property === 'map' || property === 'flatMap') {
|
||||
counters[property] += 1
|
||||
const method = Reflect.get(array, property) as (...args: unknown[]) => unknown
|
||||
return method.bind(array)
|
||||
return array[property].bind(array)
|
||||
}
|
||||
if (property === Symbol.iterator) {
|
||||
counters.iterator += 1
|
||||
@@ -30,6 +29,7 @@ function countCollectionReads<T>(items: readonly T[]): {
|
||||
if (property === 'length') {
|
||||
counters.length += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(array, property)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -695,6 +695,7 @@ describe('selectFloatingWorkspaceHasUnread', () => {
|
||||
if (typeof property === 'string') {
|
||||
terminalUnreadReads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ describe('appendOwnedTabIdsToGroups', () => {
|
||||
if (typeof property === 'string' && /^\d+$/.test(property)) {
|
||||
reads++
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -136,6 +136,7 @@ describe('whole-session workspace tab-model reconciliation', () => {
|
||||
if (property === 'filter') {
|
||||
scans += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -73,6 +73,7 @@ describe('terminal tab owner index', () => {
|
||||
if (typeof property === 'string' && property.startsWith('wt-')) {
|
||||
bucketVisits += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -140,6 +140,7 @@ describe('terminal tab title batches', () => {
|
||||
if (typeof property === 'string' && property.startsWith('wt-')) {
|
||||
bucketVisits += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -42,6 +42,7 @@ function countOpenFileScans(
|
||||
return target.filter(predicate)
|
||||
}
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ export function withFallback<T extends object>(target: T, path: string[]): T {
|
||||
return new Proxy(target, {
|
||||
get(current, property, receiver) {
|
||||
if (property in current) {
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
const value = Reflect.get(current, property, receiver) as unknown
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
return withFallback(value as object, [...path, String(property)])
|
||||
|
||||
@@ -78,7 +78,8 @@ describe('web preload API composition', () => {
|
||||
'telemetryAcknowledgeBanner'
|
||||
])
|
||||
expect(Object.keys(globals.window.api.projects)).toEqual([])
|
||||
expect(Reflect.get(globals.window.api.projects, 'then')).toBeUndefined()
|
||||
const projects: Record<string, unknown> = globals.window.api.projects
|
||||
expect(projects.then).toBeUndefined()
|
||||
})
|
||||
|
||||
it('snapshots E2E config before runtime storage initialization', async () => {
|
||||
|
||||
@@ -102,7 +102,7 @@ describe('web preload runtime calls', () => {
|
||||
if (!(rejection instanceof Error)) {
|
||||
throw new Error('Expected a domain Error rejection')
|
||||
}
|
||||
expect(Reflect.get(rejection, 'code')).toBe('repo_unavailable')
|
||||
expect('code' in rejection ? rejection.code : undefined).toBe('repo_unavailable')
|
||||
expect(
|
||||
JSON.parse(globals.storage.getItem('orca.web.runtimeEnvironment.v1') ?? '{}')
|
||||
).toMatchObject({ runtimeId: 'runtime-domain-failure' })
|
||||
|
||||
@@ -210,6 +210,7 @@ describe('projectAutomationList', () => {
|
||||
if (property === 'map' || property === 'filter') {
|
||||
collectionMethodReads.push(String(property))
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -22,6 +22,7 @@ it('retires exhausted host buckets from subsequent listing rounds', () => {
|
||||
if (typeof key === 'string' && /^\d+$/.test(key)) {
|
||||
reads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy `get` trap: only Reflect.get forwards a raw string|symbol key with the proxy receiver.
|
||||
return Reflect.get(target, key, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ describe('PR bot author override normalization', () => {
|
||||
if (typeof property === 'string' && /^\d+$/.test(property)) {
|
||||
reads += 1
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -69,9 +69,9 @@ describe('SearchSubprocessLineAccumulator', () => {
|
||||
}
|
||||
|
||||
expect(accepted).toBe(true)
|
||||
expect(Reflect.get(parser, 'buffer')).toBeInstanceOf(Buffer)
|
||||
expect(parser.retainedCapacityBytes()).toBeGreaterThanOrEqual(200_000)
|
||||
expect(parser.finish()).toBe('x'.repeat(200_000))
|
||||
expect(Reflect.get(parser, 'buffer')).toBeNull()
|
||||
expect(parser.retainedCapacityBytes()).toBeNull()
|
||||
})
|
||||
|
||||
it('rejects invalid byte limits', () => {
|
||||
|
||||
@@ -65,6 +65,11 @@ export class SearchSubprocessLineAccumulator {
|
||||
this.bytes = 0
|
||||
}
|
||||
|
||||
/** Capacity of the retained growable buffer, or null once it has been released. */
|
||||
retainedCapacityBytes(): number | null {
|
||||
return this.buffer?.length ?? null
|
||||
}
|
||||
|
||||
private append(segment: Buffer): void {
|
||||
const requiredBytes = this.bytes + segment.length
|
||||
if (!this.buffer || this.buffer.length < requiredBytes) {
|
||||
|
||||
@@ -198,6 +198,7 @@ export function createHostTerminalRuntimeStub(
|
||||
}
|
||||
return () => undefined
|
||||
}
|
||||
// oxlint-disable-next-line anti-slop/no-reflect-get -- Proxy get trap default forward.
|
||||
return Reflect.get(target, property, receiver)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -69,10 +69,10 @@ type DispatcherModule = {
|
||||
function registeredMethodNames(methods: readonly unknown[]): string[] {
|
||||
return methods
|
||||
.flatMap((method) => {
|
||||
if (!method || typeof method !== 'object') {
|
||||
if (!method || typeof method !== 'object' || !('name' in method)) {
|
||||
return []
|
||||
}
|
||||
const name = Reflect.get(method, 'name')
|
||||
const { name } = method
|
||||
return typeof name === 'string' ? [name] : []
|
||||
})
|
||||
.sort()
|
||||
|
||||
@@ -66,13 +66,24 @@ type TransitionFrame = {
|
||||
targetSelected: boolean
|
||||
}
|
||||
|
||||
declare global {
|
||||
// oxlint-disable-next-line typescript-eslint/consistent-type-definitions -- declaration merging requires interface
|
||||
interface Window {
|
||||
// Per-provider capture buffers written by startTransitionCapture below.
|
||||
__githubUrlTransitionFrames?: TransitionFrame[]
|
||||
__gitlabUrlTransitionFrames?: TransitionFrame[]
|
||||
}
|
||||
}
|
||||
|
||||
type TransitionFrameKey = '__githubUrlTransitionFrames' | '__gitlabUrlTransitionFrames'
|
||||
|
||||
function pasteChord(): string {
|
||||
return process.platform === 'darwin' ? 'Meta+V' : 'Control+V'
|
||||
}
|
||||
|
||||
async function startTransitionCapture(
|
||||
page: Page,
|
||||
frameKey: string,
|
||||
frameKey: TransitionFrameKey,
|
||||
wrongTitle: string,
|
||||
targetTitle: string
|
||||
): Promise<void> {
|
||||
@@ -95,20 +106,29 @@ async function startTransitionCapture(
|
||||
requestAnimationFrame(capture)
|
||||
}
|
||||
}
|
||||
Reflect.set(window, frameKey, frames)
|
||||
window[frameKey] = frames
|
||||
capture()
|
||||
},
|
||||
{ frameKey, frameLimit: TRANSITION_FRAME_LIMIT, wrongTitle, targetTitle }
|
||||
)
|
||||
}
|
||||
|
||||
async function readTransitionFrames(page: Page, frameKey: string): Promise<TransitionFrame[]> {
|
||||
return page.evaluate((key) => Reflect.get(window, key) as TransitionFrame[], frameKey)
|
||||
async function readTransitionFrames(
|
||||
page: Page,
|
||||
frameKey: TransitionFrameKey
|
||||
): Promise<TransitionFrame[]> {
|
||||
return page.evaluate((key) => {
|
||||
const frames = window[key]
|
||||
if (!frames) {
|
||||
throw new Error(`Transition capture ${key} was never installed`)
|
||||
}
|
||||
return frames
|
||||
}, frameKey)
|
||||
}
|
||||
|
||||
async function expectLookupHeldWithoutStaleRow(
|
||||
page: Page,
|
||||
frameKey: string,
|
||||
frameKey: TransitionFrameKey,
|
||||
targetUrl: string,
|
||||
wrongOption: Locator,
|
||||
targetOption: Locator
|
||||
@@ -125,7 +145,7 @@ async function expectLookupHeldWithoutStaleRow(
|
||||
|
||||
async function expectExactTargetAfterLookup(
|
||||
page: Page,
|
||||
frameKey: string,
|
||||
frameKey: TransitionFrameKey,
|
||||
targetUrl: string,
|
||||
targetOption: Locator
|
||||
): Promise<void> {
|
||||
@@ -268,7 +288,7 @@ test('a pasted GitHub URL never selects a stale cached issue', async ({
|
||||
})
|
||||
await expect(wrongOption).toBeVisible()
|
||||
|
||||
const frameKey = '__githubUrlTransitionFrames'
|
||||
const frameKey: TransitionFrameKey = '__githubUrlTransitionFrames'
|
||||
await startTransitionCapture(orcaPage, frameKey, WRONG_TITLE, TARGET_TITLE)
|
||||
|
||||
await orcaPage.evaluate((text) => window.api.ui.writeClipboardText(text), TARGET_URL)
|
||||
@@ -317,7 +337,7 @@ test('a pasted GitLab URL never selects a stale cached merge request', async ({
|
||||
})
|
||||
await expect(wrongOption).toBeVisible()
|
||||
|
||||
const frameKey = '__gitlabUrlTransitionFrames'
|
||||
const frameKey: TransitionFrameKey = '__gitlabUrlTransitionFrames'
|
||||
await startTransitionCapture(orcaPage, frameKey, GITLAB_WRONG_TITLE, GITLAB_TARGET_TITLE)
|
||||
|
||||
await orcaPage.evaluate((text) => window.api.ui.writeClipboardText(text), GITLAB_TARGET_URL)
|
||||
|
||||
@@ -25,6 +25,14 @@ const LINEAR_ISSUE: LinearIssue = {
|
||||
updatedAt: '2026-08-12T00:00:00.000Z'
|
||||
}
|
||||
|
||||
declare global {
|
||||
// oxlint-disable-next-line typescript-eslint/consistent-type-definitions -- declaration merging requires interface
|
||||
interface Window {
|
||||
// Set by the fixture below while a Linear lookup is deliberately held open.
|
||||
__orcaTestReleaseLinearLookup?: () => void
|
||||
}
|
||||
}
|
||||
|
||||
function pasteChord(): string {
|
||||
return process.platform === 'darwin' ? 'Meta+V' : 'Control+V'
|
||||
}
|
||||
@@ -86,7 +94,7 @@ async function installLinearFixture(
|
||||
|
||||
async function releaseHeldLinearLookup(page: Page): Promise<void> {
|
||||
await page.evaluate(() => {
|
||||
const release = Reflect.get(window, '__orcaTestReleaseLinearLookup')
|
||||
const release = window.__orcaTestReleaseLinearLookup
|
||||
if (typeof release !== 'function') {
|
||||
throw new Error('Linear lookup is not held')
|
||||
}
|
||||
@@ -131,9 +139,7 @@ test.describe('Linear URL workspace entry', () => {
|
||||
await pasteLinearUrl(orcaPage, input)
|
||||
await expect
|
||||
.poll(() =>
|
||||
orcaPage.evaluate(
|
||||
() => typeof Reflect.get(window, '__orcaTestReleaseLinearLookup') === 'function'
|
||||
)
|
||||
orcaPage.evaluate(() => typeof window.__orcaTestReleaseLinearLookup === 'function')
|
||||
)
|
||||
.toBe(true)
|
||||
await input.press('Enter')
|
||||
|
||||
@@ -7,6 +7,11 @@ import { runProcess } from '../../src/shared/child-process/run-process'
|
||||
|
||||
test.use({ seedTestRepo: false })
|
||||
|
||||
declare global {
|
||||
// Resolved by the main-process gate this spec installs around the group-create response.
|
||||
var __releaseGroupCreateResponse: (() => void) | undefined
|
||||
}
|
||||
|
||||
for (const delayCreateResponse of [false, true]) {
|
||||
test(`created groups survive sidebar expansion (${delayCreateResponse ? 'refresh first' : 'ordinary timing'})`, async ({
|
||||
orcaPage,
|
||||
@@ -97,7 +102,7 @@ for (const delayCreateResponse of [false, true]) {
|
||||
.toBe(true)
|
||||
} finally {
|
||||
await electronApp.evaluate(() => {
|
||||
const release = Reflect.get(globalThis, '__releaseGroupCreateResponse')
|
||||
const release = globalThis.__releaseGroupCreateResponse
|
||||
if (typeof release !== 'function') {
|
||||
throw new Error('Group create response gate unavailable')
|
||||
}
|
||||
|
||||
@@ -17,6 +17,14 @@ type RowRemovalFrame = {
|
||||
targetExists: boolean
|
||||
}
|
||||
|
||||
declare global {
|
||||
// oxlint-disable-next-line typescript-eslint/consistent-type-definitions -- declaration merging requires interface
|
||||
interface Window {
|
||||
// Frame sampling started in the page and awaited once the removal animation settles.
|
||||
__activeDeleteRowRemovalFrames?: Promise<RowRemovalFrame[]>
|
||||
}
|
||||
}
|
||||
|
||||
async function pauseForVisualProof(page: Page): Promise<void> {
|
||||
if (process.env.ORCA_E2E_RECORD_VIDEO === '1') {
|
||||
await page.waitForTimeout(VISUAL_PROOF_PAUSE_MS)
|
||||
@@ -215,7 +223,7 @@ async function startRowRemovalSampling(
|
||||
|
||||
async function finishRowRemovalSampling(page: Page): Promise<RowRemovalFrame[]> {
|
||||
return page.evaluate(async () => {
|
||||
const pending = Reflect.get(window, '__activeDeleteRowRemovalFrames')
|
||||
const pending = window.__activeDeleteRowRemovalFrames
|
||||
if (!(pending instanceof Promise)) {
|
||||
throw new Error('Row removal sampling was not started')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user