(This brings in CL 703995.)

For #71859

Change-Id: I823f2da9ebbdbef943cb37123d44a7ad2e6d708b
Reviewed-on: https://go-review.googlesource.com/c/go/+/703896
Reviewed-by: Michael Matloob <matloob@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Michael Matloob <matloob@google.com>
This commit is contained in:
Alan Donovan 2025-09-15 16:33:47 -04:00
parent 8ace10dad2
commit dbde15800c
13 changed files with 64 additions and 47 deletions

View file

@ -11,7 +11,7 @@ require (
golang.org/x/sys v0.36.0
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053
golang.org/x/term v0.34.0
golang.org/x/tools v0.37.1-0.20250911182313-3adf0e96d87d
golang.org/x/tools v0.37.1-0.20250915202913-9fccddc465ef
)
require (

View file

@ -22,7 +22,7 @@ golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.37.1-0.20250911182313-3adf0e96d87d h1:ykSs3aFXygx903AtF0IG27E/xLaCW5t85BAdCALyp8s=
golang.org/x/tools v0.37.1-0.20250911182313-3adf0e96d87d/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools v0.37.1-0.20250915202913-9fccddc465ef h1:ISPkUgvOYIt0oS7oVnwAPktCKBvgWkDlWWGMgX0veZM=
golang.org/x/tools v0.37.1-0.20250915202913-9fccddc465ef/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=

View file

@ -237,7 +237,7 @@ Files:
// so accumulate them all and then prefer the one that
// matches build.Default.GOARCH.
var archCandidates []*asmArch
for _, fld := range strings.Fields(m[1]) {
for fld := range strings.FieldsSeq(m[1]) {
for _, a := range arches {
if a.name == fld {
archCandidates = append(archCandidates, a)

View file

@ -298,7 +298,7 @@ func (check *checker) plusBuildLine(pos token.Pos, line string) {
fields := strings.Fields(line[len("//"):])
// IsPlusBuildConstraint check above implies fields[0] == "+build"
for _, arg := range fields[1:] {
for _, elem := range strings.Split(arg, ",") {
for elem := range strings.SplitSeq(arg, ",") {
if strings.HasPrefix(elem, "!!") {
check.pass.Reportf(pos, "invalid double negative in build constraint: %s", arg)
check.crossCheck = false

View file

@ -10,7 +10,9 @@ import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
@ -57,13 +59,16 @@ func run(pass *analysis.Pass) (any, error) {
}
// checkAddr reports a diagnostic (and returns true) if e
// is a call of the form fmt.Sprintf("%d:%d", ...).
// is a call of the form fmt.Sprintf("%s:%d", ...).
// The diagnostic includes a fix.
//
// dialCall is non-nil if the Dial call is non-local
// but within the same file.
checkAddr := func(e ast.Expr, dialCall *ast.CallExpr) {
if call, ok := e.(*ast.CallExpr); ok && typeutil.Callee(info, call) == fmtSprintf {
if call, ok := e.(*ast.CallExpr); ok &&
len(call.Args) == 3 &&
typeutil.Callee(info, call) == fmtSprintf {
// Examine format string.
formatArg := call.Args[0]
if tv := info.Types[formatArg]; tv.Value != nil {
@ -99,21 +104,41 @@ func run(pass *analysis.Pass) (any, error) {
// Turn numeric port into a string.
if numericPort {
// port => fmt.Sprintf("%d", port)
// 123 => "123"
port := call.Args[2]
newPort := fmt.Sprintf(`fmt.Sprintf("%%d", %s)`, port)
if port := info.Types[port].Value; port != nil {
if i, ok := constant.Int64Val(port); ok {
newPort = fmt.Sprintf(`"%d"`, i) // numeric constant
// Is port an integer literal?
//
// (Don't allow arbitrary constants k otherwise the
// transformation k => fmt.Sprintf("%d", "123")
// loses the symbolic connection to k.)
var kPort int64 = -1
if lit, ok := port.(*ast.BasicLit); ok && lit.Kind == token.INT {
if v, err := strconv.ParseInt(lit.Value, 0, 64); err == nil {
kPort = v
}
}
edits = append(edits, analysis.TextEdit{
Pos: port.Pos(),
End: port.End(),
NewText: []byte(newPort),
})
if kPort >= 0 {
// literal: 0x7B => "123"
edits = append(edits, analysis.TextEdit{
Pos: port.Pos(),
End: port.End(),
NewText: fmt.Appendf(nil, `"%d"`, kPort), // (decimal)
})
} else {
// non-literal: port => fmt.Sprintf("%d", port)
edits = append(edits, []analysis.TextEdit{
{
Pos: port.Pos(),
End: port.Pos(),
NewText: []byte(`fmt.Sprintf("%d", `),
},
{
Pos: port.End(),
End: port.End(),
NewText: []byte(`)`),
},
}...)
}
}
// Refer to Dial call, if not adjacent.

View file

@ -992,7 +992,7 @@ func (ss stringSet) String() string {
}
func (ss stringSet) Set(flag string) error {
for _, name := range strings.Split(flag, ",") {
for name := range strings.SplitSeq(flag, ",") {
if len(name) == 0 {
return fmt.Errorf("empty string")
}

View file

@ -188,7 +188,7 @@ func (ss *stringSetFlag) String() string {
func (ss *stringSetFlag) Set(s string) error {
m := make(map[string]bool) // clobber previous value
if s != "" {
for _, name := range strings.Split(s, ",") {
for name := range strings.SplitSeq(s, ",") {
if name == "" {
continue // TODO: report error? proceed?
}

View file

@ -74,7 +74,8 @@ type Config struct {
VetxOnly bool // run analysis only for facts, not diagnostics
VetxOutput string // where to write file of fact information
Stdout string // write stdout (e.g. JSON, unified diff) to this file
SucceedOnTypecheckFailure bool
SucceedOnTypecheckFailure bool // obsolete awful hack; see #18395 and below
WarnDiagnostics bool // printing diagnostics should not cause a non-zero exit
}
// Main is the main function of a vet-like analysis tool that must be
@ -162,7 +163,7 @@ func Run(configFile string, analyzers []*analysis.Analyzer) {
// In VetxOnly mode, the analysis is run only for facts.
if !cfg.VetxOnly {
code = processResults(fset, cfg.ID, results)
code = processResults(fset, cfg.ID, results, cfg.WarnDiagnostics)
}
os.Exit(code)
@ -186,7 +187,7 @@ func readConfig(filename string) (*Config, error) {
return cfg, nil
}
func processResults(fset *token.FileSet, id string, results []result) (exit int) {
func processResults(fset *token.FileSet, id string, results []result, warnDiagnostics bool) (exit int) {
if analysisflags.Fix {
// Don't print the diagnostics,
// but apply all fixes from the root actions.
@ -235,7 +236,9 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
for _, res := range results {
for _, diag := range res.diagnostics {
analysisflags.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
exit = 1
if !warnDiagnostics {
exit = 1
}
}
}
}

View file

@ -74,25 +74,6 @@ func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos
return end
}
// WalkASTWithParent walks the AST rooted at n. The semantics are
// similar to ast.Inspect except it does not call f(nil).
func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
var ancestors []ast.Node
ast.Inspect(n, func(n ast.Node) (recurse bool) {
if n == nil {
ancestors = ancestors[:len(ancestors)-1]
return false
}
var parent ast.Node
if len(ancestors) > 0 {
parent = ancestors[len(ancestors)-1]
}
ancestors = append(ancestors, n)
return f(n, parent)
})
}
// MatchingIdents finds the names of all identifiers in 'node' that match any of the given types.
// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within
// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that

View file

@ -97,7 +97,7 @@ func ExtractDoc(content, name string) (string, error) {
if f.Doc == nil {
return "", fmt.Errorf("Go source file has no package doc comment")
}
for _, section := range strings.Split(f.Doc.Text(), "\n# ") {
for section := range strings.SplitSeq(f.Doc.Text(), "\n# ") {
if body := strings.TrimPrefix(section, "Analyzer "+name); body != section &&
body != "" &&
body[0] == '\r' || body[0] == '\n' {

View file

@ -15,7 +15,7 @@ import (
// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not
// deprecated.
func Deprecation(doc *ast.CommentGroup) string {
for _, p := range strings.Split(doc.Text(), "\n\n") {
for p := range strings.SplitSeq(doc.Text(), "\n\n") {
// There is still some ambiguity for deprecation message. This function
// only returns the paragraph introduced by "Deprecated: ". More
// information related to the deprecation may follow in additional

View file

@ -15,6 +15,14 @@ import (
// file.
// If the same package is imported multiple times, the last appearance is
// recorded.
//
// TODO(adonovan): this function ignores the effect of shadowing. It
// should accept a [token.Pos] and a [types.Info] and compute only the
// set of imports that are not shadowed at that point, analogous to
// [analysisinternal.AddImport]. It could also compute (as a side
// effect) the set of additional imports required to ensure that there
// is an accessible import for each necessary package, making it
// converge even more closely with AddImport.
func FileQualifier(f *ast.File, pkg *types.Package) types.Qualifier {
// Construct mapping of import paths to their defined names.
// It is only necessary to look at renaming imports.

View file

@ -73,7 +73,7 @@ golang.org/x/text/internal/tag
golang.org/x/text/language
golang.org/x/text/transform
golang.org/x/text/unicode/norm
# golang.org/x/tools v0.37.1-0.20250911182313-3adf0e96d87d
# golang.org/x/tools v0.37.1-0.20250915202913-9fccddc465ef
## explicit; go 1.24.0
golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover