diff --git a/DEPS.bzl b/DEPS.bzl index db09c1f163cbf..528f216b3a59d 100644 --- a/DEPS.bzl +++ b/DEPS.bzl @@ -934,6 +934,13 @@ def go_deps(): sum = "h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks=", version = "v0.0.0-20190930125516-244bba706f1a", ) + go_repository( + name = "com_github_golangci_misspell", + build_file_proto_mode = "disable", + importpath = "github.com/golangci/misspell", + sum = "h1:pLzmVdl3VxTOncgzHcvLOKirdvcx/TydsClUQXTehjo=", + version = "v0.3.5", + ) go_repository( name = "com_github_golangci_prealloc", diff --git a/build/BUILD.bazel b/build/BUILD.bazel index 3b3ceede0cc22..ea8958f219b75 100644 --- a/build/BUILD.bazel +++ b/build/BUILD.bazel @@ -125,6 +125,7 @@ nogo( "//build/linter/exportloopref:exportloopref", "//build/linter/gofmt:gofmt", "//build/linter/ineffassign:ineffassign", + "//build/linter/misspell:misspell", "//build/linter/prealloc:prealloc", "//build/linter/predeclared:predeclared", "//build/linter/unconvert:unconvert", diff --git a/build/linter/asciicheck/BUILD.bazel b/build/linter/asciicheck/BUILD.bazel index 8b1346d863744..f686bc348ca2c 100644 --- a/build/linter/asciicheck/BUILD.bazel +++ b/build/linter/asciicheck/BUILD.bazel @@ -5,5 +5,5 @@ go_library( srcs = ["analysis.go"], importpath = "github.com/pingcap/tidb/build/linter/asciicheck", visibility = ["//visibility:public"], - deps = ["@com_github_tdakkota_asciicheck//:go_default_library"], + deps = ["@com_github_tdakkota_asciicheck//:asciicheck"], ) diff --git a/build/linter/misspell/BUILD.bazel b/build/linter/misspell/BUILD.bazel new file mode 100644 index 0000000000000..8cdc74330a98a --- /dev/null +++ b/build/linter/misspell/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "misspell", + srcs = ["analyzer.go"], + importpath = "github.com/pingcap/tidb/build/linter/misspell", + visibility = ["//visibility:public"], + deps = [ + "//build/linter/util", + "@com_github_golangci_misspell//:go_default_library", + "@org_golang_x_tools//go/analysis", + ], +) diff --git a/build/linter/misspell/analyzer.go b/build/linter/misspell/analyzer.go new file mode 100644 index 0000000000000..3bf4dd16ae77c --- /dev/null +++ b/build/linter/misspell/analyzer.go @@ -0,0 +1,92 @@ +// Copyright 2022 PingCAP, 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 misspell + +import ( + "fmt" + "go/token" + + "github.com/golangci/misspell" + "github.com/pingcap/tidb/build/linter/util" + "golang.org/x/tools/go/analysis" +) + +// Name is the name of the analyzer. +const Name = "misspell" + +// Analyzer is the analyzer struct of misspell. +var Analyzer = &analysis.Analyzer{ + Name: Name, + Doc: "Checks the spelling error in code", + Run: run, +} + +func init() { + util.SkipAnalyzer(Analyzer) +} + +// Misspell is the config of misspell. +type Misspell struct { + Locale string + IgnoreWords []string `mapstructure:"ignore-words"` +} + +func run(pass *analysis.Pass) (interface{}, error) { + r := misspell.Replacer{ + Replacements: misspell.DictMain, + } + + // Figure out regional variations + settings := &Misspell{ + Locale: "", + } + + if len(settings.IgnoreWords) != 0 { + r.RemoveRule(settings.IgnoreWords) + } + + r.Compile() + files := make([]string, 0, len(pass.Files)) + for _, file := range pass.Files { + pos := pass.Fset.PositionFor(file.Pos(), false) + files = append(files, pos.Filename) + } + for _, f := range files { + err := runOnFile(f, &r, pass) + if err != nil { + return nil, err + } + } + + return nil, nil +} + +func runOnFile(fileName string, r *misspell.Replacer, pass *analysis.Pass) error { + fileContent, tf, err := util.ReadFile(pass.Fset, fileName) + if err != nil { + return fmt.Errorf("can't get file %s contents: %s", fileName, err) + } + + // use r.Replace, not r.ReplaceGo because r.ReplaceGo doesn't find + // issues inside strings: it searches only inside comments. r.Replace + // searches all words: it treats input as a plain text. A standalone misspell + // tool uses r.Replace by default. + _, diffs := r.Replace(string(fileContent)) + for _, diff := range diffs { + text := fmt.Sprintf("[%s] `%s` is a misspelling of `%s`", Name, diff.Original, diff.Corrected) + pass.Reportf(token.Pos(tf.Base()+util.FindOffset(string(fileContent), diff.Line, diff.Column)), text) + } + return nil +} diff --git a/build/linter/util/util.go b/build/linter/util/util.go index 5b58883576d60..0aaeb2bbb7419 100644 --- a/build/linter/util/util.go +++ b/build/linter/util/util.go @@ -18,6 +18,7 @@ import ( "fmt" "go/ast" "go/token" + "io/ioutil" "reflect" "strings" @@ -167,3 +168,39 @@ func MakeFakeLoaderPackageInfo(pass *analysis.Pass) *loader.PackageInfo { Info: *typeInfo, } } + +// ReadFile reads a file and adds it to the FileSet +// so that we can report errors against it using lineStart. +func ReadFile(fset *token.FileSet, filename string) ([]byte, *token.File, error) { + content, err := ioutil.ReadFile(filename) + if err != nil { + return nil, nil, err + } + tf := fset.AddFile(filename, -1, len(content)) + tf.SetLinesForContent(content) + return content, tf, nil +} + +// FindOffset returns the offset of a given position in a file. +func FindOffset(fileText string, line, column int) int { + // we count our current line and column position + currentCol := 1 + currentLine := 1 + + for offset, ch := range fileText { + // see if we found where we wanted to go to + if currentLine == line && currentCol == column { + return offset + } + + // line break - increment the line counter and reset the column + if ch == '\n' { + currentLine++ + currentCol = 1 + } else { + currentCol++ + } + + } + return -1 //not found +} diff --git a/build/nogo_config.json b/build/nogo_config.json index 3b9938e39c8d1..b5edb72788f4f 100644 --- a/build/nogo_config.json +++ b/build/nogo_config.json @@ -178,6 +178,13 @@ ".*_generated\\.go$": "ignore generated code" } }, + "misspell": { + "exclude_files": { + "/cgo/": "ignore cgo code", + "/external/": "no need to vet third party code", + ".*_generated\\.go$": "ignore generated code" + } + }, "nilfunc": { "exclude_files": { "/external/": "no need to vet third party code", diff --git a/go.mod b/go.mod index 17d8a6f3938d4..64211d111e21b 100644 --- a/go.mod +++ b/go.mod @@ -99,6 +99,7 @@ require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.1581 github.com/charithe/durationcheck v0.0.9 github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a + github.com/golangci/misspell v0.3.5 github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 github.com/kisielk/errcheck v1.6.1 diff --git a/go.sum b/go.sum index a002b3c67bdc7..efdf0b627e904 100644 --- a/go.sum +++ b/go.sum @@ -345,6 +345,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/misspell v0.3.5 h1:pLzmVdl3VxTOncgzHcvLOKirdvcx/TydsClUQXTehjo= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= diff --git a/parser/parser_test.go b/parser/parser_test.go index f97ca11feada5..ee5c9fc7e384c 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -2177,7 +2177,7 @@ func TestIdentifier(t *testing.T) { {`select COUNT from DESC`, false, ""}, {`select COUNT from SELECT.DESC`, true, "SELECT `COUNT` FROM `SELECT`.`DESC`"}, {"use `select`", true, "USE `select`"}, - {"use `sel``ect`", true, "USE `sel``ect`"}, + {"use `sel``ect`", true, "USE `sel``ect`"}, //nolint: misspell {"use select", false, "USE `select`"}, {`select * from t as a`, true, "SELECT * FROM `t` AS `a`"}, {"select 1 full, 1 row, 1 abs", false, ""}, diff --git a/tools/check/ut.go b/tools/check/ut.go index bf1d87aafc7a0..45b969360d245 100644 --- a/tools/check/ut.go +++ b/tools/check/ut.go @@ -125,7 +125,7 @@ func cmdList(args ...string) bool { } exist, err := testBinaryExist(pkg) if err != nil { - fmt.Println("check test binary existance error", err) + fmt.Println("check test binary existence error", err) return false } if !exist { @@ -203,7 +203,7 @@ func cmdRun(args ...string) bool { for _, pkg := range pkgs { exist, err := testBinaryExist(pkg) if err != nil { - fmt.Println("check test binary existance error", err) + fmt.Println("check test binary existence error", err) return false } if !exist { @@ -229,7 +229,7 @@ func cmdRun(args ...string) bool { } exist, err := testBinaryExist(pkg) if err != nil { - fmt.Println("check test binary existance error", err) + fmt.Println("check test binary existence error", err) return false } @@ -254,7 +254,7 @@ func cmdRun(args ...string) bool { } exist, err := testBinaryExist(pkg) if err != nil { - fmt.Println("check test binary existance error", err) + fmt.Println("check test binary existence error", err) return false } if !exist {