Skip to content

Commit fa85734

Browse files
committed
add a format arg
1 parent cebbfe8 commit fa85734

File tree

4 files changed

+42
-38
lines changed

4 files changed

+42
-38
lines changed

go/analysis/passes/printf/printf.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
_ "embed"
1010
"fmt"
1111
"go/ast"
12+
"go/constant"
1213
"go/token"
1314
"go/types"
1415
"reflect"
@@ -452,7 +453,7 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, name string
452453
return
453454
}
454455
formatArg := call.Args[idx]
455-
format, ok := fmtstr.StringConstantExpr(pass.TypesInfo, formatArg)
456+
format, ok := stringConstantExpr(pass.TypesInfo, formatArg)
456457
if !ok {
457458
// Format string argument is non-constant.
458459

@@ -486,7 +487,11 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, name string
486487
}
487488
return
488489
}
489-
directives, err := fmtstr.ParsePrintf(pass.TypesInfo, call)
490+
491+
// Pass the string constant value so
492+
// fmt.Sprintf("%"+("s"), "hi", 3) can be reported as
493+
// "fmt.Sprintf call needs 1 arg but has 2 args".
494+
directives, err := fmtstr.ParsePrintf(pass.TypesInfo, call, format)
490495
if err != nil {
491496
pass.ReportRangef(call.Fun, "%s %s", name, err.Error())
492497
return
@@ -856,7 +861,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
856861
}
857862

858863
arg := args[0]
859-
if s, ok := fmtstr.StringConstantExpr(pass.TypesInfo, arg); ok {
864+
if s, ok := stringConstantExpr(pass.TypesInfo, arg); ok {
860865
// Ignore trailing % character
861866
// The % in "abc 0.0%" couldn't be a formatting directive.
862867
s = strings.TrimSuffix(s, "%")
@@ -870,7 +875,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
870875
if strings.HasSuffix(name, "ln") {
871876
// The last item, if a string, should not have a newline.
872877
arg = args[len(args)-1]
873-
if s, ok := fmtstr.StringConstantExpr(pass.TypesInfo, arg); ok {
878+
if s, ok := stringConstantExpr(pass.TypesInfo, arg); ok {
874879
if strings.HasSuffix(s, "\n") {
875880
pass.ReportRangef(call, "%s arg list ends with redundant newline", name)
876881
}
@@ -886,6 +891,17 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
886891
}
887892
}
888893

894+
// StringConstantExpr returns expression's string constant value.
895+
//
896+
// ("", false) is returned if expression isn't a string constant.
897+
func stringConstantExpr(info *types.Info, expr ast.Expr) (string, bool) {
898+
lit := info.Types[expr].Value
899+
if lit != nil && lit.Kind() == constant.String {
900+
return constant.StringVal(lit), true
901+
}
902+
return "", false
903+
}
904+
889905
// count(n, what) returns "1 what" or "N whats"
890906
// (assuming the plural of what is whats).
891907
func count(n int, what string) string {

gopls/internal/golang/highlight.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ func highlightPath(info *types.Info, path []ast.Node, pos token.Pos) (map[posRan
8080
if call, ok := node.(*ast.CallExpr); ok {
8181
idx := fmtstr.FormatStringIndex(info, call)
8282
if idx >= 0 && idx < len(call.Args) {
83-
format, ok := fmtstr.StringConstantExpr(info, call.Args[idx])
84-
if ok && strings.Contains(format, "%") {
85-
highlightPrintf(info, call, call.Args[idx].Pos(), pos, result)
83+
// We only care string literal, so fmt.Sprint("a"+"b%s", "bar") won't highlight.
84+
if lit, ok := call.Args[idx].(*ast.BasicLit); ok && strings.Contains(lit.Value, "%") {
85+
highlightPrintf(info, call, call.Args[idx].Pos(), pos, lit.Value, result)
8686
}
8787
}
8888
}
@@ -157,18 +157,18 @@ func highlightPath(info *types.Info, path []ast.Node, pos token.Pos) (map[posRan
157157
//
158158
// If the cursor is on %s or name, highlightPrintf will highlight %s as a write operation,
159159
// and name as a read operation.
160-
func highlightPrintf(info *types.Info, call *ast.CallExpr, formatPos token.Pos, cursorPos token.Pos, result map[posRange]protocol.DocumentHighlightKind) {
161-
directives, err := fmtstr.ParsePrintf(info, call)
160+
func highlightPrintf(info *types.Info, call *ast.CallExpr, formatPos token.Pos, cursorPos token.Pos, format string, result map[posRange]protocol.DocumentHighlightKind) {
161+
directives, err := fmtstr.ParsePrintf(info, call, format)
162162
if err != nil {
163163
return
164164
}
165165

166166
// highlightPair highlights the directive and its potential argument pair if the cursor is within either range.
167167
highlightPair := func(start, end token.Pos, argIndex int) bool {
168168
var (
169-
rangeStart = formatPos + token.Pos(start) + 1 // add offset for leading '"'
170-
rangeEnd = formatPos + token.Pos(end) + 1 // add offset for leading '"'
171-
arg ast.Expr // may not exist
169+
rangeStart = formatPos + token.Pos(start)
170+
rangeEnd = formatPos + token.Pos(end)
171+
arg ast.Expr // may not exist
172172
)
173173
if len(call.Args) > argIndex {
174174
arg = call.Args[argIndex]

gopls/internal/test/marker/testdata/highlight/highlight_printf.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ func SpecialChars() {
3838
fmt.Printf("Hello \n %s, you \t \n have %d new messages!", "Alice", 5) //@hiloc(speciald, "%d", write),hiloc(specialargs1, "5", read),highlightall(speciald, specialargs1)
3939
}
4040

41+
func Escaped() {
42+
fmt.Printf("Hello %% \n %s, you \t%% \n have %d new m%%essages!", "Alice", 5) //@hiloc(escapeds, "%s", write),hiloc(escapedargs0, "\"Alice\"", read),highlightall(escapeds, escapedargs0)
43+
fmt.Printf("Hello %% \n %s, you \t%% \n have %d new m%%essages!", "Alice", 5) //@hiloc(escapedd, "%s", write),hiloc(escapedargs1, "\"Alice\"", read),highlightall(escapedd, escapedargs1)
44+
}
45+
4146
func Indexed() {
4247
fmt.Printf("%[1]d", 3) //@hiloc(i1, "%[1]d", write),hiloc(i2, "3", read),highlightall(i1, i2)
4348
fmt.Printf("%[1]*d", 3, 6) //@hiloc(i3, "[1]*", write),hiloc(i4, "3", read),hiloc(i5, "d", write),hiloc(i6, "6", read),highlightall(i3, i4),highlightall(i5, i6)

internal/fmtstr/parse.go

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2010 The Go Authors. All rights reserved.
1+
// Copyright 2024 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

@@ -7,7 +7,6 @@ package fmtstr
77
import (
88
"fmt"
99
"go/ast"
10-
"go/constant"
1110
"go/types"
1211
"strconv"
1312
"strings"
@@ -67,23 +66,18 @@ type posRange struct {
6766
}
6867

6968
// ParsePrintf takes a printf-like call expression,
70-
// extracts the format string, and parses out all format directives. Each
71-
// directive describes flags, width, precision, verb, and argument indexing.
72-
// This function returns a slice of parsed FormatDirective objects or an error
73-
// if parsing fails. It does not perform any validation of flags, verbs, and
74-
// existence of corresponding argument.
75-
//
76-
// Typical use case: Validate arguments passed to a Printf-like function call,
77-
// DocumentHighlight, Hover, or SemanticHighlight.
78-
func ParsePrintf(info *types.Info, call *ast.CallExpr) ([]*FormatDirective, error) {
69+
// extracts the format string, and parses out all format directives.
70+
// It returns a slice of parsed [FormatDirective] which describes
71+
// flags, width, precision, verb, and argument indexing, or an error
72+
// if parsing fails. It does not perform any validation of flags, verbs, nor the
73+
// existence of corresponding arguments.
74+
// The provided format may differ from the one in CallExpr, such as a concatenated string or a string
75+
// referred to by the argument in CallExpr.
76+
func ParsePrintf(info *types.Info, call *ast.CallExpr, format string) ([]*FormatDirective, error) {
7977
idx := FormatStringIndex(info, call)
8078
if idx < 0 || idx >= len(call.Args) {
8179
return nil, fmt.Errorf("not a valid printf-like call")
8280
}
83-
format, ok := StringConstantExpr(info, call.Args[idx])
84-
if !ok {
85-
return nil, fmt.Errorf("non-constant format string")
86-
}
8781
if !strings.Contains(format, "%") {
8882
return nil, fmt.Errorf("call has arguments but no formatting directives")
8983
}
@@ -208,17 +202,6 @@ func FormatStringIndex(info *types.Info, call *ast.CallExpr) int {
208202
return idx
209203
}
210204

211-
// StringConstantExpr returns expression's string constant value.
212-
//
213-
// ("", false) is returned if expression isn't a string constant.
214-
func StringConstantExpr(info *types.Info, expr ast.Expr) (string, bool) {
215-
lit := info.Types[expr].Value
216-
if lit != nil && lit.Kind() == constant.String {
217-
return constant.StringVal(lit), true
218-
}
219-
return "", false
220-
}
221-
222205
// addOffset adjusts the recorded positions in Verb, Width, Prec, and the
223206
// directive's overall Range to be relative to the position in the full format string.
224207
func (s *FormatDirective) addOffset(parsedLen int) {

0 commit comments

Comments
 (0)