@@ -1047,8 +1047,16 @@ var (
1047
1047
//
1048
1048
// Converters should return an error rather than calling marker.errorf().
1049
1049
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
+ }
1052
1060
}
1053
1061
1054
1062
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) {
1086
1094
// convertLocation converts a string or regexp argument into the protocol
1087
1095
// location corresponding to the first position of the string (or first match
1088
1096
// 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
+
1090
1102
switch arg := arg .(type ) {
1091
1103
case protocol.Location :
1092
- return arg , nil
1104
+ return arg , nil // nothing to do
1093
1105
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
1101
1112
}
1102
- off := startOff + idx
1103
- return m .OffsetLocation (off , off + len (arg ))
1104
1113
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
+ }
1106
1130
default :
1107
1131
return protocol.Location {}, fmt .Errorf ("cannot convert argument type %T to location (must be a string or regexp to match the preceding line)" , arg )
1108
1132
}
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 )
1109
1155
}
1110
1156
1111
1157
// completionLabel is a special parameter type that may be converted from a
@@ -1122,7 +1168,7 @@ type completionLabel string
1122
1168
//
1123
1169
// This allows us to stage a migration of the "snippet" marker to a simpler
1124
1170
// 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 ) {
1126
1172
switch arg := arg .(type ) {
1127
1173
case string :
1128
1174
return completionLabel (arg ), nil
@@ -1133,50 +1179,6 @@ func convertCompletionLabel(mark marker, arg any) (any, error) {
1133
1179
}
1134
1180
}
1135
1181
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
-
1180
1182
// convertStringMatcher converts a string, regexp, or identifier
1181
1183
// argument into a stringMatcher. The string is a substring of the
1182
1184
// expected error, the regexp is a pattern than matches the expected
0 commit comments