@@ -22,6 +22,7 @@ import (
2222 "golang.org/x/tools/go/ast/inspector"
2323 "golang.org/x/tools/go/types/typeutil"
2424 "golang.org/x/tools/internal/analysisinternal"
25+ "golang.org/x/tools/internal/astutil"
2526 "golang.org/x/tools/internal/fmtstr"
2627 "golang.org/x/tools/internal/typeparams"
2728 "golang.org/x/tools/internal/versions"
@@ -540,7 +541,7 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
540541 firstArg := idx + 1 // Arguments are immediately after format string.
541542 if ! strings .Contains (format , "%" ) {
542543 if len (call .Args ) > firstArg {
543- pass .Reportf (call .Lparen , "%s call has arguments but no formatting directives" , name )
544+ pass .ReportRangef (call .Args [ firstArg ] , "%s call has arguments but no formatting directives" , name )
544545 }
545546 return
546547 }
@@ -552,28 +553,29 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
552553 if err != nil {
553554 // All error messages are in predicate form ("call has a problem")
554555 // so that they may be affixed into a subject ("log.Printf ").
555- pass .ReportRangef (call . Args [ idx ] , "%s %s" , name , err )
556+ pass .ReportRangef (formatArg , "%s %s" , name , err )
556557 return
557558 }
558559
559560 // index of the highest used index.
560561 maxArgIndex := firstArg - 1
561562 anyIndex := false
562563 // Check formats against args.
563- for _ , operation := range operations {
564- if operation .Prec .Index != - 1 ||
565- operation .Width .Index != - 1 ||
566- operation .Verb .Index != - 1 {
564+ for _ , op := range operations {
565+ if op .Prec .Index != - 1 ||
566+ op .Width .Index != - 1 ||
567+ op .Verb .Index != - 1 {
567568 anyIndex = true
568569 }
569- if ! okPrintfArg (pass , call , & maxArgIndex , firstArg , name , operation ) {
570+ rng := opRange (formatArg , op )
571+ if ! okPrintfArg (pass , call , rng , & maxArgIndex , firstArg , name , op ) {
570572 // One error per format is enough.
571573 return
572574 }
573- if operation .Verb .Verb == 'w' {
575+ if op .Verb .Verb == 'w' {
574576 switch kind {
575577 case KindNone , KindPrint , KindPrintf :
576- pass .Reportf ( call . Pos () , "%s does not support error-wrapping directive %%w" , name )
578+ pass .ReportRangef ( rng , "%s does not support error-wrapping directive %%w" , name )
577579 return
578580 }
579581 }
@@ -594,6 +596,18 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
594596 }
595597}
596598
599+ // opRange returns the source range for the specified printf operation,
600+ // such as the position of the %v substring of "...%v...".
601+ func opRange (formatArg ast.Expr , op * fmtstr.Operation ) analysis.Range {
602+ if lit , ok := formatArg .(* ast.BasicLit ); ok {
603+ start , end , err := astutil .RangeInStringLiteral (lit , op .Range .Start , op .Range .End )
604+ if err == nil {
605+ return analysisinternal .Range (start , end ) // position of "%v"
606+ }
607+ }
608+ return formatArg // entire format string
609+ }
610+
597611// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask.
598612type printfArgType int
599613
@@ -657,7 +671,7 @@ var printVerbs = []printVerb{
657671// okPrintfArg compares the operation to the arguments actually present,
658672// reporting any discrepancies it can discern, maxArgIndex was the index of the highest used index.
659673// If the final argument is ellipsissed, there's little it can do for that.
660- func okPrintfArg (pass * analysis.Pass , call * ast.CallExpr , maxArgIndex * int , firstArg int , name string , operation * fmtstr.Operation ) (ok bool ) {
674+ func okPrintfArg (pass * analysis.Pass , call * ast.CallExpr , rng analysis. Range , maxArgIndex * int , firstArg int , name string , operation * fmtstr.Operation ) (ok bool ) {
661675 verb := operation .Verb .Verb
662676 var v printVerb
663677 found := false
@@ -680,7 +694,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, maxArgIndex *int, firs
680694
681695 if ! formatter {
682696 if ! found {
683- pass .ReportRangef (call , "%s format %s has unknown verb %c" , name , operation .Text , verb )
697+ pass .ReportRangef (rng , "%s format %s has unknown verb %c" , name , operation .Text , verb )
684698 return false
685699 }
686700 for _ , flag := range operation .Flags {
@@ -690,7 +704,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, maxArgIndex *int, firs
690704 continue
691705 }
692706 if ! strings .ContainsRune (v .flags , rune (flag )) {
693- pass .ReportRangef (call , "%s format %s has unrecognized flag %c" , name , operation .Text , flag )
707+ pass .ReportRangef (rng , "%s format %s has unrecognized flag %c" , name , operation .Text , flag )
694708 return false
695709 }
696710 }
@@ -707,7 +721,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, maxArgIndex *int, firs
707721 // If len(argIndexes)>0, we have something like %.*s and all
708722 // indexes in argIndexes must be an integer.
709723 for _ , argIndex := range argIndexes {
710- if ! argCanBeChecked (pass , call , argIndex , firstArg , operation , name ) {
724+ if ! argCanBeChecked (pass , call , rng , argIndex , firstArg , operation , name ) {
711725 return
712726 }
713727 arg := call .Args [argIndex ]
@@ -716,7 +730,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, maxArgIndex *int, firs
716730 if reason != "" {
717731 details = " (" + reason + ")"
718732 }
719- pass .ReportRangef (call , "%s format %s uses non-int %s%s as argument of *" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ), details )
733+ pass .ReportRangef (rng , "%s format %s uses non-int %s%s as argument of *" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ), details )
720734 return false
721735 }
722736 }
@@ -738,12 +752,12 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, maxArgIndex *int, firs
738752
739753 // Now check verb's type.
740754 verbArgIndex := operation .Verb .ArgIndex
741- if ! argCanBeChecked (pass , call , verbArgIndex , firstArg , operation , name ) {
755+ if ! argCanBeChecked (pass , call , rng , verbArgIndex , firstArg , operation , name ) {
742756 return false
743757 }
744758 arg := call .Args [verbArgIndex ]
745759 if isFunctionValue (pass , arg ) && verb != 'p' && verb != 'T' {
746- pass .ReportRangef (call , "%s format %s arg %s is a func value, not called" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ))
760+ pass .ReportRangef (rng , "%s format %s arg %s is a func value, not called" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ))
747761 return false
748762 }
749763 if reason , ok := matchArgType (pass , v .typ , arg ); ! ok {
@@ -755,14 +769,14 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, maxArgIndex *int, firs
755769 if reason != "" {
756770 details = " (" + reason + ")"
757771 }
758- pass .ReportRangef (call , "%s format %s has arg %s of wrong type %s%s" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ), typeString , details )
772+ pass .ReportRangef (rng , "%s format %s has arg %s of wrong type %s%s" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ), typeString , details )
759773 return false
760774 }
761775 // Detect recursive formatting via value's String/Error methods.
762776 // The '#' flag suppresses the methods, except with %x, %X, and %q.
763777 if v .typ & argString != 0 && v .verb != 'T' && (! strings .Contains (operation .Flags , "#" ) || strings .ContainsRune ("qxX" , v .verb )) {
764778 if methodName , ok := recursiveStringer (pass , arg ); ok {
765- pass .ReportRangef (call , "%s format %s with arg %s causes recursive %s method call" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ), methodName )
779+ pass .ReportRangef (rng , "%s format %s with arg %s causes recursive %s method call" , name , operation .Text , analysisinternal .Format (pass .Fset , arg ), methodName )
766780 return false
767781 }
768782 }
@@ -846,7 +860,7 @@ func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
846860// argCanBeChecked reports whether the specified argument is statically present;
847861// it may be beyond the list of arguments or in a terminal slice... argument, which
848862// means we can't see it.
849- func argCanBeChecked (pass * analysis.Pass , call * ast.CallExpr , argIndex , firstArg int , operation * fmtstr.Operation , name string ) bool {
863+ func argCanBeChecked (pass * analysis.Pass , call * ast.CallExpr , rng analysis. Range , argIndex , firstArg int , operation * fmtstr.Operation , name string ) bool {
850864 if argIndex <= 0 {
851865 // Shouldn't happen, so catch it with prejudice.
852866 panic ("negative argIndex" )
@@ -863,7 +877,7 @@ func argCanBeChecked(pass *analysis.Pass, call *ast.CallExpr, argIndex, firstArg
863877 // There are bad indexes in the format or there are fewer arguments than the format needs.
864878 // This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
865879 arg := argIndex - firstArg + 1 // People think of arguments as 1-indexed.
866- pass .ReportRangef (call , "%s format %s reads arg #%d, but call has %v" , name , operation .Text , arg , count (len (call .Args )- firstArg , "arg" ))
880+ pass .ReportRangef (rng , "%s format %s reads arg #%d, but call has %v" , name , operation .Text , arg , count (len (call .Args )- firstArg , "arg" ))
867881 return false
868882}
869883
0 commit comments