refactor: restrict reflective access (#48860)

This commit is contained in:
Dax
2026-09-13 17:08:43 -04:00
committed by GitHub
parent b901cb28af
commit 1eeaaab7a6
51 changed files with 135 additions and 97 deletions
@@ -1,4 +1,4 @@
name: typecheck
name: check
on:
push:
@@ -8,7 +8,7 @@ on:
workflow_dispatch:
jobs:
typecheck:
check:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout repository
@@ -17,5 +17,5 @@ jobs:
- name: Setup Bun
uses: ./.github/actions/setup-bun
- name: Run typecheck
run: bun typecheck
- name: Run checks
run: bun run check
+1 -1
View File
@@ -17,4 +17,4 @@ if (process.versions.bun !== expectedBunVersion) {
console.warn(`Warning: Bun version ${process.versions.bun} differs from expected ${expectedBunVersion}`);
}
'
bun typecheck
bun run check
+14 -37
View File
@@ -1,45 +1,22 @@
{
"$schema": "https://raw.githubusercontent.com/nicolo-ribaudo/oxc-project.github.io/refs/heads/json-schema/src/public/.oxlintrc.schema.json",
"options": {
"typeAware": true
},
"categories": {
"suspicious": "warn"
"correctness": "off",
"suspicious": "off",
"pedantic": "off",
"perf": "off",
"style": "off",
"restriction": "off",
"nursery": "off"
},
"rules": {
"typescript/no-base-to-string": "warn",
// Effect uses `function*` with Effect.gen/Effect.fnUntraced that don't always yield
"require-yield": "off",
// SolidJS uses `let ref: T | undefined` for JSX ref bindings assigned at runtime
"no-unassigned-vars": "off",
// SolidJS tracks reactive deps by reading properties inside createEffect
"no-unused-expressions": "off",
// Intentional control char matching (ANSI escapes, null byte sanitization)
"no-control-regex": "off",
// SST and plugin tools require triple-slash references
"triple-slash-reference": "off",
// Suspicious category: suppress noisy rules
// Effect's nested function* closures inherently shadow outer scope
"no-shadow": "off",
// Namespace-heavy codebase makes this too noisy
"unicorn/consistent-function-scoping": "off",
// Opinionated — .sort()/.reverse() mutation is fine in this codebase
"unicorn/no-array-sort": "off",
"unicorn/no-array-reverse": "off",
// Not relevant — this isn't a DOM event handler codebase
"unicorn/prefer-add-event-listener": "off",
// Bundler handles module resolution
"unicorn/require-module-specifiers": "off",
// postMessage target origin not relevant for this codebase
"unicorn/require-post-message-target-origin": "off",
// Side-effectful constructors are intentional in some places
"no-new": "off",
// Type-aware: catch unhandled promises
"typescript/no-floating-promises": "warn",
// Warn when spreading non-plain objects (Headers, class instances, etc.)
"typescript/no-misused-spread": "warn"
"no-restricted-globals": [
"error",
{
"name": "Reflect",
"message": "Use typed property access or direct invocation. Suppress this rule only for genuine reflection."
}
]
},
"ignorePatterns": ["**/node_modules", "**/dist", "**/.build", "**/.sst", "**/*.d.ts", "**/sdk.gen.ts"]
}
+3 -2
View File
@@ -170,9 +170,10 @@ const table = sqliteTable("session", {
- Test actual implementation, do not duplicate logic into tests
- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package directories such as `packages/core`.
## Type Checking
## Checks
- Always run `bun typecheck` from package directories (for example, `packages/core`), never `tsc` directly.
- Run `bun run check` from the repository root as the canonical full lint and type-check verification.
- During focused iteration, run `bun typecheck` from the affected package directory (for example, `packages/core`). Never run `tsc` directly.
## V2 Session Core
+1
View File
@@ -24,6 +24,7 @@
"test:lint-rules": "ast-grep test -c script/ast-grep/sgconfig.yml",
"test:effect-simplification-rules": "ast-grep test -c script/ast-grep/effect-simplifications/sgconfig.yml",
"typecheck": "bun turbo typecheck --concurrency=3",
"check": "bun run lint && bun run typecheck",
"typecheck:profile": "bun script/profile-typecheck.ts",
"typecheck:profile:packages": "bun script/profile-typecheck-packages.ts",
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
+11
View File
@@ -325,6 +325,7 @@ describe("provider package entrypoints", () => {
test("requires an Anthropic-compatible base URL at runtime", async () => {
const AnthropicCompatible = await import("@opencode/ai/providers/anthropic-compatible")
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally bypasses static required-option checks.
Reflect.apply(AnthropicCompatible.model, undefined, ["compatible-model", { apiKey: "fixture" }]),
).toThrow(configuration("anthropic-compatible", "Anthropic-compatible providers require a baseURL"))
})
@@ -333,6 +334,7 @@ describe("provider package entrypoints", () => {
const Anthropic = await import("@opencode/ai/providers/anthropic")
const AnthropicCompatible = await import("@opencode/ai/providers/anthropic-compatible")
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes a statically invalid option combination.
Reflect.apply(AnthropicCompatible.model, undefined, [
"compatible-model",
{
@@ -343,6 +345,7 @@ describe("provider package entrypoints", () => {
]),
).toThrow(configuration("anthropic-compatible", "Anthropic-compatible apiKey cannot be combined with authToken"))
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes a statically invalid option combination.
Reflect.apply(Anthropic.model, undefined, ["claude-sonnet-4-6", { apiKey: "fixture", authToken: "token" }]),
).toThrow(configuration("anthropic", "Anthropic apiKey cannot be combined with authToken"))
})
@@ -490,11 +493,13 @@ describe("provider package entrypoints", () => {
const GoogleVertexResponses = await import("@opencode/ai/providers/google-vertex/responses")
const Providers = await import("@opencode/ai/providers")
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes a statically invalid option combination.
Reflect.apply(GoogleVertex.model, undefined, [
"gemini-3.5-flash",
{ accessToken: "token", apiKey: "fixture", project: "vertex-project" },
]),
).toThrow(configuration("google-vertex", "Google Vertex apiKey cannot be combined with accessToken or auth"))
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes a statically invalid option combination.
const configured = Reflect.apply(GoogleVertex.configure, undefined, [
{ accessToken: "token", auth: {}, project: "vertex-project" },
])
@@ -502,34 +507,40 @@ describe("provider package entrypoints", () => {
configuration("google-vertex", "Google Vertex accessToken cannot be combined with auth"),
)
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes an unsupported authentication option.
Reflect.apply(GoogleVertexMessages.model, undefined, [
"claude-sonnet-4-6",
{ apiKey: "fixture", project: "vertex-project" },
]),
).toThrow(configuration("google-vertex", "Google Vertex Messages does not support API keys"))
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes an unsupported authentication option.
Reflect.apply(Providers.GoogleVertexMessages.configure, undefined, [
{ apiKey: "fixture", project: "vertex-project" },
]),
).toThrow(configuration("google-vertex", "Google Vertex Messages does not support API keys"))
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes an unsupported authentication option.
Reflect.apply(GoogleVertexChat.model, undefined, [
"deepseek-ai/deepseek-v3.2-maas",
{ apiKey: "fixture", project: "vertex-project" },
]),
).toThrow(configuration("google-vertex", "Google Vertex Chat does not support API keys"))
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes an unsupported authentication option.
Reflect.apply(Providers.GoogleVertexChat.configure, undefined, [
{ apiKey: "fixture", project: "vertex-project" },
]),
).toThrow(configuration("google-vertex", "Google Vertex Chat does not support API keys"))
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes an unsupported authentication option.
Reflect.apply(GoogleVertexResponses.model, undefined, [
"xai/grok-4.20-reasoning",
{ apiKey: "fixture", project: "vertex-project" },
]),
).toThrow(configuration("google-vertex", "Google Vertex Responses does not support API keys"))
expect(() =>
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes an unsupported authentication option.
Reflect.apply(Providers.GoogleVertexResponses.configure, undefined, [
{ apiKey: "fixture", project: "vertex-project" },
]),
+8 -6
View File
@@ -120,16 +120,16 @@ describe("LLMClient tools", () => {
const second = bodies[1]
if (!second || typeof second !== "object") throw new Error("Expected second request body")
const messages = Reflect.get(second, "messages")
const tools = Reflect.get(second, "tools")
const messages = "messages" in second ? second.messages : undefined
const tools = "tools" in second ? second.tools : undefined
expect(Reflect.get(second, "max_completion_tokens")).toBe(50)
expect(Reflect.get(second, "tool_choice")).toBe("auto")
expect("max_completion_tokens" in second ? second.max_completion_tokens : undefined).toBe(50)
expect("tool_choice" in second ? second.tool_choice : undefined).toBe("auto")
expect(tools).toHaveLength(1)
expect(
Array.isArray(messages)
? messages.map((message) =>
message && typeof message === "object" ? Reflect.get(message, "role") : undefined,
message && typeof message === "object" && "role" in message ? message.role : undefined,
)
: undefined,
).toEqual(["user", "assistant", "tool"])
@@ -398,7 +398,9 @@ describe("LLMClient tools", () => {
required: ["temperature", "condition"],
additionalProperties: false,
})
expect(Reflect.get(Reflect.get(typed?.outputSchema ?? {}, "properties") as object, "temperature")).toBeDefined()
const properties =
typed?.outputSchema && "properties" in typed.outputSchema ? typed.outputSchema.properties : undefined
expect(properties && "temperature" in properties ? properties.temperature : undefined).toBeDefined()
expect(dynamic?.outputSchema).toEqual(schema)
})
@@ -63,20 +63,20 @@ for (const size of ["typical", "large"]) {
const source = page.locator(`[data-timeline-part-id="${sourcePart}"] [data-component="markdown"]`)
await expect(source).toHaveAttribute("data-markdown-ready", "")
await page.locator(`[data-slot="titlebar-tabs"] a[href="${stressSessionHref(fixture.targetID)}"]`).click()
await page.waitForFunction(() => Reflect.get(window, "markdownGate").held)
await page.waitForFunction(() => window.markdownGate.held)
await expect(page.locator(`[data-timeline-part-id="${targetPart}"]`)).toBeAttached()
const cdp = await page.context().newCDPSession(page)
await cdp.send("Performance.enable")
const before = await cdp.send("Performance.getMetrics")
await page.evaluate(() => Reflect.get(window, "markdownGate").arm())
await page.evaluate(() => window.markdownGate.arm())
await page.locator(`[data-slot="titlebar-tabs"] a[href="${stressSessionHref(fixture.sourceID)}"]`).click()
await expect(source).toHaveAttribute("data-markdown-ready", "")
await expect(source.getByRole("heading", { name: "Current destination" })).toBeVisible()
await expect(page.locator(`[data-timeline-part-id="${targetPart}"]`)).toHaveCount(0)
await page.waitForFunction(() => Reflect.get(window, "markdownGate").settled > 0)
await page.waitForFunction(() => window.markdownGate.settled > 0)
const after = await cdp.send("Performance.getMetrics")
const stats = await page.evaluate(() => {
const value = Reflect.get(window, "markdownGate")
const value = window.markdownGate
return {
admitted: value.admitted,
responses: value.responses,
@@ -4,6 +4,23 @@ import type {
MarkdownWorkerResponse,
} from "../../../../session-ui/src/components/markdown-worker-protocol"
declare global {
interface Window {
markdownGate: {
admitted: number
responses: number
held: boolean
started: number
ready: number
released: number
settled: number
sanitizeCalls: number
sanitizeChars: number
arm: () => void
}
}
}
export async function installMarkdownGate(
page: Page,
input: { answer: string; sourcePart: string; targetPart: string; href: string },
@@ -51,7 +51,7 @@ Terminal.prototype.open = function (element) {
context.drawImage = function (...args: unknown[]) {
probe.draws++
if (hidden) probe.hiddenDraws++
Reflect.apply(draw, this, args)
draw.apply(this, args)
}
}
}
@@ -74,7 +74,7 @@ for (const scenario of ["visible-output", "hidden-output", "full-scrollback-tear
window.terminalProbe.draws++
if (!this.canvas.checkVisibility()) window.terminalProbe.hiddenDraws++
}
Reflect.apply(fill, this, args)
fill.apply(this, args)
}
})
}
@@ -136,7 +136,7 @@ export async function installTimelineStreamProbe(
}
if (state.scroll.lastCallFrame === state.scroll.frame) state.scroll.sameFrameCalls += 1
state.scroll.lastCallFrame = state.scroll.frame
Reflect.apply(scrollTo, this, typeof first === "number" ? [first, second] : [first])
scrollTo.apply(this, typeof first === "number" ? [first, second] : [first])
}
Element.prototype.scrollTo = measuredScrollTo
Object.defineProperty(Element.prototype, "scrollTop", {
@@ -13,8 +13,8 @@ test("loads home and the directory picker without newer browser APIs", async ({
})
await page.addInitScript((directory) => {
// Safari 16.6 has neither API. Remove them before the web entry runs.
Reflect.deleteProperty(Map, "groupBy")
Reflect.deleteProperty(Promise, "withResolvers")
delete (Map as Partial<typeof Map>).groupBy
delete (Promise as Partial<typeof Promise>).withResolvers
localStorage.setItem(
"opencode.global.dat:server",
JSON.stringify({
@@ -59,7 +59,7 @@ describe("checkServerHealth", () => {
await checkServerHealth(server, fetch).finally(() => {
if (timeout) Object.defineProperty(AbortSignal, "timeout", timeout)
if (!timeout) Reflect.deleteProperty(AbortSignal, "timeout")
if (!timeout) delete (AbortSignal as Partial<typeof AbortSignal>).timeout
})
expect(timeoutMs).toBe(30_000)
@@ -100,7 +100,7 @@ describe("checkServerHealth", () => {
timeoutMs: 10,
}).finally(() => {
if (timeout) Object.defineProperty(AbortSignal, "timeout", timeout)
if (!timeout) Reflect.deleteProperty(AbortSignal, "timeout")
if (!timeout) delete (AbortSignal as Partial<typeof AbortSignal>).timeout
})
expect(aborted).toBe(true)
+1 -1
View File
@@ -27,7 +27,7 @@ test("detects iOS home-screen apps when the standalone media query does not matc
expect(isStandalone()).toBe(true)
} finally {
if (descriptor) Object.defineProperty(navigator, "standalone", descriptor)
if (!descriptor) Reflect.deleteProperty(navigator, "standalone")
if (!descriptor) delete (navigator as Navigator & { standalone?: boolean }).standalone
}
})
@@ -219,6 +219,7 @@ test("snapshots materialize only measured rows and restore their current geometr
new Proxy(measurements, {
get(target, key, receiver) {
if (typeof key === "string" && /^\d+$/.test(key)) reads.push(Number(key))
// oxlint-disable-next-line no-restricted-globals -- Proxy forwarding requires receiver-aware property access.
return Reflect.get(target, key, receiver)
},
})
+1 -1
View File
@@ -78,7 +78,7 @@ export async function syncEditedFiles(input: {
const files = Array.isArray(input.metadata.files)
? input.metadata.files.flatMap((file): string[] => {
if (!file || typeof file !== "object") return []
const path = Reflect.get(file, "file")
const path = "file" in file ? file.file : undefined
return typeof path === "string" ? [path] : []
})
: []
+1 -1
View File
@@ -761,7 +761,7 @@ function formRequestOptions(location: LocationRef | undefined): [] | [{ headers:
}
function formAlreadySettled(error: unknown) {
return !!error && typeof error === "object" && Reflect.get(error, "_tag") === "FormAlreadySettledError"
return !!error && typeof error === "object" && "_tag" in error && error._tag === "FormAlreadySettledError"
}
function partID(eventID: string) {
+1 -1
View File
@@ -102,7 +102,7 @@ async function selectSession(input: {
}) {
const explicit = input.session
? await input.client.session.get({ sessionID: input.session }, ...requestOptions(input.signal)).catch((error) => {
if (error && typeof error === "object" && Reflect.get(error, "_tag") === "SessionNotFoundError")
if (error && typeof error === "object" && "_tag" in error && error._tag === "SessionNotFoundError")
return undefined
throw error
})
+1 -1
View File
@@ -27,7 +27,7 @@ test("acp prompt resolves after ordered turn updates", async () => {
if (!body || typeof body !== "object") {
return new Response(null, { status: 400 })
}
const id = Reflect.get(body, "id")
const id = "id" in body ? body.id : undefined
if (typeof id !== "string") return new Response(null, { status: 400 })
queueMicrotask(() => {
if (!events) return
@@ -574,7 +574,7 @@ function permissionReplies(fixture: Fixture) {
return fixture.requests.flatMap((request): Array<[string, string]> => {
const match = /^\/api\/session\/[^/]+\/permission\/([^/]+)\/reply$/.exec(request.path)
if (!match?.[1] || !request.body || typeof request.body !== "object") return []
const reply = Reflect.get(request.body, "reply")
const reply = "reply" in request.body ? request.body.reply : undefined
return typeof reply === "string" ? [[decodeURIComponent(match[1]), reply]] : []
})
}
+1 -1
View File
@@ -237,7 +237,7 @@ describe("acp service prompt routing and usage", () => {
function requestID(request: FixtureRequest) {
if (!request.body || typeof request.body !== "object") throw new Error(`missing body for ${request.path}`)
const id = Reflect.get(request.body, "id")
const id = "id" in request.body ? request.body.id : undefined
if (typeof id !== "string") throw new Error(`missing prompt id for ${request.path}`)
return id
}
+1 -1
View File
@@ -190,7 +190,7 @@ export async function withTimeout<Value>(promise: Promise<Value>, message: strin
function stringField(value: unknown, key: string) {
if (!value || typeof value !== "object") return undefined
const field = Reflect.get(value, key)
const field = (value as Record<string, unknown>)[key]
return typeof field === "string" ? field : undefined
}
+1
View File
@@ -301,6 +301,7 @@ test("uses migrated keybinds when persistence fails", async () => {
const fs = new Proxy(node, {
get(target, property, receiver) {
if (property === "rename") return () => Effect.die(new Error("read-only config"))
// oxlint-disable-next-line no-restricted-globals -- Proxy forwarding requires receiver-aware property access.
return Reflect.get(target, property, receiver)
},
})
@@ -21,6 +21,7 @@ const writeFileString: FileSystem.FileSystem["writeFileString"] = (target, data,
const fs = new Proxy(node, {
get(target, property, receiver) {
if (property === "writeFileString") return writeFileString
// oxlint-disable-next-line no-restricted-globals -- Proxy forwarding requires receiver-aware property access.
return Reflect.get(target, property, receiver)
},
})
+1 -1
View File
@@ -249,7 +249,7 @@ export const arrayGlobal = <R>(runner: Runner<R>) => {
items[index] = item
})
Array.from({ length: holeCount }, (_, index) => itemCount + index).forEach((index) => {
Reflect.deleteProperty(items, index)
delete items[index]
})
return target
})
@@ -23,7 +23,7 @@ const metadataBoolean = (metadata: unknown, key: string) => {
if (metadata === undefined || metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
return undefined
}
const value = Reflect.get(metadata, key)
const value = (metadata as Record<string, unknown>)[key]
if (typeof value === "boolean") return value
if (typeof value !== "string") return undefined
const normalized = value.trim().toLowerCase()
+1 -5
View File
@@ -88,11 +88,7 @@ const layer = Layer.effect(
const trigger: Interface["trigger"] = Effect.fnUntraced(function* (domain, name, event) {
for (const entry of callbacks.get(key(domain, name)) ?? []) {
if (entry.options?.providerID !== undefined && entry.options.providerID !== eventProviderID(event)) continue
const result: Effect.Effect<void, Failures[typeof domain][typeof name]> = Reflect.apply(
entry.callback,
undefined,
[event],
)
const result: Effect.Effect<void, Failures[typeof domain][typeof name]> = entry.callback(event)
yield* result
}
return event
+2
View File
@@ -177,6 +177,7 @@ export const make = Effect.fn("PluginHost.make")(function* (
options: event.options,
sdk: event.sdk,
}
// oxlint-disable-next-line no-restricted-globals -- The generic hook callback remains a union after narrowing by hook name.
return Reflect.apply(callback, undefined, [output]).pipe(
Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))),
)
@@ -190,6 +191,7 @@ export const make = Effect.fn("PluginHost.make")(function* (
sdk: event.sdk,
language: event.language,
}
// oxlint-disable-next-line no-restricted-globals -- The generic hook callback remains a union after narrowing by hook name.
return Reflect.apply(callback, undefined, [output]).pipe(
Effect.tap(() => Effect.sync(() => (event.language = output.language))),
)
+1 -1
View File
@@ -119,7 +119,7 @@ const layer = Layer.effect(
)
const result = yield* Effect.suspend(() => {
// The heterogeneous registry erases handlers after their selected schema validates input.
const execution: Effect.Effect<unknown, unknown> = Reflect.apply(handler, undefined, [parsed, callContext])
const execution: Effect.Effect<unknown, unknown> = handler(parsed, callContext)
return execution
}).pipe(
Effect.catch((error) => encodeError(method, error)),
@@ -27,7 +27,7 @@ describe("renderer response headers", () => {
test("adds the crash-report document policy only to renderer documents", () => {
const document = {}
addRendererHeaders(document, { document: true })
expect(Reflect.get(document, documentPolicyHeader)).toEqual([jsCallStacksDocumentPolicy])
expect(document[documentPolicyHeader]).toEqual([jsCallStacksDocumentPolicy])
const asset = {}
addRendererHeaders(asset, { document: false })
expect(Object.keys(asset)).not.toContain(documentPolicyHeader)
+4 -4
View File
@@ -7,7 +7,7 @@ export const jsCallStacksDocumentPolicy = "include-js-call-stacks-in-crash-repor
// A server's own `Access-Control-Allow-Headers` is kept as-is. Chromium reuses a cached preflight
// only when that header names `authorization` explicitly (`*` never covers it), so overwriting the
// server's exact list with `*` forced a fresh OPTIONS round trip in front of every API call.
export function addRendererHeaders(headers: object, options: { document: boolean }) {
export function addRendererHeaders(headers: Record<string, string | string[]>, options: { document: boolean }) {
upsertHeader(headers, "Access-Control-Allow-Origin", ["*"])
if (!hasHeader(headers, "Access-Control-Allow-Headers")) {
upsertHeader(headers, "Access-Control-Allow-Headers", ["*, authorization"])
@@ -19,11 +19,11 @@ export function addRendererHeaders(headers: object, options: { document: boolean
if (options.document) upsertHeader(headers, documentPolicyHeader, [jsCallStacksDocumentPolicy])
}
export function hasHeader(headers: object, key: string) {
export function hasHeader(headers: Record<string, string | string[]>, key: string) {
return Object.keys(headers).some((header) => header.toLowerCase() === key.toLowerCase())
}
export function upsertHeader(headers: object, key: string, value: string | string[]) {
export function upsertHeader(headers: Record<string, string | string[]>, key: string, value: string | string[]) {
const current = Object.keys(headers).find((header) => header.toLowerCase() === key.toLowerCase())
Reflect.set(headers, current ?? key, value)
headers[current ?? key] = value
}
@@ -374,7 +374,7 @@ const makeRecordingWebSocketConstructor = (
return (url, protocols) => {
const sequence = nextSequence++
const requestedProtocols = normalizeProtocols(protocols)
const native = Reflect.apply(upstream, undefined, [url, protocols])
const native = upstream(url, protocols)
const events: WebSocketEvent[] = []
let opened = false
let failed = false
@@ -439,12 +439,15 @@ const makeRecordingWebSocketConstructor = (
get: (target, property) => {
if (property === "send")
return (data: string | ArrayBufferLike | Blob | ArrayBufferView) => {
// oxlint-disable-next-line no-restricted-globals -- The socket implementation's overloaded send signature is erased by the proxy boundary.
Reflect.apply(target.send, target, [data])
appendEvent("client", data)
}
// oxlint-disable-next-line no-restricted-globals -- Proxy forwarding requires receiver-aware property access.
const value: unknown = Reflect.get(target, property, target)
return typeof value === "function" ? value.bind(target) : value
},
// oxlint-disable-next-line no-restricted-globals -- Proxy forwarding requires receiver-aware property assignment.
set: (target, property, value) => Reflect.set(target, property, value, target),
})
}
@@ -99,6 +99,7 @@ describe("WebSocket", () => {
Effect.gen(function* () {
const constructor = yield* Socket.WebSocketConstructor
const options = { headers: { authorization: "Bearer fixture" } }
// oxlint-disable-next-line no-restricted-globals -- This test intentionally passes runtime constructor options absent from the public socket type.
const socket = Reflect.apply(constructor, undefined, ["wss://echo.example.test/options", options])
yield* Effect.callback<void>((resume) => {
socket.addEventListener("open", () => {
+1
View File
@@ -1726,6 +1726,7 @@ function streamDataSchema(schema: Extract<HttpApiSchema.StreamSchema, { readonly
}
function streamEncodedDataSchema(schema: Extract<HttpApiSchema.StreamSchema, { readonly _tag: "StreamSse" }>) {
// oxlint-disable-next-line no-restricted-globals -- Effect exposes this runtime helper without a public type.
const replaceEncoding: unknown = Reflect.get(SchemaAST, "replaceEncoding")
if (typeof replaceEncoding !== "function") throw new GenerationError({ reason: "Invalid SSE data schema" })
const ast: unknown = replaceEncoding(streamDataAst(schema.events.ast), undefined)
@@ -1491,6 +1491,7 @@ describe("HttpApiCodegen.generate", () => {
const link = JsonNumber.ast.encoding?.[0]
if (link === undefined) throw new Error("Expected JSON number encoding")
// This helper is present at runtime but omitted from the public declaration surface.
// oxlint-disable-next-line no-restricted-globals -- The test verifies an Effect runtime helper without a public type.
const replaceEncoding: unknown = Reflect.get(SchemaAST, "replaceEncoding")
if (typeof replaceEncoding !== "function") throw new Error("Expected SchemaAST.replaceEncoding")
const ast: unknown = replaceEncoding(JsonNumber.ast, [
+1
View File
@@ -130,6 +130,7 @@ const rpcFromEffect = Effect.fn("Plugin.Rpc.fromEffect")(function* (host: HostRp
try: (signal) => {
// SAFETY: Promise RPC handlers return Promise values before this adapter erases their concrete types.
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
// oxlint-disable-next-line no-restricted-globals -- The portable RPC registry intentionally erases each handler's concrete signature.
return Reflect.apply(handler, undefined, [
input,
{
@@ -127,4 +127,10 @@ render(() => {
)
}, document.getElementById("root")!)
declare global {
interface Window {
markdownLifetime: Omit<typeof stats, "cacheChars"> & { cacheChars?: number; cacheBytes?: number }
}
}
Object.defineProperty(window, "markdownLifetime", { value: stats })
@@ -14,9 +14,9 @@ for (const scenario of ["mounted", "leave", "shared"]) {
await expect(page.locator("body")).toHaveAttribute("data-ready", "true")
await expect(page.locator("body")).toHaveAttribute("data-settled", "true")
const after = await session.send("Performance.getMetrics")
const stats = await page.evaluate(() => Reflect.get(window, "markdownLifetime"))
const stats = await page.evaluate(() => window.markdownLifetime)
// The frozen initial baseline used a byte label for this character count.
stats.cacheChars ??= stats.cacheBytes
stats.cacheChars ??= stats.cacheBytes ?? 0
delete stats.cacheBytes
expect(stats.requests).toBe(1)
expect(stats.responses).toBe(1)
@@ -50,7 +50,9 @@ function all(renderable: Renderable): Renderable[] {
}
function mouseListeners(renderable: Renderable) {
// oxlint-disable-next-line no-restricted-globals -- OpenTUI does not expose listener state through its public API.
const general = Reflect.get(renderable, "_mouseListener")
// oxlint-disable-next-line no-restricted-globals -- OpenTUI does not expose listener state through its public API.
const specific = Reflect.get(renderable, "_mouseListeners")
return Boolean(general) || (specific && typeof specific === "object" && Object.keys(specific).length > 0)
}
@@ -87,6 +89,7 @@ export function createHarness(renderer: CliRenderer): Harness {
// captureCharFrame follows the test renderer's output sink. Recording
// redirects that sink to the timeline, so read the live render buffer
// instead; it is also the source used by screenshots.
// oxlint-disable-next-line no-restricted-globals -- OpenTUI does not expose the live render buffer publicly.
screen: () => decoder.decode((Reflect.get(renderer, "currentRenderBuffer") as RenderBuffer).getRealCharBytes()),
}
}
@@ -7,14 +7,12 @@ import type { SimulationProtocol } from "../protocol"
export type Definition = Omit<SimulationProtocol.Frontend.SemanticNode, "id" | "element" | "parent">
const key = Symbol.for("opencode.simulation.semantics")
type SemanticRenderable = Renderable & { [key]?: () => Definition }
const bind = (definition: () => Definition) => (renderable: Renderable) => {
Object.defineProperty(renderable, key, { value: definition, configurable: true })
}
export const read = (renderable: Renderable) => {
const definition: unknown = Reflect.get(renderable, key)
return typeof definition === "function" ? (definition as () => Definition) : undefined
}
export const read = (renderable: Renderable) => (renderable as SemanticRenderable)[key]
export const SimulationSemantics = { bind, read }
@@ -82,7 +82,9 @@ test.each(["pointer", "output"])("joins both recording streams after an early %s
if (failed === "pointer") await mkdir(join(directory, "timeline.pointers.jsonl"))
const error = new Promise<Error>((resolve) => timeline.once("error", resolve))
timeline.pointer("move", 10, 5)
// oxlint-disable-next-line no-restricted-globals -- This failure-path test must access Timeline's owned private stream.
const output: unknown = Reflect.get(timeline, "output")
// oxlint-disable-next-line no-restricted-globals -- This failure-path test must access Timeline's owned private stream.
const pointers: unknown = Reflect.get(timeline, "pointers")
if (!(output instanceof WriteStream) || !(pointers instanceof WriteStream)) throw new Error("missing owned streams")
if (failed === "output") {
@@ -95,7 +97,9 @@ test.each(["pointer", "output"])("joins both recording streams after an early %s
await expect(finishing).rejects.toBe(failure)
expect(output.closed).toBe(true)
expect(pointers.closed).toBe(true)
// oxlint-disable-next-line no-restricted-globals -- Node does not expose the stream descriptor state publicly.
expect(Reflect.get(output, "fd")).toBeNull()
// oxlint-disable-next-line no-restricted-globals -- Node does not expose the stream descriptor state publicly.
expect(Reflect.get(pointers, "fd")).toBeNull()
timeline.pointer("move", 99, 99)
expect(timeline.finish()).toBe(finishing)
+2 -2
View File
@@ -232,9 +232,9 @@ export function formDisplay(field: FormAnswerField, value: FormValue | undefined
export function formErrorMessage(error: unknown) {
if (typeof error === "string" && error.trim()) return error
if (error && typeof error === "object") {
const message = Reflect.get(error, "message")
const message = "message" in error ? error.message : undefined
if (typeof message === "string" && message.trim()) return message
const tag = Reflect.get(error, "_tag")
const tag = "_tag" in error ? error._tag : undefined
if (typeof tag === "string" && tag.trim()) return tag
}
return "Form request failed"
+3
View File
@@ -100,6 +100,7 @@ function monoCode(renderable: CodeRenderable): void {
const initialSetter = initialDescriptor.set.bind(renderable)
const contentGetter = contentDescriptor.get.bind(renderable)
const contentSetter = contentDescriptor.set.bind(renderable)
// oxlint-disable-next-line no-restricted-globals -- OpenTUI does not expose its eager styled-text cache publicly.
const initial = Reflect.get(renderable, "_initialStyledText")
Object.defineProperty(renderable, "initialStyledText", {
configurable: true,
@@ -111,6 +112,7 @@ function monoCode(renderable: CodeRenderable): void {
configurable: true,
get: contentGetter,
set(value: string) {
// oxlint-disable-next-line no-restricted-globals -- OpenTUI does not expose its eager styled-text cache publicly.
if (renderable.filetype !== "markdown" || !isStyledText(Reflect.get(renderable, "_initialStyledText"))) {
renderable.drawUnstyledText = true
renderable.initialStyledText = stringToStyledText(value)
@@ -137,6 +139,7 @@ function monoCode(renderable: CodeRenderable): void {
function monoTreeSitter(client: TreeSitterClient): TreeSitterClient {
return new Proxy(client, {
get(target, property) {
// oxlint-disable-next-line no-restricted-globals -- Proxy forwarding requires receiver-aware property access.
if (property !== "highlightOnce") return Reflect.get(target, property, target)
// Keep parser failures on the chunk path instead of OpenTUI's raw-text fallback.
return (...args: Parameters<TreeSitterClient["highlightOnce"]>) =>
+1 -1
View File
@@ -159,7 +159,7 @@ function formRequestOptions(location: LocationRef | undefined) {
}
function formAlreadySettled(error: unknown) {
return !!error && typeof error === "object" && Reflect.get(error, "_tag") === "FormAlreadySettledError"
return !!error && typeof error === "object" && "_tag" in error && error._tag === "FormAlreadySettledError"
}
const RESIZE_DELAY = 250
+2 -2
View File
@@ -168,9 +168,9 @@ export function formatUnknownError(error: unknown): string {
if (typeof error === "string") return error
if (error instanceof Error) return error.message || error.name
if (error && typeof error === "object") {
const message = Reflect.get(error, "message")
const message = "message" in error ? error.message : undefined
if (typeof message === "string" && message.trim()) return message
const tag = Reflect.get(error, "_tag")
const tag = "_tag" in error ? error._tag : undefined
if (typeof tag === "string" && tag.trim()) return tag
}
return "unknown error"
+1 -1
View File
@@ -104,6 +104,6 @@ export function createSourceWatcher(onChange: () => void) {
function isMissing(error: unknown) {
if (!error || typeof error !== "object") return false
const code = Reflect.get(error, "code")
const code = "code" in error ? error.code : undefined
return code === "ENOENT" || code === "ENOTDIR"
}
+1 -1
View File
@@ -6,7 +6,7 @@ export async function discoverThemes(directories: string[]) {
for (const directory of directories) {
const themeDirectory = path.join(directory, "themes")
const entries = await readdir(themeDirectory, { withFileTypes: true }).catch((error: unknown) => {
if (error && typeof error === "object" && Reflect.get(error, "code") === "ENOENT") return []
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return []
return Promise.reject(error)
})
const files = entries
+1 -1
View File
@@ -31,7 +31,7 @@ export async function localProjectDirectory(cwd: string) {
export function isMissingPath(error: unknown) {
if (!error || typeof error !== "object") return false
const code = Reflect.get(error, "code")
const code = "code" in error ? error.code : undefined
return code === "ENOENT" || code === "ENOTDIR"
}
@@ -53,6 +53,7 @@ async function setup(
out.renderer.destroy()
})
out.renderer.on(CliRenderEvents.EXTERNAL_OUTPUT, () => {
// oxlint-disable-next-line no-restricted-globals -- RunScrollbackStream does not expose its image surface publicly.
const surface = Reflect.get(out.scrollback, "imageSurface") as ScrollbackSurface | undefined
if (!surface || out.previews.some((preview) => preview.surface === surface)) return
const image = surface.root.getChildren().find((child) => child instanceof ImageRenderable)
@@ -126,6 +127,7 @@ test("reserves wrapped caption rows and allows caption-only output in a short te
test("uses physical cell geometry without enlarging a small image", async () => {
const out = await setup({ imagePreview: true })
// oxlint-disable-next-line no-restricted-globals -- OpenTUI does not expose test resolution mutation publicly.
Reflect.set(out.renderer, "_resolution", { width: 800, height: 720 })
await out.scrollback.append(image(wide))
expect(out.previews[0]!.size).toEqual({ width: 6, height: 1 })
@@ -196,6 +198,7 @@ test.each(["stream", "renderer"])("disposes an image load when the %s is destroy
const out = await setup({ imagePreview: true })
const pending = out.scrollback.append(image())
await Promise.resolve()
// oxlint-disable-next-line no-restricted-globals -- RunScrollbackStream does not expose its image surface publicly.
const surface = Reflect.get(out.scrollback, "imageSurface") as ScrollbackSurface
const preview = surface.root.getChildren().find((child) => child instanceof ImageRenderable)!
expect(preview.loading).toBe(true)
@@ -29,6 +29,7 @@ afterEach(() => {
})
function claim(renderer: TestRenderer): ClaimedCommit[] {
// oxlint-disable-next-line no-restricted-globals -- OpenTUI does not expose its external-output queue publicly.
const queue = Reflect.get(renderer, "externalOutputQueue")
if (!queue || typeof queue !== "object" || !("claim" in queue) || typeof queue.claim !== "function") {
throw new Error("renderer missing external output queue")
@@ -183,6 +184,7 @@ test("theme swaps restyle active reasoning without resetting the stream", async
})
function activeSyntax(scrollback: RunScrollbackStream) {
// oxlint-disable-next-line no-restricted-globals -- RunScrollbackStream does not expose its active entry publicly.
const entry = Reflect.get(scrollback, "active") as { renderable?: { syntaxStyle?: SyntaxStyle } } | undefined
return entry?.renderable?.syntaxStyle
}
@@ -277,12 +279,14 @@ test("renders monochrome scrollback as ASCII markdown", async () => {
try {
await out.scrollback.append(assistant("# H"))
// oxlint-disable-next-line no-restricted-globals -- RunScrollbackStream does not expose its active entry publicly.
expect(Reflect.get(out.scrollback, "active")?.renderable).toBeInstanceOf(MarkdownRenderable)
await out.scrollback.append(
assistant(
"éading →\n\n> “quote”\n\n---\n\n| A | B |\n| - | - |\n| α | β |\n\n• literal\n\n———\n\n[café](https://example.com/café)",
),
)
// oxlint-disable-next-line no-restricted-globals -- RunScrollbackStream does not expose its active entry publicly.
const active: unknown = Reflect.get(out.scrollback, "active")
const renderable =
active && typeof active === "object" && "renderable" in active && active.renderable instanceof MarkdownRenderable