feat(next-typed-href): require explicit type arguments on routes()#82
Open
akameco wants to merge 5 commits into
Open
feat(next-typed-href): require explicit type arguments on routes()#82akameco wants to merge 5 commits into
akameco wants to merge 5 commits into
Conversation
2583a4a to
a840c34
Compare
amotarao
reviewed
May 27, 2026
Comment on lines
+66
to
+70
| export type RequireExplicitRoutesArgs<Routes extends string> = string extends Routes | ||
| ? [ | ||
| pass_Routes_and_RouteParamsMap_as_type_arguments: "routes<Routes, RouteParamsMap>() requires explicit type arguments", | ||
| ] | ||
| : []; |
Member
There was a problem hiding this comment.
型エラーメッセージが意図通りでてなさそうな気がする
src/index.test.ts:139:21 - error TS2554: Expected 1 arguments, but got 0.
139 defineTypedHref.routes();
~~~~~~
src/index.ts:28:5
28 ...args: RequireExplicitRoutesArgs<Routes>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Arguments for the rest parameter 'args' were not provided.
Contributor
Author
There was a problem hiding this comment.
ご指摘ありがとうございます!
当初 ...args: だったため tsc が args という generic 名しか出力せず、せっかくの説明が埋もれていました。rest パラメータ名自体を説明的にする形で解消しています。
routes: <Routes, RouteParamsMap>(
...typeArguments: RequireExplicitRoutesArgs<Routes>
) => ...実際の tsc --pretty 出力:
check.ts:4:17 - error TS2554: Expected 1 arguments, but got 0.
4 defineTypedHref.routes();
~~~~~~
src/index.ts:63:5
63 ...typeArguments: RequireExplicitRoutesArgs<Routes>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Arguments for the rest parameter 'typeArguments' were not provided.
詳細な指示メッセージ ("routes<Routes, RouteParamsMap>() requires explicit type arguments") は型リテラルとして残し、IDE hover で参照できる形にしています。
将来の degrade 防止に、TS Compiler API でコンパイルしてエラーテキストを assert するテストも追加しました (src/routes-error-message.test.ts)。こちらは不要であれば削除してもいいかなと思います。
`defineTypedHref.routes()` and `defineTypedHrefWithNuqs.routes()` now
fail to compile when called without explicit `<Routes, RouteParamsMap>`
type arguments. Without this, `Routes` would silently default to its
constraint upper bound `string`, defeating the type-safety the library
promises.
Implementation uses a conditional rest parameter via a shared
`RequireExplicitRoutesArgs<Routes>` helper:
string extends Routes
? [pass_Routes_and_RouteParamsMap_as_type_arguments: "...message..."]
: []
`string extends Routes` is true only when no explicit type argument was
supplied. Missing the now-required argument fails the call AT the
`.routes()` site with "Expected 1 arguments, but got 0", and hover
reveals the parameter name and the literal-string message explaining
the fix. Valid call sites with a literal-union `Routes` stay nullary.
Type-level only; no runtime behavior changes.
…S error `...args` shows up as the literal word `args` in TS's "Arguments for the rest parameter 'args' were not provided." message, hiding the explanatory parameter name that was supposed to do the teaching. Renaming the rest binding itself puts the guidance directly in the error text — both at the .routes() call site and in the related-info pointer.
…ter name The whole point of naming the rest parameter `pass_Routes_and_RouteParamsMap_as_type_arguments` is that TS prints it back to the user as part of "Arguments for the rest parameter 'X' were not provided." A future rename to something generic like `args` would silently strip the guidance from the error text without breaking any compile-time check. Compile small snippets via the TypeScript compiler API and assert the formatted diagnostic text contains the expected parameter name, for both defineTypedHref and defineTypedHrefWithNuqs.
- types.ts: trim RequireExplicitRoutesArgs JSDoc — drop the mechanism walkthrough
(self-evident from the type body) and clarify that the inner string literal is
hover-only, while the rest-param name is what tsc actually prints.
- routes-error-message.test.ts:
- dedupe the two builder snippets via test.each
- swap lib.dom.d.ts → lib.webworker.d.ts (URLSearchParams still present, ~26%
faster: 430ms → 317ms test phase)
- drop dead fileExists/readFile host overrides (compiler never calls them
once getSourceFile returns a SourceFile for the virtual file)
- rename __virtual__check__.ts → snippet.ts to match repo naming conventions
a840c34 to
a7f1ace
Compare
`pass_Routes_and_RouteParamsMap_as_type_arguments` (47 chars, snake_case)
was load-bearing for the error message but unidiomatic in TS and hard to
read in the diagnostic. The inner tuple-element type literal already
carries the full guidance ("routes<Routes, RouteParamsMap>() requires
explicit type arguments"), which is surfaced on IDE hover.
Shorten the rest-param to `typeArguments` (13 chars, camelCase). The error
text now reads "Arguments for the rest parameter 'typeArguments' were not
provided." — concise and scannable, with the verbose explanation still
available on hover and in the related-info pointer.
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
defineTypedHref.routes()/defineTypedHrefWithNuqs.routes()を型引数なしで呼べないように変更.routes()呼び出し行自体 に出る (downstream の.$href(...)ではなく)背景
.routes()には型引数<Routes, RouteParamsMap>が必要だが、省略しても TypeScript はRoutesを制約上限stringとして推論し、結果的に型安全性が静かに失われていた。このPRで
.routes()行に直接エラーを出して早期検知する。実装
common/types.tsに汎用ヘルパを追加:routesのシグネチャに conditional rest parameter として適用:仕組み:
.routes<MyRoutes, MyMap>()と書く →Routes = MyRoutes→ rest tuple が[]→ 引数 0 で OK.routes()と書く →Routes = string(制約上限) → rest tuple が[phantom_arg]→ 引数 0 で "Expected 1 arguments, but got 0"なぜ rest parameter にしたか
最初は returns 型を error brand object に置き換える案 (
RequireExplicitRoutes<R, B>→{ __error: "..." }) を試したが、以下の欠点があった:.$href(...)で発火するため、ユーザーは.routes()まで OK と誤解しがちas unknown as TypedHrefBuilder["routes"]のキャストが必要rest parameter 案ではエラーが
.routes()行に出る上、ランタイム impl のシグネチャと型宣言が自然に一致するためキャスト不要。IDE 体験
ホバーすると以下が見える:
パラメータ名が指示、パラメータ型 (string literal) がメッセージという役割分離。
Breaking change?
技術的には型レベルの破壊的変更だが、
.routes()を型引数なしで呼んでいたコードは型安全性が崩れた状態だったので、修正されるべきもの。changeset はminor扱い。Test plan
pnpm --filter @plainbrew/next-typed-href test run— 52テスト全通過defineTypedHref.routes()で// @ts-expect-errorが成立すること (TS2554: Expected 1 arguments, but got 0)defineTypedHref.routes<R, M>()明示時に通常の builder が返ることdefineTypedHrefWithNuqs側も同様の挙動🤖 Generated with Claude Code