* change to new code location * vendor * tagged version v0.2.0 * gitea-vet v0.2.1 Co-authored-by: techknowlogick <techknowlogick@gitea.io>tags/v1.13.0-rc1
@@ -222,7 +222,7 @@ vet: | |||
# Default vet | |||
$(GO) vet $(GO_PACKAGES) | |||
# Custom vet | |||
$(GO) build -mod=vendor gitea.com/jolheiser/gitea-vet | |||
$(GO) build -mod=vendor code.gitea.io/gitea-vet | |||
$(GO) vet -vettool=gitea-vet $(GO_PACKAGES) | |||
.PHONY: $(TAGS_EVIDENCE) |
@@ -25,7 +25,7 @@ import ( | |||
_ "golang.org/x/tools/cover" | |||
// for vet | |||
_ "gitea.com/jolheiser/gitea-vet" | |||
_ "code.gitea.io/gitea-vet" | |||
// for swagger | |||
_ "github.com/go-swagger/go-swagger/cmd/swagger" |
@@ -4,7 +4,7 @@ go 1.14 | |||
require ( | |||
cloud.google.com/go v0.45.0 // indirect | |||
gitea.com/jolheiser/gitea-vet v0.1.0 | |||
code.gitea.io/gitea-vet v0.2.1 | |||
gitea.com/lunny/levelqueue v0.3.0 | |||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b | |||
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76 | |||
@@ -104,12 +104,12 @@ require ( | |||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 | |||
github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 | |||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de | |||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 | |||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 | |||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d | |||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 | |||
golang.org/x/text v0.3.2 | |||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect | |||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 | |||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d | |||
google.golang.org/appengine v1.6.5 // indirect | |||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect | |||
gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect |
@@ -9,8 +9,8 @@ cloud.google.com/go v0.45.0 h1:bALuGBSgE+BD4rxsopAYlqjcwqcQtye6pWG4bC3N/k0= | |||
cloud.google.com/go v0.45.0/go.mod h1:452BcPOeI9AZfbvDw0Tbo7D32wA+WX9WME8AZwMEDZU= | |||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= | |||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= | |||
gitea.com/jolheiser/gitea-vet v0.1.0 h1:gJEms9YWbIcrPOEmDOJ+5JZXCYFxNpwxlI73uRulAi4= | |||
gitea.com/jolheiser/gitea-vet v0.1.0/go.mod h1:2Oa6TAdEp1N/38oBNh3ZeiSEER60D/CeDaBFv2sdH58= | |||
code.gitea.io/gitea-vet v0.2.1 h1:b30by7+3SkmiftK0RjuXqFvZg2q4p68uoPGuxhzBN0s= | |||
code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= | |||
gitea.com/lunny/levelqueue v0.3.0 h1:MHn1GuSZkxvVEDMyAPqlc7A3cOW+q8RcGhRgH/xtm6I= | |||
gitea.com/lunny/levelqueue v0.3.0/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= | |||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b h1:vXt85uYV17KURaUlhU7v4GbCShkqRZDSfo0TkC0YCjQ= | |||
@@ -775,6 +775,7 @@ github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec | |||
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds= | |||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= | |||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | |||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio= | |||
@@ -817,6 +818,8 @@ golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8U | |||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | |||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= | |||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= | |||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | |||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | |||
@@ -832,6 +835,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zH | |||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | |||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= | |||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= | |||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | |||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | |||
@@ -860,8 +865,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL | |||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= | |||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | |||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | |||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= | |||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | |||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= | |||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= | |||
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | |||
@@ -876,6 +881,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ | |||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | |||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | |||
@@ -949,6 +955,8 @@ golang.org/x/tools v0.0.0-20200225230052-807dcd883420 h1:4RJNOV+2rLxMEfr6QIpC7GE | |||
golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | |||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 h1:azwY/v0y0K4mFHVsg5+UrTgchqALYWpqVo6vL5OmkmI= | |||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= | |||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d h1:XZxUC4/ZNKTjrT4/Oc9gCgIYnzPW3/CefdPjsndrVWM= | |||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= | |||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | |||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
@@ -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` | |
@@ -12,7 +12,7 @@ import ( | |||
var Imports = &analysis.Analyzer{ | |||
Name: "imports", | |||
Doc: "check for import order.", | |||
Doc: "check for import order", | |||
Run: runImports, | |||
} | |||
@@ -22,11 +22,12 @@ func runImports(pass *analysis.Pass) (interface{}, error) { | |||
for _, im := range file.Imports { | |||
var lvl int | |||
val := im.Path.Value | |||
if importHasPrefix(val, "code.gitea.io") { | |||
switch { | |||
case importHasPrefix(val, "code.gitea.io"): | |||
lvl = 2 | |||
} else if strings.Contains(val, ".") { | |||
case strings.Contains(val, "."): | |||
lvl = 3 | |||
} else { | |||
default: | |||
lvl = 1 | |||
} | |||
@@ -43,12 +44,3 @@ func runImports(pass *analysis.Pass) (interface{}, error) { | |||
func importHasPrefix(s, p string) bool { | |||
return strings.HasPrefix(s, "\""+p) | |||
} | |||
func sliceHasPrefix(s string, prefixes ...string) bool { | |||
for _, p := range prefixes { | |||
if importHasPrefix(s, p) { | |||
return true | |||
} | |||
} | |||
return false | |||
} |
@@ -19,7 +19,7 @@ var ( | |||
var License = &analysis.Analyzer{ | |||
Name: "license", | |||
Doc: "check for a copyright header.", | |||
Doc: "check for a copyright header", | |||
Run: runLicense, | |||
} | |||
@@ -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 | |||
@@ -5,7 +5,8 @@ | |||
package main | |||
import ( | |||
"gitea.com/jolheiser/gitea-vet/checks" | |||
"code.gitea.io/gitea-vet/checks" | |||
"golang.org/x/tools/go/analysis/unitchecker" | |||
) | |||
@@ -13,5 +14,6 @@ func main() { | |||
unitchecker.Main( | |||
checks.Imports, | |||
checks.License, | |||
checks.Migrations, | |||
) | |||
} |
@@ -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`| |
@@ -7,6 +7,8 @@ import ( | |||
"go/token" | |||
"go/types" | |||
"reflect" | |||
"golang.org/x/tools/internal/analysisinternal" | |||
) | |||
// An Analyzer describes an analysis function and its options. | |||
@@ -69,6 +71,17 @@ type Analyzer struct { | |||
func (a *Analyzer) String() string { return a.Name } | |||
func init() { | |||
// Set the analysisinternal functions to be able to pass type errors | |||
// to the Pass type without modifying the go/analysis API. | |||
analysisinternal.SetTypeErrors = func(p interface{}, errors []types.Error) { | |||
p.(*Pass).typeErrors = errors | |||
} | |||
analysisinternal.GetTypeErrors = func(p interface{}) []types.Error { | |||
return p.(*Pass).typeErrors | |||
} | |||
} | |||
// A Pass provides information to the Run function that | |||
// applies a specific analyzer to a single Go package. | |||
// | |||
@@ -138,6 +151,9 @@ type Pass struct { | |||
// WARNING: This is an experimental API and may change in the future. | |||
AllObjectFacts func() []ObjectFact | |||
// typeErrors contains types.Errors that are associated with the pkg. | |||
typeErrors []types.Error | |||
/* Further fields may be added in future. */ | |||
// For example, suggested or applied refactorings. | |||
} |
@@ -170,6 +170,15 @@ Diagnostic is defined as: | |||
The optional Category field is a short identifier that classifies the | |||
kind of message when an analysis produces several kinds of diagnostic. | |||
Many analyses want to associate diagnostics with a severity level. | |||
Because Diagnostic does not have a severity level field, an Analyzer's | |||
diagnostics effectively all have the same severity level. To separate which | |||
diagnostics are high severity and which are low severity, expose multiple | |||
Analyzers instead. Analyzers should also be separated when their | |||
diagnostics belong in different groups, or could be tagged differently | |||
before being shown to the end user. Analyzers should document their severity | |||
level to help downstream tools surface diagnostics properly. | |||
Most Analyzers inspect typed Go syntax trees, but a few, such as asmdecl | |||
and buildtag, inspect the raw text of Go source files or even non-Go | |||
files such as assembly. To report a diagnostic against a line of a |
@@ -382,7 +382,7 @@ func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis. | |||
func (tree JSONTree) Print() { | |||
data, err := json.MarshalIndent(tree, "", "\t") | |||
if err != nil { | |||
log.Panicf("internal error: JSON marshalling failed: %v", err) | |||
log.Panicf("internal error: JSON marshaling failed: %v", err) | |||
} | |||
fmt.Printf("%s\n", data) | |||
} |
@@ -19,8 +19,7 @@ import ( | |||
var debug = false | |||
// GetSizes returns the sizes used by the underlying driver with the given parameters. | |||
func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { | |||
func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { | |||
// TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. | |||
const toolPrefix = "GOPACKAGESDRIVER=" | |||
tool := "" | |||
@@ -40,7 +39,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp | |||
} | |||
if tool == "off" { | |||
return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData) | |||
return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir) | |||
} | |||
req, err := json.Marshal(struct { | |||
@@ -76,7 +75,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp | |||
return response.Sizes, nil | |||
} | |||
func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { | |||
func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { | |||
inv := gocommand.Invocation{ | |||
Verb: "list", | |||
Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"}, | |||
@@ -84,7 +83,7 @@ func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, u | |||
BuildFlags: buildFlags, | |||
WorkingDir: dir, | |||
} | |||
stdout, stderr, friendlyErr, rawErr := inv.RunRaw(ctx) | |||
stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) | |||
var goarch, compiler string | |||
if rawErr != nil { | |||
if strings.Contains(rawErr.Error(), "cannot find main module") { | |||
@@ -96,7 +95,7 @@ func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, u | |||
Env: env, | |||
WorkingDir: dir, | |||
} | |||
envout, enverr := inv.Run(ctx) | |||
envout, enverr := gocmdRunner.Run(ctx, inv) | |||
if enverr != nil { | |||
return nil, enverr | |||
} |
@@ -24,7 +24,7 @@ import ( | |||
"golang.org/x/tools/go/internal/packagesdriver" | |||
"golang.org/x/tools/internal/gocommand" | |||
"golang.org/x/tools/internal/packagesinternal" | |||
"golang.org/x/xerrors" | |||
) | |||
// debug controls verbose logging. | |||
@@ -89,6 +89,10 @@ type golistState struct { | |||
rootDirsError error | |||
rootDirs map[string]string | |||
goVersionOnce sync.Once | |||
goVersionError error | |||
goVersion string // third field of 'go version' | |||
// vendorDirs caches the (non)existence of vendor directories. | |||
vendorDirs map[string]bool | |||
} | |||
@@ -142,7 +146,7 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { | |||
sizeswg.Add(1) | |||
go func() { | |||
var sizes types.Sizes | |||
sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) | |||
sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.gocmdRunner, cfg.Dir) | |||
// types.SizesFor always returns nil or a *types.StdSizes. | |||
response.dr.Sizes, _ = sizes.(*types.StdSizes) | |||
sizeswg.Done() | |||
@@ -381,7 +385,7 @@ type jsonPackage struct { | |||
Imports []string | |||
ImportMap map[string]string | |||
Deps []string | |||
Module *packagesinternal.Module | |||
Module *Module | |||
TestGoFiles []string | |||
TestImports []string | |||
XTestGoFiles []string | |||
@@ -502,10 +506,19 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||
errkind = "use of internal package not allowed" | |||
} | |||
if errkind != "" { | |||
if len(old.Error.ImportStack) < 2 { | |||
return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack with fewer than two elements`, errkind) | |||
if len(old.Error.ImportStack) < 1 { | |||
return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind) | |||
} | |||
importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1] | |||
if importingPkg == old.ImportPath { | |||
// Using an older version of Go which put this package itself on top of import | |||
// stack, instead of the importer. Look for importer in second from top | |||
// position. | |||
if len(old.Error.ImportStack) < 2 { | |||
return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind) | |||
} | |||
importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2] | |||
} | |||
importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-2] | |||
additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ | |||
Pos: old.Error.Pos, | |||
Msg: old.Error.Err, | |||
@@ -531,7 +544,26 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), | |||
OtherFiles: absJoin(p.Dir, otherFiles(p)...), | |||
forTest: p.ForTest, | |||
module: p.Module, | |||
Module: p.Module, | |||
} | |||
if (state.cfg.Mode&typecheckCgo) != 0 && len(p.CgoFiles) != 0 { | |||
if len(p.CompiledGoFiles) > len(p.GoFiles) { | |||
// We need the cgo definitions, which are in the first | |||
// CompiledGoFile after the non-cgo ones. This is a hack but there | |||
// isn't currently a better way to find it. We also need the pure | |||
// Go files and unprocessed cgo files, all of which are already | |||
// in pkg.GoFiles. | |||
cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] | |||
pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) | |||
} else { | |||
// golang/go#38990: go list silently fails to do cgo processing | |||
pkg.CompiledGoFiles = nil | |||
pkg.Errors = append(pkg.Errors, Error{ | |||
Msg: "go list failed to return CompiledGoFiles; https://golang.org/issue/38990?", | |||
Kind: ListError, | |||
}) | |||
} | |||
} | |||
// Work around https://golang.org/issue/28749: | |||
@@ -607,6 +639,39 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||
pkg.CompiledGoFiles = pkg.GoFiles | |||
} | |||
// Temporary work-around for golang/go#39986. Parse filenames out of | |||
// error messages. This happens if there are unrecoverable syntax | |||
// errors in the source, so we can't match on a specific error message. | |||
if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) { | |||
addFilenameFromPos := func(pos string) bool { | |||
split := strings.Split(pos, ":") | |||
if len(split) < 1 { | |||
return false | |||
} | |||
filename := strings.TrimSpace(split[0]) | |||
if filename == "" { | |||
return false | |||
} | |||
if !filepath.IsAbs(filename) { | |||
filename = filepath.Join(state.cfg.Dir, filename) | |||
} | |||
info, _ := os.Stat(filename) | |||
if info == nil { | |||
return false | |||
} | |||
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename) | |||
pkg.GoFiles = append(pkg.GoFiles, filename) | |||
return true | |||
} | |||
found := addFilenameFromPos(err.Pos) | |||
// In some cases, go list only reports the error position in the | |||
// error text, not the error position. One such case is when the | |||
// file's package name is a keyword (see golang.org/issue/39763). | |||
if !found { | |||
addFilenameFromPos(err.Err) | |||
} | |||
} | |||
if p.Error != nil { | |||
msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363. | |||
// Address golang.org/issue/35964 by appending import stack to error message. | |||
@@ -636,6 +701,58 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse | |||
return &response, nil | |||
} | |||
func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool { | |||
if len(p.GoFiles) > 0 || len(p.CompiledGoFiles) > 0 { | |||
return false | |||
} | |||
goV, err := state.getGoVersion() | |||
if err != nil { | |||
return false | |||
} | |||
// On Go 1.14 and earlier, only add filenames from errors if the import stack is empty. | |||
// The import stack behaves differently for these versions than newer Go versions. | |||
if strings.HasPrefix(goV, "go1.13") || strings.HasPrefix(goV, "go1.14") { | |||
return len(p.Error.ImportStack) == 0 | |||
} | |||
// On Go 1.15 and later, only parse filenames out of error if there's no import stack, | |||
// or the current package is at the top of the import stack. This is not guaranteed | |||
// to work perfectly, but should avoid some cases where files in errors don't belong to this | |||
// package. | |||
return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath | |||
} | |||
func (state *golistState) getGoVersion() (string, error) { | |||
state.goVersionOnce.Do(func() { | |||
var b *bytes.Buffer | |||
// Invoke go version. Don't use invokeGo because it will supply build flags, and | |||
// go version doesn't expect build flags. | |||
inv := gocommand.Invocation{ | |||
Verb: "version", | |||
Env: state.cfg.Env, | |||
Logf: state.cfg.Logf, | |||
} | |||
gocmdRunner := state.cfg.gocmdRunner | |||
if gocmdRunner == nil { | |||
gocmdRunner = &gocommand.Runner{} | |||
} | |||
b, _, _, state.goVersionError = gocmdRunner.RunRaw(state.cfg.Context, inv) | |||
if state.goVersionError != nil { | |||
return | |||
} | |||
sp := strings.Split(b.String(), " ") | |||
if len(sp) < 3 { | |||
state.goVersionError = fmt.Errorf("go version output: expected 'go version <version>', got '%s'", b.String()) | |||
return | |||
} | |||
state.goVersion = sp[2] | |||
}) | |||
return state.goVersion, state.goVersionError | |||
} | |||
// getPkgPath finds the package path of a directory if it's relative to a root directory. | |||
func (state *golistState) getPkgPath(dir string) (string, bool, error) { | |||
absDir, err := filepath.Abs(dir) | |||
@@ -707,7 +824,7 @@ func golistargs(cfg *Config, words []string) []string { | |||
func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { | |||
cfg := state.cfg | |||
inv := &gocommand.Invocation{ | |||
inv := gocommand.Invocation{ | |||
Verb: verb, | |||
Args: args, | |||
BuildFlags: cfg.BuildFlags, | |||
@@ -715,8 +832,11 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, | |||
Logf: cfg.Logf, | |||
WorkingDir: cfg.Dir, | |||
} | |||
stdout, stderr, _, err := inv.RunRaw(cfg.Context) | |||
gocmdRunner := cfg.gocmdRunner | |||
if gocmdRunner == nil { | |||
gocmdRunner = &gocommand.Runner{} | |||
} | |||
stdout, stderr, _, err := gocmdRunner.RunRaw(cfg.Context, inv) | |||
if err != nil { | |||
// Check for 'go' executable not being found. | |||
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | |||
@@ -727,7 +847,7 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, | |||
if !ok { | |||
// Catastrophic error: | |||
// - context cancellation | |||
return nil, fmt.Errorf("couldn't run 'go': %v", err) | |||
return nil, xerrors.Errorf("couldn't run 'go': %w", err) | |||
} | |||
// Old go version? |
@@ -5,6 +5,7 @@ import ( | |||
"fmt" | |||
"go/parser" | |||
"go/token" | |||
"log" | |||
"os" | |||
"path/filepath" | |||
"sort" | |||
@@ -22,10 +23,15 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||
needPkgsSet := make(map[string]bool) | |||
modifiedPkgsSet := make(map[string]bool) | |||
pkgOfDir := make(map[string][]*Package) | |||
for _, pkg := range response.dr.Packages { | |||
// This is an approximation of import path to id. This can be | |||
// wrong for tests, vendored packages, and a number of other cases. | |||
havePkgs[pkg.PkgPath] = pkg.ID | |||
x := commonDir(pkg.GoFiles) | |||
if x != "" { | |||
pkgOfDir[x] = append(pkgOfDir[x], pkg) | |||
} | |||
} | |||
// If no new imports are added, it is safe to avoid loading any needPkgs. | |||
@@ -64,6 +70,9 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||
// to the overlay. | |||
continue | |||
} | |||
// If all the overlay files belong to a different package, change the | |||
// package name to that package. | |||
maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir]) | |||
nextPackage: | |||
for _, p := range response.dr.Packages { | |||
if pkgName != p.Name && p.ID != "command-line-arguments" { | |||
@@ -93,8 +102,11 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||
} | |||
} | |||
} | |||
// The overlay could have included an entirely new package. | |||
if pkg == nil { | |||
// The overlay could have included an entirely new package or an | |||
// ad-hoc package. An ad-hoc package is one that we have manually | |||
// constructed from inadequate `go list` results for a file= query. | |||
// It will have the ID command-line-arguments. | |||
if pkg == nil || pkg.ID == "command-line-arguments" { | |||
// Try to find the module or gopath dir the file is contained in. | |||
// Then for modules, add the module opath to the beginning. | |||
pkgPath, ok, err := state.getPkgPath(dir) | |||
@@ -104,34 +116,53 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||
if !ok { | |||
break | |||
} | |||
var forTest string // only set for x tests | |||
isXTest := strings.HasSuffix(pkgName, "_test") | |||
if isXTest { | |||
forTest = pkgPath | |||
pkgPath += "_test" | |||
} | |||
id := pkgPath | |||
if isTestFile && !isXTest { | |||
id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath) | |||
} | |||
// Try to reclaim a package with the same id if it exists in the response. | |||
for _, p := range response.dr.Packages { | |||
if reclaimPackage(p, id, opath, contents) { | |||
pkg = p | |||
break | |||
if isTestFile { | |||
if isXTest { | |||
id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest) | |||
} else { | |||
id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath) | |||
} | |||
} | |||
// Otherwise, create a new package | |||
if pkg == nil { | |||
pkg = &Package{PkgPath: pkgPath, ID: id, Name: pkgName, Imports: make(map[string]*Package)} | |||
response.addPackage(pkg) | |||
havePkgs[pkg.PkgPath] = id | |||
// Add the production package's sources for a test variant. | |||
if isTestFile && !isXTest && testVariantOf != nil { | |||
pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) | |||
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) | |||
// Add the package under test and its imports to the test variant. | |||
pkg.forTest = testVariantOf.PkgPath | |||
for k, v := range testVariantOf.Imports { | |||
pkg.Imports[k] = &Package{ID: v.ID} | |||
if pkg != nil { | |||
// TODO(rstambler): We should change the package's path and ID | |||
// here. The only issue is that this messes with the roots. | |||
} else { | |||
// Try to reclaim a package with the same ID, if it exists in the response. | |||
for _, p := range response.dr.Packages { | |||
if reclaimPackage(p, id, opath, contents) { | |||
pkg = p | |||
break | |||
} | |||
} | |||
// Otherwise, create a new package. | |||
if pkg == nil { | |||
pkg = &Package{ | |||
PkgPath: pkgPath, | |||
ID: id, | |||
Name: pkgName, | |||
Imports: make(map[string]*Package), | |||
} | |||
response.addPackage(pkg) | |||
havePkgs[pkg.PkgPath] = id | |||
// Add the production package's sources for a test variant. | |||
if isTestFile && !isXTest && testVariantOf != nil { | |||
pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) | |||
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) | |||
// Add the package under test and its imports to the test variant. | |||
pkg.forTest = testVariantOf.PkgPath | |||
for k, v := range testVariantOf.Imports { | |||
pkg.Imports[k] = &Package{ID: v.ID} | |||
} | |||
} | |||
if isXTest { | |||
pkg.forTest = forTest | |||
} | |||
} | |||
} | |||
@@ -149,6 +180,8 @@ func (state *golistState) processGolistOverlay(response *responseDeduper) (modif | |||
continue | |||
} | |||
for _, imp := range imports { | |||
// TODO(rstambler): If the package is an x test and the import has | |||
// a test variant, make sure to replace it. | |||
if _, found := pkg.Imports[imp]; found { | |||
continue | |||
} | |||
@@ -282,7 +315,17 @@ func (state *golistState) determineRootDirs() (map[string]string, error) { | |||
} | |||
func (state *golistState) determineRootDirsModules() (map[string]string, error) { | |||
out, err := state.invokeGo("list", "-m", "-json", "all") | |||
// This will only return the root directory for the main module. | |||
// For now we only support overlays in main modules. | |||
// Editing files in the module cache isn't a great idea, so we don't | |||
// plan to ever support that, but editing files in replaced modules | |||
// is something we may want to support. To do that, we'll want to | |||
// do a go list -m to determine the replaced module's module path and | |||
// directory, and then a go list -m {{with .Replace}}{{.Dir}}{{end}} <replaced module's path> | |||
// from the main module to determine if that module is actually a replacement. | |||
// See bcmills's comment here: https://github.com/golang/go/issues/37629#issuecomment-594179751 | |||
// for more information. | |||
out, err := state.invokeGo("list", "-m", "-json") | |||
if err != nil { | |||
return nil, err | |||
} | |||
@@ -374,3 +417,57 @@ func extractPackageName(filename string, contents []byte) (string, bool) { | |||
} | |||
return f.Name.Name, true | |||
} | |||
func commonDir(a []string) string { | |||
seen := make(map[string]bool) | |||
x := append([]string{}, a...) | |||
for _, f := range x { | |||
seen[filepath.Dir(f)] = true | |||
} | |||
if len(seen) > 1 { | |||
log.Fatalf("commonDir saw %v for %v", seen, x) | |||
} | |||
for k := range seen { | |||
// len(seen) == 1 | |||
return k | |||
} | |||
return "" // no files | |||
} | |||
// It is possible that the files in the disk directory dir have a different package | |||
// name from newName, which is deduced from the overlays. If they all have a different | |||
// package name, and they all have the same package name, then that name becomes | |||
// the package name. | |||
// It returns true if it changes the package name, false otherwise. | |||
func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) { | |||
names := make(map[string]int) | |||
for _, p := range pkgsOfDir { | |||
names[p.Name]++ | |||
} | |||
if len(names) != 1 { | |||
// some files are in different packages | |||
return | |||
} | |||
var oldName string | |||
for k := range names { | |||
oldName = k | |||
} | |||
if newName == oldName { | |||
return | |||
} | |||
// We might have a case where all of the package names in the directory are | |||
// the same, but the overlay file is for an x test, which belongs to its | |||
// own package. If the x test does not yet exist on disk, we may not yet | |||
// have its package name on disk, but we should not rename the packages. | |||
// | |||
// We use a heuristic to determine if this file belongs to an x test: | |||
// The test file should have a package name whose package name has a _test | |||
// suffix or looks like "newName_test". | |||
maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test") | |||
if isTestFile && maybeXTest { | |||
return | |||
} | |||
for _, p := range pkgsOfDir { | |||
p.Name = newName | |||
} | |||
} |
@@ -38,7 +38,7 @@ var modeStrings = []string{ | |||
func (mod LoadMode) String() string { | |||
m := mod | |||
if m == 0 { | |||
return fmt.Sprintf("LoadMode(0)") | |||
return "LoadMode(0)" | |||
} | |||
var out []string | |||
for i, x := range allModes { |
@@ -21,9 +21,12 @@ import ( | |||
"path/filepath" | |||
"strings" | |||
"sync" | |||
"time" | |||
"golang.org/x/tools/go/gcexportdata" | |||
"golang.org/x/tools/internal/gocommand" | |||
"golang.org/x/tools/internal/packagesinternal" | |||
"golang.org/x/tools/internal/typesinternal" | |||
) | |||
// A LoadMode controls the amount of detail to return when loading. | |||
@@ -69,6 +72,13 @@ const ( | |||
// NeedTypesSizes adds TypesSizes. | |||
NeedTypesSizes | |||
// typecheckCgo enables full support for type checking cgo. Requires Go 1.15+. | |||
// Modifies CompiledGoFiles and Types, and has no effect on its own. | |||
typecheckCgo | |||
// NeedModule adds Module. | |||
NeedModule | |||
) | |||
const ( | |||
@@ -127,6 +137,9 @@ type Config struct { | |||
// | |||
Env []string | |||
// gocmdRunner guards go command calls from concurrency errors. | |||
gocmdRunner *gocommand.Runner | |||
// BuildFlags is a list of command-line flags to be passed through to | |||
// the build system's query tool. | |||
BuildFlags []string | |||
@@ -178,6 +191,13 @@ type driver func(cfg *Config, patterns ...string) (*driverResponse, error) | |||
// driverResponse contains the results for a driver query. | |||
type driverResponse struct { | |||
// NotHandled is returned if the request can't be handled by the current | |||
// driver. If an external driver returns a response with NotHandled, the | |||
// rest of the driverResponse is ignored, and go/packages will fallback | |||
// to the next driver. If go/packages is extended in the future to support | |||
// lists of multiple drivers, go/packages will fall back to the next driver. | |||
NotHandled bool | |||
// Sizes, if not nil, is the types.Sizes to use when type checking. | |||
Sizes *types.StdSizes | |||
@@ -219,14 +239,22 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { | |||
return l.refine(response.Roots, response.Packages...) | |||
} | |||
// defaultDriver is a driver that looks for an external driver binary, and if | |||
// it does not find it falls back to the built in go list driver. | |||
// defaultDriver is a driver that implements go/packages' fallback behavior. | |||
// It will try to request to an external driver, if one exists. If there's | |||
// no external driver, or the driver returns a response with NotHandled set, | |||
// defaultDriver will fall back to the go list driver. | |||
func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) { | |||
driver := findExternalDriver(cfg) | |||
if driver == nil { | |||
driver = goListDriver | |||
} | |||
return driver(cfg, patterns...) | |||
response, err := driver(cfg, patterns...) | |||
if err != nil { | |||
return response, err | |||
} else if response.NotHandled { | |||
return goListDriver(cfg, patterns...) | |||
} | |||
return response, nil | |||
} | |||
// A Package describes a loaded Go package. | |||
@@ -253,7 +281,7 @@ type Package struct { | |||
GoFiles []string | |||
// CompiledGoFiles lists the absolute file paths of the package's source | |||
// files that were presented to the compiler. | |||
// files that are suitable for type checking. | |||
// This may differ from GoFiles if files are processed before compilation. | |||
CompiledGoFiles []string | |||
@@ -301,16 +329,39 @@ type Package struct { | |||
forTest string | |||
// module is the module information for the package if it exists. | |||
module *packagesinternal.Module | |||
Module *Module | |||
} | |||
// Module provides module information for a package. | |||
type Module struct { | |||
Path string // module path | |||
Version string // module version | |||
Replace *Module // replaced by this module | |||
Time *time.Time // time version was created | |||
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 | |||
} | |||
// ModuleError holds errors loading a module. | |||
type ModuleError struct { | |||
Err string // the error itself | |||
} | |||
func init() { | |||
packagesinternal.GetForTest = func(p interface{}) string { | |||
return p.(*Package).forTest | |||
} | |||
packagesinternal.GetModule = func(p interface{}) *packagesinternal.Module { | |||
return p.(*Package).module | |||
packagesinternal.GetGoCmdRunner = func(config interface{}) *gocommand.Runner { | |||
return config.(*Config).gocmdRunner | |||
} | |||
packagesinternal.SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) { | |||
config.(*Config).gocmdRunner = runner | |||
} | |||
packagesinternal.TypecheckCgo = int(typecheckCgo) | |||
} | |||
// An Error describes a problem with a package's metadata, syntax, or types. | |||
@@ -473,6 +524,9 @@ func newLoader(cfg *Config) *loader { | |||
if ld.Config.Env == nil { | |||
ld.Config.Env = os.Environ() | |||
} | |||
if ld.Config.gocmdRunner == nil { | |||
ld.Config.gocmdRunner = &gocommand.Runner{} | |||
} | |||
if ld.Context == nil { | |||
ld.Context = context.Background() | |||
} | |||
@@ -690,6 +744,9 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { | |||
if ld.requestedMode&NeedTypesSizes == 0 { | |||
ld.pkgs[i].TypesSizes = nil | |||
} | |||
if ld.requestedMode&NeedModule == 0 { | |||
ld.pkgs[i].Module = nil | |||
} | |||
} | |||
return result, nil | |||
@@ -865,6 +922,15 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { | |||
Error: appendError, | |||
Sizes: ld.sizes, | |||
} | |||
if (ld.Mode & typecheckCgo) != 0 { | |||
if !typesinternal.SetUsesCgo(tc) { | |||
appendError(Error{ | |||
Msg: "typecheckCgo requires Go 1.15+", | |||
Kind: ListError, | |||
}) | |||
return | |||
} | |||
} | |||
types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) | |||
lpkg.importErrors = nil // no longer needed |
@@ -226,7 +226,8 @@ func For(obj types.Object) (Path, error) { | |||
// the best paths because non-types may | |||
// refer to types, but not the reverse. | |||
empty := make([]byte, 0, 48) // initial space | |||
for _, name := range scope.Names() { | |||
names := scope.Names() | |||
for _, name := range names { | |||
o := scope.Lookup(name) | |||
tname, ok := o.(*types.TypeName) | |||
if !ok { | |||
@@ -253,7 +254,7 @@ func For(obj types.Object) (Path, error) { | |||
// Then inspect everything else: | |||
// non-types, and declared methods of defined types. | |||
for _, name := range scope.Names() { | |||
for _, name := range names { | |||
o := scope.Lookup(name) | |||
path := append(empty, name...) | |||
if _, ok := o.(*types.TypeName); !ok { |
@@ -3,10 +3,10 @@ | |||
package imports // import "golang.org/x/tools/imports" | |||
import ( | |||
"go/build" | |||
"io/ioutil" | |||
"log" | |||
"os" | |||
"golang.org/x/tools/internal/gocommand" | |||
intimp "golang.org/x/tools/internal/imports" | |||
) | |||
@@ -31,31 +31,34 @@ var Debug = false | |||
var LocalPrefix string | |||
// Process formats and adjusts imports for the provided file. | |||
// If opt is nil the defaults are used. | |||
// If opt is nil the defaults are used, and if src is nil the source | |||
// is read from the filesystem. | |||
// | |||
// Note that filename's directory influences which imports can be chosen, | |||
// so it is important that filename be accurate. | |||
// To process data ``as if'' it were in filename, pass the data as a non-nil src. | |||
func Process(filename string, src []byte, opt *Options) ([]byte, error) { | |||
var err error | |||
if src == nil { | |||
src, err = ioutil.ReadFile(filename) | |||
if err != nil { | |||
return nil, err | |||
} | |||
} | |||
if opt == nil { | |||
opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} | |||
} | |||
intopt := &intimp.Options{ | |||
Env: &intimp.ProcessEnv{ | |||
GOPATH: build.Default.GOPATH, | |||
GOROOT: build.Default.GOROOT, | |||
GOFLAGS: os.Getenv("GOFLAGS"), | |||
GO111MODULE: os.Getenv("GO111MODULE"), | |||
GOPROXY: os.Getenv("GOPROXY"), | |||
GOSUMDB: os.Getenv("GOSUMDB"), | |||
LocalPrefix: LocalPrefix, | |||
GocmdRunner: &gocommand.Runner{}, | |||
}, | |||
AllErrors: opt.AllErrors, | |||
Comments: opt.Comments, | |||
FormatOnly: opt.FormatOnly, | |||
Fragment: opt.Fragment, | |||
TabIndent: opt.TabIndent, | |||
TabWidth: opt.TabWidth, | |||
LocalPrefix: LocalPrefix, | |||
AllErrors: opt.AllErrors, | |||
Comments: opt.Comments, | |||
FormatOnly: opt.FormatOnly, | |||
Fragment: opt.Fragment, | |||
TabIndent: opt.TabIndent, | |||
TabWidth: opt.TabWidth, | |||
} | |||
if Debug { | |||
intopt.Env.Logf = log.Printf |
@@ -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} | |||
} |
@@ -1,3 +1,7 @@ | |||
// 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 is a helper for calling the go command. | |||
package gocommand | |||
@@ -8,10 +12,119 @@ import ( | |||
"io" | |||
"os" | |||
"os/exec" | |||
"regexp" | |||
"strings" | |||
"sync" | |||
"time" | |||
"golang.org/x/tools/internal/event" | |||
) | |||
// An Runner will run go command invocations and serialize | |||
// them if it sees a concurrency error. | |||
type Runner struct { | |||
// once guards the runner initialization. | |||
once sync.Once | |||
// inFlight tracks available workers. | |||
inFlight chan struct{} | |||
// serialized guards the ability to run a go command serially, | |||
// to avoid deadlocks when claiming workers. | |||
serialized chan struct{} | |||
} | |||
const maxInFlight = 10 | |||
func (runner *Runner) initialize() { | |||
runner.once.Do(func() { | |||
runner.inFlight = make(chan struct{}, maxInFlight) | |||
runner.serialized = make(chan struct{}, 1) | |||
}) | |||
} | |||
// 1.13: go: updates to go.mod needed, but contents have changed | |||
// 1.14: go: updating go.mod: existing contents have changed since last read | |||
var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) | |||
// Run is a convenience wrapper around RunRaw. | |||
// It returns only stdout and a "friendly" error. | |||
func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { | |||
stdout, _, friendly, _ := runner.RunRaw(ctx, inv) | |||
return stdout, friendly | |||
} | |||
// RunPiped runs the invocation serially, always waiting for any concurrent | |||
// invocations to complete first. | |||
func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error { | |||
_, err := runner.runPiped(ctx, inv, stdout, stderr) | |||
return err | |||
} | |||
// RunRaw runs the invocation, serializing requests only if they fight over | |||
// go.mod changes. | |||
func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { | |||
// Make sure the runner is always initialized. | |||
runner.initialize() | |||
// First, try to run the go command concurrently. | |||
stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv) | |||
// If we encounter a load concurrency error, we need to retry serially. | |||
if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) { | |||
return stdout, stderr, friendlyErr, err | |||
} | |||
event.Error(ctx, "Load concurrency error, will retry serially", err) | |||
// Run serially by calling runPiped. | |||
stdout.Reset() | |||
stderr.Reset() | |||
friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) | |||
return stdout, stderr, friendlyErr, err | |||
} | |||
func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { | |||
// Wait for 1 worker to become available. | |||
select { | |||
case <-ctx.Done(): | |||
return nil, nil, nil, ctx.Err() | |||
case runner.inFlight <- struct{}{}: | |||
defer func() { <-runner.inFlight }() | |||
} | |||
stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} | |||
friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr) | |||
return stdout, stderr, friendlyErr, err | |||
} | |||
func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) { | |||
// Make sure the runner is always initialized. | |||
runner.initialize() | |||
// Acquire the serialization lock. This avoids deadlocks between two | |||
// runPiped commands. | |||
select { | |||
case <-ctx.Done(): | |||
return nil, ctx.Err() | |||
case runner.serialized <- struct{}{}: | |||
defer func() { <-runner.serialized }() | |||
} | |||
// Wait for all in-progress go commands to return before proceeding, | |||
// to avoid load concurrency errors. | |||
for i := 0; i < maxInFlight; i++ { | |||
select { | |||
case <-ctx.Done(): | |||
return nil, ctx.Err() | |||
case runner.inFlight <- struct{}{}: | |||
// Make sure we always "return" any workers we took. | |||
defer func() { <-runner.inFlight }() | |||
} | |||
} | |||
return inv.runWithFriendlyError(ctx, stdout, stderr) | |||
} | |||
// An Invocation represents a call to the go command. | |||
type Invocation struct { | |||
Verb string | |||
@@ -22,20 +135,10 @@ type Invocation struct { | |||
Logf func(format string, args ...interface{}) | |||
} | |||
// Run runs the invocation, returning its stdout and an error suitable for | |||
// human consumption, including stderr. | |||
func (i *Invocation) Run(ctx context.Context) (*bytes.Buffer, error) { | |||
stdout, _, friendly, _ := i.RunRaw(ctx) | |||
return stdout, friendly | |||
} | |||
// RunRaw is like RunPiped, but also returns the raw stderr and error for callers | |||
// that want to do low-level error handling/recovery. | |||
func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) { | |||
stdout = &bytes.Buffer{} | |||
stderr = &bytes.Buffer{} | |||
rawError = i.RunPiped(ctx, stdout, stderr) | |||
func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) { | |||
rawError = i.run(ctx, stdout, stderr) | |||
if rawError != nil { | |||
friendlyError = rawError | |||
// Check for 'go' executable not being found. | |||
if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { | |||
friendlyError = fmt.Errorf("go command required, not found: %v", ee) | |||
@@ -43,13 +146,12 @@ func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr * | |||
if ctx.Err() != nil { | |||
friendlyError = ctx.Err() | |||
} | |||
friendlyError = fmt.Errorf("err: %v: stderr: %s", rawError, stderr) | |||
friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) | |||
} | |||
return | |||
} | |||
// RunPiped is like Run, but relies on the given stdout/stderr | |||
func (i *Invocation) RunPiped(ctx context.Context, stdout, stderr io.Writer) error { | |||
func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { | |||
log := i.Logf | |||
if log == nil { | |||
log = func(string, ...interface{}) {} | |||
@@ -78,9 +180,11 @@ func (i *Invocation) RunPiped(ctx context.Context, stdout, stderr io.Writer) err | |||
// The Go stdlib has a special feature where if the cwd and the PWD are the | |||
// same node then it trusts the PWD, so by setting it in the env for the child | |||
// process we fix up all the paths returned by the go command. | |||
cmd.Env = append(append([]string{}, i.Env...), "PWD="+i.WorkingDir) | |||
cmd.Dir = i.WorkingDir | |||
cmd.Env = append(os.Environ(), i.Env...) | |||
if i.WorkingDir != "" { | |||
cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) | |||
cmd.Dir = i.WorkingDir | |||
} | |||
defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) | |||
return runCmdContext(ctx, cmd) |
@@ -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 | |||
} |
@@ -10,7 +10,6 @@ import ( | |||
"bufio" | |||
"bytes" | |||
"fmt" | |||
"go/build" | |||
"io/ioutil" | |||
"log" | |||
"os" | |||
@@ -47,16 +46,6 @@ type Root struct { | |||
Type RootType | |||
} | |||
// SrcDirsRoots returns the roots from build.Default.SrcDirs(). Not modules-compatible. | |||
func SrcDirsRoots(ctx *build.Context) []Root { | |||
var roots []Root | |||
roots = append(roots, Root{filepath.Join(ctx.GOROOT, "src"), RootGOROOT}) | |||
for _, p := range filepath.SplitList(ctx.GOPATH) { | |||
roots = append(roots, Root{filepath.Join(p, "src"), RootGOPATH}) | |||
} | |||
return roots | |||
} | |||
// Walk walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. | |||
// For each package found, add will be called (concurrently) with the absolute | |||
// paths of the containing source directory and the package directory. |
@@ -7,6 +7,7 @@ package imports | |||
import ( | |||
"bytes" | |||
"context" | |||
"encoding/json" | |||
"fmt" | |||
"go/ast" | |||
"go/build" | |||
@@ -31,35 +32,36 @@ import ( | |||
// importToGroup is a list of functions which map from an import path to | |||
// a group number. | |||
var importToGroup = []func(env *ProcessEnv, importPath string) (num int, ok bool){ | |||
func(env *ProcessEnv, importPath string) (num int, ok bool) { | |||
if env.LocalPrefix == "" { | |||
var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){ | |||
func(localPrefix, importPath string) (num int, ok bool) { | |||
if localPrefix == "" { | |||
return | |||
} | |||
for _, p := range strings.Split(env.LocalPrefix, ",") { | |||
for _, p := range strings.Split(localPrefix, ",") { | |||
if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath { | |||
return 3, true | |||
} | |||
} | |||
return | |||
}, | |||
func(_ *ProcessEnv, importPath string) (num int, ok bool) { | |||
func(_, importPath string) (num int, ok bool) { | |||
if strings.HasPrefix(importPath, "appengine") { | |||
return 2, true | |||
} | |||
return | |||
}, | |||
func(_ *ProcessEnv, importPath string) (num int, ok bool) { | |||
if strings.Contains(importPath, ".") { | |||
func(_, importPath string) (num int, ok bool) { | |||
firstComponent := strings.Split(importPath, "/")[0] | |||
if strings.Contains(firstComponent, ".") { | |||
return 1, true | |||
} | |||
return | |||
}, | |||
} | |||
func importGroup(env *ProcessEnv, importPath string) int { | |||
func importGroup(localPrefix, importPath string) int { | |||
for _, fn := range importToGroup { | |||
if n, ok := fn(env, importPath); ok { | |||
if n, ok := fn(localPrefix, importPath); ok { | |||
return n | |||
} | |||
} | |||
@@ -276,7 +278,12 @@ func (p *pass) loadPackageNames(imports []*ImportInfo) error { | |||
unknown = append(unknown, imp.ImportPath) | |||
} | |||
names, err := p.env.GetResolver().loadPackageNames(unknown, p.srcDir) | |||
resolver, err := p.env.GetResolver() | |||
if err != nil { | |||
return err | |||
} | |||
names, err := resolver.loadPackageNames(unknown, p.srcDir) | |||
if err != nil { | |||
return err | |||
} | |||
@@ -566,7 +573,9 @@ func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv | |||
return fixes, nil | |||
} | |||
addStdlibCandidates(p, p.missingRefs) | |||
if err := addStdlibCandidates(p, p.missingRefs); err != nil { | |||
return nil, err | |||
} | |||
p.assumeSiblingImportsValid() | |||
if fixes, done := p.fix(); done { | |||
return fixes, nil | |||
@@ -594,10 +603,14 @@ func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filena | |||
notSelf := func(p *pkg) bool { | |||
return p.packageName != filePkg || p.dir != filepath.Dir(filename) | |||
} | |||
goenv, err := env.goEnv() | |||
if err != nil { | |||
return err | |||
} | |||
// Start off with the standard library. | |||
for importPath, exports := range stdlib { | |||
p := &pkg{ | |||
dir: filepath.Join(env.GOROOT, "src", importPath), | |||
dir: filepath.Join(goenv["GOROOT"], "src", importPath), | |||
importPathShort: importPath, | |||
packageName: path.Base(importPath), | |||
relevance: MaxRelevance, | |||
@@ -638,15 +651,23 @@ func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filena | |||
wrappedCallback.exportsLoaded(pkg, exports) | |||
}, | |||
} | |||
return env.GetResolver().scan(ctx, scanFilter) | |||
resolver, err := env.GetResolver() | |||
if err != nil { | |||
return err | |||
} | |||
return resolver.scan(ctx, scanFilter) | |||
} | |||
func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) map[string]int { | |||
func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]int, error) { | |||
result := make(map[string]int) | |||
resolver, err := env.GetResolver() | |||
if err != nil { | |||
return nil, err | |||
} | |||
for _, path := range paths { | |||
result[path] = env.GetResolver().scoreImportPath(ctx, path) | |||
result[path] = resolver.scoreImportPath(ctx, path) | |||
} | |||
return result | |||
return result, nil | |||
} | |||
func PrimeCache(ctx context.Context, env *ProcessEnv) error { | |||
@@ -672,8 +693,9 @@ func candidateImportName(pkg *pkg) string { | |||
return "" | |||
} | |||
// getAllCandidates gets all of the candidates to be imported, regardless of if they are needed. | |||
func getAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { | |||
// GetAllCandidates gets all of the packages starting with prefix that can be | |||
// imported by filename, sorted by import path. | |||
func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { | |||
callback := &scanCallback{ | |||
rootFound: func(gopathwalk.Root) bool { | |||
return true | |||
@@ -712,7 +734,8 @@ type PackageExport struct { | |||
Exports []string | |||
} | |||
func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error { | |||
// GetPackageExports returns all known packages with name pkg and their exports. | |||
func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error { | |||
callback := &scanCallback{ | |||
rootFound: func(gopathwalk.Root) bool { | |||
return true | |||
@@ -742,67 +765,126 @@ func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchP | |||
return getCandidatePkgs(ctx, callback, filename, filePkg, env) | |||
} | |||
var RequiredGoEnvVars = []string{"GO111MODULE", "GOFLAGS", "GOINSECURE", "GOMOD", "GOMODCACHE", "GONOPROXY", "GONOSUMDB", "GOPATH", "GOPROXY", "GOROOT", "GOSUMDB"} | |||
// ProcessEnv contains environment variables and settings that affect the use of | |||
// the go command, the go/build package, etc. | |||
type ProcessEnv struct { | |||
LocalPrefix string | |||
GocmdRunner *gocommand.Runner | |||
BuildFlags []string | |||
// If non-empty, these will be used instead of the | |||
// process-wide values. | |||
GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS, GOSUMDB string | |||
WorkingDir string | |||
// Env overrides the OS environment, and can be used to specify | |||
// GOPROXY, GO111MODULE, etc. PATH cannot be set here, because | |||
// exec.Command will not honor it. | |||
// Specifying all of RequiredGoEnvVars avoids a call to `go env`. | |||
Env map[string]string | |||
WorkingDir string | |||
// If Logf is non-nil, debug logging is enabled through this function. | |||
Logf func(format string, args ...interface{}) | |||
initialized bool | |||
resolver Resolver | |||
} | |||
func (e *ProcessEnv) goEnv() (map[string]string, error) { | |||
if err := e.init(); err != nil { | |||
return nil, err | |||
} | |||
return e.Env, nil | |||
} | |||
func (e *ProcessEnv) matchFile(dir, name string) (bool, error) { | |||
return build.Default.MatchFile(dir, name) | |||
} | |||
// CopyConfig copies the env's configuration into a new env. | |||
func (e *ProcessEnv) CopyConfig() *ProcessEnv { | |||
copy := *e | |||
copy.resolver = nil | |||
return © | |||
copy := &ProcessEnv{ | |||
GocmdRunner: e.GocmdRunner, | |||
initialized: e.initialized, | |||
BuildFlags: e.BuildFlags, | |||
Logf: e.Logf, | |||
WorkingDir: e.WorkingDir, | |||
resolver: nil, | |||
Env: map[string]string{}, | |||
} | |||
for k, v := range e.Env { | |||
copy.Env[k] = v | |||
} | |||
return copy | |||
} | |||
func (e *ProcessEnv) env() []string { | |||
env := os.Environ() | |||
add := func(k, v string) { | |||
if v != "" { | |||
env = append(env, k+"="+v) | |||
func (e *ProcessEnv) init() error { | |||
if e.initialized { | |||
return nil | |||
} | |||
foundAllRequired := true | |||
for _, k := range RequiredGoEnvVars { | |||
if _, ok := e.Env[k]; !ok { | |||
foundAllRequired = false | |||
break | |||
} | |||
} | |||
add("GOPATH", e.GOPATH) | |||
add("GOROOT", e.GOROOT) | |||
add("GO111MODULE", e.GO111MODULE) | |||
add("GOPROXY", e.GOPROXY) | |||
add("GOFLAGS", e.GOFLAGS) | |||
add("GOSUMDB", e.GOSUMDB) | |||
if e.WorkingDir != "" { | |||
add("PWD", e.WorkingDir) | |||
if foundAllRequired { | |||
e.initialized = true | |||
return nil | |||
} | |||
if e.Env == nil { | |||
e.Env = map[string]string{} | |||
} | |||
goEnv := map[string]string{} | |||
stdout, err := e.invokeGo(context.TODO(), "env", append([]string{"-json"}, RequiredGoEnvVars...)...) | |||
if err != nil { | |||
return err | |||
} | |||
if err := json.Unmarshal(stdout.Bytes(), &goEnv); err != nil { | |||
return err | |||
} | |||
for k, v := range goEnv { | |||
e.Env[k] = v | |||
} | |||
e.initialized = true | |||
return nil | |||
} | |||
func (e *ProcessEnv) env() []string { | |||
var env []string // the gocommand package will prepend os.Environ. | |||
for k, v := range e.Env { | |||
env = append(env, k+"="+v) | |||
} | |||
return env | |||
} | |||
func (e *ProcessEnv) GetResolver() Resolver { | |||
func (e *ProcessEnv) GetResolver() (Resolver, error) { | |||
if e.resolver != nil { | |||
return e.resolver | |||
return e.resolver, nil | |||
} | |||
out, err := e.invokeGo(context.TODO(), "env", "GOMOD") | |||
if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 { | |||
if err := e.init(); err != nil { | |||
return nil, err | |||
} | |||
if len(e.Env["GOMOD"]) == 0 { | |||
e.resolver = newGopathResolver(e) | |||
return e.resolver | |||
return e.resolver, nil | |||
} | |||
e.resolver = newModuleResolver(e) | |||
return e.resolver | |||
return e.resolver, nil | |||
} | |||
func (e *ProcessEnv) buildContext() *build.Context { | |||
func (e *ProcessEnv) buildContext() (*build.Context, error) { | |||
ctx := build.Default | |||
ctx.GOROOT = e.GOROOT | |||
ctx.GOPATH = e.GOPATH | |||
goenv, err := e.goEnv() | |||
if err != nil { | |||
return nil, err | |||
} | |||
ctx.GOROOT = goenv["GOROOT"] | |||
ctx.GOPATH = goenv["GOPATH"] | |||
// As of Go 1.14, build.Context has a Dir field | |||
// (see golang.org/issue/34860). | |||
@@ -818,7 +900,7 @@ func (e *ProcessEnv) buildContext() *build.Context { | |||
dir.SetString(e.WorkingDir) | |||
} | |||
return &ctx | |||
return &ctx, nil | |||
} | |||
func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) { | |||
@@ -830,13 +912,17 @@ func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) | |||
Logf: e.Logf, | |||
WorkingDir: e.WorkingDir, | |||
} | |||
return inv.Run(ctx) | |||
return e.GocmdRunner.Run(ctx, inv) | |||
} | |||
func addStdlibCandidates(pass *pass, refs references) { | |||
func addStdlibCandidates(pass *pass, refs references) error { | |||
goenv, err := pass.env.goEnv() | |||
if err != nil { | |||
return err | |||
} | |||
add := func(pkg string) { | |||
// Prevent self-imports. | |||
if path.Base(pkg) == pass.f.Name.Name && filepath.Join(pass.env.GOROOT, "src", pkg) == pass.srcDir { | |||
if path.Base(pkg) == pass.f.Name.Name && filepath.Join(goenv["GOROOT"], "src", pkg) == pass.srcDir { | |||
return | |||
} | |||
exports := copyExports(stdlib[pkg]) | |||
@@ -857,6 +943,7 @@ func addStdlibCandidates(pass *pass, refs references) { | |||
} | |||
} | |||
} | |||
return nil | |||
} | |||
// A Resolver does the build-system-specific parts of goimports. | |||
@@ -921,10 +1008,13 @@ func addExternalCandidates(pass *pass, refs references, filename string) error { | |||
return false // We'll do our own loading after we sort. | |||
}, | |||
} | |||
err := pass.env.GetResolver().scan(context.Background(), callback) | |||
resolver, err := pass.env.GetResolver() | |||
if err != nil { | |||
return err | |||
} | |||
if err = resolver.scan(context.Background(), callback); err != nil { | |||
return err | |||
} | |||
// Search for imports matching potential package references. | |||
type result struct { | |||
@@ -1050,21 +1140,24 @@ func (r *gopathResolver) ClearForNewScan() { | |||
func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { | |||
names := map[string]string{} | |||
bctx, err := r.env.buildContext() | |||
if err != nil { | |||
return nil, err | |||
} | |||
for _, path := range importPaths { | |||
names[path] = importPathToName(r.env, path, srcDir) | |||
names[path] = importPathToName(bctx, path, srcDir) | |||
} | |||
return names, nil | |||
} | |||
// importPathToName finds out the actual package name, as declared in its .go files. | |||
// If there's a problem, it returns "". | |||
func importPathToName(env *ProcessEnv, importPath, srcDir string) (packageName string) { | |||
func importPathToName(bctx *build.Context, importPath, srcDir string) string { | |||
// Fast path for standard library without going to disk. | |||
if _, ok := stdlib[importPath]; ok { | |||
return path.Base(importPath) // stdlib packages always match their paths. | |||
} | |||
buildPkg, err := env.buildContext().Import(importPath, srcDir, build.FindOnly) | |||
buildPkg, err := bctx.Import(importPath, srcDir, build.FindOnly) | |||
if err != nil { | |||
return "" | |||
} | |||
@@ -1225,8 +1318,18 @@ func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error | |||
} | |||
stop := r.cache.ScanAndListen(ctx, processDir) | |||
defer stop() | |||
goenv, err := r.env.goEnv() | |||
if err != nil { | |||
return err | |||
} | |||
var roots []gopathwalk.Root | |||
roots = append(roots, gopathwalk.Root{filepath.Join(goenv["GOROOT"], "src"), gopathwalk.RootGOROOT}) | |||
for _, p := range filepath.SplitList(goenv["GOPATH"]) { | |||
roots = append(roots, gopathwalk.Root{filepath.Join(p, "src"), gopathwalk.RootGOPATH}) | |||
} | |||
// The callback is not necessarily safe to use in the goroutine below. Process roots eagerly. | |||
roots := filterRoots(gopathwalk.SrcDirsRoots(r.env.buildContext()), callback.rootFound) | |||
roots = filterRoots(roots, callback.rootFound) | |||
// We can't cancel walks, because we need them to finish to have a usable | |||
// cache. Instead, run them in a separate goroutine and detach. | |||
scanDone := make(chan struct{}) | |||
@@ -1286,8 +1389,6 @@ func VendorlessPath(ipath string) string { | |||
} | |||
func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []string, error) { | |||
var exports []string | |||
// Look for non-test, buildable .go files which could provide exports. | |||
all, err := ioutil.ReadDir(dir) | |||
if err != nil { | |||
@@ -1299,7 +1400,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, incl | |||
if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) { | |||
continue | |||
} | |||
match, err := env.buildContext().MatchFile(dir, fi.Name()) | |||
match, err := env.matchFile(dir, fi.Name()) | |||
if err != nil || !match { | |||
continue | |||
} | |||
@@ -1311,6 +1412,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, incl | |||
} | |||
var pkgName string | |||
var exports []string | |||
fset := token.NewFileSet() | |||
for _, fi := range files { | |||
select { | |||
@@ -1322,7 +1424,10 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, incl | |||
fullFile := filepath.Join(dir, fi.Name()) | |||
f, err := parser.ParseFile(fset, fullFile, nil, 0) | |||
if err != nil { | |||
return "", nil, fmt.Errorf("parsing %s: %v", fullFile, err) | |||
if env.Logf != nil { | |||
env.Logf("error parsing %v: %v", fullFile, err) | |||
} | |||
continue | |||
} | |||
if f.Name.Name == "documentation" { | |||
// Special case from go/build.ImportDir, not | |||
@@ -1362,6 +1467,10 @@ func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgNa | |||
pass.env.Logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir) | |||
} | |||
} | |||
resolver, err := pass.env.GetResolver() | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Collect exports for packages with matching names. | |||
rescv := make([]chan *pkg, len(candidates)) | |||
@@ -1400,7 +1509,7 @@ func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgNa | |||
} | |||
// If we're an x_test, load the package under test's test variant. | |||
includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir | |||
_, exports, err := pass.env.GetResolver().loadExports(ctx, c.pkg, includeTest) | |||
_, exports, err := resolver.loadExports(ctx, c.pkg, includeTest) | |||
if err != nil { | |||
if pass.env.Logf != nil { | |||
pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) |
@@ -11,17 +11,13 @@ package imports | |||
import ( | |||
"bufio" | |||
"bytes" | |||
"context" | |||
"fmt" | |||
"go/ast" | |||
"go/build" | |||
"go/format" | |||
"go/parser" | |||
"go/printer" | |||
"go/token" | |||
"io" | |||
"io/ioutil" | |||
"os" | |||
"regexp" | |||
"strconv" | |||
"strings" | |||
@@ -33,6 +29,11 @@ import ( | |||
type Options struct { | |||
Env *ProcessEnv // The environment to use. Note: this contains the cached module and filesystem state. | |||
// LocalPrefix is a comma-separated string of import path prefixes, which, if | |||
// set, instructs Process to sort the import paths with the given prefixes | |||
// into another group after 3rd-party packages. | |||
LocalPrefix string | |||
Fragment bool // Accept fragment of a source file (no package statement) | |||
AllErrors bool // Report all errors (not just the first 10 on different lines) | |||
@@ -43,13 +44,8 @@ type Options struct { | |||
FormatOnly bool // Disable the insertion and deletion of imports | |||
} | |||
// Process implements golang.org/x/tools/imports.Process with explicit context in env. | |||
// Process implements golang.org/x/tools/imports.Process with explicit context in opt.Env. | |||
func Process(filename string, src []byte, opt *Options) (formatted []byte, err error) { | |||
src, opt, err = initialize(filename, src, opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
fileSet := token.NewFileSet() | |||
file, adjust, err := parse(fileSet, filename, src, opt) | |||
if err != nil { | |||
@@ -65,16 +61,12 @@ func Process(filename string, src []byte, opt *Options) (formatted []byte, err e | |||
} | |||
// FixImports returns a list of fixes to the imports that, when applied, | |||
// will leave the imports in the same state as Process. | |||
// will leave the imports in the same state as Process. src and opt must | |||
// be specified. | |||
// | |||
// Note that filename's directory influences which imports can be chosen, | |||
// so it is important that filename be accurate. | |||
func FixImports(filename string, src []byte, opt *Options) (fixes []*ImportFix, err error) { | |||
src, opt, err = initialize(filename, src, opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
fileSet := token.NewFileSet() | |||
file, _, err := parse(fileSet, filename, src, opt) | |||
if err != nil { | |||
@@ -85,13 +77,9 @@ func FixImports(filename string, src []byte, opt *Options) (fixes []*ImportFix, | |||
} | |||
// ApplyFixes applies all of the fixes to the file and formats it. extraMode | |||
// is added in when parsing the file. | |||
// is added in when parsing the file. src and opts must be specified, but no | |||
// env is needed. | |||
func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, extraMode parser.Mode) (formatted []byte, err error) { | |||
src, opt, err = initialize(filename, src, opt) | |||
if err != nil { | |||
return nil, err | |||
} | |||
// Don't use parse() -- we don't care about fragments or statement lists | |||
// here, and we need to work with unparseable files. | |||
fileSet := token.NewFileSet() | |||
@@ -115,59 +103,9 @@ func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, e | |||
return formatFile(fileSet, file, src, nil, opt) | |||
} | |||
// GetAllCandidates gets all of the packages starting with prefix that can be | |||
// imported by filename, sorted by import path. | |||
func GetAllCandidates(ctx context.Context, callback func(ImportFix), searchPrefix, filename, filePkg string, opt *Options) error { | |||
_, opt, err := initialize(filename, []byte{}, opt) | |||
if err != nil { | |||
return err | |||
} | |||
return getAllCandidates(ctx, callback, searchPrefix, filename, filePkg, opt.Env) | |||
} | |||
// GetPackageExports returns all known packages with name pkg and their exports. | |||
func GetPackageExports(ctx context.Context, callback func(PackageExport), searchPkg, filename, filePkg string, opt *Options) error { | |||
_, opt, err := initialize(filename, []byte{}, opt) | |||
if err != nil { | |||
return err | |||
} | |||
return getPackageExports(ctx, callback, searchPkg, filename, filePkg, opt.Env) | |||
} | |||
// initialize sets the values for opt and src. | |||
// If they are provided, they are not changed. Otherwise opt is set to the | |||
// default values and src is read from the file system. | |||
func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, error) { | |||
// Use defaults if opt is nil. | |||
if opt == nil { | |||
opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} | |||
} | |||
// Set the env if the user has not provided it. | |||
if opt.Env == nil { | |||
opt.Env = &ProcessEnv{ | |||
GOPATH: build.Default.GOPATH, | |||
GOROOT: build.Default.GOROOT, | |||
GOFLAGS: os.Getenv("GOFLAGS"), | |||
GO111MODULE: os.Getenv("GO111MODULE"), | |||
GOPROXY: os.Getenv("GOPROXY"), | |||
GOSUMDB: os.Getenv("GOSUMDB"), | |||
} | |||
} | |||
if src == nil { | |||
b, err := ioutil.ReadFile(filename) | |||
if err != nil { | |||
return nil, nil, err | |||
} | |||
src = b | |||
} | |||
return src, opt, nil | |||
} | |||
func formatFile(fileSet *token.FileSet, file *ast.File, src []byte, adjust func(orig []byte, src []byte) []byte, opt *Options) ([]byte, error) { | |||
mergeImports(opt.Env, fileSet, file) | |||
sortImports(opt.Env, fileSet, file) | |||
mergeImports(fileSet, file) | |||
sortImports(opt.LocalPrefix, fileSet, file) | |||
imps := astutil.Imports(fileSet, file) | |||
var spacesBefore []string // import paths we need spaces before | |||
for _, impSection := range imps { | |||
@@ -178,7 +116,7 @@ func formatFile(fileSet *token.FileSet, file *ast.File, src []byte, adjust func( | |||
lastGroup := -1 | |||
for _, importSpec := range impSection { | |||
importPath, _ := strconv.Unquote(importSpec.Path.Value) | |||
groupNum := importGroup(opt.Env, importPath) | |||
groupNum := importGroup(opt.LocalPrefix, importPath) | |||
if groupNum != lastGroup && lastGroup != -1 { | |||
spacesBefore = append(spacesBefore, importPath) | |||
} |
@@ -15,7 +15,7 @@ import ( | |||
"strings" | |||
"golang.org/x/mod/module" | |||
"golang.org/x/mod/semver" | |||
"golang.org/x/tools/internal/gocommand" | |||
"golang.org/x/tools/internal/gopathwalk" | |||
) | |||
@@ -24,31 +24,21 @@ import ( | |||
type ModuleResolver struct { | |||
env *ProcessEnv | |||
moduleCacheDir string | |||
dummyVendorMod *ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory. | |||
dummyVendorMod *gocommand.ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory. | |||
roots []gopathwalk.Root | |||
scanSema chan struct{} // scanSema prevents concurrent scans and guards scannedRoots. | |||
scannedRoots map[gopathwalk.Root]bool | |||
initialized bool | |||
main *ModuleJSON | |||
modsByModPath []*ModuleJSON // All modules, ordered by # of path components in module Path... | |||
modsByDir []*ModuleJSON // ...or Dir. | |||
main *gocommand.ModuleJSON | |||
modsByModPath []*gocommand.ModuleJSON // All modules, ordered by # of path components in module Path... | |||
modsByDir []*gocommand.ModuleJSON // ...or Dir. | |||
// moduleCacheCache stores information about the module cache. | |||
moduleCacheCache *dirInfoCache | |||
otherCache *dirInfoCache | |||
} | |||
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 | |||
} | |||
func newModuleResolver(e *ProcessEnv) *ModuleResolver { | |||
r := &ModuleResolver{ | |||
env: e, | |||
@@ -62,7 +52,18 @@ func (r *ModuleResolver) init() error { | |||
if r.initialized { | |||
return nil | |||
} | |||
mainMod, vendorEnabled, err := vendorEnabled(r.env) | |||
goenv, err := r.env.goEnv() | |||
if err != nil { | |||
return err | |||
} | |||
inv := gocommand.Invocation{ | |||
BuildFlags: r.env.BuildFlags, | |||
Env: r.env.env(), | |||
Logf: r.env.Logf, | |||
WorkingDir: r.env.WorkingDir, | |||
} | |||
mainMod, vendorEnabled, err := gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner) | |||
if err != nil { | |||
return err | |||
} | |||
@@ -71,18 +72,22 @@ func (r *ModuleResolver) init() error { | |||
// Vendor mode is on, so all the non-Main modules are irrelevant, | |||
// and we need to search /vendor for everything. | |||
r.main = mainMod | |||
r.dummyVendorMod = &ModuleJSON{ | |||
r.dummyVendorMod = &gocommand.ModuleJSON{ | |||
Path: "", | |||
Dir: filepath.Join(mainMod.Dir, "vendor"), | |||
} | |||
r.modsByModPath = []*ModuleJSON{mainMod, r.dummyVendorMod} | |||
r.modsByDir = []*ModuleJSON{mainMod, r.dummyVendorMod} | |||
r.modsByModPath = []*gocommand.ModuleJSON{mainMod, r.dummyVendorMod} | |||
r.modsByDir = []*gocommand.ModuleJSON{mainMod, r.dummyVendorMod} | |||
} else { | |||
// Vendor mode is off, so run go list -m ... to find everything. | |||
r.initAllMods() | |||
} | |||
r.moduleCacheDir = filepath.Join(filepath.SplitList(r.env.GOPATH)[0], "/pkg/mod") | |||
if gmc := r.env.Env["GOMODCACHE"]; gmc != "" { | |||
r.moduleCacheDir = gmc | |||
} else { | |||
r.moduleCacheDir = filepath.Join(filepath.SplitList(goenv["GOPATH"])[0], "/pkg/mod") | |||
} | |||
sort.Slice(r.modsByModPath, func(i, j int) bool { | |||
count := func(x int) int { | |||
@@ -98,7 +103,7 @@ func (r *ModuleResolver) init() error { | |||
}) | |||
r.roots = []gopathwalk.Root{ | |||
{filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT}, | |||
{filepath.Join(goenv["GOROOT"], "/src"), gopathwalk.RootGOROOT}, | |||
} | |||
if r.main != nil { | |||
r.roots = append(r.roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule}) | |||
@@ -106,7 +111,7 @@ func (r *ModuleResolver) init() error { | |||
if vendorEnabled { | |||
r.roots = append(r.roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther}) | |||
} else { | |||
addDep := func(mod *ModuleJSON) { | |||
addDep := func(mod *gocommand.ModuleJSON) { | |||
if mod.Replace == nil { | |||
// This is redundant with the cache, but we'll skip it cheaply enough. | |||
r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootModuleCache}) | |||
@@ -151,7 +156,7 @@ func (r *ModuleResolver) initAllMods() error { | |||
return err | |||
} | |||
for dec := json.NewDecoder(stdout); dec.More(); { | |||
mod := &ModuleJSON{} | |||
mod := &gocommand.ModuleJSON{} | |||
if err := dec.Decode(mod); err != nil { | |||
return err | |||
} | |||
@@ -197,7 +202,7 @@ func (r *ModuleResolver) ClearForNewMod() { | |||
// findPackage returns the module and directory that contains the package at | |||
// the given import path, or returns nil, "" if no module is in scope. | |||
func (r *ModuleResolver) findPackage(importPath string) (*ModuleJSON, string) { | |||
func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) { | |||
// This can't find packages in the stdlib, but that's harmless for all | |||
// the existing code paths. | |||
for _, m := range r.modsByModPath { | |||
@@ -239,7 +244,7 @@ func (r *ModuleResolver) findPackage(importPath string) (*ModuleJSON, string) { | |||
// files in that directory. If not, it could be provided by an | |||
// outer module. See #29736. | |||
for _, fi := range pkgFiles { | |||
if ok, _ := r.env.buildContext().MatchFile(pkgDir, fi.Name()); ok { | |||
if ok, _ := r.env.matchFile(pkgDir, fi.Name()); ok { | |||
return m, pkgDir | |||
} | |||
} | |||
@@ -283,7 +288,7 @@ func (r *ModuleResolver) cacheExports(ctx context.Context, env *ProcessEnv, info | |||
// findModuleByDir returns the module that contains dir, or nil if no such | |||
// module is in scope. | |||
func (r *ModuleResolver) findModuleByDir(dir string) *ModuleJSON { | |||
func (r *ModuleResolver) findModuleByDir(dir string) *gocommand.ModuleJSON { | |||
// This is quite tricky and may not be correct. dir could be: | |||
// - a package in the main module. | |||
// - a replace target underneath the main module's directory. | |||
@@ -310,7 +315,7 @@ func (r *ModuleResolver) findModuleByDir(dir string) *ModuleJSON { | |||
// dirIsNestedModule reports if dir is contained in a nested module underneath | |||
// mod, not actually in mod. | |||
func (r *ModuleResolver) dirIsNestedModule(dir string, mod *ModuleJSON) bool { | |||
func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON) bool { | |||
if !strings.HasPrefix(dir, mod.Dir) { | |||
return false | |||
} | |||
@@ -490,7 +495,7 @@ func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) int { | |||
return modRelevance(mod) | |||
} | |||
func modRelevance(mod *ModuleJSON) int { | |||
func modRelevance(mod *gocommand.ModuleJSON) int { | |||
switch { | |||
case mod == nil: // out of scope | |||
return MaxRelevance - 4 | |||
@@ -656,63 +661,3 @@ func modulePath(mod []byte) string { | |||
} | |||
return "" // missing module path | |||
} | |||
var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) | |||
// vendorEnabled indicates if vendoring is enabled. | |||
// Inspired by setDefaultBuildMod in modload/init.go | |||
func vendorEnabled(env *ProcessEnv) (*ModuleJSON, bool, error) { | |||
mainMod, go114, err := getMainModuleAnd114(env) | |||
if err != nil { | |||
return nil, false, err | |||
} | |||
matches := modFlagRegexp.FindStringSubmatch(env.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(env *ProcessEnv) (*ModuleJSON, bool, error) { | |||
const format = `{{.Path}} | |||
{{.Dir}} | |||
{{.GoMod}} | |||
{{.GoVersion}} | |||
{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}} | |||
` | |||
stdout, err := env.invokeGo(context.TODO(), "list", "-m", "-f", format) | |||
if err != nil { | |||
return nil, false, nil | |||
} | |||
lines := strings.Split(stdout.String(), "\n") | |||
if len(lines) < 5 { | |||
return nil, false, fmt.Errorf("unexpected stdout: %q", stdout) | |||
} | |||
mod := &ModuleJSON{ | |||
Path: lines[0], | |||
Dir: lines[1], | |||
GoMod: lines[2], | |||
GoVersion: lines[3], | |||
Main: true, | |||
} | |||
return mod, lines[4] == "go1.14", nil | |||
} |
@@ -15,7 +15,7 @@ import ( | |||
// sortImports sorts runs of consecutive import lines in import blocks in f. | |||
// It also removes duplicate imports when it is possible to do so without data loss. | |||
func sortImports(env *ProcessEnv, fset *token.FileSet, f *ast.File) { | |||
func sortImports(localPrefix string, fset *token.FileSet, f *ast.File) { | |||
for i, d := range f.Decls { | |||
d, ok := d.(*ast.GenDecl) | |||
if !ok || d.Tok != token.IMPORT { | |||
@@ -40,11 +40,11 @@ func sortImports(env *ProcessEnv, fset *token.FileSet, f *ast.File) { | |||
for j, s := range d.Specs { | |||
if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { | |||
// j begins a new run. End this one. | |||
specs = append(specs, sortSpecs(env, fset, f, d.Specs[i:j])...) | |||
specs = append(specs, sortSpecs(localPrefix, fset, f, d.Specs[i:j])...) | |||
i = j | |||
} | |||
} | |||
specs = append(specs, sortSpecs(env, fset, f, d.Specs[i:])...) | |||
specs = append(specs, sortSpecs(localPrefix, fset, f, d.Specs[i:])...) | |||
d.Specs = specs | |||
// Deduping can leave a blank line before the rparen; clean that up. | |||
@@ -60,7 +60,7 @@ func sortImports(env *ProcessEnv, fset *token.FileSet, f *ast.File) { | |||
// mergeImports merges all the import declarations into the first one. | |||
// Taken from golang.org/x/tools/ast/astutil. | |||
func mergeImports(env *ProcessEnv, fset *token.FileSet, f *ast.File) { | |||
func mergeImports(fset *token.FileSet, f *ast.File) { | |||
if len(f.Decls) <= 1 { | |||
return | |||
} | |||
@@ -142,7 +142,7 @@ type posSpan struct { | |||
End token.Pos | |||
} | |||
func sortSpecs(env *ProcessEnv, fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { | |||
func sortSpecs(localPrefix string, fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { | |||
// Can't short-circuit here even if specs are already sorted, | |||
// since they might yet need deduplication. | |||
// A lone import, however, may be safely ignored. | |||
@@ -191,7 +191,7 @@ func sortSpecs(env *ProcessEnv, fset *token.FileSet, f *ast.File, specs []ast.Sp | |||
// Reassign the import paths to have the same position sequence. | |||
// Reassign each comment to abut the end of its spec. | |||
// Sort the comments by new position. | |||
sort.Sort(byImportSpec{env, specs}) | |||
sort.Sort(byImportSpec{localPrefix, specs}) | |||
// Dedup. Thanks to our sorting, we can just consider | |||
// adjacent pairs of imports. | |||
@@ -245,8 +245,8 @@ func sortSpecs(env *ProcessEnv, fset *token.FileSet, f *ast.File, specs []ast.Sp | |||
} | |||
type byImportSpec struct { | |||
env *ProcessEnv | |||
specs []ast.Spec // slice of *ast.ImportSpec | |||
localPrefix string | |||
specs []ast.Spec // slice of *ast.ImportSpec | |||
} | |||
func (x byImportSpec) Len() int { return len(x.specs) } | |||
@@ -255,8 +255,8 @@ func (x byImportSpec) Less(i, j int) bool { | |||
ipath := importPath(x.specs[i]) | |||
jpath := importPath(x.specs[j]) | |||
igroup := importGroup(x.env, ipath) | |||
jgroup := importGroup(x.env, jpath) | |||
igroup := importGroup(x.localPrefix, ipath) | |||
jgroup := importGroup(x.localPrefix, jpath) | |||
if igroup != jgroup { | |||
return igroup < jgroup | |||
} |
@@ -56,6 +56,7 @@ var stdlib = map[string][]string{ | |||
}, | |||
"bufio": []string{ | |||
"ErrAdvanceTooFar", | |||
"ErrBadReadCount", | |||
"ErrBufferFull", | |||
"ErrFinalToken", | |||
"ErrInvalidUnreadByte", | |||
@@ -303,7 +304,9 @@ var stdlib = map[string][]string{ | |||
"PrivateKey", | |||
"PublicKey", | |||
"Sign", | |||
"SignASN1", | |||
"Verify", | |||
"VerifyASN1", | |||
}, | |||
"crypto/ed25519": []string{ | |||
"GenerateKey", | |||
@@ -322,11 +325,13 @@ var stdlib = map[string][]string{ | |||
"CurveParams", | |||
"GenerateKey", | |||
"Marshal", | |||
"MarshalCompressed", | |||
"P224", | |||
"P256", | |||
"P384", | |||
"P521", | |||
"Unmarshal", | |||
"UnmarshalCompressed", | |||
}, | |||
"crypto/hmac": []string{ | |||
"Equal", | |||
@@ -415,6 +420,9 @@ var stdlib = map[string][]string{ | |||
"crypto/tls": []string{ | |||
"Certificate", | |||
"CertificateRequestInfo", | |||
"CipherSuite", | |||
"CipherSuiteName", | |||
"CipherSuites", | |||
"Client", | |||
"ClientAuthType", | |||
"ClientHelloInfo", | |||
@@ -429,11 +437,13 @@ var stdlib = map[string][]string{ | |||
"CurveP521", | |||
"Dial", | |||
"DialWithDialer", | |||
"Dialer", | |||
"ECDSAWithP256AndSHA256", | |||
"ECDSAWithP384AndSHA384", | |||
"ECDSAWithP521AndSHA512", | |||
"ECDSAWithSHA1", | |||
"Ed25519", | |||
"InsecureCipherSuites", | |||
"Listen", | |||
"LoadX509KeyPair", | |||
"NewLRUClientSessionCache", | |||
@@ -465,6 +475,7 @@ var stdlib = map[string][]string{ | |||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", | |||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", | |||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", | |||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", | |||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", | |||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", | |||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", | |||
@@ -473,6 +484,7 @@ var stdlib = map[string][]string{ | |||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", | |||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", | |||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", | |||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", | |||
"TLS_ECDHE_RSA_WITH_RC4_128_SHA", | |||
"TLS_FALLBACK_SCSV", | |||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA", | |||
@@ -501,6 +513,7 @@ var stdlib = map[string][]string{ | |||
"ConstraintViolationError", | |||
"CreateCertificate", | |||
"CreateCertificateRequest", | |||
"CreateRevocationList", | |||
"DSA", | |||
"DSAWithSHA1", | |||
"DSAWithSHA256", | |||
@@ -575,6 +588,7 @@ var stdlib = map[string][]string{ | |||
"PublicKeyAlgorithm", | |||
"PureEd25519", | |||
"RSA", | |||
"RevocationList", | |||
"SHA1WithRSA", | |||
"SHA256WithRSA", | |||
"SHA256WithRSAPSS", | |||
@@ -688,6 +702,7 @@ var stdlib = map[string][]string{ | |||
"String", | |||
"Tx", | |||
"TxOptions", | |||
"Validator", | |||
"Value", | |||
"ValueConverter", | |||
"Valuer", | |||
@@ -698,36 +713,65 @@ var stdlib = map[string][]string{ | |||
"Attr", | |||
"AttrAbstractOrigin", | |||
"AttrAccessibility", | |||
"AttrAddrBase", | |||
"AttrAddrClass", | |||
"AttrAlignment", | |||
"AttrAllocated", | |||
"AttrArtificial", | |||
"AttrAssociated", | |||
"AttrBaseTypes", | |||
"AttrBinaryScale", | |||
"AttrBitOffset", | |||
"AttrBitSize", | |||
"AttrByteSize", | |||
"AttrCallAllCalls", | |||
"AttrCallAllSourceCalls", | |||
"AttrCallAllTailCalls", | |||
"AttrCallColumn", | |||
"AttrCallDataLocation", | |||
"AttrCallDataValue", | |||
"AttrCallFile", | |||
"AttrCallLine", | |||
"AttrCallOrigin", | |||
"AttrCallPC", | |||
"AttrCallParameter", | |||
"AttrCallReturnPC", | |||
"AttrCallTailCall", | |||
"AttrCallTarget", | |||
"AttrCallTargetClobbered", | |||
"AttrCallValue", | |||
"AttrCalling", | |||
"AttrCommonRef", | |||
"AttrCompDir", | |||
"AttrConstExpr", | |||
"AttrConstValue", | |||
"AttrContainingType", | |||
"AttrCount", | |||
"AttrDataBitOffset", | |||
"AttrDataLocation", | |||
"AttrDataMemberLoc", | |||
"AttrDecimalScale", | |||
"AttrDecimalSign", | |||
"AttrDeclColumn", | |||
"AttrDeclFile", | |||
"AttrDeclLine", | |||
"AttrDeclaration", | |||
"AttrDefaultValue", | |||
"AttrDefaulted", | |||
"AttrDeleted", | |||
"AttrDescription", | |||
"AttrDigitCount", | |||
"AttrDiscr", | |||
"AttrDiscrList", | |||
"AttrDiscrValue", | |||
"AttrDwoName", | |||
"AttrElemental", | |||
"AttrEncoding", | |||
"AttrEndianity", | |||
"AttrEntrypc", | |||
"AttrEnumClass", | |||
"AttrExplicit", | |||
"AttrExportSymbols", | |||
"AttrExtension", | |||
"AttrExternal", | |||
"AttrFrameBase", | |||
@@ -738,27 +782,47 @@ var stdlib = map[string][]string{ | |||
"AttrInline", | |||
"AttrIsOptional", | |||
"AttrLanguage", | |||
"AttrLinkageName", | |||
"AttrLocation", | |||
"AttrLoclistsBase", | |||
"AttrLowerBound", | |||
"AttrLowpc", | |||
"AttrMacroInfo", | |||
"AttrMacros", | |||
"AttrMainSubprogram", | |||
"AttrMutable", | |||
"AttrName", | |||
"AttrNamelistItem", | |||
"AttrNoreturn", | |||
"AttrObjectPointer", | |||
"AttrOrdering", | |||
"AttrPictureString", | |||
"AttrPriority", | |||
"AttrProducer", | |||
"AttrPrototyped", | |||
"AttrPure", | |||
"AttrRanges", | |||
"AttrRank", | |||
"AttrRecursive", | |||
"AttrReference", | |||
"AttrReturnAddr", | |||
"AttrRnglistsBase", | |||
"AttrRvalueReference", | |||
"AttrSegment", | |||
"AttrSibling", | |||
"AttrSignature", | |||
"AttrSmall", | |||
"AttrSpecification", | |||
"AttrStartScope", | |||
"AttrStaticLink", | |||
"AttrStmtList", | |||
"AttrStrOffsetsBase", | |||
"AttrStride", | |||
"AttrStrideSize", | |||
"AttrStringLength", | |||
"AttrStringLengthBitSize", | |||
"AttrStringLengthByteSize", | |||
"AttrThreadsScaled", | |||
"AttrTrampoline", | |||
"AttrType", | |||
"AttrUpperBound", | |||
@@ -772,18 +836,23 @@ var stdlib = map[string][]string{ | |||
"BoolType", | |||
"CharType", | |||
"Class", | |||
"ClassAddrPtr", | |||
"ClassAddress", | |||
"ClassBlock", | |||
"ClassConstant", | |||
"ClassExprLoc", | |||
"ClassFlag", | |||
"ClassLinePtr", | |||
"ClassLocList", | |||
"ClassLocListPtr", | |||
"ClassMacPtr", | |||
"ClassRangeListPtr", | |||
"ClassReference", | |||
"ClassReferenceAlt", | |||
"ClassReferenceSig", | |||
"ClassRngList", | |||
"ClassRngListsPtr", | |||
"ClassStrOffsetsPtr", | |||
"ClassString", | |||
"ClassStringAlt", | |||
"ClassUnknown", | |||
@@ -814,9 +883,13 @@ var stdlib = map[string][]string{ | |||
"Tag", | |||
"TagAccessDeclaration", | |||
"TagArrayType", | |||
"TagAtomicType", | |||
"TagBaseType", | |||
"TagCallSite", | |||
"TagCallSiteParameter", | |||
"TagCatchDwarfBlock", | |||
"TagClassType", | |||
"TagCoarrayType", | |||
"TagCommonDwarfBlock", | |||
"TagCommonInclusion", | |||
"TagCompileUnit", | |||
@@ -824,12 +897,15 @@ var stdlib = map[string][]string{ | |||
"TagConstType", | |||
"TagConstant", | |||
"TagDwarfProcedure", | |||
"TagDynamicType", | |||
"TagEntryPoint", | |||
"TagEnumerationType", | |||
"TagEnumerator", | |||
"TagFileType", | |||
"TagFormalParameter", | |||
"TagFriend", | |||
"TagGenericSubrange", | |||
"TagImmutableType", | |||
"TagImportedDeclaration", | |||
"TagImportedModule", | |||
"TagImportedUnit", | |||
@@ -853,6 +929,7 @@ var stdlib = map[string][]string{ | |||
"TagRvalueReferenceType", | |||
"TagSetType", | |||
"TagSharedType", | |||
"TagSkeletonUnit", | |||
"TagStringType", | |||
"TagStructType", | |||
"TagSubprogram", | |||
@@ -2281,6 +2358,27 @@ var stdlib = map[string][]string{ | |||
"IMAGE_DIRECTORY_ENTRY_RESOURCE", | |||
"IMAGE_DIRECTORY_ENTRY_SECURITY", | |||
"IMAGE_DIRECTORY_ENTRY_TLS", | |||
"IMAGE_DLLCHARACTERISTICS_APPCONTAINER", | |||
"IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE", | |||
"IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY", | |||
"IMAGE_DLLCHARACTERISTICS_GUARD_CF", | |||
"IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA", | |||
"IMAGE_DLLCHARACTERISTICS_NO_BIND", | |||
"IMAGE_DLLCHARACTERISTICS_NO_ISOLATION", | |||
"IMAGE_DLLCHARACTERISTICS_NO_SEH", | |||
"IMAGE_DLLCHARACTERISTICS_NX_COMPAT", | |||
"IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE", | |||
"IMAGE_DLLCHARACTERISTICS_WDM_DRIVER", | |||
"IMAGE_FILE_32BIT_MACHINE", | |||
"IMAGE_FILE_AGGRESIVE_WS_TRIM", | |||
"IMAGE_FILE_BYTES_REVERSED_HI", | |||
"IMAGE_FILE_BYTES_REVERSED_LO", | |||
"IMAGE_FILE_DEBUG_STRIPPED", | |||
"IMAGE_FILE_DLL", | |||
"IMAGE_FILE_EXECUTABLE_IMAGE", | |||
"IMAGE_FILE_LARGE_ADDRESS_AWARE", | |||
"IMAGE_FILE_LINE_NUMS_STRIPPED", | |||
"IMAGE_FILE_LOCAL_SYMS_STRIPPED", | |||
"IMAGE_FILE_MACHINE_AM33", | |||
"IMAGE_FILE_MACHINE_AMD64", | |||
"IMAGE_FILE_MACHINE_ARM", | |||
@@ -2303,6 +2401,25 @@ var stdlib = map[string][]string{ | |||
"IMAGE_FILE_MACHINE_THUMB", | |||
"IMAGE_FILE_MACHINE_UNKNOWN", | |||
"IMAGE_FILE_MACHINE_WCEMIPSV2", | |||
"IMAGE_FILE_NET_RUN_FROM_SWAP", | |||
"IMAGE_FILE_RELOCS_STRIPPED", | |||
"IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP", | |||
"IMAGE_FILE_SYSTEM", | |||
"IMAGE_FILE_UP_SYSTEM_ONLY", | |||
"IMAGE_SUBSYSTEM_EFI_APPLICATION", | |||
"IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", | |||
"IMAGE_SUBSYSTEM_EFI_ROM", | |||
"IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER", | |||
"IMAGE_SUBSYSTEM_NATIVE", | |||
"IMAGE_SUBSYSTEM_NATIVE_WINDOWS", | |||
"IMAGE_SUBSYSTEM_OS2_CUI", | |||
"IMAGE_SUBSYSTEM_POSIX_CUI", | |||
"IMAGE_SUBSYSTEM_UNKNOWN", | |||
"IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION", | |||
"IMAGE_SUBSYSTEM_WINDOWS_CE_GUI", | |||
"IMAGE_SUBSYSTEM_WINDOWS_CUI", | |||
"IMAGE_SUBSYSTEM_WINDOWS_GUI", | |||
"IMAGE_SUBSYSTEM_XBOX", | |||
"ImportDirectory", | |||
"NewFile", | |||
"Open", | |||
@@ -2359,6 +2476,7 @@ var stdlib = map[string][]string{ | |||
"RawValue", | |||
"StructuralError", | |||
"SyntaxError", | |||
"TagBMPString", | |||
"TagBitString", | |||
"TagBoolean", | |||
"TagEnum", | |||
@@ -2787,6 +2905,7 @@ var stdlib = map[string][]string{ | |||
"IsPredeclared", | |||
"Mode", | |||
"New", | |||
"NewFromFiles", | |||
"Note", | |||
"Package", | |||
"PreserveAST", | |||
@@ -3115,6 +3234,11 @@ var stdlib = map[string][]string{ | |||
"New64", | |||
"New64a", | |||
}, | |||
"hash/maphash": []string{ | |||
"Hash", | |||
"MakeSeed", | |||
"Seed", | |||
}, | |||
"html": []string{ | |||
"EscapeString", | |||
"UnescapeString", | |||
@@ -3367,6 +3491,7 @@ var stdlib = map[string][]string{ | |||
"Ldate", | |||
"Llongfile", | |||
"Lmicroseconds", | |||
"Lmsgprefix", | |||
"Logger", | |||
"Lshortfile", | |||
"LstdFlags", | |||
@@ -3443,6 +3568,7 @@ var stdlib = map[string][]string{ | |||
"Exp", | |||
"Exp2", | |||
"Expm1", | |||
"FMA", | |||
"Float32bits", | |||
"Float32frombits", | |||
"Float64bits", | |||
@@ -3567,6 +3693,9 @@ var stdlib = map[string][]string{ | |||
"OnesCount32", | |||
"OnesCount64", | |||
"OnesCount8", | |||
"Rem", | |||
"Rem32", | |||
"Rem64", | |||
"Reverse", | |||
"Reverse16", | |||
"Reverse32", | |||
@@ -4108,6 +4237,7 @@ var stdlib = map[string][]string{ | |||
"DevNull", | |||
"Environ", | |||
"ErrClosed", | |||
"ErrDeadlineExceeded", | |||
"ErrExist", | |||
"ErrInvalid", | |||
"ErrNoDeadline", | |||
@@ -4566,6 +4696,7 @@ var stdlib = map[string][]string{ | |||
"ErrRange", | |||
"ErrSyntax", | |||
"FormatBool", | |||
"FormatComplex", | |||
"FormatFloat", | |||
"FormatInt", | |||
"FormatUint", | |||
@@ -4575,6 +4706,7 @@ var stdlib = map[string][]string{ | |||
"Itoa", | |||
"NumError", | |||
"ParseBool", | |||
"ParseComplex", | |||
"ParseFloat", | |||
"ParseInt", | |||
"ParseUint", | |||
@@ -5140,7 +5272,10 @@ var stdlib = map[string][]string{ | |||
"CTL_NET", | |||
"CTL_QUERY", | |||
"CTRL_BREAK_EVENT", | |||
"CTRL_CLOSE_EVENT", | |||
"CTRL_C_EVENT", | |||
"CTRL_LOGOFF_EVENT", | |||
"CTRL_SHUTDOWN_EVENT", | |||
"CancelIo", | |||
"CancelIoEx", | |||
"CertAddCertificateContextToStore", | |||
@@ -10112,6 +10247,7 @@ var stdlib = map[string][]string{ | |||
"Duployan", | |||
"Egyptian_Hieroglyphs", | |||
"Elbasan", | |||
"Elymaic", | |||
"Ethiopic", | |||
"Extender", | |||
"FoldCategory", | |||
@@ -10215,6 +10351,7 @@ var stdlib = map[string][]string{ | |||
"Myanmar", | |||
"N", | |||
"Nabataean", | |||
"Nandinagari", | |||
"Nd", | |||
"New_Tai_Lue", | |||
"Newa", | |||
@@ -10224,6 +10361,7 @@ var stdlib = map[string][]string{ | |||
"Noncharacter_Code_Point", | |||
"Number", | |||
"Nushu", | |||
"Nyiakeng_Puachue_Hmong", | |||
"Ogham", | |||
"Ol_Chiki", | |||
"Old_Hungarian", | |||
@@ -10331,6 +10469,7 @@ var stdlib = map[string][]string{ | |||
"Vai", | |||
"Variation_Selector", | |||
"Version", | |||
"Wancho", | |||
"Warang_Citi", | |||
"White_Space", | |||
"Yi", |
@@ -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 | |||
} |
@@ -1,10 +1,10 @@ | |||
# cloud.google.com/go v0.45.0 | |||
## explicit | |||
cloud.google.com/go/compute/metadata | |||
# gitea.com/jolheiser/gitea-vet v0.1.0 | |||
# code.gitea.io/gitea-vet v0.2.1 | |||
## explicit | |||
gitea.com/jolheiser/gitea-vet | |||
gitea.com/jolheiser/gitea-vet/checks | |||
code.gitea.io/gitea-vet | |||
code.gitea.io/gitea-vet/checks | |||
# gitea.com/lunny/levelqueue v0.3.0 | |||
## explicit | |||
gitea.com/lunny/levelqueue | |||
@@ -769,10 +769,10 @@ golang.org/x/crypto/ssh | |||
golang.org/x/crypto/ssh/agent | |||
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf | |||
golang.org/x/crypto/ssh/knownhosts | |||
# golang.org/x/mod v0.2.0 | |||
# golang.org/x/mod v0.3.0 | |||
golang.org/x/mod/module | |||
golang.org/x/mod/semver | |||
# golang.org/x/net v0.0.0-20200602114024-627f9648deb9 | |||
# golang.org/x/net v0.0.0-20200625001655-4c5254603344 | |||
## explicit | |||
golang.org/x/net/context | |||
golang.org/x/net/context/ctxhttp | |||
@@ -823,7 +823,7 @@ golang.org/x/text/width | |||
# golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 | |||
## explicit | |||
golang.org/x/time/rate | |||
# golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224 | |||
# golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d | |||
## explicit | |||
golang.org/x/tools/cover | |||
golang.org/x/tools/go/analysis | |||
@@ -840,11 +840,18 @@ golang.org/x/tools/go/loader | |||
golang.org/x/tools/go/packages | |||
golang.org/x/tools/go/types/objectpath | |||
golang.org/x/tools/imports | |||
golang.org/x/tools/internal/analysisinternal | |||
golang.org/x/tools/internal/event | |||
golang.org/x/tools/internal/event/core | |||
golang.org/x/tools/internal/event/keys | |||
golang.org/x/tools/internal/event/label | |||
golang.org/x/tools/internal/fastwalk | |||
golang.org/x/tools/internal/gocommand | |||
golang.org/x/tools/internal/gopathwalk | |||
golang.org/x/tools/internal/imports | |||
golang.org/x/tools/internal/lsp/fuzzy | |||
golang.org/x/tools/internal/packagesinternal | |||
golang.org/x/tools/internal/typesinternal | |||
# golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 | |||
golang.org/x/xerrors | |||
golang.org/x/xerrors/internal |