Skip to content

Commit 6b0cfff

Browse files
findleyrgopherbot
authored andcommitted
internal/test/marker: support multi-line locations
Address a long-standing TODO to permit multi-line locations in the codeactionedit marker. This should unblock use of codeactionedit with CL 610976. Also use a 'converter' wrapper to add a bit more type safety in argument conversion functions. Change-Id: I851785c567bcde1a8df82a7921c2fba42def9085 Reviewed-on: https://go-review.googlesource.com/c/tools/+/612035 Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 9d7d14e commit 6b0cfff

File tree

7 files changed

+97
-91
lines changed

7 files changed

+97
-91
lines changed

gopls/internal/test/marker/doc.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ The following markers are supported within marker tests:
107107
If titles are provided, they are used to filter the matching code
108108
action.
109109
110-
TODO(rfindley): consolidate with codeactionedit, via a @loc2 marker that
111-
allows binding multi-line locations.
110+
TODO(rfindley): now that 'location' supports multi-line matches, replace
111+
uses of 'codeaction' with codeactionedit.
112112
113-
- codeactionedit(range, kind, golden, ...titles): a shorter form of
113+
- codeactionedit(location, kind, golden, ...titles): a shorter form of
114114
codeaction. Invokes a code action of the given kind for the given
115115
in-line range, and compares the resulting formatted unified *edits*
116116
(notably, not the full file content) with the golden directory.
@@ -292,11 +292,15 @@ test function. Additional value conversions may occur for these argument ->
292292
parameter type pairs:
293293
- string->regexp: the argument is parsed as a regular expressions.
294294
- string->location: the argument is converted to the location of the first
295-
instance of the argument in the partial line preceding the note.
295+
instance of the argument in the file content starting from the beginning of
296+
the line containing the note. Multi-line matches are permitted, but the
297+
match must begin before the note.
296298
- regexp->location: the argument is converted to the location of the first
297-
match for the argument in the partial line preceding the note. If the
298-
regular expression contains exactly one subgroup, the position of the
299-
subgroup is used rather than the position of the submatch.
299+
match for the argument in the file content starting from the beginning of
300+
the line containing the note. Multi-line matches are permitted, but the
301+
match must begin before the note. If the regular expression contains
302+
exactly one subgroup, the position of the subgroup is used rather than the
303+
position of the submatch.
300304
- name->location: the argument is replaced by the named location.
301305
- name->Golden: the argument is used to look up golden content prefixed by
302306
@<argument>.
@@ -336,12 +340,12 @@ files, and sandboxed directory.
336340
337341
Argument converters translate the "b" and "abc" arguments into locations by
338342
interpreting each one as a substring (or as a regular expression, if of the
339-
form re"a|b") and finding the location of its first occurrence on the preceding
340-
portion of the line, and the abc identifier into a the golden content contained
341-
in the file @abc. Then the hoverMarker method executes a textDocument/hover LSP
342-
request at the src position, and ensures the result spans "abc", with the
343-
markdown content from @abc. (Note that the markdown content includes the expect
344-
annotation as the doc comment.)
343+
form re"a|b") and finding the location of its first occurrence starting on the
344+
preceding portion of the line, and the abc identifier into a the golden content
345+
contained in the file @abc. Then the hoverMarker method executes a
346+
textDocument/hover LSP request at the src position, and ensures the result
347+
spans "abc", with the markdown content from @abc. (Note that the markdown
348+
content includes the expect annotation as the doc comment.)
345349
346350
The next hover on the same line asserts the same result, but initiates the
347351
hover immediately after "abc" in the source. This tests that we find the

gopls/internal/test/marker/marker_test.go

Lines changed: 61 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,8 +1047,16 @@ var (
10471047
//
10481048
// Converters should return an error rather than calling marker.errorf().
10491049
var customConverters = map[reflect.Type]func(marker, any) (any, error){
1050-
reflect.TypeOf(protocol.Location{}): convertLocation,
1051-
reflect.TypeOf(completionLabel("")): convertCompletionLabel,
1050+
reflect.TypeOf(protocol.Location{}): converter(convertLocation),
1051+
reflect.TypeOf(completionLabel("")): converter(convertCompletionLabel),
1052+
}
1053+
1054+
// converter transforms a typed argument conversion function to an untyped
1055+
// conversion function.
1056+
func converter[T any](f func(marker, any) (T, error)) func(marker, any) (any, error) {
1057+
return func(m marker, arg any) (any, error) {
1058+
return f(m, arg)
1059+
}
10521060
}
10531061

10541062
func convert(mark marker, arg any, paramType reflect.Type) (any, error) {
@@ -1086,26 +1094,64 @@ func convert(mark marker, arg any, paramType reflect.Type) (any, error) {
10861094
// convertLocation converts a string or regexp argument into the protocol
10871095
// location corresponding to the first position of the string (or first match
10881096
// of the regexp) in the line preceding the note.
1089-
func convertLocation(mark marker, arg any) (any, error) {
1097+
func convertLocation(mark marker, arg any) (protocol.Location, error) {
1098+
// matchContent is used to match the given argument against the file content
1099+
// starting at the marker line.
1100+
var matchContent func([]byte) (int, int, error)
1101+
10901102
switch arg := arg.(type) {
10911103
case protocol.Location:
1092-
return arg, nil
1104+
return arg, nil // nothing to do
10931105
case string:
1094-
startOff, preceding, m, err := linePreceding(mark.run, mark.note.Pos)
1095-
if err != nil {
1096-
return protocol.Location{}, err
1097-
}
1098-
idx := bytes.Index(preceding, []byte(arg))
1099-
if idx < 0 {
1100-
return protocol.Location{}, fmt.Errorf("substring %q not found in %q", arg, preceding)
1106+
matchContent = func(content []byte) (int, int, error) {
1107+
idx := bytes.Index(content, []byte(arg))
1108+
if idx < 0 {
1109+
return 0, 0, fmt.Errorf("substring %q not found", arg)
1110+
}
1111+
return idx, idx + len(arg), nil
11011112
}
1102-
off := startOff + idx
1103-
return m.OffsetLocation(off, off+len(arg))
11041113
case *regexp.Regexp:
1105-
return findRegexpInLine(mark.run, mark.note.Pos, arg)
1114+
matchContent = func(content []byte) (int, int, error) {
1115+
matches := arg.FindSubmatchIndex(content)
1116+
if len(matches) == 0 {
1117+
return 0, 0, fmt.Errorf("no match for regexp %q", arg)
1118+
}
1119+
switch len(matches) {
1120+
case 2:
1121+
// no subgroups: return the range of the regexp expression
1122+
return matches[0], matches[1], nil
1123+
case 4:
1124+
// one subgroup: return its range
1125+
return matches[2], matches[3], nil
1126+
default:
1127+
return 0, 0, fmt.Errorf("invalid location regexp %q: expect either 0 or 1 subgroups, got %d", arg, len(matches)/2-1)
1128+
}
1129+
}
11061130
default:
11071131
return protocol.Location{}, fmt.Errorf("cannot convert argument type %T to location (must be a string or regexp to match the preceding line)", arg)
11081132
}
1133+
1134+
// Now use matchFunc to match a range starting on the marker line.
1135+
1136+
file := mark.run.test.fset.File(mark.note.Pos)
1137+
posn := safetoken.Position(file, mark.note.Pos)
1138+
lineStart := file.LineStart(posn.Line)
1139+
lineStartOff, lineEndOff, err := safetoken.Offsets(file, lineStart, mark.note.Pos)
1140+
if err != nil {
1141+
return protocol.Location{}, err
1142+
}
1143+
m := mark.mapper()
1144+
start, end, err := matchContent(m.Content[lineStartOff:])
1145+
if err != nil {
1146+
return protocol.Location{}, err
1147+
}
1148+
startOff, endOff := lineStartOff+start, lineStartOff+end
1149+
if startOff > lineEndOff {
1150+
// The start of the match must be between the start of the line and the
1151+
// marker position (inclusive).
1152+
return protocol.Location{}, fmt.Errorf("no matching range found starting on the current line")
1153+
}
1154+
return m.OffsetLocation(startOff, endOff)
11091155
}
11101156

11111157
// completionLabel is a special parameter type that may be converted from a
@@ -1122,7 +1168,7 @@ type completionLabel string
11221168
//
11231169
// This allows us to stage a migration of the "snippet" marker to a simpler
11241170
// model where the completion label can just be listed explicitly.
1125-
func convertCompletionLabel(mark marker, arg any) (any, error) {
1171+
func convertCompletionLabel(mark marker, arg any) (completionLabel, error) {
11261172
switch arg := arg.(type) {
11271173
case string:
11281174
return completionLabel(arg), nil
@@ -1133,50 +1179,6 @@ func convertCompletionLabel(mark marker, arg any) (any, error) {
11331179
}
11341180
}
11351181

1136-
// findRegexpInLine searches the partial line preceding pos for a match for the
1137-
// regular expression re, returning a location spanning the first match. If re
1138-
// contains exactly one subgroup, the position of this subgroup match is
1139-
// returned rather than the position of the full match.
1140-
func findRegexpInLine(run *markerTestRun, pos token.Pos, re *regexp.Regexp) (protocol.Location, error) {
1141-
startOff, preceding, m, err := linePreceding(run, pos)
1142-
if err != nil {
1143-
return protocol.Location{}, err
1144-
}
1145-
1146-
matches := re.FindSubmatchIndex(preceding)
1147-
if len(matches) == 0 {
1148-
return protocol.Location{}, fmt.Errorf("no match for regexp %q found in %q", re, string(preceding))
1149-
}
1150-
var start, end int
1151-
switch len(matches) {
1152-
case 2:
1153-
// no subgroups: return the range of the regexp expression
1154-
start, end = matches[0], matches[1]
1155-
case 4:
1156-
// one subgroup: return its range
1157-
start, end = matches[2], matches[3]
1158-
default:
1159-
return protocol.Location{}, fmt.Errorf("invalid location regexp %q: expect either 0 or 1 subgroups, got %d", re, len(matches)/2-1)
1160-
}
1161-
1162-
return m.OffsetLocation(start+startOff, end+startOff)
1163-
}
1164-
1165-
func linePreceding(run *markerTestRun, pos token.Pos) (int, []byte, *protocol.Mapper, error) {
1166-
file := run.test.fset.File(pos)
1167-
posn := safetoken.Position(file, pos)
1168-
lineStart := file.LineStart(posn.Line)
1169-
startOff, endOff, err := safetoken.Offsets(file, lineStart, pos)
1170-
if err != nil {
1171-
return 0, nil, nil, err
1172-
}
1173-
m, err := run.env.Editor.Mapper(file.Name())
1174-
if err != nil {
1175-
return 0, nil, nil, err
1176-
}
1177-
return startOff, m.Content[startOff:endOff], m, nil
1178-
}
1179-
11801182
// convertStringMatcher converts a string, regexp, or identifier
11811183
// argument into a stringMatcher. The string is a substring of the
11821184
// expected error, the regexp is a pattern than matches the expected

gopls/internal/test/marker/testdata/codeaction/extract-variadic-63287.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ package a
1313

1414
func _() {
1515
var logf func(string, ...any)
16-
{ println(logf) } //@loc(block, re`{.*}`)
16+
{ println(logf) } //@loc(block, re`{[^}]*}`)
1717
}
1818

1919
-- @out/a/a.go --
2020
@@ -7 +7 @@
21-
- { println(logf) } //@loc(block, re`{.*}`)
22-
+ { newFunction(logf) } //@loc(block, re`{.*}`)
21+
- { println(logf) } //@loc(block, re`{[^}]*}`)
22+
+ { newFunction(logf) } //@loc(block, re`{[^}]*}`)
2323
@@ -10 +10,4 @@
2424
+func newFunction(logf func( string, ...any)) {
2525
+ println(logf)

gopls/internal/test/marker/testdata/codeaction/extract_method.txt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (a *A) XLessThanYP() bool {
3030

3131
func (a *A) AddP() int {
3232
sum := a.x + a.y //@loc(A_AddP1, re`sum.*a\.y`)
33-
return sum //@loc(A_AddP2, re`return.*sum`)
33+
return sum //@loc(A_AddP2, re`return.*?sum`)
3434
}
3535

3636
func (a A) XLessThanY() bool {
@@ -39,7 +39,7 @@ func (a A) XLessThanY() bool {
3939

4040
func (a A) Add() int {
4141
sum := a.x + a.y //@loc(A_Add1, re`sum.*a\.y`)
42-
return sum //@loc(A_Add2, re`return.*sum`)
42+
return sum //@loc(A_Add2, re`return.*?sum`)
4343
}
4444

4545
-- @func1/basic.go --
@@ -63,8 +63,8 @@ func (a A) Add() int {
6363
+
6464
-- @func3/basic.go --
6565
@@ -27 +27 @@
66-
- return sum //@loc(A_AddP2, re`return.*sum`)
67-
+ return newFunction(sum) //@loc(A_AddP2, re`return.*sum`)
66+
- return sum //@loc(A_AddP2, re`return.*?sum`)
67+
+ return newFunction(sum) //@loc(A_AddP2, re`return.*?sum`)
6868
@@ -30 +30,4 @@
6969
+func newFunction(sum int) int {
7070
+ return sum
@@ -91,8 +91,8 @@ func (a A) Add() int {
9191
+
9292
-- @func6/basic.go --
9393
@@ -36 +36 @@
94-
- return sum //@loc(A_Add2, re`return.*sum`)
95-
+ return newFunction(sum) //@loc(A_Add2, re`return.*sum`)
94+
- return sum //@loc(A_Add2, re`return.*?sum`)
95+
+ return newFunction(sum) //@loc(A_Add2, re`return.*?sum`)
9696
@@ -39 +39,4 @@
9797
+func newFunction(sum int) int {
9898
+ return sum
@@ -119,8 +119,8 @@ func (a A) Add() int {
119119
+
120120
-- @meth3/basic.go --
121121
@@ -27 +27 @@
122-
- return sum //@loc(A_AddP2, re`return.*sum`)
123-
+ return a.newMethod(sum) //@loc(A_AddP2, re`return.*sum`)
122+
- return sum //@loc(A_AddP2, re`return.*?sum`)
123+
+ return a.newMethod(sum) //@loc(A_AddP2, re`return.*?sum`)
124124
@@ -30 +30,4 @@
125125
+func (*A) newMethod(sum int) int {
126126
+ return sum
@@ -147,8 +147,8 @@ func (a A) Add() int {
147147
+
148148
-- @meth6/basic.go --
149149
@@ -36 +36 @@
150-
- return sum //@loc(A_Add2, re`return.*sum`)
151-
+ return a.newMethod(sum) //@loc(A_Add2, re`return.*sum`)
150+
- return sum //@loc(A_Add2, re`return.*?sum`)
151+
+ return a.newMethod(sum) //@loc(A_Add2, re`return.*?sum`)
152152
@@ -39 +39,4 @@
153153
+func (A) newMethod(sum int) int {
154154
+ return sum

gopls/internal/test/marker/testdata/completion/comment.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@ package comment_completion
1313

1414
var p bool
1515

16-
//@complete(re"$")
16+
//@complete(re"//()")
1717

1818
func _() {
1919
var a int
2020

2121
switch a {
2222
case 1:
23-
//@complete(re"$")
23+
//@complete(re"//()")
2424
_ = a
2525
}
2626

2727
var b chan int
2828
select {
2929
case <-b:
30-
//@complete(re"$")
30+
//@complete(re"//()")
3131
_ = b
3232
}
3333

3434
var (
35-
//@complete(re"$")
35+
//@complete(re"//()")
3636
_ = a
3737
)
3838
}

gopls/internal/test/marker/testdata/definition/embed.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type J interface { //@loc(J, "J")
4747
-- b/b.go --
4848
package b
4949

50-
import "mod.com/a" //@loc(AImport, re"\".*\"")
50+
import "mod.com/a" //@loc(AImport, re"\"[^\"]*\"")
5151

5252
type embed struct {
5353
F int //@loc(F, "F")

gopls/internal/test/marker/testdata/diagnostics/analyzers.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func _() {
2727

2828
// printf
2929
func _() {
30-
printfWrapper("%s") //@diag(re`printfWrapper\(.*\)`, re"example.com.printfWrapper format %s reads arg #1, but call has 0 args")
30+
printfWrapper("%s") //@diag(re`printfWrapper\(.*?\)`, re"example.com.printfWrapper format %s reads arg #1, but call has 0 args")
3131
}
3232

3333
func printfWrapper(format string, args ...interface{}) {

0 commit comments

Comments
 (0)