tka: keep the CompactionDefaults alongside the other limits #6
@@ -0,0 +1,132 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package lowerell forbids variables named "l" (lowercase ell) or "I"
|
||||
// (uppercase i), because they are hard to distinguish from the digit
|
||||
// "1" and from each other in too many fonts.
|
||||
package lowerell
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// Analyzer reports variables named "l" (lowercase ell) or "I" (uppercase i).
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "lowerell",
|
||||
Doc: `forbid variables named "l" (lowercase ell) or "I" (uppercase i), which are hard to distinguish from "1"`,
|
||||
Run: run,
|
||||
}
|
||||
|
||||
// messages maps a banned identifier name to the diagnostic shown to users.
|
||||
// Each message names the specific symbol that triggered it, so the
|
||||
// reader does not have to guess which of "l" or "I" they typed.
|
||||
var messages = map[string]string{
|
||||
"l": `do not use "l" (lowercase ell) as a variable name; it is hard to distinguish from "1" and "I" in too many fonts; see https://github.com/tailscale/tailscale/issues/19631`,
|
||||
"I": `do not use "I" (uppercase i) as a variable name; it is hard to distinguish from "1" and "l" in too many fonts; see https://github.com/tailscale/tailscale/issues/19631`,
|
||||
}
|
||||
|
||||
// reported tracks identifier positions already reported, to avoid duplicate
|
||||
// diagnostics when the same declaration is reachable from multiple AST nodes.
|
||||
type reportedSet map[token.Pos]bool
|
||||
|
||||
func (rs reportedSet) check(pass *analysis.Pass, ident *ast.Ident) {
|
||||
if ident == nil {
|
||||
return
|
||||
}
|
||||
msg, ok := messages[ident.Name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if rs[ident.Pos()] {
|
||||
return
|
||||
}
|
||||
rs[ident.Pos()] = true
|
||||
pass.Reportf(ident.Pos(), "%s", msg)
|
||||
}
|
||||
|
||||
func (rs reportedSet) checkFieldList(pass *analysis.Pass, fl *ast.FieldList) {
|
||||
if fl == nil {
|
||||
return
|
||||
}
|
||||
for _, f := range fl.List {
|
||||
for _, n := range f.Names {
|
||||
rs.check(pass, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
rs := reportedSet{}
|
||||
|
||||
for _, file := range pass.Files {
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
switch n := n.(type) {
|
||||
case *ast.FuncDecl:
|
||||
// Receiver name.
|
||||
rs.checkFieldList(pass, n.Recv)
|
||||
// Parameters, results, and type parameters
|
||||
// are checked via the FuncType case below.
|
||||
|
||||
case *ast.FuncType:
|
||||
rs.checkFieldList(pass, n.TypeParams)
|
||||
rs.checkFieldList(pass, n.Params)
|
||||
rs.checkFieldList(pass, n.Results)
|
||||
|
||||
case *ast.StructType:
|
||||
rs.checkFieldList(pass, n.Fields)
|
||||
|
||||
case *ast.GenDecl:
|
||||
if n.Tok != token.VAR && n.Tok != token.CONST {
|
||||
return true
|
||||
}
|
||||
for _, spec := range n.Specs {
|
||||
vs, ok := spec.(*ast.ValueSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, name := range vs.Names {
|
||||
rs.check(pass, name)
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.AssignStmt:
|
||||
if n.Tok != token.DEFINE {
|
||||
return true
|
||||
}
|
||||
for _, lhs := range n.Lhs {
|
||||
if id, ok := lhs.(*ast.Ident); ok {
|
||||
rs.check(pass, id)
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.RangeStmt:
|
||||
if n.Tok != token.DEFINE {
|
||||
return true
|
||||
}
|
||||
if id, ok := n.Key.(*ast.Ident); ok {
|
||||
rs.check(pass, id)
|
||||
}
|
||||
if id, ok := n.Value.(*ast.Ident); ok {
|
||||
rs.check(pass, id)
|
||||
}
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
// switch l := x.(type) { ... }
|
||||
as, ok := n.Assign.(*ast.AssignStmt)
|
||||
if !ok || as.Tok != token.DEFINE {
|
||||
return true
|
||||
}
|
||||
for _, lhs := range as.Lhs {
|
||||
if id, ok := lhs.(*ast.Ident); ok {
|
||||
rs.check(pass, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package lowerell
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/analysis/analysistest"
|
||||
)
|
||||
|
||||
func TestAnalyzer(t *testing.T) {
|
||||
testdata := analysistest.TestData()
|
||||
analysistest.Run(t, testdata, Analyzer, "example")
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package example
|
||||
|
||||
import "sync"
|
||||
|
||||
// Bad: var declarations.
|
||||
var l int // want `do not use "l"`
|
||||
var I int // want `do not use "I"`
|
||||
|
||||
// OK: variables named "ll", "II", "i" are fine.
|
||||
var (
|
||||
ll int
|
||||
II int
|
||||
i int
|
||||
)
|
||||
|
||||
// Bad: const declaration in a function scope.
|
||||
func F0() {
|
||||
const l = 3 // want `do not use "l"`
|
||||
const I = 4 // want `do not use "I"`
|
||||
_ = l
|
||||
_ = I
|
||||
}
|
||||
|
||||
// Bad: function parameters.
|
||||
func F1a(l int) {} // want `do not use "l"`
|
||||
func F1b(I int) {} // want `do not use "I"`
|
||||
|
||||
// Bad: named return values.
|
||||
func F2a() (l int) { return } // want `do not use "l"`
|
||||
func F2b() (I int) { return } // want `do not use "I"`
|
||||
|
||||
// Bad: receiver names.
|
||||
type T struct{}
|
||||
|
||||
func (l *T) Ml() {} // want `do not use "l"`
|
||||
func (I *T) MI() {} // want `do not use "I"`
|
||||
|
||||
// Bad: struct fields.
|
||||
type S struct {
|
||||
l int // want `do not use "l"`
|
||||
I int // want `do not use "I"`
|
||||
}
|
||||
|
||||
// Bad: short variable declarations.
|
||||
func F3() {
|
||||
l := 1 // want `do not use "l"`
|
||||
I := 2 // want `do not use "I"`
|
||||
_ = l
|
||||
_ = I
|
||||
}
|
||||
|
||||
// Bad: var statement inside a function.
|
||||
func F4() {
|
||||
var l int // want `do not use "l"`
|
||||
var I int // want `do not use "I"`
|
||||
_ = l
|
||||
_ = I
|
||||
}
|
||||
|
||||
// Bad: range key/value.
|
||||
func F5(xs []int) {
|
||||
for l, v := range xs { // want `do not use "l"`
|
||||
_ = l
|
||||
_ = v
|
||||
}
|
||||
for _, I := range xs { // want `do not use "I"`
|
||||
_ = I
|
||||
}
|
||||
}
|
||||
|
||||
// Bad: type parameters.
|
||||
func F6a[l any](x l) l { return x } // want `do not use "l"`
|
||||
func F6b[I any](x I) I { return x } // want `do not use "I"`
|
||||
|
||||
// Bad: type switch guards.
|
||||
func F7(x any) {
|
||||
switch l := x.(type) { // want `do not use "l"`
|
||||
case int:
|
||||
_ = l
|
||||
}
|
||||
switch I := x.(type) { // want `do not use "I"`
|
||||
case int:
|
||||
_ = I
|
||||
}
|
||||
}
|
||||
|
||||
// OK: clean code with no banned variables.
|
||||
func F8() {
|
||||
count := 0
|
||||
for i := 0; i < 10; i++ {
|
||||
count++
|
||||
}
|
||||
_ = count
|
||||
}
|
||||
|
||||
// OK: sync.Mutex named "mu".
|
||||
var mu sync.Mutex
|
||||
+2
-1
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"golang.org/x/tools/go/analysis/unitchecker"
|
||||
"tailscale.com/cmd/vet/jsontags"
|
||||
"tailscale.com/cmd/vet/lowerell"
|
||||
"tailscale.com/cmd/vet/subtestnames"
|
||||
)
|
||||
|
||||
@@ -21,5 +22,5 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
unitchecker.Main(jsontags.Analyzer, subtestnames.Analyzer)
|
||||
unitchecker.Main(jsontags.Analyzer, lowerell.Analyzer, subtestnames.Analyzer)
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ func TestLOCK(t *testing.T) {
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("http://%s/%s/%s/%s/%s",
|
||||
s.local.l.Addr(),
|
||||
s.local.ln.Addr(),
|
||||
url.PathEscape(domain),
|
||||
url.PathEscape(remote1),
|
||||
url.PathEscape(share11),
|
||||
@@ -365,7 +365,7 @@ func TestUNLOCK(t *testing.T) {
|
||||
}
|
||||
|
||||
u := fmt.Sprintf("http://%s/%s/%s/%s/%s",
|
||||
s.local.l.Addr(),
|
||||
s.local.ln.Addr(),
|
||||
url.PathEscape(domain),
|
||||
url.PathEscape(remote1),
|
||||
url.PathEscape(share11),
|
||||
@@ -428,12 +428,12 @@ func TestUNLOCK(t *testing.T) {
|
||||
}
|
||||
|
||||
type local struct {
|
||||
l net.Listener
|
||||
ln net.Listener
|
||||
fs *FileSystemForLocal
|
||||
}
|
||||
|
||||
type remote struct {
|
||||
l net.Listener
|
||||
ln net.Listener
|
||||
fs *FileSystemForRemote
|
||||
fileServer *FileServer
|
||||
shares map[string]string
|
||||
@@ -487,7 +487,7 @@ func newSystem(t *testing.T) *system {
|
||||
client.SetTransport(&http.Transport{DisableKeepAlives: true})
|
||||
s := &system{
|
||||
t: t,
|
||||
local: &local{l: ln, fs: fs},
|
||||
local: &local{ln: ln, fs: fs},
|
||||
client: client,
|
||||
remotes: make(map[string]*remote),
|
||||
}
|
||||
@@ -510,7 +510,7 @@ func (s *system) addRemote(name string) string {
|
||||
s.t.Logf("FileServer for %v listening at %s", name, fileServer.Addr())
|
||||
|
||||
r := &remote{
|
||||
l: ln,
|
||||
ln: ln,
|
||||
fileServer: fileServer,
|
||||
fs: NewFileSystemForRemote(log.Printf),
|
||||
shares: make(map[string]string),
|
||||
@@ -524,7 +524,7 @@ func (s *system) addRemote(name string) string {
|
||||
for name, r := range s.remotes {
|
||||
remotes = append(remotes, &drive.Remote{
|
||||
Name: name,
|
||||
URL: func() string { return fmt.Sprintf("http://%s", r.l.Addr()) },
|
||||
URL: func() string { return fmt.Sprintf("http://%s", r.ln.Addr()) },
|
||||
})
|
||||
}
|
||||
s.local.fs.SetRemotes(
|
||||
@@ -683,7 +683,7 @@ func (s *system) stop() {
|
||||
s.t.Fatalf("failed to Close fs: %s", err)
|
||||
}
|
||||
|
||||
err = s.local.l.Close()
|
||||
err = s.local.ln.Close()
|
||||
if err != nil {
|
||||
s.t.Fatalf("failed to Close listener: %s", err)
|
||||
}
|
||||
@@ -694,7 +694,7 @@ func (s *system) stop() {
|
||||
s.t.Fatalf("failed to Close remote fs: %s", err)
|
||||
}
|
||||
|
||||
err = r.l.Close()
|
||||
err = r.ln.Close()
|
||||
if err != nil {
|
||||
s.t.Fatalf("failed to Close remote listener: %s", err)
|
||||
}
|
||||
|
||||
@@ -86,10 +86,10 @@ metricname{foo="si",bar="si"} 5
|
||||
|
||||
func TestMultiLabelMapTypes(t *testing.T) {
|
||||
type LabelTypes struct {
|
||||
S string
|
||||
B bool
|
||||
I int
|
||||
U uint
|
||||
S string
|
||||
B bool
|
||||
Int int
|
||||
U uint
|
||||
}
|
||||
|
||||
m := new(MultiLabelMap[LabelTypes])
|
||||
@@ -100,7 +100,7 @@ func TestMultiLabelMapTypes(t *testing.T) {
|
||||
m.WritePrometheus(&buf, "metricname")
|
||||
const want = `# TYPE metricname counter
|
||||
# HELP metricname some good stuff
|
||||
metricname{s="a",b="true",i="-1",u="2"} 3
|
||||
metricname{s="a",b="true",int="-1",u="2"} 3
|
||||
`
|
||||
if got := buf.String(); got != want {
|
||||
t.Errorf("got %q; want %q", got, want)
|
||||
|
||||
@@ -41,6 +41,9 @@ func TestPackageDocs(t *testing.T) {
|
||||
if fi.Mode().IsDir() && path != "." && strings.HasPrefix(filepath.Base(path), ".") {
|
||||
return filepath.SkipDir // No documentation lives in dot directories (.git, .claude, etc)
|
||||
}
|
||||
if fi.Mode().IsDir() && filepath.Base(path) == "testdata" {
|
||||
return filepath.SkipDir // testdata is ignored by the go tool; not real packages
|
||||
}
|
||||
if fi.Mode().IsRegular() && strings.HasSuffix(path, ".go") {
|
||||
if strings.HasSuffix(path, "_test.go") {
|
||||
return nil
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestHash(t *testing.T) {
|
||||
I16 int16
|
||||
I32 int32
|
||||
I64 int64
|
||||
I int
|
||||
Int int
|
||||
U8 uint8
|
||||
U16 uint16
|
||||
U32 uint32
|
||||
@@ -92,7 +92,7 @@ func TestHash(t *testing.T) {
|
||||
{in: tuple{scalars{I16: math.MinInt16}, scalars{I16: math.MinInt16 / 2}}, wantEq: false},
|
||||
{in: tuple{scalars{I32: math.MinInt32}, scalars{I32: math.MinInt32 / 2}}, wantEq: false},
|
||||
{in: tuple{scalars{I64: math.MinInt64}, scalars{I64: math.MinInt64 / 2}}, wantEq: false},
|
||||
{in: tuple{scalars{I: -1234}, scalars{I: -1234 / 2}}, wantEq: false},
|
||||
{in: tuple{scalars{Int: -1234}, scalars{Int: -1234 / 2}}, wantEq: false},
|
||||
{in: tuple{scalars{U8: math.MaxUint8}, scalars{U8: math.MaxUint8 / 2}}, wantEq: false},
|
||||
{in: tuple{scalars{U16: math.MaxUint16}, scalars{U16: math.MaxUint16 / 2}}, wantEq: false},
|
||||
{in: tuple{scalars{U32: math.MaxUint32}, scalars{U32: math.MaxUint32 / 2}}, wantEq: false},
|
||||
|
||||
Reference in New Issue
Block a user