* change to new code location * vendor * tagged version v0.2.0 * gitea-vet v0.2.1 Co-authored-by: techknowlogick <techknowlogick@gitea.io>for-closed-social
@ -0,0 +1,30 @@ | |||||
# The full repository name | |||||
repo: gitea/gitea-vet | |||||
# Service type (gitea or github) | |||||
service: gitea | |||||
# Base URL for Gitea instance if using gitea service type (optional) | |||||
base-url: https://gitea.com | |||||
# Changelog groups and which labeled PRs to add to each group | |||||
groups: | |||||
- | |||||
name: BREAKING | |||||
labels: | |||||
- breaking | |||||
- | |||||
name: FEATURES | |||||
labels: | |||||
- feature | |||||
- | |||||
name: BUGFIXES | |||||
labels: | |||||
- bug | |||||
- | |||||
name: ENHANCEMENTS | |||||
labels: | |||||
- enhancement | |||||
# regex indicating which labels to skip for the changelog | |||||
skip-labels: skip-changelog|backport\/.+ |
@ -0,0 +1,45 @@ | |||||
--- | |||||
kind: pipeline | |||||
name: compliance | |||||
platform: | |||||
os: linux | |||||
arch: arm64 | |||||
trigger: | |||||
event: | |||||
- pull_request | |||||
steps: | |||||
- name: check | |||||
pull: always | |||||
image: golang:1.14 | |||||
environment: | |||||
GOPROXY: https://goproxy.cn | |||||
commands: | |||||
- make build | |||||
- make lint | |||||
- make vet | |||||
--- | |||||
kind: pipeline | |||||
name: build-master | |||||
platform: | |||||
os: linux | |||||
arch: amd64 | |||||
trigger: | |||||
branch: | |||||
- master | |||||
event: | |||||
- push | |||||
steps: | |||||
- name: build | |||||
pull: always | |||||
image: techknowlogick/xgo:latest | |||||
environment: | |||||
GOPROXY: https://goproxy.cn | |||||
commands: | |||||
- make build |
@ -0,0 +1,23 @@ | |||||
linters: | |||||
enable: | |||||
- deadcode | |||||
- dogsled | |||||
- dupl | |||||
- errcheck | |||||
- gocognit | |||||
- goconst | |||||
- gocritic | |||||
- gocyclo | |||||
- gofmt | |||||
- golint | |||||
- gosimple | |||||
- govet | |||||
- maligned | |||||
- misspell | |||||
- prealloc | |||||
- staticcheck | |||||
- structcheck | |||||
- typecheck | |||||
- unparam | |||||
- unused | |||||
- varcheck |
@ -0,0 +1,11 @@ | |||||
## [v0.2.1](https://gitea.com/gitea/gitea-vet/releases/tag/v0.2.1) - 2020-08-15 | |||||
* BUGFIXES | |||||
* Split migration check to Deps and Imports (#9) | |||||
## [0.2.0](https://gitea.com/gitea/gitea-vet/pulls?q=&type=all&state=closed&milestone=1272) - 2020-07-20 | |||||
* FEATURES | |||||
* Add migrations check (#5) | |||||
* BUGFIXES | |||||
* Correct Import Paths (#6) |
@ -0,0 +1,22 @@ | |||||
GO ?= go | |||||
.PHONY: build | |||||
build: | |||||
$(GO) build | |||||
.PHONY: fmt | |||||
fmt: | |||||
$(GO) fmt ./... | |||||
.PHONY: vet | |||||
vet: build | |||||
$(GO) vet ./... | |||||
$(GO) vet -vettool=gitea-vet ./... | |||||
.PHONY: lint | |||||
lint: | |||||
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ | |||||
export BINARY="golangci-lint"; \ | |||||
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell $(GO) env GOPATH)/bin v1.24.0; \ | |||||
fi | |||||
golangci-lint run --timeout 5m |
@ -0,0 +1,11 @@ | |||||
# gitea-vet | |||||
[![Build Status](https://drone.gitea.com/api/badges/gitea/gitea-vet/status.svg)](https://drone.gitea.com/gitea/gitea-vet) | |||||
`go vet` tool for Gitea | |||||
| Analyzer | Description | | |||||
|------------|-----------------------------------------------------------------------------| | |||||
| Imports | Checks for import sorting. stdlib->code.gitea.io->other | | |||||
| License | Checks file headers for some form of `Copyright...YYYY...Gitea/Gogs` | | |||||
| Migrations | Checks for black-listed packages in `code.gitea.io/gitea/models/migrations` | |
@ -0,0 +1,77 @@ | |||||
// Copyright 2020 The Gitea Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package checks | |||||
import ( | |||||
"errors" | |||||
"os/exec" | |||||
"strings" | |||||
"golang.org/x/tools/go/analysis" | |||||
) | |||||
var Migrations = &analysis.Analyzer{ | |||||
Name: "migrations", | |||||
Doc: "check migrations for black-listed packages.", | |||||
Run: checkMigrations, | |||||
} | |||||
var ( | |||||
migrationDepBlockList = []string{ | |||||
"code.gitea.io/gitea/models", | |||||
} | |||||
migrationImpBlockList = []string{ | |||||
"code.gitea.io/gitea/modules/structs", | |||||
} | |||||
) | |||||
func checkMigrations(pass *analysis.Pass) (interface{}, error) { | |||||
if !strings.EqualFold(pass.Pkg.Path(), "code.gitea.io/gitea/models/migrations") { | |||||
return nil, nil | |||||
} | |||||
if _, err := exec.LookPath("go"); err != nil { | |||||
return nil, errors.New("go was not found in the PATH") | |||||
} | |||||
depsCmd := exec.Command("go", "list", "-f", `{{join .Deps "\n"}}`, "code.gitea.io/gitea/models/migrations") | |||||
depsOut, err := depsCmd.Output() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
deps := strings.Split(string(depsOut), "\n") | |||||
for _, dep := range deps { | |||||
if stringInSlice(dep, migrationDepBlockList) { | |||||
pass.Reportf(0, "code.gitea.io/gitea/models/migrations cannot depend on the following packages: %s", migrationDepBlockList) | |||||
return nil, nil | |||||
} | |||||
} | |||||
impsCmd := exec.Command("go", "list", "-f", `{{join .Imports "\n"}}`, "code.gitea.io/gitea/models/migrations") | |||||
impsOut, err := impsCmd.Output() | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
imps := strings.Split(string(impsOut), "\n") | |||||
for _, imp := range imps { | |||||
if stringInSlice(imp, migrationImpBlockList) { | |||||
pass.Reportf(0, "code.gitea.io/gitea/models/migrations cannot import the following packages: %s", migrationImpBlockList) | |||||
return nil, nil | |||||
} | |||||
} | |||||
return nil, nil | |||||
} | |||||
func stringInSlice(needle string, haystack []string) bool { | |||||
for _, h := range haystack { | |||||
if strings.EqualFold(needle, h) { | |||||
return true | |||||
} | |||||
} | |||||
return false | |||||
} |
@ -1,4 +1,4 @@ | |||||
module gitea.com/jolheiser/gitea-vet | |||||
module code.gitea.io/gitea-vet | |||||
go 1.14 | go 1.14 | ||||
@ -1,7 +0,0 @@ | |||||
.PHONY: build | |||||
build: | |||||
go build | |||||
.PHONY: fmt | |||||
fmt: | |||||
go fmt ./... |
@ -1,7 +0,0 @@ | |||||
# gitea-vet | |||||
`go vet` tool for Gitea | |||||
| Analyzer | Description | | |||||
|----------|---------------------------------------------------------------------| | |||||
| Imports | Checks for import sorting. stdlib->code.gitea.io->other | | |||||
| License | Checks file headers for some form of `Copyright...YYYY...Gitea/Gogs`| |
@ -0,0 +1,421 @@ | |||||
// Copyright 2020 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// Package analysisinternal exposes internal-only fields from go/analysis. | |||||
package analysisinternal | |||||
import ( | |||||
"bytes" | |||||
"fmt" | |||||
"go/ast" | |||||
"go/token" | |||||
"go/types" | |||||
"strings" | |||||
"golang.org/x/tools/go/ast/astutil" | |||||
"golang.org/x/tools/internal/lsp/fuzzy" | |||||
) | |||||
var ( | |||||
GetTypeErrors func(p interface{}) []types.Error | |||||
SetTypeErrors func(p interface{}, errors []types.Error) | |||||
) | |||||
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos { | |||||
// Get the end position for the type error. | |||||
offset, end := fset.PositionFor(start, false).Offset, start | |||||
if offset >= len(src) { | |||||
return end | |||||
} | |||||
if width := bytes.IndexAny(src[offset:], " \n,():;[]+-*"); width > 0 { | |||||
end = start + token.Pos(width) | |||||
} | |||||
return end | |||||
} | |||||
func ZeroValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { | |||||
under := typ | |||||
if n, ok := typ.(*types.Named); ok { | |||||
under = n.Underlying() | |||||
} | |||||
switch u := under.(type) { | |||||
case *types.Basic: | |||||
switch { | |||||
case u.Info()&types.IsNumeric != 0: | |||||
return &ast.BasicLit{Kind: token.INT, Value: "0"} | |||||
case u.Info()&types.IsBoolean != 0: | |||||
return &ast.Ident{Name: "false"} | |||||
case u.Info()&types.IsString != 0: | |||||
return &ast.BasicLit{Kind: token.STRING, Value: `""`} | |||||
default: | |||||
panic("unknown basic type") | |||||
} | |||||
case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice, *types.Array: | |||||
return ast.NewIdent("nil") | |||||
case *types.Struct: | |||||
texpr := TypeExpr(fset, f, pkg, typ) // typ because we want the name here. | |||||
if texpr == nil { | |||||
return nil | |||||
} | |||||
return &ast.CompositeLit{ | |||||
Type: texpr, | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
// IsZeroValue checks whether the given expression is a 'zero value' (as determined by output of | |||||
// analysisinternal.ZeroValue) | |||||
func IsZeroValue(expr ast.Expr) bool { | |||||
switch e := expr.(type) { | |||||
case *ast.BasicLit: | |||||
return e.Value == "0" || e.Value == `""` | |||||
case *ast.Ident: | |||||
return e.Name == "nil" || e.Name == "false" | |||||
default: | |||||
return false | |||||
} | |||||
} | |||||
func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr { | |||||
switch t := typ.(type) { | |||||
case *types.Basic: | |||||
switch t.Kind() { | |||||
case types.UnsafePointer: | |||||
return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")} | |||||
default: | |||||
return ast.NewIdent(t.Name()) | |||||
} | |||||
case *types.Pointer: | |||||
x := TypeExpr(fset, f, pkg, t.Elem()) | |||||
if x == nil { | |||||
return nil | |||||
} | |||||
return &ast.UnaryExpr{ | |||||
Op: token.MUL, | |||||
X: x, | |||||
} | |||||
case *types.Array: | |||||
elt := TypeExpr(fset, f, pkg, t.Elem()) | |||||
if elt == nil { | |||||
return nil | |||||
} | |||||
return &ast.ArrayType{ | |||||
Len: &ast.BasicLit{ | |||||
Kind: token.INT, | |||||
Value: fmt.Sprintf("%d", t.Len()), | |||||
}, | |||||
Elt: elt, | |||||
} | |||||
case *types.Slice: | |||||
elt := TypeExpr(fset, f, pkg, t.Elem()) | |||||
if elt == nil { | |||||
return nil | |||||
} | |||||
return &ast.ArrayType{ | |||||
Elt: elt, | |||||
} | |||||
case *types.Map: | |||||
key := TypeExpr(fset, f, pkg, t.Key()) | |||||
value := TypeExpr(fset, f, pkg, t.Elem()) | |||||
if key == nil || value == nil { | |||||
return nil | |||||
} | |||||
return &ast.MapType{ | |||||
Key: key, | |||||
Value: value, | |||||
} | |||||
case *types.Chan: | |||||
dir := ast.ChanDir(t.Dir()) | |||||
if t.Dir() == types.SendRecv { | |||||
dir = ast.SEND | ast.RECV | |||||
} | |||||
value := TypeExpr(fset, f, pkg, t.Elem()) | |||||
if value == nil { | |||||
return nil | |||||
} | |||||
return &ast.ChanType{ | |||||
Dir: dir, | |||||
Value: value, | |||||
} | |||||
case *types.Signature: | |||||
var params []*ast.Field | |||||
for i := 0; i < t.Params().Len(); i++ { | |||||
p := TypeExpr(fset, f, pkg, t.Params().At(i).Type()) | |||||
if p == nil { | |||||
return nil | |||||
} | |||||
params = append(params, &ast.Field{ | |||||
Type: p, | |||||
Names: []*ast.Ident{ | |||||
{ | |||||
Name: t.Params().At(i).Name(), | |||||
}, | |||||
}, | |||||
}) | |||||
} | |||||
var returns []*ast.Field | |||||
for i := 0; i < t.Results().Len(); i++ { | |||||
r := TypeExpr(fset, f, pkg, t.Results().At(i).Type()) | |||||
if r == nil { | |||||
return nil | |||||
} | |||||
returns = append(returns, &ast.Field{ | |||||
Type: r, | |||||
}) | |||||
} | |||||
return &ast.FuncType{ | |||||
Params: &ast.FieldList{ | |||||
List: params, | |||||
}, | |||||
Results: &ast.FieldList{ | |||||
List: returns, | |||||
}, | |||||
} | |||||
case *types.Named: | |||||
if t.Obj().Pkg() == nil { | |||||
return ast.NewIdent(t.Obj().Name()) | |||||
} | |||||
if t.Obj().Pkg() == pkg { | |||||
return ast.NewIdent(t.Obj().Name()) | |||||
} | |||||
pkgName := t.Obj().Pkg().Name() | |||||
// If the file already imports the package under another name, use that. | |||||
for _, group := range astutil.Imports(fset, f) { | |||||
for _, cand := range group { | |||||
if strings.Trim(cand.Path.Value, `"`) == t.Obj().Pkg().Path() { | |||||
if cand.Name != nil && cand.Name.Name != "" { | |||||
pkgName = cand.Name.Name | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if pkgName == "." { | |||||
return ast.NewIdent(t.Obj().Name()) | |||||
} | |||||
return &ast.SelectorExpr{ | |||||
X: ast.NewIdent(pkgName), | |||||
Sel: ast.NewIdent(t.Obj().Name()), | |||||
} | |||||
default: | |||||
return nil // TODO: anonymous structs, but who does that | |||||
} | |||||
} | |||||
type TypeErrorPass string | |||||
const ( | |||||
NoNewVars TypeErrorPass = "nonewvars" | |||||
NoResultValues TypeErrorPass = "noresultvalues" | |||||
UndeclaredName TypeErrorPass = "undeclaredname" | |||||
) | |||||
// StmtToInsertVarBefore returns the ast.Stmt before which we can safely insert a new variable. | |||||
// Some examples: | |||||
// | |||||
// Basic Example: | |||||
// z := 1 | |||||
// y := z + x | |||||
// If x is undeclared, then this function would return `y := z + x`, so that we | |||||
// can insert `x := ` on the line before `y := z + x`. | |||||
// | |||||
// If stmt example: | |||||
// if z == 1 { | |||||
// } else if z == y {} | |||||
// If y is undeclared, then this function would return `if z == 1 {`, because we cannot | |||||
// insert a statement between an if and an else if statement. As a result, we need to find | |||||
// the top of the if chain to insert `y := ` before. | |||||
func StmtToInsertVarBefore(path []ast.Node) ast.Stmt { | |||||
enclosingIndex := -1 | |||||
for i, p := range path { | |||||
if _, ok := p.(ast.Stmt); ok { | |||||
enclosingIndex = i | |||||
break | |||||
} | |||||
} | |||||
if enclosingIndex == -1 { | |||||
return nil | |||||
} | |||||
enclosingStmt := path[enclosingIndex] | |||||
switch enclosingStmt.(type) { | |||||
case *ast.IfStmt: | |||||
// The enclosingStmt is inside of the if declaration, | |||||
// We need to check if we are in an else-if stmt and | |||||
// get the base if statement. | |||||
return baseIfStmt(path, enclosingIndex) | |||||
case *ast.CaseClause: | |||||
// Get the enclosing switch stmt if the enclosingStmt is | |||||
// inside of the case statement. | |||||
for i := enclosingIndex + 1; i < len(path); i++ { | |||||
if node, ok := path[i].(*ast.SwitchStmt); ok { | |||||
return node | |||||
} else if node, ok := path[i].(*ast.TypeSwitchStmt); ok { | |||||
return node | |||||
} | |||||
} | |||||
} | |||||
if len(path) <= enclosingIndex+1 { | |||||
return enclosingStmt.(ast.Stmt) | |||||
} | |||||
// Check if the enclosing statement is inside another node. | |||||
switch expr := path[enclosingIndex+1].(type) { | |||||
case *ast.IfStmt: | |||||
// Get the base if statement. | |||||
return baseIfStmt(path, enclosingIndex+1) | |||||
case *ast.ForStmt: | |||||
if expr.Init == enclosingStmt || expr.Post == enclosingStmt { | |||||
return expr | |||||
} | |||||
} | |||||
return enclosingStmt.(ast.Stmt) | |||||
} | |||||
// baseIfStmt walks up the if/else-if chain until we get to | |||||
// the top of the current if chain. | |||||
func baseIfStmt(path []ast.Node, index int) ast.Stmt { | |||||
stmt := path[index] | |||||
for i := index + 1; i < len(path); i++ { | |||||
if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt { | |||||
stmt = node | |||||
continue | |||||
} | |||||
break | |||||
} | |||||
return stmt.(ast.Stmt) | |||||
} | |||||
// WalkASTWithParent walks the AST rooted at n. The semantics are | |||||
// similar to ast.Inspect except it does not call f(nil). | |||||
func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) { | |||||
var ancestors []ast.Node | |||||
ast.Inspect(n, func(n ast.Node) (recurse bool) { | |||||
if n == nil { | |||||
ancestors = ancestors[:len(ancestors)-1] | |||||
return false | |||||
} | |||||
var parent ast.Node | |||||
if len(ancestors) > 0 { | |||||
parent = ancestors[len(ancestors)-1] | |||||
} | |||||
ancestors = append(ancestors, n) | |||||
return f(n, parent) | |||||
}) | |||||
} | |||||
// FindMatchingIdents finds all identifiers in 'node' that match any of the given types. | |||||
// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within | |||||
// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that | |||||
// is unrecognized. | |||||
func FindMatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]*ast.Ident { | |||||
matches := map[types.Type][]*ast.Ident{} | |||||
// Initialize matches to contain the variable types we are searching for. | |||||
for _, typ := range typs { | |||||
if typ == nil { | |||||
continue | |||||
} | |||||
matches[typ] = []*ast.Ident{} | |||||
} | |||||
seen := map[types.Object]struct{}{} | |||||
ast.Inspect(node, func(n ast.Node) bool { | |||||
if n == nil { | |||||
return false | |||||
} | |||||
// Prevent circular definitions. If 'pos' is within an assignment statement, do not | |||||
// allow any identifiers in that assignment statement to be selected. Otherwise, | |||||
// we could do the following, where 'x' satisfies the type of 'f0': | |||||
// | |||||
// x := fakeStruct{f0: x} | |||||
// | |||||
assignment, ok := n.(*ast.AssignStmt) | |||||
if ok && pos > assignment.Pos() && pos <= assignment.End() { | |||||
return false | |||||
} | |||||
if n.End() > pos { | |||||
return n.Pos() <= pos | |||||
} | |||||
ident, ok := n.(*ast.Ident) | |||||
if !ok || ident.Name == "_" { | |||||
return true | |||||
} | |||||
obj := info.Defs[ident] | |||||
if obj == nil || obj.Type() == nil { | |||||
return true | |||||
} | |||||
if _, ok := obj.(*types.TypeName); ok { | |||||
return true | |||||
} | |||||
// Prevent duplicates in matches' values. | |||||
if _, ok = seen[obj]; ok { | |||||
return true | |||||
} | |||||
seen[obj] = struct{}{} | |||||
// Find the scope for the given position. Then, check whether the object | |||||
// exists within the scope. | |||||
innerScope := pkg.Scope().Innermost(pos) | |||||
if innerScope == nil { | |||||
return true | |||||
} | |||||
_, foundObj := innerScope.LookupParent(ident.Name, pos) | |||||
if foundObj != obj { | |||||
return true | |||||
} | |||||
// The object must match one of the types that we are searching for. | |||||
if idents, ok := matches[obj.Type()]; ok { | |||||
matches[obj.Type()] = append(idents, ast.NewIdent(ident.Name)) | |||||
} | |||||
// If the object type does not exactly match any of the target types, greedily | |||||
// find the first target type that the object type can satisfy. | |||||
for typ := range matches { | |||||
if obj.Type() == typ { | |||||
continue | |||||
} | |||||
if equivalentTypes(obj.Type(), typ) { | |||||
matches[typ] = append(matches[typ], ast.NewIdent(ident.Name)) | |||||
} | |||||
} | |||||
return true | |||||
}) | |||||
return matches | |||||
} | |||||
func equivalentTypes(want, got types.Type) bool { | |||||
if want == got || types.Identical(want, got) { | |||||
return true | |||||
} | |||||
// Code segment to help check for untyped equality from (golang/go#32146). | |||||
if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 { | |||||
if lhs, ok := got.Underlying().(*types.Basic); ok { | |||||
return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType | |||||
} | |||||
} | |||||
return types.AssignableTo(want, got) | |||||
} | |||||
// FindBestMatch employs fuzzy matching to evaluate the similarity of each given identifier to the | |||||
// given pattern. We return the identifier whose name is most similar to the pattern. | |||||
func FindBestMatch(pattern string, idents []*ast.Ident) ast.Expr { | |||||
fuzz := fuzzy.NewMatcher(pattern) | |||||
var bestFuzz ast.Expr | |||||
highScore := float32(-1) // minimum score is -1 (no match) | |||||
for _, ident := range idents { | |||||
// TODO: Improve scoring algorithm. | |||||
score := fuzz.Score(ident.Name) | |||||
if score > highScore { | |||||
highScore = score | |||||
bestFuzz = ident | |||||
} else if score == -1 { | |||||
// Order matters in the fuzzy matching algorithm. If we find no match | |||||
// when matching the target to the identifier, try matching the identifier | |||||
// to the target. | |||||
revFuzz := fuzzy.NewMatcher(ident.Name) | |||||
revScore := revFuzz.Score(pattern) | |||||
if revScore > highScore { | |||||
highScore = revScore | |||||
bestFuzz = ident | |||||
} | |||||
} | |||||
} | |||||
return bestFuzz | |||||
} |
@ -0,0 +1,85 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// Package core provides support for event based telemetry. | |||||
package core | |||||
import ( | |||||
"fmt" | |||||
"time" | |||||
"golang.org/x/tools/internal/event/label" | |||||
) | |||||
// Event holds the information about an event of note that ocurred. | |||||
type Event struct { | |||||
at time.Time | |||||
// As events are often on the stack, storing the first few labels directly | |||||
// in the event can avoid an allocation at all for the very common cases of | |||||
// simple events. | |||||
// The length needs to be large enough to cope with the majority of events | |||||
// but no so large as to cause undue stack pressure. | |||||
// A log message with two values will use 3 labels (one for each value and | |||||
// one for the message itself). | |||||
static [3]label.Label // inline storage for the first few labels | |||||
dynamic []label.Label // dynamically sized storage for remaining labels | |||||
} | |||||
// eventLabelMap implements label.Map for a the labels of an Event. | |||||
type eventLabelMap struct { | |||||
event Event | |||||
} | |||||
func (ev Event) At() time.Time { return ev.at } | |||||
func (ev Event) Format(f fmt.State, r rune) { | |||||
if !ev.at.IsZero() { | |||||
fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) | |||||
} | |||||
for index := 0; ev.Valid(index); index++ { | |||||
if l := ev.Label(index); l.Valid() { | |||||
fmt.Fprintf(f, "\n\t%v", l) | |||||
} | |||||
} | |||||
} | |||||
func (ev Event) Valid(index int) bool { | |||||
return index >= 0 && index < len(ev.static)+len(ev.dynamic) | |||||
} | |||||
func (ev Event) Label(index int) label.Label { | |||||
if index < len(ev.static) { | |||||
return ev.static[index] | |||||
} | |||||
return ev.dynamic[index-len(ev.static)] | |||||
} | |||||
func (ev Event) Find(key label.Key) label.Label { | |||||
for _, l := range ev.static { | |||||
if l.Key() == key { | |||||
return l | |||||
} | |||||
} | |||||
for _, l := range ev.dynamic { | |||||
if l.Key() == key { | |||||
return l | |||||
} | |||||
} | |||||
return label.Label{} | |||||
} | |||||
func MakeEvent(static [3]label.Label, labels []label.Label) Event { | |||||
return Event{ | |||||
static: static, | |||||
dynamic: labels, | |||||
} | |||||
} | |||||
// CloneEvent event returns a copy of the event with the time adjusted to at. | |||||
func CloneEvent(ev Event, at time.Time) Event { | |||||
ev.at = at | |||||
return ev | |||||
} |
@ -0,0 +1,70 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package core | |||||
import ( | |||||
"context" | |||||
"sync/atomic" | |||||
"time" | |||||
"unsafe" | |||||
"golang.org/x/tools/internal/event/label" | |||||
) | |||||
// Exporter is a function that handles events. | |||||
// It may return a modified context and event. | |||||
type Exporter func(context.Context, Event, label.Map) context.Context | |||||
var ( | |||||
exporter unsafe.Pointer | |||||
) | |||||
// SetExporter sets the global exporter function that handles all events. | |||||
// The exporter is called synchronously from the event call site, so it should | |||||
// return quickly so as not to hold up user code. | |||||
func SetExporter(e Exporter) { | |||||
p := unsafe.Pointer(&e) | |||||
if e == nil { | |||||
// &e is always valid, and so p is always valid, but for the early abort | |||||
// of ProcessEvent to be efficient it needs to make the nil check on the | |||||
// pointer without having to dereference it, so we make the nil function | |||||
// also a nil pointer | |||||
p = nil | |||||
} | |||||
atomic.StorePointer(&exporter, p) | |||||
} | |||||
// deliver is called to deliver an event to the supplied exporter. | |||||
// it will fill in the time. | |||||
func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context { | |||||
// add the current time to the event | |||||
ev.at = time.Now() | |||||
// hand the event off to the current exporter | |||||
return exporter(ctx, ev, ev) | |||||
} | |||||
// Export is called to deliver an event to the global exporter if set. | |||||
func Export(ctx context.Context, ev Event) context.Context { | |||||
// get the global exporter and abort early if there is not one | |||||
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) | |||||
if exporterPtr == nil { | |||||
return ctx | |||||
} | |||||
return deliver(ctx, *exporterPtr, ev) | |||||
} | |||||
// ExportPair is called to deliver a start event to the supplied exporter. | |||||
// It also returns a function that will deliver the end event to the same | |||||
// exporter. | |||||
// It will fill in the time. | |||||
func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) { | |||||
// get the global exporter and abort early if there is not one | |||||
exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) | |||||
if exporterPtr == nil { | |||||
return ctx, func() {} | |||||
} | |||||
ctx = deliver(ctx, *exporterPtr, begin) | |||||
return ctx, func() { deliver(ctx, *exporterPtr, end) } | |||||
} |
@ -0,0 +1,77 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package core | |||||
import ( | |||||
"context" | |||||
"golang.org/x/tools/internal/event/keys" | |||||
"golang.org/x/tools/internal/event/label" | |||||
) | |||||
// Log1 takes a message and one label delivers a log event to the exporter. | |||||
// It is a customized version of Print that is faster and does no allocation. | |||||
func Log1(ctx context.Context, message string, t1 label.Label) { | |||||
Export(ctx, MakeEvent([3]label.Label{ | |||||
keys.Msg.Of(message), | |||||
t1, | |||||
}, nil)) | |||||
} | |||||
// Log2 takes a message and two labels and delivers a log event to the exporter. | |||||
// It is a customized version of Print that is faster and does no allocation. | |||||
func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) { | |||||
Export(ctx, MakeEvent([3]label.Label{ | |||||
keys.Msg.Of(message), | |||||
t1, | |||||
t2, | |||||
}, nil)) | |||||
} | |||||
// Metric1 sends a label event to the exporter with the supplied labels. | |||||
func Metric1(ctx context.Context, t1 label.Label) context.Context { | |||||
return Export(ctx, MakeEvent([3]label.Label{ | |||||
keys.Metric.New(), | |||||
t1, | |||||
}, nil)) | |||||
} | |||||
// Metric2 sends a label event to the exporter with the supplied labels. | |||||
func Metric2(ctx context.Context, t1, t2 label.Label) context.Context { | |||||
return Export(ctx, MakeEvent([3]label.Label{ | |||||
keys.Metric.New(), | |||||
t1, | |||||
t2, | |||||
}, nil)) | |||||
} | |||||
// Start1 sends a span start event with the supplied label list to the exporter. | |||||
// It also returns a function that will end the span, which should normally be | |||||
// deferred. | |||||
func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) { | |||||
return ExportPair(ctx, | |||||
MakeEvent([3]label.Label{ | |||||
keys.Start.Of(name), | |||||
t1, | |||||
}, nil), | |||||
MakeEvent([3]label.Label{ | |||||
keys.End.New(), | |||||
}, nil)) | |||||
} | |||||
// Start2 sends a span start event with the supplied label list to the exporter. | |||||
// It also returns a function that will end the span, which should normally be | |||||
// deferred. | |||||
func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) { | |||||
return ExportPair(ctx, | |||||
MakeEvent([3]label.Label{ | |||||
keys.Start.Of(name), | |||||
t1, | |||||
t2, | |||||
}, nil), | |||||
MakeEvent([3]label.Label{ | |||||
keys.End.New(), | |||||
}, nil)) | |||||
} |
@ -0,0 +1,7 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// Package event provides a set of packages that cover the main | |||||
// concepts of telemetry in an implementation agnostic way. | |||||
package event |
@ -0,0 +1,127 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package event | |||||
import ( | |||||
"context" | |||||
"golang.org/x/tools/internal/event/core" | |||||
"golang.org/x/tools/internal/event/keys" | |||||
"golang.org/x/tools/internal/event/label" | |||||
) | |||||
// Exporter is a function that handles events. | |||||
// It may return a modified context and event. | |||||
type Exporter func(context.Context, core.Event, label.Map) context.Context | |||||
// SetExporter sets the global exporter function that handles all events. | |||||
// The exporter is called synchronously from the event call site, so it should | |||||
// return quickly so as not to hold up user code. | |||||
func SetExporter(e Exporter) { | |||||
core.SetExporter(core.Exporter(e)) | |||||
} | |||||
// Log takes a message and a label list and combines them into a single event | |||||
// before delivering them to the exporter. | |||||
func Log(ctx context.Context, message string, labels ...label.Label) { | |||||
core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
keys.Msg.Of(message), | |||||
}, labels)) | |||||
} | |||||
// IsLog returns true if the event was built by the Log function. | |||||
// It is intended to be used in exporters to identify the semantics of the | |||||
// event when deciding what to do with it. | |||||
func IsLog(ev core.Event) bool { | |||||
return ev.Label(0).Key() == keys.Msg | |||||
} | |||||
// Error takes a message and a label list and combines them into a single event | |||||
// before delivering them to the exporter. It captures the error in the | |||||
// delivered event. | |||||
func Error(ctx context.Context, message string, err error, labels ...label.Label) { | |||||
core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
keys.Msg.Of(message), | |||||
keys.Err.Of(err), | |||||
}, labels)) | |||||
} | |||||
// IsError returns true if the event was built by the Error function. | |||||
// It is intended to be used in exporters to identify the semantics of the | |||||
// event when deciding what to do with it. | |||||
func IsError(ev core.Event) bool { | |||||
return ev.Label(0).Key() == keys.Msg && | |||||
ev.Label(1).Key() == keys.Err | |||||
} | |||||
// Metric sends a label event to the exporter with the supplied labels. | |||||
func Metric(ctx context.Context, labels ...label.Label) { | |||||
core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
keys.Metric.New(), | |||||
}, labels)) | |||||
} | |||||
// IsMetric returns true if the event was built by the Metric function. | |||||
// It is intended to be used in exporters to identify the semantics of the | |||||
// event when deciding what to do with it. | |||||
func IsMetric(ev core.Event) bool { | |||||
return ev.Label(0).Key() == keys.Metric | |||||
} | |||||
// Label sends a label event to the exporter with the supplied labels. | |||||
func Label(ctx context.Context, labels ...label.Label) context.Context { | |||||
return core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
keys.Label.New(), | |||||
}, labels)) | |||||
} | |||||
// IsLabel returns true if the event was built by the Label function. | |||||
// It is intended to be used in exporters to identify the semantics of the | |||||
// event when deciding what to do with it. | |||||
func IsLabel(ev core.Event) bool { | |||||
return ev.Label(0).Key() == keys.Label | |||||
} | |||||
// Start sends a span start event with the supplied label list to the exporter. | |||||
// It also returns a function that will end the span, which should normally be | |||||
// deferred. | |||||
func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) { | |||||
return core.ExportPair(ctx, | |||||
core.MakeEvent([3]label.Label{ | |||||
keys.Start.Of(name), | |||||
}, labels), | |||||
core.MakeEvent([3]label.Label{ | |||||
keys.End.New(), | |||||
}, nil)) | |||||
} | |||||
// IsStart returns true if the event was built by the Start function. | |||||
// It is intended to be used in exporters to identify the semantics of the | |||||
// event when deciding what to do with it. | |||||
func IsStart(ev core.Event) bool { | |||||
return ev.Label(0).Key() == keys.Start | |||||
} | |||||
// IsEnd returns true if the event was built by the End function. | |||||
// It is intended to be used in exporters to identify the semantics of the | |||||
// event when deciding what to do with it. | |||||
func IsEnd(ev core.Event) bool { | |||||
return ev.Label(0).Key() == keys.End | |||||
} | |||||
// Detach returns a context without an associated span. | |||||
// This allows the creation of spans that are not children of the current span. | |||||
func Detach(ctx context.Context) context.Context { | |||||
return core.Export(ctx, core.MakeEvent([3]label.Label{ | |||||
keys.Detach.New(), | |||||
}, nil)) | |||||
} | |||||
// IsDetach returns true if the event was built by the Detach function. | |||||
// It is intended to be used in exporters to identify the semantics of the | |||||
// event when deciding what to do with it. | |||||
func IsDetach(ev core.Event) bool { | |||||
return ev.Label(0).Key() == keys.Detach | |||||
} |
@ -0,0 +1,564 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package keys | |||||
import ( | |||||
"fmt" | |||||
"io" | |||||
"math" | |||||
"strconv" | |||||
"golang.org/x/tools/internal/event/label" | |||||
) | |||||
// Value represents a key for untyped values. | |||||
type Value struct { | |||||
name string | |||||
description string | |||||
} | |||||
// New creates a new Key for untyped values. | |||||
func New(name, description string) *Value { | |||||
return &Value{name: name, description: description} | |||||
} | |||||
func (k *Value) Name() string { return k.name } | |||||
func (k *Value) Description() string { return k.description } | |||||
func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { | |||||
fmt.Fprint(w, k.From(l)) | |||||
} | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Value) Get(lm label.Map) interface{} { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return nil | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() } | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) } | |||||
// Tag represents a key for tagging labels that have no value. | |||||
// These are used when the existence of the label is the entire information it | |||||
// carries, such as marking events to be of a specific kind, or from a specific | |||||
// package. | |||||
type Tag struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewTag creates a new Key for tagging labels. | |||||
func NewTag(name, description string) *Tag { | |||||
return &Tag{name: name, description: description} | |||||
} | |||||
func (k *Tag) Name() string { return k.name } | |||||
func (k *Tag) Description() string { return k.description } | |||||
func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} | |||||
// New creates a new Label with this key. | |||||
func (k *Tag) New() label.Label { return label.OfValue(k, nil) } | |||||
// Int represents a key | |||||
type Int struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewInt creates a new Key for int values. | |||||
func NewInt(name, description string) *Int { | |||||
return &Int{name: name, description: description} | |||||
} | |||||
func (k *Int) Name() string { return k.name } | |||||
func (k *Int) Description() string { return k.description } | |||||
func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Int) Get(lm label.Map) int { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } | |||||
// Int8 represents a key | |||||
type Int8 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewInt8 creates a new Key for int8 values. | |||||
func NewInt8(name, description string) *Int8 { | |||||
return &Int8{name: name, description: description} | |||||
} | |||||
func (k *Int8) Name() string { return k.name } | |||||
func (k *Int8) Description() string { return k.description } | |||||
func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Int8) Get(lm label.Map) int8 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } | |||||
// Int16 represents a key | |||||
type Int16 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewInt16 creates a new Key for int16 values. | |||||
func NewInt16(name, description string) *Int16 { | |||||
return &Int16{name: name, description: description} | |||||
} | |||||
func (k *Int16) Name() string { return k.name } | |||||
func (k *Int16) Description() string { return k.description } | |||||
func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Int16) Get(lm label.Map) int16 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } | |||||
// Int32 represents a key | |||||
type Int32 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewInt32 creates a new Key for int32 values. | |||||
func NewInt32(name, description string) *Int32 { | |||||
return &Int32{name: name, description: description} | |||||
} | |||||
func (k *Int32) Name() string { return k.name } | |||||
func (k *Int32) Description() string { return k.description } | |||||
func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Int32) Get(lm label.Map) int32 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } | |||||
// Int64 represents a key | |||||
type Int64 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewInt64 creates a new Key for int64 values. | |||||
func NewInt64(name, description string) *Int64 { | |||||
return &Int64{name: name, description: description} | |||||
} | |||||
func (k *Int64) Name() string { return k.name } | |||||
func (k *Int64) Description() string { return k.description } | |||||
func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendInt(buf, k.From(l), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Int64) Get(lm label.Map) int64 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } | |||||
// UInt represents a key | |||||
type UInt struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewUInt creates a new Key for uint values. | |||||
func NewUInt(name, description string) *UInt { | |||||
return &UInt{name: name, description: description} | |||||
} | |||||
func (k *UInt) Name() string { return k.name } | |||||
func (k *UInt) Description() string { return k.description } | |||||
func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *UInt) Get(lm label.Map) uint { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } | |||||
// UInt8 represents a key | |||||
type UInt8 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewUInt8 creates a new Key for uint8 values. | |||||
func NewUInt8(name, description string) *UInt8 { | |||||
return &UInt8{name: name, description: description} | |||||
} | |||||
func (k *UInt8) Name() string { return k.name } | |||||
func (k *UInt8) Description() string { return k.description } | |||||
func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *UInt8) Get(lm label.Map) uint8 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } | |||||
// UInt16 represents a key | |||||
type UInt16 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewUInt16 creates a new Key for uint16 values. | |||||
func NewUInt16(name, description string) *UInt16 { | |||||
return &UInt16{name: name, description: description} | |||||
} | |||||
func (k *UInt16) Name() string { return k.name } | |||||
func (k *UInt16) Description() string { return k.description } | |||||
func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *UInt16) Get(lm label.Map) uint16 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } | |||||
// UInt32 represents a key | |||||
type UInt32 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewUInt32 creates a new Key for uint32 values. | |||||
func NewUInt32(name, description string) *UInt32 { | |||||
return &UInt32{name: name, description: description} | |||||
} | |||||
func (k *UInt32) Name() string { return k.name } | |||||
func (k *UInt32) Description() string { return k.description } | |||||
func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *UInt32) Get(lm label.Map) uint32 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } | |||||
// UInt64 represents a key | |||||
type UInt64 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewUInt64 creates a new Key for uint64 values. | |||||
func NewUInt64(name, description string) *UInt64 { | |||||
return &UInt64{name: name, description: description} | |||||
} | |||||
func (k *UInt64) Name() string { return k.name } | |||||
func (k *UInt64) Description() string { return k.description } | |||||
func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendUint(buf, k.From(l), 10)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *UInt64) Get(lm label.Map) uint64 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } | |||||
// Float32 represents a key | |||||
type Float32 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewFloat32 creates a new Key for float32 values. | |||||
func NewFloat32(name, description string) *Float32 { | |||||
return &Float32{name: name, description: description} | |||||
} | |||||
func (k *Float32) Name() string { return k.name } | |||||
func (k *Float32) Description() string { return k.description } | |||||
func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Float32) Of(v float32) label.Label { | |||||
return label.Of64(k, uint64(math.Float32bits(v))) | |||||
} | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Float32) Get(lm label.Map) float32 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Float32) From(t label.Label) float32 { | |||||
return math.Float32frombits(uint32(t.Unpack64())) | |||||
} | |||||
// Float64 represents a key | |||||
type Float64 struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewFloat64 creates a new Key for int64 values. | |||||
func NewFloat64(name, description string) *Float64 { | |||||
return &Float64{name: name, description: description} | |||||
} | |||||
func (k *Float64) Name() string { return k.name } | |||||
func (k *Float64) Description() string { return k.description } | |||||
func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Float64) Of(v float64) label.Label { | |||||
return label.Of64(k, math.Float64bits(v)) | |||||
} | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Float64) Get(lm label.Map) float64 { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return 0 | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Float64) From(t label.Label) float64 { | |||||
return math.Float64frombits(t.Unpack64()) | |||||
} | |||||
// String represents a key | |||||
type String struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewString creates a new Key for int64 values. | |||||
func NewString(name, description string) *String { | |||||
return &String{name: name, description: description} | |||||
} | |||||
func (k *String) Name() string { return k.name } | |||||
func (k *String) Description() string { return k.description } | |||||
func (k *String) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendQuote(buf, k.From(l))) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *String) Of(v string) label.Label { return label.OfString(k, v) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *String) Get(lm label.Map) string { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return "" | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *String) From(t label.Label) string { return t.UnpackString() } | |||||
// Boolean represents a key | |||||
type Boolean struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewBoolean creates a new Key for bool values. | |||||
func NewBoolean(name, description string) *Boolean { | |||||
return &Boolean{name: name, description: description} | |||||
} | |||||
func (k *Boolean) Name() string { return k.name } | |||||
func (k *Boolean) Description() string { return k.description } | |||||
func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { | |||||
w.Write(strconv.AppendBool(buf, k.From(l))) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Boolean) Of(v bool) label.Label { | |||||
if v { | |||||
return label.Of64(k, 1) | |||||
} | |||||
return label.Of64(k, 0) | |||||
} | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Boolean) Get(lm label.Map) bool { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return false | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } | |||||
// Error represents a key | |||||
type Error struct { | |||||
name string | |||||
description string | |||||
} | |||||
// NewError creates a new Key for int64 values. | |||||
func NewError(name, description string) *Error { | |||||
return &Error{name: name, description: description} | |||||
} | |||||
func (k *Error) Name() string { return k.name } | |||||
func (k *Error) Description() string { return k.description } | |||||
func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { | |||||
io.WriteString(w, k.From(l).Error()) | |||||
} | |||||
// Of creates a new Label with this key and the supplied value. | |||||
func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } | |||||
// Get can be used to get a label for the key from a label.Map. | |||||
func (k *Error) Get(lm label.Map) error { | |||||
if t := lm.Find(k); t.Valid() { | |||||
return k.From(t) | |||||
} | |||||
return nil | |||||
} | |||||
// From can be used to get a value from a Label. | |||||
func (k *Error) From(t label.Label) error { | |||||
err, _ := t.UnpackValue().(error) | |||||
return err | |||||
} |
@ -0,0 +1,22 @@ | |||||
// Copyright 2020 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package keys | |||||
var ( | |||||
// Msg is a key used to add message strings to label lists. | |||||
Msg = NewString("message", "a readable message") | |||||
// Label is a key used to indicate an event adds labels to the context. | |||||
Label = NewTag("label", "a label context marker") | |||||
// Start is used for things like traces that have a name. | |||||
Start = NewString("start", "span start") | |||||
// Metric is a key used to indicate an event records metrics. | |||||
End = NewTag("end", "a span end marker") | |||||
// Metric is a key used to indicate an event records metrics. | |||||
Detach = NewTag("detach", "a span detach marker") | |||||
// Err is a key used to add error values to label lists. | |||||
Err = NewError("error", "an error that occurred") | |||||
// Metric is a key used to indicate an event records metrics. | |||||
Metric = NewTag("metric", "a metric event marker") | |||||
) |
@ -0,0 +1,213 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package label | |||||
import ( | |||||
"fmt" | |||||
"io" | |||||
"reflect" | |||||
"unsafe" | |||||
) | |||||
// Key is used as the identity of a Label. | |||||
// Keys are intended to be compared by pointer only, the name should be unique | |||||
// for communicating with external systems, but it is not required or enforced. | |||||
type Key interface { | |||||
// Name returns the key name. | |||||
Name() string | |||||
// Description returns a string that can be used to describe the value. | |||||
Description() string | |||||
// Format is used in formatting to append the value of the label to the | |||||
// supplied buffer. | |||||
// The formatter may use the supplied buf as a scratch area to avoid | |||||
// allocations. | |||||
Format(w io.Writer, buf []byte, l Label) | |||||
} | |||||
// Label holds a key and value pair. | |||||
// It is normally used when passing around lists of labels. | |||||
type Label struct { | |||||
key Key | |||||
packed uint64 | |||||
untyped interface{} | |||||
} | |||||
// Map is the interface to a collection of Labels indexed by key. | |||||
type Map interface { | |||||
// Find returns the label that matches the supplied key. | |||||
Find(key Key) Label | |||||
} | |||||
// List is the interface to something that provides an iterable | |||||
// list of labels. | |||||
// Iteration should start from 0 and continue until Valid returns false. | |||||
type List interface { | |||||
// Valid returns true if the index is within range for the list. | |||||
// It does not imply the label at that index will itself be valid. | |||||
Valid(index int) bool | |||||
// Label returns the label at the given index. | |||||
Label(index int) Label | |||||
} | |||||
// list implements LabelList for a list of Labels. | |||||
type list struct { | |||||
labels []Label | |||||
} | |||||
// filter wraps a LabelList filtering out specific labels. | |||||
type filter struct { | |||||
keys []Key | |||||
underlying List | |||||
} | |||||
// listMap implements LabelMap for a simple list of labels. | |||||
type listMap struct { | |||||
labels []Label | |||||
} | |||||
// mapChain implements LabelMap for a list of underlying LabelMap. | |||||
type mapChain struct { | |||||
maps []Map | |||||
} | |||||
// OfValue creates a new label from the key and value. | |||||
// This method is for implementing new key types, label creation should | |||||
// normally be done with the Of method of the key. | |||||
func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} } | |||||
// UnpackValue assumes the label was built using LabelOfValue and returns the value | |||||
// that was passed to that constructor. | |||||
// This method is for implementing new key types, for type safety normal | |||||
// access should be done with the From method of the key. | |||||
func (t Label) UnpackValue() interface{} { return t.untyped } | |||||
// Of64 creates a new label from a key and a uint64. This is often | |||||
// used for non uint64 values that can be packed into a uint64. | |||||
// This method is for implementing new key types, label creation should | |||||
// normally be done with the Of method of the key. | |||||
func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} } | |||||
// Unpack64 assumes the label was built using LabelOf64 and returns the value that | |||||
// was passed to that constructor. | |||||
// This method is for implementing new key types, for type safety normal | |||||
// access should be done with the From method of the key. | |||||
func (t Label) Unpack64() uint64 { return t.packed } | |||||
// OfString creates a new label from a key and a string. | |||||
// This method is for implementing new key types, label creation should | |||||
// normally be done with the Of method of the key. | |||||
func OfString(k Key, v string) Label { | |||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) | |||||
return Label{ | |||||
key: k, | |||||
packed: uint64(hdr.Len), | |||||
untyped: unsafe.Pointer(hdr.Data), | |||||
} | |||||
} | |||||
// UnpackString assumes the label was built using LabelOfString and returns the | |||||
// value that was passed to that constructor. | |||||
// This method is for implementing new key types, for type safety normal | |||||
// access should be done with the From method of the key. | |||||
func (t Label) UnpackString() string { | |||||
var v string | |||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) | |||||
hdr.Data = uintptr(t.untyped.(unsafe.Pointer)) | |||||
hdr.Len = int(t.packed) | |||||
return *(*string)(unsafe.Pointer(hdr)) | |||||
} | |||||
// Valid returns true if the Label is a valid one (it has a key). | |||||
func (t Label) Valid() bool { return t.key != nil } | |||||
// Key returns the key of this Label. | |||||
func (t Label) Key() Key { return t.key } | |||||
// Format is used for debug printing of labels. | |||||
func (t Label) Format(f fmt.State, r rune) { | |||||
if !t.Valid() { | |||||
io.WriteString(f, `nil`) | |||||
return | |||||
} | |||||
io.WriteString(f, t.Key().Name()) | |||||
io.WriteString(f, "=") | |||||
var buf [128]byte | |||||
t.Key().Format(f, buf[:0], t) | |||||
} | |||||
func (l *list) Valid(index int) bool { | |||||
return index >= 0 && index < len(l.labels) | |||||
} | |||||
func (l *list) Label(index int) Label { | |||||
return l.labels[index] | |||||
} | |||||
func (f *filter) Valid(index int) bool { | |||||
return f.underlying.Valid(index) | |||||
} | |||||
func (f *filter) Label(index int) Label { | |||||
l := f.underlying.Label(index) | |||||
for _, f := range f.keys { | |||||
if l.Key() == f { | |||||
return Label{} | |||||
} | |||||
} | |||||
return l | |||||
} | |||||
func (lm listMap) Find(key Key) Label { | |||||
for _, l := range lm.labels { | |||||
if l.Key() == key { | |||||
return l | |||||
} | |||||
} | |||||
return Label{} | |||||
} | |||||
func (c mapChain) Find(key Key) Label { | |||||
for _, src := range c.maps { | |||||
l := src.Find(key) | |||||
if l.Valid() { | |||||
return l | |||||
} | |||||
} | |||||
return Label{} | |||||
} | |||||
var emptyList = &list{} | |||||
func NewList(labels ...Label) List { | |||||
if len(labels) == 0 { | |||||
return emptyList | |||||
} | |||||
return &list{labels: labels} | |||||
} | |||||
func Filter(l List, keys ...Key) List { | |||||
if len(keys) == 0 { | |||||
return l | |||||
} | |||||
return &filter{keys: keys, underlying: l} | |||||
} | |||||
func NewMap(labels ...Label) Map { | |||||
return listMap{labels: labels} | |||||
} | |||||
func MergeMaps(srcs ...Map) Map { | |||||
var nonNil []Map | |||||
for _, src := range srcs { | |||||
if src != nil { | |||||
nonNil = append(nonNil, src) | |||||
} | |||||
} | |||||
if len(nonNil) == 1 { | |||||
return nonNil[0] | |||||
} | |||||
return mapChain{maps: nonNil} | |||||
} |
@ -0,0 +1,102 @@ | |||||
// Copyright 2020 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package gocommand | |||||
import ( | |||||
"bytes" | |||||
"context" | |||||
"fmt" | |||||
"os" | |||||
"path/filepath" | |||||
"regexp" | |||||
"strings" | |||||
"golang.org/x/mod/semver" | |||||
) | |||||
// ModuleJSON holds information about a module. | |||||
type ModuleJSON struct { | |||||
Path string // module path | |||||
Replace *ModuleJSON // replaced by this module | |||||
Main bool // is this the main module? | |||||
Indirect bool // is this module only an indirect dependency of main module? | |||||
Dir string // directory holding files for this module, if any | |||||
GoMod string // path to go.mod file for this module, if any | |||||
GoVersion string // go version used in module | |||||
} | |||||
var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) | |||||
// VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands | |||||
// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields, | |||||
// of which only Verb and Args are modified to run the appropriate Go command. | |||||
// Inspired by setDefaultBuildMod in modload/init.go | |||||
func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) { | |||||
mainMod, go114, err := getMainModuleAnd114(ctx, inv, r) | |||||
if err != nil { | |||||
return nil, false, err | |||||
} | |||||
// We check the GOFLAGS to see if there is anything overridden or not. | |||||
inv.Verb = "env" | |||||
inv.Args = []string{"GOFLAGS"} | |||||
stdout, err := r.Run(ctx, inv) | |||||
if err != nil { | |||||
return nil, false, err | |||||
} | |||||
goflags := string(bytes.TrimSpace(stdout.Bytes())) | |||||
matches := modFlagRegexp.FindStringSubmatch(goflags) | |||||
var modFlag string | |||||
if len(matches) != 0 { | |||||
modFlag = matches[1] | |||||
} | |||||
if modFlag != "" { | |||||
// Don't override an explicit '-mod=' argument. | |||||
return mainMod, modFlag == "vendor", nil | |||||
} | |||||
if mainMod == nil || !go114 { | |||||
return mainMod, false, nil | |||||
} | |||||
// Check 1.14's automatic vendor mode. | |||||
if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() { | |||||
if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 { | |||||
// The Go version is at least 1.14, and a vendor directory exists. | |||||
// Set -mod=vendor by default. | |||||
return mainMod, true, nil | |||||
} | |||||
} | |||||
return mainMod, false, nil | |||||
} | |||||
// getMainModuleAnd114 gets the main module's information and whether the | |||||
// go command in use is 1.14+. This is the information needed to figure out | |||||
// if vendoring should be enabled. | |||||
func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) { | |||||
const format = `{{.Path}} | |||||
{{.Dir}} | |||||
{{.GoMod}} | |||||
{{.GoVersion}} | |||||
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}} | |||||
` | |||||
inv.Verb = "list" | |||||
inv.Args = []string{"-m", "-f", format} | |||||
stdout, err := r.Run(ctx, inv) | |||||
if err != nil { | |||||
return nil, false, err | |||||
} | |||||
lines := strings.Split(stdout.String(), "\n") | |||||
if len(lines) < 5 { | |||||
return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String()) | |||||
} | |||||
mod := &ModuleJSON{ | |||||
Path: lines[0], | |||||
Dir: lines[1], | |||||
GoMod: lines[2], | |||||
GoVersion: lines[3], | |||||
Main: true, | |||||
} | |||||
return mod, lines[4] == "go1.14", nil | |||||
} |
@ -0,0 +1,168 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package fuzzy | |||||
import ( | |||||
"unicode" | |||||
) | |||||
// RuneRole specifies the role of a rune in the context of an input. | |||||
type RuneRole byte | |||||
const ( | |||||
// RNone specifies a rune without any role in the input (i.e., whitespace/non-ASCII). | |||||
RNone RuneRole = iota | |||||
// RSep specifies a rune with the role of segment separator. | |||||
RSep | |||||
// RTail specifies a rune which is a lower-case tail in a word in the input. | |||||
RTail | |||||
// RUCTail specifies a rune which is an upper-case tail in a word in the input. | |||||
RUCTail | |||||
// RHead specifies a rune which is the first character in a word in the input. | |||||
RHead | |||||
) | |||||
// RuneRoles detects the roles of each byte rune in an input string and stores it in the output | |||||
// slice. The rune role depends on the input type. Stops when it parsed all the runes in the string | |||||
// or when it filled the output. If output is nil, then it gets created. | |||||
func RuneRoles(str string, reuse []RuneRole) []RuneRole { | |||||
var output []RuneRole | |||||
if cap(reuse) < len(str) { | |||||
output = make([]RuneRole, 0, len(str)) | |||||
} else { | |||||
output = reuse[:0] | |||||
} | |||||
prev, prev2 := rtNone, rtNone | |||||
for i := 0; i < len(str); i++ { | |||||
r := rune(str[i]) | |||||
role := RNone | |||||
curr := rtLower | |||||
if str[i] <= unicode.MaxASCII { | |||||
curr = runeType(rt[str[i]] - '0') | |||||
} | |||||
if curr == rtLower { | |||||
if prev == rtNone || prev == rtPunct { | |||||
role = RHead | |||||
} else { | |||||
role = RTail | |||||
} | |||||
} else if curr == rtUpper { | |||||
role = RHead | |||||
if prev == rtUpper { | |||||
// This and previous characters are both upper case. | |||||
if i+1 == len(str) { | |||||
// This is last character, previous was also uppercase -> this is UCTail | |||||
// i.e., (current char is C): aBC / BC / ABC | |||||
role = RUCTail | |||||
} | |||||
} | |||||
} else if curr == rtPunct { | |||||
switch r { | |||||
case '.', ':': | |||||
role = RSep | |||||
} | |||||
} | |||||
if curr != rtLower { | |||||
if i > 1 && output[i-1] == RHead && prev2 == rtUpper && (output[i-2] == RHead || output[i-2] == RUCTail) { | |||||
// The previous two characters were uppercase. The current one is not a lower case, so the | |||||
// previous one can't be a HEAD. Make it a UCTail. | |||||
// i.e., (last char is current char - B must be a UCTail): ABC / ZABC / AB. | |||||
output[i-1] = RUCTail | |||||
} | |||||
} | |||||
output = append(output, role) | |||||
prev2 = prev | |||||
prev = curr | |||||
} | |||||
return output | |||||
} | |||||
type runeType byte | |||||
const ( | |||||
rtNone runeType = iota | |||||
rtPunct | |||||
rtLower | |||||
rtUpper | |||||
) | |||||
const rt = "00000000000000000000000000000000000000000000001122222222221000000333333333333333333333333330000002222222222222222222222222200000" | |||||
// LastSegment returns the substring representing the last segment from the input, where each | |||||
// byte has an associated RuneRole in the roles slice. This makes sense only for inputs of Symbol | |||||
// or Filename type. | |||||
func LastSegment(input string, roles []RuneRole) string { | |||||
// Exclude ending separators. | |||||
end := len(input) - 1 | |||||
for end >= 0 && roles[end] == RSep { | |||||
end-- | |||||
} | |||||
if end < 0 { | |||||
return "" | |||||
} | |||||
start := end - 1 | |||||
for start >= 0 && roles[start] != RSep { | |||||
start-- | |||||
} | |||||
return input[start+1 : end+1] | |||||
} | |||||
// ToLower transforms the input string to lower case, which is stored in the output byte slice. | |||||
// The lower casing considers only ASCII values - non ASCII values are left unmodified. | |||||
// Stops when parsed all input or when it filled the output slice. If output is nil, then it gets | |||||
// created. | |||||
func ToLower(input string, reuse []byte) []byte { | |||||
output := reuse | |||||
if cap(reuse) < len(input) { | |||||
output = make([]byte, len(input)) | |||||
} | |||||
for i := 0; i < len(input); i++ { | |||||
r := rune(input[i]) | |||||
if r <= unicode.MaxASCII { | |||||
if 'A' <= r && r <= 'Z' { | |||||
r += 'a' - 'A' | |||||
} | |||||
} | |||||
output[i] = byte(r) | |||||
} | |||||
return output[:len(input)] | |||||
} | |||||
// WordConsumer defines a consumer for a word delimited by the [start,end) byte offsets in an input | |||||
// (start is inclusive, end is exclusive). | |||||
type WordConsumer func(start, end int) | |||||
// Words find word delimiters in an input based on its bytes' mappings to rune roles. The offset | |||||
// delimiters for each word are fed to the provided consumer function. | |||||
func Words(roles []RuneRole, consume WordConsumer) { | |||||
var wordStart int | |||||
for i, r := range roles { | |||||
switch r { | |||||
case RUCTail, RTail: | |||||
case RHead, RNone, RSep: | |||||
if i != wordStart { | |||||
consume(wordStart, i) | |||||
} | |||||
wordStart = i | |||||
if r != RHead { | |||||
// Skip this character. | |||||
wordStart = i + 1 | |||||
} | |||||
} | |||||
} | |||||
if wordStart != len(roles) { | |||||
consume(wordStart, len(roles)) | |||||
} | |||||
} |
@ -0,0 +1,398 @@ | |||||
// Copyright 2019 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
// Package fuzzy implements a fuzzy matching algorithm. | |||||
package fuzzy | |||||
import ( | |||||
"bytes" | |||||
"fmt" | |||||
) | |||||
const ( | |||||
// MaxInputSize is the maximum size of the input scored against the fuzzy matcher. Longer inputs | |||||
// will be truncated to this size. | |||||
MaxInputSize = 127 | |||||
// MaxPatternSize is the maximum size of the pattern used to construct the fuzzy matcher. Longer | |||||
// inputs are truncated to this size. | |||||
MaxPatternSize = 63 | |||||
) | |||||
type scoreVal int | |||||
func (s scoreVal) val() int { | |||||
return int(s) >> 1 | |||||
} | |||||
func (s scoreVal) prevK() int { | |||||
return int(s) & 1 | |||||
} | |||||
func score(val int, prevK int /*0 or 1*/) scoreVal { | |||||
return scoreVal(val<<1 + prevK) | |||||
} | |||||
// Matcher implements a fuzzy matching algorithm for scoring candidates against a pattern. | |||||
// The matcher does not support parallel usage. | |||||
type Matcher struct { | |||||
pattern string | |||||
patternLower []byte // lower-case version of the pattern | |||||
patternShort []byte // first characters of the pattern | |||||
caseSensitive bool // set if the pattern is mix-cased | |||||
patternRoles []RuneRole // the role of each character in the pattern | |||||
roles []RuneRole // the role of each character in the tested string | |||||
scores [MaxInputSize + 1][MaxPatternSize + 1][2]scoreVal | |||||
scoreScale float32 | |||||
lastCandidateLen int // in bytes | |||||
lastCandidateMatched bool | |||||
// Here we save the last candidate in lower-case. This is basically a byte slice we reuse for | |||||
// performance reasons, so the slice is not reallocated for every candidate. | |||||
lowerBuf [MaxInputSize]byte | |||||
rolesBuf [MaxInputSize]RuneRole | |||||
} | |||||
func (m *Matcher) bestK(i, j int) int { | |||||
if m.scores[i][j][0].val() < m.scores[i][j][1].val() { | |||||
return 1 | |||||
} | |||||
return 0 | |||||
} | |||||
// NewMatcher returns a new fuzzy matcher for scoring candidates against the provided pattern. | |||||
func NewMatcher(pattern string) *Matcher { | |||||
if len(pattern) > MaxPatternSize { | |||||
pattern = pattern[:MaxPatternSize] | |||||
} | |||||
m := &Matcher{ | |||||
pattern: pattern, | |||||
patternLower: ToLower(pattern, nil), | |||||
} | |||||
for i, c := range m.patternLower { | |||||
if pattern[i] != c { | |||||
m.caseSensitive = true | |||||
break | |||||
} | |||||
} | |||||
if len(pattern) > 3 { | |||||
m.patternShort = m.patternLower[:3] | |||||
} else { | |||||
m.patternShort = m.patternLower | |||||
} | |||||
m.patternRoles = RuneRoles(pattern, nil) | |||||
if len(pattern) > 0 { | |||||
maxCharScore := 4 | |||||
m.scoreScale = 1 / float32(maxCharScore*len(pattern)) | |||||
} | |||||
return m | |||||
} | |||||
// Score returns the score returned by matching the candidate to the pattern. | |||||
// This is not designed for parallel use. Multiple candidates must be scored sequentially. | |||||
// Returns a score between 0 and 1 (0 - no match, 1 - perfect match). | |||||
func (m *Matcher) Score(candidate string) float32 { | |||||
if len(candidate) > MaxInputSize { | |||||
candidate = candidate[:MaxInputSize] | |||||
} | |||||
lower := ToLower(candidate, m.lowerBuf[:]) | |||||
m.lastCandidateLen = len(candidate) | |||||
if len(m.pattern) == 0 { | |||||
// Empty patterns perfectly match candidates. | |||||
return 1 | |||||
} | |||||
if m.match(candidate, lower) { | |||||
sc := m.computeScore(candidate, lower) | |||||
if sc > minScore/2 && !m.poorMatch() { | |||||
m.lastCandidateMatched = true | |||||
if len(m.pattern) == len(candidate) { | |||||
// Perfect match. | |||||
return 1 | |||||
} | |||||
if sc < 0 { | |||||
sc = 0 | |||||
} | |||||
normalizedScore := float32(sc) * m.scoreScale | |||||
if normalizedScore > 1 { | |||||
normalizedScore = 1 | |||||
} | |||||
return normalizedScore | |||||
} | |||||
} | |||||
m.lastCandidateMatched = false | |||||
return -1 | |||||
} | |||||
const minScore = -10000 | |||||
// MatchedRanges returns matches ranges for the last scored string as a flattened array of | |||||
// [begin, end) byte offset pairs. | |||||
func (m *Matcher) MatchedRanges() []int { | |||||
if len(m.pattern) == 0 || !m.lastCandidateMatched { | |||||
return nil | |||||
} | |||||
i, j := m.lastCandidateLen, len(m.pattern) | |||||
if m.scores[i][j][0].val() < minScore/2 && m.scores[i][j][1].val() < minScore/2 { | |||||
return nil | |||||
} | |||||
var ret []int | |||||
k := m.bestK(i, j) | |||||
for i > 0 { | |||||
take := (k == 1) | |||||
k = m.scores[i][j][k].prevK() | |||||
if take { | |||||
if len(ret) == 0 || ret[len(ret)-1] != i { | |||||
ret = append(ret, i) | |||||
ret = append(ret, i-1) | |||||
} else { | |||||
ret[len(ret)-1] = i - 1 | |||||
} | |||||
j-- | |||||
} | |||||
i-- | |||||
} | |||||
// Reverse slice. | |||||
for i := 0; i < len(ret)/2; i++ { | |||||
ret[i], ret[len(ret)-1-i] = ret[len(ret)-1-i], ret[i] | |||||
} | |||||
return ret | |||||
} | |||||
func (m *Matcher) match(candidate string, candidateLower []byte) bool { | |||||
i, j := 0, 0 | |||||
for ; i < len(candidateLower) && j < len(m.patternLower); i++ { | |||||
if candidateLower[i] == m.patternLower[j] { | |||||
j++ | |||||
} | |||||
} | |||||
if j != len(m.patternLower) { | |||||
return false | |||||
} | |||||
// The input passes the simple test against pattern, so it is time to classify its characters. | |||||
// Character roles are used below to find the last segment. | |||||
m.roles = RuneRoles(candidate, m.rolesBuf[:]) | |||||
return true | |||||
} | |||||
func (m *Matcher) computeScore(candidate string, candidateLower []byte) int { | |||||
pattLen, candLen := len(m.pattern), len(candidate) | |||||
for j := 0; j <= len(m.pattern); j++ { | |||||
m.scores[0][j][0] = minScore << 1 | |||||
m.scores[0][j][1] = minScore << 1 | |||||
} | |||||
m.scores[0][0][0] = score(0, 0) // Start with 0. | |||||
segmentsLeft, lastSegStart := 1, 0 | |||||
for i := 0; i < candLen; i++ { | |||||
if m.roles[i] == RSep { | |||||
segmentsLeft++ | |||||
lastSegStart = i + 1 | |||||
} | |||||
} | |||||
// A per-character bonus for a consecutive match. | |||||
consecutiveBonus := 2 | |||||
wordIdx := 0 // Word count within segment. | |||||
for i := 1; i <= candLen; i++ { | |||||
role := m.roles[i-1] | |||||
isHead := role == RHead | |||||
if isHead { | |||||
wordIdx++ | |||||
} else if role == RSep && segmentsLeft > 1 { | |||||
wordIdx = 0 | |||||
segmentsLeft-- | |||||
} | |||||
var skipPenalty int | |||||
if i == 1 || (i-1) == lastSegStart { | |||||
// Skipping the start of first or last segment. | |||||
skipPenalty++ | |||||
} | |||||
for j := 0; j <= pattLen; j++ { | |||||
// By default, we don't have a match. Fill in the skip data. | |||||
m.scores[i][j][1] = minScore << 1 | |||||
// Compute the skip score. | |||||
k := 0 | |||||
if m.scores[i-1][j][0].val() < m.scores[i-1][j][1].val() { | |||||
k = 1 | |||||
} | |||||
skipScore := m.scores[i-1][j][k].val() | |||||
// Do not penalize missing characters after the last matched segment. | |||||
if j != pattLen { | |||||
skipScore -= skipPenalty | |||||
} | |||||
m.scores[i][j][0] = score(skipScore, k) | |||||
if j == 0 || candidateLower[i-1] != m.patternLower[j-1] { | |||||
// Not a match. | |||||
continue | |||||
} | |||||
pRole := m.patternRoles[j-1] | |||||
if role == RTail && pRole == RHead { | |||||
if j > 1 { | |||||
// Not a match: a head in the pattern matches a tail character in the candidate. | |||||
continue | |||||
} | |||||
// Special treatment for the first character of the pattern. We allow | |||||
// matches in the middle of a word if they are long enough, at least | |||||
// min(3, pattern.length) characters. | |||||
if !bytes.HasPrefix(candidateLower[i-1:], m.patternShort) { | |||||
continue | |||||
} | |||||
} | |||||
// Compute the char score. | |||||
var charScore int | |||||
// Bonus 1: the char is in the candidate's last segment. | |||||
if segmentsLeft <= 1 { | |||||
charScore++ | |||||
} | |||||
// Bonus 2: Case match or a Head in the pattern aligns with one in the word. | |||||
// Single-case patterns lack segmentation signals and we assume any character | |||||
// can be a head of a segment. | |||||
if candidate[i-1] == m.pattern[j-1] || role == RHead && (!m.caseSensitive || pRole == RHead) { | |||||
charScore++ | |||||
} | |||||
// Penalty 1: pattern char is Head, candidate char is Tail. | |||||
if role == RTail && pRole == RHead { | |||||
charScore-- | |||||
} | |||||
// Penalty 2: first pattern character matched in the middle of a word. | |||||
if j == 1 && role == RTail { | |||||
charScore -= 4 | |||||
} | |||||
// Third dimension encodes whether there is a gap between the previous match and the current | |||||
// one. | |||||
for k := 0; k < 2; k++ { | |||||
sc := m.scores[i-1][j-1][k].val() + charScore | |||||
isConsecutive := k == 1 || i-1 == 0 || i-1 == lastSegStart | |||||
if isConsecutive { | |||||
// Bonus 3: a consecutive match. First character match also gets a bonus to | |||||
// ensure prefix final match score normalizes to 1.0. | |||||
// Logically, this is a part of charScore, but we have to compute it here because it | |||||
// only applies for consecutive matches (k == 1). | |||||
sc += consecutiveBonus | |||||
} | |||||
if k == 0 { | |||||
// Penalty 3: Matching inside a segment (and previous char wasn't matched). Penalize for the lack | |||||
// of alignment. | |||||
if role == RTail || role == RUCTail { | |||||
sc -= 3 | |||||
} | |||||
} | |||||
if sc > m.scores[i][j][1].val() { | |||||
m.scores[i][j][1] = score(sc, k) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
result := m.scores[len(candidate)][len(m.pattern)][m.bestK(len(candidate), len(m.pattern))].val() | |||||
return result | |||||
} | |||||
// ScoreTable returns the score table computed for the provided candidate. Used only for debugging. | |||||
func (m *Matcher) ScoreTable(candidate string) string { | |||||
var buf bytes.Buffer | |||||
var line1, line2, separator bytes.Buffer | |||||
line1.WriteString("\t") | |||||
line2.WriteString("\t") | |||||
for j := 0; j < len(m.pattern); j++ { | |||||
line1.WriteString(fmt.Sprintf("%c\t\t", m.pattern[j])) | |||||
separator.WriteString("----------------") | |||||
} | |||||
buf.WriteString(line1.String()) | |||||
buf.WriteString("\n") | |||||
buf.WriteString(separator.String()) | |||||
buf.WriteString("\n") | |||||
for i := 1; i <= len(candidate); i++ { | |||||
line1.Reset() | |||||
line2.Reset() | |||||
line1.WriteString(fmt.Sprintf("%c\t", candidate[i-1])) | |||||
line2.WriteString("\t") | |||||
for j := 1; j <= len(m.pattern); j++ { | |||||
line1.WriteString(fmt.Sprintf("M%6d(%c)\t", m.scores[i][j][0].val(), dir(m.scores[i][j][0].prevK()))) | |||||
line2.WriteString(fmt.Sprintf("H%6d(%c)\t", m.scores[i][j][1].val(), dir(m.scores[i][j][1].prevK()))) | |||||
} | |||||
buf.WriteString(line1.String()) | |||||
buf.WriteString("\n") | |||||
buf.WriteString(line2.String()) | |||||
buf.WriteString("\n") | |||||
buf.WriteString(separator.String()) | |||||
buf.WriteString("\n") | |||||
} | |||||
return buf.String() | |||||
} | |||||
func dir(prevK int) rune { | |||||
if prevK == 0 { | |||||
return 'M' | |||||
} | |||||
return 'H' | |||||
} | |||||
func (m *Matcher) poorMatch() bool { | |||||
if len(m.pattern) < 2 { | |||||
return false | |||||
} | |||||
i, j := m.lastCandidateLen, len(m.pattern) | |||||
k := m.bestK(i, j) | |||||
var counter, len int | |||||
for i > 0 { | |||||
take := (k == 1) | |||||
k = m.scores[i][j][k].prevK() | |||||
if take { | |||||
len++ | |||||
if k == 0 && len < 3 && m.roles[i-1] == RTail { | |||||
// Short match in the middle of a word | |||||
counter++ | |||||
if counter > 1 { | |||||
return true | |||||
} | |||||
} | |||||
j-- | |||||
} else { | |||||
len = 0 | |||||
} | |||||
i-- | |||||
} | |||||
return false | |||||
} |
@ -1,27 +1,14 @@ | |||||
// Package packagesinternal exposes internal-only fields from go/packages. | // Package packagesinternal exposes internal-only fields from go/packages. | ||||
package packagesinternal | package packagesinternal | ||||
import "time" | |||||
// Fields must match go list; | |||||
type Module struct { | |||||
Path string // module path | |||||
Version string // module version | |||||
Versions []string // available module versions (with -versions) | |||||
Replace *Module // replaced by this module | |||||
Time *time.Time // time version was created | |||||
Update *Module // available update, if any (with -u) | |||||
Main bool // is this the main module? | |||||
Indirect bool // is this module only an indirect dependency of main module? | |||||
Dir string // directory holding files for this module, if any | |||||
GoMod string // path to go.mod file used when loading this module, if any | |||||
GoVersion string // go version used in module | |||||
Error *ModuleError // error loading module | |||||
} | |||||
type ModuleError struct { | |||||
Err string // the error itself | |||||
} | |||||
import ( | |||||
"golang.org/x/tools/internal/gocommand" | |||||
) | |||||
var GetForTest = func(p interface{}) string { return "" } | var GetForTest = func(p interface{}) string { return "" } | ||||
var GetModule = func(p interface{}) *Module { return nil } | |||||
var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil } | |||||
var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {} | |||||
var TypecheckCgo int |
@ -0,0 +1,28 @@ | |||||
// Copyright 2020 The Go Authors. All rights reserved. | |||||
// Use of this source code is governed by a BSD-style | |||||
// license that can be found in the LICENSE file. | |||||
package typesinternal | |||||
import ( | |||||
"go/types" | |||||
"reflect" | |||||
"unsafe" | |||||
) | |||||
func SetUsesCgo(conf *types.Config) bool { | |||||
v := reflect.ValueOf(conf).Elem() | |||||
f := v.FieldByName("go115UsesCgo") | |||||
if !f.IsValid() { | |||||
f = v.FieldByName("UsesCgo") | |||||
if !f.IsValid() { | |||||
return false | |||||
} | |||||
} | |||||
addr := unsafe.Pointer(f.UnsafeAddr()) | |||||
*(*bool)(addr) = true | |||||
return true | |||||
} |