Skip to content

Commit

Permalink
Adding identypo
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkohler committed Sep 3, 2018
0 parents commit f1516df
Show file tree
Hide file tree
Showing 7 changed files with 1,297 additions and 0 deletions.
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# identypo

identypo is a Go static analysis tool to find typos in identifiers (functions, function calls, variables, constants, type declarations, packages, labels). It is built on top of [client9's misspell package](https://github.com/client9/misspell).

## Installation

go get -u github.com/alexkohler/identypo/cmd/identypo

## Usage

Similar to other Go static analysis tools (such as golint, go vet), identypo can be invoked with one or more filenames, directories, or packages named by its import path. Identypo also supports the `...` wildcard. By default, it will search for typos in every identifier (functions, function calls, variables, constants, type declarations, packages, labels).

identypo [flags] files/directories/packages

### Flags
- **-tests** (default true) - Include test files in analysis
- **-i** - Comma separated list of corrections to be ignored (for example, to stop corrections on "nto" and "creater", pass `-i="nto,creater"). This is a direct passthrough to the misspell package.
- **-functions** - Find typos in function declarations only.
- **-constants** - Find typos in constants only.
- **-variables** - Find typos in variables only.
- **-set_exit_status** (default false) - Set exit status to 1 if any issues are found.

NOTE: by default, identypo will check for typos in every identifier (functions, function calls, variables, constants, type declarations, packages, labels). In this case, no flag needs specified. Due to a lack of frequency, there are currently no flags to find only type declarations, packages, or labels.

## Example uses in popular Go repos

Some selected examples from [Kubernetes](https://github.com/kubernetes/kubernetes):
```Bash
$ identypo ./...
cmd/kubeadm/app/cmd/phases/kubeconfig_test.go:325 "Authorithy" should be Authority in SetupPkiDirWithCertificateAuthorithy
cmd/kubeadm/app/util/apiclient/wait.go:51 "inital" should be initial in initalTimeout
pkg/apis/certificates/types.go:125 "Committment" should be Commitment in UsageContentCommittment
controller/nodeipam/ipam/cidrset/cidr_set.go:158 "Begining" should be Beginning in getBeginingAndEndIndices
staging/src/k8s.io/apimachinery/pkg/conversion/converter_test.go:358 "Overriden" should be Overridden in TestConverter_WithConversionOverriden
```

```Go
// cmd/kubeadm/app/cmd/phases/kubeconfig_test.go:325 "Authorithy" should be Authority in SetupPkiDirWithCertificateAuthorithy
pkidir := testutil.SetupPkiDirWithCertificateAuthorithy(t, tmpdir)

// cmd/kubeadm/app/util/apiclient/wait.go:51 "inital" should be initial in initalTimeout
WaitForHealthyKubelet(initalTimeout time.Duration, healthzEndpoint string) error

// pkg/apis/certificates/types.go:125 "Committment" should be Commitment in UsageContentCommittment
UsageContentCommittment KeyUsage = "content commitment"

// controller/nodeipam/ipam/cidrset/cidr_set.go:158 "Begining" should be Beginning in getBeginingAndEndIndices
func (s *CidrSet) getBeginingAndEndIndices(cidr *net.IPNet) (begin, end int, err error) {

// staging/src/k8s.io/apimachinery/pkg/conversion/converter_test.go:358 "Overriden" should be Overridden in TestConverter_WithConversionOverriden
func TestConverter_WithConversionOverriden(t *testing.T) {
```
Some examples from the [Go standard library](https://github.com/golang/go) (utilizing the `-i` flag to suppress some non-isses):
```Bash
$ identypo -i="rela,nto,onot,alltime" ./...
cmd/trace/goroutines.go:169 "dividened" should be dividend in dividened
cmd/trace/goroutines.go:173 "dividened" should be dividend in dividened
cmd/trace/goroutines.go:175 "dividened" should be dividend in dividened
cmd/trace/goroutines.go:179 "dividened" should be dividend in dividened
cmd/trace/annotations.go:1162 "dividened" should be dividend in dividened
cmd/trace/annotations.go:1166 "dividened" should be dividend in dividened
cmd/trace/annotations.go:1168 "dividened" should be dividend in dividened
cmd/trace/annotations.go:1172 "dividened" should be dividend in dividened
crypto/x509/verify.go:208 "Comparisions" should be Comparisons in MaxConstraintComparisions
crypto/x509/verify.go:585 "Comparisions" should be Comparisons in MaxConstraintComparisions
```
```Go
// cmd/trace/annotations.go:1162-1172 dividened" should be dividend in dividened
"percent": func(dividened, divisor int64) template.HTML {
if divisor == 0 {
return ""
}
return template.HTML(fmt.Sprintf("(%.1f%%)", float6(dividened)/float64(divisor)*100))
},
"barLen": func(dividened, divisor int64) template.HTML {
if divisor == 0 {
return "0"
}
return template.HTML(fmt.Sprintf("%.2f%%", float6(dividened)/float64(divisor)*100))
},

// crypto/x509/verify.go:208 "Comparisions" should be Comparisons in MaxConstraintComparisions
type VerifyOptions struct {
...
Roots *CertPool // if nil, the system roots are used
CurrentTime time.Time // if zero, the current time is used
...
MaxConstraintComparisions int
}
```
## Packages used
- https://github.com/client9/misspell
- https://github.com/fatih/camelcase
## Contributing
Please open an issue and/or a PR for any features/bugs.
## Other static analysis tools
If you've enjoyed identypo, take a look at my other static anaylsis tools!
- [prealloc](https://github.com/alexkohler/prealloc) - Finds slice declarations that could potentially be preallocated.
- [nakedret](https://github.com/alexkohler/nakedret) - Finds naked returns.
- [unimport](https://github.com/alexkohler/unimport) - Finds unnecessary import aliases.
51 changes: 51 additions & 0 deletions cmd/identypo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"flag"
"go/build"
"log"
"os"

"github.com/alexkohler/identypo"
)

func init() {
build.Default.UseAllFiles = false
}

func usage() {
log.Printf("Usage of %s:\n", os.Args[0])
log.Printf("\nidentypo[flags] # runs on package in current directory\n")
log.Printf("\nidentypo [flags] [packages]\n")
log.Printf("Flags:\n")
flag.PrintDefaults()
log.Printf("\nNOTE: by default, identypo will check for typos in every identifier (functions, function calls, variables, constants, type declarations, packages, labels). In this case, no flag needs specified.\n")
}

func main() {

// Remove log timestamp
log.SetFlags(0)

ignores := flag.String("i", "", "ignore the following words requiring correction, comma separated (e.g. -i=\"nto,creater\")")
includeTests := flag.Bool("tests", true, "include test (*_test.go) files")
functionsOnly := flag.Bool("functions", false, "find typos in function declarations only")
constantsOnly := flag.Bool("constants", false, "find typos in constants only")
variablesOnly := flag.Bool("variables", false, "find typos in variables only")
setExitStatus := flag.Bool("set_exit_status", false, "Set exit status to 1 if any issues are found")
flag.Usage = usage
flag.Parse()

flags := identypo.Flags{
Ignores: *ignores,
IncludeTests: *includeTests,
FunctionsOnly: *functionsOnly,
ConstantsOnly: *constantsOnly,
VariablesOnly: *variablesOnly,
SetExitStatus: *setExitStatus,
}

if err := identypo.CheckForIdentiferTypos(flag.Args(), flags); err != nil {
log.Println(err)
}
}
127 changes: 127 additions & 0 deletions identifier_typo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package identypo

import (
"fmt"
"go/ast"
"go/token"
"log"
"os"
"strings"

"github.com/client9/misspell"
"github.com/fatih/camelcase"
)

// Flags contains configuration specific to identypo.
// * Ignores - comma separated list of corrections to be ignored (for example, to stop corrections on "nto" and "creater", pass `-i="nto,creater"). This is a direct passthrough to the misspell package.
// * IncludeTests - include test files in analysis
// * FunctionsOnly - Find typos in function declarations only.
// * ConstantsOnly - Find typos in constants only.
// * VariablesOnly - Find typos in variables only.
// * SetExitStatus - Set exit status to 1 if any issues are found.
// Note: If FunctionsOnly, ConstantsOnly, and VariablesOnly are all false, every identifier will be searched for typos.
// (functions, function calls, variables, constants, type declarations, packages, labels).
type Flags struct {
Ignores string
IncludeTests bool
FunctionsOnly, ConstantsOnly, VariablesOnly bool
SetExitStatus bool
}

// CheckForIdentiferTypos takes a slice of file arguments (this could be file names, directories, or packages (with or without the ... wildcard).
// Further configuration (such as words to ignore, whether or not to include tests, etc.) can be specified with the flags argument. Output is written
// using the log.Printf function. This is currently not configurable. For redirection to a file/buffer, see the log.SetOutput() method.
func CheckForIdentiferTypos(args []string, flags Flags) error {

fset := token.NewFileSet()

files, err := parseInput(args, fset, flags.IncludeTests)
if err != nil {
return fmt.Errorf("could not parse input %v", err)
}

return processIdentifiers(fset, files, flags)
}

func processIdentifiers(fset *token.FileSet, files []*ast.File, flags Flags) error {
all := !flags.FunctionsOnly && !flags.ConstantsOnly && !flags.VariablesOnly

retVis := &returnsVisitor{
f: fset,
replacer: misspell.New(),
}

if len(flags.Ignores) > 0 {
lci := strings.ToLower(flags.Ignores)
retVis.replacer.RemoveRule(strings.Split(lci, ","))
}

retVis.replacer.Compile()

for _, f := range files {
if f == nil {
continue
}
ast.Walk(retVis, f)
}

exitStatus := 0

for _, ident := range retVis.identifiers {
for _, word := range camelcase.Split(ident.Name) {
v, d := retVis.replacer.Replace(word)
if len(d) > 0 {
exitStatus = 1
file := retVis.f.File(ident.Pos())
fileName := file.Name()
line := file.Position(ident.Pos()).Line

if all {
// if we're including everything, no need to look at the kind of identifier we have
log.Printf("%v:%v %q should be %v in %v\n", fileName, line, word, v, ident.Name)
} else if ident.Obj != nil {
switch ident.Obj.Kind {
case ast.Fun:
if !flags.FunctionsOnly {
continue
}
case ast.Var:
if !flags.VariablesOnly {
continue
}
case ast.Con:
if !flags.ConstantsOnly {
continue
}
default:
// labels, packages, etc. currently do not have individual flags and will be skipped
continue
}
log.Printf("%v:%v %q should be %v in %v\n", fileName, line, word, v, ident.Name)
}
}
}
}

if flags.SetExitStatus {
os.Exit(exitStatus)
}
return nil
}

type returnsVisitor struct {
f *token.FileSet
identifiers []*ast.Ident
replacer *misspell.Replacer
}

func (v *returnsVisitor) Visit(node ast.Node) ast.Visitor {
funcDecl, ok := node.(*ast.Ident)
if !ok {
return v
}

v.identifiers = append(v.identifiers, funcDecl)

return v
}
Loading

0 comments on commit f1516df

Please sign in to comment.