diff options
Diffstat (limited to 'gopls/internal/lsp/analysis/simplifyslice')
6 files changed, 334 insertions, 0 deletions
diff --git a/gopls/internal/lsp/analysis/simplifyslice/simplifyslice.go b/gopls/internal/lsp/analysis/simplifyslice/simplifyslice.go new file mode 100644 index 000000000..da1728e6f --- /dev/null +++ b/gopls/internal/lsp/analysis/simplifyslice/simplifyslice.go @@ -0,0 +1,94 @@ +// Copyright 2020 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. + +// Package simplifyslice defines an Analyzer that simplifies slice statements. +// https://github.com/golang/go/blob/master/src/cmd/gofmt/simplify.go +// https://golang.org/cmd/gofmt/#hdr-The_simplify_command +package simplifyslice + +import ( + "bytes" + "fmt" + "go/ast" + "go/printer" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +const Doc = `check for slice simplifications + +A slice expression of the form: + s[a:len(s)] +will be simplified to: + s[a:] + +This is one of the simplifications that "gofmt -s" applies.` + +var Analyzer = &analysis.Analyzer{ + Name: "simplifyslice", + Doc: Doc, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: run, +} + +// Note: We could also simplify slice expressions of the form s[0:b] to s[:b] +// but we leave them as is since sometimes we want to be very explicit +// about the lower bound. +// An example where the 0 helps: +// x, y, z := b[0:2], b[2:4], b[4:6] +// An example where it does not: +// x, y := b[:n], b[n:] + +func run(pass *analysis.Pass) (interface{}, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter := []ast.Node{ + (*ast.SliceExpr)(nil), + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + expr := n.(*ast.SliceExpr) + // - 3-index slices always require the 2nd and 3rd index + if expr.Max != nil { + return + } + s, ok := expr.X.(*ast.Ident) + // the array/slice object is a single, resolved identifier + if !ok || s.Obj == nil { + return + } + call, ok := expr.High.(*ast.CallExpr) + // the high expression is a function call with a single argument + if !ok || len(call.Args) != 1 || call.Ellipsis.IsValid() { + return + } + fun, ok := call.Fun.(*ast.Ident) + // the function called is "len" and it is not locally defined; and + // because we don't have dot imports, it must be the predefined len() + if !ok || fun.Name != "len" || fun.Obj != nil { + return + } + arg, ok := call.Args[0].(*ast.Ident) + // the len argument is the array/slice object + if !ok || arg.Obj != s.Obj { + return + } + var b bytes.Buffer + printer.Fprint(&b, pass.Fset, expr.High) + pass.Report(analysis.Diagnostic{ + Pos: expr.High.Pos(), + End: expr.High.End(), + Message: fmt.Sprintf("unneeded: %s", b.String()), + SuggestedFixes: []analysis.SuggestedFix{{ + Message: fmt.Sprintf("Remove '%s'", b.String()), + TextEdits: []analysis.TextEdit{{ + Pos: expr.High.Pos(), + End: expr.High.End(), + NewText: []byte{}, + }}, + }}, + }) + }) + return nil, nil +} diff --git a/gopls/internal/lsp/analysis/simplifyslice/simplifyslice_test.go b/gopls/internal/lsp/analysis/simplifyslice/simplifyslice_test.go new file mode 100644 index 000000000..41914ba31 --- /dev/null +++ b/gopls/internal/lsp/analysis/simplifyslice/simplifyslice_test.go @@ -0,0 +1,22 @@ +// Copyright 2020 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. + +package simplifyslice_test + +import ( + "testing" + + "golang.org/x/tools/go/analysis/analysistest" + "golang.org/x/tools/gopls/internal/lsp/analysis/simplifyslice" + "golang.org/x/tools/internal/typeparams" +) + +func Test(t *testing.T) { + testdata := analysistest.TestData() + tests := []string{"a"} + if typeparams.Enabled { + tests = append(tests, "typeparams") + } + analysistest.RunWithSuggestedFixes(t, testdata, simplifyslice.Analyzer, tests...) +} diff --git a/gopls/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go new file mode 100644 index 000000000..20792105d --- /dev/null +++ b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go @@ -0,0 +1,70 @@ +// Copyright 2020 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. + +package testdata + +var ( + a [10]byte + b [20]float32 + s []int + t struct { + s []byte + } + + _ = a[0:] + _ = a[1:10] + _ = a[2:len(a)] // want "unneeded: len\\(a\\)" + _ = a[3:(len(a))] + _ = a[len(a)-1 : len(a)] // want "unneeded: len\\(a\\)" + _ = a[2:len(a):len(a)] + + _ = a[:] + _ = a[:10] + _ = a[:len(a)] // want "unneeded: len\\(a\\)" + _ = a[:(len(a))] + _ = a[:len(a)-1] + _ = a[:len(a):len(a)] + + _ = s[0:] + _ = s[1:10] + _ = s[2:len(s)] // want "unneeded: len\\(s\\)" + _ = s[3:(len(s))] + _ = s[len(a) : len(s)-1] + _ = s[0:len(b)] + _ = s[2:len(s):len(s)] + + _ = s[:] + _ = s[:10] + _ = s[:len(s)] // want "unneeded: len\\(s\\)" + _ = s[:(len(s))] + _ = s[:len(s)-1] + _ = s[:len(b)] + _ = s[:len(s):len(s)] + + _ = t.s[0:] + _ = t.s[1:10] + _ = t.s[2:len(t.s)] + _ = t.s[3:(len(t.s))] + _ = t.s[len(a) : len(t.s)-1] + _ = t.s[0:len(b)] + _ = t.s[2:len(t.s):len(t.s)] + + _ = t.s[:] + _ = t.s[:10] + _ = t.s[:len(t.s)] + _ = t.s[:(len(t.s))] + _ = t.s[:len(t.s)-1] + _ = t.s[:len(b)] + _ = t.s[:len(t.s):len(t.s)] +) + +func _() { + s := s[0:len(s)] // want "unneeded: len\\(s\\)" + _ = s +} + +func m() { + maps := []int{} + _ = maps[1:len(maps)] // want "unneeded: len\\(maps\\)" +} diff --git a/gopls/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go.golden b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go.golden new file mode 100644 index 000000000..45c791421 --- /dev/null +++ b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/a/a.go.golden @@ -0,0 +1,70 @@ +// Copyright 2020 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. + +package testdata + +var ( + a [10]byte + b [20]float32 + s []int + t struct { + s []byte + } + + _ = a[0:] + _ = a[1:10] + _ = a[2:] // want "unneeded: len\\(a\\)" + _ = a[3:(len(a))] + _ = a[len(a)-1:] // want "unneeded: len\\(a\\)" + _ = a[2:len(a):len(a)] + + _ = a[:] + _ = a[:10] + _ = a[:] // want "unneeded: len\\(a\\)" + _ = a[:(len(a))] + _ = a[:len(a)-1] + _ = a[:len(a):len(a)] + + _ = s[0:] + _ = s[1:10] + _ = s[2:] // want "unneeded: len\\(s\\)" + _ = s[3:(len(s))] + _ = s[len(a) : len(s)-1] + _ = s[0:len(b)] + _ = s[2:len(s):len(s)] + + _ = s[:] + _ = s[:10] + _ = s[:] // want "unneeded: len\\(s\\)" + _ = s[:(len(s))] + _ = s[:len(s)-1] + _ = s[:len(b)] + _ = s[:len(s):len(s)] + + _ = t.s[0:] + _ = t.s[1:10] + _ = t.s[2:len(t.s)] + _ = t.s[3:(len(t.s))] + _ = t.s[len(a) : len(t.s)-1] + _ = t.s[0:len(b)] + _ = t.s[2:len(t.s):len(t.s)] + + _ = t.s[:] + _ = t.s[:10] + _ = t.s[:len(t.s)] + _ = t.s[:(len(t.s))] + _ = t.s[:len(t.s)-1] + _ = t.s[:len(b)] + _ = t.s[:len(t.s):len(t.s)] +) + +func _() { + s := s[0:] // want "unneeded: len\\(s\\)" + _ = s +} + +func m() { + maps := []int{} + _ = maps[1:] // want "unneeded: len\\(maps\\)" +} diff --git a/gopls/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go new file mode 100644 index 000000000..69db3100a --- /dev/null +++ b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go @@ -0,0 +1,39 @@ +// Copyright 2021 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. +// +//go:build go1.18 +// +build go1.18 + +package testdata + +type List[E any] []E + +// TODO(suzmue): add a test for generic slice expressions when https://github.com/golang/go/issues/48618 is closed. +// type S interface{ ~[]int } + +var ( + a [10]byte + b [20]float32 + p List[int] + + _ = p[0:] + _ = p[1:10] + _ = p[2:len(p)] // want "unneeded: len\\(p\\)" + _ = p[3:(len(p))] + _ = p[len(a) : len(p)-1] + _ = p[0:len(b)] + _ = p[2:len(p):len(p)] + + _ = p[:] + _ = p[:10] + _ = p[:len(p)] // want "unneeded: len\\(p\\)" + _ = p[:(len(p))] + _ = p[:len(p)-1] + _ = p[:len(b)] + _ = p[:len(p):len(p)] +) + +func foo[E any](a List[E]) { + _ = a[0:len(a)] // want "unneeded: len\\(a\\)" +} diff --git a/gopls/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden new file mode 100644 index 000000000..99ca9e447 --- /dev/null +++ b/gopls/internal/lsp/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden @@ -0,0 +1,39 @@ +// Copyright 2021 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. +// +//go:build go1.18 +// +build go1.18 + +package testdata + +type List[E any] []E + +// TODO(suzmue): add a test for generic slice expressions when https://github.com/golang/go/issues/48618 is closed. +// type S interface{ ~[]int } + +var ( + a [10]byte + b [20]float32 + p List[int] + + _ = p[0:] + _ = p[1:10] + _ = p[2:] // want "unneeded: len\\(p\\)" + _ = p[3:(len(p))] + _ = p[len(a) : len(p)-1] + _ = p[0:len(b)] + _ = p[2:len(p):len(p)] + + _ = p[:] + _ = p[:10] + _ = p[:] // want "unneeded: len\\(p\\)" + _ = p[:(len(p))] + _ = p[:len(p)-1] + _ = p[:len(b)] + _ = p[:len(p):len(p)] +) + +func foo[E any](a List[E]) { + _ = a[0:] // want "unneeded: len\\(a\\)" +} |