From 192a33e3b5a5056792ad1f2d3d03765eacb5e275 Mon Sep 17 00:00:00 2001 From: xzb <2598514867@qq.com> Date: Fri, 20 Dec 2024 02:34:39 +0800 Subject: [PATCH] add a format arg --- go/analysis/passes/printf/printf.go | 24 ++++++++++++++++---- gopls/internal/golang/highlight.go | 16 ++++++------- internal/fmtstr/parse.go | 35 ++++++++--------------------- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/go/analysis/passes/printf/printf.go b/go/analysis/passes/printf/printf.go index f2bffb64551..ec7d843fb89 100644 --- a/go/analysis/passes/printf/printf.go +++ b/go/analysis/passes/printf/printf.go @@ -9,6 +9,7 @@ import ( _ "embed" "fmt" "go/ast" + "go/constant" "go/token" "go/types" "reflect" @@ -452,7 +453,7 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, name string return } formatArg := call.Args[idx] - format, ok := fmtstr.StringConstantExpr(pass.TypesInfo, formatArg) + format, ok := stringConstantExpr(pass.TypesInfo, formatArg) if !ok { // Format string argument is non-constant. @@ -486,7 +487,11 @@ func checkPrintf(pass *analysis.Pass, kind Kind, call *ast.CallExpr, name string } return } - directives, err := fmtstr.ParsePrintf(pass.TypesInfo, call) + + // Pass the string constant value so + // fmt.Sprintf("%"+("s"), "hi", 3) can be reported as + // "fmt.Sprintf call needs 1 arg but has 2 args". + directives, err := fmtstr.ParsePrintf(pass.TypesInfo, call, format) if err != nil { pass.ReportRangef(call.Fun, "%s %s", name, err.Error()) return @@ -856,7 +861,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) { } arg := args[0] - if s, ok := fmtstr.StringConstantExpr(pass.TypesInfo, arg); ok { + if s, ok := stringConstantExpr(pass.TypesInfo, arg); ok { // Ignore trailing % character // The % in "abc 0.0%" couldn't be a formatting directive. s = strings.TrimSuffix(s, "%") @@ -870,7 +875,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) { if strings.HasSuffix(name, "ln") { // The last item, if a string, should not have a newline. arg = args[len(args)-1] - if s, ok := fmtstr.StringConstantExpr(pass.TypesInfo, arg); ok { + if s, ok := stringConstantExpr(pass.TypesInfo, arg); ok { if strings.HasSuffix(s, "\n") { pass.ReportRangef(call, "%s arg list ends with redundant newline", name) } @@ -886,6 +891,17 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) { } } +// StringConstantExpr returns expression's string constant value. +// +// ("", false) is returned if expression isn't a string constant. +func stringConstantExpr(info *types.Info, expr ast.Expr) (string, bool) { + lit := info.Types[expr].Value + if lit != nil && lit.Kind() == constant.String { + return constant.StringVal(lit), true + } + return "", false +} + // count(n, what) returns "1 what" or "N whats" // (assuming the plural of what is whats). func count(n int, what string) string { diff --git a/gopls/internal/golang/highlight.go b/gopls/internal/golang/highlight.go index 8330df4a5ec..beb3555038d 100644 --- a/gopls/internal/golang/highlight.go +++ b/gopls/internal/golang/highlight.go @@ -80,9 +80,9 @@ func highlightPath(info *types.Info, path []ast.Node, pos token.Pos) (map[posRan if call, ok := node.(*ast.CallExpr); ok { idx := fmtstr.FormatStringIndex(info, call) if idx >= 0 && idx < len(call.Args) { - format, ok := fmtstr.StringConstantExpr(info, call.Args[idx]) - if ok && strings.Contains(format, "%") { - highlightPrintf(info, call, call.Args[idx].Pos(), pos, result) + // We only care string literal, so fmt.Sprint("a"+"b%s", "bar") won't highlight. + if lit, ok := call.Args[idx].(*ast.BasicLit); ok && strings.Contains(lit.Value, "%") { + highlightPrintf(info, call, call.Args[idx].Pos(), pos, lit.Value, result) } } } @@ -157,8 +157,8 @@ func highlightPath(info *types.Info, path []ast.Node, pos token.Pos) (map[posRan // // If the cursor is on %s or name, highlightPrintf will highlight %s as a write operation, // and name as a read operation. -func highlightPrintf(info *types.Info, call *ast.CallExpr, formatPos token.Pos, cursorPos token.Pos, result map[posRange]protocol.DocumentHighlightKind) { - directives, err := fmtstr.ParsePrintf(info, call) +func highlightPrintf(info *types.Info, call *ast.CallExpr, formatPos token.Pos, cursorPos token.Pos, format string, result map[posRange]protocol.DocumentHighlightKind) { + directives, err := fmtstr.ParsePrintf(info, call, format) if err != nil { return } @@ -166,9 +166,9 @@ func highlightPrintf(info *types.Info, call *ast.CallExpr, formatPos token.Pos, // highlightPair highlights the directive and its potential argument pair if the cursor is within either range. highlightPair := func(start, end token.Pos, argIndex int) bool { var ( - rangeStart = formatPos + token.Pos(start) + 1 // add offset for leading '"' - rangeEnd = formatPos + token.Pos(end) + 1 // add offset for leading '"' - arg ast.Expr // may not exist + rangeStart = formatPos + token.Pos(start) + rangeEnd = formatPos + token.Pos(end) + arg ast.Expr // may not exist ) if len(call.Args) > argIndex { arg = call.Args[argIndex] diff --git a/internal/fmtstr/parse.go b/internal/fmtstr/parse.go index ee15f28cb6a..81458d1c672 100644 --- a/internal/fmtstr/parse.go +++ b/internal/fmtstr/parse.go @@ -1,4 +1,4 @@ -// Copyright 2010 The Go Authors. All rights reserved. +// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -7,7 +7,6 @@ package fmtstr import ( "fmt" "go/ast" - "go/constant" "go/types" "strconv" "strings" @@ -67,23 +66,18 @@ type posRange struct { } // ParsePrintf takes a printf-like call expression, -// extracts the format string, and parses out all format directives. Each -// directive describes flags, width, precision, verb, and argument indexing. -// This function returns a slice of parsed FormatDirective objects or an error -// if parsing fails. It does not perform any validation of flags, verbs, and -// existence of corresponding argument. -// -// Typical use case: Validate arguments passed to a Printf-like function call, -// DocumentHighlight, Hover, or SemanticHighlight. -func ParsePrintf(info *types.Info, call *ast.CallExpr) ([]*FormatDirective, error) { +// extracts the format string, and parses out all format directives. +// It returns a slice of parsed [FormatDirective] which describes +// flags, width, precision, verb, and argument indexing, or an error +// if parsing fails. It does not perform any validation of flags, verbs, nor the +// existence of corresponding arguments. +// The provided format may differ from the one in CallExpr, such as a concatenated string or a string +// referred to by the argument in CallExpr. +func ParsePrintf(info *types.Info, call *ast.CallExpr, format string) ([]*FormatDirective, error) { idx := FormatStringIndex(info, call) if idx < 0 || idx >= len(call.Args) { return nil, fmt.Errorf("not a valid printf-like call") } - format, ok := StringConstantExpr(info, call.Args[idx]) - if !ok { - return nil, fmt.Errorf("non-constant format string") - } if !strings.Contains(format, "%") { return nil, fmt.Errorf("call has arguments but no formatting directives") } @@ -208,17 +202,6 @@ func FormatStringIndex(info *types.Info, call *ast.CallExpr) int { return idx } -// StringConstantExpr returns expression's string constant value. -// -// ("", false) is returned if expression isn't a string constant. -func StringConstantExpr(info *types.Info, expr ast.Expr) (string, bool) { - lit := info.Types[expr].Value - if lit != nil && lit.Kind() == constant.String { - return constant.StringVal(lit), true - } - return "", false -} - // addOffset adjusts the recorded positions in Verb, Width, Prec, and the // directive's overall Range to be relative to the position in the full format string. func (s *FormatDirective) addOffset(parsedLen int) {