Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

58
app/tool/bgr/BUILD Normal file
View File

@@ -0,0 +1,58 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
"go_binary",
)
go_test(
name = "go_default_test",
srcs = ["bgl_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"ast_inspect.go",
"cache.go",
"file_filter.go",
"lint_register.go",
"main.go",
"model.go",
"script_parser.go",
],
importpath = "go-common/app/tool/bgr",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/bgr/log:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/tool/bgr/log:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_binary(
name = "bgr",
embed = [":go_default_library"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,5 @@
# v1.0.1
1. 修复depend package寻址失败的问题
# v1.0.0
1. golang syntax 检查规则解释器

View File

@@ -0,0 +1,8 @@
# Owner
muyang
# Author
muyang
# Reviewer
muyang

10
app/tool/bgr/OWNERS Normal file
View File

@@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- muyang
labels:
- tool
options:
no_parent_owners: true
reviewers:
- muyang

14
app/tool/bgr/README.md Normal file
View File

@@ -0,0 +1,14 @@
# bgr (bili golang rule)
## 项目简介
golang syntax 检查规则解释器
## 编译环境
golang >= 1.8
## 依赖包
github.com/pkg/errors
go stdlib
## 编译执行
go build

16
app/tool/bgr/RULE.bgl Normal file
View File

@@ -0,0 +1,16 @@
// 请填写禁止规则
T expr.call.decl.comment
V (?im)^\s*deprecated
L e
D 禁止调用Deprecated方法
T stmt.label
V .*
L w
D 请谨慎使用Label
//T decl.func.comment
//V Deprecated
//L w
//D 禁止声明Deprecated方法

View File

@@ -0,0 +1,91 @@
package main
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"path/filepath"
"github.com/pkg/errors"
)
var (
_defaultFileFilter = FilterOnlyCodeFile
_errors = make([]string, 0)
_warns = make([]string, 0)
)
// AstInspect .
func AstInspect(dir string) (err error) {
var (
files []*ast.File
fset *token.FileSet
)
if dir, err = filepath.Abs(dir); err != nil {
err = errors.WithStack(err)
return
}
if files, fset, err = parsePackageFiles(dir); err != nil {
return
}
for _, f := range files {
handler := defaultNodeHandler(f, dir, fset)
ast.Inspect(f, handler)
}
return
}
func defaultNodeHandler(f *ast.File, dir string, fileset *token.FileSet) func(ast.Node) bool {
return func(node ast.Node) bool {
if node == nil {
return false
}
for _, l := range _lints {
if !l.fn(dir, f, node) {
switch l.s.l {
case "e":
_errors = append(_errors, fmt.Sprintf("%s --> %s", fileset.PositionFor(node.Pos(), true), l.s.d))
case "w":
_warns = append(_warns, fmt.Sprintf("%s --> %s", fileset.PositionFor(node.Pos(), true), l.s.d))
}
}
}
return true
}
}
func parsePackageFilesByPath(dir string, importPath string) (files []*ast.File, fset *token.FileSet, err error) {
var pkg *build.Package
if pkg, err = build.Import(importPath, dir, build.FindOnly); err != nil {
return
}
return parsePackageFiles(pkg.Dir)
}
func parsePackageFiles(absDir string) (files []*ast.File, fset *token.FileSet, err error) {
var ok bool
if files, fset, ok = packageCache(absDir); ok {
return
}
defer func() {
setPackageCache(absDir, fset, files)
}()
fset = token.NewFileSet()
var (
pkgMap map[string]*ast.Package
)
if pkgMap, err = parser.ParseDir(fset, absDir, _defaultFileFilter, parser.ParseComments); err != nil {
err = errors.WithStack(err)
return
}
for _, v := range pkgMap {
for _, f := range v.Files {
files = append(files, f)
}
}
return
}

9
app/tool/bgr/bgl_test.go Normal file
View File

@@ -0,0 +1,9 @@
package main
import (
"testing"
)
func TestComment(t *testing.T) {
}

28
app/tool/bgr/cache.go Normal file
View File

@@ -0,0 +1,28 @@
package main
import (
"go/ast"
"go/token"
)
var (
_packageCache = make(map[string]*pkgC)
)
type pkgC struct {
files []*ast.File
fset *token.FileSet
}
func packageCache(dir string) (files []*ast.File, fset *token.FileSet, ok bool) {
c, ok := _packageCache[dir]
if ok {
files = c.files
fset = c.fset
}
return
}
func setPackageCache(dir string, fset *token.FileSet, files []*ast.File) {
_packageCache[dir] = &pkgC{fset: fset, files: files}
}

View File

@@ -0,0 +1,22 @@
package main
import (
"os"
"strings"
)
// FilterOnlyCodeFile .
var FilterOnlyCodeFile = func(f os.FileInfo) bool {
if strings.HasSuffix(f.Name(), "_test.go") {
return false
}
if strings.HasSuffix(f.Name(), ".go") {
return true
}
return false
}
// FilterOnlyTestFile .
var FilterOnlyTestFile = func(f os.FileInfo) bool {
return strings.HasSuffix(f.Name(), "_test.go")
}

View File

@@ -0,0 +1,132 @@
package main
import (
"fmt"
"go/ast"
"strings"
)
var (
_parsers = make(map[string]func(curDir string, f *ast.File, n ast.Node) (v string, hit bool))
)
func init() {
// 函数定义注释
_parsers["decl.func.comment"] = func(curDir string, f *ast.File, n ast.Node) (v string, hit bool) {
var ele *ast.FuncDecl
if ele, hit = n.(*ast.FuncDecl); !hit {
return
}
v = ele.Doc.Text()
return
}
// 标签语句
_parsers["stmt.label"] = func(curDir string, f *ast.File, n ast.Node) (v string, hit bool) {
var ele *ast.LabeledStmt
if ele, hit = n.(*ast.LabeledStmt); !hit {
return
}
v = ele.Label.Name
return
}
// 调用函数的定义注释
_parsers["expr.call.decl.comment"] = func(curDir string, f *ast.File, n ast.Node) (v string, hit bool) {
var ele *ast.CallExpr
if ele, hit = n.(*ast.CallExpr); !hit {
return
}
var (
files []*ast.File
declName string
err error
doc *ast.CommentGroup
)
switch ele2 := ele.Fun.(type) {
case *ast.SelectorExpr: // like a.b()
for _, impt := range f.Imports {
if fmt.Sprintf("%s", ele2.X) == importName(impt) {
var imptPath = strings.Trim(impt.Path.Value, `"`)
if files, _, err = parsePackageFilesByPath(curDir, imptPath); err != nil {
_log.Debugf("parsePackageFiles err: %+v", err)
continue
}
declName = ele2.Sel.Name
}
}
case *ast.Ident: // like a()
if files, _, err = parsePackageFiles(curDir); err != nil {
_log.Debugf("parsePackageFiles err: %+v", err)
return
}
declName = ele2.Name
}
doc = declDocFromFiles(files, declName)
if doc != nil {
v = doc.Text()
}
return
}
// struct定义方法列表
// _parsers["decl.gen.decl.method"] = func(curDir string, f *ast.File, n ast.Node) (v string, hit bool) {
// var ele *ast.GenDecl
// if ele, hit = n.(*ast.GenDecl); !hit {
// return
// }
// switch ele.Tok {
// case token.DEFINE, token.VAR:
// for _, spec := range ele.Specs {
// ele2, ok := spec.(*ast.ValueSpec)
// if !ok {
// _log.Debugf("spec: %T %+v can't convert to *ast.ValueSpec")
// continue
// }
// switch ele3 := ele2.Type.(type) {
// case *ast.CallExpr:
// ele3.Fun.()
// }
// }
// default:
// hit = false
// return
// }
// }
}
func importName(impt *ast.ImportSpec) string {
if impt.Name != nil {
return impt.Name.Name
}
sep := strings.Split(strings.Trim(impt.Path.Value, `"`), "/")
return sep[len(sep)-1]
}
func declDocFromFiles(files []*ast.File, declName string) (doc *ast.CommentGroup) {
for _, f := range files {
for _, fd := range f.Decls {
switch decl := fd.(type) {
case *ast.FuncDecl: // like func a(){}
if decl.Name.Name == declName {
return decl.Doc
}
case *ast.GenDecl: // like var a int = 0
for _, s := range decl.Specs {
switch spec := s.(type) {
case *ast.ValueSpec:
for _, specName := range spec.Names {
if specName.Name == declName {
return spec.Doc
}
}
}
}
default:
_log.Debugf("decl(%+v,%s) unknown fDecl.(type): %T %+v", files, declName, decl, decl)
}
}
}
return
}

35
app/tool/bgr/log/BUILD Normal file
View File

@@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["log.go"],
importpath = "go-common/app/tool/bgr/log",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/bgr/log/color:go_default_library",
"//vendor/golang.org/x/crypto/ssh/terminal:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/tool/bgr/log/color:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["color.go"],
importpath = "go-common/app/tool/bgr/log/color",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,51 @@
package colorful
const (
colorOff = string("\033[0m")
colorRed = string("\033[0;31m")
colorGreen = string("\033[0;32m")
colorOrange = string("\033[0;33m")
colorBlue = string("\033[0;34m")
colorPurple = string("\033[0;35m")
colorCyan = string("\033[0;36m")
colorGray = string("\033[0;37m")
)
func paint(data string, color string) string {
return color + data + colorOff
}
// Red draw red on data
func Red(data string) string {
return paint(data, colorRed)
}
// Green draw Green on data
func Green(data string) string {
return paint(data, colorGreen)
}
// Orange draw Orange on data
func Orange(data string) string {
return paint(data, colorOrange)
}
// Blue draw Blue on data
func Blue(data string) string {
return paint(data, colorBlue)
}
// Purple draw Purple on data
func Purple(data string) string {
return paint(data, colorPurple)
}
// Cyan draw Cyan on data
func Cyan(data string) string {
return paint(data, colorCyan)
}
// Gray draw Gray on data
func Gray(data string) string {
return paint(data, colorGray)
}

160
app/tool/bgr/log/log.go Normal file
View File

@@ -0,0 +1,160 @@
package log
import (
"fmt"
"io"
"os"
"strings"
"sync"
"go-common/app/tool/bgr/log/color"
"golang.org/x/crypto/ssh/terminal"
)
// FDWriter interface extends io.Writer with file descriptor function
type FDWriter interface {
io.Writer
Fd() uintptr
}
// Logger struct definition
type Logger struct {
mu sync.RWMutex
out FDWriter
color bool
debug bool
buf strings.Builder
}
type prefix struct {
Plain string
Color string
}
const (
_plainError = "[ ERROR ] "
_plainWarn = "[ WARN ] "
_plainInfo = "[ INFO ] "
_plainDebug = "[ DEBUG ] "
_plainFatal = "[ FATAL ] "
)
var (
_prefixError = prefix{
Plain: _plainError,
Color: colorful.Red(_plainError),
}
_prefixWarn = prefix{
Plain: _plainWarn,
Color: colorful.Orange(_plainWarn),
}
_prefixInfo = prefix{
Plain: _plainInfo,
Color: colorful.Green(_plainInfo),
}
_prefixDebug = prefix{
Plain: _plainDebug,
Color: colorful.Purple(_plainDebug),
}
_prefixFatal = prefix{
Plain: _plainFatal,
Color: colorful.Gray(_plainFatal),
}
)
// New returns new Logger instance with predefined writer output and
// automatically detect terminal coloring support
func New(out FDWriter, debug bool) *Logger {
return &Logger{
color: terminal.IsTerminal(int(out.Fd())),
out: out,
debug: debug,
buf: strings.Builder{},
}
}
func (l *Logger) output(prefix prefix, data string) (err error) {
l.mu.Lock()
defer l.mu.Unlock()
l.buf.Reset()
if l.color {
if _, err = l.buf.WriteString(prefix.Color); err != nil {
return
}
} else {
if _, err = l.buf.WriteString(prefix.Plain); err != nil {
return
}
}
if _, err = l.buf.WriteString(data); err != nil {
return
}
if data[len(data)-1] != '\n' {
l.buf.WriteString("\n")
}
_, err = l.out.Write([]byte(l.buf.String()))
return
}
// Error print error message to output
func (l *Logger) Error(v ...interface{}) {
l.output(_prefixError, fmt.Sprintln(v...))
}
// Errorf print formatted error message to output
func (l *Logger) Errorf(format string, v ...interface{}) {
l.output(_prefixError, fmt.Sprintf(format, v...))
}
// Warn print warning message to output
func (l *Logger) Warn(v ...interface{}) {
l.output(_prefixWarn, fmt.Sprintln(v...))
}
// Warnf print formatted warning message to output
func (l *Logger) Warnf(format string, v ...interface{}) {
l.output(_prefixWarn, fmt.Sprintf(format, v...))
}
// Info print informational message to output
func (l *Logger) Info(v ...interface{}) {
l.output(_prefixInfo, fmt.Sprintln(v...))
}
// Infof print formatted informational message to output
func (l *Logger) Infof(format string, v ...interface{}) {
l.output(_prefixInfo, fmt.Sprintf(format, v...))
}
// Debug print debug message to output if debug output enabled
func (l *Logger) Debug(v ...interface{}) {
if l.debug {
l.output(_prefixDebug, fmt.Sprintln(v...))
}
}
// Debugf print formatted debug message to output if debug output enabled
func (l *Logger) Debugf(format string, v ...interface{}) {
if l.debug {
l.output(_prefixDebug, fmt.Sprintf(format, v...))
}
}
// Fatal print fatal message to output and then exit(1)
func (l *Logger) Fatal(v ...interface{}) {
l.output(_prefixFatal, fmt.Sprintln(v...))
os.Exit(1)
}
// Fatalf print formatted fatal message to output and then exit(1)
func (l *Logger) Fatalf(format string, v ...interface{}) {
l.output(_prefixFatal, fmt.Sprintf(format, v...))
os.Exit(1)
}

103
app/tool/bgr/main.go Normal file
View File

@@ -0,0 +1,103 @@
package main
import (
"flag"
"os"
"path/filepath"
"strings"
"go-common/app/tool/bgr/log"
)
var (
_flagType string
_flagScript string
_flagDebug bool
_flagHit string
_log *log.Logger
)
func init() {
flag.StringVar(&_flagType, "type", "file", "args type, file or dir")
flag.StringVar(&_flagScript, "script", defaultDir(), "input script dir")
flag.BoolVar(&_flagDebug, "debug", false, "set true, if need print debug info")
flag.StringVar(&_flagHit, "hit", "", "filter hit key")
flag.Parse()
_log = log.New(os.Stdout, _flagDebug)
}
func defaultDir() string {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
return dir
}
func main() {
targets := flag.Args()
switch _flagType {
case "file":
targets = filterFiles(targets)
targets = combineDirs(targets)
}
_log.Debugf("check targets: %+v", targets)
walkScript(_flagScript)
for _, dir := range targets {
if strings.HasSuffix(dir, "...") {
walkDir(strings.TrimRight(dir, "..."))
} else {
if err := AstInspect(dir); err != nil {
_log.Fatalf("%+v", err)
}
}
}
for _, desc := range _warns {
_log.Warn(desc)
}
for _, desc := range _errors {
_log.Error(desc)
}
if len(_errors) > 0 {
os.Exit(1)
}
}
func walkDir(dir string) {
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
if err := AstInspect(path); err != nil {
_log.Fatalf("%+v", err)
}
}
return nil
})
}
func combineDirs(files []string) (fs []string) {
fmap := make(map[string]struct{})
for _, f := range files {
index := strings.LastIndex(f, "/")
if index > 0 {
fmap[f[:index]] = struct{}{}
}
}
for k := range fmap {
fs = append(fs, k)
}
return
}
func filterFiles(files []string) (fs []string) {
for _, f := range files {
if strings.Contains(f, _flagHit) {
fs = append(fs, f)
}
}
return
}

24
app/tool/bgr/model.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
"go/ast"
"strings"
)
type lint struct {
s *script
fn func(curDir string, f *ast.File, node ast.Node) bool
}
type script struct {
dir string
ts []string // type slice
v string
l string
d string
}
func (s script) String() string {
return fmt.Sprintf("script path: %s, type: %s, value: %s, level: %s", s.dir, strings.Join(s.ts, "."), s.v, s.l)
}

View File

@@ -0,0 +1,137 @@
package main
import (
"bufio"
"fmt"
"go/ast"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/pkg/errors"
)
var (
_lints = make([]*lint, 0)
)
func walkScript(dir string) {
fn := func(path string, info os.FileInfo, err error) error {
if err != nil {
_log.Debugf("%+v", err)
return err
}
if !strings.HasSuffix(info.Name(), ".bgl") {
return nil
}
var (
file *os.File
newErr error
scripts []*script
)
if file, newErr = os.Open(path); newErr != nil {
newErr = errors.WithStack(newErr)
return newErr
}
if scripts, newErr = fileToScript(file, path); newErr != nil {
newErr = errors.WithStack(newErr)
return newErr
}
for _, s := range scripts {
registerLints(s)
}
return nil
}
if err := filepath.Walk(dir, fn); err != nil {
panic(err)
}
}
func fileToScript(file *os.File, path string) (scripts []*script, err error) {
var (
br = bufio.NewReader(file)
curScript *script
line []byte
isPrefix bool
)
for line, isPrefix, err = br.ReadLine(); err != io.EOF; line, isPrefix, err = br.ReadLine() {
if err != nil {
err = errors.WithStack(err)
return
}
if isPrefix {
_log.Fatalf("parseScript file: %s/%s err: some line too long", path, file.Name())
}
strs := strings.Split(strings.TrimSpace(string(line)), " ")
if len(strs) != 2 {
continue
}
k, v := strs[0], strs[1]
switch k {
case "T":
ts := strings.Split(strings.TrimSpace(v), ".")
curScript = &script{
dir: filepath.Dir(path),
ts: ts,
l: "e",
d: fmt.Sprintf("{%s : %s}", strings.Join(ts, "."), v),
}
case "V":
if curScript != nil {
curScript.v = v
}
scripts = append(scripts, curScript)
case "L":
if curScript != nil {
curScript.l = v
}
case "D":
if curScript != nil {
curScript.d = v
}
}
}
err = nil
return
}
func registerLints(script *script) {
_lints = append(_lints, &lint{
s: script,
fn: assembleLint(script),
})
}
func assembleLint(script *script) func(curDir string, f *ast.File, node ast.Node) bool {
var (
reg *regexp.Regexp
err error
)
_log.Debugf("assembleLint script: %+v", script)
if reg, err = regexp.Compile(script.v); err != nil {
_log.Fatalf("assembleLint script: %s, v compile error: %+v", script, err)
return nil
}
return func(curDir string, f *ast.File, n ast.Node) bool {
// if !strings.HasPrefix(curDir, script.dir) {
// return true
// }
var (
parse func(curDir string, f *ast.File, n ast.Node) (v string, hit bool)
ok bool
k = strings.Join(script.ts, ".")
)
if parse, ok = _parsers[k]; !ok {
return true
}
content, hit := parse(curDir, f, n)
if !hit {
return true
}
return !reg.MatchString(content) // 返回是否是正常node未命中lint
}
}

2
app/tool/bgr/test.sh Normal file
View File

@@ -0,0 +1,2 @@
go install
bgr -debug=true -type=dir ../../...