From 5f197976ee0116ee7e377de8219078c7521f6496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 24 Mar 2026 20:12:02 +0100 Subject: [PATCH 1/5] Fix error spans for `@satisfies` --- internal/checker/checker.go | 3 +-- internal/scanner/scanner.go | 18 ++++++++++++---- .../checkJsdocSatisfiesTag1.errors.txt | 4 ++-- .../checkJsdocSatisfiesTag1.errors.txt.diff | 8 +++---- .../checkJsdocSatisfiesTag12.errors.txt | 8 +++---- .../checkJsdocSatisfiesTag12.errors.txt.diff | 21 +++++-------------- .../checkJsdocSatisfiesTag4.errors.txt | 4 ++-- .../checkJsdocSatisfiesTag4.errors.txt.diff | 8 +++---- 8 files changed, 34 insertions(+), 40 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 6f070f102ec..a67573f4368 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -10331,8 +10331,7 @@ func (c *Checker) checkSatisfiesExpression(node *ast.Node) *Type { if c.isErrorType(targetType) { return targetType } - errorNode := core.IfElse(typeNode.Flags&ast.NodeFlagsReparsed != 0, typeNode, node) - c.checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, errorNode, node.Expression(), diagnostics.Type_0_does_not_satisfy_the_expected_type_1, nil) + c.checkTypeAssignableToAndOptionallyElaborate(exprType, targetType, node, node.Expression(), diagnostics.Type_0_does_not_satisfy_the_expected_type_1, nil) return exprType } diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index aec39a8b4b4..264e96be5d8 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -2535,6 +2535,20 @@ func GetErrorRangeForNode(sourceFile *ast.SourceFile, node *ast.Node) core.TextR pos := SkipTrivia(sourceFile.Text(), node.Pos()) return GetRangeOfTokenAtPosition(sourceFile, pos) case ast.KindSatisfiesExpression: + if node.AsSatisfiesExpression().Type.Flags&ast.NodeFlagsReparsed != 0 { + 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) + } + } + } + } + } + } pos := SkipTrivia(sourceFile.Text(), node.AsSatisfiesExpression().Expression.End()) return GetRangeOfTokenAtPosition(sourceFile, pos) case ast.KindConstructor: @@ -2548,10 +2562,6 @@ func GetErrorRangeForNode(sourceFile *ast.SourceFile, node *ast.Node) core.TextR scanner.Scan() } return core.NewTextRange(start, scanner.TokenEnd()) - // !!! - // case KindJSDocSatisfiesTag: - // pos := scanner.SkipTrivia(sourceFile.Text(), node.tagName.pos) - // return scanner.GetRangeOfTokenAtPosition(sourceFile, pos) } if errorNode == nil { // If we don't have a better node, then just set the error on the first token of diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt index d9dbd2c6e51..f6d39be52d4 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt @@ -1,5 +1,5 @@ /a.js(21,44): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. -/a.js(22,28): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. +/a.js(22,17): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. /a.js(31,49): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T4'. @@ -28,7 +28,7 @@ ~ !!! error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. const t3 = /** @satisfies {T1} */ ({}); - ~~ + ~~~~~~~~~ !!! error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. !!! related TS2728 /a.js:3:23: 'a' is declared here. diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt.diff index 30a35d0db40..444ccf3f1dc 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag1.errors.txt.diff @@ -4,19 +4,17 @@ /a.js(21,44): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. -/a.js(22,17): error TS1360: Type '{}' does not satisfy the expected type 'T1'. - Property 'a' is missing in type '{}' but required in type 'T1'. -+/a.js(22,28): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. ++/a.js(22,17): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. /a.js(31,49): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T4'. -@@= skipped -28, +27 lines =@@ - ~ +@@= skipped -29, +28 lines =@@ !!! error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. const t3 = /** @satisfies {T1} */ ({}); -- ~~~~~~~~~ + ~~~~~~~~~ -!!! error TS1360: Type '{}' does not satisfy the expected type 'T1'. -!!! error TS1360: Property 'a' is missing in type '{}' but required in type 'T1'. -!!! related TS2728 /a.js:3:4: 'a' is declared here. -+ ~~ +!!! error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. +!!! related TS2728 /a.js:3:23: 'a' is declared here. diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt index 23f2335a68d..13365202edc 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt @@ -1,7 +1,7 @@ /a.js(24,20): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. -/a.js(27,16): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. +/a.js(27,5): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. /a.js(44,25): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T2'. -/a.js(51,17): error TS1360: Type 'number' does not satisfy the expected type 'string'. +/a.js(51,6): error TS1360: Type 'number' does not satisfy the expected type 'string'. ==== /a.js (4 errors) ==== @@ -34,7 +34,7 @@ /** * @satisfies {T1} - ~~ + ~~~~~~~~~ !!! error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. !!! related TS2728 /a.js:3:23: 'a' is declared here. */ @@ -63,6 +63,6 @@ const t7 = { a: "a" }; /** @satisfies {string} */ const t8 = (1); - ~~~~~~ + ~~~~~~~~~ !!! error TS1360: Type 'number' does not satisfy the expected type 'string'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt.diff index 9fb50fa561e..a10a0c758ae 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag12.errors.txt.diff @@ -2,15 +2,12 @@ +++ new.checkJsdocSatisfiesTag12.errors.txt @@= skipped -0, +0 lines =@@ /a.js(24,20): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T1'. -+/a.js(27,16): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. ++/a.js(27,5): error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. /a.js(44,25): error TS2353: Object literal may only specify known properties, and 'b' does not exist in type 'T2'. --/a.js(51,6): error TS1360: Type 'number' does not satisfy the expected type 'string'. -- -- + /a.js(51,6): error TS1360: Type 'number' does not satisfy the expected type 'string'. + + -==== /a.js (3 errors) ==== -+/a.js(51,17): error TS1360: Type 'number' does not satisfy the expected type 'string'. -+ -+ +==== /a.js (4 errors) ==== /** * @typedef {Object} T1 @@ -19,17 +16,9 @@ /** * @satisfies {T1} -+ ~~ ++ ~~~~~~~~~ +!!! error TS2741: Property 'a' is missing in type '{}' but required in type 'T1'. +!!! related TS2728 /a.js:3:23: 'a' is declared here. */ const t3 = {}; - -@@= skipped -26, +29 lines =@@ - const t7 = { a: "a" }; - - /** @satisfies {string} */ const t8 = (1); -- ~~~~~~~~~ -+ ~~~~~~ - !!! error TS1360: Type 'number' does not satisfy the expected type 'string'. \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt index c5445a8150d..4f08a1f03ef 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt @@ -1,4 +1,4 @@ -/a.js(5,32): error TS2741: Property 'a' is missing in type '{}' but required in type 'Foo'. +/a.js(5,21): error TS2741: Property 'a' is missing in type '{}' but required in type 'Foo'. ==== /a.js (1 errors) ==== @@ -7,7 +7,7 @@ * @property {number} a */ export default /** @satisfies {Foo} */ ({}); - ~~~ + ~~~~~~~~~ !!! error TS2741: Property 'a' is missing in type '{}' but required in type 'Foo'. !!! related TS2728 /a.js:3:23: 'a' is declared here. diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt.diff index 3d8e1e84b39..acedf400751 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag4.errors.txt.diff @@ -3,19 +3,17 @@ @@= skipped -0, +0 lines =@@ -/a.js(5,21): error TS1360: Type '{}' does not satisfy the expected type 'Foo'. - Property 'a' is missing in type '{}' but required in type 'Foo'. -+/a.js(5,32): error TS2741: Property 'a' is missing in type '{}' but required in type 'Foo'. ++/a.js(5,21): error TS2741: Property 'a' is missing in type '{}' but required in type 'Foo'. ==== /a.js (1 errors) ==== -@@= skipped -7, +6 lines =@@ - * @property {number} a +@@= skipped -8, +7 lines =@@ */ export default /** @satisfies {Foo} */ ({}); -- ~~~~~~~~~ + ~~~~~~~~~ -!!! error TS1360: Type '{}' does not satisfy the expected type 'Foo'. -!!! error TS1360: Property 'a' is missing in type '{}' but required in type 'Foo'. -!!! related TS2728 /a.js:3:4: 'a' is declared here. -+ ~~~ +!!! error TS2741: Property 'a' is missing in type '{}' but required in type 'Foo'. +!!! related TS2728 /a.js:3:23: 'a' is declared here. From 165fd4007fbd8a08d1063e40b2f865c9d1911710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 18 May 2026 09:25:09 +0200 Subject: [PATCH 2/5] move to util --- internal/scanner/scanner.go | 48 +++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index af57dff68da..023b46b0e4e 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -2503,6 +2503,38 @@ func getErrorRangeForArrowFunction(sourceFile *ast.SourceFile, node *ast.Node) c return core.NewTextRange(pos, node.End()) } +func findOriginatingJSDocSatisfiesTag(sourceFile *ast.SourceFile, node *ast.Node) *ast.Node { + targetType := node.AsSatisfiesExpression().Type + if targetType.Flags&ast.NodeFlagsReparsed == 0 { + return nil + } + for current := node.Parent; current != nil; current = current.Parent { + if current.Flags&ast.NodeFlagsHasJSDoc == 0 { + continue + } + var firstSatisfiesTag *ast.Node + for _, jsDoc := range current.JSDoc(sourceFile) { + if tags := jsDoc.AsJSDoc().Tags; tags != nil { + for _, tag := range tags.Nodes { + if !ast.IsJSDocSatisfiesTag(tag) { + continue + } + if firstSatisfiesTag == nil { + firstSatisfiesTag = tag + } + if typeExpr := tag.AsJSDocSatisfiesTag().TypeExpression; typeExpr != nil { + if t := typeExpr.Type(); t != nil && t.Loc == targetType.Loc { + return tag + } + } + } + } + } + return firstSatisfiesTag + } + return nil +} + func GetErrorRangeForNode(sourceFile *ast.SourceFile, node *ast.Node) core.TextRange { errorNode := node switch node.Kind { @@ -2541,19 +2573,9 @@ func GetErrorRangeForNode(sourceFile *ast.SourceFile, node *ast.Node) core.TextR pos := SkipTrivia(sourceFile.Text(), node.Pos()) return GetRangeOfTokenAtPosition(sourceFile, pos) case ast.KindSatisfiesExpression: - if node.AsSatisfiesExpression().Type.Flags&ast.NodeFlagsReparsed != 0 { - 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) - } - } - } - } - } + if jsDocSatisfiesTag := findOriginatingJSDocSatisfiesTag(sourceFile, node); jsDocSatisfiesTag != nil { + pos := SkipTrivia(sourceFile.Text(), jsDocSatisfiesTag.TagName().Pos()) + return GetRangeOfTokenAtPosition(sourceFile, pos) } pos := SkipTrivia(sourceFile.Text(), node.AsSatisfiesExpression().Expression.End()) return GetRangeOfTokenAtPosition(sourceFile, pos) From 1c50689f0e144c4feaa3c8150cd2707a4177414e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 18 May 2026 18:06:24 +0200 Subject: [PATCH 3/5] generate --- .../jsdocSatisfiesTagWithParamTag.errors.txt | 4 ++-- .../checkJsdocSatisfiesTag15.errors.txt.diff | 18 ------------------ 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt.diff diff --git a/testdata/baselines/reference/compiler/jsdocSatisfiesTagWithParamTag.errors.txt b/testdata/baselines/reference/compiler/jsdocSatisfiesTagWithParamTag.errors.txt index fc08b92f658..db5c0fab15a 100644 --- a/testdata/baselines/reference/compiler/jsdocSatisfiesTagWithParamTag.errors.txt +++ b/testdata/baselines/reference/compiler/jsdocSatisfiesTagWithParamTag.errors.txt @@ -1,4 +1,4 @@ -/a.js(9,16): error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. +/a.js(9,5): error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. Types of parameters 'b' and 'args' are incompatible. Type 'number' is not assignable to type 'string'. @@ -13,7 +13,7 @@ /** * @satisfies {(a: string, ...args: number[]) => void} - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~ !!! error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. !!! error TS1360: Types of parameters 'b' and 'args' are incompatible. !!! error TS1360: Type 'number' is not assignable to type 'string'. diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt.diff deleted file mode 100644 index 3fca8047981..00000000000 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt.diff +++ /dev/null @@ -1,18 +0,0 @@ ---- old.checkJsdocSatisfiesTag15.errors.txt -+++ new.checkJsdocSatisfiesTag15.errors.txt -@@= skipped -0, +0 lines =@@ - /a.js(9,20): error TS2322: Type 'number' is not assignable to type 'string'. --/a.js(28,5): error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. -+/a.js(28,16): error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. - Types of parameters 'b' and 'args' are incompatible. - Type 'number' is not assignable to type 'string'. - /a.js(42,21): error TS7006: Parameter 'uuid' implicitly has an 'any' type. -@@= skipped -35, +35 lines =@@ - - /** - * @satisfies {(a: string, ...args: number[]) => void} -- ~~~~~~~~~ -+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - !!! error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. - !!! error TS1360: Types of parameters 'b' and 'args' are incompatible. - !!! error TS1360: Type 'number' is not assignable to type 'string'. \ No newline at end of file From 642fc0cba56be7153f12e86e0c670b305fa2569f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 19 May 2026 16:54:23 +0200 Subject: [PATCH 4/5] update baseline --- .../submodule/conformance/checkJsdocSatisfiesTag15.errors.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt index eee6000eb69..40ceaa21a4e 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocSatisfiesTag15.errors.txt @@ -1,5 +1,5 @@ /a.js(9,20): error TS2322: Type 'number' is not assignable to type 'string'. -/a.js(28,16): error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. +/a.js(28,5): error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. Types of parameters 'b' and 'args' are incompatible. Type 'number' is not assignable to type 'string'. /a.js(42,21): error TS7006: Parameter 'uuid' implicitly has an 'any' type. @@ -36,7 +36,7 @@ /** * @satisfies {(a: string, ...args: number[]) => void} - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~ !!! error TS1360: Type '(a: string, b: string) => void' does not satisfy the expected type '(a: string, ...args: number[]) => void'. !!! error TS1360: Types of parameters 'b' and 'args' are incompatible. !!! error TS1360: Type 'number' is not assignable to type 'string'. From 3a37812077f6d67e6141d7dbdb42c24e882cc5ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 19 May 2026 17:17:23 +0200 Subject: [PATCH 5/5] use EagerJSDoc --- internal/scanner/scanner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 023b46b0e4e..7a34c47c0db 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -2513,7 +2513,7 @@ func findOriginatingJSDocSatisfiesTag(sourceFile *ast.SourceFile, node *ast.Node continue } var firstSatisfiesTag *ast.Node - for _, jsDoc := range current.JSDoc(sourceFile) { + for _, jsDoc := range current.EagerJSDoc(sourceFile) { if tags := jsDoc.AsJSDoc().Tags; tags != nil { for _, tag := range tags.Nodes { if !ast.IsJSDocSatisfiesTag(tag) {