44 "go/ast"
55 "go/parser"
66 "go/token"
7+ "go/types"
78 "testing"
89)
910
@@ -53,6 +54,20 @@ func example() {
5354 },
5455 expectedIssues : 1 ,
5556 },
57+ {
58+ name : "duplicate computed consts" ,
59+ code : `package example
60+ const ConstA = "te"
61+ const Test = "test"
62+ func example() {
63+ const ConstB = ConstA + "st"
64+ }` ,
65+ config : & Config {
66+ FindDuplicates : true ,
67+ EvalConstExpressions : true ,
68+ },
69+ expectedIssues : 1 ,
70+ },
5671 {
5772 name : "string duplication with ignore" ,
5873 code : `package example
@@ -164,7 +179,10 @@ func example() {
164179 t .Fatalf ("Failed to parse test code: %v" , err )
165180 }
166181
167- issues , err := Run ([]* ast.File {f }, fset , tt .config )
182+ chkr , info := checker (fset )
183+ _ = chkr .Files ([]* ast.File {f })
184+
185+ issues , err := Run ([]* ast.File {f }, fset , info , tt .config )
168186 if err != nil {
169187 t .Fatalf ("Run() error = %v" , err )
170188 }
@@ -201,7 +219,10 @@ func example() {
201219 MatchWithConstants : true ,
202220 }
203221
204- issues , err := Run ([]* ast.File {f }, fset , config )
222+ chkr , info := checker (fset )
223+ _ = chkr .Files ([]* ast.File {f })
224+
225+ issues , err := Run ([]* ast.File {f }, fset , info , config )
205226 if err != nil {
206227 t .Fatalf ("Run() error = %v" , err )
207228 }
@@ -256,16 +277,16 @@ func example2() {
256277 expectedOccurrenceCount : 3 ,
257278 },
258279 {
259- name : "duplicate consts in different packages " ,
260- code1 : `package package1
280+ name : "duplicate consts in different files " ,
281+ code1 : `package example
261282const ConstA = "shared"
262283const ConstB = "shared"
263284` ,
264- code2 : `package package2
285+ code2 : `package example
265286const (
266287 ConstC = "shared"
267288 ConstD = "shared"
268- ConstE= "unique"
289+ ConstE = "unique"
269290)` ,
270291 config : & Config {
271292 FindDuplicates : true ,
@@ -290,7 +311,10 @@ const (
290311 t .Fatalf ("Failed to parse test code: %v" , err )
291312 }
292313
293- issues , err := Run ([]* ast.File {f1 , f2 }, fset , tt .config )
314+ chkr , info := checker (fset )
315+ _ = chkr .Files ([]* ast.File {f1 , f2 })
316+
317+ issues , err := Run ([]* ast.File {f1 , f2 }, fset , info , tt .config )
294318 if err != nil {
295319 t .Fatalf ("Run() error = %v" , err )
296320 }
@@ -348,8 +372,10 @@ func allContexts(param string) string {
348372 MinStringLength : 3 ,
349373 MinOccurrences : 2 ,
350374 }
375+ chkr , info := checker (fset )
376+ _ = chkr .Files ([]* ast.File {f })
351377
352- issues , err := Run ([]* ast.File {f }, fset , config )
378+ issues , err := Run ([]* ast.File {f }, fset , info , config )
353379 if err != nil {
354380 t .Fatalf ("Run() error = %v" , err )
355381 }
@@ -429,8 +455,10 @@ func multipleContexts() {
429455 MinOccurrences : 2 ,
430456 ExcludeTypes : tt .excludeTypes ,
431457 }
458+ chkr , info := checker (fset )
459+ _ = chkr .Files ([]* ast.File {f })
432460
433- issues , err := Run ([]* ast.File {f }, fset , config )
461+ issues , err := Run ([]* ast.File {f }, fset , info , config )
434462 if err != nil {
435463 t .Fatalf ("Run() error = %v" , err )
436464 }
@@ -453,3 +481,84 @@ func multipleContexts() {
453481 })
454482 }
455483}
484+
485+ func TestConstExpressions (t * testing.T ) {
486+ // Test detecting and matching string constants derived from expressions
487+ code := `package example
488+
489+ const (
490+ Prefix = "example.com/"
491+ Label1 = Prefix + "some_label"
492+ Label2 = Prefix + "another_label"
493+ )
494+
495+ func example() {
496+ // These should match the constants from expressions
497+ a := "example.com/some_label"
498+ b := "example.com/some_label"
499+
500+ // This should also match
501+ web1 := "example.com/another_label"
502+ web2 := "example.com/another_label"
503+ }
504+ `
505+ fset := token .NewFileSet ()
506+ f , err := parser .ParseFile (fset , "example.go" , code , 0 )
507+ if err != nil {
508+ t .Fatalf ("Failed to parse test code: %v" , err )
509+ }
510+
511+ config := & Config {
512+ MinStringLength : 3 ,
513+ MinOccurrences : 2 ,
514+ MatchWithConstants : true ,
515+ EvalConstExpressions : true ,
516+ }
517+ chkr , info := checker (fset )
518+ _ = chkr .Files ([]* ast.File {f })
519+
520+ issues , err := Run ([]* ast.File {f }, fset , info , config )
521+ if err != nil {
522+ t .Fatalf ("Run() error = %v" , err )
523+ }
524+
525+ // We expect issues for both labels
526+ expectedMatches := map [string ]string {
527+ "example.com/some_label" : "Label1" ,
528+ "example.com/another_label" : "Label2" ,
529+ }
530+
531+ // Check that we have two issues
532+ if len (issues ) != 2 {
533+ t .Errorf ("Expected 2 issues, got %d" , len (issues ))
534+ for _ , issue := range issues {
535+ t .Logf ("Found issue: %q matches constant %q with %d occurrences" ,
536+ issue .Str , issue .MatchingConst , issue .OccurrencesCount )
537+ }
538+ return
539+ }
540+
541+ // Check that each string matches the expected constant
542+ for _ , issue := range issues {
543+ expectedConst , ok := expectedMatches [issue .Str ]
544+ if ! ok {
545+ t .Errorf ("Unexpected issue for string: %s" , issue .Str )
546+ continue
547+ }
548+
549+ if issue .MatchingConst != expectedConst {
550+ t .Errorf ("For string %q: got matching const %q, want %q" ,
551+ issue .Str , issue .MatchingConst , expectedConst )
552+ }
553+ }
554+ }
555+
556+ func checker (fset * token.FileSet ) (* types.Checker , * types.Info ) {
557+ cfg := & types.Config {
558+ Error : func (err error ) {},
559+ }
560+ info := & types.Info {
561+ Types : make (map [ast.Expr ]types.TypeAndValue ),
562+ }
563+ return types .NewChecker (cfg , fset , types .NewPackage ("" , "example" ), info ), info
564+ }
0 commit comments