Skip to content

Fix error spans for @satisfies#3212

Open
Andarist wants to merge 1 commit intomicrosoft:mainfrom
Andarist:fix/match-satisfies-error-span-highlight
Open

Fix error spans for @satisfies#3212
Andarist wants to merge 1 commit intomicrosoft:mainfrom
Andarist:fix/match-satisfies-error-span-highlight

Conversation

@Andarist
Copy link
Copy Markdown
Contributor

No description provided.

Comment on lines +2539 to +2550
for current := node.Parent; current != nil; current = current.Parent {
for _, jsDoc := range current.JSDoc(nil) {
if tags := jsDoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if ast.IsJSDocSatisfiesTag(tag) {
pos := SkipTrivia(sourceFile.Text(), tag.TagName().Pos())
return GetRangeOfTokenAtPosition(sourceFile, pos)
}
}
}
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not super fond of this but I couldn't find a better way to go from a reparsed node to the original JSDoc right now, and I tried a bunch. Am I missing something?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not sure there's anything better, given we are in the scanner package and therefore if such a helper existed in astnav it would be unavailable.

But maybe @gabritto / @andrewbranch have a better thought on their minds.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adjusts diagnostic locations for JSDoc @satisfies so errors point at the satisfies tag (and related spans/columns) instead of the reparsed type node, aligning baseline expectations for conformance tests.

Changes:

  • Update GetErrorRangeForNode to map reparsed SatisfiesExpression errors back to the JSDoc @satisfies tag.
  • Change checkSatisfiesExpression to report assignability diagnostics on the SatisfiesExpression node (enabling the new error range behavior).
  • Refresh reference baseline outputs/diffs for several checkJsdocSatisfiesTag* conformance cases.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/scanner/scanner.go Special-cases error ranges for SatisfiesExpression with reparsed types to target the JSDoc @satisfies tag.
internal/checker/checker.go Reports satisfies assignability diagnostics on the expression node to use the updated error-span logic.
testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt Updates expected error location/underline for @satisfies case.
testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt.diff Updates divergence diff for the same case.
testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt Updates expected error location/underline for @satisfies case.
testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt.diff Updates divergence diff for the same case.
testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt Updates expected error locations/underlines for multiple @satisfies scenarios.
testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt.diff Updates divergence diff for the same cases.

Comment on lines +2539 to +2550
for current := node.Parent; current != nil; current = current.Parent {
for _, jsDoc := range current.JSDoc(nil) {
if tags := jsDoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if ast.IsJSDocSatisfiesTag(tag) {
pos := SkipTrivia(sourceFile.Text(), tag.TagName().Pos())
return GetRangeOfTokenAtPosition(sourceFile, pos)
}
}
}
}
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

When the satisfies type is reparsed, this returns the first @satisfies tag found on any ancestor JSDoc. If a JSDoc comment contains multiple @satisfies tags (which the reparser will apply by wrapping the initializer multiple times), this can highlight the wrong tag for inner/outer SatisfiesExpression nodes. Consider selecting the tag whose TypeExpression.Type().Loc (or pos/end) matches node.AsSatisfiesExpression().Type.Loc, and only fall back to the first tag if no match is found.

Suggested change
for current := node.Parent; current != nil; current = current.Parent {
for _, jsDoc := range current.JSDoc(nil) {
if tags := jsDoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if ast.IsJSDocSatisfiesTag(tag) {
pos := SkipTrivia(sourceFile.Text(), tag.TagName().Pos())
return GetRangeOfTokenAtPosition(sourceFile, pos)
}
}
}
}
}
targetType := node.AsSatisfiesExpression().Type
var firstSatisfiesTag *ast.Node
for current := node.Parent; current != nil; current = current.Parent {
for _, jsDoc := range current.JSDoc(nil) {
if tags := jsDoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if ast.IsJSDocSatisfiesTag(tag) {
if firstSatisfiesTag == nil {
firstSatisfiesTag = tag
}
typeExpr := tag.AsJSDocSatisfiesTag().TypeExpression()
if typeExpr != nil {
if typeNode := typeExpr.Type(); typeNode != nil {
if typeNode.Pos() == targetType.Pos() && typeNode.End() == targetType.End() {
pos := SkipTrivia(sourceFile.Text(), tag.TagName().Pos())
return GetRangeOfTokenAtPosition(sourceFile, pos)
}
}
}
}
}
}
}
}
if firstSatisfiesTag != nil {
pos := SkipTrivia(sourceFile.Text(), firstSatisfiesTag.TagName().Pos())
return GetRangeOfTokenAtPosition(sourceFile, pos)
}

Copilot uses AI. Check for mistakes.
Comment on lines +2539 to +2550
for current := node.Parent; current != nil; current = current.Parent {
for _, jsDoc := range current.JSDoc(nil) {
if tags := jsDoc.AsJSDoc().Tags; tags != nil {
for _, tag := range tags.Nodes {
if ast.IsJSDocSatisfiesTag(tag) {
pos := SkipTrivia(sourceFile.Text(), tag.TagName().Pos())
return GetRangeOfTokenAtPosition(sourceFile, pos)
}
}
}
}
}
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

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

current.JSDoc(nil) triggers a GetSourceFileOfNode walk each time (since file is nil), and this code is already walking up the parent chain. This makes the reparsed-@satisfies path potentially O(depth^2). Consider computing sourceFile := ast.GetSourceFileOfNode(node) once and using current.JSDoc(sourceFile) inside the loop.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants