@@ -112,6 +112,7 @@ func Test(t *testing.T) {
112
112
test := test
113
113
t .Run (test .name , func (t * testing.T ) {
114
114
t .Parallel ()
115
+
115
116
if test .skipReason != "" {
116
117
t .Skip (test .skipReason )
117
118
}
@@ -154,6 +155,7 @@ func Test(t *testing.T) {
154
155
}
155
156
testenv .NeedsTool (t , "cgo" )
156
157
}
158
+
157
159
config := fake.EditorConfig {
158
160
Settings : test .settings ,
159
161
CapabilitiesJSON : test .capabilities ,
@@ -177,6 +179,7 @@ func Test(t *testing.T) {
177
179
diags : make (map [protocol.Location ][]protocol.Diagnostic ),
178
180
extraNotes : make (map [protocol.DocumentURI ]map [string ][]* expect.Note ),
179
181
}
182
+
180
183
// TODO(rfindley): make it easier to clean up the integration test environment.
181
184
defer run .env .Editor .Shutdown (context .Background ()) // ignore error
182
185
defer run .env .Sandbox .Close () // ignore error
@@ -346,7 +349,16 @@ func (mark marker) mapper() *protocol.Mapper {
346
349
return mapper
347
350
}
348
351
349
- // errorf reports an error with a prefix indicating the position of the marker note.
352
+ // error reports an error with a prefix indicating the position of the marker
353
+ // note.
354
+ func (mark marker ) error (args ... any ) {
355
+ mark .T ().Helper ()
356
+ msg := fmt .Sprint (args ... )
357
+ mark .T ().Errorf ("%s: %s" , mark .run .fmtPos (mark .note .Pos ), msg )
358
+ }
359
+
360
+ // errorf reports a formatted error with a prefix indicating the position of
361
+ // the marker note.
350
362
//
351
363
// It formats the error message using mark.sprintf.
352
364
func (mark marker ) errorf (format string , args ... any ) {
@@ -402,7 +414,7 @@ func valueMarkerFunc(fn any) func(marker) {
402
414
args := append ([]any {mark }, mark .note .Args [1 :]... )
403
415
argValues , err := convertArgs (mark , ftype , args )
404
416
if err != nil {
405
- mark .errorf ( "converting args: %v" , err )
417
+ mark .error ( err )
406
418
return
407
419
}
408
420
results := reflect .ValueOf (fn ).Call (argValues )
@@ -445,7 +457,7 @@ func actionMarkerFunc(fn any, allowedNames ...string) func(marker) {
445
457
args := append ([]any {mark }, mark .note .Args ... )
446
458
argValues , err := convertArgs (mark , ftype , args )
447
459
if err != nil {
448
- mark .errorf ( "converting args: %v" , err )
460
+ mark .error ( err )
449
461
return
450
462
}
451
463
reflect .ValueOf (fn ).Call (argValues )
@@ -540,9 +552,10 @@ func is[T any](arg any) bool {
540
552
541
553
// Supported value marker functions. See [valueMarkerFunc] for more details.
542
554
var valueMarkerFuncs = map [string ]func (marker ){
543
- "loc" : valueMarkerFunc (locMarker ),
544
- "item" : valueMarkerFunc (completionItemMarker ),
545
- "hiloc" : valueMarkerFunc (highlightLocationMarker ),
555
+ "loc" : valueMarkerFunc (locMarker ),
556
+ "item" : valueMarkerFunc (completionItemMarker ),
557
+ "hiloc" : valueMarkerFunc (highlightLocationMarker ),
558
+ "defloc" : valueMarkerFunc (defLocMarker ),
546
559
}
547
560
548
561
// Supported action marker functions. See [actionMarkerFunc] for more details.
@@ -1029,22 +1042,10 @@ func (run *markerTestRun) fmtPos(pos token.Pos) string {
1029
1042
// archive-relative paths for files and including the line number in the full
1030
1043
// archive file.
1031
1044
func (run * markerTestRun ) fmtLoc (loc protocol.Location ) string {
1032
- formatted := run .fmtLocDetails (loc , true )
1033
- if formatted == "" {
1045
+ if loc == (protocol.Location {}) {
1034
1046
run .env .T .Errorf ("unable to find %s in test archive" , loc )
1035
1047
return "<invalid location>"
1036
1048
}
1037
- return formatted
1038
- }
1039
-
1040
- // See fmtLoc. If includeTxtPos is not set, the position in the full archive
1041
- // file is omitted.
1042
- //
1043
- // If the location cannot be found within the archive, fmtLocDetails returns "".
1044
- func (run * markerTestRun ) fmtLocDetails (loc protocol.Location , includeTxtPos bool ) string {
1045
- if loc == (protocol.Location {}) {
1046
- return ""
1047
- }
1048
1049
lines := bytes .Count (run .test .archive .Comment , []byte ("\n " ))
1049
1050
var name string
1050
1051
for _ , f := range run .test .archive .Files {
@@ -1057,39 +1058,74 @@ func (run *markerTestRun) fmtLocDetails(loc protocol.Location, includeTxtPos boo
1057
1058
lines += bytes .Count (f .Data , []byte ("\n " ))
1058
1059
}
1059
1060
if name == "" {
1060
- return ""
1061
- }
1061
+ // Fall back to formatting the "lsp" location.
1062
+ // These will be in UTF-16, but we probably don't need to clarify that,
1063
+ // since it will be implied by the file:// URI format.
1064
+ return summarizeLoc (string (loc .URI ),
1065
+ int (loc .Range .Start .Line ), int (loc .Range .Start .Character ),
1066
+ int (loc .Range .End .Line ), int (loc .Range .End .Character ))
1067
+ }
1068
+ name , startLine , startCol , endLine , endCol := run .mapLocation (loc )
1069
+ innerSpan := summarizeLoc (name , startLine , startCol , endLine , endCol )
1070
+ outerSpan := summarizeLoc (run .test .name , lines + startLine , startCol , lines + endLine , endCol )
1071
+ return fmt .Sprintf ("%s (%s)" , innerSpan , outerSpan )
1072
+ }
1073
+
1074
+ // mapLocation returns the relative path and utf8 span of the corresponding
1075
+ // location, which must be a valid location in an archive file.
1076
+ func (run * markerTestRun ) mapLocation (loc protocol.Location ) (name string , startLine , startCol , endLine , endCol int ) {
1077
+ // Note: Editor.Mapper fails if loc.URI is not open, but we always open all
1078
+ // archive files, so this is probably OK.
1079
+ //
1080
+ // In the future, we may want to have the editor read contents from disk if
1081
+ // the URI is not open.
1082
+ name = run .env .Sandbox .Workdir .URIToPath (loc .URI )
1062
1083
m , err := run .env .Editor .Mapper (name )
1063
1084
if err != nil {
1064
1085
run .env .T .Errorf ("internal error: %v" , err )
1065
- return "<invalid location>"
1086
+ return
1066
1087
}
1067
1088
start , end , err := m .RangeOffsets (loc .Range )
1068
1089
if err != nil {
1069
1090
run .env .T .Errorf ("error formatting location %s: %v" , loc , err )
1091
+ return
1092
+ }
1093
+ startLine , startCol = m .OffsetLineCol8 (start )
1094
+ endLine , endCol = m .OffsetLineCol8 (end )
1095
+ return name , startLine , startCol , endLine , endCol
1096
+ }
1097
+
1098
+ // fmtLocForGolden is like fmtLoc, but chooses more succinct and stable
1099
+ // formatting, such as would be used for formatting locations in Golden
1100
+ // content.
1101
+ func (run * markerTestRun ) fmtLocForGolden (loc protocol.Location ) string {
1102
+ if loc == (protocol.Location {}) {
1070
1103
return "<invalid location>"
1071
1104
}
1072
- var (
1073
- startLine , startCol8 = m .OffsetLineCol8 (start )
1074
- endLine , endCol8 = m .OffsetLineCol8 (end )
1075
- )
1076
- innerSpan := fmt .Sprintf ("%d:%d" , startLine , startCol8 ) // relative to the embedded file
1077
- outerSpan := fmt .Sprintf ("%d:%d" , lines + startLine , startCol8 ) // relative to the archive file
1078
- if start != end {
1079
- if endLine == startLine {
1080
- innerSpan += fmt .Sprintf ("-%d" , endCol8 )
1081
- outerSpan += fmt .Sprintf ("-%d" , endCol8 )
1082
- } else {
1083
- innerSpan += fmt .Sprintf ("-%d:%d" , endLine , endCol8 )
1084
- outerSpan += fmt .Sprintf ("-%d:%d" , lines + endLine , endCol8 )
1085
- }
1105
+ name := run .env .Sandbox .Workdir .URIToPath (loc .URI )
1106
+ // Note: we check IsAbs on filepaths rather than the slash-ified name for
1107
+ // accurate handling of windows drive letters.
1108
+ if filepath .IsAbs (filepath .FromSlash (name )) {
1109
+ // Don't format any position information in this case, since it will be
1110
+ // volatile.
1111
+ return "<external>"
1086
1112
}
1113
+ return summarizeLoc (run .mapLocation (loc ))
1114
+ }
1087
1115
1088
- if includeTxtPos {
1089
- return fmt .Sprintf ("%s:%s (%s:%s)" , name , innerSpan , run .test .name , outerSpan )
1090
- } else {
1091
- return fmt .Sprintf ("%s:%s" , name , innerSpan )
1116
+ // summarizeLoc formats a summary of the given location, in the form
1117
+ //
1118
+ // <name>:<startLine>:<startCol>[-[<endLine>:]endCol]
1119
+ func summarizeLoc (name string , startLine , startCol , endLine , endCol int ) string {
1120
+ span := fmt .Sprintf ("%s:%d:%d" , name , startLine , startCol )
1121
+ if startLine != endLine || startCol != endCol {
1122
+ span += "-"
1123
+ if endLine != startLine {
1124
+ span += fmt .Sprintf ("%d:" , endLine )
1125
+ }
1126
+ span += fmt .Sprintf ("%d" , endCol )
1092
1127
}
1128
+ return span
1093
1129
}
1094
1130
1095
1131
// ---- converters ----
@@ -1144,7 +1180,7 @@ func convert(mark marker, arg any, paramType reflect.Type) (any, error) {
1144
1180
if converter , ok := customConverters [paramType ]; ok {
1145
1181
arg2 , err := converter (mark , arg )
1146
1182
if err != nil {
1147
- return nil , fmt . Errorf ( "converting for input type %T to %v: %v" , arg , paramType , err )
1183
+ return nil , err
1148
1184
}
1149
1185
arg = arg2
1150
1186
}
@@ -1763,10 +1799,15 @@ func hoverErrMarker(mark marker, src protocol.Location, em stringMatcher) {
1763
1799
em .checkErr (mark , err )
1764
1800
}
1765
1801
1766
- // locMarker implements the @loc marker. It is executed before other
1767
- // markers, so that locations are available.
1802
+ // locMarker implements the @loc marker.
1768
1803
func locMarker (mark marker , loc protocol.Location ) protocol.Location { return loc }
1769
1804
1805
+ // defLocMarker implements the @defloc marker, which binds a location to the
1806
+ // (first) result of a jump-to-definition request.
1807
+ func defLocMarker (mark marker , loc protocol.Location ) protocol.Location {
1808
+ return mark .run .env .GoToDefinition (loc )
1809
+ }
1810
+
1770
1811
// diagMarker implements the @diag marker. It eliminates diagnostics from
1771
1812
// the observed set in mark.test.
1772
1813
func diagMarker (mark marker , loc protocol.Location , re * regexp.Regexp ) {
@@ -2101,7 +2142,7 @@ func documentLinkMarker(mark marker, g *Golden) {
2101
2142
continue
2102
2143
}
2103
2144
loc := protocol.Location {URI : mark .uri (), Range : l .Range }
2104
- fmt .Fprintln (& b , mark .run .fmtLocDetails (loc , false ), * l .Target )
2145
+ fmt .Fprintln (& b , mark .run .fmtLocForGolden (loc ), * l .Target )
2105
2146
}
2106
2147
2107
2148
compareGolden (mark , b .Bytes (), g )
@@ -2554,9 +2595,7 @@ func workspaceSymbolMarker(mark marker, query string, golden *Golden) {
2554
2595
for _ , s := range gotSymbols {
2555
2596
// Omit the txtar position of the symbol location; otherwise edits to the
2556
2597
// txtar archive lead to unexpected failures.
2557
- loc := mark .run .fmtLocDetails (s .Location , false )
2558
- // TODO(rfindley): can we do better here, by detecting if the location is
2559
- // relative to GOROOT?
2598
+ loc := mark .run .fmtLocForGolden (s .Location )
2560
2599
if loc == "" {
2561
2600
loc = "<unknown>"
2562
2601
}
0 commit comments