blob: ef29e68a371bb19004a4991604110da30adee19e [file] [log] [blame] [edit]
// Copyright 2020-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package bufstyle defines lint analyzers that help enforce Buf's Go code standards.
package bufstyle
import (
"go/token"
"path/filepath"
"strings"
"golang.org/x/tools/go/analysis"
)
// we don't store this as a global because we modify these in the analyzerProvider.
func newAnalyzers() []*analysis.Analyzer {
return []*analysis.Analyzer{
{
Name: "PACKAGE_FILENAME",
Doc: "Verifies that every package has a file with the same name as the package.",
Run: func(pass *analysis.Pass) (interface{}, error) {
if len(pass.Files) == 0 {
// Nothing to do. We can't report the error anywhere because
// this package doesn't have any files.
return nil, nil
}
packageName := pass.Pkg.Name()
if strings.HasSuffix(packageName, "_test") {
// Ignore test packages.
return nil, nil
}
var found bool
pass.Fset.Iterate(
func(file *token.File) bool {
filename := filepath.Base(file.Name())
if strings.TrimSuffix(filename, ".go") == packageName {
found = true
return false
}
return true
},
)
if !found {
// The package is guaranteed to have at least one
// file with a package declaration, so we report the failure there.
// We checked that len(pass.Files) > 0 above.
pass.Reportf(pass.Files[0].Package, "Package %q does not have a %s.go", packageName, packageName)
}
return nil, nil
},
},
{
Name: "NO_SYNC_POOL",
Doc: "Verifies that sync.Pool is not used.",
Run: func(pass *analysis.Pass) (interface{}, error) {
if typesInfo := pass.TypesInfo; typesInfo != nil {
for expr, typeAndValue := range pass.TypesInfo.Types {
if t := typeAndValue.Type; t != nil {
if t.String() == "sync.Pool" {
pass.Reportf(expr.Pos(), "sync.Pool cannot be used")
}
}
}
}
return nil, nil
},
},
{
Name: "BEHAVIOUR",
Doc: "Verifies that the word \"behaviour\" is not used in any comment.",
Run: func(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
for _, commentGroup := range file.Comments {
for _, comment := range commentGroup.List {
if strings.Contains(strings.ToLower(comment.Text), "behaviour") {
pass.Reportf(comment.Slash, `It is spelled "behavior" not "behaviour"`)
}
}
}
}
return nil, nil
},
},
{
Name: "FILEPATH_CASING",
Doc: "Verifies filePath or FilePath is used, not filepath or Filepath.",
Run: func(pass *analysis.Pass) (interface{}, error) {
if typesInfo := pass.TypesInfo; typesInfo != nil {
for _, object := range pass.TypesInfo.Defs {
if object != nil {
if strings.Contains(object.Name(), "Filepath") {
pass.Reportf(object.Pos(), `Use "FilePath" instead of "Filepath" in name %q`, object.Name())
}
if strings.Contains(object.Name(), "filepath") {
pass.Reportf(object.Pos(), `Use "filePath" instead of "filepath" in name %q`, object.Name())
}
}
}
}
return nil, nil
},
},
//{
//Name: "FILENAME_CASING",
//Doc: "Verifies fileName or FileName is used, not filename or Filename.",
//Run: func(pass *analysis.Pass) (interface{}, error) {
//if typesInfo := pass.TypesInfo; typesInfo != nil {
//for _, object := range pass.TypesInfo.Defs {
//if object != nil {
//if strings.Contains(object.Name(), "Filename") {
//pass.Reportf(object.Pos(), `Use "FileName" instead of "Filename" in name %q`, object.Name())
//}
//if strings.Contains(object.Name(), "filename") {
//pass.Reportf(object.Pos(), `Use "fileName" instead of "filename" in name %q`, object.Name())
//}
//}
//}
//}
//return nil, nil
//},
//},
//{
//Name: "DIRPATH_CASING",
//Doc: "Verifies dirPath or DirPath is used, not dirpath or Dirpath.",
//Run: func(pass *analysis.Pass) (interface{}, error) {
//if typesInfo := pass.TypesInfo; typesInfo != nil {
//for _, object := range pass.TypesInfo.Defs {
//if object != nil {
//if strings.Contains(object.Name(), "Dirpath") {
//pass.Reportf(object.Pos(), `Use "DirPath" instead of "Dirpath" in name %q`, object.Name())
//}
//if strings.Contains(object.Name(), "dirpath") {
//pass.Reportf(object.Pos(), `Use "dirPath" instead of "dirpath" in name %q`, object.Name())
//}
//}
//}
//}
//return nil, nil
//},
//},
}
}