Skip to content

Commit

Permalink
add a format arg
Browse files Browse the repository at this point in the history
  • Loading branch information
xzbdmw committed Dec 19, 2024
1 parent cebbfe8 commit fa85734
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 38 deletions.
24 changes: 20 additions & 4 deletions go/analysis/passes/printf/printf.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
_ "embed"
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"reflect"
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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, "%")
Expand All @@ -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)
}
Expand All @@ -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 {
Expand Down
16 changes: 8 additions & 8 deletions gopls/internal/golang/highlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -157,18 +157,18 @@ 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
}

// 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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func SpecialChars() {
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)
}

func Escaped() {
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)
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)
}

func Indexed() {
fmt.Printf("%[1]d", 3) //@hiloc(i1, "%[1]d", write),hiloc(i2, "3", read),highlightall(i1, i2)
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)
Expand Down
35 changes: 9 additions & 26 deletions internal/fmtstr/parse.go
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -7,7 +7,6 @@ package fmtstr
import (
"fmt"
"go/ast"
"go/constant"
"go/types"
"strconv"
"strings"
Expand Down Expand Up @@ -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")
}
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit fa85734

Please sign in to comment.