@@ -112,6 +112,7 @@ func Test(t *testing.T) {
112112 test := test
113113 t .Run (test .name , func (t * testing.T ) {
114114 t .Parallel ()
115+
115116 if test .skipReason != "" {
116117 t .Skip (test .skipReason )
117118 }
@@ -154,6 +155,7 @@ func Test(t *testing.T) {
154155 }
155156 testenv .NeedsTool (t , "cgo" )
156157 }
158+
157159 config := fake.EditorConfig {
158160 Settings : test .settings ,
159161 CapabilitiesJSON : test .capabilities ,
@@ -177,6 +179,7 @@ func Test(t *testing.T) {
177179 diags : make (map [protocol.Location ][]protocol.Diagnostic ),
178180 extraNotes : make (map [protocol.DocumentURI ]map [string ][]* expect.Note ),
179181 }
182+
180183 // TODO(rfindley): make it easier to clean up the integration test environment.
181184 defer run .env .Editor .Shutdown (context .Background ()) // ignore error
182185 defer run .env .Sandbox .Close () // ignore error
@@ -346,7 +349,16 @@ func (mark marker) mapper() *protocol.Mapper {
346349 return mapper
347350}
348351
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.
350362//
351363// It formats the error message using mark.sprintf.
352364func (mark marker ) errorf (format string , args ... any ) {
@@ -402,7 +414,7 @@ func valueMarkerFunc(fn any) func(marker) {
402414 args := append ([]any {mark }, mark .note .Args [1 :]... )
403415 argValues , err := convertArgs (mark , ftype , args )
404416 if err != nil {
405- mark .errorf ( "converting args: %v" , err )
417+ mark .error ( err )
406418 return
407419 }
408420 results := reflect .ValueOf (fn ).Call (argValues )
@@ -445,7 +457,7 @@ func actionMarkerFunc(fn any, allowedNames ...string) func(marker) {
445457 args := append ([]any {mark }, mark .note .Args ... )
446458 argValues , err := convertArgs (mark , ftype , args )
447459 if err != nil {
448- mark .errorf ( "converting args: %v" , err )
460+ mark .error ( err )
449461 return
450462 }
451463 reflect .ValueOf (fn ).Call (argValues )
@@ -540,9 +552,10 @@ func is[T any](arg any) bool {
540552
541553// Supported value marker functions. See [valueMarkerFunc] for more details.
542554var 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 ),
546559}
547560
548561// Supported action marker functions. See [actionMarkerFunc] for more details.
@@ -1029,22 +1042,10 @@ func (run *markerTestRun) fmtPos(pos token.Pos) string {
10291042// archive-relative paths for files and including the line number in the full
10301043// archive file.
10311044func (run * markerTestRun ) fmtLoc (loc protocol.Location ) string {
1032- formatted := run .fmtLocDetails (loc , true )
1033- if formatted == "" {
1045+ if loc == (protocol.Location {}) {
10341046 run .env .T .Errorf ("unable to find %s in test archive" , loc )
10351047 return "<invalid location>"
10361048 }
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- }
10481049 lines := bytes .Count (run .test .archive .Comment , []byte ("\n " ))
10491050 var name string
10501051 for _ , f := range run .test .archive .Files {
@@ -1057,39 +1058,74 @@ func (run *markerTestRun) fmtLocDetails(loc protocol.Location, includeTxtPos boo
10571058 lines += bytes .Count (f .Data , []byte ("\n " ))
10581059 }
10591060 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 )
10621083 m , err := run .env .Editor .Mapper (name )
10631084 if err != nil {
10641085 run .env .T .Errorf ("internal error: %v" , err )
1065- return "<invalid location>"
1086+ return
10661087 }
10671088 start , end , err := m .RangeOffsets (loc .Range )
10681089 if err != nil {
10691090 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 {}) {
10701103 return "<invalid location>"
10711104 }
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>"
10861112 }
1113+ return summarizeLoc (run .mapLocation (loc ))
1114+ }
10871115
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 )
10921127 }
1128+ return span
10931129}
10941130
10951131// ---- converters ----
@@ -1144,7 +1180,7 @@ func convert(mark marker, arg any, paramType reflect.Type) (any, error) {
11441180 if converter , ok := customConverters [paramType ]; ok {
11451181 arg2 , err := converter (mark , arg )
11461182 if err != nil {
1147- return nil , fmt . Errorf ( "converting for input type %T to %v: %v" , arg , paramType , err )
1183+ return nil , err
11481184 }
11491185 arg = arg2
11501186 }
@@ -1763,10 +1799,15 @@ func hoverErrMarker(mark marker, src protocol.Location, em stringMatcher) {
17631799 em .checkErr (mark , err )
17641800}
17651801
1766- // locMarker implements the @loc marker. It is executed before other
1767- // markers, so that locations are available.
1802+ // locMarker implements the @loc marker.
17681803func locMarker (mark marker , loc protocol.Location ) protocol.Location { return loc }
17691804
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+
17701811// diagMarker implements the @diag marker. It eliminates diagnostics from
17711812// the observed set in mark.test.
17721813func diagMarker (mark marker , loc protocol.Location , re * regexp.Regexp ) {
@@ -2101,7 +2142,7 @@ func documentLinkMarker(mark marker, g *Golden) {
21012142 continue
21022143 }
21032144 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 )
21052146 }
21062147
21072148 compareGolden (mark , b .Bytes (), g )
@@ -2554,9 +2595,7 @@ func workspaceSymbolMarker(mark marker, query string, golden *Golden) {
25542595 for _ , s := range gotSymbols {
25552596 // Omit the txtar position of the symbol location; otherwise edits to the
25562597 // 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 )
25602599 if loc == "" {
25612600 loc = "<unknown>"
25622601 }
0 commit comments