* 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 | |||
@ -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 | |||
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 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 | |||
} |