@@ -19,6 +19,7 @@ import (
1919	"golang.org/x/tools/gopls/internal/cache" 
2020	"golang.org/x/tools/gopls/internal/cache/metadata" 
2121	"golang.org/x/tools/gopls/internal/cache/parsego" 
22+ 	"golang.org/x/tools/gopls/internal/cache/testfuncs" 
2223	"golang.org/x/tools/gopls/internal/file" 
2324	"golang.org/x/tools/gopls/internal/protocol" 
2425	"golang.org/x/tools/gopls/internal/protocol/command" 
@@ -213,6 +214,29 @@ func regenerateCgoLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Ha
213214}
214215
215216func  goToTestCodeLens (ctx  context.Context , snapshot  * cache.Snapshot , fh  file.Handle ) ([]protocol.CodeLens , error ) {
217+ 	matches , err  :=  matchFunctionsWithTests (ctx , snapshot , fh )
218+ 	if  err  !=  nil  {
219+ 		return  nil , err 
220+ 	}
221+ 
222+ 	lenses  :=  make ([]protocol.CodeLens , 0 , len (matches ))
223+ 	for  _ , t  :=  range  matches  {
224+ 		lenses  =  append (lenses , protocol.CodeLens {
225+ 			Range :   protocol.Range {Start : t .FuncPos , End : t .FuncPos },
226+ 			Command : command .NewGoToTestCommand ("Go to " + t .Name , t .Loc ),
227+ 		})
228+ 	}
229+ 	return  lenses , nil 
230+ }
231+ 
232+ type  TestMatch  struct  {
233+ 	FuncPos  protocol.Position   // function position 
234+ 	Name     string              // test name 
235+ 	Loc      protocol.Location   // test location 
236+ 	Type     testfuncs.TestType  // test type 
237+ }
238+ 
239+ func  matchFunctionsWithTests (ctx  context.Context , snapshot  * cache.Snapshot , fh  file.Handle ) (matches  []TestMatch , err  error ) {
216240	if  strings .HasSuffix (fh .URI ().Path (), "_test.go" ) {
217241		// Ignore test files. 
218242		return  nil , nil 
@@ -238,7 +262,12 @@ func goToTestCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Han
238262	if  err  !=  nil  {
239263		return  nil , fmt .Errorf ("couldn't parse file: %w" , err )
240264	}
241- 	funcPos  :=  make (map [string ]protocol.Position )
265+ 
266+ 	type  Func  struct  {
267+ 		Name  string 
268+ 		Pos   protocol.Position 
269+ 	}
270+ 	var  fileFuncs  []Func 
242271	for  _ , d  :=  range  pgf .File .Decls  {
243272		fn , ok  :=  d .(* ast.FuncDecl )
244273		if  ! ok  {
@@ -254,32 +283,11 @@ func goToTestCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Han
254283			_ , rname , _  :=  astutil .UnpackRecv (fn .Recv .List [0 ].Type )
255284			name  =  rname .Name  +  "_"  +  fn .Name .Name 
256285		}
257- 		funcPos [name ] =  rng .Start 
258- 	}
259- 
260- 	type  TestType  int 
261- 
262- 	// Types are sorted by priority from high to low. 
263- 	const  (
264- 		T  TestType  =  iota  +  1 
265- 		E 
266- 		B 
267- 		F 
268- 	)
269- 	testTypes  :=  map [string ]TestType {
270- 		"Test" :      T ,
271- 		"Example" :   E ,
272- 		"Benchmark" : B ,
273- 		"Fuzz" :      F ,
274- 	}
275- 
276- 	type  Test  struct  {
277- 		FuncPos  protocol.Position 
278- 		Name     string 
279- 		Loc      protocol.Location 
280- 		Type     TestType 
286+ 		fileFuncs  =  append (fileFuncs , Func {
287+ 			Name : name ,
288+ 			Pos :  rng .Start ,
289+ 		})
281290	}
282- 	var  matchedTests  []Test 
283291
284292	pkgIDs  :=  make ([]PackageID , 0 , len (testPackages ))
285293	for  _ , pkg  :=  range  testPackages  {
@@ -291,48 +299,54 @@ func goToTestCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Han
291299	}
292300	for  _ , tests  :=  range  allTests  {
293301		for  _ , test  :=  range  tests .All () {
294- 			var  (
295- 				name      string 
296- 				testType  TestType 
297- 			)
298- 			for  prefix , t  :=  range  testTypes  {
299- 				if  strings .HasPrefix (test .Name , prefix ) {
300- 					testType  =  t 
301- 					name  =  test .Name [len (prefix ):]
302- 					break 
303- 				}
302+ 			if  test .Subtest  {
303+ 				continue 
304304			}
305- 			if  testType  ==  0  {
306- 				continue  // unknown type 
305+ 			potentialFuncNames  :=  getPotentialFuncNames (test )
306+ 			if  len (potentialFuncNames ) ==  0  {
307+ 				continue 
307308			}
308- 			name  =  strings .TrimPrefix (name , "_" )
309- 
310- 			// Try to find 'Foo' for 'TestFoo' and 'foo' for 'Test_foo'. 
311- 			pos , ok  :=  funcPos [name ]
312- 			if  ! ok  &&  token .IsExported (name ) {
313- 				// Try to find 'foo' for 'TestFoo'. 
314- 				runes  :=  []rune (name )
315- 				runes [0 ] =  unicode .ToLower (runes [0 ])
316- 				pos , ok  =  funcPos [string (runes )]
309+ 
310+ 			var  matchedFunc  Func 
311+ 			for  _ , fn  :=  range  fileFuncs  {
312+ 				var  matched  bool 
313+ 				for  _ , n  :=  range  potentialFuncNames  {
314+ 					// Check the prefix to be able to match 'TestDeletePanics' with 'Delete'. 
315+ 					if  strings .HasPrefix (n , fn .Name ) {
316+ 						matched  =  true 
317+ 						break 
318+ 					}
319+ 				}
320+ 				if  ! matched  {
321+ 					continue 
322+ 				}
323+ 
324+ 				// Use the most specific function: 
325+ 				// 
326+ 				//   - match 'TestDelete', 'TestDeletePanics' with 'Delete' 
327+ 				//   - match 'TestDeleteFunc', 'TestDeleteFuncClearTail' with 'DeleteFunc', not 'Delete' 
328+ 				if  len (matchedFunc .Name ) <  len (fn .Name ) {
329+ 					matchedFunc  =  fn 
330+ 				}
317331			}
318- 			if  ok  {
332+ 			if  matchedFunc . Name   !=   ""  {
319333				loc  :=  test .Location 
320334				loc .Range .End  =  loc .Range .Start  // move cursor to the test's beginning 
321335
322- 				matchedTests  =  append (matchedTests ,  Test {
323- 					FuncPos : pos ,
336+ 				matches  =  append (matches ,  TestMatch {
337+ 					FuncPos : matchedFunc . Pos ,
324338					Name :    test .Name ,
325339					Loc :     loc ,
326- 					Type :    testType ,
340+ 					Type :    test . Type ,
327341				})
328342			}
329343		}
330344	}
331- 	if  len (matchedTests ) ==  0  {
345+ 	if  len (matches ) ==  0  {
332346		return  nil , nil 
333347	}
334348
335- 	slices .SortFunc (matchedTests , func (a , b  Test ) int  {
349+ 	slices .SortFunc (matches , func (a , b  TestMatch ) int  {
336350		if  v  :=  protocol .ComparePosition (a .FuncPos , b .FuncPos ); v  !=  0  {
337351			return  v 
338352		}
@@ -341,13 +355,31 @@ func goToTestCodeLens(ctx context.Context, snapshot *cache.Snapshot, fh file.Han
341355		}
342356		return  cmp .Compare (a .Name , b .Name )
343357	})
358+ 	return  matches , nil 
359+ }
344360
345- 	lenses  :=  make ([]protocol.CodeLens , 0 , len (matchedTests ))
346- 	for  _ , t  :=  range  matchedTests  {
347- 		lenses  =  append (lenses , protocol.CodeLens {
348- 			Range :   protocol.Range {Start : t .FuncPos , End : t .FuncPos },
349- 			Command : command .NewGoToTestCommand ("Go to " + t .Name , t .Loc ),
350- 		})
361+ func  getPotentialFuncNames (test  testfuncs.Result ) []string  {
362+ 	var  name  string 
363+ 	switch  test .Type  {
364+ 	case  testfuncs .TypeTest :
365+ 		name  =  strings .TrimPrefix (test .Name , "Test" )
366+ 	case  testfuncs .TypeBenchmark :
367+ 		name  =  strings .TrimPrefix (test .Name , "Benchmark" )
368+ 	case  testfuncs .TypeFuzz :
369+ 		name  =  strings .TrimPrefix (test .Name , "Fuzz" )
370+ 	case  testfuncs .TypeExample :
371+ 		name  =  strings .TrimPrefix (test .Name , "Example" )
372+ 	}
373+ 	if  name  ==  ""  {
374+ 		return  nil 
375+ 	}
376+ 	name  =  strings .TrimPrefix (name , "_" )
377+ 
378+ 	lowerCasedName  :=  []rune (name )
379+ 	lowerCasedName [0 ] =  unicode .ToLower (lowerCasedName [0 ])
380+ 
381+ 	return  []string {
382+ 		name ,                   // 'Foo' for 'TestFoo', 'foo' for 'Test_foo' 
383+ 		string (lowerCasedName ), // 'foo' for 'TestFoo' 
351384	}
352- 	return  lenses , nil 
353385}
0 commit comments