diff --git a/gopls/internal/golang/highlight.go b/gopls/internal/golang/highlight.go index 3aa875a7c4a..8fda50e19e8 100644 --- a/gopls/internal/golang/highlight.go +++ b/gopls/internal/golang/highlight.go @@ -51,7 +51,7 @@ func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, po } } } - result, err := highlightPath(path, pgf.File, pkg.TypesInfo(), pos) + result, err := highlightPath(pkg.TypesInfo(), path, pos) if err != nil { return nil, err } @@ -71,9 +71,8 @@ func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, po // highlightPath returns ranges to highlight for the given enclosing path, // which should be the result of astutil.PathEnclosingInterval. -func highlightPath(path []ast.Node, file *ast.File, info *types.Info, pos token.Pos) (map[posRange]protocol.DocumentHighlightKind, error) { +func highlightPath(info *types.Info, path []ast.Node, pos token.Pos) (map[posRange]protocol.DocumentHighlightKind, error) { result := make(map[posRange]protocol.DocumentHighlightKind) - // Inside a printf-style call, printf("...%v...", arg)? // Treat each corresponding ("%v", arg) pair as a highlight class. for _, node := range path { @@ -88,6 +87,7 @@ func highlightPath(path []ast.Node, file *ast.File, info *types.Info, pos token. } } + file := path[len(path)-1].(*ast.File) switch node := path[0].(type) { case *ast.BasicLit: // Import path string literal? diff --git a/internal/fmtstr/parse.go b/internal/fmtstr/parse.go index c24cd7eec46..a3443c93231 100644 --- a/internal/fmtstr/parse.go +++ b/internal/fmtstr/parse.go @@ -5,7 +5,6 @@ package fmtstr import ( - _ "embed" "fmt" "go/ast" "go/constant" @@ -13,9 +12,60 @@ import ( "strconv" "strings" "unicode/utf8" - // "golang.org/x/tools/go/analysis/passes/internal/analysisutil" ) +// FormatDirective holds the parsed representation of a printf directive such as "%3.*[4]d". +// It is constructed by [ParsePrintf]. +type FormatDirective struct { + Format string // Full directive, e.g. "%[2]*.3d" + Range posRange // The range of Format within the overall format string + FirstArg int // Index of the first argument after the format string + Flags []byte // Formatting flags, e.g. ['-', '0'] + Width *DirectiveSize // Width specifier, if any (e.g., '3' in '%3d') + Prec *DirectiveSize // Precision specifier, if any (e.g., '.4' in '%.4f') + Verb *DirectiveVerb // Verb specifier, if any (e.g., '[1]d' in '%[1]d') + + // Parsing state (not used after parsing): + call *ast.CallExpr + argNum int + hasIndex bool + index int + indexPos int + indexPending bool + nbytes int +} + +type SizeKind int + +const ( + Literal SizeKind = iota // A literal number, e.g. "4" in "%4d" + Asterisk // A dynamic size from an argument, e.g. "%*d" + IndexedAsterisk // A dynamic size with an explicit index, e.g. "%[2]*d" +) + +// DirectiveSize describes a width or precision in a format directive. +// Depending on Kind, it may represent a literal number, a asterisk, or an indexed asterisk. +type DirectiveSize struct { + Kind SizeKind // Type of size specifier + Range posRange // Position of the size specifier within the directive + Size int // The literal size if Kind == Literal, otherwise -1 + Index int // If Kind == IndexedAsterisk, the argument index used to obtain the size + ArgNum int // The argument position if Kind == Asterisk or IndexedAsterisk, relative to CallExpr. +} + +// DirectiveVerb represents the verb character of a format directive (e.g., 'd', 's', 'f'). +// It also includes positional information and any explicit argument indexing. +type DirectiveVerb struct { + Verb rune + Range posRange // The positional range of the verb in the format string + Index int // If the verb uses an indexed argument, this is the index, otherwise -1 + ArgNum int // The argument position associated with this verb, relative to CallExpr. +} + +type posRange struct { + Start, End int +} + // 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. @@ -28,7 +78,7 @@ import ( func ParsePrintf(info *types.Info, call *ast.CallExpr) ([]*FormatDirective, error) { idx := FormatStringIndex(info, call) if idx < 0 || idx >= len(call.Args) { - return nil, fmt.Errorf("%s", "can't parse") + return nil, fmt.Errorf("not a valid printf-like call") } format, ok := StringConstantExpr(info, call.Args[idx]) if !ok { @@ -169,58 +219,6 @@ func StringConstantExpr(info *types.Info, expr ast.Expr) (string, bool) { return "", false } -// FormatDirective holds the parsed representation of a printf directive such as "%3.*[4]d". -// It is constructed by [ParsePrintf]. -type FormatDirective struct { - Format string // Full directive, e.g. "%[2]*.3d" - Range posRange // The range of Format within the overall format string - FirstArg int // Index of the first argument after the format string - Flags []byte // Formatting flags, e.g. ['-', '0'] - Width *DirectiveSize // Width specifier, if any (e.g., '3' in '%3d') - Prec *DirectiveSize // Precision specifier, if any (e.g., '.4' in '%.4f') - Verb *DirectiveVerb // Verb specifier, if any (e.g., '[1]d' in '%[1]d') - - // Parsing state (not used after parsing): - call *ast.CallExpr - argNum int - hasIndex bool - index int - indexPos int - indexPending bool - nbytes int -} - -type SizeKind int - -const ( - Literal SizeKind = iota // A literal number, e.g. "4" in "%4d" - Asterisk // A dynamic size from an argument, e.g. "%*d" - IndexedAsterisk // A dynamic size with an explicit index, e.g. "%[2]*d" -) - -// DirectiveSize describes a width or precision in a format directive. -// Depending on Kind, it may represent a literal number, a asterisk, or an indexed asterisk. -type DirectiveSize struct { - Kind SizeKind // Type of size specifier - Range posRange // Position of the size specifier within the directive - Size int // The literal size if Kind == Literal, otherwise -1 - Index int // If Kind == IndexedAsterisk, the argument index used to obtain the size - ArgNum int // The argument position if Kind == Asterisk or IndexedAsterisk, relative to CallExpr. -} - -// DirectiveVerb represents the verb character of a format directive (e.g., 'd', 's', 'f'). -// It also includes positional information and any explicit argument indexing. -type DirectiveVerb struct { - Verb rune - Range posRange // The positional range of the verb in the format string - Index int // If the verb uses an indexed argument, this is the index, otherwise -1 - ArgNum int // The argument position associated with this verb, relative to CallExpr. -} - -type posRange struct { - Start, End int -} - // 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) { @@ -254,23 +252,6 @@ func (s *FormatDirective) parseFlags() { } } -// scanNum advances through a decimal number if present, which represents a [Size] or [Index]. -func (s *FormatDirective) scanNum() (int, bool) { - start := s.nbytes - for ; s.nbytes < len(s.Format); s.nbytes++ { - c := s.Format[s.nbytes] - if c < '0' || '9' < c { - if start < s.nbytes { - num, _ := strconv.ParseInt(s.Format[start:s.nbytes], 10, 32) - return int(num), true - } else { - return 0, false - } - } - } - return 0, false -} - // parseIndex parses an argument index of the form "[n]" that can appear // in a printf directive (e.g., "%[2]d"). Returns an error if syntax is // malformed or index is invalid. @@ -310,6 +291,23 @@ func (s *FormatDirective) parseIndex() error { return nil } +// scanNum advances through a decimal number if present, which represents a [Size] or [Index]. +func (s *FormatDirective) scanNum() (int, bool) { + start := s.nbytes + for ; s.nbytes < len(s.Format); s.nbytes++ { + c := s.Format[s.nbytes] + if c < '0' || '9' < c { + if start < s.nbytes { + num, _ := strconv.ParseInt(s.Format[start:s.nbytes], 10, 32) + return int(num), true + } else { + return 0, false + } + } + } + return 0, false +} + type sizeType int const (