Skip to content

Lint the RedisClient class in redis.d.ts against the valkey.classes.ts tables - #39271

Merged
alii merged 1 commit into
mainfrom
farm/610d0ce7/redis-client-types-lint
Aug 16, 2026
Merged

Lint the RedisClient class in redis.d.ts against the valkey.classes.ts tables#39271
alii merged 1 commit into
mainfrom
farm/610d0ce7/redis-client-types-lint

Conversation

@robobun

@robobun robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Problem

Fix

  • test/internal/source-lints/redis-client-types.test.ts imports valkey.classes.ts (the module the codegen reads; class-definitions.ts has no dependencies), collects the members the codegen installs (proto entries by name, klass entries as static name, constructor when construct is set, skipping internal/privateSymbol/publicSymbol entries, which the codegen does not install under an identifier), parses the member names declared in the class RedisClient body of redis.d.ts ([Symbol.x] maps onto the table's @@x spelling), and requires the two sets to be equal. Each direction is its own test, so a table entry without a declaration and a declaration without a table entry are both reported by name.
  • The five names missing today sit in a pendingDeclarations table keyed by the PR that declares each. A fourth test fails as soon as a listed name is declared or unregistered, so the entry gets deleted when its PR lands (whichever of this PR and types: declare RedisClient.pubsub() and RedisClient.select() #39208/redis: add bitmap, HLL, geo, scripting, server, and stream commands #29339/redis: implement psubscribe/punsubscribe with listener routing #35521 lands second trips it on rebase, and the message says which entry to delete). A name that is neither declared nor listed fails the lint outright.
  • Declaration lines the parser does not recognize are reported as failures rather than skipped, so a new d.ts shape cannot silently hide a member from the comparison.
  • .github/workflows/source-lints.yml gains src/**/*.classes.ts, src/codegen/class-definitions.ts and packages/bun-types/redis.d.ts as triggers: the workflow is path-filtered and those are the files this lint reads, so without them the edits it guards would not run it. The directory README now states that rule; a comment on the proto table points at the lint.
  • Scope is RedisClient only. A checker for every *.classes.ts needs a class-to-declaration mapping and inheritance handling and is a separate project.
  • Verified:
    • bun test test/internal/source-lints/redis-client-types.test.ts passes on this branch (4 tests); the whole directory is 170 green.
    • With pendingDeclarations emptied, the lint fails against main's files naming exactly psubscribe, pubsub, punsubscribe, script, select (output below).
    • Simulated the eight drift shapes in the details block (new table entry, pending name declared, phantom declaration, pending name unregistered, unparseable member, @@asyncDispose + a klass entry with and without declarations, internal/privateSymbol entries); each fails the intended test or passes as intended.
  • No runtime code changes: the valkey.classes.ts hunk is a comment and produces identical codegen output. This PR has no src/packages diff for a fail-before run to strip; the fail-before evidence is the emptied-pending-table run above.

Background

  • *.classes.ts files are the input of src/codegen/generate-classes.ts. define({ proto, klass, construct }) describes a native class: proto entries become properties of the prototype (fn methods, getter/setter accessors), klass entries become statics, and construct: true makes it newable. Entries with internal, privateSymbol or publicSymbol are installed under private names or Symbol.for() symbols (or not at all), and keys spelled @@x are installed under the well-known symbol Symbol.x.
  • packages/bun-types is the published @types/bun surface; it is hand-written, not generated from the class definitions, which is why it can drift.
  • test/internal/source-lints/ holds tests that only read the source tree; .buildkite/ci.mjs excludes the directory from the binary lanes and source-lints.yml runs it against a released bun on a bare checkout, so tests there may only import built-ins and relative paths.
Lint output against main's files with the pending table emptied, and the simulated drift shapes
(pass) every member of class RedisClient in packages/bun-types/redis.d.ts has a shape this lint can read
(fail) packages/bun-types/redis.d.ts declares every RedisClient member src/runtime/valkey_jsc/valkey.classes.ts installs
    - []
    + [
    +   "psubscribe",
    +   "pubsub",
    +   "punsubscribe",
    +   "script",
    +   "select",
    + ]
(pass) packages/bun-types/redis.d.ts declares no RedisClient member src/runtime/valkey_jsc/valkey.classes.ts does not install
(pass) pendingDeclarations lists only members that are still registered and still undeclared
### proto gains waitaof, d.ts untouched
    +   "waitaof",
    (fail) packages/bun-types/redis.d.ts declares every RedisClient member src/runtime/valkey_jsc/valkey.classes.ts installs
### d.ts declares select while it is still pending
    +   "select (#39208) is declared in packages/bun-types/redis.d.ts now; delete its entry",
    (fail) pendingDeclarations lists only members that are still registered and still undeclared
### d.ts declares flushall, which is not registered
    +   "flushall",
    (fail) packages/bun-types/redis.d.ts declares no RedisClient member src/runtime/valkey_jsc/valkey.classes.ts does not install
### proto drops script while it is still pending
    +   "script (#29339) is no longer registered in src/runtime/valkey_jsc/valkey.classes.ts",
    (fail) pendingDeclarations lists only members that are still registered and still undeclared
### d.ts gains `private brand: never;`
    +   "private brand: never;",
    (fail) every member of class RedisClient in packages/bun-types/redis.d.ts has a shape this lint can read
### proto gains "@@asyncDispose" and a klass entry, d.ts declares [Symbol.asyncDispose]() and the static
    4 pass
### same, with nothing declared
    +   "@@asyncDispose",
    +   "static parseURL",
    (fail) packages/bun-types/redis.d.ts declares every RedisClient member src/runtime/valkey_jsc/valkey.classes.ts installs
### proto gains an `internal: true` entry and a `privateSymbol` entry, d.ts untouched
    4 pass

…s tables

packages/bun-types/redis.d.ts mirrors the proto table in
src/runtime/valkey_jsc/valkey.classes.ts by hand and nothing compared the
two; psubscribe, pubsub, punsubscribe, script and select have been
registered since the first Bun.redis commit and are still undeclared.

The new source lint imports valkey.classes.ts, collects the members the
codegen installs (proto, klass, constructor), parses the members declared
in the class body of redis.d.ts, and requires the two sets to be equal in
both directions. The five names currently missing are listed in a pending
table keyed by the PR that declares each; the lint also fails once a
listed name is declared or unregistered, so the table cannot go stale.

source-lints.yml now triggers on *.classes.ts, class-definitions.ts and
redis.d.ts, the files this lint reads.
@robobun

robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 7:37 PM PT - Aug 15th, 2026

@robobun, your commit e1843f25d86a9f3418fe1323760008464c00542a passed in Build #98987! 🎉


🧪   To try this PR locally:

bunx bun-pr 39271

That installs a local version of the PR into your bun-39271 executable, so you can run:

bun-39271 --bun

@robobun

robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author

Status: merged as f0f6b2c.

Reproduced on main by comparing the proto table in src/runtime/valkey_jsc/valkey.classes.ts (167 entries) with the members declared in the class RedisClient body of packages/bun-types/redis.d.ts: psubscribe, pubsub, punsubscribe, script and select are registered and undeclared; the reverse direction is clean. With the pending table in the lint emptied, it reports exactly those five names.

The five declarations themselves are owned by #39208 (pubsub, select), #29339 (script) and #35521 (psubscribe, punsubscribe); each of those now gets a lint failure on rebase naming the pendingDeclarations entry to delete.

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc6ce13f-a84e-43de-9fc4-6b1565ae6a01

📥 Commits

Reviewing files that changed from the base of the PR and between a42889a and e1843f2.

📒 Files selected for processing (4)
  • .github/workflows/source-lints.yml
  • src/runtime/valkey_jsc/valkey.classes.ts
  • test/internal/source-lints/README.md
  • test/internal/source-lints/redis-client-types.test.ts

Included review availability: Your plan includes up to 5 reviews per rolling hour; 0 remain after this review.


Walkthrough

Changes

RedisClient synchronization

Layer / File(s) Summary
Declaration parity lint
test/internal/source-lints/redis-client-types.test.ts, src/runtime/valkey_jsc/valkey.classes.ts, test/internal/source-lints/README.md
The new lint compares generated RedisClient members with redis.d.ts, validates symbols and accessors, checks exceptions, and documents the synchronization contract.
Source-lint workflow coverage
.github/workflows/source-lints.yml
Push and pull-request filters now include class sources, class definitions, and packages/bun-types/redis.d.ts.

Suggested reviewers: jarred-sumner

Merge Risk: ⚪ Minimal · up to e1843

This PR adds a focused lint that keeps RedisClient type declarations aligned with the runtime command table and updates the checks that run it. No actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: linting RedisClient declarations against the class definition tables.
Description check ✅ Passed The description explains the problem, implementation, verification steps, scope, and workflow changes required by the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — test-infrastructure only, no runtime code changes.

What was reviewed:

  • Ran the new lint locally against the checked-out tree: 4 pass; class-definitions.ts has no imports so the bare-checkout constraint holds.
  • Checked redis.d.ts indentation and the \\n }\\n terminator: lines 43/51 precede the class open at 53, so indexOf after bodyStart lands on 3309 (the class close).
  • define() defaults klass = {}, so iterating definition.klass on RedisClient (which sets none) is a no-op, not a crash.
  • Workflow path additions mirror across push/pull_request and cover exactly the files the lint reads/imports.
Extended reasoning...

Overview

Adds a source-lint test (test/internal/source-lints/redis-client-types.test.ts) that imports valkey.classes.ts, collects the members the codegen would install on RedisClient.prototype, parses the member names from the class RedisClient body in packages/bun-types/redis.d.ts, and asserts the two sets match. The five currently-undeclared commands are held in a pendingDeclarations table with a self-pruning test. The remaining hunks are a two-line pointer comment in valkey.classes.ts, three workflow paths: triggers (mirrored push/PR), and a README paragraph documenting the trigger-path rule.

Security risks

None. Reads two repo files, imports a codegen-input module with zero external deps, no network, no subprocess, no writes.

Level of scrutiny

Low-to-medium. No shipped code changes — the valkey.classes.ts hunk is a comment above proto:. The interesting part is the d.ts body parser: it anchors on the exact two-space-indented export class RedisClient { line, slices to the next \\n }\\n, strips comments, and matches four-space-indented lines whose first non-space char isn't a continuation closer. I checked the actual file: the only two-space } lines before the class are at 43/51 (before bodyStart), so the terminator search finds the class close at 3309. The test's own "unrecognized shape" assertion fails loudly if a future d.ts edit introduces a member line the regex can't read, so silent drift is prevented rather than assumed away.

Other factors

  • Ran bun test test/internal/source-lints/redis-client-types.test.ts on this checkout: 4 pass, 191ms. Fits the directory's speed budget and its no-install constraint (class-definitions.ts has no imports; valkey.classes.ts imports only that).
  • define() defaults klass = {}, so the [definition.klass, "static "] loop entry is safe on RedisClient which has no klass block.
  • The pendingDeclarations table introduces intentional cross-PR coupling with #39208/#29339/#35521; the failure message names the entry to delete, which is the standard suppression-list pattern already used elsewhere in this directory.
  • Follows the established test/internal/source-lints/ conventions (relative imports, toEqual([]) on sorted diffs so the offending names print).

@alii

alii commented Aug 16, 2026

Copy link
Copy Markdown
Member

This is good but honestly we should find a much proper solution for this in the future with some actual generator between the two. I bet there are so many other problems we could be fixing to have these match up

@alii
alii merged commit f0f6b2c into main Aug 16, 2026
11 of 12 checks passed
@alii
alii deleted the farm/610d0ce7/redis-client-types-lint branch August 16, 2026 02:41
@robobun

robobun commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator Author

Agreed. To put numbers on the second half, I ran the same comparison by exact name over all 30 *.classes.ts files (93 class definitions) against packages/bun-types:

  • 34 definitions have a same-name class/interface in bun-types. The other 59 are either internal (the stream sources, H2FrameParser, NodeJSFS, the jest helper classes) or public under another name (TCPSocket -> Socket, Listener -> TCPSocketListener, HTTPServer -> Server, ...). That is the name map a general checker or generator needs first.
  • Of the 34, a name-only comparison flags 22, but most of that is inheritance, not drift: BuildArtifact extends Blob, the SHA*/MD5 classes extend CryptoHashInterface, CronJob extends Disposable, Subprocess extends AsyncDisposable, Blob's table carrying BunFile's members, Expect vs Matchers. So the checker also has to resolve extends before it can say anything.
  • Real drift it did turn up: the runtime members of BuildMessage/ResolveMessage (bun-types: declare the Error base and the runtime members of BuildMessage and ResolveMessage #38564 is open for that), SocketAddress (net: align SocketAddress and BlockList with Node's API surface #33572), Subprocess.connected (bun-types: declare Subprocess.connected #38677) and Subprocess.writable (not covered), MatchedRoute.scriptSrc undeclared, and FileSystemRouter.assetPrefix declared but undefined at runtime. The ones without an open PR are being picked up separately.

On a generator: the tables only know names, arity and getter/setter-ness; every signature, overload and doc comment exists only in the d.ts (the codegen already emits the untyped skeleton as ZigGeneratedClasses.d.ts, for internal use). So a generator could own the member list but the hand-written half stays, and the check is still the join between the two. Generalizing this lint with the name map plus extends resolution is probably the realistic next step; the numbers above are roughly what it would need to handle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants