Improve IDE integration for inferred object properties to support Go To Definition, Rename Symbol, etc. #1117
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.
This PR fixes the issue I raised here: #1115
To summarise, the properties of types declared using
z.infer
onz.object({ ... })
do not support IDE language server features such as Find All References, Rename Symbol or Go To Definition.To take a concrete example using the latest version of Zod in VS Code, the language server features
Find All References
,Rename Symbol
andGo To Definition
do not work when you select the propertyprop
in the code below:As you no doubt know, these features are incredibly useful for working on TypeScript projects, particularly as they grow in size.
These features are broken because
z.object({ ... })
uses a mapped type,addQuestionMarks
, that transforms its keys usingrequiredKeys
andoptionalKeys
. Unfortunately, due to a limitation in the TypeScript compiler (microsoft/TypeScript#47813), transforming keys in a mapped type prevents the compiler from linking the mapped type properties to their definition.Fortunately, it is possible to preserve the behaviour of
addQuestionMarks
without mapping the keys on one side of the intersection (by taking advantage of the fact that(T | undefined) & T
equalsT
):It appears that the built-in type helpers
Omit
andPick
are also special cased to preserve the linkage between mapped keys, so I was able to use those forobject({}).extend()
,object({}).pick()
andobject({}).omit()
:These changes preserve the existing behaviour while enabling the IDE features for
z.object({ ... })
as well as when usingz.object({ ... }).merge(z.object({ ... }))
,z.object({ ... }).partial()
,z.union()
,z.object().pick
andz.object().omit
.I have added a number of tests that use the TypeScript language server (via the library
ts-morph
) to verify that Go To Definition (and by extension the other features) work correctly. I have also added additional tests around the types inferred fromz.object
to ensure I have not broken existing behaviour.What do you think? Happy to make any changes. Thanks!