storj/scripts/check-imports.go
2018-11-30 17:02:01 +02:00

185 lines
3.3 KiB
Go

// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
// +build ignore
package main
import (
"flag"
"fmt"
"go/ast"
"go/token"
"os"
"runtime"
"sort"
"strconv"
"strings"
"golang.org/x/tools/go/packages"
)
/*
This tool verifies whether imports are divided into three blocks:
std packages
external packages
storj.io packages
*/
func main() {
flag.Parse()
pkgNames := flag.Args()
if len(pkgNames) == 0 {
pkgNames = []string{"."}
}
roots, err := packages.Load(&packages.Config{
Mode: packages.LoadAllSyntax,
Env: os.Environ(),
}, pkgNames...)
if err != nil {
panic(err)
}
seen := map[*packages.Package]bool{}
pkgs := []*packages.Package{}
var visit func(*packages.Package)
visit = func(p *packages.Package) {
if seen[p] {
return
}
includeStd(p)
if strings.HasPrefix(p.ID, "storj.io") {
pkgs = append(pkgs, p)
}
seen[p] = true
for _, pkg := range p.Imports {
visit(pkg)
}
}
for _, pkg := range roots {
visit(pkg)
}
sort.Slice(pkgs, func(i, k int) bool { return pkgs[i].ID < pkgs[k].ID })
for _, pkg := range pkgs {
process(pkg)
}
}
func process(pkg *packages.Package) {
for i, file := range pkg.Syntax {
checkImports(pkg.Fset, pkg.CompiledGoFiles[i], file)
}
}
func checkImports(fset *token.FileSet, name string, f *ast.File) {
for _, d := range f.Decls {
d, ok := d.(*ast.GenDecl)
if !ok || d.Tok != token.IMPORT {
// Not an import declaration, so we're done.
// Imports are always first.
break
}
if !d.Lparen.IsValid() {
// Not a block: sorted by default.
continue
}
// Identify and sort runs of specs on successive lines.
lastGroup := 0
specgroups := [][]ast.Spec{}
for i, s := range d.Specs {
if i > lastGroup && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[i-1].End()).Line {
// i begins a new run. End this one.
specgroups = append(specgroups, d.Specs[lastGroup:i])
lastGroup = i
}
}
specgroups = append(specgroups, d.Specs[lastGroup:])
if !correctOrder(specgroups) {
fmt.Println(name)
}
}
}
func correctOrder(specgroups [][]ast.Spec) bool {
if len(specgroups) == 0 {
return true
}
// remove std group from beginning
std, other, storj := countGroup(specgroups[0])
if std > 0 {
if other+storj != 0 {
return false
}
specgroups = specgroups[1:]
}
if len(specgroups) == 0 {
return true
}
// remove storj.io group from the end
std, other, storj = countGroup(specgroups[len(specgroups)-1])
if storj > 0 {
if std+other > 0 {
return false
}
specgroups = specgroups[:len(specgroups)-1]
}
if len(specgroups) == 0 {
return true
}
// check that we have a center group for misc stuff
if len(specgroups) != 1 {
return false
}
std, other, storj = countGroup(specgroups[0])
return other > 0 && std+storj == 0
}
func countGroup(p []ast.Spec) (std, other, storj int) {
for _, imp := range p {
imp := imp.(*ast.ImportSpec)
path, err := strconv.Unquote(imp.Path.Value)
if err != nil {
panic(err)
}
if strings.HasPrefix(path, "storj.io/") {
storj++
} else if stdlib[path] {
std++
} else {
other++
}
}
return std, other, storj
}
var root = runtime.GOROOT()
var stdlib = map[string]bool{}
func includeStd(p *packages.Package) {
if len(p.GoFiles) == 0 {
stdlib[p.ID] = true
return
}
if strings.HasPrefix(p.GoFiles[0], root) {
stdlib[p.ID] = true
return
}
}