-
Notifications
You must be signed in to change notification settings - Fork 649
Add callback tag, fix jsdoc type parameter resolution #1187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Direct from Copilot agent Claude, needs lots of cleanup/expansion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR enhances JSDoc support by adding @callback
tag handling, tightening how @template
tags attach to their specific hosts, and simplifying type parameter resolution across @typedef
/@overload
/callback declarations.
- Added support for parsing and emitting
@callback
tags as type aliases. - Refactored
gatherTypeParameters
to only apply templates preceding their host tag. - Introduced a
Host
field onJSDocTemplateTag
and updated parent traversal (GetEffectiveTypeParent
).
Reviewed Changes
Copilot reviewed 105 out of 105 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
internal/printer/printer.go | Added nil-check in emitReturnType to guard against missing nodes. |
internal/parser/reparser.go | Major reparser overhaul for @typedef , @callback , and @overload with host assignment. |
internal/checker/checker.go | Updated parent traversal to use GetEffectiveTypeParent . |
internal/binder/nameresolver.go | Integrated effective parent logic in name-resolution loop. |
internal/ast/utilities.go | Extended GetEffectiveTypeParent to handle @template hosts. |
internal/ast/ast.go | Added Host to JSDocTemplateTag and updated JSDocSignature . |
internal/api/encoder/encoder.go | Adjusted encoding mask to reflect renamed TypeParameters field. |
Comments suppressed due to low confidence (1)
internal/parser/reparser.go:82
- [nitpick] The indentation of
typeAlias.Loc
,typeAlias.Flags
, and the append call is inconsistent with the surrounding block; aligning these lines will improve readability.
typeAlias.Loc = tag.Loc
typeAlias.Loc = tag.Loc | ||
typeAlias.Flags = p.contextFlags | ast.NodeFlagsReparsed | ||
p.reparseList = append(p.reparseList, typeAlias) | ||
case ast.KindJSDocImportTag: | ||
importTag := tag.AsJSDocImportTag() | ||
importClause := importTag.ImportClause.Clone(&p.factory) | ||
importClause := importTag.ImportClause |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cloning of importTag.ImportClause
was removed, so setting Flags
will mutate the shared AST node. Please clone the clause (e.g., importTag.ImportClause.Clone(&p.factory)
) before updating its flags to avoid side-effects.
importClause := importTag.ImportClause | |
importClause := importTag.ImportClause.Clone(&p.factory) |
Copilot uses AI. Check for mistakes.
lastLocation = location | ||
location = ast.GetEffectiveTypeParent(location.Parent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assigning lastLocation
before normalizing location
means lastLocation
may reference raw JSDoc nodes instead of their hosts, which could break self-reference detection. Consider updating lastLocation
after computing the effective parent.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it regresses generic typedefs?
...submoduleAccepted/compiler/contravariantOnlyInferenceFromAnnotatedFunctionJs.errors.txt.diff
Show resolved
Hide resolved
@@ -17,7 +17,7 @@ | |||
*/ | |||
transform(fn) { | |||
->transform : { <U>(fn: (y: T) => U): U; <U>(): T; } | |||
+>transform : { <U>(fn: (y: T) => U): U; <U_1>(): T; } | |||
+>transform : { <U>(fn: (y: T) => U): U; (): T; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is spooky; now there's a func without type params.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
intentional change that's an improvement in this case (T comes from the class not the function). see my comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's that sharing thing again. I feel like it can't be good to break it...
...es/reference/submoduleAccepted/compiler/unmetTypeConstraintInJSDocImportCall.errors.txt.diff
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
explanation of some test diffs
@@ -27,6 +28,8 @@ | |||
+ * @template A | |||
+ * @template {Record<string, unknown>} B | |||
+ * @param {Funcs<A, B>} fns | |||
+ ~~~~~~~~~~~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template tags have to come before their usage now. If this restriction is lifted, then this test passes. But I really don't want to because it makes so little sense and complicates the code for choosing between possible hosts so much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better than nothing to have it work, but I do wonder how breaky this would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will try to support this specific case (only one possible host in the comment, template after typedef) if somebody complains about it. But I really don't think anybody expects this to work.
@@ -6,27 +6,22 @@ | |||
const MyComponent = () => /* @type {any} */(null); | |||
->MyComponent : StatelessComponent<MyComponentProps> | |||
->() => /* @type {any} */(null) : { (): any; defaultProps: Partial<MyComponentProps>; } | |||
+>MyComponent : { (): any; defaultProps?: P; } | |||
+>() => /* @type {any} */(null) : { (): any; defaultProps: { color: string; }; } | |||
+>MyComponent : { (): any; defaultProps?: Partial<{ color: "blue" | "red"; }>; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type printing still de-aliases typedef when it shouldn't. It's on my list of things to investigate.
*/ | ||
function flatMap(array, iterable = identity) { | ||
->flatMap : { <T, U>(array: T[], iterable: (x: T) => U[]): U[]; <T, U>(array: T[][]): T[]; } | ||
+>flatMap : { <T, U>(array: T[], iterable: (x: T) => U[]): U[]; (array: T[][]): T[]; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no type parameter sharing means that the second overload would need to add a @template T
in order to be correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it's going to end up being even more breaky...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@overload
is usually written with separated jsdoc comments to avoid the ambiguity that arises when looking for the end of a JSDocSignature, so most code will already be written in a way that requires the non-shared template tags (template tags are not shared between jsdoc comments)
+>flatMap : { <T, U>(array: T[], iterable: (x: T) => U[]): U[]; (array: T[][]): T[]; } | ||
>array : unknown[] | ||
>iterable : (x: unknown) => unknown | ||
->identity : <T_1>(x: T_1) => T_1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty sure T_1 was because the implementation signature had its own T in scope, but that's no longer shared.
* @typedef {import('./file1').Foo<T>} Bar | ||
- /** | ||
- * @template T | ||
- * @typedef {import('./file1').Foo<T>} Bar |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still not checking tags on EOF
+ export const x = () => 1 | ||
+ var res = x('a', 'b') | ||
+ ~~~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...T
no longer makes an argument with its type into a rest type
- } | ||
- var z = new Zet(1) | ||
- z.t = 2 | ||
- z.u = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still no constructor functions
+ /** | ||
+ * @template const T | ||
+ ~~~~~ | ||
+!!! error TS1277: 'const' modifier can only appear on a type parameter of a function, method or class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh nooo, I completely forgot about this feature. Not sure what is broken here exactly.
>flatMap : { <U>(): any; (): any; } | ||
function flatMap(array, iterable = identity) { | ||
->flatMap : { <U>(): any; (): any; } | ||
+>flatMap : { (): any; (): any; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no more sharing (unintended in this case, so it's an improvement)
>dibbity : T1 | ||
>dibbity : T1 | ||
|
||
test(1) // ok, T=1 | ||
>test(1) : 1 | ||
->test : Test<number> | ||
+>test : <T1 extends T>(data: T1) => T1 | ||
+>test : <T1 extends number>(data: T1) => T1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type printing still following aliases incorrectly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going to approve since it seems like it's net improving things, but the behavior changes do seem unfortunate and likely to bite us later
@@ -27,6 +28,8 @@ | |||
+ * @template A | |||
+ * @template {Record<string, unknown>} B | |||
+ * @param {Funcs<A, B>} fns | |||
+ ~~~~~~~~~~~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better than nothing to have it work, but I do wonder how breaky this would be.
*/ | ||
function flatMap(array, iterable = identity) { | ||
->flatMap : { <T, U>(array: T[], iterable: (x: T) => U[]): U[]; <T, U>(array: T[][]): T[]; } | ||
+>flatMap : { <T, U>(array: T[], iterable: (x: T) => U[]): U[]; (array: T[][]): T[]; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it's going to end up being even more breaky...
@@ -17,7 +17,7 @@ | |||
*/ | |||
transform(fn) { | |||
->transform : { <U>(fn: (y: T) => U): U; <U>(): T; } | |||
+>transform : { <U>(fn: (y: T) => U): U; <U_1>(): T; } | |||
+>transform : { <U>(fn: (y: T) => U): U; (): T; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's that sharing thing again. I feel like it can't be good to break it...
const f2 = async str => { | ||
+ ~~ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
complex and very hard to support
this will not stop me from asking for the feature back 🫠
This PR adds support for the
@callback
tag. More importantly, it fixes type parameter resolution and number of other bugs in@typedef
and@template
. It does some of this by simplifying the rules for how@template
attaches its type parameter to its host.Specifically, all
@template
tags would previously apply to all@overloads
,@typedef
, function declarations, etc, that they shared a jsdoc with. They didn't even have to come before their usage:Not only is this nonsensical, it's really hard to implement, and I don't think people write their types in one single jsdoc like this in the first place. Corsa, as implemented in this PR, requires
@template
to precede its usage and for it to apply in only one place:This will break any intentional sharing, but those will have to be rewritten. Sharing reparsed nodes to more than one other place in the tree would require
Host
to be a slice, plus some way to choose the correct host when resolving names.Speaking of
Host
,JSDocTemplateTag
now has a host, and the grossly-namedGetEffectiveTypeParent
now has two cases. It's also used consistently in the 5 places it makes sense. I do think we're nearing the total number of places it'll be needed, but there are 512 uses ofNode.Parent
in the checker alone, so I could be wrong.Despite the improvements in typing, there is little code churn outside reparser.go. Inside, I refactored callback/typedef/overload quite a bit to share code, so there are almost 200 lines changed. Best reviewed ignoring whitespace, as always.