Fix lint errs (#59)
This commit is contained in:
parent
15ef6deabc
commit
1b84292332
90 changed files with 10877 additions and 2179 deletions
|
@ -35,6 +35,8 @@ steps:
|
||||||
- make ineffassign-check
|
- make ineffassign-check
|
||||||
- make misspell-check
|
- make misspell-check
|
||||||
- make goconst-check
|
- make goconst-check
|
||||||
|
- make gocyclo-check
|
||||||
|
- make static-check
|
||||||
- make build
|
- make build
|
||||||
when:
|
when:
|
||||||
event: [ push, tag, pull_request ]
|
event: [ push, tag, pull_request ]
|
||||||
|
|
|
@ -163,15 +163,15 @@ Sorry for some of them being in German, I'll tranlate them at some point.
|
||||||
* [x] ListTaskRights, sollte überall gleich funktionieren, gibt ja mittlerweile auch eine Methode um liste von nem Task aus zu kriegen oder so
|
* [x] ListTaskRights, sollte überall gleich funktionieren, gibt ja mittlerweile auch eine Methode um liste von nem Task aus zu kriegen oder so
|
||||||
* [x] Re-check all `{List|Namespace}{User|Team}` if really all parameters need to be exposed via json or are overwritten via param anyway.
|
* [x] Re-check all `{List|Namespace}{User|Team}` if really all parameters need to be exposed via json or are overwritten via param anyway.
|
||||||
* [x] Things like list/task order should use queries and not url params
|
* [x] Things like list/task order should use queries and not url params
|
||||||
* [ ] Fix lint errors
|
* [x] Fix lint errors
|
||||||
|
|
||||||
### Linters
|
### Linters
|
||||||
|
|
||||||
* [x] goconst
|
* [x] goconst
|
||||||
* [ ] Gosimple -> waiting for mod
|
* [x] Staticcheck -> waiting for mod
|
||||||
* [ ] Staticcheck -> waiting for mod
|
* [x] gocyclo-check
|
||||||
* [ ] unused -> waiting for mod
|
* [ ] gosec-check -> waiting for mod
|
||||||
* [ ] gosec -> waiting for mod
|
* [x] goconst-check -> waiting for mod
|
||||||
|
|
||||||
### More server settings
|
### More server settings
|
||||||
|
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -199,13 +199,15 @@ ineffassign-check:
|
||||||
.PHONY: gocyclo-check
|
.PHONY: gocyclo-check
|
||||||
gocyclo-check:
|
gocyclo-check:
|
||||||
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
go get -u github.com/fzipp/gocyclo; \
|
||||||
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
|
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
|
||||||
fi
|
fi
|
||||||
for S in $(GOFILES); do gocyclo -over 14 $$S || exit 1; done;
|
for S in $(GOFILES); do gocyclo -over 16 $$S || exit 1; done;
|
||||||
|
|
||||||
.PHONY: static-check
|
.PHONY: static-check
|
||||||
static-check:
|
static-check:
|
||||||
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash staticcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
go get -u honnef.co/go/tools; \
|
||||||
go install $(GOFLAGS) honnef.co/go/tools/cmd/staticcheck; \
|
go install $(GOFLAGS) honnef.co/go/tools/cmd/staticcheck; \
|
||||||
fi
|
fi
|
||||||
staticcheck $(PACKAGES);
|
staticcheck $(PACKAGES);
|
||||||
|
@ -220,6 +222,7 @@ gosec-check:
|
||||||
.PHONY: goconst-check
|
.PHONY: goconst-check
|
||||||
goconst-check:
|
goconst-check:
|
||||||
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
go get github.com/jgautheron/goconst/cmd/goconst; \
|
go get -u github.com/jgautheron/goconst/cmd/goconst; \
|
||||||
|
go install $(GOFLAGS) github.com/jgautheron/goconst/cmd/goconst; \
|
||||||
fi
|
fi
|
||||||
for S in $(PACKAGES); do goconst $$S || exit 1; done;
|
for S in $(PACKAGES); do goconst $$S || exit 1; done;
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -67,5 +67,5 @@ require (
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
gopkg.in/testfixtures.v2 v2.5.3
|
gopkg.in/testfixtures.v2 v2.5.3
|
||||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||||
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe
|
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c
|
||||||
)
|
)
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -205,3 +205,5 @@ honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3 h1:LyX67rVB0kBUFoROrQfzKwd
|
||||||
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe h1:/GZ/onp6W295MEgrIwtlbnxmFSKGavFp7/D7tMVyuaM=
|
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe h1:/GZ/onp6W295MEgrIwtlbnxmFSKGavFp7/D7tMVyuaM=
|
||||||
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c h1:z+UFwlQ7KVwdlQTE5JjvDvfZmyyAVrEiiwau20b7X8k=
|
||||||
|
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|
2
main.go
2
main.go
|
@ -65,7 +65,7 @@ func main() {
|
||||||
|
|
||||||
// Wait for interrupt signal to gracefully shutdown the server with
|
// Wait for interrupt signal to gracefully shutdown the server with
|
||||||
// a timeout of 10 seconds.
|
// a timeout of 10 seconds.
|
||||||
quit := make(chan os.Signal)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, os.Interrupt)
|
signal.Notify(quit, os.Interrupt)
|
||||||
<-quit
|
<-quit
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
|
|
@ -91,7 +91,6 @@ func GetLogWriter(logfile string) (writer io.Writer) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
writer = f
|
writer = f
|
||||||
break
|
|
||||||
case "stderr":
|
case "stderr":
|
||||||
writer = os.Stderr
|
writer = os.Stderr
|
||||||
case "stdout":
|
case "stdout":
|
||||||
|
|
|
@ -109,7 +109,7 @@ func (bt *BulkTask) Update() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// And because a false is considered to be a null value, we need to explicitly check that case here.
|
// And because a false is considered to be a null value, we need to explicitly check that case here.
|
||||||
if bt.ListTask.Done == false {
|
if !bt.ListTask.Done {
|
||||||
oldtask.Done = false
|
oldtask.Done = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -204,10 +204,8 @@ func (t *ListTask) updateTaskLabels(creator web.Auth, labels []*Label) (err erro
|
||||||
|
|
||||||
// Make a hashmap of the new labels for easier comparison
|
// Make a hashmap of the new labels for easier comparison
|
||||||
newLabels := make(map[int64]*Label, len(labels))
|
newLabels := make(map[int64]*Label, len(labels))
|
||||||
var allLabelIDs []int64
|
|
||||||
for _, newLabel := range labels {
|
for _, newLabel := range labels {
|
||||||
newLabels[newLabel.ID] = newLabel
|
newLabels[newLabel.ID] = newLabel
|
||||||
allLabelIDs = append(allLabelIDs, newLabel.ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get old labels to delete
|
// Get old labels to delete
|
||||||
|
|
|
@ -58,12 +58,12 @@ func GetListsByNamespaceID(nID int64, doer *User) (lists []*List, err error) {
|
||||||
Or("ul.user_id = ?", doer.ID).
|
Or("ul.user_id = ?", doer.ID).
|
||||||
GroupBy("l.id").
|
GroupBy("l.id").
|
||||||
Find(&lists)
|
Find(&lists)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err = x.Where("namespace_id = ?", nID).Find(&lists)
|
err = x.Where("namespace_id = ?", nID).Find(&lists)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// get more list details
|
// get more list details
|
||||||
err = AddListDetails(lists)
|
err = AddListDetails(lists)
|
||||||
|
|
|
@ -129,7 +129,7 @@ func (t *ListTask) Update() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// And because a false is considered to be a null value, we need to explicitly check that case here.
|
// And because a false is considered to be a null value, we need to explicitly check that case here.
|
||||||
if t.Done == false {
|
if !t.Done {
|
||||||
ot.Done = false
|
ot.Done = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ func init() {
|
||||||
func SetEngine() (err error) {
|
func SetEngine() (err error) {
|
||||||
x, err = getEngine()
|
x, err = getEngine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to connect to database: %v", err)
|
return fmt.Errorf("failed to connect to database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
|
@ -104,13 +104,11 @@ func SetEngine() (err error) {
|
||||||
case "memory":
|
case "memory":
|
||||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), viper.GetInt("cache.maxelementsize"))
|
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), viper.GetInt("cache.maxelementsize"))
|
||||||
x.SetDefaultCacher(cacher)
|
x.SetDefaultCacher(cacher)
|
||||||
break
|
|
||||||
case "redis":
|
case "redis":
|
||||||
cacher := xrc.NewRedisCacher(viper.GetString("redis.host"), viper.GetString("redis.password"), xrc.DEFAULT_EXPIRATION, x.Logger())
|
cacher := xrc.NewRedisCacher(viper.GetString("redis.host"), viper.GetString("redis.password"), xrc.DEFAULT_EXPIRATION, x.Logger())
|
||||||
x.SetDefaultCacher(cacher)
|
x.SetDefaultCacher(cacher)
|
||||||
gob.Register(tables)
|
gob.Register(tables)
|
||||||
gob.Register(tablesWithPointer) // Need to register tables with pointer as well...
|
gob.Register(tablesWithPointer) // Need to register tables with pointer as well...
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
log.Log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.")
|
log.Log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.")
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,9 @@ func (n *Namespace) Delete() (err error) {
|
||||||
|
|
||||||
// Delete all lists with their tasks
|
// Delete all lists with their tasks
|
||||||
lists, err := GetListsByNamespaceID(n.ID, &User{})
|
lists, err := GetListsByNamespaceID(n.ID, &User{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
var listIDs []int64
|
var listIDs []int64
|
||||||
// We need to do that for here because we need the list ids to delete two times:
|
// We need to do that for here because we need the list ids to delete two times:
|
||||||
// 1) to delete the lists itself
|
// 1) to delete the lists itself
|
||||||
|
|
|
@ -49,6 +49,9 @@ func (tm *TeamMember) Create(a web.Auth) (err error) {
|
||||||
// Check if that user is already part of the team
|
// Check if that user is already part of the team
|
||||||
exists, err := x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).
|
exists, err := x.Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).
|
||||||
Get(&TeamMember{})
|
Get(&TeamMember{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if exists {
|
if exists {
|
||||||
return ErrUserIsMemberOfTeam{tm.TeamID, tm.UserID}
|
return ErrUserIsMemberOfTeam{tm.TeamID, tm.UserID}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,8 +98,7 @@ func TestIsErrInvalidRight(t *testing.T) {
|
||||||
assert.NoError(t, RightWrite.isValid())
|
assert.NoError(t, RightWrite.isValid())
|
||||||
|
|
||||||
// Check invalid
|
// Check invalid
|
||||||
var tr Right
|
var tr Right = 938
|
||||||
tr = 938
|
|
||||||
err := tr.isValid()
|
err := tr.isValid()
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.True(t, IsErrInvalidRight(err))
|
assert.True(t, IsErrInvalidRight(err))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
// This file was generated by swaggo/swag at
|
// This file was generated by swaggo/swag at
|
||||||
// 2019-02-18 18:58:14.354492295 +0100 CET m=+0.122724247
|
// 2019-02-18 19:07:23.651383203 +0100 CET m=+0.098746766
|
||||||
|
|
||||||
package swagger
|
package swagger
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ var doc = `{
|
||||||
"JWTKeyAuth": []
|
"JWTKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Returns an array with all assignees for this task.",
|
"description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -45,9 +45,9 @@ var doc = `{
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"assignees"
|
"labels"
|
||||||
],
|
],
|
||||||
"summary": "Get all assignees for a task",
|
"summary": "Get all labels a user has access to",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
@ -57,18 +57,18 @@ var doc = `{
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Search assignees by their username.",
|
"description": "Search labels by label text.",
|
||||||
"name": "s",
|
"name": "s",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The assignees",
|
"description": "The labels",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/models.User"
|
"$ref": "#/definitions/models.Label"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -391,7 +391,7 @@ var doc = `{
|
||||||
"JWTKeyAuth": []
|
"JWTKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Returns a team by its ID.",
|
"description": "Returns a list by its ID.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -399,13 +399,13 @@ var doc = `{
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"team"
|
"list"
|
||||||
],
|
],
|
||||||
"summary": "Gets one team",
|
"summary": "Gets one list",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Team ID",
|
"description": "List ID",
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
@ -413,14 +413,14 @@ var doc = `{
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The team",
|
"description": "The list",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"$ref": "#/definitions/models.Team"
|
"$ref": "#/definitions/models.List"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"403": {
|
"403": {
|
||||||
"description": "The user does not have access to the team",
|
"description": "The user does not have access to the list",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"$ref": "#/definitions/code.vikunja.io.web.HTTPError"
|
"$ref": "#/definitions/code.vikunja.io.web.HTTPError"
|
||||||
|
@ -2319,6 +2319,138 @@ var doc = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/tasks/all/{sortby}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns all tasks on any list the user has access to.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"task"
|
||||||
|
],
|
||||||
|
"summary": "Get tasks sorted",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.",
|
||||||
|
"name": "p",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search tasks by task text.",
|
||||||
|
"name": "s",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.",
|
||||||
|
"name": "sortby",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The tasks",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.List"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/models.Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/tasks/all/{sortby}/{startdate}/{enddate}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns all tasks on any list the user has access to.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"task"
|
||||||
|
],
|
||||||
|
"summary": "Get tasks sorted and within a date range",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.",
|
||||||
|
"name": "p",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search tasks by task text.",
|
||||||
|
"name": "s",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.",
|
||||||
|
"name": "sortby",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The start date parameter. Expects a unix timestamp.",
|
||||||
|
"name": "startdate",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The end date parameter. Expects a unix timestamp.",
|
||||||
|
"name": "enddate",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The tasks",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.List"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/models.Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/tasks/bulk": {
|
"/tasks/bulk": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"JWTKeyAuth": []
|
"JWTKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Returns an array with all assignees for this task.",
|
"description": "Returns all labels which are either created by the user or associated with a task the user has at least read-access to.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -32,9 +32,9 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"assignees"
|
"labels"
|
||||||
],
|
],
|
||||||
"summary": "Get all assignees for a task",
|
"summary": "Get all labels a user has access to",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
@ -44,18 +44,18 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Search assignees by their username.",
|
"description": "Search labels by label text.",
|
||||||
"name": "s",
|
"name": "s",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The assignees",
|
"description": "The labels",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/models.User"
|
"$ref": "#/definitions/models.Label"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -378,7 +378,7 @@
|
||||||
"JWTKeyAuth": []
|
"JWTKeyAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Returns a team by its ID.",
|
"description": "Returns a list by its ID.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
@ -386,13 +386,13 @@
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"team"
|
"list"
|
||||||
],
|
],
|
||||||
"summary": "Gets one team",
|
"summary": "Gets one list",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Team ID",
|
"description": "List ID",
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
|
@ -400,14 +400,14 @@
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The team",
|
"description": "The list",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"$ref": "#/definitions/models.Team"
|
"$ref": "#/definitions/models.List"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"403": {
|
"403": {
|
||||||
"description": "The user does not have access to the team",
|
"description": "The user does not have access to the list",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"$ref": "#/definitions/code.vikunja.io/web.HTTPError"
|
"$ref": "#/definitions/code.vikunja.io/web.HTTPError"
|
||||||
|
@ -2306,6 +2306,138 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/tasks/all/{sortby}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns all tasks on any list the user has access to.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"task"
|
||||||
|
],
|
||||||
|
"summary": "Get tasks sorted",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.",
|
||||||
|
"name": "p",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search tasks by task text.",
|
||||||
|
"name": "s",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.",
|
||||||
|
"name": "sortby",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The tasks",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.List"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/models.Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/tasks/all/{sortby}/{startdate}/{enddate}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"JWTKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns all tasks on any list the user has access to.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"task"
|
||||||
|
],
|
||||||
|
"summary": "Get tasks sorted and within a date range",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.",
|
||||||
|
"name": "p",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Search tasks by task text.",
|
||||||
|
"name": "s",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.",
|
||||||
|
"name": "sortby",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The start date parameter. Expects a unix timestamp.",
|
||||||
|
"name": "startdate",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "The end date parameter. Expects a unix timestamp.",
|
||||||
|
"name": "enddate",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "The tasks",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/models.List"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"$ref": "#/definitions/models.Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/tasks/bulk": {
|
"/tasks/bulk": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
|
@ -639,14 +639,15 @@ paths:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Returns an array with all assignees for this task.
|
description: Returns all labels which are either created by the user or associated
|
||||||
|
with a task the user has at least read-access to.
|
||||||
parameters:
|
parameters:
|
||||||
- description: The page number. Used for pagination. If not provided, the first
|
- description: The page number. Used for pagination. If not provided, the first
|
||||||
page of results is returned.
|
page of results is returned.
|
||||||
in: query
|
in: query
|
||||||
name: p
|
name: p
|
||||||
type: integer
|
type: integer
|
||||||
- description: Search assignees by their username.
|
- description: Search labels by label text.
|
||||||
in: query
|
in: query
|
||||||
name: s
|
name: s
|
||||||
type: string
|
type: string
|
||||||
|
@ -654,10 +655,10 @@ paths:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The assignees
|
description: The labels
|
||||||
schema:
|
schema:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/models.User'
|
$ref: '#/definitions/models.Label'
|
||||||
type: array
|
type: array
|
||||||
"500":
|
"500":
|
||||||
description: Internal error
|
description: Internal error
|
||||||
|
@ -666,9 +667,9 @@ paths:
|
||||||
type: object
|
type: object
|
||||||
security:
|
security:
|
||||||
- JWTKeyAuth: []
|
- JWTKeyAuth: []
|
||||||
summary: Get all assignees for a task
|
summary: Get all labels a user has access to
|
||||||
tags:
|
tags:
|
||||||
- assignees
|
- labels
|
||||||
put:
|
put:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
@ -912,9 +913,9 @@ paths:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Returns a team by its ID.
|
description: Returns a list by its ID.
|
||||||
parameters:
|
parameters:
|
||||||
- description: Team ID
|
- description: List ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
|
@ -923,12 +924,12 @@ paths:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The team
|
description: The list
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/models.Team'
|
$ref: '#/definitions/models.List'
|
||||||
type: object
|
type: object
|
||||||
"403":
|
"403":
|
||||||
description: The user does not have access to the team
|
description: The user does not have access to the list
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/code.vikunja.io/web.HTTPError'
|
$ref: '#/definitions/code.vikunja.io/web.HTTPError'
|
||||||
type: object
|
type: object
|
||||||
|
@ -939,9 +940,9 @@ paths:
|
||||||
type: object
|
type: object
|
||||||
security:
|
security:
|
||||||
- JWTKeyAuth: []
|
- JWTKeyAuth: []
|
||||||
summary: Gets one team
|
summary: Gets one list
|
||||||
tags:
|
tags:
|
||||||
- team
|
- list
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
@ -2564,6 +2565,96 @@ paths:
|
||||||
summary: Get tasks
|
summary: Get tasks
|
||||||
tags:
|
tags:
|
||||||
- task
|
- task
|
||||||
|
/tasks/all/{sortby}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Returns all tasks on any list the user has access to.
|
||||||
|
parameters:
|
||||||
|
- description: The page number. Used for pagination. If not provided, the first
|
||||||
|
page of results is returned.
|
||||||
|
in: query
|
||||||
|
name: p
|
||||||
|
type: integer
|
||||||
|
- description: Search tasks by task text.
|
||||||
|
in: query
|
||||||
|
name: s
|
||||||
|
type: string
|
||||||
|
- description: The sorting parameter. Possible values to sort by are priority,
|
||||||
|
prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.
|
||||||
|
in: path
|
||||||
|
name: sortby
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The tasks
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.List'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.Message'
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- JWTKeyAuth: []
|
||||||
|
summary: Get tasks sorted
|
||||||
|
tags:
|
||||||
|
- task
|
||||||
|
/tasks/all/{sortby}/{startdate}/{enddate}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Returns all tasks on any list the user has access to.
|
||||||
|
parameters:
|
||||||
|
- description: The page number. Used for pagination. If not provided, the first
|
||||||
|
page of results is returned.
|
||||||
|
in: query
|
||||||
|
name: p
|
||||||
|
type: integer
|
||||||
|
- description: Search tasks by task text.
|
||||||
|
in: query
|
||||||
|
name: s
|
||||||
|
type: string
|
||||||
|
- description: The sorting parameter. Possible values to sort by are priority,
|
||||||
|
prioritydesc, priorityasc, dueadate, dueadatedesc, dueadateasc.
|
||||||
|
in: path
|
||||||
|
name: sortby
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: The start date parameter. Expects a unix timestamp.
|
||||||
|
in: path
|
||||||
|
name: startdate
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: The end date parameter. Expects a unix timestamp.
|
||||||
|
in: path
|
||||||
|
name: enddate
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The tasks
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/models.List'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/models.Message'
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- JWTKeyAuth: []
|
||||||
|
summary: Get tasks sorted and within a date range
|
||||||
|
tags:
|
||||||
|
- task
|
||||||
/tasks/bulk:
|
/tasks/bulk:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|
5
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
5
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
TAGS
|
||||||
|
tags
|
||||||
|
.*.swp
|
||||||
|
tomlcheck/tomlcheck
|
||||||
|
toml.test
|
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.1
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- 1.5
|
||||||
|
- 1.6
|
||||||
|
- tip
|
||||||
|
install:
|
||||||
|
- go install ./...
|
||||||
|
- go get github.com/BurntSushi/toml-test
|
||||||
|
script:
|
||||||
|
- export PATH="$PATH:$HOME/gopath/bin"
|
||||||
|
- make test
|
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
Normal file
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Compatible with TOML version
|
||||||
|
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
|
||||||
|
|
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013 TOML authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
Normal file
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
install:
|
||||||
|
go install ./...
|
||||||
|
|
||||||
|
test: install
|
||||||
|
go test -v
|
||||||
|
toml-test toml-test-decoder
|
||||||
|
toml-test -encoder toml-test-encoder
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
gofmt -w *.go */*.go
|
||||||
|
colcheck *.go */*.go
|
||||||
|
|
||||||
|
tags:
|
||||||
|
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
|
||||||
|
|
||||||
|
push:
|
||||||
|
git push origin master
|
||||||
|
git push github master
|
||||||
|
|
218
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
218
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
## TOML parser and encoder for Go with reflection
|
||||||
|
|
||||||
|
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||||
|
reflection interface similar to Go's standard library `json` and `xml`
|
||||||
|
packages. This package also supports the `encoding.TextUnmarshaler` and
|
||||||
|
`encoding.TextMarshaler` interfaces so that you can define custom data
|
||||||
|
representations. (There is an example of this below.)
|
||||||
|
|
||||||
|
Spec: https://github.com/toml-lang/toml
|
||||||
|
|
||||||
|
Compatible with TOML version
|
||||||
|
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||||
|
|
||||||
|
Documentation: https://godoc.org/github.com/BurntSushi/toml
|
||||||
|
|
||||||
|
Installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/BurntSushi/toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Try the toml validator:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/BurntSushi/toml/cmd/tomlv
|
||||||
|
tomlv some-toml-file.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml)
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
This package passes all tests in
|
||||||
|
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
||||||
|
and the encoder.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
This package works similarly to how the Go standard library handles `XML`
|
||||||
|
and `JSON`. Namely, data is loaded into Go values via reflection.
|
||||||
|
|
||||||
|
For the simplest example, consider some TOML file as just a list of keys
|
||||||
|
and values:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
Age = 25
|
||||||
|
Cats = [ "Cauchy", "Plato" ]
|
||||||
|
Pi = 3.14
|
||||||
|
Perfection = [ 6, 28, 496, 8128 ]
|
||||||
|
DOB = 1987-07-05T05:45:00Z
|
||||||
|
```
|
||||||
|
|
||||||
|
Which could be defined in Go as:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
Age int
|
||||||
|
Cats []string
|
||||||
|
Pi float64
|
||||||
|
Perfection []int
|
||||||
|
DOB time.Time // requires `import time`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then decoded with:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var conf Config
|
||||||
|
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use struct tags if your struct field name doesn't map to a TOML
|
||||||
|
key value directly:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
some_key_NAME = "wat"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TOML struct {
|
||||||
|
ObscureKey string `toml:"some_key_NAME"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the `encoding.TextUnmarshaler` interface
|
||||||
|
|
||||||
|
Here's an example that automatically parses duration strings into
|
||||||
|
`time.Duration` values:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[song]]
|
||||||
|
name = "Thunder Road"
|
||||||
|
duration = "4m49s"
|
||||||
|
|
||||||
|
[[song]]
|
||||||
|
name = "Stairway to Heaven"
|
||||||
|
duration = "8m03s"
|
||||||
|
```
|
||||||
|
|
||||||
|
Which can be decoded with:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type song struct {
|
||||||
|
Name string
|
||||||
|
Duration duration
|
||||||
|
}
|
||||||
|
type songs struct {
|
||||||
|
Song []song
|
||||||
|
}
|
||||||
|
var favorites songs
|
||||||
|
if _, err := toml.Decode(blob, &favorites); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range favorites.Song {
|
||||||
|
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And you'll also need a `duration` type that satisfies the
|
||||||
|
`encoding.TextUnmarshaler` interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type duration struct {
|
||||||
|
time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *duration) UnmarshalText(text []byte) error {
|
||||||
|
var err error
|
||||||
|
d.Duration, err = time.ParseDuration(string(text))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### More complex usage
|
||||||
|
|
||||||
|
Here's an example of how to load the example from the official spec page:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# This is a TOML document. Boom.
|
||||||
|
|
||||||
|
title = "TOML Example"
|
||||||
|
|
||||||
|
[owner]
|
||||||
|
name = "Tom Preston-Werner"
|
||||||
|
organization = "GitHub"
|
||||||
|
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
||||||
|
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
||||||
|
|
||||||
|
[database]
|
||||||
|
server = "192.168.1.1"
|
||||||
|
ports = [ 8001, 8001, 8002 ]
|
||||||
|
connection_max = 5000
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[servers]
|
||||||
|
|
||||||
|
# You can indent as you please. Tabs or spaces. TOML don't care.
|
||||||
|
[servers.alpha]
|
||||||
|
ip = "10.0.0.1"
|
||||||
|
dc = "eqdc10"
|
||||||
|
|
||||||
|
[servers.beta]
|
||||||
|
ip = "10.0.0.2"
|
||||||
|
dc = "eqdc10"
|
||||||
|
|
||||||
|
[clients]
|
||||||
|
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
||||||
|
|
||||||
|
# Line breaks are OK when inside arrays
|
||||||
|
hosts = [
|
||||||
|
"alpha",
|
||||||
|
"omega"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
And the corresponding Go types are:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type tomlConfig struct {
|
||||||
|
Title string
|
||||||
|
Owner ownerInfo
|
||||||
|
DB database `toml:"database"`
|
||||||
|
Servers map[string]server
|
||||||
|
Clients clients
|
||||||
|
}
|
||||||
|
|
||||||
|
type ownerInfo struct {
|
||||||
|
Name string
|
||||||
|
Org string `toml:"organization"`
|
||||||
|
Bio string
|
||||||
|
DOB time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type database struct {
|
||||||
|
Server string
|
||||||
|
Ports []int
|
||||||
|
ConnMax int `toml:"connection_max"`
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct {
|
||||||
|
IP string
|
||||||
|
DC string
|
||||||
|
}
|
||||||
|
|
||||||
|
type clients struct {
|
||||||
|
Data [][]interface{}
|
||||||
|
Hosts []string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that a case insensitive match will be tried if an exact match can't be
|
||||||
|
found.
|
||||||
|
|
||||||
|
A working example of the above can be found in `_examples/example.{go,toml}`.
|
509
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
509
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
|
@ -0,0 +1,509 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func e(format string, args ...interface{}) error {
|
||||||
|
return fmt.Errorf("toml: "+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||||
|
// TOML description of themselves.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
UnmarshalTOML(interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
||||||
|
func Unmarshal(p []byte, v interface{}) error {
|
||||||
|
_, err := Decode(string(p), v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||||
|
// When using the various `Decode*` functions, the type `Primitive` may
|
||||||
|
// be given to any value, and its decoding will be delayed.
|
||||||
|
//
|
||||||
|
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
|
||||||
|
//
|
||||||
|
// The underlying representation of a `Primitive` value is subject to change.
|
||||||
|
// Do not rely on it.
|
||||||
|
//
|
||||||
|
// N.B. Primitive values are still parsed, so using them will only avoid
|
||||||
|
// the overhead of reflection. They can be useful when you don't know the
|
||||||
|
// exact type of TOML data until run time.
|
||||||
|
type Primitive struct {
|
||||||
|
undecoded interface{}
|
||||||
|
context Key
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
|
//
|
||||||
|
// Use MetaData.PrimitiveDecode instead.
|
||||||
|
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||||
|
md := MetaData{decoded: make(map[string]bool)}
|
||||||
|
return md.unify(primValue.undecoded, rvalue(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||||||
|
// decodes a TOML value that has already been parsed. Valid primitive values
|
||||||
|
// can *only* be obtained from values filled by the decoder functions,
|
||||||
|
// including this method. (i.e., `v` may contain more `Primitive`
|
||||||
|
// values.)
|
||||||
|
//
|
||||||
|
// Meta data for primitive values is included in the meta data returned by
|
||||||
|
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
||||||
|
// method will only reflect keys that were decoded. Namely, any keys hidden
|
||||||
|
// behind a Primitive will be considered undecoded. Executing this method will
|
||||||
|
// update the undecoded keys in the meta data. (See the example.)
|
||||||
|
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||||
|
md.context = primValue.context
|
||||||
|
defer func() { md.context = nil }()
|
||||||
|
return md.unify(primValue.undecoded, rvalue(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode will decode the contents of `data` in TOML format into a pointer
|
||||||
|
// `v`.
|
||||||
|
//
|
||||||
|
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
|
||||||
|
// used interchangeably.)
|
||||||
|
//
|
||||||
|
// TOML arrays of tables correspond to either a slice of structs or a slice
|
||||||
|
// of maps.
|
||||||
|
//
|
||||||
|
// TOML datetimes correspond to Go `time.Time` values.
|
||||||
|
//
|
||||||
|
// All other TOML types (float, string, int, bool and array) correspond
|
||||||
|
// to the obvious Go types.
|
||||||
|
//
|
||||||
|
// An exception to the above rules is if a type implements the
|
||||||
|
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
|
||||||
|
// (floats, strings, integers, booleans and datetimes) will be converted to
|
||||||
|
// a byte string and given to the value's UnmarshalText method. See the
|
||||||
|
// Unmarshaler example for a demonstration with time duration strings.
|
||||||
|
//
|
||||||
|
// Key mapping
|
||||||
|
//
|
||||||
|
// TOML keys can map to either keys in a Go map or field names in a Go
|
||||||
|
// struct. The special `toml` struct tag may be used to map TOML keys to
|
||||||
|
// struct fields that don't match the key name exactly. (See the example.)
|
||||||
|
// A case insensitive match to struct names will be tried if an exact match
|
||||||
|
// can't be found.
|
||||||
|
//
|
||||||
|
// The mapping between TOML values and Go values is loose. That is, there
|
||||||
|
// may exist TOML values that cannot be placed into your representation, and
|
||||||
|
// there may be parts of your representation that do not correspond to
|
||||||
|
// TOML values. This loose mapping can be made stricter by using the IsDefined
|
||||||
|
// and/or Undecoded methods on the MetaData returned.
|
||||||
|
//
|
||||||
|
// This decoder will not handle cyclic types. If a cyclic type is passed,
|
||||||
|
// `Decode` will not terminate.
|
||||||
|
func Decode(data string, v interface{}) (MetaData, error) {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() != reflect.Ptr {
|
||||||
|
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
if rv.IsNil() {
|
||||||
|
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
p, err := parse(data)
|
||||||
|
if err != nil {
|
||||||
|
return MetaData{}, err
|
||||||
|
}
|
||||||
|
md := MetaData{
|
||||||
|
p.mapping, p.types, p.ordered,
|
||||||
|
make(map[string]bool, len(p.ordered)), nil,
|
||||||
|
}
|
||||||
|
return md, md.unify(p.mapping, indirect(rv))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFile is just like Decode, except it will automatically read the
|
||||||
|
// contents of the file at `fpath` and decode it for you.
|
||||||
|
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
|
||||||
|
bs, err := ioutil.ReadFile(fpath)
|
||||||
|
if err != nil {
|
||||||
|
return MetaData{}, err
|
||||||
|
}
|
||||||
|
return Decode(string(bs), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReader is just like Decode, except it will consume all bytes
|
||||||
|
// from the reader and decode it for you.
|
||||||
|
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
||||||
|
bs, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return MetaData{}, err
|
||||||
|
}
|
||||||
|
return Decode(string(bs), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unify performs a sort of type unification based on the structure of `rv`,
|
||||||
|
// which is the client representation.
|
||||||
|
//
|
||||||
|
// Any type mismatch produces an error. Finding a type that we don't know
|
||||||
|
// how to handle produces an unsupported type error.
|
||||||
|
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||||
|
|
||||||
|
// Special case. Look for a `Primitive` value.
|
||||||
|
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
||||||
|
// Save the undecoded data and the key context into the primitive
|
||||||
|
// value.
|
||||||
|
context := make(Key, len(md.context))
|
||||||
|
copy(context, md.context)
|
||||||
|
rv.Set(reflect.ValueOf(Primitive{
|
||||||
|
undecoded: data,
|
||||||
|
context: context,
|
||||||
|
}))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case. Unmarshaler Interface support.
|
||||||
|
if rv.CanAddr() {
|
||||||
|
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
||||||
|
return v.UnmarshalTOML(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case. Handle time.Time values specifically.
|
||||||
|
// TODO: Remove this code when we decide to drop support for Go 1.1.
|
||||||
|
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
|
||||||
|
// interfaces.
|
||||||
|
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
|
||||||
|
return md.unifyDatetime(data, rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
||||||
|
if v, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||||
|
return md.unifyText(data, v)
|
||||||
|
}
|
||||||
|
// BUG(burntsushi)
|
||||||
|
// The behavior here is incorrect whenever a Go type satisfies the
|
||||||
|
// encoding.TextUnmarshaler interface but also corresponds to a TOML
|
||||||
|
// hash or array. In particular, the unmarshaler should only be applied
|
||||||
|
// to primitive TOML values. But at this point, it will be applied to
|
||||||
|
// all kinds of values and produce an incorrect error whenever those values
|
||||||
|
// are hashes or arrays (including arrays of tables).
|
||||||
|
|
||||||
|
k := rv.Kind()
|
||||||
|
|
||||||
|
// laziness
|
||||||
|
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||||
|
return md.unifyInt(data, rv)
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case reflect.Ptr:
|
||||||
|
elem := reflect.New(rv.Type().Elem())
|
||||||
|
err := md.unify(data, reflect.Indirect(elem))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rv.Set(elem)
|
||||||
|
return nil
|
||||||
|
case reflect.Struct:
|
||||||
|
return md.unifyStruct(data, rv)
|
||||||
|
case reflect.Map:
|
||||||
|
return md.unifyMap(data, rv)
|
||||||
|
case reflect.Array:
|
||||||
|
return md.unifyArray(data, rv)
|
||||||
|
case reflect.Slice:
|
||||||
|
return md.unifySlice(data, rv)
|
||||||
|
case reflect.String:
|
||||||
|
return md.unifyString(data, rv)
|
||||||
|
case reflect.Bool:
|
||||||
|
return md.unifyBool(data, rv)
|
||||||
|
case reflect.Interface:
|
||||||
|
// we only support empty interfaces.
|
||||||
|
if rv.NumMethod() > 0 {
|
||||||
|
return e("unsupported type %s", rv.Type())
|
||||||
|
}
|
||||||
|
return md.unifyAnything(data, rv)
|
||||||
|
case reflect.Float32:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Float64:
|
||||||
|
return md.unifyFloat64(data, rv)
|
||||||
|
}
|
||||||
|
return e("unsupported type %s", rv.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||||
|
tmap, ok := mapping.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
if mapping == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e("type mismatch for %s: expected table but found %T",
|
||||||
|
rv.Type().String(), mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, datum := range tmap {
|
||||||
|
var f *field
|
||||||
|
fields := cachedTypeFields(rv.Type())
|
||||||
|
for i := range fields {
|
||||||
|
ff := &fields[i]
|
||||||
|
if ff.name == key {
|
||||||
|
f = ff
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if f == nil && strings.EqualFold(ff.name, key) {
|
||||||
|
f = ff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f != nil {
|
||||||
|
subv := rv
|
||||||
|
for _, i := range f.index {
|
||||||
|
subv = indirect(subv.Field(i))
|
||||||
|
}
|
||||||
|
if isUnifiable(subv) {
|
||||||
|
md.decoded[md.context.add(key).String()] = true
|
||||||
|
md.context = append(md.context, key)
|
||||||
|
if err := md.unify(datum, subv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
md.context = md.context[0 : len(md.context)-1]
|
||||||
|
} else if f.name != "" {
|
||||||
|
// Bad user! No soup for you!
|
||||||
|
return e("cannot write unexported field %s.%s",
|
||||||
|
rv.Type().String(), f.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||||
|
tmap, ok := mapping.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
if tmap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("map", mapping)
|
||||||
|
}
|
||||||
|
if rv.IsNil() {
|
||||||
|
rv.Set(reflect.MakeMap(rv.Type()))
|
||||||
|
}
|
||||||
|
for k, v := range tmap {
|
||||||
|
md.decoded[md.context.add(k).String()] = true
|
||||||
|
md.context = append(md.context, k)
|
||||||
|
|
||||||
|
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||||
|
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||||
|
if err := md.unify(v, rvval); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
md.context = md.context[0 : len(md.context)-1]
|
||||||
|
|
||||||
|
rvkey.SetString(k)
|
||||||
|
rv.SetMapIndex(rvkey, rvval)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||||
|
datav := reflect.ValueOf(data)
|
||||||
|
if datav.Kind() != reflect.Slice {
|
||||||
|
if !datav.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("slice", data)
|
||||||
|
}
|
||||||
|
sliceLen := datav.Len()
|
||||||
|
if sliceLen != rv.Len() {
|
||||||
|
return e("expected array length %d; got TOML array of length %d",
|
||||||
|
rv.Len(), sliceLen)
|
||||||
|
}
|
||||||
|
return md.unifySliceArray(datav, rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||||
|
datav := reflect.ValueOf(data)
|
||||||
|
if datav.Kind() != reflect.Slice {
|
||||||
|
if !datav.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("slice", data)
|
||||||
|
}
|
||||||
|
n := datav.Len()
|
||||||
|
if rv.IsNil() || rv.Cap() < n {
|
||||||
|
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||||
|
}
|
||||||
|
rv.SetLen(n)
|
||||||
|
return md.unifySliceArray(datav, rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||||
|
sliceLen := data.Len()
|
||||||
|
for i := 0; i < sliceLen; i++ {
|
||||||
|
v := data.Index(i).Interface()
|
||||||
|
sliceval := indirect(rv.Index(i))
|
||||||
|
if err := md.unify(v, sliceval); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||||||
|
if _, ok := data.(time.Time); ok {
|
||||||
|
rv.Set(reflect.ValueOf(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("time.Time", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||||
|
if s, ok := data.(string); ok {
|
||||||
|
rv.SetString(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("string", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||||
|
if num, ok := data.(float64); ok {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Float32:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Float64:
|
||||||
|
rv.SetFloat(num)
|
||||||
|
default:
|
||||||
|
panic("bug")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("float", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||||
|
if num, ok := data.(int64); ok {
|
||||||
|
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int64:
|
||||||
|
// No bounds checking necessary.
|
||||||
|
case reflect.Int8:
|
||||||
|
if num < math.MinInt8 || num > math.MaxInt8 {
|
||||||
|
return e("value %d is out of range for int8", num)
|
||||||
|
}
|
||||||
|
case reflect.Int16:
|
||||||
|
if num < math.MinInt16 || num > math.MaxInt16 {
|
||||||
|
return e("value %d is out of range for int16", num)
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
if num < math.MinInt32 || num > math.MaxInt32 {
|
||||||
|
return e("value %d is out of range for int32", num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rv.SetInt(num)
|
||||||
|
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
||||||
|
unum := uint64(num)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Uint, reflect.Uint64:
|
||||||
|
// No bounds checking necessary.
|
||||||
|
case reflect.Uint8:
|
||||||
|
if num < 0 || unum > math.MaxUint8 {
|
||||||
|
return e("value %d is out of range for uint8", num)
|
||||||
|
}
|
||||||
|
case reflect.Uint16:
|
||||||
|
if num < 0 || unum > math.MaxUint16 {
|
||||||
|
return e("value %d is out of range for uint16", num)
|
||||||
|
}
|
||||||
|
case reflect.Uint32:
|
||||||
|
if num < 0 || unum > math.MaxUint32 {
|
||||||
|
return e("value %d is out of range for uint32", num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rv.SetUint(unum)
|
||||||
|
} else {
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("integer", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||||
|
if b, ok := data.(bool); ok {
|
||||||
|
rv.SetBool(b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return badtype("boolean", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||||
|
rv.Set(reflect.ValueOf(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
||||||
|
var s string
|
||||||
|
switch sdata := data.(type) {
|
||||||
|
case TextMarshaler:
|
||||||
|
text, err := sdata.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s = string(text)
|
||||||
|
case fmt.Stringer:
|
||||||
|
s = sdata.String()
|
||||||
|
case string:
|
||||||
|
s = sdata
|
||||||
|
case bool:
|
||||||
|
s = fmt.Sprintf("%v", sdata)
|
||||||
|
case int64:
|
||||||
|
s = fmt.Sprintf("%d", sdata)
|
||||||
|
case float64:
|
||||||
|
s = fmt.Sprintf("%f", sdata)
|
||||||
|
default:
|
||||||
|
return badtype("primitive (string-like)", data)
|
||||||
|
}
|
||||||
|
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||||
|
func rvalue(v interface{}) reflect.Value {
|
||||||
|
return indirect(reflect.ValueOf(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirect returns the value pointed to by a pointer.
|
||||||
|
// Pointers are followed until the value is not a pointer.
|
||||||
|
// New values are allocated for each nil pointer.
|
||||||
|
//
|
||||||
|
// An exception to this rule is if the value satisfies an interface of
|
||||||
|
// interest to us (like encoding.TextUnmarshaler).
|
||||||
|
func indirect(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() != reflect.Ptr {
|
||||||
|
if v.CanSet() {
|
||||||
|
pv := v.Addr()
|
||||||
|
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
||||||
|
return pv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v.IsNil() {
|
||||||
|
v.Set(reflect.New(v.Type().Elem()))
|
||||||
|
}
|
||||||
|
return indirect(reflect.Indirect(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUnifiable(rv reflect.Value) bool {
|
||||||
|
if rv.CanSet() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func badtype(expected string, data interface{}) error {
|
||||||
|
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||||||
|
}
|
121
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
Normal file
121
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// MetaData allows access to meta information about TOML data that may not
|
||||||
|
// be inferrable via reflection. In particular, whether a key has been defined
|
||||||
|
// and the TOML type of a key.
|
||||||
|
type MetaData struct {
|
||||||
|
mapping map[string]interface{}
|
||||||
|
types map[string]tomlType
|
||||||
|
keys []Key
|
||||||
|
decoded map[string]bool
|
||||||
|
context Key // Used only during decoding.
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDefined returns true if the key given exists in the TOML data. The key
|
||||||
|
// should be specified hierarchially. e.g.,
|
||||||
|
//
|
||||||
|
// // access the TOML key 'a.b.c'
|
||||||
|
// IsDefined("a", "b", "c")
|
||||||
|
//
|
||||||
|
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||||
|
func (md *MetaData) IsDefined(key ...string) bool {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash map[string]interface{}
|
||||||
|
var ok bool
|
||||||
|
var hashOrVal interface{} = md.mapping
|
||||||
|
for _, k := range key {
|
||||||
|
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if hashOrVal, ok = hash[k]; !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns a string representation of the type of the key specified.
|
||||||
|
//
|
||||||
|
// Type will return the empty string if given an empty key or a key that
|
||||||
|
// does not exist. Keys are case sensitive.
|
||||||
|
func (md *MetaData) Type(key ...string) string {
|
||||||
|
fullkey := strings.Join(key, ".")
|
||||||
|
if typ, ok := md.types[fullkey]; ok {
|
||||||
|
return typ.typeString()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
|
||||||
|
// to get values of this type.
|
||||||
|
type Key []string
|
||||||
|
|
||||||
|
func (k Key) String() string {
|
||||||
|
return strings.Join(k, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Key) maybeQuotedAll() string {
|
||||||
|
var ss []string
|
||||||
|
for i := range k {
|
||||||
|
ss = append(ss, k.maybeQuoted(i))
|
||||||
|
}
|
||||||
|
return strings.Join(ss, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Key) maybeQuoted(i int) string {
|
||||||
|
quote := false
|
||||||
|
for _, c := range k[i] {
|
||||||
|
if !isBareKeyChar(c) {
|
||||||
|
quote = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if quote {
|
||||||
|
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
|
||||||
|
}
|
||||||
|
return k[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Key) add(piece string) Key {
|
||||||
|
newKey := make(Key, len(k)+1)
|
||||||
|
copy(newKey, k)
|
||||||
|
newKey[len(k)] = piece
|
||||||
|
return newKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||||
|
// Each key is itself a slice, where the first element is the top of the
|
||||||
|
// hierarchy and the last is the most specific.
|
||||||
|
//
|
||||||
|
// The list will have the same order as the keys appeared in the TOML data.
|
||||||
|
//
|
||||||
|
// All keys returned are non-empty.
|
||||||
|
func (md *MetaData) Keys() []Key {
|
||||||
|
return md.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undecoded returns all keys that have not been decoded in the order in which
|
||||||
|
// they appear in the original TOML document.
|
||||||
|
//
|
||||||
|
// This includes keys that haven't been decoded because of a Primitive value.
|
||||||
|
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||||
|
//
|
||||||
|
// Also note that decoding into an empty interface will result in no decoding,
|
||||||
|
// and so no keys will be considered decoded.
|
||||||
|
//
|
||||||
|
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||||
|
// that do not have a concrete type in your representation.
|
||||||
|
func (md *MetaData) Undecoded() []Key {
|
||||||
|
undecoded := make([]Key, 0, len(md.keys))
|
||||||
|
for _, key := range md.keys {
|
||||||
|
if !md.decoded[key.String()] {
|
||||||
|
undecoded = append(undecoded, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undecoded
|
||||||
|
}
|
27
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
27
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
Package toml provides facilities for decoding and encoding TOML configuration
|
||||||
|
files via reflection. There is also support for delaying decoding with
|
||||||
|
the Primitive type, and querying the set of keys in a TOML document with the
|
||||||
|
MetaData type.
|
||||||
|
|
||||||
|
The specification implemented: https://github.com/toml-lang/toml
|
||||||
|
|
||||||
|
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
|
||||||
|
whether a file is a valid TOML document. It can also be used to print the
|
||||||
|
type of each key in a TOML document.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
|
||||||
|
There are two important types of tests used for this package. The first is
|
||||||
|
contained inside '*_test.go' files and uses the standard Go unit testing
|
||||||
|
framework. These tests are primarily devoted to holistically testing the
|
||||||
|
decoder and encoder.
|
||||||
|
|
||||||
|
The second type of testing is used to verify the implementation's adherence
|
||||||
|
to the TOML specification. These tests have been factored into their own
|
||||||
|
project: https://github.com/BurntSushi/toml-test
|
||||||
|
|
||||||
|
The reason the tests are in a separate project is so that they can be used by
|
||||||
|
any implementation of TOML. Namely, it is language agnostic.
|
||||||
|
*/
|
||||||
|
package toml
|
568
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
568
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
|
@ -0,0 +1,568 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tomlEncodeError struct{ error }
|
||||||
|
|
||||||
|
var (
|
||||||
|
errArrayMixedElementTypes = errors.New(
|
||||||
|
"toml: cannot encode array with mixed element types")
|
||||||
|
errArrayNilElement = errors.New(
|
||||||
|
"toml: cannot encode array with nil element")
|
||||||
|
errNonString = errors.New(
|
||||||
|
"toml: cannot encode a map with non-string key type")
|
||||||
|
errAnonNonStruct = errors.New(
|
||||||
|
"toml: cannot encode an anonymous field that is not a struct")
|
||||||
|
errArrayNoTable = errors.New(
|
||||||
|
"toml: TOML array element cannot contain a table")
|
||||||
|
errNoKey = errors.New(
|
||||||
|
"toml: top-level values must be Go maps or structs")
|
||||||
|
errAnything = errors.New("") // used in testing
|
||||||
|
)
|
||||||
|
|
||||||
|
var quotedReplacer = strings.NewReplacer(
|
||||||
|
"\t", "\\t",
|
||||||
|
"\n", "\\n",
|
||||||
|
"\r", "\\r",
|
||||||
|
"\"", "\\\"",
|
||||||
|
"\\", "\\\\",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoder controls the encoding of Go values to a TOML document to some
|
||||||
|
// io.Writer.
|
||||||
|
//
|
||||||
|
// The indentation level can be controlled with the Indent field.
|
||||||
|
type Encoder struct {
|
||||||
|
// A single indentation level. By default it is two spaces.
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// hasWritten is whether we have written any output to w yet.
|
||||||
|
hasWritten bool
|
||||||
|
w *bufio.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
||||||
|
// given. By default, a single indentation level is 2 spaces.
|
||||||
|
func NewEncoder(w io.Writer) *Encoder {
|
||||||
|
return &Encoder{
|
||||||
|
w: bufio.NewWriter(w),
|
||||||
|
Indent: " ",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode writes a TOML representation of the Go value to the underlying
|
||||||
|
// io.Writer. If the value given cannot be encoded to a valid TOML document,
|
||||||
|
// then an error is returned.
|
||||||
|
//
|
||||||
|
// The mapping between Go values and TOML values should be precisely the same
|
||||||
|
// as for the Decode* functions. Similarly, the TextMarshaler interface is
|
||||||
|
// supported by encoding the resulting bytes as strings. (If you want to write
|
||||||
|
// arbitrary binary data then you will need to use something like base64 since
|
||||||
|
// TOML does not have any binary types.)
|
||||||
|
//
|
||||||
|
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
|
||||||
|
// sub-hashes are encoded first.
|
||||||
|
//
|
||||||
|
// If a Go map is encoded, then its keys are sorted alphabetically for
|
||||||
|
// deterministic output. More control over this behavior may be provided if
|
||||||
|
// there is demand for it.
|
||||||
|
//
|
||||||
|
// Encoding Go values without a corresponding TOML representation---like map
|
||||||
|
// types with non-string keys---will cause an error to be returned. Similarly
|
||||||
|
// for mixed arrays/slices, arrays/slices with nil elements, embedded
|
||||||
|
// non-struct types and nested slices containing maps or structs.
|
||||||
|
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
|
||||||
|
// and so is []map[string][]string.)
|
||||||
|
func (enc *Encoder) Encode(v interface{}) error {
|
||||||
|
rv := eindirect(reflect.ValueOf(v))
|
||||||
|
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return enc.w.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if terr, ok := r.(tomlEncodeError); ok {
|
||||||
|
err = terr.error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
enc.encode(key, rv)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||||
|
// Special case. Time needs to be in ISO8601 format.
|
||||||
|
// Special case. If we can marshal the type to text, then we used that.
|
||||||
|
// Basically, this prevents the encoder for handling these types as
|
||||||
|
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
||||||
|
switch rv.Interface().(type) {
|
||||||
|
case time.Time, TextMarshaler:
|
||||||
|
enc.keyEqElement(key, rv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k := rv.Kind()
|
||||||
|
switch k {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64,
|
||||||
|
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||||
|
enc.keyEqElement(key, rv)
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||||
|
enc.eArrayOfTables(key, rv)
|
||||||
|
} else {
|
||||||
|
enc.keyEqElement(key, rv)
|
||||||
|
}
|
||||||
|
case reflect.Interface:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc.encode(key, rv.Elem())
|
||||||
|
case reflect.Map:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc.eTable(key, rv)
|
||||||
|
case reflect.Ptr:
|
||||||
|
if rv.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc.encode(key, rv.Elem())
|
||||||
|
case reflect.Struct:
|
||||||
|
enc.eTable(key, rv)
|
||||||
|
default:
|
||||||
|
panic(e("unsupported type for key '%s': %s", key, k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eElement encodes any value that can be an array element (primitives and
|
||||||
|
// arrays).
|
||||||
|
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||||
|
switch v := rv.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
|
// Special case time.Time as a primitive. Has to come before
|
||||||
|
// TextMarshaler below because time.Time implements
|
||||||
|
// encoding.TextMarshaler, but we need to always use UTC.
|
||||||
|
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
||||||
|
return
|
||||||
|
case TextMarshaler:
|
||||||
|
// Special case. Use text marshaler if it's available for this value.
|
||||||
|
if s, err := v.MarshalText(); err != nil {
|
||||||
|
encPanic(err)
|
||||||
|
} else {
|
||||||
|
enc.writeQuoted(string(s))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||||
|
reflect.Uint32, reflect.Uint64:
|
||||||
|
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||||
|
case reflect.Float32:
|
||||||
|
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
|
||||||
|
case reflect.Float64:
|
||||||
|
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
enc.eArrayOrSliceElement(rv)
|
||||||
|
case reflect.Interface:
|
||||||
|
enc.eElement(rv.Elem())
|
||||||
|
case reflect.String:
|
||||||
|
enc.writeQuoted(rv.String())
|
||||||
|
default:
|
||||||
|
panic(e("unexpected primitive type: %s", rv.Kind()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By the TOML spec, all floats must have a decimal with at least one
|
||||||
|
// number on either side.
|
||||||
|
func floatAddDecimal(fstr string) string {
|
||||||
|
if !strings.Contains(fstr, ".") {
|
||||||
|
return fstr + ".0"
|
||||||
|
}
|
||||||
|
return fstr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) writeQuoted(s string) {
|
||||||
|
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||||
|
length := rv.Len()
|
||||||
|
enc.wf("[")
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
elem := rv.Index(i)
|
||||||
|
enc.eElement(elem)
|
||||||
|
if i != length-1 {
|
||||||
|
enc.wf(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enc.wf("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
encPanic(errNoKey)
|
||||||
|
}
|
||||||
|
for i := 0; i < rv.Len(); i++ {
|
||||||
|
trv := rv.Index(i)
|
||||||
|
if isNil(trv) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
panicIfInvalidKey(key)
|
||||||
|
enc.newline()
|
||||||
|
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
||||||
|
enc.newline()
|
||||||
|
enc.eMapOrStruct(key, trv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||||
|
panicIfInvalidKey(key)
|
||||||
|
if len(key) == 1 {
|
||||||
|
// Output an extra newline between top-level tables.
|
||||||
|
// (The newline isn't written if nothing else has been written though.)
|
||||||
|
enc.newline()
|
||||||
|
}
|
||||||
|
if len(key) > 0 {
|
||||||
|
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
||||||
|
enc.newline()
|
||||||
|
}
|
||||||
|
enc.eMapOrStruct(key, rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
|
||||||
|
switch rv := eindirect(rv); rv.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
enc.eMap(key, rv)
|
||||||
|
case reflect.Struct:
|
||||||
|
enc.eStruct(key, rv)
|
||||||
|
default:
|
||||||
|
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
||||||
|
rt := rv.Type()
|
||||||
|
if rt.Key().Kind() != reflect.String {
|
||||||
|
encPanic(errNonString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort keys so that we have deterministic output. And write keys directly
|
||||||
|
// underneath this key first, before writing sub-structs or sub-maps.
|
||||||
|
var mapKeysDirect, mapKeysSub []string
|
||||||
|
for _, mapKey := range rv.MapKeys() {
|
||||||
|
k := mapKey.String()
|
||||||
|
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||||
|
mapKeysSub = append(mapKeysSub, k)
|
||||||
|
} else {
|
||||||
|
mapKeysDirect = append(mapKeysDirect, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var writeMapKeys = func(mapKeys []string) {
|
||||||
|
sort.Strings(mapKeys)
|
||||||
|
for _, mapKey := range mapKeys {
|
||||||
|
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
|
||||||
|
if isNil(mrv) {
|
||||||
|
// Don't write anything for nil fields.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
enc.encode(key.add(mapKey), mrv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeMapKeys(mapKeysDirect)
|
||||||
|
writeMapKeys(mapKeysSub)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
||||||
|
// Write keys for fields directly under this key first, because if we write
|
||||||
|
// a field that creates a new table, then all keys under it will be in that
|
||||||
|
// table (not the one we're writing here).
|
||||||
|
rt := rv.Type()
|
||||||
|
var fieldsDirect, fieldsSub [][]int
|
||||||
|
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||||
|
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||||
|
for i := 0; i < rt.NumField(); i++ {
|
||||||
|
f := rt.Field(i)
|
||||||
|
// skip unexported fields
|
||||||
|
if f.PkgPath != "" && !f.Anonymous {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
frv := rv.Field(i)
|
||||||
|
if f.Anonymous {
|
||||||
|
t := f.Type
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
// Treat anonymous struct fields with
|
||||||
|
// tag names as though they are not
|
||||||
|
// anonymous, like encoding/json does.
|
||||||
|
if getOptions(f.Tag).name == "" {
|
||||||
|
addFields(t, frv, f.Index)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if t.Elem().Kind() == reflect.Struct &&
|
||||||
|
getOptions(f.Tag).name == "" {
|
||||||
|
if !frv.IsNil() {
|
||||||
|
addFields(t.Elem(), frv.Elem(), f.Index)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Fall through to the normal field encoding logic below
|
||||||
|
// for non-struct anonymous fields.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||||
|
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||||
|
} else {
|
||||||
|
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFields(rt, rv, nil)
|
||||||
|
|
||||||
|
var writeFields = func(fields [][]int) {
|
||||||
|
for _, fieldIndex := range fields {
|
||||||
|
sft := rt.FieldByIndex(fieldIndex)
|
||||||
|
sf := rv.FieldByIndex(fieldIndex)
|
||||||
|
if isNil(sf) {
|
||||||
|
// Don't write anything for nil fields.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := getOptions(sft.Tag)
|
||||||
|
if opts.skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keyName := sft.Name
|
||||||
|
if opts.name != "" {
|
||||||
|
keyName = opts.name
|
||||||
|
}
|
||||||
|
if opts.omitempty && isEmpty(sf) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if opts.omitzero && isZero(sf) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
enc.encode(key.add(keyName), sf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeFields(fieldsDirect)
|
||||||
|
writeFields(fieldsSub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
||||||
|
// used to determine whether the types of array elements are mixed (which is
|
||||||
|
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
||||||
|
// element, and valueIsNil is returned as true.
|
||||||
|
|
||||||
|
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
||||||
|
// no concrete TOML type could be found.
|
||||||
|
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||||
|
if isNil(rv) || !rv.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return tomlBool
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||||
|
reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
return tomlInteger
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return tomlFloat
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
||||||
|
return tomlArrayHash
|
||||||
|
}
|
||||||
|
return tomlArray
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return tomlTypeOfGo(rv.Elem())
|
||||||
|
case reflect.String:
|
||||||
|
return tomlString
|
||||||
|
case reflect.Map:
|
||||||
|
return tomlHash
|
||||||
|
case reflect.Struct:
|
||||||
|
switch rv.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
|
return tomlDatetime
|
||||||
|
case TextMarshaler:
|
||||||
|
return tomlString
|
||||||
|
default:
|
||||||
|
return tomlHash
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unexpected reflect.Kind: " + rv.Kind().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tomlArrayType returns the element type of a TOML array. The type returned
|
||||||
|
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
||||||
|
// slize). This function may also panic if it finds a type that cannot be
|
||||||
|
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
||||||
|
// nested arrays of tables).
|
||||||
|
func tomlArrayType(rv reflect.Value) tomlType {
|
||||||
|
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
firstType := tomlTypeOfGo(rv.Index(0))
|
||||||
|
if firstType == nil {
|
||||||
|
encPanic(errArrayNilElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
rvlen := rv.Len()
|
||||||
|
for i := 1; i < rvlen; i++ {
|
||||||
|
elem := rv.Index(i)
|
||||||
|
switch elemType := tomlTypeOfGo(elem); {
|
||||||
|
case elemType == nil:
|
||||||
|
encPanic(errArrayNilElement)
|
||||||
|
case !typeEqual(firstType, elemType):
|
||||||
|
encPanic(errArrayMixedElementTypes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we have a nested array, then we must make sure that the nested
|
||||||
|
// array contains ONLY primitives.
|
||||||
|
// This checks arbitrarily nested arrays.
|
||||||
|
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
|
||||||
|
nest := tomlArrayType(eindirect(rv.Index(0)))
|
||||||
|
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
|
||||||
|
encPanic(errArrayNoTable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return firstType
|
||||||
|
}
|
||||||
|
|
||||||
|
type tagOptions struct {
|
||||||
|
skip bool // "-"
|
||||||
|
name string
|
||||||
|
omitempty bool
|
||||||
|
omitzero bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOptions(tag reflect.StructTag) tagOptions {
|
||||||
|
t := tag.Get("toml")
|
||||||
|
if t == "-" {
|
||||||
|
return tagOptions{skip: true}
|
||||||
|
}
|
||||||
|
var opts tagOptions
|
||||||
|
parts := strings.Split(t, ",")
|
||||||
|
opts.name = parts[0]
|
||||||
|
for _, s := range parts[1:] {
|
||||||
|
switch s {
|
||||||
|
case "omitempty":
|
||||||
|
opts.omitempty = true
|
||||||
|
case "omitzero":
|
||||||
|
opts.omitzero = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func isZero(rv reflect.Value) bool {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return rv.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return rv.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return rv.Float() == 0.0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmpty(rv reflect.Value) bool {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||||
|
return rv.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !rv.Bool()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) newline() {
|
||||||
|
if enc.hasWritten {
|
||||||
|
enc.wf("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
encPanic(errNoKey)
|
||||||
|
}
|
||||||
|
panicIfInvalidKey(key)
|
||||||
|
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||||
|
enc.eElement(val)
|
||||||
|
enc.newline()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||||
|
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
||||||
|
encPanic(err)
|
||||||
|
}
|
||||||
|
enc.hasWritten = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (enc *Encoder) indentStr(key Key) string {
|
||||||
|
return strings.Repeat(enc.Indent, len(key)-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encPanic(err error) {
|
||||||
|
panic(tomlEncodeError{err})
|
||||||
|
}
|
||||||
|
|
||||||
|
func eindirect(v reflect.Value) reflect.Value {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
return eindirect(v.Elem())
|
||||||
|
default:
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNil(rv reflect.Value) bool {
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||||
|
return rv.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func panicIfInvalidKey(key Key) {
|
||||||
|
for _, k := range key {
|
||||||
|
if len(k) == 0 {
|
||||||
|
encPanic(e("Key '%s' is not a valid table name. Key names "+
|
||||||
|
"cannot be empty.", key.maybeQuotedAll()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidKeyName(s string) bool {
|
||||||
|
return len(s) != 0
|
||||||
|
}
|
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
Normal file
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// +build go1.2
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
// In order to support Go 1.1, we define our own TextMarshaler and
|
||||||
|
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
|
||||||
|
// standard library interfaces.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||||
|
// so that Go 1.1 can be supported.
|
||||||
|
type TextMarshaler encoding.TextMarshaler
|
||||||
|
|
||||||
|
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||||
|
// here so that Go 1.1 can be supported.
|
||||||
|
type TextUnmarshaler encoding.TextUnmarshaler
|
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
Normal file
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// +build !go1.2
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
// These interfaces were introduced in Go 1.2, so we add them manually when
|
||||||
|
// compiling for Go 1.1.
|
||||||
|
|
||||||
|
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||||
|
// so that Go 1.1 can be supported.
|
||||||
|
type TextMarshaler interface {
|
||||||
|
MarshalText() (text []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||||
|
// here so that Go 1.1 can be supported.
|
||||||
|
type TextUnmarshaler interface {
|
||||||
|
UnmarshalText(text []byte) error
|
||||||
|
}
|
953
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
953
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
|
@ -0,0 +1,953 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type itemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
itemError itemType = iota
|
||||||
|
itemNIL // used in the parser to indicate no type
|
||||||
|
itemEOF
|
||||||
|
itemText
|
||||||
|
itemString
|
||||||
|
itemRawString
|
||||||
|
itemMultilineString
|
||||||
|
itemRawMultilineString
|
||||||
|
itemBool
|
||||||
|
itemInteger
|
||||||
|
itemFloat
|
||||||
|
itemDatetime
|
||||||
|
itemArray // the start of an array
|
||||||
|
itemArrayEnd
|
||||||
|
itemTableStart
|
||||||
|
itemTableEnd
|
||||||
|
itemArrayTableStart
|
||||||
|
itemArrayTableEnd
|
||||||
|
itemKeyStart
|
||||||
|
itemCommentStart
|
||||||
|
itemInlineTableStart
|
||||||
|
itemInlineTableEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
eof = 0
|
||||||
|
comma = ','
|
||||||
|
tableStart = '['
|
||||||
|
tableEnd = ']'
|
||||||
|
arrayTableStart = '['
|
||||||
|
arrayTableEnd = ']'
|
||||||
|
tableSep = '.'
|
||||||
|
keySep = '='
|
||||||
|
arrayStart = '['
|
||||||
|
arrayEnd = ']'
|
||||||
|
commentStart = '#'
|
||||||
|
stringStart = '"'
|
||||||
|
stringEnd = '"'
|
||||||
|
rawStringStart = '\''
|
||||||
|
rawStringEnd = '\''
|
||||||
|
inlineTableStart = '{'
|
||||||
|
inlineTableEnd = '}'
|
||||||
|
)
|
||||||
|
|
||||||
|
type stateFn func(lx *lexer) stateFn
|
||||||
|
|
||||||
|
type lexer struct {
|
||||||
|
input string
|
||||||
|
start int
|
||||||
|
pos int
|
||||||
|
line int
|
||||||
|
state stateFn
|
||||||
|
items chan item
|
||||||
|
|
||||||
|
// Allow for backing up up to three runes.
|
||||||
|
// This is necessary because TOML contains 3-rune tokens (""" and ''').
|
||||||
|
prevWidths [3]int
|
||||||
|
nprev int // how many of prevWidths are in use
|
||||||
|
// If we emit an eof, we can still back up, but it is not OK to call
|
||||||
|
// next again.
|
||||||
|
atEOF bool
|
||||||
|
|
||||||
|
// A stack of state functions used to maintain context.
|
||||||
|
// The idea is to reuse parts of the state machine in various places.
|
||||||
|
// For example, values can appear at the top level or within arbitrarily
|
||||||
|
// nested arrays. The last state on the stack is used after a value has
|
||||||
|
// been lexed. Similarly for comments.
|
||||||
|
stack []stateFn
|
||||||
|
}
|
||||||
|
|
||||||
|
type item struct {
|
||||||
|
typ itemType
|
||||||
|
val string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lx *lexer) nextItem() item {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case item := <-lx.items:
|
||||||
|
return item
|
||||||
|
default:
|
||||||
|
lx.state = lx.state(lx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lex(input string) *lexer {
|
||||||
|
lx := &lexer{
|
||||||
|
input: input,
|
||||||
|
state: lexTop,
|
||||||
|
line: 1,
|
||||||
|
items: make(chan item, 10),
|
||||||
|
stack: make([]stateFn, 0, 10),
|
||||||
|
}
|
||||||
|
return lx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lx *lexer) push(state stateFn) {
|
||||||
|
lx.stack = append(lx.stack, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lx *lexer) pop() stateFn {
|
||||||
|
if len(lx.stack) == 0 {
|
||||||
|
return lx.errorf("BUG in lexer: no states to pop")
|
||||||
|
}
|
||||||
|
last := lx.stack[len(lx.stack)-1]
|
||||||
|
lx.stack = lx.stack[0 : len(lx.stack)-1]
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lx *lexer) current() string {
|
||||||
|
return lx.input[lx.start:lx.pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lx *lexer) emit(typ itemType) {
|
||||||
|
lx.items <- item{typ, lx.current(), lx.line}
|
||||||
|
lx.start = lx.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lx *lexer) emitTrim(typ itemType) {
|
||||||
|
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
|
||||||
|
lx.start = lx.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lx *lexer) next() (r rune) {
|
||||||
|
if lx.atEOF {
|
||||||
|
panic("next called after EOF")
|
||||||
|
}
|
||||||
|
if lx.pos >= len(lx.input) {
|
||||||
|
lx.atEOF = true
|
||||||
|
return eof
|
||||||
|
}
|
||||||
|
|
||||||
|
if lx.input[lx.pos] == '\n' {
|
||||||
|
lx.line++
|
||||||
|
}
|
||||||
|
lx.prevWidths[2] = lx.prevWidths[1]
|
||||||
|
lx.prevWidths[1] = lx.prevWidths[0]
|
||||||
|
if lx.nprev < 3 {
|
||||||
|
lx.nprev++
|
||||||
|
}
|
||||||
|
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
|
||||||
|
lx.prevWidths[0] = w
|
||||||
|
lx.pos += w
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore skips over the pending input before this point.
|
||||||
|
func (lx *lexer) ignore() {
|
||||||
|
lx.start = lx.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// backup steps back one rune. Can be called only twice between calls to next.
|
||||||
|
func (lx *lexer) backup() {
|
||||||
|
if lx.atEOF {
|
||||||
|
lx.atEOF = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lx.nprev < 1 {
|
||||||
|
panic("backed up too far")
|
||||||
|
}
|
||||||
|
w := lx.prevWidths[0]
|
||||||
|
lx.prevWidths[0] = lx.prevWidths[1]
|
||||||
|
lx.prevWidths[1] = lx.prevWidths[2]
|
||||||
|
lx.nprev--
|
||||||
|
lx.pos -= w
|
||||||
|
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
||||||
|
lx.line--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept consumes the next rune if it's equal to `valid`.
|
||||||
|
func (lx *lexer) accept(valid rune) bool {
|
||||||
|
if lx.next() == valid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// peek returns but does not consume the next rune in the input.
|
||||||
|
func (lx *lexer) peek() rune {
|
||||||
|
r := lx.next()
|
||||||
|
lx.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip ignores all input that matches the given predicate.
|
||||||
|
func (lx *lexer) skip(pred func(rune) bool) {
|
||||||
|
for {
|
||||||
|
r := lx.next()
|
||||||
|
if pred(r) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
lx.ignore()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorf stops all lexing by emitting an error and returning `nil`.
|
||||||
|
// Note that any value that is a character is escaped if it's a special
|
||||||
|
// character (newlines, tabs, etc.).
|
||||||
|
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
||||||
|
lx.items <- item{
|
||||||
|
itemError,
|
||||||
|
fmt.Sprintf(format, values...),
|
||||||
|
lx.line,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexTop consumes elements at the top level of TOML data.
|
||||||
|
func lexTop(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
if isWhitespace(r) || isNL(r) {
|
||||||
|
return lexSkip(lx, lexTop)
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case commentStart:
|
||||||
|
lx.push(lexTop)
|
||||||
|
return lexCommentStart
|
||||||
|
case tableStart:
|
||||||
|
return lexTableStart
|
||||||
|
case eof:
|
||||||
|
if lx.pos > lx.start {
|
||||||
|
return lx.errorf("unexpected EOF")
|
||||||
|
}
|
||||||
|
lx.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, the only valid item can be a key, so we back up
|
||||||
|
// and let the key lexer do the rest.
|
||||||
|
lx.backup()
|
||||||
|
lx.push(lexTopEnd)
|
||||||
|
return lexKeyStart
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
|
||||||
|
// or a table.) It must see only whitespace, and will turn back to lexTop
|
||||||
|
// upon a newline. If it sees EOF, it will quit the lexer successfully.
|
||||||
|
func lexTopEnd(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case r == commentStart:
|
||||||
|
// a comment will read to a newline for us.
|
||||||
|
lx.push(lexTop)
|
||||||
|
return lexCommentStart
|
||||||
|
case isWhitespace(r):
|
||||||
|
return lexTopEnd
|
||||||
|
case isNL(r):
|
||||||
|
lx.ignore()
|
||||||
|
return lexTop
|
||||||
|
case r == eof:
|
||||||
|
lx.emit(itemEOF)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return lx.errorf("expected a top-level item to end with a newline, "+
|
||||||
|
"comment, or EOF, but got %q instead", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexTable lexes the beginning of a table. Namely, it makes sure that
|
||||||
|
// it starts with a character other than '.' and ']'.
|
||||||
|
// It assumes that '[' has already been consumed.
|
||||||
|
// It also handles the case that this is an item in an array of tables.
|
||||||
|
// e.g., '[[name]]'.
|
||||||
|
func lexTableStart(lx *lexer) stateFn {
|
||||||
|
if lx.peek() == arrayTableStart {
|
||||||
|
lx.next()
|
||||||
|
lx.emit(itemArrayTableStart)
|
||||||
|
lx.push(lexArrayTableEnd)
|
||||||
|
} else {
|
||||||
|
lx.emit(itemTableStart)
|
||||||
|
lx.push(lexTableEnd)
|
||||||
|
}
|
||||||
|
return lexTableNameStart
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexTableEnd(lx *lexer) stateFn {
|
||||||
|
lx.emit(itemTableEnd)
|
||||||
|
return lexTopEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexArrayTableEnd(lx *lexer) stateFn {
|
||||||
|
if r := lx.next(); r != arrayTableEnd {
|
||||||
|
return lx.errorf("expected end of table array name delimiter %q, "+
|
||||||
|
"but got %q instead", arrayTableEnd, r)
|
||||||
|
}
|
||||||
|
lx.emit(itemArrayTableEnd)
|
||||||
|
return lexTopEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexTableNameStart(lx *lexer) stateFn {
|
||||||
|
lx.skip(isWhitespace)
|
||||||
|
switch r := lx.peek(); {
|
||||||
|
case r == tableEnd || r == eof:
|
||||||
|
return lx.errorf("unexpected end of table name " +
|
||||||
|
"(table names cannot be empty)")
|
||||||
|
case r == tableSep:
|
||||||
|
return lx.errorf("unexpected table separator " +
|
||||||
|
"(table names cannot be empty)")
|
||||||
|
case r == stringStart || r == rawStringStart:
|
||||||
|
lx.ignore()
|
||||||
|
lx.push(lexTableNameEnd)
|
||||||
|
return lexValue // reuse string lexing
|
||||||
|
default:
|
||||||
|
return lexBareTableName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexBareTableName lexes the name of a table. It assumes that at least one
|
||||||
|
// valid character for the table has already been read.
|
||||||
|
func lexBareTableName(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
if isBareKeyChar(r) {
|
||||||
|
return lexBareTableName
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemText)
|
||||||
|
return lexTableNameEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexTableNameEnd reads the end of a piece of a table name, optionally
|
||||||
|
// consuming whitespace.
|
||||||
|
func lexTableNameEnd(lx *lexer) stateFn {
|
||||||
|
lx.skip(isWhitespace)
|
||||||
|
switch r := lx.next(); {
|
||||||
|
case isWhitespace(r):
|
||||||
|
return lexTableNameEnd
|
||||||
|
case r == tableSep:
|
||||||
|
lx.ignore()
|
||||||
|
return lexTableNameStart
|
||||||
|
case r == tableEnd:
|
||||||
|
return lx.pop()
|
||||||
|
default:
|
||||||
|
return lx.errorf("expected '.' or ']' to end table name, "+
|
||||||
|
"but got %q instead", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexKeyStart consumes a key name up until the first non-whitespace character.
|
||||||
|
// lexKeyStart will ignore whitespace.
|
||||||
|
func lexKeyStart(lx *lexer) stateFn {
|
||||||
|
r := lx.peek()
|
||||||
|
switch {
|
||||||
|
case r == keySep:
|
||||||
|
return lx.errorf("unexpected key separator %q", keySep)
|
||||||
|
case isWhitespace(r) || isNL(r):
|
||||||
|
lx.next()
|
||||||
|
return lexSkip(lx, lexKeyStart)
|
||||||
|
case r == stringStart || r == rawStringStart:
|
||||||
|
lx.ignore()
|
||||||
|
lx.emit(itemKeyStart)
|
||||||
|
lx.push(lexKeyEnd)
|
||||||
|
return lexValue // reuse string lexing
|
||||||
|
default:
|
||||||
|
lx.ignore()
|
||||||
|
lx.emit(itemKeyStart)
|
||||||
|
return lexBareKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexBareKey consumes the text of a bare key. Assumes that the first character
|
||||||
|
// (which is not whitespace) has not yet been consumed.
|
||||||
|
func lexBareKey(lx *lexer) stateFn {
|
||||||
|
switch r := lx.next(); {
|
||||||
|
case isBareKeyChar(r):
|
||||||
|
return lexBareKey
|
||||||
|
case isWhitespace(r):
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemText)
|
||||||
|
return lexKeyEnd
|
||||||
|
case r == keySep:
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemText)
|
||||||
|
return lexKeyEnd
|
||||||
|
default:
|
||||||
|
return lx.errorf("bare keys cannot contain %q", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
|
||||||
|
// separator).
|
||||||
|
func lexKeyEnd(lx *lexer) stateFn {
|
||||||
|
switch r := lx.next(); {
|
||||||
|
case r == keySep:
|
||||||
|
return lexSkip(lx, lexValue)
|
||||||
|
case isWhitespace(r):
|
||||||
|
return lexSkip(lx, lexKeyEnd)
|
||||||
|
default:
|
||||||
|
return lx.errorf("expected key separator %q, but got %q instead",
|
||||||
|
keySep, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexValue starts the consumption of a value anywhere a value is expected.
|
||||||
|
// lexValue will ignore whitespace.
|
||||||
|
// After a value is lexed, the last state on the next is popped and returned.
|
||||||
|
func lexValue(lx *lexer) stateFn {
|
||||||
|
// We allow whitespace to precede a value, but NOT newlines.
|
||||||
|
// In array syntax, the array states are responsible for ignoring newlines.
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case isWhitespace(r):
|
||||||
|
return lexSkip(lx, lexValue)
|
||||||
|
case isDigit(r):
|
||||||
|
lx.backup() // avoid an extra state and use the same as above
|
||||||
|
return lexNumberOrDateStart
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case arrayStart:
|
||||||
|
lx.ignore()
|
||||||
|
lx.emit(itemArray)
|
||||||
|
return lexArrayValue
|
||||||
|
case inlineTableStart:
|
||||||
|
lx.ignore()
|
||||||
|
lx.emit(itemInlineTableStart)
|
||||||
|
return lexInlineTableValue
|
||||||
|
case stringStart:
|
||||||
|
if lx.accept(stringStart) {
|
||||||
|
if lx.accept(stringStart) {
|
||||||
|
lx.ignore() // Ignore """
|
||||||
|
return lexMultilineString
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
}
|
||||||
|
lx.ignore() // ignore the '"'
|
||||||
|
return lexString
|
||||||
|
case rawStringStart:
|
||||||
|
if lx.accept(rawStringStart) {
|
||||||
|
if lx.accept(rawStringStart) {
|
||||||
|
lx.ignore() // Ignore """
|
||||||
|
return lexMultilineRawString
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
}
|
||||||
|
lx.ignore() // ignore the "'"
|
||||||
|
return lexRawString
|
||||||
|
case '+', '-':
|
||||||
|
return lexNumberStart
|
||||||
|
case '.': // special error case, be kind to users
|
||||||
|
return lx.errorf("floats must start with a digit, not '.'")
|
||||||
|
}
|
||||||
|
if unicode.IsLetter(r) {
|
||||||
|
// Be permissive here; lexBool will give a nice error if the
|
||||||
|
// user wrote something like
|
||||||
|
// x = foo
|
||||||
|
// (i.e. not 'true' or 'false' but is something else word-like.)
|
||||||
|
lx.backup()
|
||||||
|
return lexBool
|
||||||
|
}
|
||||||
|
return lx.errorf("expected value but found %q instead", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
|
||||||
|
// have already been consumed. All whitespace and newlines are ignored.
|
||||||
|
func lexArrayValue(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case isWhitespace(r) || isNL(r):
|
||||||
|
return lexSkip(lx, lexArrayValue)
|
||||||
|
case r == commentStart:
|
||||||
|
lx.push(lexArrayValue)
|
||||||
|
return lexCommentStart
|
||||||
|
case r == comma:
|
||||||
|
return lx.errorf("unexpected comma")
|
||||||
|
case r == arrayEnd:
|
||||||
|
// NOTE(caleb): The spec isn't clear about whether you can have
|
||||||
|
// a trailing comma or not, so we'll allow it.
|
||||||
|
return lexArrayEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
lx.backup()
|
||||||
|
lx.push(lexArrayValueEnd)
|
||||||
|
return lexValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexArrayValueEnd consumes everything between the end of an array value and
|
||||||
|
// the next value (or the end of the array): it ignores whitespace and newlines
|
||||||
|
// and expects either a ',' or a ']'.
|
||||||
|
func lexArrayValueEnd(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case isWhitespace(r) || isNL(r):
|
||||||
|
return lexSkip(lx, lexArrayValueEnd)
|
||||||
|
case r == commentStart:
|
||||||
|
lx.push(lexArrayValueEnd)
|
||||||
|
return lexCommentStart
|
||||||
|
case r == comma:
|
||||||
|
lx.ignore()
|
||||||
|
return lexArrayValue // move on to the next value
|
||||||
|
case r == arrayEnd:
|
||||||
|
return lexArrayEnd
|
||||||
|
}
|
||||||
|
return lx.errorf(
|
||||||
|
"expected a comma or array terminator %q, but got %q instead",
|
||||||
|
arrayEnd, r,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexArrayEnd finishes the lexing of an array.
|
||||||
|
// It assumes that a ']' has just been consumed.
|
||||||
|
func lexArrayEnd(lx *lexer) stateFn {
|
||||||
|
lx.ignore()
|
||||||
|
lx.emit(itemArrayEnd)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexInlineTableValue consumes one key/value pair in an inline table.
|
||||||
|
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
|
||||||
|
func lexInlineTableValue(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case isWhitespace(r):
|
||||||
|
return lexSkip(lx, lexInlineTableValue)
|
||||||
|
case isNL(r):
|
||||||
|
return lx.errorf("newlines not allowed within inline tables")
|
||||||
|
case r == commentStart:
|
||||||
|
lx.push(lexInlineTableValue)
|
||||||
|
return lexCommentStart
|
||||||
|
case r == comma:
|
||||||
|
return lx.errorf("unexpected comma")
|
||||||
|
case r == inlineTableEnd:
|
||||||
|
return lexInlineTableEnd
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
lx.push(lexInlineTableValueEnd)
|
||||||
|
return lexKeyStart
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexInlineTableValueEnd consumes everything between the end of an inline table
|
||||||
|
// key/value pair and the next pair (or the end of the table):
|
||||||
|
// it ignores whitespace and expects either a ',' or a '}'.
|
||||||
|
func lexInlineTableValueEnd(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case isWhitespace(r):
|
||||||
|
return lexSkip(lx, lexInlineTableValueEnd)
|
||||||
|
case isNL(r):
|
||||||
|
return lx.errorf("newlines not allowed within inline tables")
|
||||||
|
case r == commentStart:
|
||||||
|
lx.push(lexInlineTableValueEnd)
|
||||||
|
return lexCommentStart
|
||||||
|
case r == comma:
|
||||||
|
lx.ignore()
|
||||||
|
return lexInlineTableValue
|
||||||
|
case r == inlineTableEnd:
|
||||||
|
return lexInlineTableEnd
|
||||||
|
}
|
||||||
|
return lx.errorf("expected a comma or an inline table terminator %q, "+
|
||||||
|
"but got %q instead", inlineTableEnd, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexInlineTableEnd finishes the lexing of an inline table.
|
||||||
|
// It assumes that a '}' has just been consumed.
|
||||||
|
func lexInlineTableEnd(lx *lexer) stateFn {
|
||||||
|
lx.ignore()
|
||||||
|
lx.emit(itemInlineTableEnd)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexString consumes the inner contents of a string. It assumes that the
|
||||||
|
// beginning '"' has already been consumed and ignored.
|
||||||
|
func lexString(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case r == eof:
|
||||||
|
return lx.errorf("unexpected EOF")
|
||||||
|
case isNL(r):
|
||||||
|
return lx.errorf("strings cannot contain newlines")
|
||||||
|
case r == '\\':
|
||||||
|
lx.push(lexString)
|
||||||
|
return lexStringEscape
|
||||||
|
case r == stringEnd:
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemString)
|
||||||
|
lx.next()
|
||||||
|
lx.ignore()
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
return lexString
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexMultilineString consumes the inner contents of a string. It assumes that
|
||||||
|
// the beginning '"""' has already been consumed and ignored.
|
||||||
|
func lexMultilineString(lx *lexer) stateFn {
|
||||||
|
switch lx.next() {
|
||||||
|
case eof:
|
||||||
|
return lx.errorf("unexpected EOF")
|
||||||
|
case '\\':
|
||||||
|
return lexMultilineStringEscape
|
||||||
|
case stringEnd:
|
||||||
|
if lx.accept(stringEnd) {
|
||||||
|
if lx.accept(stringEnd) {
|
||||||
|
lx.backup()
|
||||||
|
lx.backup()
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemMultilineString)
|
||||||
|
lx.next()
|
||||||
|
lx.next()
|
||||||
|
lx.next()
|
||||||
|
lx.ignore()
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lexMultilineString
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
|
||||||
|
// It assumes that the beginning "'" has already been consumed and ignored.
|
||||||
|
func lexRawString(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch {
|
||||||
|
case r == eof:
|
||||||
|
return lx.errorf("unexpected EOF")
|
||||||
|
case isNL(r):
|
||||||
|
return lx.errorf("strings cannot contain newlines")
|
||||||
|
case r == rawStringEnd:
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemRawString)
|
||||||
|
lx.next()
|
||||||
|
lx.ignore()
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
return lexRawString
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
||||||
|
// a string. It assumes that the beginning "'''" has already been consumed and
|
||||||
|
// ignored.
|
||||||
|
func lexMultilineRawString(lx *lexer) stateFn {
|
||||||
|
switch lx.next() {
|
||||||
|
case eof:
|
||||||
|
return lx.errorf("unexpected EOF")
|
||||||
|
case rawStringEnd:
|
||||||
|
if lx.accept(rawStringEnd) {
|
||||||
|
if lx.accept(rawStringEnd) {
|
||||||
|
lx.backup()
|
||||||
|
lx.backup()
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemRawMultilineString)
|
||||||
|
lx.next()
|
||||||
|
lx.next()
|
||||||
|
lx.next()
|
||||||
|
lx.ignore()
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lexMultilineRawString
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexMultilineStringEscape consumes an escaped character. It assumes that the
|
||||||
|
// preceding '\\' has already been consumed.
|
||||||
|
func lexMultilineStringEscape(lx *lexer) stateFn {
|
||||||
|
// Handle the special case first:
|
||||||
|
if isNL(lx.next()) {
|
||||||
|
return lexMultilineString
|
||||||
|
}
|
||||||
|
lx.backup()
|
||||||
|
lx.push(lexMultilineString)
|
||||||
|
return lexStringEscape(lx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexStringEscape(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
switch r {
|
||||||
|
case 'b':
|
||||||
|
fallthrough
|
||||||
|
case 't':
|
||||||
|
fallthrough
|
||||||
|
case 'n':
|
||||||
|
fallthrough
|
||||||
|
case 'f':
|
||||||
|
fallthrough
|
||||||
|
case 'r':
|
||||||
|
fallthrough
|
||||||
|
case '"':
|
||||||
|
fallthrough
|
||||||
|
case '\\':
|
||||||
|
return lx.pop()
|
||||||
|
case 'u':
|
||||||
|
return lexShortUnicodeEscape
|
||||||
|
case 'U':
|
||||||
|
return lexLongUnicodeEscape
|
||||||
|
}
|
||||||
|
return lx.errorf("invalid escape character %q; only the following "+
|
||||||
|
"escape characters are allowed: "+
|
||||||
|
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
||||||
|
var r rune
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
r = lx.next()
|
||||||
|
if !isHexadecimal(r) {
|
||||||
|
return lx.errorf(`expected four hexadecimal digits after '\u', `+
|
||||||
|
"but got %q instead", lx.current())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func lexLongUnicodeEscape(lx *lexer) stateFn {
|
||||||
|
var r rune
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
r = lx.next()
|
||||||
|
if !isHexadecimal(r) {
|
||||||
|
return lx.errorf(`expected eight hexadecimal digits after '\U', `+
|
||||||
|
"but got %q instead", lx.current())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexNumberOrDateStart consumes either an integer, a float, or datetime.
|
||||||
|
func lexNumberOrDateStart(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
if isDigit(r) {
|
||||||
|
return lexNumberOrDate
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '_':
|
||||||
|
return lexNumber
|
||||||
|
case 'e', 'E':
|
||||||
|
return lexFloat
|
||||||
|
case '.':
|
||||||
|
return lx.errorf("floats must start with a digit, not '.'")
|
||||||
|
}
|
||||||
|
return lx.errorf("expected a digit but got %q", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexNumberOrDate consumes either an integer, float or datetime.
|
||||||
|
func lexNumberOrDate(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
if isDigit(r) {
|
||||||
|
return lexNumberOrDate
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '-':
|
||||||
|
return lexDatetime
|
||||||
|
case '_':
|
||||||
|
return lexNumber
|
||||||
|
case '.', 'e', 'E':
|
||||||
|
return lexFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemInteger)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexDatetime consumes a Datetime, to a first approximation.
|
||||||
|
// The parser validates that it matches one of the accepted formats.
|
||||||
|
func lexDatetime(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
if isDigit(r) {
|
||||||
|
return lexDatetime
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '-', 'T', ':', '.', 'Z', '+':
|
||||||
|
return lexDatetime
|
||||||
|
}
|
||||||
|
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemDatetime)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexNumberStart consumes either an integer or a float. It assumes that a sign
|
||||||
|
// has already been read, but that *no* digits have been consumed.
|
||||||
|
// lexNumberStart will move to the appropriate integer or float states.
|
||||||
|
func lexNumberStart(lx *lexer) stateFn {
|
||||||
|
// We MUST see a digit. Even floats have to start with a digit.
|
||||||
|
r := lx.next()
|
||||||
|
if !isDigit(r) {
|
||||||
|
if r == '.' {
|
||||||
|
return lx.errorf("floats must start with a digit, not '.'")
|
||||||
|
}
|
||||||
|
return lx.errorf("expected a digit but got %q", r)
|
||||||
|
}
|
||||||
|
return lexNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexNumber consumes an integer or a float after seeing the first digit.
|
||||||
|
func lexNumber(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
if isDigit(r) {
|
||||||
|
return lexNumber
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '_':
|
||||||
|
return lexNumber
|
||||||
|
case '.', 'e', 'E':
|
||||||
|
return lexFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemInteger)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexFloat consumes the elements of a float. It allows any sequence of
|
||||||
|
// float-like characters, so floats emitted by the lexer are only a first
|
||||||
|
// approximation and must be validated by the parser.
|
||||||
|
func lexFloat(lx *lexer) stateFn {
|
||||||
|
r := lx.next()
|
||||||
|
if isDigit(r) {
|
||||||
|
return lexFloat
|
||||||
|
}
|
||||||
|
switch r {
|
||||||
|
case '_', '.', '-', '+', 'e', 'E':
|
||||||
|
return lexFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
lx.backup()
|
||||||
|
lx.emit(itemFloat)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexBool consumes a bool string: 'true' or 'false.
|
||||||
|
func lexBool(lx *lexer) stateFn {
|
||||||
|
var rs []rune
|
||||||
|
for {
|
||||||
|
r := lx.next()
|
||||||
|
if !unicode.IsLetter(r) {
|
||||||
|
lx.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rs = append(rs, r)
|
||||||
|
}
|
||||||
|
s := string(rs)
|
||||||
|
switch s {
|
||||||
|
case "true", "false":
|
||||||
|
lx.emit(itemBool)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
return lx.errorf("expected value but found %q instead", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexCommentStart begins the lexing of a comment. It will emit
|
||||||
|
// itemCommentStart and consume no characters, passing control to lexComment.
|
||||||
|
func lexCommentStart(lx *lexer) stateFn {
|
||||||
|
lx.ignore()
|
||||||
|
lx.emit(itemCommentStart)
|
||||||
|
return lexComment
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
|
||||||
|
// It will consume *up to* the first newline character, and pass control
|
||||||
|
// back to the last state on the stack.
|
||||||
|
func lexComment(lx *lexer) stateFn {
|
||||||
|
r := lx.peek()
|
||||||
|
if isNL(r) || r == eof {
|
||||||
|
lx.emit(itemText)
|
||||||
|
return lx.pop()
|
||||||
|
}
|
||||||
|
lx.next()
|
||||||
|
return lexComment
|
||||||
|
}
|
||||||
|
|
||||||
|
// lexSkip ignores all slurped input and moves on to the next state.
|
||||||
|
func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
||||||
|
return func(lx *lexer) stateFn {
|
||||||
|
lx.ignore()
|
||||||
|
return nextState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isWhitespace returns true if `r` is a whitespace character according
|
||||||
|
// to the spec.
|
||||||
|
func isWhitespace(r rune) bool {
|
||||||
|
return r == '\t' || r == ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNL(r rune) bool {
|
||||||
|
return r == '\n' || r == '\r'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDigit(r rune) bool {
|
||||||
|
return r >= '0' && r <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHexadecimal(r rune) bool {
|
||||||
|
return (r >= '0' && r <= '9') ||
|
||||||
|
(r >= 'a' && r <= 'f') ||
|
||||||
|
(r >= 'A' && r <= 'F')
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBareKeyChar(r rune) bool {
|
||||||
|
return (r >= 'A' && r <= 'Z') ||
|
||||||
|
(r >= 'a' && r <= 'z') ||
|
||||||
|
(r >= '0' && r <= '9') ||
|
||||||
|
r == '_' ||
|
||||||
|
r == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (itype itemType) String() string {
|
||||||
|
switch itype {
|
||||||
|
case itemError:
|
||||||
|
return "Error"
|
||||||
|
case itemNIL:
|
||||||
|
return "NIL"
|
||||||
|
case itemEOF:
|
||||||
|
return "EOF"
|
||||||
|
case itemText:
|
||||||
|
return "Text"
|
||||||
|
case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
|
||||||
|
return "String"
|
||||||
|
case itemBool:
|
||||||
|
return "Bool"
|
||||||
|
case itemInteger:
|
||||||
|
return "Integer"
|
||||||
|
case itemFloat:
|
||||||
|
return "Float"
|
||||||
|
case itemDatetime:
|
||||||
|
return "DateTime"
|
||||||
|
case itemTableStart:
|
||||||
|
return "TableStart"
|
||||||
|
case itemTableEnd:
|
||||||
|
return "TableEnd"
|
||||||
|
case itemKeyStart:
|
||||||
|
return "KeyStart"
|
||||||
|
case itemArray:
|
||||||
|
return "Array"
|
||||||
|
case itemArrayEnd:
|
||||||
|
return "ArrayEnd"
|
||||||
|
case itemCommentStart:
|
||||||
|
return "CommentStart"
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item item) String() string {
|
||||||
|
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
|
||||||
|
}
|
592
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
592
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
|
@ -0,0 +1,592 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
mapping map[string]interface{}
|
||||||
|
types map[string]tomlType
|
||||||
|
lx *lexer
|
||||||
|
|
||||||
|
// A list of keys in the order that they appear in the TOML data.
|
||||||
|
ordered []Key
|
||||||
|
|
||||||
|
// the full key for the current hash in scope
|
||||||
|
context Key
|
||||||
|
|
||||||
|
// the base key name for everything except hashes
|
||||||
|
currentKey string
|
||||||
|
|
||||||
|
// rough approximation of line number
|
||||||
|
approxLine int
|
||||||
|
|
||||||
|
// A map of 'key.group.names' to whether they were created implicitly.
|
||||||
|
implicits map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type parseError string
|
||||||
|
|
||||||
|
func (pe parseError) Error() string {
|
||||||
|
return string(pe)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(data string) (p *parser, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
var ok bool
|
||||||
|
if err, ok = r.(parseError); ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
p = &parser{
|
||||||
|
mapping: make(map[string]interface{}),
|
||||||
|
types: make(map[string]tomlType),
|
||||||
|
lx: lex(data),
|
||||||
|
ordered: make([]Key, 0),
|
||||||
|
implicits: make(map[string]bool),
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
item := p.next()
|
||||||
|
if item.typ == itemEOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.topLevel(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) panicf(format string, v ...interface{}) {
|
||||||
|
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
||||||
|
p.approxLine, p.current(), fmt.Sprintf(format, v...))
|
||||||
|
panic(parseError(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) next() item {
|
||||||
|
it := p.lx.nextItem()
|
||||||
|
if it.typ == itemError {
|
||||||
|
p.panicf("%s", it.val)
|
||||||
|
}
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) bug(format string, v ...interface{}) {
|
||||||
|
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) expect(typ itemType) item {
|
||||||
|
it := p.next()
|
||||||
|
p.assertEqual(typ, it.typ)
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) assertEqual(expected, got itemType) {
|
||||||
|
if expected != got {
|
||||||
|
p.bug("Expected '%s' but got '%s'.", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) topLevel(item item) {
|
||||||
|
switch item.typ {
|
||||||
|
case itemCommentStart:
|
||||||
|
p.approxLine = item.line
|
||||||
|
p.expect(itemText)
|
||||||
|
case itemTableStart:
|
||||||
|
kg := p.next()
|
||||||
|
p.approxLine = kg.line
|
||||||
|
|
||||||
|
var key Key
|
||||||
|
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||||
|
key = append(key, p.keyString(kg))
|
||||||
|
}
|
||||||
|
p.assertEqual(itemTableEnd, kg.typ)
|
||||||
|
|
||||||
|
p.establishContext(key, false)
|
||||||
|
p.setType("", tomlHash)
|
||||||
|
p.ordered = append(p.ordered, key)
|
||||||
|
case itemArrayTableStart:
|
||||||
|
kg := p.next()
|
||||||
|
p.approxLine = kg.line
|
||||||
|
|
||||||
|
var key Key
|
||||||
|
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||||
|
key = append(key, p.keyString(kg))
|
||||||
|
}
|
||||||
|
p.assertEqual(itemArrayTableEnd, kg.typ)
|
||||||
|
|
||||||
|
p.establishContext(key, true)
|
||||||
|
p.setType("", tomlArrayHash)
|
||||||
|
p.ordered = append(p.ordered, key)
|
||||||
|
case itemKeyStart:
|
||||||
|
kname := p.next()
|
||||||
|
p.approxLine = kname.line
|
||||||
|
p.currentKey = p.keyString(kname)
|
||||||
|
|
||||||
|
val, typ := p.value(p.next())
|
||||||
|
p.setValue(p.currentKey, val)
|
||||||
|
p.setType(p.currentKey, typ)
|
||||||
|
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||||
|
p.currentKey = ""
|
||||||
|
default:
|
||||||
|
p.bug("Unexpected type at top level: %s", item.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets a string for a key (or part of a key in a table name).
|
||||||
|
func (p *parser) keyString(it item) string {
|
||||||
|
switch it.typ {
|
||||||
|
case itemText:
|
||||||
|
return it.val
|
||||||
|
case itemString, itemMultilineString,
|
||||||
|
itemRawString, itemRawMultilineString:
|
||||||
|
s, _ := p.value(it)
|
||||||
|
return s.(string)
|
||||||
|
default:
|
||||||
|
p.bug("Unexpected key type: %s", it.typ)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// value translates an expected value from the lexer into a Go value wrapped
|
||||||
|
// as an empty interface.
|
||||||
|
func (p *parser) value(it item) (interface{}, tomlType) {
|
||||||
|
switch it.typ {
|
||||||
|
case itemString:
|
||||||
|
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
||||||
|
case itemMultilineString:
|
||||||
|
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
|
||||||
|
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
|
||||||
|
case itemRawString:
|
||||||
|
return it.val, p.typeOfPrimitive(it)
|
||||||
|
case itemRawMultilineString:
|
||||||
|
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||||
|
case itemBool:
|
||||||
|
switch it.val {
|
||||||
|
case "true":
|
||||||
|
return true, p.typeOfPrimitive(it)
|
||||||
|
case "false":
|
||||||
|
return false, p.typeOfPrimitive(it)
|
||||||
|
}
|
||||||
|
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||||
|
case itemInteger:
|
||||||
|
if !numUnderscoresOK(it.val) {
|
||||||
|
p.panicf("Invalid integer %q: underscores must be surrounded by digits",
|
||||||
|
it.val)
|
||||||
|
}
|
||||||
|
val := strings.Replace(it.val, "_", "", -1)
|
||||||
|
num, err := strconv.ParseInt(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||||
|
// provides an invalid integer, but it's possible that the number is
|
||||||
|
// out of range of valid values (which the lexer cannot determine).
|
||||||
|
// So mark the former as a bug but the latter as a legitimate user
|
||||||
|
// error.
|
||||||
|
if e, ok := err.(*strconv.NumError); ok &&
|
||||||
|
e.Err == strconv.ErrRange {
|
||||||
|
|
||||||
|
p.panicf("Integer '%s' is out of the range of 64-bit "+
|
||||||
|
"signed integers.", it.val)
|
||||||
|
} else {
|
||||||
|
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num, p.typeOfPrimitive(it)
|
||||||
|
case itemFloat:
|
||||||
|
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||||
|
switch r {
|
||||||
|
case '.', 'e', 'E':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
for _, part := range parts {
|
||||||
|
if !numUnderscoresOK(part) {
|
||||||
|
p.panicf("Invalid float %q: underscores must be "+
|
||||||
|
"surrounded by digits", it.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !numPeriodsOK(it.val) {
|
||||||
|
// As a special case, numbers like '123.' or '1.e2',
|
||||||
|
// which are valid as far as Go/strconv are concerned,
|
||||||
|
// must be rejected because TOML says that a fractional
|
||||||
|
// part consists of '.' followed by 1+ digits.
|
||||||
|
p.panicf("Invalid float %q: '.' must be followed "+
|
||||||
|
"by one or more digits", it.val)
|
||||||
|
}
|
||||||
|
val := strings.Replace(it.val, "_", "", -1)
|
||||||
|
num, err := strconv.ParseFloat(val, 64)
|
||||||
|
if err != nil {
|
||||||
|
if e, ok := err.(*strconv.NumError); ok &&
|
||||||
|
e.Err == strconv.ErrRange {
|
||||||
|
|
||||||
|
p.panicf("Float '%s' is out of the range of 64-bit "+
|
||||||
|
"IEEE-754 floating-point numbers.", it.val)
|
||||||
|
} else {
|
||||||
|
p.panicf("Invalid float value: %q", it.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num, p.typeOfPrimitive(it)
|
||||||
|
case itemDatetime:
|
||||||
|
var t time.Time
|
||||||
|
var ok bool
|
||||||
|
var err error
|
||||||
|
for _, format := range []string{
|
||||||
|
"2006-01-02T15:04:05Z07:00",
|
||||||
|
"2006-01-02T15:04:05",
|
||||||
|
"2006-01-02",
|
||||||
|
} {
|
||||||
|
t, err = time.ParseInLocation(format, it.val, time.Local)
|
||||||
|
if err == nil {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
||||||
|
}
|
||||||
|
return t, p.typeOfPrimitive(it)
|
||||||
|
case itemArray:
|
||||||
|
array := make([]interface{}, 0)
|
||||||
|
types := make([]tomlType, 0)
|
||||||
|
|
||||||
|
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||||
|
if it.typ == itemCommentStart {
|
||||||
|
p.expect(itemText)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, typ := p.value(it)
|
||||||
|
array = append(array, val)
|
||||||
|
types = append(types, typ)
|
||||||
|
}
|
||||||
|
return array, p.typeOfArray(types)
|
||||||
|
case itemInlineTableStart:
|
||||||
|
var (
|
||||||
|
hash = make(map[string]interface{})
|
||||||
|
outerContext = p.context
|
||||||
|
outerKey = p.currentKey
|
||||||
|
)
|
||||||
|
|
||||||
|
p.context = append(p.context, p.currentKey)
|
||||||
|
p.currentKey = ""
|
||||||
|
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||||
|
if it.typ != itemKeyStart {
|
||||||
|
p.bug("Expected key start but instead found %q, around line %d",
|
||||||
|
it.val, p.approxLine)
|
||||||
|
}
|
||||||
|
if it.typ == itemCommentStart {
|
||||||
|
p.expect(itemText)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve key
|
||||||
|
k := p.next()
|
||||||
|
p.approxLine = k.line
|
||||||
|
kname := p.keyString(k)
|
||||||
|
|
||||||
|
// retrieve value
|
||||||
|
p.currentKey = kname
|
||||||
|
val, typ := p.value(p.next())
|
||||||
|
// make sure we keep metadata up to date
|
||||||
|
p.setType(kname, typ)
|
||||||
|
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||||
|
hash[kname] = val
|
||||||
|
}
|
||||||
|
p.context = outerContext
|
||||||
|
p.currentKey = outerKey
|
||||||
|
return hash, tomlHash
|
||||||
|
}
|
||||||
|
p.bug("Unexpected value type: %s", it.typ)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||||
|
// characters that are not underscores.
|
||||||
|
func numUnderscoresOK(s string) bool {
|
||||||
|
accept := false
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '_' {
|
||||||
|
if !accept {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
accept = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
accept = true
|
||||||
|
}
|
||||||
|
return accept
|
||||||
|
}
|
||||||
|
|
||||||
|
// numPeriodsOK checks whether every period in s is followed by a digit.
|
||||||
|
func numPeriodsOK(s string) bool {
|
||||||
|
period := false
|
||||||
|
for _, r := range s {
|
||||||
|
if period && !isDigit(r) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
period = r == '.'
|
||||||
|
}
|
||||||
|
return !period
|
||||||
|
}
|
||||||
|
|
||||||
|
// establishContext sets the current context of the parser,
|
||||||
|
// where the context is either a hash or an array of hashes. Which one is
|
||||||
|
// set depends on the value of the `array` parameter.
|
||||||
|
//
|
||||||
|
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||||
|
// will create implicit hashes automatically.
|
||||||
|
func (p *parser) establishContext(key Key, array bool) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
// Always start at the top level and drill down for our context.
|
||||||
|
hashContext := p.mapping
|
||||||
|
keyContext := make(Key, 0)
|
||||||
|
|
||||||
|
// We only need implicit hashes for key[0:-1]
|
||||||
|
for _, k := range key[0 : len(key)-1] {
|
||||||
|
_, ok = hashContext[k]
|
||||||
|
keyContext = append(keyContext, k)
|
||||||
|
|
||||||
|
// No key? Make an implicit hash and move on.
|
||||||
|
if !ok {
|
||||||
|
p.addImplicit(keyContext)
|
||||||
|
hashContext[k] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the hash context is actually an array of tables, then set
|
||||||
|
// the hash context to the last element in that array.
|
||||||
|
//
|
||||||
|
// Otherwise, it better be a table, since this MUST be a key group (by
|
||||||
|
// virtue of it not being the last element in a key).
|
||||||
|
switch t := hashContext[k].(type) {
|
||||||
|
case []map[string]interface{}:
|
||||||
|
hashContext = t[len(t)-1]
|
||||||
|
case map[string]interface{}:
|
||||||
|
hashContext = t
|
||||||
|
default:
|
||||||
|
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.context = keyContext
|
||||||
|
if array {
|
||||||
|
// If this is the first element for this array, then allocate a new
|
||||||
|
// list of tables for it.
|
||||||
|
k := key[len(key)-1]
|
||||||
|
if _, ok := hashContext[k]; !ok {
|
||||||
|
hashContext[k] = make([]map[string]interface{}, 0, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new table. But make sure the key hasn't already been used
|
||||||
|
// for something else.
|
||||||
|
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||||
|
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||||
|
} else {
|
||||||
|
p.panicf("Key '%s' was already created and cannot be used as "+
|
||||||
|
"an array.", keyContext)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||||
|
}
|
||||||
|
p.context = append(p.context, key[len(key)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// setValue sets the given key to the given value in the current context.
|
||||||
|
// It will make sure that the key hasn't already been defined, account for
|
||||||
|
// implicit key groups.
|
||||||
|
func (p *parser) setValue(key string, value interface{}) {
|
||||||
|
var tmpHash interface{}
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
hash := p.mapping
|
||||||
|
keyContext := make(Key, 0)
|
||||||
|
for _, k := range p.context {
|
||||||
|
keyContext = append(keyContext, k)
|
||||||
|
if tmpHash, ok = hash[k]; !ok {
|
||||||
|
p.bug("Context for key '%s' has not been established.", keyContext)
|
||||||
|
}
|
||||||
|
switch t := tmpHash.(type) {
|
||||||
|
case []map[string]interface{}:
|
||||||
|
// The context is a table of hashes. Pick the most recent table
|
||||||
|
// defined as the current hash.
|
||||||
|
hash = t[len(t)-1]
|
||||||
|
case map[string]interface{}:
|
||||||
|
hash = t
|
||||||
|
default:
|
||||||
|
p.bug("Expected hash to have type 'map[string]interface{}', but "+
|
||||||
|
"it has '%T' instead.", tmpHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyContext = append(keyContext, key)
|
||||||
|
|
||||||
|
if _, ok := hash[key]; ok {
|
||||||
|
// Typically, if the given key has already been set, then we have
|
||||||
|
// to raise an error since duplicate keys are disallowed. However,
|
||||||
|
// it's possible that a key was previously defined implicitly. In this
|
||||||
|
// case, it is allowed to be redefined concretely. (See the
|
||||||
|
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
|
||||||
|
//
|
||||||
|
// But we have to make sure to stop marking it as an implicit. (So that
|
||||||
|
// another redefinition provokes an error.)
|
||||||
|
//
|
||||||
|
// Note that since it has already been defined (as a hash), we don't
|
||||||
|
// want to overwrite it. So our business is done.
|
||||||
|
if p.isImplicit(keyContext) {
|
||||||
|
p.removeImplicit(keyContext)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we have a concrete key trying to override a previous
|
||||||
|
// key, which is *always* wrong.
|
||||||
|
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||||
|
}
|
||||||
|
hash[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// setType sets the type of a particular value at a given key.
|
||||||
|
// It should be called immediately AFTER setValue.
|
||||||
|
//
|
||||||
|
// Note that if `key` is empty, then the type given will be applied to the
|
||||||
|
// current context (which is either a table or an array of tables).
|
||||||
|
func (p *parser) setType(key string, typ tomlType) {
|
||||||
|
keyContext := make(Key, 0, len(p.context)+1)
|
||||||
|
for _, k := range p.context {
|
||||||
|
keyContext = append(keyContext, k)
|
||||||
|
}
|
||||||
|
if len(key) > 0 { // allow type setting for hashes
|
||||||
|
keyContext = append(keyContext, key)
|
||||||
|
}
|
||||||
|
p.types[keyContext.String()] = typ
|
||||||
|
}
|
||||||
|
|
||||||
|
// addImplicit sets the given Key as having been created implicitly.
|
||||||
|
func (p *parser) addImplicit(key Key) {
|
||||||
|
p.implicits[key.String()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeImplicit stops tagging the given key as having been implicitly
|
||||||
|
// created.
|
||||||
|
func (p *parser) removeImplicit(key Key) {
|
||||||
|
p.implicits[key.String()] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isImplicit returns true if the key group pointed to by the key was created
|
||||||
|
// implicitly.
|
||||||
|
func (p *parser) isImplicit(key Key) bool {
|
||||||
|
return p.implicits[key.String()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// current returns the full key name of the current context.
|
||||||
|
func (p *parser) current() string {
|
||||||
|
if len(p.currentKey) == 0 {
|
||||||
|
return p.context.String()
|
||||||
|
}
|
||||||
|
if len(p.context) == 0 {
|
||||||
|
return p.currentKey
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripFirstNewline(s string) string {
|
||||||
|
if len(s) == 0 || s[0] != '\n' {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripEscapedWhitespace(s string) string {
|
||||||
|
esc := strings.Split(s, "\\\n")
|
||||||
|
if len(esc) > 1 {
|
||||||
|
for i := 1; i < len(esc); i++ {
|
||||||
|
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(esc, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) replaceEscapes(str string) string {
|
||||||
|
var replaced []rune
|
||||||
|
s := []byte(str)
|
||||||
|
r := 0
|
||||||
|
for r < len(s) {
|
||||||
|
if s[r] != '\\' {
|
||||||
|
c, size := utf8.DecodeRune(s[r:])
|
||||||
|
r += size
|
||||||
|
replaced = append(replaced, c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r += 1
|
||||||
|
if r >= len(s) {
|
||||||
|
p.bug("Escape sequence at end of string.")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
switch s[r] {
|
||||||
|
default:
|
||||||
|
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||||
|
return ""
|
||||||
|
case 'b':
|
||||||
|
replaced = append(replaced, rune(0x0008))
|
||||||
|
r += 1
|
||||||
|
case 't':
|
||||||
|
replaced = append(replaced, rune(0x0009))
|
||||||
|
r += 1
|
||||||
|
case 'n':
|
||||||
|
replaced = append(replaced, rune(0x000A))
|
||||||
|
r += 1
|
||||||
|
case 'f':
|
||||||
|
replaced = append(replaced, rune(0x000C))
|
||||||
|
r += 1
|
||||||
|
case 'r':
|
||||||
|
replaced = append(replaced, rune(0x000D))
|
||||||
|
r += 1
|
||||||
|
case '"':
|
||||||
|
replaced = append(replaced, rune(0x0022))
|
||||||
|
r += 1
|
||||||
|
case '\\':
|
||||||
|
replaced = append(replaced, rune(0x005C))
|
||||||
|
r += 1
|
||||||
|
case 'u':
|
||||||
|
// At this point, we know we have a Unicode escape of the form
|
||||||
|
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||||
|
// for us.)
|
||||||
|
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
||||||
|
replaced = append(replaced, escaped)
|
||||||
|
r += 5
|
||||||
|
case 'U':
|
||||||
|
// At this point, we know we have a Unicode escape of the form
|
||||||
|
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||||
|
// for us.)
|
||||||
|
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
||||||
|
replaced = append(replaced, escaped)
|
||||||
|
r += 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(replaced)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||||
|
s := string(bs)
|
||||||
|
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||||
|
"lexer claims it's OK: %s", s, err)
|
||||||
|
}
|
||||||
|
if !utf8.ValidRune(rune(hex)) {
|
||||||
|
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||||
|
}
|
||||||
|
return rune(hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStringType(ty itemType) bool {
|
||||||
|
return ty == itemString || ty == itemMultilineString ||
|
||||||
|
ty == itemRawString || ty == itemRawMultilineString
|
||||||
|
}
|
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
Normal file
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
au BufWritePost *.go silent!make tags > /dev/null 2>&1
|
91
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
Normal file
91
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
// tomlType represents any Go type that corresponds to a TOML type.
|
||||||
|
// While the first draft of the TOML spec has a simplistic type system that
|
||||||
|
// probably doesn't need this level of sophistication, we seem to be militating
|
||||||
|
// toward adding real composite types.
|
||||||
|
type tomlType interface {
|
||||||
|
typeString() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeEqual accepts any two types and returns true if they are equal.
|
||||||
|
func typeEqual(t1, t2 tomlType) bool {
|
||||||
|
if t1 == nil || t2 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t1.typeString() == t2.typeString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeIsHash(t tomlType) bool {
|
||||||
|
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tomlBaseType string
|
||||||
|
|
||||||
|
func (btype tomlBaseType) typeString() string {
|
||||||
|
return string(btype)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (btype tomlBaseType) String() string {
|
||||||
|
return btype.typeString()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tomlInteger tomlBaseType = "Integer"
|
||||||
|
tomlFloat tomlBaseType = "Float"
|
||||||
|
tomlDatetime tomlBaseType = "Datetime"
|
||||||
|
tomlString tomlBaseType = "String"
|
||||||
|
tomlBool tomlBaseType = "Bool"
|
||||||
|
tomlArray tomlBaseType = "Array"
|
||||||
|
tomlHash tomlBaseType = "Hash"
|
||||||
|
tomlArrayHash tomlBaseType = "ArrayHash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
||||||
|
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
||||||
|
//
|
||||||
|
// Passing a lexer item other than the following will cause a BUG message
|
||||||
|
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
||||||
|
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
||||||
|
switch lexItem.typ {
|
||||||
|
case itemInteger:
|
||||||
|
return tomlInteger
|
||||||
|
case itemFloat:
|
||||||
|
return tomlFloat
|
||||||
|
case itemDatetime:
|
||||||
|
return tomlDatetime
|
||||||
|
case itemString:
|
||||||
|
return tomlString
|
||||||
|
case itemMultilineString:
|
||||||
|
return tomlString
|
||||||
|
case itemRawString:
|
||||||
|
return tomlString
|
||||||
|
case itemRawMultilineString:
|
||||||
|
return tomlString
|
||||||
|
case itemBool:
|
||||||
|
return tomlBool
|
||||||
|
}
|
||||||
|
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeOfArray returns a tomlType for an array given a list of types of its
|
||||||
|
// values.
|
||||||
|
//
|
||||||
|
// In the current spec, if an array is homogeneous, then its type is always
|
||||||
|
// "Array". If the array is not homogeneous, an error is generated.
|
||||||
|
func (p *parser) typeOfArray(types []tomlType) tomlType {
|
||||||
|
// Empty arrays are cool.
|
||||||
|
if len(types) == 0 {
|
||||||
|
return tomlArray
|
||||||
|
}
|
||||||
|
|
||||||
|
theType := types[0]
|
||||||
|
for _, t := range types[1:] {
|
||||||
|
if !typeEqual(theType, t) {
|
||||||
|
p.panicf("Array contains values of type '%s' and '%s', but "+
|
||||||
|
"arrays must be homogeneous.", theType, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tomlArray
|
||||||
|
}
|
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
package toml
|
||||||
|
|
||||||
|
// Struct field handling is adapted from code in encoding/json:
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the Go distribution.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A field represents a single field found in a struct.
|
||||||
|
type field struct {
|
||||||
|
name string // the name of the field (`toml` tag included)
|
||||||
|
tag bool // whether field has a `toml` tag
|
||||||
|
index []int // represents the depth of an anonymous field
|
||||||
|
typ reflect.Type // the type of the field
|
||||||
|
}
|
||||||
|
|
||||||
|
// byName sorts field by name, breaking ties with depth,
|
||||||
|
// then breaking ties with "name came from toml tag", then
|
||||||
|
// breaking ties with index sequence.
|
||||||
|
type byName []field
|
||||||
|
|
||||||
|
func (x byName) Len() int { return len(x) }
|
||||||
|
|
||||||
|
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
func (x byName) Less(i, j int) bool {
|
||||||
|
if x[i].name != x[j].name {
|
||||||
|
return x[i].name < x[j].name
|
||||||
|
}
|
||||||
|
if len(x[i].index) != len(x[j].index) {
|
||||||
|
return len(x[i].index) < len(x[j].index)
|
||||||
|
}
|
||||||
|
if x[i].tag != x[j].tag {
|
||||||
|
return x[i].tag
|
||||||
|
}
|
||||||
|
return byIndex(x).Less(i, j)
|
||||||
|
}
|
||||||
|
|
||||||
|
// byIndex sorts field by index sequence.
|
||||||
|
type byIndex []field
|
||||||
|
|
||||||
|
func (x byIndex) Len() int { return len(x) }
|
||||||
|
|
||||||
|
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
|
|
||||||
|
func (x byIndex) Less(i, j int) bool {
|
||||||
|
for k, xik := range x[i].index {
|
||||||
|
if k >= len(x[j].index) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if xik != x[j].index[k] {
|
||||||
|
return xik < x[j].index[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(x[i].index) < len(x[j].index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeFields returns a list of fields that TOML should recognize for the given
|
||||||
|
// type. The algorithm is breadth-first search over the set of structs to
|
||||||
|
// include - the top struct and then any reachable anonymous structs.
|
||||||
|
func typeFields(t reflect.Type) []field {
|
||||||
|
// Anonymous fields to explore at the current level and the next.
|
||||||
|
current := []field{}
|
||||||
|
next := []field{{typ: t}}
|
||||||
|
|
||||||
|
// Count of queued names for current level and the next.
|
||||||
|
count := map[reflect.Type]int{}
|
||||||
|
nextCount := map[reflect.Type]int{}
|
||||||
|
|
||||||
|
// Types already visited at an earlier level.
|
||||||
|
visited := map[reflect.Type]bool{}
|
||||||
|
|
||||||
|
// Fields found.
|
||||||
|
var fields []field
|
||||||
|
|
||||||
|
for len(next) > 0 {
|
||||||
|
current, next = next, current[:0]
|
||||||
|
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||||
|
|
||||||
|
for _, f := range current {
|
||||||
|
if visited[f.typ] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
visited[f.typ] = true
|
||||||
|
|
||||||
|
// Scan f.typ for fields to include.
|
||||||
|
for i := 0; i < f.typ.NumField(); i++ {
|
||||||
|
sf := f.typ.Field(i)
|
||||||
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts := getOptions(sf.Tag)
|
||||||
|
if opts.skip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index := make([]int, len(f.index)+1)
|
||||||
|
copy(index, f.index)
|
||||||
|
index[len(f.index)] = i
|
||||||
|
|
||||||
|
ft := sf.Type
|
||||||
|
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||||
|
// Follow pointer.
|
||||||
|
ft = ft.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record found field and index sequence.
|
||||||
|
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||||
|
tagged := opts.name != ""
|
||||||
|
name := opts.name
|
||||||
|
if name == "" {
|
||||||
|
name = sf.Name
|
||||||
|
}
|
||||||
|
fields = append(fields, field{name, tagged, index, ft})
|
||||||
|
if count[f.typ] > 1 {
|
||||||
|
// If there were multiple instances, add a second,
|
||||||
|
// so that the annihilation code will see a duplicate.
|
||||||
|
// It only cares about the distinction between 1 or 2,
|
||||||
|
// so don't bother generating any more copies.
|
||||||
|
fields = append(fields, fields[len(fields)-1])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record new anonymous struct to explore in next round.
|
||||||
|
nextCount[ft]++
|
||||||
|
if nextCount[ft] == 1 {
|
||||||
|
f := field{name: ft.Name(), index: index, typ: ft}
|
||||||
|
next = append(next, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(byName(fields))
|
||||||
|
|
||||||
|
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||||
|
// except that fields with TOML tags are promoted.
|
||||||
|
|
||||||
|
// The fields are sorted in primary order of name, secondary order
|
||||||
|
// of field index length. Loop over names; for each name, delete
|
||||||
|
// hidden fields by choosing the one dominant field that survives.
|
||||||
|
out := fields[:0]
|
||||||
|
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||||
|
// One iteration per name.
|
||||||
|
// Find the sequence of fields with the name of this first field.
|
||||||
|
fi := fields[i]
|
||||||
|
name := fi.name
|
||||||
|
for advance = 1; i+advance < len(fields); advance++ {
|
||||||
|
fj := fields[i+advance]
|
||||||
|
if fj.name != name {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if advance == 1 { // Only one field with this name
|
||||||
|
out = append(out, fi)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dominant, ok := dominantField(fields[i : i+advance])
|
||||||
|
if ok {
|
||||||
|
out = append(out, dominant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = out
|
||||||
|
sort.Sort(byIndex(fields))
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// dominantField looks through the fields, all of which are known to
|
||||||
|
// have the same name, to find the single field that dominates the
|
||||||
|
// others using Go's embedding rules, modified by the presence of
|
||||||
|
// TOML tags. If there are multiple top-level fields, the boolean
|
||||||
|
// will be false: This condition is an error in Go and we skip all
|
||||||
|
// the fields.
|
||||||
|
func dominantField(fields []field) (field, bool) {
|
||||||
|
// The fields are sorted in increasing index-length order. The winner
|
||||||
|
// must therefore be one with the shortest index length. Drop all
|
||||||
|
// longer entries, which is easy: just truncate the slice.
|
||||||
|
length := len(fields[0].index)
|
||||||
|
tagged := -1 // Index of first tagged field.
|
||||||
|
for i, f := range fields {
|
||||||
|
if len(f.index) > length {
|
||||||
|
fields = fields[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if f.tag {
|
||||||
|
if tagged >= 0 {
|
||||||
|
// Multiple tagged fields at the same level: conflict.
|
||||||
|
// Return no field.
|
||||||
|
return field{}, false
|
||||||
|
}
|
||||||
|
tagged = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tagged >= 0 {
|
||||||
|
return fields[tagged], true
|
||||||
|
}
|
||||||
|
// All remaining fields have the same length. If there's more than one,
|
||||||
|
// we have a conflict (two fields named "X" at the same level) and we
|
||||||
|
// return no field.
|
||||||
|
if len(fields) > 1 {
|
||||||
|
return field{}, false
|
||||||
|
}
|
||||||
|
return fields[0], true
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[reflect.Type][]field
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||||
|
func cachedTypeFields(t reflect.Type) []field {
|
||||||
|
fieldCache.RLock()
|
||||||
|
f := fieldCache.m[t]
|
||||||
|
fieldCache.RUnlock()
|
||||||
|
if f != nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fields without lock.
|
||||||
|
// Might duplicate effort but won't hold other computations back.
|
||||||
|
f = typeFields(t)
|
||||||
|
if f == nil {
|
||||||
|
f = []field{}
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldCache.Lock()
|
||||||
|
if fieldCache.m == nil {
|
||||||
|
fieldCache.m = map[reflect.Type][]field{}
|
||||||
|
}
|
||||||
|
fieldCache.m[t] = f
|
||||||
|
fieldCache.Unlock()
|
||||||
|
return f
|
||||||
|
}
|
23
vendor/github.com/kisielk/gotool/.travis.yml
generated
vendored
23
vendor/github.com/kisielk/gotool/.travis.yml
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
||||||
- 1.7
|
|
||||||
- 1.8
|
|
||||||
- 1.9
|
|
||||||
- master
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: master
|
|
||||||
fast_finish: true
|
|
||||||
install:
|
|
||||||
- # Skip.
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go tool vet .
|
|
||||||
- go test -v -race ./...
|
|
32
vendor/github.com/kisielk/gotool/LEGAL
generated
vendored
32
vendor/github.com/kisielk/gotool/LEGAL
generated
vendored
|
@ -1,32 +0,0 @@
|
||||||
All the files in this distribution are covered under either the MIT
|
|
||||||
license (see the file LICENSE) except some files mentioned below.
|
|
||||||
|
|
||||||
match.go, match_test.go:
|
|
||||||
|
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
20
vendor/github.com/kisielk/gotool/LICENSE
generated
vendored
20
vendor/github.com/kisielk/gotool/LICENSE
generated
vendored
|
@ -1,20 +0,0 @@
|
||||||
Copyright (c) 2013 Kamil Kisiel <kamil@kamilkisiel.net>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
6
vendor/github.com/kisielk/gotool/README.md
generated
vendored
6
vendor/github.com/kisielk/gotool/README.md
generated
vendored
|
@ -1,6 +0,0 @@
|
||||||
gotool
|
|
||||||
======
|
|
||||||
[![GoDoc](https://godoc.org/github.com/kisielk/gotool?status.svg)](https://godoc.org/github.com/kisielk/gotool)
|
|
||||||
[![Build Status](https://travis-ci.org/kisielk/gotool.svg?branch=master)](https://travis-ci.org/kisielk/gotool)
|
|
||||||
|
|
||||||
Package gotool contains utility functions used to implement the standard "cmd/go" tool, provided as a convenience to developers who want to write tools with similar semantics.
|
|
1
vendor/github.com/kisielk/gotool/go.mod
generated
vendored
1
vendor/github.com/kisielk/gotool/go.mod
generated
vendored
|
@ -1 +0,0 @@
|
||||||
module "github.com/kisielk/gotool"
|
|
15
vendor/github.com/kisielk/gotool/go13.go
generated
vendored
15
vendor/github.com/kisielk/gotool/go13.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// +build !go1.4
|
|
||||||
|
|
||||||
package gotool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/build"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var gorootSrc = filepath.Join(runtime.GOROOT(), "src", "pkg")
|
|
||||||
|
|
||||||
func shouldIgnoreImport(p *build.Package) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
15
vendor/github.com/kisielk/gotool/go14-15.go
generated
vendored
15
vendor/github.com/kisielk/gotool/go14-15.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// +build go1.4,!go1.6
|
|
||||||
|
|
||||||
package gotool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/build"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var gorootSrc = filepath.Join(runtime.GOROOT(), "src")
|
|
||||||
|
|
||||||
func shouldIgnoreImport(p *build.Package) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
15
vendor/github.com/kisielk/gotool/go16-18.go
generated
vendored
15
vendor/github.com/kisielk/gotool/go16-18.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// +build go1.6,!go1.9
|
|
||||||
|
|
||||||
package gotool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/build"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
var gorootSrc = filepath.Join(runtime.GOROOT(), "src")
|
|
||||||
|
|
||||||
func shouldIgnoreImport(p *build.Package) bool {
|
|
||||||
return p == nil || len(p.InvalidGoFiles) == 0
|
|
||||||
}
|
|
27
vendor/github.com/kisielk/gotool/internal/load/path.go
generated
vendored
27
vendor/github.com/kisielk/gotool/internal/load/path.go
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
// Copyright 2017 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.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package load
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// hasPathPrefix reports whether the path s begins with the
|
|
||||||
// elements in prefix.
|
|
||||||
func hasPathPrefix(s, prefix string) bool {
|
|
||||||
switch {
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
case len(s) == len(prefix):
|
|
||||||
return s == prefix
|
|
||||||
case len(s) > len(prefix):
|
|
||||||
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
|
||||||
return strings.HasPrefix(s, prefix)
|
|
||||||
}
|
|
||||||
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
|
||||||
}
|
|
||||||
}
|
|
25
vendor/github.com/kisielk/gotool/internal/load/pkg.go
generated
vendored
25
vendor/github.com/kisielk/gotool/internal/load/pkg.go
generated
vendored
|
@ -1,25 +0,0 @@
|
||||||
// Copyright 2011 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.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
// Package load loads packages.
|
|
||||||
package load
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// isStandardImportPath reports whether $GOROOT/src/path should be considered
|
|
||||||
// part of the standard distribution. For historical reasons we allow people to add
|
|
||||||
// their own code to $GOROOT instead of using $GOPATH, but we assume that
|
|
||||||
// code will start with a domain name (dot in the first element).
|
|
||||||
func isStandardImportPath(path string) bool {
|
|
||||||
i := strings.Index(path, "/")
|
|
||||||
if i < 0 {
|
|
||||||
i = len(path)
|
|
||||||
}
|
|
||||||
elem := path[:i]
|
|
||||||
return !strings.Contains(elem, ".")
|
|
||||||
}
|
|
354
vendor/github.com/kisielk/gotool/internal/load/search.go
generated
vendored
354
vendor/github.com/kisielk/gotool/internal/load/search.go
generated
vendored
|
@ -1,354 +0,0 @@
|
||||||
// Copyright 2017 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.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package load
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context specifies values for operation of ImportPaths that would
|
|
||||||
// otherwise come from cmd/go/internal/cfg package.
|
|
||||||
//
|
|
||||||
// This is a construct added for gotool purposes and doesn't have
|
|
||||||
// an equivalent upstream in cmd/go.
|
|
||||||
type Context struct {
|
|
||||||
// BuildContext is the build context to use.
|
|
||||||
BuildContext build.Context
|
|
||||||
|
|
||||||
// GOROOTsrc is the location of the src directory in GOROOT.
|
|
||||||
// At this time, it's used only in MatchPackages to skip
|
|
||||||
// GOOROOT/src entry from BuildContext.SrcDirs output.
|
|
||||||
GOROOTsrc string
|
|
||||||
}
|
|
||||||
|
|
||||||
// allPackages returns all the packages that can be found
|
|
||||||
// under the $GOPATH directories and $GOROOT matching pattern.
|
|
||||||
// The pattern is either "all" (all packages), "std" (standard packages),
|
|
||||||
// "cmd" (standard commands), or a path including "...".
|
|
||||||
func (c *Context) allPackages(pattern string) []string {
|
|
||||||
pkgs := c.MatchPackages(pattern)
|
|
||||||
if len(pkgs) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
|
||||||
}
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// allPackagesInFS is like allPackages but is passed a pattern
|
|
||||||
// beginning ./ or ../, meaning it should scan the tree rooted
|
|
||||||
// at the given directory. There are ... in the pattern too.
|
|
||||||
func (c *Context) allPackagesInFS(pattern string) []string {
|
|
||||||
pkgs := c.MatchPackagesInFS(pattern)
|
|
||||||
if len(pkgs) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
|
||||||
}
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchPackages returns a list of package paths matching pattern
|
|
||||||
// (see go help packages for pattern syntax).
|
|
||||||
func (c *Context) MatchPackages(pattern string) []string {
|
|
||||||
match := func(string) bool { return true }
|
|
||||||
treeCanMatch := func(string) bool { return true }
|
|
||||||
if !IsMetaPackage(pattern) {
|
|
||||||
match = matchPattern(pattern)
|
|
||||||
treeCanMatch = treeCanMatchPattern(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
have := map[string]bool{
|
|
||||||
"builtin": true, // ignore pseudo-package that exists only for documentation
|
|
||||||
}
|
|
||||||
if !c.BuildContext.CgoEnabled {
|
|
||||||
have["runtime/cgo"] = true // ignore during walk
|
|
||||||
}
|
|
||||||
var pkgs []string
|
|
||||||
|
|
||||||
for _, src := range c.BuildContext.SrcDirs() {
|
|
||||||
if (pattern == "std" || pattern == "cmd") && src != c.GOROOTsrc {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
src = filepath.Clean(src) + string(filepath.Separator)
|
|
||||||
root := src
|
|
||||||
if pattern == "cmd" {
|
|
||||||
root += "cmd" + string(filepath.Separator)
|
|
||||||
}
|
|
||||||
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
|
||||||
if err != nil || path == src {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
want := true
|
|
||||||
// Avoid .foo, _foo, and testdata directory trees.
|
|
||||||
_, elem := filepath.Split(path)
|
|
||||||
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
|
||||||
want = false
|
|
||||||
}
|
|
||||||
|
|
||||||
name := filepath.ToSlash(path[len(src):])
|
|
||||||
if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
|
|
||||||
// The name "std" is only the standard library.
|
|
||||||
// If the name is cmd, it's the root of the command tree.
|
|
||||||
want = false
|
|
||||||
}
|
|
||||||
if !treeCanMatch(name) {
|
|
||||||
want = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fi.IsDir() {
|
|
||||||
if fi.Mode()&os.ModeSymlink != 0 && want {
|
|
||||||
if target, err := os.Stat(path); err == nil && target.IsDir() {
|
|
||||||
fmt.Fprintf(os.Stderr, "warning: ignoring symlink %s\n", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !want {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
if have[name] {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
have[name] = true
|
|
||||||
if !match(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pkg, err := c.BuildContext.ImportDir(path, 0)
|
|
||||||
if err != nil {
|
|
||||||
if _, noGo := err.(*build.NoGoError); noGo {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are expanding "cmd", skip main
|
|
||||||
// packages under cmd/vendor. At least as of
|
|
||||||
// March, 2017, there is one there for the
|
|
||||||
// vendored pprof tool.
|
|
||||||
if pattern == "cmd" && strings.HasPrefix(pkg.ImportPath, "cmd/vendor") && pkg.Name == "main" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs = append(pkgs, name)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchPackagesInFS returns a list of package paths matching pattern,
|
|
||||||
// which must begin with ./ or ../
|
|
||||||
// (see go help packages for pattern syntax).
|
|
||||||
func (c *Context) MatchPackagesInFS(pattern string) []string {
|
|
||||||
// Find directory to begin the scan.
|
|
||||||
// Could be smarter but this one optimization
|
|
||||||
// is enough for now, since ... is usually at the
|
|
||||||
// end of a path.
|
|
||||||
i := strings.Index(pattern, "...")
|
|
||||||
dir, _ := path.Split(pattern[:i])
|
|
||||||
|
|
||||||
// pattern begins with ./ or ../.
|
|
||||||
// path.Clean will discard the ./ but not the ../.
|
|
||||||
// We need to preserve the ./ for pattern matching
|
|
||||||
// and in the returned import paths.
|
|
||||||
prefix := ""
|
|
||||||
if strings.HasPrefix(pattern, "./") {
|
|
||||||
prefix = "./"
|
|
||||||
}
|
|
||||||
match := matchPattern(pattern)
|
|
||||||
|
|
||||||
var pkgs []string
|
|
||||||
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
|
||||||
if err != nil || !fi.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if path == dir {
|
|
||||||
// filepath.Walk starts at dir and recurses. For the recursive case,
|
|
||||||
// the path is the result of filepath.Join, which calls filepath.Clean.
|
|
||||||
// The initial case is not Cleaned, though, so we do this explicitly.
|
|
||||||
//
|
|
||||||
// This converts a path like "./io/" to "io". Without this step, running
|
|
||||||
// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
|
|
||||||
// package, because prepending the prefix "./" to the unclean path would
|
|
||||||
// result in "././io", and match("././io") returns false.
|
|
||||||
path = filepath.Clean(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
|
|
||||||
_, elem := filepath.Split(path)
|
|
||||||
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
|
|
||||||
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
name := prefix + filepath.ToSlash(path)
|
|
||||||
if !match(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// We keep the directory if we can import it, or if we can't import it
|
|
||||||
// due to invalid Go source files. This means that directories containing
|
|
||||||
// parse errors will be built (and fail) instead of being silently skipped
|
|
||||||
// as not matching the pattern. Go 1.5 and earlier skipped, but that
|
|
||||||
// behavior means people miss serious mistakes.
|
|
||||||
// See golang.org/issue/11407.
|
|
||||||
if p, err := c.BuildContext.ImportDir(path, 0); err != nil && (p == nil || len(p.InvalidGoFiles) == 0) {
|
|
||||||
if _, noGo := err.(*build.NoGoError); !noGo {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pkgs = append(pkgs, name)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// treeCanMatchPattern(pattern)(name) reports whether
|
|
||||||
// name or children of name can possibly match pattern.
|
|
||||||
// Pattern is the same limited glob accepted by matchPattern.
|
|
||||||
func treeCanMatchPattern(pattern string) func(name string) bool {
|
|
||||||
wildCard := false
|
|
||||||
if i := strings.Index(pattern, "..."); i >= 0 {
|
|
||||||
wildCard = true
|
|
||||||
pattern = pattern[:i]
|
|
||||||
}
|
|
||||||
return func(name string) bool {
|
|
||||||
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
|
|
||||||
wildCard && strings.HasPrefix(name, pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchPattern(pattern)(name) reports whether
|
|
||||||
// name matches pattern. Pattern is a limited glob
|
|
||||||
// pattern in which '...' means 'any string' and there
|
|
||||||
// is no other special syntax.
|
|
||||||
// Unfortunately, there are two special cases. Quoting "go help packages":
|
|
||||||
//
|
|
||||||
// First, /... at the end of the pattern can match an empty string,
|
|
||||||
// so that net/... matches both net and packages in its subdirectories, like net/http.
|
|
||||||
// Second, any slash-separted pattern element containing a wildcard never
|
|
||||||
// participates in a match of the "vendor" element in the path of a vendored
|
|
||||||
// package, so that ./... does not match packages in subdirectories of
|
|
||||||
// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
|
|
||||||
// Note, however, that a directory named vendor that itself contains code
|
|
||||||
// is not a vendored package: cmd/vendor would be a command named vendor,
|
|
||||||
// and the pattern cmd/... matches it.
|
|
||||||
func matchPattern(pattern string) func(name string) bool {
|
|
||||||
// Convert pattern to regular expression.
|
|
||||||
// The strategy for the trailing /... is to nest it in an explicit ? expression.
|
|
||||||
// The strategy for the vendor exclusion is to change the unmatchable
|
|
||||||
// vendor strings to a disallowed code point (vendorChar) and to use
|
|
||||||
// "(anything but that codepoint)*" as the implementation of the ... wildcard.
|
|
||||||
// This is a bit complicated but the obvious alternative,
|
|
||||||
// namely a hand-written search like in most shell glob matchers,
|
|
||||||
// is too easy to make accidentally exponential.
|
|
||||||
// Using package regexp guarantees linear-time matching.
|
|
||||||
|
|
||||||
const vendorChar = "\x00"
|
|
||||||
|
|
||||||
if strings.Contains(pattern, vendorChar) {
|
|
||||||
return func(name string) bool { return false }
|
|
||||||
}
|
|
||||||
|
|
||||||
re := regexp.QuoteMeta(pattern)
|
|
||||||
re = replaceVendor(re, vendorChar)
|
|
||||||
switch {
|
|
||||||
case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
|
|
||||||
re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
|
|
||||||
case re == vendorChar+`/\.\.\.`:
|
|
||||||
re = `(/vendor|/` + vendorChar + `/\.\.\.)`
|
|
||||||
case strings.HasSuffix(re, `/\.\.\.`):
|
|
||||||
re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
|
|
||||||
}
|
|
||||||
re = strings.Replace(re, `\.\.\.`, `[^`+vendorChar+`]*`, -1)
|
|
||||||
|
|
||||||
reg := regexp.MustCompile(`^` + re + `$`)
|
|
||||||
|
|
||||||
return func(name string) bool {
|
|
||||||
if strings.Contains(name, vendorChar) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return reg.MatchString(replaceVendor(name, vendorChar))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceVendor returns the result of replacing
|
|
||||||
// non-trailing vendor path elements in x with repl.
|
|
||||||
func replaceVendor(x, repl string) string {
|
|
||||||
if !strings.Contains(x, "vendor") {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
elem := strings.Split(x, "/")
|
|
||||||
for i := 0; i < len(elem)-1; i++ {
|
|
||||||
if elem[i] == "vendor" {
|
|
||||||
elem[i] = repl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(elem, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportPaths returns the import paths to use for the given command line.
|
|
||||||
func (c *Context) ImportPaths(args []string) []string {
|
|
||||||
args = c.ImportPathsNoDotExpansion(args)
|
|
||||||
var out []string
|
|
||||||
for _, a := range args {
|
|
||||||
if strings.Contains(a, "...") {
|
|
||||||
if build.IsLocalImport(a) {
|
|
||||||
out = append(out, c.allPackagesInFS(a)...)
|
|
||||||
} else {
|
|
||||||
out = append(out, c.allPackages(a)...)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, a)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportPathsNoDotExpansion returns the import paths to use for the given
|
|
||||||
// command line, but it does no ... expansion.
|
|
||||||
func (c *Context) ImportPathsNoDotExpansion(args []string) []string {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return []string{"."}
|
|
||||||
}
|
|
||||||
var out []string
|
|
||||||
for _, a := range args {
|
|
||||||
// Arguments are supposed to be import paths, but
|
|
||||||
// as a courtesy to Windows developers, rewrite \ to /
|
|
||||||
// in command-line arguments. Handles .\... and so on.
|
|
||||||
if filepath.Separator == '\\' {
|
|
||||||
a = strings.Replace(a, `\`, `/`, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put argument in canonical form, but preserve leading ./.
|
|
||||||
if strings.HasPrefix(a, "./") {
|
|
||||||
a = "./" + path.Clean(a)
|
|
||||||
if a == "./." {
|
|
||||||
a = "."
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
a = path.Clean(a)
|
|
||||||
}
|
|
||||||
if IsMetaPackage(a) {
|
|
||||||
out = append(out, c.allPackages(a)...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, a)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsMetaPackage checks if name is a reserved package name that expands to multiple packages.
|
|
||||||
func IsMetaPackage(name string) bool {
|
|
||||||
return name == "std" || name == "cmd" || name == "all"
|
|
||||||
}
|
|
56
vendor/github.com/kisielk/gotool/match.go
generated
vendored
56
vendor/github.com/kisielk/gotool/match.go
generated
vendored
|
@ -1,56 +0,0 @@
|
||||||
// Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
// +build go1.9
|
|
||||||
|
|
||||||
package gotool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/kisielk/gotool/internal/load"
|
|
||||||
)
|
|
||||||
|
|
||||||
// importPaths returns the import paths to use for the given command line.
|
|
||||||
func (c *Context) importPaths(args []string) []string {
|
|
||||||
lctx := load.Context{
|
|
||||||
BuildContext: c.BuildContext,
|
|
||||||
GOROOTsrc: c.joinPath(c.BuildContext.GOROOT, "src"),
|
|
||||||
}
|
|
||||||
return lctx.ImportPaths(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// joinPath calls c.BuildContext.JoinPath (if not nil) or else filepath.Join.
|
|
||||||
//
|
|
||||||
// It's a copy of the unexported build.Context.joinPath helper.
|
|
||||||
func (c *Context) joinPath(elem ...string) string {
|
|
||||||
if f := c.BuildContext.JoinPath; f != nil {
|
|
||||||
return f(elem...)
|
|
||||||
}
|
|
||||||
return filepath.Join(elem...)
|
|
||||||
}
|
|
317
vendor/github.com/kisielk/gotool/match18.go
generated
vendored
317
vendor/github.com/kisielk/gotool/match18.go
generated
vendored
|
@ -1,317 +0,0 @@
|
||||||
// Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following disclaimer
|
|
||||||
// in the documentation and/or other materials provided with the
|
|
||||||
// distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived from
|
|
||||||
// this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
// +build !go1.9
|
|
||||||
|
|
||||||
package gotool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/build"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file contains code from the Go distribution.
|
|
||||||
|
|
||||||
// matchPattern(pattern)(name) reports whether
|
|
||||||
// name matches pattern. Pattern is a limited glob
|
|
||||||
// pattern in which '...' means 'any string' and there
|
|
||||||
// is no other special syntax.
|
|
||||||
func matchPattern(pattern string) func(name string) bool {
|
|
||||||
re := regexp.QuoteMeta(pattern)
|
|
||||||
re = strings.Replace(re, `\.\.\.`, `.*`, -1)
|
|
||||||
// Special case: foo/... matches foo too.
|
|
||||||
if strings.HasSuffix(re, `/.*`) {
|
|
||||||
re = re[:len(re)-len(`/.*`)] + `(/.*)?`
|
|
||||||
}
|
|
||||||
reg := regexp.MustCompile(`^` + re + `$`)
|
|
||||||
return reg.MatchString
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchPackages returns a list of package paths matching pattern
|
|
||||||
// (see go help packages for pattern syntax).
|
|
||||||
func (c *Context) matchPackages(pattern string) []string {
|
|
||||||
match := func(string) bool { return true }
|
|
||||||
treeCanMatch := func(string) bool { return true }
|
|
||||||
if !isMetaPackage(pattern) {
|
|
||||||
match = matchPattern(pattern)
|
|
||||||
treeCanMatch = treeCanMatchPattern(pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
have := map[string]bool{
|
|
||||||
"builtin": true, // ignore pseudo-package that exists only for documentation
|
|
||||||
}
|
|
||||||
if !c.BuildContext.CgoEnabled {
|
|
||||||
have["runtime/cgo"] = true // ignore during walk
|
|
||||||
}
|
|
||||||
var pkgs []string
|
|
||||||
|
|
||||||
for _, src := range c.BuildContext.SrcDirs() {
|
|
||||||
if (pattern == "std" || pattern == "cmd") && src != gorootSrc {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
src = filepath.Clean(src) + string(filepath.Separator)
|
|
||||||
root := src
|
|
||||||
if pattern == "cmd" {
|
|
||||||
root += "cmd" + string(filepath.Separator)
|
|
||||||
}
|
|
||||||
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
|
||||||
if err != nil || !fi.IsDir() || path == src {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid .foo, _foo, and testdata directory trees.
|
|
||||||
_, elem := filepath.Split(path)
|
|
||||||
if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
name := filepath.ToSlash(path[len(src):])
|
|
||||||
if pattern == "std" && (!isStandardImportPath(name) || name == "cmd") {
|
|
||||||
// The name "std" is only the standard library.
|
|
||||||
// If the name is cmd, it's the root of the command tree.
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
if !treeCanMatch(name) {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
if have[name] {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
have[name] = true
|
|
||||||
if !match(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = c.BuildContext.ImportDir(path, 0)
|
|
||||||
if err != nil {
|
|
||||||
if _, noGo := err.(*build.NoGoError); noGo {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pkgs = append(pkgs, name)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// importPathsNoDotExpansion returns the import paths to use for the given
|
|
||||||
// command line, but it does no ... expansion.
|
|
||||||
func (c *Context) importPathsNoDotExpansion(args []string) []string {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return []string{"."}
|
|
||||||
}
|
|
||||||
var out []string
|
|
||||||
for _, a := range args {
|
|
||||||
// Arguments are supposed to be import paths, but
|
|
||||||
// as a courtesy to Windows developers, rewrite \ to /
|
|
||||||
// in command-line arguments. Handles .\... and so on.
|
|
||||||
if filepath.Separator == '\\' {
|
|
||||||
a = strings.Replace(a, `\`, `/`, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put argument in canonical form, but preserve leading ./.
|
|
||||||
if strings.HasPrefix(a, "./") {
|
|
||||||
a = "./" + path.Clean(a)
|
|
||||||
if a == "./." {
|
|
||||||
a = "."
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
a = path.Clean(a)
|
|
||||||
}
|
|
||||||
if isMetaPackage(a) {
|
|
||||||
out = append(out, c.allPackages(a)...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, a)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// importPaths returns the import paths to use for the given command line.
|
|
||||||
func (c *Context) importPaths(args []string) []string {
|
|
||||||
args = c.importPathsNoDotExpansion(args)
|
|
||||||
var out []string
|
|
||||||
for _, a := range args {
|
|
||||||
if strings.Contains(a, "...") {
|
|
||||||
if build.IsLocalImport(a) {
|
|
||||||
out = append(out, c.allPackagesInFS(a)...)
|
|
||||||
} else {
|
|
||||||
out = append(out, c.allPackages(a)...)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, a)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// allPackages returns all the packages that can be found
|
|
||||||
// under the $GOPATH directories and $GOROOT matching pattern.
|
|
||||||
// The pattern is either "all" (all packages), "std" (standard packages),
|
|
||||||
// "cmd" (standard commands), or a path including "...".
|
|
||||||
func (c *Context) allPackages(pattern string) []string {
|
|
||||||
pkgs := c.matchPackages(pattern)
|
|
||||||
if len(pkgs) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
|
||||||
}
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// allPackagesInFS is like allPackages but is passed a pattern
|
|
||||||
// beginning ./ or ../, meaning it should scan the tree rooted
|
|
||||||
// at the given directory. There are ... in the pattern too.
|
|
||||||
func (c *Context) allPackagesInFS(pattern string) []string {
|
|
||||||
pkgs := c.matchPackagesInFS(pattern)
|
|
||||||
if len(pkgs) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern)
|
|
||||||
}
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchPackagesInFS returns a list of package paths matching pattern,
|
|
||||||
// which must begin with ./ or ../
|
|
||||||
// (see go help packages for pattern syntax).
|
|
||||||
func (c *Context) matchPackagesInFS(pattern string) []string {
|
|
||||||
// Find directory to begin the scan.
|
|
||||||
// Could be smarter but this one optimization
|
|
||||||
// is enough for now, since ... is usually at the
|
|
||||||
// end of a path.
|
|
||||||
i := strings.Index(pattern, "...")
|
|
||||||
dir, _ := path.Split(pattern[:i])
|
|
||||||
|
|
||||||
// pattern begins with ./ or ../.
|
|
||||||
// path.Clean will discard the ./ but not the ../.
|
|
||||||
// We need to preserve the ./ for pattern matching
|
|
||||||
// and in the returned import paths.
|
|
||||||
prefix := ""
|
|
||||||
if strings.HasPrefix(pattern, "./") {
|
|
||||||
prefix = "./"
|
|
||||||
}
|
|
||||||
match := matchPattern(pattern)
|
|
||||||
|
|
||||||
var pkgs []string
|
|
||||||
filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error {
|
|
||||||
if err != nil || !fi.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if path == dir {
|
|
||||||
// filepath.Walk starts at dir and recurses. For the recursive case,
|
|
||||||
// the path is the result of filepath.Join, which calls filepath.Clean.
|
|
||||||
// The initial case is not Cleaned, though, so we do this explicitly.
|
|
||||||
//
|
|
||||||
// This converts a path like "./io/" to "io". Without this step, running
|
|
||||||
// "cd $GOROOT/src; go list ./io/..." would incorrectly skip the io
|
|
||||||
// package, because prepending the prefix "./" to the unclean path would
|
|
||||||
// result in "././io", and match("././io") returns false.
|
|
||||||
path = filepath.Clean(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..".
|
|
||||||
_, elem := filepath.Split(path)
|
|
||||||
dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".."
|
|
||||||
if dot || strings.HasPrefix(elem, "_") || elem == "testdata" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
name := prefix + filepath.ToSlash(path)
|
|
||||||
if !match(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// We keep the directory if we can import it, or if we can't import it
|
|
||||||
// due to invalid Go source files. This means that directories containing
|
|
||||||
// parse errors will be built (and fail) instead of being silently skipped
|
|
||||||
// as not matching the pattern. Go 1.5 and earlier skipped, but that
|
|
||||||
// behavior means people miss serious mistakes.
|
|
||||||
// See golang.org/issue/11407.
|
|
||||||
if p, err := c.BuildContext.ImportDir(path, 0); err != nil && shouldIgnoreImport(p) {
|
|
||||||
if _, noGo := err.(*build.NoGoError); !noGo {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pkgs = append(pkgs, name)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
// isMetaPackage checks if name is a reserved package name that expands to multiple packages.
|
|
||||||
func isMetaPackage(name string) bool {
|
|
||||||
return name == "std" || name == "cmd" || name == "all"
|
|
||||||
}
|
|
||||||
|
|
||||||
// isStandardImportPath reports whether $GOROOT/src/path should be considered
|
|
||||||
// part of the standard distribution. For historical reasons we allow people to add
|
|
||||||
// their own code to $GOROOT instead of using $GOPATH, but we assume that
|
|
||||||
// code will start with a domain name (dot in the first element).
|
|
||||||
func isStandardImportPath(path string) bool {
|
|
||||||
i := strings.Index(path, "/")
|
|
||||||
if i < 0 {
|
|
||||||
i = len(path)
|
|
||||||
}
|
|
||||||
elem := path[:i]
|
|
||||||
return !strings.Contains(elem, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasPathPrefix reports whether the path s begins with the
|
|
||||||
// elements in prefix.
|
|
||||||
func hasPathPrefix(s, prefix string) bool {
|
|
||||||
switch {
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
case len(s) == len(prefix):
|
|
||||||
return s == prefix
|
|
||||||
case len(s) > len(prefix):
|
|
||||||
if prefix != "" && prefix[len(prefix)-1] == '/' {
|
|
||||||
return strings.HasPrefix(s, prefix)
|
|
||||||
}
|
|
||||||
return s[len(prefix)] == '/' && s[:len(prefix)] == prefix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// treeCanMatchPattern(pattern)(name) reports whether
|
|
||||||
// name or children of name can possibly match pattern.
|
|
||||||
// Pattern is the same limited glob accepted by matchPattern.
|
|
||||||
func treeCanMatchPattern(pattern string) func(name string) bool {
|
|
||||||
wildCard := false
|
|
||||||
if i := strings.Index(pattern, "..."); i >= 0 {
|
|
||||||
wildCard = true
|
|
||||||
pattern = pattern[:i]
|
|
||||||
}
|
|
||||||
return func(name string) bool {
|
|
||||||
return len(name) <= len(pattern) && hasPathPrefix(pattern, name) ||
|
|
||||||
wildCard && strings.HasPrefix(name, pattern)
|
|
||||||
}
|
|
||||||
}
|
|
48
vendor/github.com/kisielk/gotool/tool.go
generated
vendored
48
vendor/github.com/kisielk/gotool/tool.go
generated
vendored
|
@ -1,48 +0,0 @@
|
||||||
// Package gotool contains utility functions used to implement the standard
|
|
||||||
// "cmd/go" tool, provided as a convenience to developers who want to write
|
|
||||||
// tools with similar semantics.
|
|
||||||
package gotool
|
|
||||||
|
|
||||||
import "go/build"
|
|
||||||
|
|
||||||
// Export functions here to make it easier to keep the implementations up to date with upstream.
|
|
||||||
|
|
||||||
// DefaultContext is the default context that uses build.Default.
|
|
||||||
var DefaultContext = Context{
|
|
||||||
BuildContext: build.Default,
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Context specifies the supporting context.
|
|
||||||
type Context struct {
|
|
||||||
// BuildContext is the build.Context that is used when computing import paths.
|
|
||||||
BuildContext build.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportPaths returns the import paths to use for the given command line.
|
|
||||||
//
|
|
||||||
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
|
|
||||||
// The path "std" is expanded to all packages in the Go standard library.
|
|
||||||
// The path "cmd" is expanded to all Go standard commands.
|
|
||||||
// The string "..." is treated as a wildcard within a path.
|
|
||||||
// When matching recursively, directories are ignored if they are prefixed with
|
|
||||||
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
|
|
||||||
// Relative import paths are not converted to full import paths.
|
|
||||||
// If args is empty, a single element "." is returned.
|
|
||||||
func (c *Context) ImportPaths(args []string) []string {
|
|
||||||
return c.importPaths(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportPaths returns the import paths to use for the given command line
|
|
||||||
// using default context.
|
|
||||||
//
|
|
||||||
// The path "all" is expanded to all packages in $GOPATH and $GOROOT.
|
|
||||||
// The path "std" is expanded to all packages in the Go standard library.
|
|
||||||
// The path "cmd" is expanded to all Go standard commands.
|
|
||||||
// The string "..." is treated as a wildcard within a path.
|
|
||||||
// When matching recursively, directories are ignored if they are prefixed with
|
|
||||||
// a dot or an underscore (such as ".foo" or "_foo"), or are named "testdata".
|
|
||||||
// Relative import paths are not converted to full import paths.
|
|
||||||
// If args is empty, a single element "." is returned.
|
|
||||||
func ImportPaths(args []string) []string {
|
|
||||||
return DefaultContext.importPaths(args)
|
|
||||||
}
|
|
241
vendor/golang.org/x/tools/go/packages/doc.go
generated
vendored
Normal file
241
vendor/golang.org/x/tools/go/packages/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
// Copyright 2018 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 packages loads Go packages for inspection and analysis.
|
||||||
|
|
||||||
|
Note: Though this package is ready for widespread use, we may make minor
|
||||||
|
breaking changes if absolutely necessary. Any such change will be
|
||||||
|
announced on golang-tools@ at least one week before it is committed. No
|
||||||
|
more breaking changes will be made after December 1, 2018.
|
||||||
|
|
||||||
|
The Load function takes as input a list of patterns and return a list of Package
|
||||||
|
structs describing individual packages matched by those patterns.
|
||||||
|
The LoadMode controls the amount of detail in the loaded packages.
|
||||||
|
|
||||||
|
Load passes most patterns directly to the underlying build tool,
|
||||||
|
but all patterns with the prefix "query=", where query is a
|
||||||
|
non-empty string of letters from [a-z], are reserved and may be
|
||||||
|
interpreted as query operators.
|
||||||
|
|
||||||
|
Only two query operators are currently supported, "file" and "pattern".
|
||||||
|
|
||||||
|
The query "file=path/to/file.go" matches the package or packages enclosing
|
||||||
|
the Go source file path/to/file.go. For example "file=~/go/src/fmt/print.go"
|
||||||
|
might returns the packages "fmt" and "fmt [fmt.test]".
|
||||||
|
|
||||||
|
The query "pattern=string" causes "string" to be passed directly to
|
||||||
|
the underlying build tool. In most cases this is unnecessary,
|
||||||
|
but an application can use Load("pattern=" + x) as an escaping mechanism
|
||||||
|
to ensure that x is not interpreted as a query operator if it contains '='.
|
||||||
|
|
||||||
|
A third query "name=identifier" will be added soon.
|
||||||
|
It will match packages whose package declaration contains the specified identifier.
|
||||||
|
For example, "name=rand" would match the packages "math/rand" and "crypto/rand",
|
||||||
|
and "name=main" would match all executables.
|
||||||
|
|
||||||
|
All other query operators are reserved for future use and currently
|
||||||
|
cause Load to report an error.
|
||||||
|
|
||||||
|
The Package struct provides basic information about the package, including
|
||||||
|
|
||||||
|
- ID, a unique identifier for the package in the returned set;
|
||||||
|
- GoFiles, the names of the package's Go source files;
|
||||||
|
- Imports, a map from source import strings to the Packages they name;
|
||||||
|
- Types, the type information for the package's exported symbols;
|
||||||
|
- Syntax, the parsed syntax trees for the package's source code; and
|
||||||
|
- TypeInfo, the result of a complete type-check of the package syntax trees.
|
||||||
|
|
||||||
|
(See the documentation for type Package for the complete list of fields
|
||||||
|
and more detailed descriptions.)
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
Load(nil, "bytes", "unicode...")
|
||||||
|
|
||||||
|
returns four Package structs describing the standard library packages
|
||||||
|
bytes, unicode, unicode/utf16, and unicode/utf8. Note that one pattern
|
||||||
|
can match multiple packages and that a package might be matched by
|
||||||
|
multiple patterns: in general it is not possible to determine which
|
||||||
|
packages correspond to which patterns.
|
||||||
|
|
||||||
|
Note that the list returned by Load contains only the packages matched
|
||||||
|
by the patterns. Their dependencies can be found by walking the import
|
||||||
|
graph using the Imports fields.
|
||||||
|
|
||||||
|
The Load function can be configured by passing a pointer to a Config as
|
||||||
|
the first argument. A nil Config is equivalent to the zero Config, which
|
||||||
|
causes Load to run in LoadFiles mode, collecting minimal information.
|
||||||
|
See the documentation for type Config for details.
|
||||||
|
|
||||||
|
As noted earlier, the Config.Mode controls the amount of detail
|
||||||
|
reported about the loaded packages, with each mode returning all the data of the
|
||||||
|
previous mode with some extra added. See the documentation for type LoadMode
|
||||||
|
for details.
|
||||||
|
|
||||||
|
Most tools should pass their command-line arguments (after any flags)
|
||||||
|
uninterpreted to the loader, so that the loader can interpret them
|
||||||
|
according to the conventions of the underlying build system.
|
||||||
|
See the Example function for typical usage.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package packages // import "golang.org/x/tools/go/packages"
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Motivation and design considerations
|
||||||
|
|
||||||
|
The new package's design solves problems addressed by two existing
|
||||||
|
packages: go/build, which locates and describes packages, and
|
||||||
|
golang.org/x/tools/go/loader, which loads, parses and type-checks them.
|
||||||
|
The go/build.Package structure encodes too much of the 'go build' way
|
||||||
|
of organizing projects, leaving us in need of a data type that describes a
|
||||||
|
package of Go source code independent of the underlying build system.
|
||||||
|
We wanted something that works equally well with go build and vgo, and
|
||||||
|
also other build systems such as Bazel and Blaze, making it possible to
|
||||||
|
construct analysis tools that work in all these environments.
|
||||||
|
Tools such as errcheck and staticcheck were essentially unavailable to
|
||||||
|
the Go community at Google, and some of Google's internal tools for Go
|
||||||
|
are unavailable externally.
|
||||||
|
This new package provides a uniform way to obtain package metadata by
|
||||||
|
querying each of these build systems, optionally supporting their
|
||||||
|
preferred command-line notations for packages, so that tools integrate
|
||||||
|
neatly with users' build environments. The Metadata query function
|
||||||
|
executes an external query tool appropriate to the current workspace.
|
||||||
|
|
||||||
|
Loading packages always returns the complete import graph "all the way down",
|
||||||
|
even if all you want is information about a single package, because the query
|
||||||
|
mechanisms of all the build systems we currently support ({go,vgo} list, and
|
||||||
|
blaze/bazel aspect-based query) cannot provide detailed information
|
||||||
|
about one package without visiting all its dependencies too, so there is
|
||||||
|
no additional asymptotic cost to providing transitive information.
|
||||||
|
(This property might not be true of a hypothetical 5th build system.)
|
||||||
|
|
||||||
|
In calls to TypeCheck, all initial packages, and any package that
|
||||||
|
transitively depends on one of them, must be loaded from source.
|
||||||
|
Consider A->B->C->D->E: if A,C are initial, A,B,C must be loaded from
|
||||||
|
source; D may be loaded from export data, and E may not be loaded at all
|
||||||
|
(though it's possible that D's export data mentions it, so a
|
||||||
|
types.Package may be created for it and exposed.)
|
||||||
|
|
||||||
|
The old loader had a feature to suppress type-checking of function
|
||||||
|
bodies on a per-package basis, primarily intended to reduce the work of
|
||||||
|
obtaining type information for imported packages. Now that imports are
|
||||||
|
satisfied by export data, the optimization no longer seems necessary.
|
||||||
|
|
||||||
|
Despite some early attempts, the old loader did not exploit export data,
|
||||||
|
instead always using the equivalent of WholeProgram mode. This was due
|
||||||
|
to the complexity of mixing source and export data packages (now
|
||||||
|
resolved by the upward traversal mentioned above), and because export data
|
||||||
|
files were nearly always missing or stale. Now that 'go build' supports
|
||||||
|
caching, all the underlying build systems can guarantee to produce
|
||||||
|
export data in a reasonable (amortized) time.
|
||||||
|
|
||||||
|
Test "main" packages synthesized by the build system are now reported as
|
||||||
|
first-class packages, avoiding the need for clients (such as go/ssa) to
|
||||||
|
reinvent this generation logic.
|
||||||
|
|
||||||
|
One way in which go/packages is simpler than the old loader is in its
|
||||||
|
treatment of in-package tests. In-package tests are packages that
|
||||||
|
consist of all the files of the library under test, plus the test files.
|
||||||
|
The old loader constructed in-package tests by a two-phase process of
|
||||||
|
mutation called "augmentation": first it would construct and type check
|
||||||
|
all the ordinary library packages and type-check the packages that
|
||||||
|
depend on them; then it would add more (test) files to the package and
|
||||||
|
type-check again. This two-phase approach had four major problems:
|
||||||
|
1) in processing the tests, the loader modified the library package,
|
||||||
|
leaving no way for a client application to see both the test
|
||||||
|
package and the library package; one would mutate into the other.
|
||||||
|
2) because test files can declare additional methods on types defined in
|
||||||
|
the library portion of the package, the dispatch of method calls in
|
||||||
|
the library portion was affected by the presence of the test files.
|
||||||
|
This should have been a clue that the packages were logically
|
||||||
|
different.
|
||||||
|
3) this model of "augmentation" assumed at most one in-package test
|
||||||
|
per library package, which is true of projects using 'go build',
|
||||||
|
but not other build systems.
|
||||||
|
4) because of the two-phase nature of test processing, all packages that
|
||||||
|
import the library package had to be processed before augmentation,
|
||||||
|
forcing a "one-shot" API and preventing the client from calling Load
|
||||||
|
in several times in sequence as is now possible in WholeProgram mode.
|
||||||
|
(TypeCheck mode has a similar one-shot restriction for a different reason.)
|
||||||
|
|
||||||
|
Early drafts of this package supported "multi-shot" operation.
|
||||||
|
Although it allowed clients to make a sequence of calls (or concurrent
|
||||||
|
calls) to Load, building up the graph of Packages incrementally,
|
||||||
|
it was of marginal value: it complicated the API
|
||||||
|
(since it allowed some options to vary across calls but not others),
|
||||||
|
it complicated the implementation,
|
||||||
|
it cannot be made to work in Types mode, as explained above,
|
||||||
|
and it was less efficient than making one combined call (when this is possible).
|
||||||
|
Among the clients we have inspected, none made multiple calls to load
|
||||||
|
but could not be easily and satisfactorily modified to make only a single call.
|
||||||
|
However, applications changes may be required.
|
||||||
|
For example, the ssadump command loads the user-specified packages
|
||||||
|
and in addition the runtime package. It is tempting to simply append
|
||||||
|
"runtime" to the user-provided list, but that does not work if the user
|
||||||
|
specified an ad-hoc package such as [a.go b.go].
|
||||||
|
Instead, ssadump no longer requests the runtime package,
|
||||||
|
but seeks it among the dependencies of the user-specified packages,
|
||||||
|
and emits an error if it is not found.
|
||||||
|
|
||||||
|
Overlays: the ParseFile hook in the API permits clients to vary the way
|
||||||
|
in which ASTs are obtained from filenames; the default implementation is
|
||||||
|
based on parser.ParseFile. This features enables editor-integrated tools
|
||||||
|
that analyze the contents of modified but unsaved buffers: rather than
|
||||||
|
read from the file system, a tool can read from an archive of modified
|
||||||
|
buffers provided by the editor.
|
||||||
|
This approach has its limits. Because package metadata is obtained by
|
||||||
|
fork/execing an external query command for each build system, we can
|
||||||
|
fake only the file contents seen by the parser, type-checker, and
|
||||||
|
application, but not by the metadata query, so, for example:
|
||||||
|
- additional imports in the fake file will not be described by the
|
||||||
|
metadata, so the type checker will fail to load imports that create
|
||||||
|
new dependencies.
|
||||||
|
- in TypeCheck mode, because export data is produced by the query
|
||||||
|
command, it will not reflect the fake file contents.
|
||||||
|
- this mechanism cannot add files to a package without first saving them.
|
||||||
|
|
||||||
|
Questions & Tasks
|
||||||
|
|
||||||
|
- Add GOARCH/GOOS?
|
||||||
|
They are not portable concepts, but could be made portable.
|
||||||
|
Our goal has been to allow users to express themselves using the conventions
|
||||||
|
of the underlying build system: if the build system honors GOARCH
|
||||||
|
during a build and during a metadata query, then so should
|
||||||
|
applications built atop that query mechanism.
|
||||||
|
Conversely, if the target architecture of the build is determined by
|
||||||
|
command-line flags, the application can pass the relevant
|
||||||
|
flags through to the build system using a command such as:
|
||||||
|
myapp -query_flag="--cpu=amd64" -query_flag="--os=darwin"
|
||||||
|
However, this approach is low-level, unwieldy, and non-portable.
|
||||||
|
GOOS and GOARCH seem important enough to warrant a dedicated option.
|
||||||
|
|
||||||
|
- How should we handle partial failures such as a mixture of good and
|
||||||
|
malformed patterns, existing and non-existent packages, successful and
|
||||||
|
failed builds, import failures, import cycles, and so on, in a call to
|
||||||
|
Load?
|
||||||
|
|
||||||
|
- Support bazel, blaze, and go1.10 list, not just go1.11 list.
|
||||||
|
|
||||||
|
- Handle (and test) various partial success cases, e.g.
|
||||||
|
a mixture of good packages and:
|
||||||
|
invalid patterns
|
||||||
|
nonexistent packages
|
||||||
|
empty packages
|
||||||
|
packages with malformed package or import declarations
|
||||||
|
unreadable files
|
||||||
|
import cycles
|
||||||
|
other parse errors
|
||||||
|
type errors
|
||||||
|
Make sure we record errors at the correct place in the graph.
|
||||||
|
|
||||||
|
- Missing packages among initial arguments are not reported.
|
||||||
|
Return bogus packages for them, like golist does.
|
||||||
|
|
||||||
|
- "undeclared name" errors (for example) are reported out of source file
|
||||||
|
order. I suspect this is due to the breadth-first resolution now used
|
||||||
|
by go/types. Is that a bug? Discuss with gri.
|
||||||
|
|
||||||
|
*/
|
68
vendor/golang.org/x/tools/go/packages/external.go
generated
vendored
Normal file
68
vendor/golang.org/x/tools/go/packages/external.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
// This file enables an external tool to intercept package requests.
|
||||||
|
// If the tool is present then its results are used in preference to
|
||||||
|
// the go list command.
|
||||||
|
|
||||||
|
package packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// findExternalTool returns the file path of a tool that supplies
|
||||||
|
// the build system package structure, or "" if not found."
|
||||||
|
// If GOPACKAGESDRIVER is set in the environment findExternalTool returns its
|
||||||
|
// value, otherwise it searches for a binary named gopackagesdriver on the PATH.
|
||||||
|
func findExternalDriver(cfg *Config) driver {
|
||||||
|
const toolPrefix = "GOPACKAGESDRIVER="
|
||||||
|
tool := ""
|
||||||
|
for _, env := range cfg.Env {
|
||||||
|
if val := strings.TrimPrefix(env, toolPrefix); val != env {
|
||||||
|
tool = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tool != "" && tool == "off" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if tool == "" {
|
||||||
|
var err error
|
||||||
|
tool, err = exec.LookPath("gopackagesdriver")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func(cfg *Config, words ...string) (*driverResponse, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
fullargs := []string{
|
||||||
|
"list",
|
||||||
|
fmt.Sprintf("-test=%t", cfg.Tests),
|
||||||
|
fmt.Sprintf("-export=%t", usesExportData(cfg)),
|
||||||
|
fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
|
||||||
|
}
|
||||||
|
for _, f := range cfg.BuildFlags {
|
||||||
|
fullargs = append(fullargs, fmt.Sprintf("-buildflag=%v", f))
|
||||||
|
}
|
||||||
|
fullargs = append(fullargs, "--")
|
||||||
|
fullargs = append(fullargs, words...)
|
||||||
|
cmd := exec.CommandContext(cfg.Context, tool, fullargs...)
|
||||||
|
cmd.Env = cfg.Env
|
||||||
|
cmd.Dir = cfg.Dir
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = new(bytes.Buffer)
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
|
||||||
|
}
|
||||||
|
var response driverResponse
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
}
|
625
vendor/golang.org/x/tools/go/packages/golist.go
generated
vendored
Normal file
625
vendor/golang.org/x/tools/go/packages/golist.go
generated
vendored
Normal file
|
@ -0,0 +1,625 @@
|
||||||
|
// Copyright 2018 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 packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/gopathwalk"
|
||||||
|
"golang.org/x/tools/internal/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A goTooOldError reports that the go command
|
||||||
|
// found by exec.LookPath is too old to use the new go list behavior.
|
||||||
|
type goTooOldError struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
// goListDriver uses the go list command to interpret the patterns and produce
|
||||||
|
// the build system package structure.
|
||||||
|
// See driver for more details.
|
||||||
|
func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
|
||||||
|
// Determine files requested in contains patterns
|
||||||
|
var containFiles []string
|
||||||
|
var packagesNamed []string
|
||||||
|
restPatterns := make([]string, 0, len(patterns))
|
||||||
|
// Extract file= and other [querytype]= patterns. Report an error if querytype
|
||||||
|
// doesn't exist.
|
||||||
|
extractQueries:
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
eqidx := strings.Index(pattern, "=")
|
||||||
|
if eqidx < 0 {
|
||||||
|
restPatterns = append(restPatterns, pattern)
|
||||||
|
} else {
|
||||||
|
query, value := pattern[:eqidx], pattern[eqidx+len("="):]
|
||||||
|
switch query {
|
||||||
|
case "file":
|
||||||
|
containFiles = append(containFiles, value)
|
||||||
|
case "pattern":
|
||||||
|
restPatterns = append(restPatterns, value)
|
||||||
|
case "name":
|
||||||
|
packagesNamed = append(packagesNamed, value)
|
||||||
|
case "": // not a reserved query
|
||||||
|
restPatterns = append(restPatterns, pattern)
|
||||||
|
default:
|
||||||
|
for _, rune := range query {
|
||||||
|
if rune < 'a' || rune > 'z' { // not a reserved query
|
||||||
|
restPatterns = append(restPatterns, pattern)
|
||||||
|
continue extractQueries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reject all other patterns containing "="
|
||||||
|
return nil, fmt.Errorf("invalid query type %q in query pattern %q", query, pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patterns = restPatterns
|
||||||
|
// Look for the deprecated contains: syntax.
|
||||||
|
// TODO(matloob): delete this around mid-October 2018.
|
||||||
|
restPatterns = restPatterns[:0]
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
if strings.HasPrefix(pattern, "contains:") {
|
||||||
|
containFile := strings.TrimPrefix(pattern, "contains:")
|
||||||
|
containFiles = append(containFiles, containFile)
|
||||||
|
} else {
|
||||||
|
restPatterns = append(restPatterns, pattern)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
containFiles = absJoin(cfg.Dir, containFiles)
|
||||||
|
|
||||||
|
// TODO(matloob): Remove the definition of listfunc and just use golistPackages once go1.12 is released.
|
||||||
|
var listfunc driver
|
||||||
|
listfunc = func(cfg *Config, words ...string) (*driverResponse, error) {
|
||||||
|
response, err := golistDriverCurrent(cfg, words...)
|
||||||
|
if _, ok := err.(goTooOldError); ok {
|
||||||
|
listfunc = golistDriverFallback
|
||||||
|
return listfunc(cfg, words...)
|
||||||
|
}
|
||||||
|
listfunc = golistDriverCurrent
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *driverResponse
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// see if we have any patterns to pass through to go list.
|
||||||
|
if len(restPatterns) > 0 {
|
||||||
|
response, err = listfunc(cfg, restPatterns...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response = &driverResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(containFiles) == 0 && len(packagesNamed) == 0 {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seenPkgs := make(map[string]*Package) // for deduplication. different containing queries could produce same packages
|
||||||
|
for _, pkg := range response.Packages {
|
||||||
|
seenPkgs[pkg.ID] = pkg
|
||||||
|
}
|
||||||
|
addPkg := func(p *Package) {
|
||||||
|
if _, ok := seenPkgs[p.ID]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seenPkgs[p.ID] = p
|
||||||
|
response.Packages = append(response.Packages, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
containsResults, err := runContainsQueries(cfg, listfunc, addPkg, containFiles)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.Roots = append(response.Roots, containsResults...)
|
||||||
|
|
||||||
|
namedResults, err := runNamedQueries(cfg, listfunc, addPkg, packagesNamed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.Roots = append(response.Roots, namedResults...)
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runContainsQueries(cfg *Config, driver driver, addPkg func(*Package), queries []string) ([]string, error) {
|
||||||
|
var results []string
|
||||||
|
for _, query := range queries {
|
||||||
|
// TODO(matloob): Do only one query per directory.
|
||||||
|
fdir := filepath.Dir(query)
|
||||||
|
cfg.Dir = fdir
|
||||||
|
dirResponse, err := driver(cfg, ".")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
isRoot := make(map[string]bool, len(dirResponse.Roots))
|
||||||
|
for _, root := range dirResponse.Roots {
|
||||||
|
isRoot[root] = true
|
||||||
|
}
|
||||||
|
for _, pkg := range dirResponse.Packages {
|
||||||
|
// Add any new packages to the main set
|
||||||
|
// We don't bother to filter packages that will be dropped by the changes of roots,
|
||||||
|
// that will happen anyway during graph construction outside this function.
|
||||||
|
// Over-reporting packages is not a problem.
|
||||||
|
addPkg(pkg)
|
||||||
|
// if the package was not a root one, it cannot have the file
|
||||||
|
if !isRoot[pkg.ID] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, pkgFile := range pkg.GoFiles {
|
||||||
|
if filepath.Base(query) == filepath.Base(pkgFile) {
|
||||||
|
results = append(results, pkg.ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// modCacheRegexp splits a path in a module cache into module, module version, and package.
|
||||||
|
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
|
||||||
|
|
||||||
|
func runNamedQueries(cfg *Config, driver driver, addPkg func(*Package), queries []string) ([]string, error) {
|
||||||
|
// Determine which directories are relevant to scan.
|
||||||
|
roots, modulesEnabled, err := roots(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the selected directories. Simple matches, from GOPATH/GOROOT
|
||||||
|
// or the local module, can simply be "go list"ed. Matches from the
|
||||||
|
// module cache need special treatment.
|
||||||
|
var matchesMu sync.Mutex
|
||||||
|
var simpleMatches, modCacheMatches []string
|
||||||
|
add := func(root gopathwalk.Root, dir string) {
|
||||||
|
// Walk calls this concurrently; protect the result slices.
|
||||||
|
matchesMu.Lock()
|
||||||
|
defer matchesMu.Unlock()
|
||||||
|
|
||||||
|
path := dir[len(root.Path)+1:]
|
||||||
|
if pathMatchesQueries(path, queries) {
|
||||||
|
switch root.Type {
|
||||||
|
case gopathwalk.RootModuleCache:
|
||||||
|
modCacheMatches = append(modCacheMatches, path)
|
||||||
|
case gopathwalk.RootCurrentModule:
|
||||||
|
// We'd need to read go.mod to find the full
|
||||||
|
// import path. Relative's easier.
|
||||||
|
rel, err := filepath.Rel(cfg.Dir, dir)
|
||||||
|
if err != nil {
|
||||||
|
// This ought to be impossible, since
|
||||||
|
// we found dir in the current module.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
simpleMatches = append(simpleMatches, "./"+rel)
|
||||||
|
case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT:
|
||||||
|
simpleMatches = append(simpleMatches, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modulesEnabled})
|
||||||
|
|
||||||
|
var results []string
|
||||||
|
addResponse := func(r *driverResponse) {
|
||||||
|
for _, pkg := range r.Packages {
|
||||||
|
addPkg(pkg)
|
||||||
|
for _, name := range queries {
|
||||||
|
if pkg.Name == name {
|
||||||
|
results = append(results, pkg.ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(simpleMatches) != 0 {
|
||||||
|
resp, err := driver(cfg, simpleMatches...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module cache matches are tricky. We want to avoid downloading new
|
||||||
|
// versions of things, so we need to use the ones present in the cache.
|
||||||
|
// go list doesn't accept version specifiers, so we have to write out a
|
||||||
|
// temporary module, and do the list in that module.
|
||||||
|
if len(modCacheMatches) != 0 {
|
||||||
|
// Collect all the matches, deduplicating by major version
|
||||||
|
// and preferring the newest.
|
||||||
|
type modInfo struct {
|
||||||
|
mod string
|
||||||
|
major string
|
||||||
|
}
|
||||||
|
mods := make(map[modInfo]string)
|
||||||
|
var imports []string
|
||||||
|
for _, modPath := range modCacheMatches {
|
||||||
|
matches := modCacheRegexp.FindStringSubmatch(modPath)
|
||||||
|
mod, ver := filepath.ToSlash(matches[1]), matches[2]
|
||||||
|
importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3]))
|
||||||
|
|
||||||
|
major := semver.Major(ver)
|
||||||
|
if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 {
|
||||||
|
mods[modInfo{mod, major}] = ver
|
||||||
|
}
|
||||||
|
|
||||||
|
imports = append(imports, importPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the temporary module.
|
||||||
|
var gomod bytes.Buffer
|
||||||
|
gomod.WriteString("module modquery\nrequire (\n")
|
||||||
|
for mod, version := range mods {
|
||||||
|
gomod.WriteString("\t" + mod.mod + " " + version + "\n")
|
||||||
|
}
|
||||||
|
gomod.WriteString(")\n")
|
||||||
|
|
||||||
|
tmpCfg := *cfg
|
||||||
|
var err error
|
||||||
|
tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpCfg.Dir)
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil {
|
||||||
|
return nil, fmt.Errorf("writing go.mod for module cache query: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the query, using the import paths calculated from the matches above.
|
||||||
|
resp, err := driver(&tmpCfg, imports...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("querying module cache matches: %v", err)
|
||||||
|
}
|
||||||
|
addResponse(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// roots selects the appropriate paths to walk based on the passed-in configuration,
|
||||||
|
// particularly the environment and the presence of a go.mod in cfg.Dir's parents.
|
||||||
|
func roots(cfg *Config) ([]gopathwalk.Root, bool, error) {
|
||||||
|
stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD")
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Split(stdout.String(), "\n")
|
||||||
|
if len(fields) != 4 || len(fields[3]) != 0 {
|
||||||
|
return nil, false, fmt.Errorf("go env returned unexpected output: %q", stdout.String())
|
||||||
|
}
|
||||||
|
goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2]
|
||||||
|
modsEnabled := gomod != ""
|
||||||
|
|
||||||
|
var roots []gopathwalk.Root
|
||||||
|
// Always add GOROOT.
|
||||||
|
roots = append(roots, gopathwalk.Root{filepath.Join(goroot, "/src"), gopathwalk.RootGOROOT})
|
||||||
|
// If modules are enabled, scan the module dir.
|
||||||
|
if modsEnabled {
|
||||||
|
roots = append(roots, gopathwalk.Root{filepath.Dir(gomod), gopathwalk.RootCurrentModule})
|
||||||
|
}
|
||||||
|
// Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode.
|
||||||
|
for _, p := range gopath {
|
||||||
|
if modsEnabled {
|
||||||
|
roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache})
|
||||||
|
} else {
|
||||||
|
roots = append(roots, gopathwalk.Root{filepath.Join(p, "/src"), gopathwalk.RootGOPATH})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return roots, modsEnabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// These functions were copied from goimports. See further documentation there.
|
||||||
|
|
||||||
|
// pathMatchesQueries is adapted from pkgIsCandidate.
|
||||||
|
// TODO: is it reasonable to do Contains here, rather than an exact match on a path component?
|
||||||
|
func pathMatchesQueries(path string, queries []string) bool {
|
||||||
|
lastTwo := lastTwoComponents(path)
|
||||||
|
for _, query := range queries {
|
||||||
|
if strings.Contains(lastTwo, query) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) {
|
||||||
|
lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
|
||||||
|
if strings.Contains(lastTwo, query) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// lastTwoComponents returns at most the last two path components
|
||||||
|
// of v, using either / or \ as the path separator.
|
||||||
|
func lastTwoComponents(v string) string {
|
||||||
|
nslash := 0
|
||||||
|
for i := len(v) - 1; i >= 0; i-- {
|
||||||
|
if v[i] == '/' || v[i] == '\\' {
|
||||||
|
nslash++
|
||||||
|
if nslash == 2 {
|
||||||
|
return v[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasHyphenOrUpperASCII(s string) bool {
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
if b == '-' || ('A' <= b && b <= 'Z') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowerASCIIAndRemoveHyphen(s string) (ret string) {
|
||||||
|
buf := make([]byte, 0, len(s))
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
b := s[i]
|
||||||
|
switch {
|
||||||
|
case b == '-':
|
||||||
|
continue
|
||||||
|
case 'A' <= b && b <= 'Z':
|
||||||
|
buf = append(buf, b+('a'-'A'))
|
||||||
|
default:
|
||||||
|
buf = append(buf, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields must match go list;
|
||||||
|
// see $GOROOT/src/cmd/go/internal/load/pkg.go.
|
||||||
|
type jsonPackage struct {
|
||||||
|
ImportPath string
|
||||||
|
Dir string
|
||||||
|
Name string
|
||||||
|
Export string
|
||||||
|
GoFiles []string
|
||||||
|
CompiledGoFiles []string
|
||||||
|
CFiles []string
|
||||||
|
CgoFiles []string
|
||||||
|
CXXFiles []string
|
||||||
|
MFiles []string
|
||||||
|
HFiles []string
|
||||||
|
FFiles []string
|
||||||
|
SFiles []string
|
||||||
|
SwigFiles []string
|
||||||
|
SwigCXXFiles []string
|
||||||
|
SysoFiles []string
|
||||||
|
Imports []string
|
||||||
|
ImportMap map[string]string
|
||||||
|
Deps []string
|
||||||
|
TestGoFiles []string
|
||||||
|
TestImports []string
|
||||||
|
XTestGoFiles []string
|
||||||
|
XTestImports []string
|
||||||
|
ForTest string // q in a "p [q.test]" package, else ""
|
||||||
|
DepOnly bool
|
||||||
|
|
||||||
|
Error *jsonPackageError
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonPackageError struct {
|
||||||
|
ImportStack []string
|
||||||
|
Pos string
|
||||||
|
Err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func otherFiles(p *jsonPackage) [][]string {
|
||||||
|
return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
|
||||||
|
}
|
||||||
|
|
||||||
|
// golistDriverCurrent uses the "go list" command to expand the
|
||||||
|
// pattern words and return metadata for the specified packages.
|
||||||
|
// dir may be "" and env may be nil, as per os/exec.Command.
|
||||||
|
func golistDriverCurrent(cfg *Config, words ...string) (*driverResponse, error) {
|
||||||
|
// go list uses the following identifiers in ImportPath and Imports:
|
||||||
|
//
|
||||||
|
// "p" -- importable package or main (command)
|
||||||
|
// "q.test" -- q's test executable
|
||||||
|
// "p [q.test]" -- variant of p as built for q's test executable
|
||||||
|
// "q_test [q.test]" -- q's external test package
|
||||||
|
//
|
||||||
|
// The packages p that are built differently for a test q.test
|
||||||
|
// are q itself, plus any helpers used by the external test q_test,
|
||||||
|
// typically including "testing" and all its dependencies.
|
||||||
|
|
||||||
|
// Run "go list" for complete
|
||||||
|
// information on the specified packages.
|
||||||
|
buf, err := invokeGo(cfg, golistargs(cfg, words)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Decode the JSON and convert it to Package form.
|
||||||
|
var response driverResponse
|
||||||
|
for dec := json.NewDecoder(buf); dec.More(); {
|
||||||
|
p := new(jsonPackage)
|
||||||
|
if err := dec.Decode(p); err != nil {
|
||||||
|
return nil, fmt.Errorf("JSON decoding failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ImportPath == "" {
|
||||||
|
// The documentation for go list says that “[e]rroneous packages will have
|
||||||
|
// a non-empty ImportPath”. If for some reason it comes back empty, we
|
||||||
|
// prefer to error out rather than silently discarding data or handing
|
||||||
|
// back a package without any way to refer to it.
|
||||||
|
if p.Error != nil {
|
||||||
|
return nil, Error{
|
||||||
|
Pos: p.Error.Pos,
|
||||||
|
Msg: p.Error.Err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("package missing import path: %+v", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg := &Package{
|
||||||
|
Name: p.Name,
|
||||||
|
ID: p.ImportPath,
|
||||||
|
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
|
||||||
|
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
|
||||||
|
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the PkgPath from the package's ID.
|
||||||
|
if i := strings.IndexByte(pkg.ID, ' '); i >= 0 {
|
||||||
|
pkg.PkgPath = pkg.ID[:i]
|
||||||
|
} else {
|
||||||
|
pkg.PkgPath = pkg.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkg.PkgPath == "unsafe" {
|
||||||
|
pkg.GoFiles = nil // ignore fake unsafe.go file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume go list emits only absolute paths for Dir.
|
||||||
|
if p.Dir != "" && !filepath.IsAbs(p.Dir) {
|
||||||
|
log.Fatalf("internal error: go list returned non-absolute Package.Dir: %s", p.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Export != "" && !filepath.IsAbs(p.Export) {
|
||||||
|
pkg.ExportFile = filepath.Join(p.Dir, p.Export)
|
||||||
|
} else {
|
||||||
|
pkg.ExportFile = p.Export
|
||||||
|
}
|
||||||
|
|
||||||
|
// imports
|
||||||
|
//
|
||||||
|
// Imports contains the IDs of all imported packages.
|
||||||
|
// ImportsMap records (path, ID) only where they differ.
|
||||||
|
ids := make(map[string]bool)
|
||||||
|
for _, id := range p.Imports {
|
||||||
|
ids[id] = true
|
||||||
|
}
|
||||||
|
pkg.Imports = make(map[string]*Package)
|
||||||
|
for path, id := range p.ImportMap {
|
||||||
|
pkg.Imports[path] = &Package{ID: id} // non-identity import
|
||||||
|
delete(ids, id)
|
||||||
|
}
|
||||||
|
for id := range ids {
|
||||||
|
if id == "C" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.Imports[id] = &Package{ID: id} // identity import
|
||||||
|
}
|
||||||
|
if !p.DepOnly {
|
||||||
|
response.Roots = append(response.Roots, pkg.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(matloob): Temporary hack since CompiledGoFiles isn't always set.
|
||||||
|
if len(pkg.CompiledGoFiles) == 0 {
|
||||||
|
pkg.CompiledGoFiles = pkg.GoFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Error != nil {
|
||||||
|
pkg.Errors = append(pkg.Errors, Error{
|
||||||
|
Pos: p.Error.Pos,
|
||||||
|
Msg: p.Error.Err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Packages = append(response.Packages, pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// absJoin absolutizes and flattens the lists of files.
|
||||||
|
func absJoin(dir string, fileses ...[]string) (res []string) {
|
||||||
|
for _, files := range fileses {
|
||||||
|
for _, file := range files {
|
||||||
|
if !filepath.IsAbs(file) {
|
||||||
|
file = filepath.Join(dir, file)
|
||||||
|
}
|
||||||
|
res = append(res, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func golistargs(cfg *Config, words []string) []string {
|
||||||
|
fullargs := []string{
|
||||||
|
"list", "-e", "-json", "-compiled",
|
||||||
|
fmt.Sprintf("-test=%t", cfg.Tests),
|
||||||
|
fmt.Sprintf("-export=%t", usesExportData(cfg)),
|
||||||
|
fmt.Sprintf("-deps=%t", cfg.Mode >= LoadImports),
|
||||||
|
}
|
||||||
|
fullargs = append(fullargs, cfg.BuildFlags...)
|
||||||
|
fullargs = append(fullargs, "--")
|
||||||
|
fullargs = append(fullargs, words...)
|
||||||
|
return fullargs
|
||||||
|
}
|
||||||
|
|
||||||
|
// invokeGo returns the stdout of a go command invocation.
|
||||||
|
func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
cmd := exec.CommandContext(cfg.Context, "go", args...)
|
||||||
|
// On darwin the cwd gets resolved to the real path, which breaks anything that
|
||||||
|
// expects the working directory to keep the original path, including the
|
||||||
|
// go command when dealing with modules.
|
||||||
|
// 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{}, cfg.Env...), "PWD="+cfg.Dir)
|
||||||
|
cmd.Dir = cfg.Dir
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
exitErr, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
|
// Catastrophic error:
|
||||||
|
// - executable not found
|
||||||
|
// - context cancellation
|
||||||
|
return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old go version?
|
||||||
|
if strings.Contains(stderr.String(), "flag provided but not defined") {
|
||||||
|
return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export mode entails a build.
|
||||||
|
// If that build fails, errors appear on stderr
|
||||||
|
// (despite the -e flag) and the Export field is blank.
|
||||||
|
// Do not fail in that case.
|
||||||
|
if !usesExportData(cfg) {
|
||||||
|
return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As of writing, go list -export prints some non-fatal compilation
|
||||||
|
// errors to stderr, even with -e set. We would prefer that it put
|
||||||
|
// them in the Package.Error JSON (see http://golang.org/issue/26319).
|
||||||
|
// In the meantime, there's nowhere good to put them, but they can
|
||||||
|
// be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS
|
||||||
|
// is set.
|
||||||
|
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "go %v stderr: <<%s>>\n", args, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// debugging
|
||||||
|
if false {
|
||||||
|
fmt.Fprintf(os.Stderr, "go %v stdout: <<%s>>\n", args, stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout, nil
|
||||||
|
}
|
457
vendor/golang.org/x/tools/go/packages/golist_fallback.go
generated
vendored
Normal file
457
vendor/golang.org/x/tools/go/packages/golist_fallback.go
generated
vendored
Normal file
|
@ -0,0 +1,457 @@
|
||||||
|
// Copyright 2018 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 packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/internal/cgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(matloob): Delete this file once Go 1.12 is released.
|
||||||
|
|
||||||
|
// This file provides backwards compatibility support for
|
||||||
|
// loading for versions of Go earlier than 1.10.4. This support is meant to
|
||||||
|
// assist with migration to the Package API until there's
|
||||||
|
// widespread adoption of these newer Go versions.
|
||||||
|
// This support will be removed once Go 1.12 is released
|
||||||
|
// in Q1 2019.
|
||||||
|
|
||||||
|
func golistDriverFallback(cfg *Config, words ...string) (*driverResponse, error) {
|
||||||
|
// Turn absolute paths into GOROOT and GOPATH-relative paths to provide to go list.
|
||||||
|
// This will have surprising behavior if GOROOT or GOPATH contain multiple packages with the same
|
||||||
|
// path and a user provides an absolute path to a directory that's shadowed by an earlier
|
||||||
|
// directory in GOROOT or GOPATH with the same package path.
|
||||||
|
words = cleanAbsPaths(cfg, words)
|
||||||
|
|
||||||
|
original, deps, err := getDeps(cfg, words...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmpdir string // used for generated cgo files
|
||||||
|
var needsTestVariant []struct {
|
||||||
|
pkg, xtestPkg *Package
|
||||||
|
}
|
||||||
|
|
||||||
|
var response driverResponse
|
||||||
|
allPkgs := make(map[string]bool)
|
||||||
|
addPackage := func(p *jsonPackage) {
|
||||||
|
id := p.ImportPath
|
||||||
|
|
||||||
|
if allPkgs[id] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
allPkgs[id] = true
|
||||||
|
|
||||||
|
isRoot := original[id] != nil
|
||||||
|
pkgpath := id
|
||||||
|
|
||||||
|
if pkgpath == "unsafe" {
|
||||||
|
p.GoFiles = nil // ignore fake unsafe.go file
|
||||||
|
}
|
||||||
|
|
||||||
|
importMap := func(importlist []string) map[string]*Package {
|
||||||
|
importMap := make(map[string]*Package)
|
||||||
|
for _, id := range importlist {
|
||||||
|
|
||||||
|
if id == "C" {
|
||||||
|
for _, path := range []string{"unsafe", "syscall", "runtime/cgo"} {
|
||||||
|
if pkgpath != path && importMap[path] == nil {
|
||||||
|
importMap[path] = &Package{ID: path}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
importMap[vendorlessPath(id)] = &Package{ID: id}
|
||||||
|
}
|
||||||
|
return importMap
|
||||||
|
}
|
||||||
|
compiledGoFiles := absJoin(p.Dir, p.GoFiles)
|
||||||
|
// Use a function to simplify control flow. It's just a bunch of gotos.
|
||||||
|
var cgoErrors []error
|
||||||
|
var outdir string
|
||||||
|
getOutdir := func() (string, error) {
|
||||||
|
if outdir != "" {
|
||||||
|
return outdir, nil
|
||||||
|
}
|
||||||
|
if tmpdir == "" {
|
||||||
|
if tmpdir, err = ioutil.TempDir("", "gopackages"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add a "go-build" component to the path to make the tests think the files are in the cache.
|
||||||
|
// This allows the same test to test the pre- and post-Go 1.11 go list logic because the Go 1.11
|
||||||
|
// go list generates test mains in the cache, and the test code knows not to rely on paths in the
|
||||||
|
// cache to stay stable.
|
||||||
|
outdir = filepath.Join(tmpdir, "go-build", strings.Replace(p.ImportPath, "/", "_", -1))
|
||||||
|
if err := os.MkdirAll(outdir, 0755); err != nil {
|
||||||
|
outdir = ""
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return outdir, nil
|
||||||
|
}
|
||||||
|
processCgo := func() bool {
|
||||||
|
// Suppress any cgo errors. Any relevant errors will show up in typechecking.
|
||||||
|
// TODO(matloob): Skip running cgo if Mode < LoadTypes.
|
||||||
|
outdir, err := getOutdir()
|
||||||
|
if err != nil {
|
||||||
|
cgoErrors = append(cgoErrors, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
files, _, err := runCgo(p.Dir, outdir, cfg.Env)
|
||||||
|
if err != nil {
|
||||||
|
cgoErrors = append(cgoErrors, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
compiledGoFiles = append(compiledGoFiles, files...)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(p.CgoFiles) == 0 || !processCgo() {
|
||||||
|
compiledGoFiles = append(compiledGoFiles, absJoin(p.Dir, p.CgoFiles)...) // Punt to typechecker.
|
||||||
|
}
|
||||||
|
if isRoot {
|
||||||
|
response.Roots = append(response.Roots, id)
|
||||||
|
}
|
||||||
|
pkg := &Package{
|
||||||
|
ID: id,
|
||||||
|
Name: p.Name,
|
||||||
|
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
|
||||||
|
CompiledGoFiles: compiledGoFiles,
|
||||||
|
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
|
||||||
|
PkgPath: pkgpath,
|
||||||
|
Imports: importMap(p.Imports),
|
||||||
|
// TODO(matloob): set errors on the Package to cgoErrors
|
||||||
|
}
|
||||||
|
if p.Error != nil {
|
||||||
|
pkg.Errors = append(pkg.Errors, Error{
|
||||||
|
Pos: p.Error.Pos,
|
||||||
|
Msg: p.Error.Err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
response.Packages = append(response.Packages, pkg)
|
||||||
|
if cfg.Tests && isRoot {
|
||||||
|
testID := fmt.Sprintf("%s [%s.test]", id, id)
|
||||||
|
if len(p.TestGoFiles) > 0 || len(p.XTestGoFiles) > 0 {
|
||||||
|
response.Roots = append(response.Roots, testID)
|
||||||
|
testPkg := &Package{
|
||||||
|
ID: testID,
|
||||||
|
Name: p.Name,
|
||||||
|
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles, p.TestGoFiles),
|
||||||
|
CompiledGoFiles: append(compiledGoFiles, absJoin(p.Dir, p.TestGoFiles)...),
|
||||||
|
OtherFiles: absJoin(p.Dir, otherFiles(p)...),
|
||||||
|
PkgPath: pkgpath,
|
||||||
|
Imports: importMap(append(p.Imports, p.TestImports...)),
|
||||||
|
// TODO(matloob): set errors on the Package to cgoErrors
|
||||||
|
}
|
||||||
|
response.Packages = append(response.Packages, testPkg)
|
||||||
|
var xtestPkg *Package
|
||||||
|
if len(p.XTestGoFiles) > 0 {
|
||||||
|
xtestID := fmt.Sprintf("%s_test [%s.test]", id, id)
|
||||||
|
response.Roots = append(response.Roots, xtestID)
|
||||||
|
// Generate test variants for all packages q where a path exists
|
||||||
|
// such that xtestPkg -> ... -> q -> ... -> p (where p is the package under test)
|
||||||
|
// and rewrite all import map entries of p to point to testPkg (the test variant of
|
||||||
|
// p), and of each q to point to the test variant of that q.
|
||||||
|
xtestPkg = &Package{
|
||||||
|
ID: xtestID,
|
||||||
|
Name: p.Name + "_test",
|
||||||
|
GoFiles: absJoin(p.Dir, p.XTestGoFiles),
|
||||||
|
CompiledGoFiles: absJoin(p.Dir, p.XTestGoFiles),
|
||||||
|
PkgPath: pkgpath + "_test",
|
||||||
|
Imports: importMap(p.XTestImports),
|
||||||
|
}
|
||||||
|
// Add to list of packages we need to rewrite imports for to refer to test variants.
|
||||||
|
// We may need to create a test variant of a package that hasn't been loaded yet, so
|
||||||
|
// the test variants need to be created later.
|
||||||
|
needsTestVariant = append(needsTestVariant, struct{ pkg, xtestPkg *Package }{pkg, xtestPkg})
|
||||||
|
response.Packages = append(response.Packages, xtestPkg)
|
||||||
|
}
|
||||||
|
// testmain package
|
||||||
|
testmainID := id + ".test"
|
||||||
|
response.Roots = append(response.Roots, testmainID)
|
||||||
|
imports := map[string]*Package{}
|
||||||
|
imports[testPkg.PkgPath] = &Package{ID: testPkg.ID}
|
||||||
|
if xtestPkg != nil {
|
||||||
|
imports[xtestPkg.PkgPath] = &Package{ID: xtestPkg.ID}
|
||||||
|
}
|
||||||
|
testmainPkg := &Package{
|
||||||
|
ID: testmainID,
|
||||||
|
Name: "main",
|
||||||
|
PkgPath: testmainID,
|
||||||
|
Imports: imports,
|
||||||
|
}
|
||||||
|
response.Packages = append(response.Packages, testmainPkg)
|
||||||
|
outdir, err := getOutdir()
|
||||||
|
if err != nil {
|
||||||
|
testmainPkg.Errors = append(testmainPkg.Errors, Error{
|
||||||
|
Pos: "-",
|
||||||
|
Msg: fmt.Sprintf("failed to generate testmain: %v", err),
|
||||||
|
Kind: ListError,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
testmain := filepath.Join(outdir, "testmain.go")
|
||||||
|
extraimports, extradeps, err := generateTestmain(testmain, testPkg, xtestPkg)
|
||||||
|
if err != nil {
|
||||||
|
testmainPkg.Errors = append(testmainPkg.Errors, Error{
|
||||||
|
Pos: "-",
|
||||||
|
Msg: fmt.Sprintf("failed to generate testmain: %v", err),
|
||||||
|
Kind: ListError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
deps = append(deps, extradeps...)
|
||||||
|
for _, imp := range extraimports { // testing, testing/internal/testdeps, and maybe os
|
||||||
|
imports[imp] = &Package{ID: imp}
|
||||||
|
}
|
||||||
|
testmainPkg.GoFiles = []string{testmain}
|
||||||
|
testmainPkg.CompiledGoFiles = []string{testmain}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range original {
|
||||||
|
addPackage(pkg)
|
||||||
|
}
|
||||||
|
if cfg.Mode < LoadImports || len(deps) == 0 {
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := invokeGo(cfg, golistArgsFallback(cfg, deps)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the JSON and convert it to Package form.
|
||||||
|
for dec := json.NewDecoder(buf); dec.More(); {
|
||||||
|
p := new(jsonPackage)
|
||||||
|
if err := dec.Decode(p); err != nil {
|
||||||
|
return nil, fmt.Errorf("JSON decoding failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addPackage(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range needsTestVariant {
|
||||||
|
createTestVariants(&response, v.pkg, v.xtestPkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(matloob): Is this the right ordering?
|
||||||
|
sort.SliceStable(response.Packages, func(i, j int) bool {
|
||||||
|
return response.Packages[i].PkgPath < response.Packages[j].PkgPath
|
||||||
|
})
|
||||||
|
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestVariants(response *driverResponse, pkgUnderTest, xtestPkg *Package) {
|
||||||
|
allPkgs := make(map[string]*Package)
|
||||||
|
for _, pkg := range response.Packages {
|
||||||
|
allPkgs[pkg.ID] = pkg
|
||||||
|
}
|
||||||
|
needsTestVariant := make(map[string]bool)
|
||||||
|
needsTestVariant[pkgUnderTest.ID] = true
|
||||||
|
var needsVariantRec func(p *Package) bool
|
||||||
|
needsVariantRec = func(p *Package) bool {
|
||||||
|
if needsTestVariant[p.ID] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, imp := range p.Imports {
|
||||||
|
if needsVariantRec(allPkgs[imp.ID]) {
|
||||||
|
// Don't break because we want to make sure all dependencies
|
||||||
|
// have been processed, and all required test variants of our dependencies
|
||||||
|
// exist.
|
||||||
|
needsTestVariant[p.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !needsTestVariant[p.ID] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Create a clone of the package. It will share the same strings and lists of source files,
|
||||||
|
// but that's okay. It's only necessary for the Imports map to have a separate identity.
|
||||||
|
testVariant := *p
|
||||||
|
testVariant.ID = fmt.Sprintf("%s [%s.test]", p.ID, pkgUnderTest.ID)
|
||||||
|
testVariant.Imports = make(map[string]*Package)
|
||||||
|
for imp, pkg := range p.Imports {
|
||||||
|
testVariant.Imports[imp] = pkg
|
||||||
|
if needsTestVariant[pkg.ID] {
|
||||||
|
testVariant.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.Packages = append(response.Packages, &testVariant)
|
||||||
|
return needsTestVariant[p.ID]
|
||||||
|
}
|
||||||
|
// finally, update the xtest package's imports
|
||||||
|
for imp, pkg := range xtestPkg.Imports {
|
||||||
|
if allPkgs[pkg.ID] == nil {
|
||||||
|
fmt.Printf("for %s: package %s doesn't exist\n", xtestPkg.ID, pkg.ID)
|
||||||
|
}
|
||||||
|
if needsVariantRec(allPkgs[pkg.ID]) {
|
||||||
|
xtestPkg.Imports[imp] = &Package{ID: fmt.Sprintf("%s [%s.test]", pkg.ID, pkgUnderTest.ID)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanAbsPaths replaces all absolute paths with GOPATH- and GOROOT-relative
|
||||||
|
// paths. If an absolute path is not GOPATH- or GOROOT- relative, it is left as an
|
||||||
|
// absolute path so an error can be returned later.
|
||||||
|
func cleanAbsPaths(cfg *Config, words []string) []string {
|
||||||
|
var searchpaths []string
|
||||||
|
var cleaned = make([]string, len(words))
|
||||||
|
for i := range cleaned {
|
||||||
|
cleaned[i] = words[i]
|
||||||
|
// Ignore relative directory paths (they must already be goroot-relative) and Go source files
|
||||||
|
// (absolute source files are already allowed for ad-hoc packages).
|
||||||
|
// TODO(matloob): Can there be non-.go files in ad-hoc packages.
|
||||||
|
if !filepath.IsAbs(cleaned[i]) || strings.HasSuffix(cleaned[i], ".go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// otherwise, it's an absolute path. Search GOPATH and GOROOT to find it.
|
||||||
|
if searchpaths == nil {
|
||||||
|
cmd := exec.Command("go", "env", "GOPATH", "GOROOT")
|
||||||
|
cmd.Env = cfg.Env
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
searchpaths = []string{}
|
||||||
|
continue // suppress the error, it will show up again when running go list
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(out), "\n")
|
||||||
|
if len(lines) != 3 || lines[0] == "" || lines[1] == "" || lines[2] != "" {
|
||||||
|
continue // suppress error
|
||||||
|
}
|
||||||
|
// first line is GOPATH
|
||||||
|
for _, path := range filepath.SplitList(lines[0]) {
|
||||||
|
searchpaths = append(searchpaths, filepath.Join(path, "src"))
|
||||||
|
}
|
||||||
|
// second line is GOROOT
|
||||||
|
searchpaths = append(searchpaths, filepath.Join(lines[1], "src"))
|
||||||
|
}
|
||||||
|
for _, sp := range searchpaths {
|
||||||
|
if strings.HasPrefix(cleaned[i], sp) {
|
||||||
|
cleaned[i] = strings.TrimPrefix(cleaned[i], sp)
|
||||||
|
cleaned[i] = strings.TrimLeft(cleaned[i], string(filepath.Separator))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cleaned
|
||||||
|
}
|
||||||
|
|
||||||
|
// vendorlessPath returns the devendorized version of the import path ipath.
|
||||||
|
// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b".
|
||||||
|
// Copied from golang.org/x/tools/imports/fix.go.
|
||||||
|
func vendorlessPath(ipath string) string {
|
||||||
|
// Devendorize for use in import statement.
|
||||||
|
if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
|
||||||
|
return ipath[i+len("/vendor/"):]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(ipath, "vendor/") {
|
||||||
|
return ipath[len("vendor/"):]
|
||||||
|
}
|
||||||
|
return ipath
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDeps runs an initial go list to determine all the dependency packages.
|
||||||
|
func getDeps(cfg *Config, words ...string) (originalSet map[string]*jsonPackage, deps []string, err error) {
|
||||||
|
buf, err := invokeGo(cfg, golistArgsFallback(cfg, words)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
depsSet := make(map[string]bool)
|
||||||
|
originalSet = make(map[string]*jsonPackage)
|
||||||
|
var testImports []string
|
||||||
|
|
||||||
|
// Extract deps from the JSON.
|
||||||
|
for dec := json.NewDecoder(buf); dec.More(); {
|
||||||
|
p := new(jsonPackage)
|
||||||
|
if err := dec.Decode(p); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("JSON decoding failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
originalSet[p.ImportPath] = p
|
||||||
|
for _, dep := range p.Deps {
|
||||||
|
depsSet[dep] = true
|
||||||
|
}
|
||||||
|
if cfg.Tests {
|
||||||
|
// collect the additional imports of the test packages.
|
||||||
|
pkgTestImports := append(p.TestImports, p.XTestImports...)
|
||||||
|
for _, imp := range pkgTestImports {
|
||||||
|
if depsSet[imp] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
depsSet[imp] = true
|
||||||
|
testImports = append(testImports, imp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get the deps of the packages imported by tests.
|
||||||
|
if len(testImports) > 0 {
|
||||||
|
buf, err = invokeGo(cfg, golistArgsFallback(cfg, testImports)...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// Extract deps from the JSON.
|
||||||
|
for dec := json.NewDecoder(buf); dec.More(); {
|
||||||
|
p := new(jsonPackage)
|
||||||
|
if err := dec.Decode(p); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("JSON decoding failed: %v", err)
|
||||||
|
}
|
||||||
|
for _, dep := range p.Deps {
|
||||||
|
depsSet[dep] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for orig := range originalSet {
|
||||||
|
delete(depsSet, orig)
|
||||||
|
}
|
||||||
|
|
||||||
|
deps = make([]string, 0, len(depsSet))
|
||||||
|
for dep := range depsSet {
|
||||||
|
deps = append(deps, dep)
|
||||||
|
}
|
||||||
|
sort.Strings(deps) // ensure output is deterministic
|
||||||
|
return originalSet, deps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func golistArgsFallback(cfg *Config, words []string) []string {
|
||||||
|
fullargs := []string{"list", "-e", "-json"}
|
||||||
|
fullargs = append(fullargs, cfg.BuildFlags...)
|
||||||
|
fullargs = append(fullargs, "--")
|
||||||
|
fullargs = append(fullargs, words...)
|
||||||
|
return fullargs
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCgo(pkgdir, tmpdir string, env []string) (files, displayfiles []string, err error) {
|
||||||
|
// Use go/build to open cgo files and determine the cgo flags, etc, from them.
|
||||||
|
// This is tricky so it's best to avoid reimplementing as much as we can, and
|
||||||
|
// we plan to delete this support once Go 1.12 is released anyways.
|
||||||
|
// TODO(matloob): This isn't completely correct because we're using the Default
|
||||||
|
// context. Perhaps we should more accurately fill in the context.
|
||||||
|
bp, err := build.ImportDir(pkgdir, build.ImportMode(0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for _, ev := range env {
|
||||||
|
if v := strings.TrimPrefix(ev, "CGO_CPPFLAGS"); v != ev {
|
||||||
|
bp.CgoCPPFLAGS = append(bp.CgoCPPFLAGS, strings.Fields(v)...)
|
||||||
|
} else if v := strings.TrimPrefix(ev, "CGO_CFLAGS"); v != ev {
|
||||||
|
bp.CgoCFLAGS = append(bp.CgoCFLAGS, strings.Fields(v)...)
|
||||||
|
} else if v := strings.TrimPrefix(ev, "CGO_CXXFLAGS"); v != ev {
|
||||||
|
bp.CgoCXXFLAGS = append(bp.CgoCXXFLAGS, strings.Fields(v)...)
|
||||||
|
} else if v := strings.TrimPrefix(ev, "CGO_LDFLAGS"); v != ev {
|
||||||
|
bp.CgoLDFLAGS = append(bp.CgoLDFLAGS, strings.Fields(v)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cgo.Run(bp, pkgdir, tmpdir, true)
|
||||||
|
}
|
318
vendor/golang.org/x/tools/go/packages/golist_fallback_testmain.go
generated
vendored
Normal file
318
vendor/golang.org/x/tools/go/packages/golist_fallback_testmain.go
generated
vendored
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
// This file is largely based on the Go 1.10-era cmd/go/internal/test/test.go
|
||||||
|
// testmain generation code.
|
||||||
|
|
||||||
|
package packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/doc"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(matloob): Delete this file once Go 1.12 is released.
|
||||||
|
|
||||||
|
// This file complements golist_fallback.go by providing
|
||||||
|
// support for generating testmains.
|
||||||
|
|
||||||
|
func generateTestmain(out string, testPkg, xtestPkg *Package) (extraimports, extradeps []string, err error) {
|
||||||
|
testFuncs, err := loadTestFuncs(testPkg, xtestPkg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
extraimports = []string{"testing", "testing/internal/testdeps"}
|
||||||
|
if testFuncs.TestMain == nil {
|
||||||
|
extraimports = append(extraimports, "os")
|
||||||
|
}
|
||||||
|
// Transitive dependencies of ("testing", "testing/internal/testdeps").
|
||||||
|
// os is part of the transitive closure so it and its transitive dependencies are
|
||||||
|
// included regardless of whether it's imported in the template below.
|
||||||
|
extradeps = []string{
|
||||||
|
"errors",
|
||||||
|
"internal/cpu",
|
||||||
|
"unsafe",
|
||||||
|
"internal/bytealg",
|
||||||
|
"internal/race",
|
||||||
|
"runtime/internal/atomic",
|
||||||
|
"runtime/internal/sys",
|
||||||
|
"runtime",
|
||||||
|
"sync/atomic",
|
||||||
|
"sync",
|
||||||
|
"io",
|
||||||
|
"unicode",
|
||||||
|
"unicode/utf8",
|
||||||
|
"bytes",
|
||||||
|
"math",
|
||||||
|
"syscall",
|
||||||
|
"time",
|
||||||
|
"internal/poll",
|
||||||
|
"internal/syscall/unix",
|
||||||
|
"internal/testlog",
|
||||||
|
"os",
|
||||||
|
"math/bits",
|
||||||
|
"strconv",
|
||||||
|
"reflect",
|
||||||
|
"fmt",
|
||||||
|
"sort",
|
||||||
|
"strings",
|
||||||
|
"flag",
|
||||||
|
"runtime/debug",
|
||||||
|
"context",
|
||||||
|
"runtime/trace",
|
||||||
|
"testing",
|
||||||
|
"bufio",
|
||||||
|
"regexp/syntax",
|
||||||
|
"regexp",
|
||||||
|
"compress/flate",
|
||||||
|
"encoding/binary",
|
||||||
|
"hash",
|
||||||
|
"hash/crc32",
|
||||||
|
"compress/gzip",
|
||||||
|
"path/filepath",
|
||||||
|
"io/ioutil",
|
||||||
|
"text/tabwriter",
|
||||||
|
"runtime/pprof",
|
||||||
|
"testing/internal/testdeps",
|
||||||
|
}
|
||||||
|
return extraimports, extradeps, writeTestmain(out, testFuncs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following is adapted from the cmd/go testmain generation code.
|
||||||
|
|
||||||
|
// isTestFunc tells whether fn has the type of a testing function. arg
|
||||||
|
// specifies the parameter type we look for: B, M or T.
|
||||||
|
func isTestFunc(fn *ast.FuncDecl, arg string) bool {
|
||||||
|
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
|
||||||
|
fn.Type.Params.List == nil ||
|
||||||
|
len(fn.Type.Params.List) != 1 ||
|
||||||
|
len(fn.Type.Params.List[0].Names) > 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ptr, ok := fn.Type.Params.List[0].Type.(*ast.StarExpr)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// We can't easily check that the type is *testing.M
|
||||||
|
// because we don't know how testing has been imported,
|
||||||
|
// but at least check that it's *M or *something.M.
|
||||||
|
// Same applies for B and T.
|
||||||
|
if name, ok := ptr.X.(*ast.Ident); ok && name.Name == arg {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if sel, ok := ptr.X.(*ast.SelectorExpr); ok && sel.Sel.Name == arg {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTest tells whether name looks like a test (or benchmark, according to prefix).
|
||||||
|
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
|
||||||
|
// We don't want TesticularCancer.
|
||||||
|
func isTest(name, prefix string) bool {
|
||||||
|
if !strings.HasPrefix(name, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(name) == len(prefix) { // "Test" is ok
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
|
||||||
|
return !unicode.IsLower(rune)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTestFuncs returns the testFuncs describing the tests that will be run.
|
||||||
|
func loadTestFuncs(ptest, pxtest *Package) (*testFuncs, error) {
|
||||||
|
t := &testFuncs{
|
||||||
|
TestPackage: ptest,
|
||||||
|
XTestPackage: pxtest,
|
||||||
|
}
|
||||||
|
for _, file := range ptest.GoFiles {
|
||||||
|
if !strings.HasSuffix(file, "_test.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := t.load(file, "_test", &t.ImportTest, &t.NeedTest); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pxtest != nil {
|
||||||
|
for _, file := range pxtest.GoFiles {
|
||||||
|
if err := t.load(file, "_xtest", &t.ImportXtest, &t.NeedXtest); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeTestmain writes the _testmain.go file for t to the file named out.
|
||||||
|
func writeTestmain(out string, t *testFuncs) error {
|
||||||
|
f, err := os.Create(out)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := testmainTmpl.Execute(f, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testFuncs struct {
|
||||||
|
Tests []testFunc
|
||||||
|
Benchmarks []testFunc
|
||||||
|
Examples []testFunc
|
||||||
|
TestMain *testFunc
|
||||||
|
TestPackage *Package
|
||||||
|
XTestPackage *Package
|
||||||
|
ImportTest bool
|
||||||
|
NeedTest bool
|
||||||
|
ImportXtest bool
|
||||||
|
NeedXtest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tested returns the name of the package being tested.
|
||||||
|
func (t *testFuncs) Tested() string {
|
||||||
|
return t.TestPackage.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type testFunc struct {
|
||||||
|
Package string // imported package name (_test or _xtest)
|
||||||
|
Name string // function name
|
||||||
|
Output string // output, for examples
|
||||||
|
Unordered bool // output is allowed to be unordered.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testFuncs) load(filename, pkg string, doImport, seen *bool) error {
|
||||||
|
var fset = token.NewFileSet()
|
||||||
|
|
||||||
|
f, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("failed to parse test file " + filename)
|
||||||
|
}
|
||||||
|
for _, d := range f.Decls {
|
||||||
|
n, ok := d.(*ast.FuncDecl)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n.Recv != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := n.Name.String()
|
||||||
|
switch {
|
||||||
|
case name == "TestMain":
|
||||||
|
if isTestFunc(n, "T") {
|
||||||
|
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
|
||||||
|
*doImport, *seen = true, true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := checkTestFunc(fset, n, "M")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t.TestMain != nil {
|
||||||
|
return errors.New("multiple definitions of TestMain")
|
||||||
|
}
|
||||||
|
t.TestMain = &testFunc{pkg, name, "", false}
|
||||||
|
*doImport, *seen = true, true
|
||||||
|
case isTest(name, "Test"):
|
||||||
|
err := checkTestFunc(fset, n, "T")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Tests = append(t.Tests, testFunc{pkg, name, "", false})
|
||||||
|
*doImport, *seen = true, true
|
||||||
|
case isTest(name, "Benchmark"):
|
||||||
|
err := checkTestFunc(fset, n, "B")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, "", false})
|
||||||
|
*doImport, *seen = true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ex := doc.Examples(f)
|
||||||
|
sort.Slice(ex, func(i, j int) bool { return ex[i].Order < ex[j].Order })
|
||||||
|
for _, e := range ex {
|
||||||
|
*doImport = true // import test file whether executed or not
|
||||||
|
if e.Output == "" && !e.EmptyOutput {
|
||||||
|
// Don't run examples with no output.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Examples = append(t.Examples, testFunc{pkg, "Example" + e.Name, e.Output, e.Unordered})
|
||||||
|
*seen = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkTestFunc(fset *token.FileSet, fn *ast.FuncDecl, arg string) error {
|
||||||
|
if !isTestFunc(fn, arg) {
|
||||||
|
name := fn.Name.String()
|
||||||
|
pos := fset.Position(fn.Pos())
|
||||||
|
return fmt.Errorf("%s: wrong signature for %s, must be: func %s(%s *testing.%s)", pos, name, name, strings.ToLower(arg), arg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var testmainTmpl = template.Must(template.New("main").Parse(`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
{{if not .TestMain}}
|
||||||
|
"os"
|
||||||
|
{{end}}
|
||||||
|
"testing"
|
||||||
|
"testing/internal/testdeps"
|
||||||
|
|
||||||
|
{{if .ImportTest}}
|
||||||
|
{{if .NeedTest}}_test{{else}}_{{end}} {{.TestPackage.PkgPath | printf "%q"}}
|
||||||
|
{{end}}
|
||||||
|
{{if .ImportXtest}}
|
||||||
|
{{if .NeedXtest}}_xtest{{else}}_{{end}} {{.XTestPackage.PkgPath | printf "%q"}}
|
||||||
|
{{end}}
|
||||||
|
)
|
||||||
|
|
||||||
|
var tests = []testing.InternalTest{
|
||||||
|
{{range .Tests}}
|
||||||
|
{"{{.Name}}", {{.Package}}.{{.Name}}},
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
var benchmarks = []testing.InternalBenchmark{
|
||||||
|
{{range .Benchmarks}}
|
||||||
|
{"{{.Name}}", {{.Package}}.{{.Name}}},
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
var examples = []testing.InternalExample{
|
||||||
|
{{range .Examples}}
|
||||||
|
{"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}, {{.Unordered}}},
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testdeps.ImportPath = {{.TestPackage.PkgPath | printf "%q"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m := testing.MainStart(testdeps.TestDeps{}, tests, benchmarks, examples)
|
||||||
|
{{with .TestMain}}
|
||||||
|
{{.Package}}.{{.Name}}(m)
|
||||||
|
{{else}}
|
||||||
|
os.Exit(m.Run())
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
|
||||||
|
`))
|
935
vendor/golang.org/x/tools/go/packages/packages.go
generated
vendored
Normal file
935
vendor/golang.org/x/tools/go/packages/packages.go
generated
vendored
Normal file
|
@ -0,0 +1,935 @@
|
||||||
|
// Copyright 2018 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 packages
|
||||||
|
|
||||||
|
// See doc.go for package documentation and implementation notes.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/parser"
|
||||||
|
"go/scanner"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/gcexportdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A LoadMode specifies the amount of detail to return when loading.
|
||||||
|
// Higher-numbered modes cause Load to return more information,
|
||||||
|
// but may be slower. Load may return more information than requested.
|
||||||
|
type LoadMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LoadFiles finds the packages and computes their source file lists.
|
||||||
|
// Package fields: ID, Name, Errors, GoFiles, and OtherFiles.
|
||||||
|
LoadFiles LoadMode = iota
|
||||||
|
|
||||||
|
// LoadImports adds import information for each package
|
||||||
|
// and its dependencies.
|
||||||
|
// Package fields added: Imports.
|
||||||
|
LoadImports
|
||||||
|
|
||||||
|
// LoadTypes adds type information for package-level
|
||||||
|
// declarations in the packages matching the patterns.
|
||||||
|
// Package fields added: Types, Fset, and IllTyped.
|
||||||
|
// This mode uses type information provided by the build system when
|
||||||
|
// possible, and may fill in the ExportFile field.
|
||||||
|
LoadTypes
|
||||||
|
|
||||||
|
// LoadSyntax adds typed syntax trees for the packages matching the patterns.
|
||||||
|
// Package fields added: Syntax, and TypesInfo, for direct pattern matches only.
|
||||||
|
LoadSyntax
|
||||||
|
|
||||||
|
// LoadAllSyntax adds typed syntax trees for the packages matching the patterns
|
||||||
|
// and all dependencies.
|
||||||
|
// Package fields added: Types, Fset, Illtyped, Syntax, and TypesInfo,
|
||||||
|
// for all packages in the import graph.
|
||||||
|
LoadAllSyntax
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Config specifies details about how packages should be loaded.
|
||||||
|
// The zero value is a valid configuration.
|
||||||
|
// Calls to Load do not modify this struct.
|
||||||
|
type Config struct {
|
||||||
|
// Mode controls the level of information returned for each package.
|
||||||
|
Mode LoadMode
|
||||||
|
|
||||||
|
// Context specifies the context for the load operation.
|
||||||
|
// If the context is cancelled, the loader may stop early
|
||||||
|
// and return an ErrCancelled error.
|
||||||
|
// If Context is nil, the load cannot be cancelled.
|
||||||
|
Context context.Context
|
||||||
|
|
||||||
|
// Dir is the directory in which to run the build system's query tool
|
||||||
|
// that provides information about the packages.
|
||||||
|
// If Dir is empty, the tool is run in the current directory.
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
// Env is the environment to use when invoking the build system's query tool.
|
||||||
|
// If Env is nil, the current environment is used.
|
||||||
|
// As in os/exec's Cmd, only the last value in the slice for
|
||||||
|
// each environment key is used. To specify the setting of only
|
||||||
|
// a few variables, append to the current environment, as in:
|
||||||
|
//
|
||||||
|
// opt.Env = append(os.Environ(), "GOOS=plan9", "GOARCH=386")
|
||||||
|
//
|
||||||
|
Env []string
|
||||||
|
|
||||||
|
// BuildFlags is a list of command-line flags to be passed through to
|
||||||
|
// the build system's query tool.
|
||||||
|
BuildFlags []string
|
||||||
|
|
||||||
|
// Fset provides source position information for syntax trees and types.
|
||||||
|
// If Fset is nil, the loader will create a new FileSet.
|
||||||
|
Fset *token.FileSet
|
||||||
|
|
||||||
|
// ParseFile is called to read and parse each file
|
||||||
|
// when preparing a package's type-checked syntax tree.
|
||||||
|
// It must be safe to call ParseFile simultaneously from multiple goroutines.
|
||||||
|
// If ParseFile is nil, the loader will uses parser.ParseFile.
|
||||||
|
//
|
||||||
|
// ParseFile should parse the source from src and use filename only for
|
||||||
|
// recording position information.
|
||||||
|
//
|
||||||
|
// An application may supply a custom implementation of ParseFile
|
||||||
|
// to change the effective file contents or the behavior of the parser,
|
||||||
|
// or to modify the syntax tree. For example, selectively eliminating
|
||||||
|
// unwanted function bodies can significantly accelerate type checking.
|
||||||
|
ParseFile func(fset *token.FileSet, filename string, src []byte) (*ast.File, error)
|
||||||
|
|
||||||
|
// If Tests is set, the loader includes not just the packages
|
||||||
|
// matching a particular pattern but also any related test packages,
|
||||||
|
// including test-only variants of the package and the test executable.
|
||||||
|
//
|
||||||
|
// For example, when using the go command, loading "fmt" with Tests=true
|
||||||
|
// returns four packages, with IDs "fmt" (the standard package),
|
||||||
|
// "fmt [fmt.test]" (the package as compiled for the test),
|
||||||
|
// "fmt_test" (the test functions from source files in package fmt_test),
|
||||||
|
// and "fmt.test" (the test binary).
|
||||||
|
//
|
||||||
|
// In build systems with explicit names for tests,
|
||||||
|
// setting Tests may have no effect.
|
||||||
|
Tests bool
|
||||||
|
|
||||||
|
// Overlay provides a mapping of absolute file paths to file contents.
|
||||||
|
// If the file with the given path already exists, the parser will use the
|
||||||
|
// alternative file contents provided by the map.
|
||||||
|
//
|
||||||
|
// The Package.Imports map may not include packages that are imported only
|
||||||
|
// by the alternative file contents provided by Overlay. This may cause
|
||||||
|
// type-checking to fail.
|
||||||
|
Overlay map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// driver is the type for functions that query the build system for the
|
||||||
|
// packages named by the patterns.
|
||||||
|
type driver func(cfg *Config, patterns ...string) (*driverResponse, error)
|
||||||
|
|
||||||
|
// driverResponse contains the results for a driver query.
|
||||||
|
type driverResponse struct {
|
||||||
|
// Roots is the set of package IDs that make up the root packages.
|
||||||
|
// We have to encode this separately because when we encode a single package
|
||||||
|
// we cannot know if it is one of the roots as that requires knowledge of the
|
||||||
|
// graph it is part of.
|
||||||
|
Roots []string `json:",omitempty"`
|
||||||
|
|
||||||
|
// Packages is the full set of packages in the graph.
|
||||||
|
// The packages are not connected into a graph.
|
||||||
|
// The Imports if populated will be stubs that only have their ID set.
|
||||||
|
// Imports will be connected and then type and syntax information added in a
|
||||||
|
// later pass (see refine).
|
||||||
|
Packages []*Package
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads and returns the Go packages named by the given patterns.
|
||||||
|
//
|
||||||
|
// Config specifies loading options;
|
||||||
|
// nil behaves the same as an empty Config.
|
||||||
|
//
|
||||||
|
// Load returns an error if any of the patterns was invalid
|
||||||
|
// as defined by the underlying build system.
|
||||||
|
// It may return an empty list of packages without an error,
|
||||||
|
// for instance for an empty expansion of a valid wildcard.
|
||||||
|
// Errors associated with a particular package are recorded in the
|
||||||
|
// corresponding Package's Errors list, and do not cause Load to
|
||||||
|
// return an error. Clients may need to handle such errors before
|
||||||
|
// proceeding with further analysis. The PrintErrors function is
|
||||||
|
// provided for convenient display of all errors.
|
||||||
|
func Load(cfg *Config, patterns ...string) ([]*Package, error) {
|
||||||
|
l := newLoader(cfg)
|
||||||
|
response, err := defaultDriver(&l.Config, patterns...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
|
||||||
|
driver := findExternalDriver(cfg)
|
||||||
|
if driver == nil {
|
||||||
|
driver = goListDriver
|
||||||
|
}
|
||||||
|
return driver(cfg, patterns...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Package describes a loaded Go package.
|
||||||
|
type Package struct {
|
||||||
|
// ID is a unique identifier for a package,
|
||||||
|
// in a syntax provided by the underlying build system.
|
||||||
|
//
|
||||||
|
// Because the syntax varies based on the build system,
|
||||||
|
// clients should treat IDs as opaque and not attempt to
|
||||||
|
// interpret them.
|
||||||
|
ID string
|
||||||
|
|
||||||
|
// Name is the package name as it appears in the package source code.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// PkgPath is the package path as used by the go/types package.
|
||||||
|
PkgPath string
|
||||||
|
|
||||||
|
// Errors contains any errors encountered querying the metadata
|
||||||
|
// of the package, or while parsing or type-checking its files.
|
||||||
|
Errors []Error
|
||||||
|
|
||||||
|
// GoFiles lists the absolute file paths of the package's Go source files.
|
||||||
|
GoFiles []string
|
||||||
|
|
||||||
|
// CompiledGoFiles lists the absolute file paths of the package's source
|
||||||
|
// files that were presented to the compiler.
|
||||||
|
// This may differ from GoFiles if files are processed before compilation.
|
||||||
|
CompiledGoFiles []string
|
||||||
|
|
||||||
|
// OtherFiles lists the absolute file paths of the package's non-Go source files,
|
||||||
|
// including assembly, C, C++, Fortran, Objective-C, SWIG, and so on.
|
||||||
|
OtherFiles []string
|
||||||
|
|
||||||
|
// ExportFile is the absolute path to a file containing type
|
||||||
|
// information for the package as provided by the build system.
|
||||||
|
ExportFile string
|
||||||
|
|
||||||
|
// Imports maps import paths appearing in the package's Go source files
|
||||||
|
// to corresponding loaded Packages.
|
||||||
|
Imports map[string]*Package
|
||||||
|
|
||||||
|
// Types provides type information for the package.
|
||||||
|
// Modes LoadTypes and above set this field for packages matching the
|
||||||
|
// patterns; type information for dependencies may be missing or incomplete.
|
||||||
|
// Mode LoadAllSyntax sets this field for all packages, including dependencies.
|
||||||
|
Types *types.Package
|
||||||
|
|
||||||
|
// Fset provides position information for Types, TypesInfo, and Syntax.
|
||||||
|
// It is set only when Types is set.
|
||||||
|
Fset *token.FileSet
|
||||||
|
|
||||||
|
// IllTyped indicates whether the package or any dependency contains errors.
|
||||||
|
// It is set only when Types is set.
|
||||||
|
IllTyped bool
|
||||||
|
|
||||||
|
// Syntax is the package's syntax trees, for the files listed in CompiledGoFiles.
|
||||||
|
//
|
||||||
|
// Mode LoadSyntax sets this field for packages matching the patterns.
|
||||||
|
// Mode LoadAllSyntax sets this field for all packages, including dependencies.
|
||||||
|
Syntax []*ast.File
|
||||||
|
|
||||||
|
// TypesInfo provides type information about the package's syntax trees.
|
||||||
|
// It is set only when Syntax is set.
|
||||||
|
TypesInfo *types.Info
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Error describes a problem with a package's metadata, syntax, or types.
|
||||||
|
type Error struct {
|
||||||
|
Pos string // "file:line:col" or "file:line" or "" or "-"
|
||||||
|
Msg string
|
||||||
|
Kind ErrorKind
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorKind describes the source of the error, allowing the user to
|
||||||
|
// differentiate between errors generated by the driver, the parser, or the
|
||||||
|
// type-checker.
|
||||||
|
type ErrorKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownError ErrorKind = iota
|
||||||
|
ListError
|
||||||
|
ParseError
|
||||||
|
TypeError
|
||||||
|
)
|
||||||
|
|
||||||
|
func (err Error) Error() string {
|
||||||
|
pos := err.Pos
|
||||||
|
if pos == "" {
|
||||||
|
pos = "-" // like token.Position{}.String()
|
||||||
|
}
|
||||||
|
return pos + ": " + err.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatPackage is the JSON form of Package
|
||||||
|
// It drops all the type and syntax fields, and transforms the Imports
|
||||||
|
//
|
||||||
|
// TODO(adonovan): identify this struct with Package, effectively
|
||||||
|
// publishing the JSON protocol.
|
||||||
|
type flatPackage struct {
|
||||||
|
ID string
|
||||||
|
Name string `json:",omitempty"`
|
||||||
|
PkgPath string `json:",omitempty"`
|
||||||
|
Errors []Error `json:",omitempty"`
|
||||||
|
GoFiles []string `json:",omitempty"`
|
||||||
|
CompiledGoFiles []string `json:",omitempty"`
|
||||||
|
OtherFiles []string `json:",omitempty"`
|
||||||
|
ExportFile string `json:",omitempty"`
|
||||||
|
Imports map[string]string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the Package in its JSON form.
|
||||||
|
// For the most part, the structure fields are written out unmodified, and
|
||||||
|
// the type and syntax fields are skipped.
|
||||||
|
// The imports are written out as just a map of path to package id.
|
||||||
|
// The errors are written using a custom type that tries to preserve the
|
||||||
|
// structure of error types we know about.
|
||||||
|
//
|
||||||
|
// This method exists to enable support for additional build systems. It is
|
||||||
|
// not intended for use by clients of the API and we may change the format.
|
||||||
|
func (p *Package) MarshalJSON() ([]byte, error) {
|
||||||
|
flat := &flatPackage{
|
||||||
|
ID: p.ID,
|
||||||
|
Name: p.Name,
|
||||||
|
PkgPath: p.PkgPath,
|
||||||
|
Errors: p.Errors,
|
||||||
|
GoFiles: p.GoFiles,
|
||||||
|
CompiledGoFiles: p.CompiledGoFiles,
|
||||||
|
OtherFiles: p.OtherFiles,
|
||||||
|
ExportFile: p.ExportFile,
|
||||||
|
}
|
||||||
|
if len(p.Imports) > 0 {
|
||||||
|
flat.Imports = make(map[string]string, len(p.Imports))
|
||||||
|
for path, ipkg := range p.Imports {
|
||||||
|
flat.Imports[path] = ipkg.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(flat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON reads in a Package from its JSON format.
|
||||||
|
// See MarshalJSON for details about the format accepted.
|
||||||
|
func (p *Package) UnmarshalJSON(b []byte) error {
|
||||||
|
flat := &flatPackage{}
|
||||||
|
if err := json.Unmarshal(b, &flat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = Package{
|
||||||
|
ID: flat.ID,
|
||||||
|
Name: flat.Name,
|
||||||
|
PkgPath: flat.PkgPath,
|
||||||
|
Errors: flat.Errors,
|
||||||
|
GoFiles: flat.GoFiles,
|
||||||
|
CompiledGoFiles: flat.CompiledGoFiles,
|
||||||
|
OtherFiles: flat.OtherFiles,
|
||||||
|
ExportFile: flat.ExportFile,
|
||||||
|
}
|
||||||
|
if len(flat.Imports) > 0 {
|
||||||
|
p.Imports = make(map[string]*Package, len(flat.Imports))
|
||||||
|
for path, id := range flat.Imports {
|
||||||
|
p.Imports[path] = &Package{ID: id}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Package) String() string { return p.ID }
|
||||||
|
|
||||||
|
// loaderPackage augments Package with state used during the loading phase
|
||||||
|
type loaderPackage struct {
|
||||||
|
*Package
|
||||||
|
importErrors map[string]error // maps each bad import to its error
|
||||||
|
loadOnce sync.Once
|
||||||
|
color uint8 // for cycle detection
|
||||||
|
needsrc bool // load from source (Mode >= LoadTypes)
|
||||||
|
needtypes bool // type information is either requested or depended on
|
||||||
|
initial bool // package was matched by a pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
// loader holds the working state of a single call to load.
|
||||||
|
type loader struct {
|
||||||
|
pkgs map[string]*loaderPackage
|
||||||
|
Config
|
||||||
|
exportMu sync.Mutex // enforces mutual exclusion of exportdata operations
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLoader(cfg *Config) *loader {
|
||||||
|
ld := &loader{}
|
||||||
|
if cfg != nil {
|
||||||
|
ld.Config = *cfg
|
||||||
|
}
|
||||||
|
if ld.Config.Env == nil {
|
||||||
|
ld.Config.Env = os.Environ()
|
||||||
|
}
|
||||||
|
if ld.Context == nil {
|
||||||
|
ld.Context = context.Background()
|
||||||
|
}
|
||||||
|
if ld.Dir == "" {
|
||||||
|
if dir, err := os.Getwd(); err == nil {
|
||||||
|
ld.Dir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ld.Mode >= LoadTypes {
|
||||||
|
if ld.Fset == nil {
|
||||||
|
ld.Fset = token.NewFileSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFile is required even in LoadTypes mode
|
||||||
|
// because we load source if export data is missing.
|
||||||
|
if ld.ParseFile == nil {
|
||||||
|
ld.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
|
||||||
|
var isrc interface{}
|
||||||
|
if src != nil {
|
||||||
|
isrc = src
|
||||||
|
}
|
||||||
|
const mode = parser.AllErrors | parser.ParseComments
|
||||||
|
return parser.ParseFile(fset, filename, isrc, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ld
|
||||||
|
}
|
||||||
|
|
||||||
|
// refine connects the supplied packages into a graph and then adds type and
|
||||||
|
// and syntax information as requested by the LoadMode.
|
||||||
|
func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
|
||||||
|
isRoot := make(map[string]bool, len(roots))
|
||||||
|
for _, root := range roots {
|
||||||
|
isRoot[root] = true
|
||||||
|
}
|
||||||
|
ld.pkgs = make(map[string]*loaderPackage)
|
||||||
|
// first pass, fixup and build the map and roots
|
||||||
|
var initial []*loaderPackage
|
||||||
|
for _, pkg := range list {
|
||||||
|
lpkg := &loaderPackage{
|
||||||
|
Package: pkg,
|
||||||
|
needtypes: ld.Mode >= LoadAllSyntax ||
|
||||||
|
ld.Mode >= LoadTypes && isRoot[pkg.ID],
|
||||||
|
needsrc: ld.Mode >= LoadAllSyntax ||
|
||||||
|
ld.Mode >= LoadSyntax && isRoot[pkg.ID] ||
|
||||||
|
pkg.ExportFile == "" && pkg.PkgPath != "unsafe",
|
||||||
|
}
|
||||||
|
ld.pkgs[lpkg.ID] = lpkg
|
||||||
|
if isRoot[lpkg.ID] {
|
||||||
|
initial = append(initial, lpkg)
|
||||||
|
lpkg.initial = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Materialize the import graph.
|
||||||
|
|
||||||
|
const (
|
||||||
|
white = 0 // new
|
||||||
|
grey = 1 // in progress
|
||||||
|
black = 2 // complete
|
||||||
|
)
|
||||||
|
|
||||||
|
// visit traverses the import graph, depth-first,
|
||||||
|
// and materializes the graph as Packages.Imports.
|
||||||
|
//
|
||||||
|
// Valid imports are saved in the Packages.Import map.
|
||||||
|
// Invalid imports (cycles and missing nodes) are saved in the importErrors map.
|
||||||
|
// Thus, even in the presence of both kinds of errors, the Import graph remains a DAG.
|
||||||
|
//
|
||||||
|
// visit returns whether the package needs src or has a transitive
|
||||||
|
// dependency on a package that does. These are the only packages
|
||||||
|
// for which we load source code.
|
||||||
|
var stack []*loaderPackage
|
||||||
|
var visit func(lpkg *loaderPackage) bool
|
||||||
|
var srcPkgs []*loaderPackage
|
||||||
|
visit = func(lpkg *loaderPackage) bool {
|
||||||
|
switch lpkg.color {
|
||||||
|
case black:
|
||||||
|
return lpkg.needsrc
|
||||||
|
case grey:
|
||||||
|
panic("internal error: grey node")
|
||||||
|
}
|
||||||
|
lpkg.color = grey
|
||||||
|
stack = append(stack, lpkg) // push
|
||||||
|
stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports
|
||||||
|
lpkg.Imports = make(map[string]*Package, len(stubs))
|
||||||
|
for importPath, ipkg := range stubs {
|
||||||
|
var importErr error
|
||||||
|
imp := ld.pkgs[ipkg.ID]
|
||||||
|
if imp == nil {
|
||||||
|
// (includes package "C" when DisableCgo)
|
||||||
|
importErr = fmt.Errorf("missing package: %q", ipkg.ID)
|
||||||
|
} else if imp.color == grey {
|
||||||
|
importErr = fmt.Errorf("import cycle: %s", stack)
|
||||||
|
}
|
||||||
|
if importErr != nil {
|
||||||
|
if lpkg.importErrors == nil {
|
||||||
|
lpkg.importErrors = make(map[string]error)
|
||||||
|
}
|
||||||
|
lpkg.importErrors[importPath] = importErr
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if visit(imp) {
|
||||||
|
lpkg.needsrc = true
|
||||||
|
}
|
||||||
|
lpkg.Imports[importPath] = imp.Package
|
||||||
|
}
|
||||||
|
if lpkg.needsrc {
|
||||||
|
srcPkgs = append(srcPkgs, lpkg)
|
||||||
|
}
|
||||||
|
stack = stack[:len(stack)-1] // pop
|
||||||
|
lpkg.color = black
|
||||||
|
|
||||||
|
return lpkg.needsrc
|
||||||
|
}
|
||||||
|
|
||||||
|
if ld.Mode < LoadImports {
|
||||||
|
//we do this to drop the stub import packages that we are not even going to try to resolve
|
||||||
|
for _, lpkg := range initial {
|
||||||
|
lpkg.Imports = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For each initial package, create its import DAG.
|
||||||
|
for _, lpkg := range initial {
|
||||||
|
visit(lpkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, lpkg := range srcPkgs {
|
||||||
|
// Complete type information is required for the
|
||||||
|
// immediate dependencies of each source package.
|
||||||
|
for _, ipkg := range lpkg.Imports {
|
||||||
|
imp := ld.pkgs[ipkg.ID]
|
||||||
|
imp.needtypes = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Load type data if needed, starting at
|
||||||
|
// the initial packages (roots of the import DAG).
|
||||||
|
if ld.Mode >= LoadTypes {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, lpkg := range initial {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(lpkg *loaderPackage) {
|
||||||
|
ld.loadRecursive(lpkg)
|
||||||
|
wg.Done()
|
||||||
|
}(lpkg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*Package, len(initial))
|
||||||
|
for i, lpkg := range initial {
|
||||||
|
result[i] = lpkg.Package
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadRecursive loads the specified package and its dependencies,
|
||||||
|
// recursively, in parallel, in topological order.
|
||||||
|
// It is atomic and idempotent.
|
||||||
|
// Precondition: ld.Mode >= LoadTypes.
|
||||||
|
func (ld *loader) loadRecursive(lpkg *loaderPackage) {
|
||||||
|
lpkg.loadOnce.Do(func() {
|
||||||
|
// Load the direct dependencies, in parallel.
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, ipkg := range lpkg.Imports {
|
||||||
|
imp := ld.pkgs[ipkg.ID]
|
||||||
|
wg.Add(1)
|
||||||
|
go func(imp *loaderPackage) {
|
||||||
|
ld.loadRecursive(imp)
|
||||||
|
wg.Done()
|
||||||
|
}(imp)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
ld.loadPackage(lpkg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadPackage loads the specified package.
|
||||||
|
// It must be called only once per Package,
|
||||||
|
// after immediate dependencies are loaded.
|
||||||
|
// Precondition: ld.Mode >= LoadTypes.
|
||||||
|
func (ld *loader) loadPackage(lpkg *loaderPackage) {
|
||||||
|
if lpkg.PkgPath == "unsafe" {
|
||||||
|
// Fill in the blanks to avoid surprises.
|
||||||
|
lpkg.Types = types.Unsafe
|
||||||
|
lpkg.Fset = ld.Fset
|
||||||
|
lpkg.Syntax = []*ast.File{}
|
||||||
|
lpkg.TypesInfo = new(types.Info)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call NewPackage directly with explicit name.
|
||||||
|
// This avoids skew between golist and go/types when the files'
|
||||||
|
// package declarations are inconsistent.
|
||||||
|
lpkg.Types = types.NewPackage(lpkg.PkgPath, lpkg.Name)
|
||||||
|
lpkg.Fset = ld.Fset
|
||||||
|
|
||||||
|
// Subtle: we populate all Types fields with an empty Package
|
||||||
|
// before loading export data so that export data processing
|
||||||
|
// never has to create a types.Package for an indirect dependency,
|
||||||
|
// which would then require that such created packages be explicitly
|
||||||
|
// inserted back into the Import graph as a final step after export data loading.
|
||||||
|
// The Diamond test exercises this case.
|
||||||
|
if !lpkg.needtypes {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !lpkg.needsrc {
|
||||||
|
ld.loadFromExportData(lpkg)
|
||||||
|
return // not a source package, don't get syntax trees
|
||||||
|
}
|
||||||
|
|
||||||
|
appendError := func(err error) {
|
||||||
|
// Convert various error types into the one true Error.
|
||||||
|
var errs []Error
|
||||||
|
switch err := err.(type) {
|
||||||
|
case Error:
|
||||||
|
// from driver
|
||||||
|
errs = append(errs, err)
|
||||||
|
|
||||||
|
case *os.PathError:
|
||||||
|
// from parser
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: err.Path + ":1",
|
||||||
|
Msg: err.Err.Error(),
|
||||||
|
Kind: ParseError,
|
||||||
|
})
|
||||||
|
|
||||||
|
case scanner.ErrorList:
|
||||||
|
// from parser
|
||||||
|
for _, err := range err {
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: err.Pos.String(),
|
||||||
|
Msg: err.Msg,
|
||||||
|
Kind: ParseError,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case types.Error:
|
||||||
|
// from type checker
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: err.Fset.Position(err.Pos).String(),
|
||||||
|
Msg: err.Msg,
|
||||||
|
Kind: TypeError,
|
||||||
|
})
|
||||||
|
|
||||||
|
default:
|
||||||
|
// unexpected impoverished error from parser?
|
||||||
|
errs = append(errs, Error{
|
||||||
|
Pos: "-",
|
||||||
|
Msg: err.Error(),
|
||||||
|
Kind: UnknownError,
|
||||||
|
})
|
||||||
|
|
||||||
|
// If you see this error message, please file a bug.
|
||||||
|
log.Printf("internal error: error %q (%T) without position", err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lpkg.Errors = append(lpkg.Errors, errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
files, errs := ld.parseFiles(lpkg.CompiledGoFiles)
|
||||||
|
for _, err := range errs {
|
||||||
|
appendError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lpkg.Syntax = files
|
||||||
|
|
||||||
|
lpkg.TypesInfo = &types.Info{
|
||||||
|
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||||
|
Defs: make(map[*ast.Ident]types.Object),
|
||||||
|
Uses: make(map[*ast.Ident]types.Object),
|
||||||
|
Implicits: make(map[ast.Node]types.Object),
|
||||||
|
Scopes: make(map[ast.Node]*types.Scope),
|
||||||
|
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||||
|
}
|
||||||
|
|
||||||
|
importer := importerFunc(func(path string) (*types.Package, error) {
|
||||||
|
if path == "unsafe" {
|
||||||
|
return types.Unsafe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The imports map is keyed by import path.
|
||||||
|
ipkg := lpkg.Imports[path]
|
||||||
|
if ipkg == nil {
|
||||||
|
if err := lpkg.importErrors[path]; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// There was skew between the metadata and the
|
||||||
|
// import declarations, likely due to an edit
|
||||||
|
// race, or because the ParseFile feature was
|
||||||
|
// used to supply alternative file contents.
|
||||||
|
return nil, fmt.Errorf("no metadata for %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ipkg.Types != nil && ipkg.Types.Complete() {
|
||||||
|
return ipkg.Types, nil
|
||||||
|
}
|
||||||
|
log.Fatalf("internal error: nil Pkg importing %q from %q", path, lpkg)
|
||||||
|
panic("unreachable")
|
||||||
|
})
|
||||||
|
|
||||||
|
// This is only an approximation.
|
||||||
|
// TODO(adonovan): derive Sizes from the underlying build system.
|
||||||
|
goarch := runtime.GOARCH
|
||||||
|
const goarchPrefix = "GOARCH="
|
||||||
|
for _, e := range ld.Config.Env {
|
||||||
|
if strings.HasPrefix(e, goarchPrefix) {
|
||||||
|
goarch = e[len(goarchPrefix):]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sizes := types.SizesFor("gc", goarch)
|
||||||
|
|
||||||
|
// type-check
|
||||||
|
tc := &types.Config{
|
||||||
|
Importer: importer,
|
||||||
|
|
||||||
|
// Type-check bodies of functions only in non-initial packages.
|
||||||
|
// Example: for import graph A->B->C and initial packages {A,C},
|
||||||
|
// we can ignore function bodies in B.
|
||||||
|
IgnoreFuncBodies: ld.Mode < LoadAllSyntax && !lpkg.initial,
|
||||||
|
|
||||||
|
Error: appendError,
|
||||||
|
Sizes: sizes,
|
||||||
|
}
|
||||||
|
types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax)
|
||||||
|
|
||||||
|
lpkg.importErrors = nil // no longer needed
|
||||||
|
|
||||||
|
// If !Cgo, the type-checker uses FakeImportC mode, so
|
||||||
|
// it doesn't invoke the importer for import "C",
|
||||||
|
// nor report an error for the import,
|
||||||
|
// or for any undefined C.f reference.
|
||||||
|
// We must detect this explicitly and correctly
|
||||||
|
// mark the package as IllTyped (by reporting an error).
|
||||||
|
// TODO(adonovan): if these errors are annoying,
|
||||||
|
// we could just set IllTyped quietly.
|
||||||
|
if tc.FakeImportC {
|
||||||
|
outer:
|
||||||
|
for _, f := range lpkg.Syntax {
|
||||||
|
for _, imp := range f.Imports {
|
||||||
|
if imp.Path.Value == `"C"` {
|
||||||
|
err := types.Error{Fset: ld.Fset, Pos: imp.Pos(), Msg: `import "C" ignored`}
|
||||||
|
appendError(err)
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record accumulated errors.
|
||||||
|
illTyped := len(lpkg.Errors) > 0
|
||||||
|
if !illTyped {
|
||||||
|
for _, imp := range lpkg.Imports {
|
||||||
|
if imp.IllTyped {
|
||||||
|
illTyped = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lpkg.IllTyped = illTyped
|
||||||
|
}
|
||||||
|
|
||||||
|
// An importFunc is an implementation of the single-method
|
||||||
|
// types.Importer interface based on a function value.
|
||||||
|
type importerFunc func(path string) (*types.Package, error)
|
||||||
|
|
||||||
|
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
|
||||||
|
|
||||||
|
// We use a counting semaphore to limit
|
||||||
|
// the number of parallel I/O calls per process.
|
||||||
|
var ioLimit = make(chan bool, 20)
|
||||||
|
|
||||||
|
// parseFiles reads and parses the Go source files and returns the ASTs
|
||||||
|
// of the ones that could be at least partially parsed, along with a
|
||||||
|
// list of I/O and parse errors encountered.
|
||||||
|
//
|
||||||
|
// Because files are scanned in parallel, the token.Pos
|
||||||
|
// positions of the resulting ast.Files are not ordered.
|
||||||
|
//
|
||||||
|
func (ld *loader) parseFiles(filenames []string) ([]*ast.File, []error) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
n := len(filenames)
|
||||||
|
parsed := make([]*ast.File, n)
|
||||||
|
errors := make([]error, n)
|
||||||
|
for i, file := range filenames {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int, filename string) {
|
||||||
|
ioLimit <- true // wait
|
||||||
|
// ParseFile may return both an AST and an error.
|
||||||
|
var src []byte
|
||||||
|
for f, contents := range ld.Config.Overlay {
|
||||||
|
if sameFile(f, filename) {
|
||||||
|
src = contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if src == nil {
|
||||||
|
src, err = ioutil.ReadFile(filename)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
parsed[i], errors[i] = nil, err
|
||||||
|
} else {
|
||||||
|
parsed[i], errors[i] = ld.ParseFile(ld.Fset, filename, src)
|
||||||
|
}
|
||||||
|
<-ioLimit // signal
|
||||||
|
wg.Done()
|
||||||
|
}(i, file)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Eliminate nils, preserving order.
|
||||||
|
var o int
|
||||||
|
for _, f := range parsed {
|
||||||
|
if f != nil {
|
||||||
|
parsed[o] = f
|
||||||
|
o++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed = parsed[:o]
|
||||||
|
|
||||||
|
o = 0
|
||||||
|
for _, err := range errors {
|
||||||
|
if err != nil {
|
||||||
|
errors[o] = err
|
||||||
|
o++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errors = errors[:o]
|
||||||
|
|
||||||
|
return parsed, errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// sameFile returns true if x and y have the same basename and denote
|
||||||
|
// the same file.
|
||||||
|
//
|
||||||
|
func sameFile(x, y string) bool {
|
||||||
|
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
|
||||||
|
if xi, err := os.Stat(x); err == nil {
|
||||||
|
if yi, err := os.Stat(y); err == nil {
|
||||||
|
return os.SameFile(xi, yi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadFromExportData returns type information for the specified
|
||||||
|
// package, loading it from an export data file on the first request.
|
||||||
|
func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error) {
|
||||||
|
if lpkg.PkgPath == "" {
|
||||||
|
log.Fatalf("internal error: Package %s has no PkgPath", lpkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because gcexportdata.Read has the potential to create or
|
||||||
|
// modify the types.Package for each node in the transitive
|
||||||
|
// closure of dependencies of lpkg, all exportdata operations
|
||||||
|
// must be sequential. (Finer-grained locking would require
|
||||||
|
// changes to the gcexportdata API.)
|
||||||
|
//
|
||||||
|
// The exportMu lock guards the Package.Pkg field and the
|
||||||
|
// types.Package it points to, for each Package in the graph.
|
||||||
|
//
|
||||||
|
// Not all accesses to Package.Pkg need to be protected by exportMu:
|
||||||
|
// graph ordering ensures that direct dependencies of source
|
||||||
|
// packages are fully loaded before the importer reads their Pkg field.
|
||||||
|
ld.exportMu.Lock()
|
||||||
|
defer ld.exportMu.Unlock()
|
||||||
|
|
||||||
|
if tpkg := lpkg.Types; tpkg != nil && tpkg.Complete() {
|
||||||
|
return tpkg, nil // cache hit
|
||||||
|
}
|
||||||
|
|
||||||
|
lpkg.IllTyped = true // fail safe
|
||||||
|
|
||||||
|
if lpkg.ExportFile == "" {
|
||||||
|
// Errors while building export data will have been printed to stderr.
|
||||||
|
return nil, fmt.Errorf("no export data file")
|
||||||
|
}
|
||||||
|
f, err := os.Open(lpkg.ExportFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Read gc export data.
|
||||||
|
//
|
||||||
|
// We don't currently support gccgo export data because all
|
||||||
|
// underlying workspaces use the gc toolchain. (Even build
|
||||||
|
// systems that support gccgo don't use it for workspace
|
||||||
|
// queries.)
|
||||||
|
r, err := gcexportdata.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading %s: %v", lpkg.ExportFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the view.
|
||||||
|
//
|
||||||
|
// The gcexportdata machinery has no concept of package ID.
|
||||||
|
// It identifies packages by their PkgPath, which although not
|
||||||
|
// globally unique is unique within the scope of one invocation
|
||||||
|
// of the linker, type-checker, or gcexportdata.
|
||||||
|
//
|
||||||
|
// So, we must build a PkgPath-keyed view of the global
|
||||||
|
// (conceptually ID-keyed) cache of packages and pass it to
|
||||||
|
// gcexportdata. The view must contain every existing
|
||||||
|
// package that might possibly be mentioned by the
|
||||||
|
// current package---its transitive closure.
|
||||||
|
//
|
||||||
|
// In loadPackage, we unconditionally create a types.Package for
|
||||||
|
// each dependency so that export data loading does not
|
||||||
|
// create new ones.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): it would be simpler and more efficient
|
||||||
|
// if the export data machinery invoked a callback to
|
||||||
|
// get-or-create a package instead of a map.
|
||||||
|
//
|
||||||
|
view := make(map[string]*types.Package) // view seen by gcexportdata
|
||||||
|
seen := make(map[*loaderPackage]bool) // all visited packages
|
||||||
|
var visit func(pkgs map[string]*Package)
|
||||||
|
visit = func(pkgs map[string]*Package) {
|
||||||
|
for _, p := range pkgs {
|
||||||
|
lpkg := ld.pkgs[p.ID]
|
||||||
|
if !seen[lpkg] {
|
||||||
|
seen[lpkg] = true
|
||||||
|
view[lpkg.PkgPath] = lpkg.Types
|
||||||
|
visit(lpkg.Imports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visit(lpkg.Imports)
|
||||||
|
|
||||||
|
viewLen := len(view) + 1 // adding the self package
|
||||||
|
// Parse the export data.
|
||||||
|
// (May modify incomplete packages in view but not create new ones.)
|
||||||
|
tpkg, err := gcexportdata.Read(r, ld.Fset, view, lpkg.PkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("reading %s: %v", lpkg.ExportFile, err)
|
||||||
|
}
|
||||||
|
if viewLen != len(view) {
|
||||||
|
log.Fatalf("Unexpected package creation during export data loading")
|
||||||
|
}
|
||||||
|
|
||||||
|
lpkg.Types = tpkg
|
||||||
|
lpkg.IllTyped = false
|
||||||
|
|
||||||
|
return tpkg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func usesExportData(cfg *Config) bool {
|
||||||
|
return LoadTypes <= cfg.Mode && cfg.Mode < LoadAllSyntax
|
||||||
|
}
|
55
vendor/golang.org/x/tools/go/packages/visit.go
generated
vendored
Normal file
55
vendor/golang.org/x/tools/go/packages/visit.go
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package packages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Visit visits all the packages in the import graph whose roots are
|
||||||
|
// pkgs, calling the optional pre function the first time each package
|
||||||
|
// is encountered (preorder), and the optional post function after a
|
||||||
|
// package's dependencies have been visited (postorder).
|
||||||
|
// The boolean result of pre(pkg) determines whether
|
||||||
|
// the imports of package pkg are visited.
|
||||||
|
func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) {
|
||||||
|
seen := make(map[*Package]bool)
|
||||||
|
var visit func(*Package)
|
||||||
|
visit = func(pkg *Package) {
|
||||||
|
if !seen[pkg] {
|
||||||
|
seen[pkg] = true
|
||||||
|
|
||||||
|
if pre == nil || pre(pkg) {
|
||||||
|
paths := make([]string, 0, len(pkg.Imports))
|
||||||
|
for path := range pkg.Imports {
|
||||||
|
paths = append(paths, path)
|
||||||
|
}
|
||||||
|
sort.Strings(paths) // for determinism
|
||||||
|
for _, path := range paths {
|
||||||
|
visit(pkg.Imports[path])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if post != nil {
|
||||||
|
post(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
visit(pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintErrors prints to os.Stderr the accumulated errors of all
|
||||||
|
// packages in the import graph rooted at pkgs, dependencies first.
|
||||||
|
// PrintErrors returns the number of errors printed.
|
||||||
|
func PrintErrors(pkgs []*Package) int {
|
||||||
|
var n int
|
||||||
|
Visit(pkgs, nil, func(pkg *Package) {
|
||||||
|
for _, err := range pkg.Errors {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return n
|
||||||
|
}
|
196
vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go
generated
vendored
Normal file
196
vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go
generated
vendored
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
// Copyright 2016 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 fastwalk provides a faster version of filepath.Walk for file system
|
||||||
|
// scanning tools.
|
||||||
|
package fastwalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraverseLink is used as a return value from WalkFuncs to indicate that the
|
||||||
|
// symlink named in the call may be traversed.
|
||||||
|
var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory")
|
||||||
|
|
||||||
|
// SkipFiles is a used as a return value from WalkFuncs to indicate that the
|
||||||
|
// callback should not be called for any other files in the current directory.
|
||||||
|
// Child directories will still be traversed.
|
||||||
|
var SkipFiles = errors.New("fastwalk: skip remaining files in directory")
|
||||||
|
|
||||||
|
// Walk is a faster implementation of filepath.Walk.
|
||||||
|
//
|
||||||
|
// filepath.Walk's design necessarily calls os.Lstat on each file,
|
||||||
|
// even if the caller needs less info.
|
||||||
|
// Many tools need only the type of each file.
|
||||||
|
// On some platforms, this information is provided directly by the readdir
|
||||||
|
// system call, avoiding the need to stat each file individually.
|
||||||
|
// fastwalk_unix.go contains a fork of the syscall routines.
|
||||||
|
//
|
||||||
|
// See golang.org/issue/16399
|
||||||
|
//
|
||||||
|
// Walk walks the file tree rooted at root, calling walkFn for
|
||||||
|
// each file or directory in the tree, including root.
|
||||||
|
//
|
||||||
|
// If fastWalk returns filepath.SkipDir, the directory is skipped.
|
||||||
|
//
|
||||||
|
// Unlike filepath.Walk:
|
||||||
|
// * file stat calls must be done by the user.
|
||||||
|
// The only provided metadata is the file type, which does not include
|
||||||
|
// any permission bits.
|
||||||
|
// * multiple goroutines stat the filesystem concurrently. The provided
|
||||||
|
// walkFn must be safe for concurrent use.
|
||||||
|
// * fastWalk can follow symlinks if walkFn returns the TraverseLink
|
||||||
|
// sentinel error. It is the walkFn's responsibility to prevent
|
||||||
|
// fastWalk from going into symlink cycles.
|
||||||
|
func Walk(root string, walkFn func(path string, typ os.FileMode) error) error {
|
||||||
|
// TODO(bradfitz): make numWorkers configurable? We used a
|
||||||
|
// minimum of 4 to give the kernel more info about multiple
|
||||||
|
// things we want, in hopes its I/O scheduling can take
|
||||||
|
// advantage of that. Hopefully most are in cache. Maybe 4 is
|
||||||
|
// even too low of a minimum. Profile more.
|
||||||
|
numWorkers := 4
|
||||||
|
if n := runtime.NumCPU(); n > numWorkers {
|
||||||
|
numWorkers = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure to wait for all workers to finish, otherwise
|
||||||
|
// walkFn could still be called after returning. This Wait call
|
||||||
|
// runs after close(e.donec) below.
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
defer wg.Wait()
|
||||||
|
|
||||||
|
w := &walker{
|
||||||
|
fn: walkFn,
|
||||||
|
enqueuec: make(chan walkItem, numWorkers), // buffered for performance
|
||||||
|
workc: make(chan walkItem, numWorkers), // buffered for performance
|
||||||
|
donec: make(chan struct{}),
|
||||||
|
|
||||||
|
// buffered for correctness & not leaking goroutines:
|
||||||
|
resc: make(chan error, numWorkers),
|
||||||
|
}
|
||||||
|
defer close(w.donec)
|
||||||
|
|
||||||
|
for i := 0; i < numWorkers; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go w.doWork(&wg)
|
||||||
|
}
|
||||||
|
todo := []walkItem{{dir: root}}
|
||||||
|
out := 0
|
||||||
|
for {
|
||||||
|
workc := w.workc
|
||||||
|
var workItem walkItem
|
||||||
|
if len(todo) == 0 {
|
||||||
|
workc = nil
|
||||||
|
} else {
|
||||||
|
workItem = todo[len(todo)-1]
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case workc <- workItem:
|
||||||
|
todo = todo[:len(todo)-1]
|
||||||
|
out++
|
||||||
|
case it := <-w.enqueuec:
|
||||||
|
todo = append(todo, it)
|
||||||
|
case err := <-w.resc:
|
||||||
|
out--
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if out == 0 && len(todo) == 0 {
|
||||||
|
// It's safe to quit here, as long as the buffered
|
||||||
|
// enqueue channel isn't also readable, which might
|
||||||
|
// happen if the worker sends both another unit of
|
||||||
|
// work and its result before the other select was
|
||||||
|
// scheduled and both w.resc and w.enqueuec were
|
||||||
|
// readable.
|
||||||
|
select {
|
||||||
|
case it := <-w.enqueuec:
|
||||||
|
todo = append(todo, it)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// doWork reads directories as instructed (via workc) and runs the
|
||||||
|
// user's callback function.
|
||||||
|
func (w *walker) doWork(wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-w.donec:
|
||||||
|
return
|
||||||
|
case it := <-w.workc:
|
||||||
|
select {
|
||||||
|
case <-w.donec:
|
||||||
|
return
|
||||||
|
case w.resc <- w.walk(it.dir, !it.callbackDone):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type walker struct {
|
||||||
|
fn func(path string, typ os.FileMode) error
|
||||||
|
|
||||||
|
donec chan struct{} // closed on fastWalk's return
|
||||||
|
workc chan walkItem // to workers
|
||||||
|
enqueuec chan walkItem // from workers
|
||||||
|
resc chan error // from workers
|
||||||
|
}
|
||||||
|
|
||||||
|
type walkItem struct {
|
||||||
|
dir string
|
||||||
|
callbackDone bool // callback already called; don't do it again
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *walker) enqueue(it walkItem) {
|
||||||
|
select {
|
||||||
|
case w.enqueuec <- it:
|
||||||
|
case <-w.donec:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error {
|
||||||
|
joined := dirName + string(os.PathSeparator) + baseName
|
||||||
|
if typ == os.ModeDir {
|
||||||
|
w.enqueue(walkItem{dir: joined})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := w.fn(joined, typ)
|
||||||
|
if typ == os.ModeSymlink {
|
||||||
|
if err == TraverseLink {
|
||||||
|
// Set callbackDone so we don't call it twice for both the
|
||||||
|
// symlink-as-symlink and the symlink-as-directory later:
|
||||||
|
w.enqueue(walkItem{dir: joined, callbackDone: true})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err == filepath.SkipDir {
|
||||||
|
// Permit SkipDir on symlinks too.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *walker) walk(root string, runUserCallback bool) error {
|
||||||
|
if runUserCallback {
|
||||||
|
err := w.fn(root, os.ModeDir)
|
||||||
|
if err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return readDir(root, w.onDirEnt)
|
||||||
|
}
|
13
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go
generated
vendored
Normal file
13
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_fileno.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build freebsd openbsd netbsd
|
||||||
|
|
||||||
|
package fastwalk
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func direntInode(dirent *syscall.Dirent) uint64 {
|
||||||
|
return uint64(dirent.Fileno)
|
||||||
|
}
|
14
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go
generated
vendored
Normal file
14
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_ino.go
generated
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build linux darwin
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package fastwalk
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func direntInode(dirent *syscall.Dirent) uint64 {
|
||||||
|
return uint64(dirent.Ino)
|
||||||
|
}
|
13
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go
generated
vendored
Normal file
13
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_bsd.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
// +build darwin freebsd openbsd netbsd
|
||||||
|
|
||||||
|
package fastwalk
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func direntNamlen(dirent *syscall.Dirent) uint64 {
|
||||||
|
return uint64(dirent.Namlen)
|
||||||
|
}
|
24
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go
generated
vendored
Normal file
24
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_dirent_namlen_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2018 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.
|
||||||
|
|
||||||
|
// +build linux
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package fastwalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func direntNamlen(dirent *syscall.Dirent) uint64 {
|
||||||
|
const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name))
|
||||||
|
nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
|
||||||
|
nameLen := bytes.IndexByte(nameBuf[:dirent.Reclen-fixedHdr], 0)
|
||||||
|
if nameLen < 0 {
|
||||||
|
panic("failed to find terminating 0 byte in dirent")
|
||||||
|
}
|
||||||
|
return uint64(nameLen)
|
||||||
|
}
|
37
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go
generated
vendored
Normal file
37
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build appengine !linux,!darwin,!freebsd,!openbsd,!netbsd
|
||||||
|
|
||||||
|
package fastwalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readDir calls fn for each directory entry in dirName.
|
||||||
|
// It does not descend into directories or follow symlinks.
|
||||||
|
// If fn returns a non-nil error, readDir returns with that error
|
||||||
|
// immediately.
|
||||||
|
func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
|
||||||
|
fis, err := ioutil.ReadDir(dirName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
skipFiles := false
|
||||||
|
for _, fi := range fis {
|
||||||
|
if fi.Mode().IsRegular() && skipFiles {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil {
|
||||||
|
if err == SkipFiles {
|
||||||
|
skipFiles = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
127
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go
generated
vendored
Normal file
127
vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
// +build linux darwin freebsd openbsd netbsd
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
package fastwalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const blockSize = 8 << 10
|
||||||
|
|
||||||
|
// unknownFileMode is a sentinel (and bogus) os.FileMode
|
||||||
|
// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
|
||||||
|
const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
|
||||||
|
|
||||||
|
func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
|
||||||
|
fd, err := syscall.Open(dirName, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "open", Path: dirName, Err: err}
|
||||||
|
}
|
||||||
|
defer syscall.Close(fd)
|
||||||
|
|
||||||
|
// The buffer must be at least a block long.
|
||||||
|
buf := make([]byte, blockSize) // stack-allocated; doesn't escape
|
||||||
|
bufp := 0 // starting read position in buf
|
||||||
|
nbuf := 0 // end valid data in buf
|
||||||
|
skipFiles := false
|
||||||
|
for {
|
||||||
|
if bufp >= nbuf {
|
||||||
|
bufp = 0
|
||||||
|
nbuf, err = syscall.ReadDirent(fd, buf)
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("readdirent", err)
|
||||||
|
}
|
||||||
|
if nbuf <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
consumed, name, typ := parseDirEnt(buf[bufp:nbuf])
|
||||||
|
bufp += consumed
|
||||||
|
if name == "" || name == "." || name == ".." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Fallback for filesystems (like old XFS) that don't
|
||||||
|
// support Dirent.Type and have DT_UNKNOWN (0) there
|
||||||
|
// instead.
|
||||||
|
if typ == unknownFileMode {
|
||||||
|
fi, err := os.Lstat(dirName + "/" + name)
|
||||||
|
if err != nil {
|
||||||
|
// It got deleted in the meantime.
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
typ = fi.Mode() & os.ModeType
|
||||||
|
}
|
||||||
|
if skipFiles && typ.IsRegular() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := fn(dirName, name, typ); err != nil {
|
||||||
|
if err == SkipFiles {
|
||||||
|
skipFiles = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) {
|
||||||
|
// golang.org/issue/15653
|
||||||
|
dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
|
||||||
|
if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
|
||||||
|
panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
|
||||||
|
}
|
||||||
|
if len(buf) < int(dirent.Reclen) {
|
||||||
|
panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
|
||||||
|
}
|
||||||
|
consumed = int(dirent.Reclen)
|
||||||
|
if direntInode(dirent) == 0 { // File absent in directory.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch dirent.Type {
|
||||||
|
case syscall.DT_REG:
|
||||||
|
typ = 0
|
||||||
|
case syscall.DT_DIR:
|
||||||
|
typ = os.ModeDir
|
||||||
|
case syscall.DT_LNK:
|
||||||
|
typ = os.ModeSymlink
|
||||||
|
case syscall.DT_BLK:
|
||||||
|
typ = os.ModeDevice
|
||||||
|
case syscall.DT_FIFO:
|
||||||
|
typ = os.ModeNamedPipe
|
||||||
|
case syscall.DT_SOCK:
|
||||||
|
typ = os.ModeSocket
|
||||||
|
case syscall.DT_UNKNOWN:
|
||||||
|
typ = unknownFileMode
|
||||||
|
default:
|
||||||
|
// Skip weird things.
|
||||||
|
// It's probably a DT_WHT (http://lwn.net/Articles/325369/)
|
||||||
|
// or something. Revisit if/when this package is moved outside
|
||||||
|
// of goimports. goimports only cares about regular files,
|
||||||
|
// symlinks, and directories.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
|
||||||
|
nameLen := direntNamlen(dirent)
|
||||||
|
|
||||||
|
// Special cases for common things:
|
||||||
|
if nameLen == 1 && nameBuf[0] == '.' {
|
||||||
|
name = "."
|
||||||
|
} else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
|
||||||
|
name = ".."
|
||||||
|
} else {
|
||||||
|
name = string(nameBuf[:nameLen])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
246
vendor/golang.org/x/tools/internal/gopathwalk/walk.go
generated
vendored
Normal file
246
vendor/golang.org/x/tools/internal/gopathwalk/walk.go
generated
vendored
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
// Copyright 2018 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 gopathwalk is like filepath.Walk but specialized for finding Go
|
||||||
|
// packages, particularly in $GOPATH and $GOROOT.
|
||||||
|
package gopathwalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"golang.org/x/tools/internal/fastwalk"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options controls the behavior of a Walk call.
|
||||||
|
type Options struct {
|
||||||
|
Debug bool // Enable debug logging
|
||||||
|
ModulesEnabled bool // Search module caches. Also disables legacy goimports ignore rules.
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootType indicates the type of a Root.
|
||||||
|
type RootType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RootUnknown RootType = iota
|
||||||
|
RootGOROOT
|
||||||
|
RootGOPATH
|
||||||
|
RootCurrentModule
|
||||||
|
RootModuleCache
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Root is a starting point for a Walk.
|
||||||
|
type Root struct {
|
||||||
|
Path string
|
||||||
|
Type RootType
|
||||||
|
}
|
||||||
|
|
||||||
|
// SrcDirsRoots returns the roots from build.Default.SrcDirs(). Not modules-compatible.
|
||||||
|
func SrcDirsRoots() []Root {
|
||||||
|
var roots []Root
|
||||||
|
roots = append(roots, Root{filepath.Join(build.Default.GOROOT, "src"), RootGOROOT})
|
||||||
|
for _, p := range filepath.SplitList(build.Default.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.
|
||||||
|
// add will be called concurrently.
|
||||||
|
func Walk(roots []Root, add func(root Root, dir string), opts Options) {
|
||||||
|
for _, root := range roots {
|
||||||
|
walkDir(root, add, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkDir(root Root, add func(Root, string), opts Options) {
|
||||||
|
if _, err := os.Stat(root.Path); os.IsNotExist(err) {
|
||||||
|
if opts.Debug {
|
||||||
|
log.Printf("skipping nonexistant directory: %v", root.Path)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if opts.Debug {
|
||||||
|
log.Printf("scanning %s", root.Path)
|
||||||
|
}
|
||||||
|
w := &walker{
|
||||||
|
root: root,
|
||||||
|
add: add,
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
w.init()
|
||||||
|
if err := fastwalk.Walk(root.Path, w.walk); err != nil {
|
||||||
|
log.Printf("gopathwalk: scanning directory %v: %v", root.Path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Debug {
|
||||||
|
log.Printf("scanned %s", root.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walker is the callback for fastwalk.Walk.
|
||||||
|
type walker struct {
|
||||||
|
root Root // The source directory to scan.
|
||||||
|
add func(Root, string) // The callback that will be invoked for every possible Go package dir.
|
||||||
|
opts Options // Options passed to Walk by the user.
|
||||||
|
|
||||||
|
ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files.
|
||||||
|
}
|
||||||
|
|
||||||
|
// init initializes the walker based on its Options.
|
||||||
|
func (w *walker) init() {
|
||||||
|
var ignoredPaths []string
|
||||||
|
if w.root.Type == RootModuleCache {
|
||||||
|
ignoredPaths = []string{"cache"}
|
||||||
|
}
|
||||||
|
if !w.opts.ModulesEnabled && w.root.Type == RootGOPATH {
|
||||||
|
ignoredPaths = w.getIgnoredDirs(w.root.Path)
|
||||||
|
ignoredPaths = append(ignoredPaths, "v", "mod")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range ignoredPaths {
|
||||||
|
full := filepath.Join(w.root.Path, p)
|
||||||
|
if fi, err := os.Stat(full); err == nil {
|
||||||
|
w.ignoredDirs = append(w.ignoredDirs, fi)
|
||||||
|
if w.opts.Debug {
|
||||||
|
log.Printf("Directory added to ignore list: %s", full)
|
||||||
|
}
|
||||||
|
} else if w.opts.Debug {
|
||||||
|
log.Printf("Error statting ignored directory: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getIgnoredDirs reads an optional config file at <path>/.goimportsignore
|
||||||
|
// of relative directories to ignore when scanning for go files.
|
||||||
|
// The provided path is one of the $GOPATH entries with "src" appended.
|
||||||
|
func (w *walker) getIgnoredDirs(path string) []string {
|
||||||
|
file := filepath.Join(path, ".goimportsignore")
|
||||||
|
slurp, err := ioutil.ReadFile(file)
|
||||||
|
if w.opts.Debug {
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Read %s", file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ignoredDirs []string
|
||||||
|
bs := bufio.NewScanner(bytes.NewReader(slurp))
|
||||||
|
for bs.Scan() {
|
||||||
|
line := strings.TrimSpace(bs.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ignoredDirs = append(ignoredDirs, line)
|
||||||
|
}
|
||||||
|
return ignoredDirs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *walker) shouldSkipDir(fi os.FileInfo) bool {
|
||||||
|
for _, ignoredDir := range w.ignoredDirs {
|
||||||
|
if os.SameFile(fi, ignoredDir) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *walker) walk(path string, typ os.FileMode) error {
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
if typ.IsRegular() {
|
||||||
|
if dir == w.root.Path {
|
||||||
|
// Doesn't make sense to have regular files
|
||||||
|
// directly in your $GOPATH/src or $GOROOT/src.
|
||||||
|
return fastwalk.SkipFiles
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(path, ".go") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.add(w.root, dir)
|
||||||
|
return fastwalk.SkipFiles
|
||||||
|
}
|
||||||
|
if typ == os.ModeDir {
|
||||||
|
base := filepath.Base(path)
|
||||||
|
if base == "" || base[0] == '.' || base[0] == '_' ||
|
||||||
|
base == "testdata" || (!w.opts.ModulesEnabled && base == "node_modules") {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
if err == nil && w.shouldSkipDir(fi) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if typ == os.ModeSymlink {
|
||||||
|
base := filepath.Base(path)
|
||||||
|
if strings.HasPrefix(base, ".#") {
|
||||||
|
// Emacs noise.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
// Just ignore it.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if w.shouldTraverse(dir, fi) {
|
||||||
|
return fastwalk.TraverseLink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldTraverse reports whether the symlink fi, found in dir,
|
||||||
|
// should be followed. It makes sure symlinks were never visited
|
||||||
|
// before to avoid symlink loops.
|
||||||
|
func (w *walker) shouldTraverse(dir string, fi os.FileInfo) bool {
|
||||||
|
path := filepath.Join(dir, fi.Name())
|
||||||
|
target, err := filepath.EvalSymlinks(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ts, err := os.Stat(target)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !ts.IsDir() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if w.shouldSkipDir(ts) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Check for symlink loops by statting each directory component
|
||||||
|
// and seeing if any are the same file as ts.
|
||||||
|
for {
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
if parent == path {
|
||||||
|
// Made it to the root without seeing a cycle.
|
||||||
|
// Use this symlink.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
parentInfo, err := os.Stat(parent)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if os.SameFile(ts, parentInfo) {
|
||||||
|
// Cycle. Don't traverse.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
path = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
388
vendor/golang.org/x/tools/internal/semver/semver.go
generated
vendored
Normal file
388
vendor/golang.org/x/tools/internal/semver/semver.go
generated
vendored
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
// Copyright 2018 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 semver implements comparison of semantic version strings.
|
||||||
|
// In this package, semantic version strings must begin with a leading "v",
|
||||||
|
// as in "v1.0.0".
|
||||||
|
//
|
||||||
|
// The general form of a semantic version string accepted by this package is
|
||||||
|
//
|
||||||
|
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
|
||||||
|
//
|
||||||
|
// where square brackets indicate optional parts of the syntax;
|
||||||
|
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
|
||||||
|
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
|
||||||
|
// using only alphanumeric characters and hyphens; and
|
||||||
|
// all-numeric PRERELEASE identifiers must not have leading zeros.
|
||||||
|
//
|
||||||
|
// This package follows Semantic Versioning 2.0.0 (see semver.org)
|
||||||
|
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
|
||||||
|
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
|
||||||
|
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
|
||||||
|
package semver
|
||||||
|
|
||||||
|
// parsed returns the parsed form of a semantic version string.
|
||||||
|
type parsed struct {
|
||||||
|
major string
|
||||||
|
minor string
|
||||||
|
patch string
|
||||||
|
short string
|
||||||
|
prerelease string
|
||||||
|
build string
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid reports whether v is a valid semantic version string.
|
||||||
|
func IsValid(v string) bool {
|
||||||
|
_, ok := parse(v)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonical returns the canonical formatting of the semantic version v.
|
||||||
|
// It fills in any missing .MINOR or .PATCH and discards build metadata.
|
||||||
|
// Two semantic versions compare equal only if their canonical formattings
|
||||||
|
// are identical strings.
|
||||||
|
// The canonical invalid semantic version is the empty string.
|
||||||
|
func Canonical(v string) string {
|
||||||
|
p, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if p.build != "" {
|
||||||
|
return v[:len(v)-len(p.build)]
|
||||||
|
}
|
||||||
|
if p.short != "" {
|
||||||
|
return v + p.short
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Major returns the major version prefix of the semantic version v.
|
||||||
|
// For example, Major("v2.1.0") == "v2".
|
||||||
|
// If v is an invalid semantic version string, Major returns the empty string.
|
||||||
|
func Major(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v[:1+len(pv.major)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MajorMinor returns the major.minor version prefix of the semantic version v.
|
||||||
|
// For example, MajorMinor("v2.1.0") == "v2.1".
|
||||||
|
// If v is an invalid semantic version string, MajorMinor returns the empty string.
|
||||||
|
func MajorMinor(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
i := 1 + len(pv.major)
|
||||||
|
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
|
||||||
|
return v[:j]
|
||||||
|
}
|
||||||
|
return v[:i] + "." + pv.minor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prerelease returns the prerelease suffix of the semantic version v.
|
||||||
|
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
|
||||||
|
// If v is an invalid semantic version string, Prerelease returns the empty string.
|
||||||
|
func Prerelease(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pv.prerelease
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build returns the build suffix of the semantic version v.
|
||||||
|
// For example, Build("v2.1.0+meta") == "+meta".
|
||||||
|
// If v is an invalid semantic version string, Build returns the empty string.
|
||||||
|
func Build(v string) string {
|
||||||
|
pv, ok := parse(v)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pv.build
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare returns an integer comparing two versions according to
|
||||||
|
// according to semantic version precedence.
|
||||||
|
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
|
||||||
|
//
|
||||||
|
// An invalid semantic version string is considered less than a valid one.
|
||||||
|
// All invalid semantic version strings compare equal to each other.
|
||||||
|
func Compare(v, w string) int {
|
||||||
|
pv, ok1 := parse(v)
|
||||||
|
pw, ok2 := parse(w)
|
||||||
|
if !ok1 && !ok2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if !ok1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if !ok2 {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.major, pw.major); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.minor, pw.minor); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if c := compareInt(pv.patch, pw.patch); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return comparePrerelease(pv.prerelease, pw.prerelease)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max canonicalizes its arguments and then returns the version string
|
||||||
|
// that compares greater.
|
||||||
|
func Max(v, w string) string {
|
||||||
|
v = Canonical(v)
|
||||||
|
w = Canonical(w)
|
||||||
|
if Compare(v, w) > 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(v string) (p parsed, ok bool) {
|
||||||
|
if v == "" || v[0] != 'v' {
|
||||||
|
p.err = "missing v prefix"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.major, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad major version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
|
p.minor = "0"
|
||||||
|
p.patch = "0"
|
||||||
|
p.short = ".0.0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != '.' {
|
||||||
|
p.err = "bad minor prefix"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.minor, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad minor version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v == "" {
|
||||||
|
p.patch = "0"
|
||||||
|
p.short = ".0"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] != '.' {
|
||||||
|
p.err = "bad patch prefix"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.patch, v, ok = parseInt(v[1:])
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad patch version"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(v) > 0 && v[0] == '-' {
|
||||||
|
p.prerelease, v, ok = parsePrerelease(v)
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad prerelease"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(v) > 0 && v[0] == '+' {
|
||||||
|
p.build, v, ok = parseBuild(v)
|
||||||
|
if !ok {
|
||||||
|
p.err = "bad build"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v != "" {
|
||||||
|
p.err = "junk on end"
|
||||||
|
ok = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(v string) (t, rest string, ok bool) {
|
||||||
|
if v == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[0] < '0' || '9' < v[0] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if v[0] == '0' && i != 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrerelease(v string) (t, rest string, ok bool) {
|
||||||
|
// "A pre-release version MAY be denoted by appending a hyphen and
|
||||||
|
// a series of dot separated identifiers immediately following the patch version.
|
||||||
|
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||||
|
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
|
||||||
|
if v == "" || v[0] != '-' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
start := 1
|
||||||
|
for i < len(v) && v[i] != '+' {
|
||||||
|
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[i] == '.' {
|
||||||
|
if start == i || isBadNum(v[start:i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if start == i || isBadNum(v[start:i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBuild(v string) (t, rest string, ok bool) {
|
||||||
|
if v == "" || v[0] != '+' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
start := 1
|
||||||
|
for i < len(v) {
|
||||||
|
if !isIdentChar(v[i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v[i] == '.' {
|
||||||
|
if start == i {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if start == i {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return v[:i], v[i:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIdentChar(c byte) bool {
|
||||||
|
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBadNum(v string) bool {
|
||||||
|
i := 0
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i == len(v) && i > 1 && v[0] == '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNum(v string) bool {
|
||||||
|
i := 0
|
||||||
|
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i == len(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareInt(x, y string) int {
|
||||||
|
if x == y {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if len(x) < len(y) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if len(x) > len(y) {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if x < y {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func comparePrerelease(x, y string) int {
|
||||||
|
// "When major, minor, and patch are equal, a pre-release version has
|
||||||
|
// lower precedence than a normal version.
|
||||||
|
// Example: 1.0.0-alpha < 1.0.0.
|
||||||
|
// Precedence for two pre-release versions with the same major, minor,
|
||||||
|
// and patch version MUST be determined by comparing each dot separated
|
||||||
|
// identifier from left to right until a difference is found as follows:
|
||||||
|
// identifiers consisting of only digits are compared numerically and
|
||||||
|
// identifiers with letters or hyphens are compared lexically in ASCII
|
||||||
|
// sort order. Numeric identifiers always have lower precedence than
|
||||||
|
// non-numeric identifiers. A larger set of pre-release fields has a
|
||||||
|
// higher precedence than a smaller set, if all of the preceding
|
||||||
|
// identifiers are equal.
|
||||||
|
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
|
||||||
|
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
|
||||||
|
if x == y {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if x == "" {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
if y == "" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
for x != "" && y != "" {
|
||||||
|
x = x[1:] // skip - or .
|
||||||
|
y = y[1:] // skip - or .
|
||||||
|
var dx, dy string
|
||||||
|
dx, x = nextIdent(x)
|
||||||
|
dy, y = nextIdent(y)
|
||||||
|
if dx != dy {
|
||||||
|
ix := isNum(dx)
|
||||||
|
iy := isNum(dy)
|
||||||
|
if ix != iy {
|
||||||
|
if ix {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ix {
|
||||||
|
if len(dx) < len(dy) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if len(dx) > len(dy) {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dx < dy {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x == "" {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextIdent(x string) (dx, rest string) {
|
||||||
|
i := 0
|
||||||
|
for i < len(x) && x[i] != '.' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return x[:i], x[i:]
|
||||||
|
}
|
48
vendor/honnef.co/go/tools/arg/arg.go
vendored
Normal file
48
vendor/honnef.co/go/tools/arg/arg.go
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package arg
|
||||||
|
|
||||||
|
var args = map[string]int{
|
||||||
|
"(*encoding/json.Decoder).Decode.v": 0,
|
||||||
|
"(*encoding/json.Encoder).Encode.v": 0,
|
||||||
|
"(*encoding/xml.Decoder).Decode.v": 0,
|
||||||
|
"(*encoding/xml.Encoder).Encode.v": 0,
|
||||||
|
"(*sync.Pool).Put.x": 0,
|
||||||
|
"(*text/template.Template).Parse.text": 0,
|
||||||
|
"(io.Seeker).Seek.offset": 0,
|
||||||
|
"(time.Time).Sub.u": 0,
|
||||||
|
"append.elems": 1,
|
||||||
|
"append.slice": 0,
|
||||||
|
"bytes.Equal.a": 0,
|
||||||
|
"bytes.Equal.b": 1,
|
||||||
|
"encoding/binary.Write.data": 2,
|
||||||
|
"errors.New.text": 0,
|
||||||
|
"fmt.Fprintf.format": 1,
|
||||||
|
"fmt.Printf.format": 0,
|
||||||
|
"fmt.Sprintf.a[0]": 1,
|
||||||
|
"fmt.Sprintf.format": 0,
|
||||||
|
"json.Marshal.v": 0,
|
||||||
|
"json.Unmarshal.v": 1,
|
||||||
|
"len.v": 0,
|
||||||
|
"make.size[0]": 1,
|
||||||
|
"make.size[1]": 2,
|
||||||
|
"make.t": 0,
|
||||||
|
"net/url.Parse.rawurl": 0,
|
||||||
|
"os.OpenFile.flag": 1,
|
||||||
|
"os/exec.Command.name": 0,
|
||||||
|
"os/signal.Notify.c": 0,
|
||||||
|
"regexp.Compile.expr": 0,
|
||||||
|
"runtime.SetFinalizer.finalizer": 1,
|
||||||
|
"runtime.SetFinalizer.obj": 0,
|
||||||
|
"sort.Sort.data": 0,
|
||||||
|
"time.Parse.layout": 0,
|
||||||
|
"time.Sleep.d": 0,
|
||||||
|
"xml.Marshal.v": 0,
|
||||||
|
"xml.Unmarshal.v": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Arg(name string) int {
|
||||||
|
n, ok := args[name]
|
||||||
|
if !ok {
|
||||||
|
panic("unknown argument " + name)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
15
vendor/honnef.co/go/tools/cmd/gosimple/README.md
vendored
15
vendor/honnef.co/go/tools/cmd/gosimple/README.md
vendored
|
@ -1,15 +0,0 @@
|
||||||
# gosimple
|
|
||||||
|
|
||||||
_gosimple_ is a linter for Go source code that specialises on
|
|
||||||
simplifying code.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Gosimple requires Go 1.6 or later.
|
|
||||||
|
|
||||||
go get honnef.co/go/tools/cmd/gosimple
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
Detailed documentation can be found on
|
|
||||||
[staticcheck.io](https://staticcheck.io/docs/gosimple).
|
|
|
@ -1,21 +0,0 @@
|
||||||
// gosimple detects code that could be rewritten in a simpler way.
|
|
||||||
package main // import "honnef.co/go/tools/cmd/gosimple"
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"honnef.co/go/tools/lint/lintutil"
|
|
||||||
"honnef.co/go/tools/simple"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fs := lintutil.FlagSet("gosimple")
|
|
||||||
gen := fs.Bool("generated", false, "Check generated code")
|
|
||||||
fs.Parse(os.Args[1:])
|
|
||||||
c := simple.NewChecker()
|
|
||||||
c.CheckGenerated = *gen
|
|
||||||
cfg := lintutil.CheckerConfig{
|
|
||||||
Checker: c,
|
|
||||||
ExitNonZero: true,
|
|
||||||
}
|
|
||||||
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
|
||||||
}
|
|
|
@ -1,16 +1,15 @@
|
||||||
# staticcheck
|
# staticcheck
|
||||||
|
|
||||||
_staticcheck_ is `go vet` on steroids, applying a ton of static analysis
|
_staticcheck_ offers extensive analysis of Go code, covering a myriad
|
||||||
checks you might be used to from tools like ReSharper for C#.
|
of categories. It will detect bugs, suggest code simplifications,
|
||||||
|
point out dead code, and more.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Staticcheck requires Go 1.6 or later.
|
See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions.
|
||||||
|
|
||||||
go get honnef.co/go/tools/cmd/staticcheck
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Detailed documentation can be found on
|
Detailed documentation can be found on
|
||||||
[staticcheck.io](https://staticcheck.io/docs/staticcheck).
|
[staticcheck.io](https://staticcheck.io/docs/).
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
// staticcheck detects a myriad of bugs and inefficiencies in your
|
// staticcheck analyses Go code and makes it better.
|
||||||
// code.
|
|
||||||
package main // import "honnef.co/go/tools/cmd/staticcheck"
|
package main // import "honnef.co/go/tools/cmd/staticcheck"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint"
|
||||||
"honnef.co/go/tools/lint/lintutil"
|
"honnef.co/go/tools/lint/lintutil"
|
||||||
|
"honnef.co/go/tools/simple"
|
||||||
"honnef.co/go/tools/staticcheck"
|
"honnef.co/go/tools/staticcheck"
|
||||||
|
"honnef.co/go/tools/stylecheck"
|
||||||
|
"honnef.co/go/tools/unused"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fs := lintutil.FlagSet("staticcheck")
|
fs := lintutil.FlagSet("staticcheck")
|
||||||
gen := fs.Bool("generated", false, "Check generated code")
|
|
||||||
fs.Parse(os.Args[1:])
|
fs.Parse(os.Args[1:])
|
||||||
c := staticcheck.NewChecker()
|
|
||||||
c.CheckGenerated = *gen
|
checkers := []lint.Checker{
|
||||||
cfg := lintutil.CheckerConfig{
|
simple.NewChecker(),
|
||||||
Checker: c,
|
staticcheck.NewChecker(),
|
||||||
ExitNonZero: true,
|
stylecheck.NewChecker(),
|
||||||
}
|
}
|
||||||
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
|
||||||
|
uc := unused.NewChecker(unused.CheckAll)
|
||||||
|
uc.ConsiderReflection = true
|
||||||
|
checkers = append(checkers, unused.NewLintChecker(uc))
|
||||||
|
|
||||||
|
lintutil.ProcessFlagSet(checkers, fs)
|
||||||
}
|
}
|
||||||
|
|
131
vendor/honnef.co/go/tools/cmd/unused/README.md
vendored
131
vendor/honnef.co/go/tools/cmd/unused/README.md
vendored
|
@ -1,131 +0,0 @@
|
||||||
# unused
|
|
||||||
|
|
||||||
_unused_ checks Go code for unused constants, variables, functions and
|
|
||||||
types.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
go get honnef.co/go/tools/cmd/unused
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
unused -help
|
|
||||||
|
|
||||||
## Usage Tips
|
|
||||||
|
|
||||||
- When running _unused_ on multiple packages, it will first try to
|
|
||||||
check them all at once, because that's faster. If any of the
|
|
||||||
packages don't compile, however, _unused_ will check each package
|
|
||||||
individually.
|
|
||||||
|
|
||||||
The first step can, depending on the number of packages, use a lot
|
|
||||||
of memory. For the entire standard library, it uses roughly 800 MB.
|
|
||||||
For a GOPATH with thousands of packages, it can quickly use several
|
|
||||||
gigabytes. If that is an issue, consider using something like this
|
|
||||||
instead:
|
|
||||||
|
|
||||||
```
|
|
||||||
for pkg in $(go list your_selection); do unused "$pkg"; done
|
|
||||||
```
|
|
||||||
|
|
||||||
This will effectively skip the first step and always check every
|
|
||||||
package individually.
|
|
||||||
|
|
||||||
## What counts as used/unused?
|
|
||||||
|
|
||||||
_unused_ checks for unused constants, functions, types and optionally
|
|
||||||
struct fields. They will be considered used or unused under the
|
|
||||||
following conditions:
|
|
||||||
|
|
||||||
- Unexported package-level objects will be reported as unused if there
|
|
||||||
are no explicit references to them.
|
|
||||||
|
|
||||||
- Unexported methods will be reported as unused if there are no
|
|
||||||
explicit references to them and if they don't implement any
|
|
||||||
interfaces.
|
|
||||||
|
|
||||||
- The `main` function is considered as used if it's in the `main`
|
|
||||||
package.
|
|
||||||
|
|
||||||
- `init` functions are always considered as used.
|
|
||||||
|
|
||||||
- Exported objects in function scope are treated like unexported
|
|
||||||
objects.
|
|
||||||
|
|
||||||
- Exported functions in tests are treated like unexported functions,
|
|
||||||
unless they're test, benchmark or example functions.
|
|
||||||
|
|
||||||
- Struct fields will be considered as unused if there are no explicit
|
|
||||||
references to them. Unkeyed composite literals with >=1 elements
|
|
||||||
mark all fields of the struct as used.
|
|
||||||
|
|
||||||
- Neither the checks for methods nor for struct fields are aware of
|
|
||||||
the reflect package and may thus produce false positives.
|
|
||||||
|
|
||||||
## Whole program analysis
|
|
||||||
|
|
||||||
Optionally via the `-exported` flag, _unused_ can analyse all
|
|
||||||
arguments as a single program and report unused exported identifiers.
|
|
||||||
This can be useful for checking "internal" packages, or large software
|
|
||||||
projects that do not export an API to the public, but use exported
|
|
||||||
methods between components.
|
|
||||||
|
|
||||||
Do note that in the whole-program analysis, all arguments must
|
|
||||||
type-check. It is not possible to check packages individually in this
|
|
||||||
mode.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```
|
|
||||||
$ time unused cmd/go
|
|
||||||
/usr/lib/go/src/cmd/go/build.go:1327:6: func hasString is unused
|
|
||||||
/usr/lib/go/src/cmd/go/build.go:2328:6: func toolVerify is unused
|
|
||||||
/usr/lib/go/src/cmd/go/generate.go:375:21: func identLength is unused
|
|
||||||
/usr/lib/go/src/cmd/go/get.go:474:5: var goTag is unused
|
|
||||||
/usr/lib/go/src/cmd/go/get.go:513:6: func cmpGoVersion is unused
|
|
||||||
/usr/lib/go/src/cmd/go/go_test.go:426:23: func grepCountStdout is unused
|
|
||||||
/usr/lib/go/src/cmd/go/go_test.go:432:23: func grepCountStderr is unused
|
|
||||||
/usr/lib/go/src/cmd/go/main.go:406:5: var logf is unused
|
|
||||||
/usr/lib/go/src/cmd/go/main.go:431:6: func runOut is unused
|
|
||||||
/usr/lib/go/src/cmd/go/pkg.go:91:2: field forceBuild is unused
|
|
||||||
/usr/lib/go/src/cmd/go/pkg.go:688:2: const toRoot is unused
|
|
||||||
/usr/lib/go/src/cmd/go/testflag.go:278:6: func setIntFlag is unused
|
|
||||||
unused cmd/go 3.33s user 0.25s system 447% cpu 0.799 total
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
$ time unused $(go list github.com/prometheus/prometheus/... | grep -v /vendor/)
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/promql/engine_test.go:11:5: var noop is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/dns.go:39:2: const interval is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/dns.go:69:2: field m is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/nerve.go:31:2: const nerveNodePrefix is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/discovery/serverset.go:33:2: const serversetNodePrefix is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/scrape.go:41:2: const ingestedSamplesCap is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/scrape.go:49:2: var errSkippedScrape is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/retrieval/targetmanager.go:184:2: field providers is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/delta.go:394:2: field error is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/delta.go:398:3: field error is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/doubledelta.go:500:2: field error is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/local/doubledelta.go:504:3: field error is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/storage/remote/opentsdb/client.go:40:2: var illegalCharsRE is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/util/stats/timer.go:56:2: field child is unused
|
|
||||||
/home/dominikh/prj/src/github.com/prometheus/prometheus/util/treecache/treecache.go:25:2: field zkEvents is unused
|
|
||||||
unused $(go list github.com/prometheus/prometheus/... | grep -v /vendor/) 5.70s user 0.43s system 535% cpu 1.142 total
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
$ time unused -exported github.com/kr/pretty/...
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/formatter.go:14:2: const limit is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/formatter.go:322:6: func tryDeepEqual is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:20:6: func Errorf is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:28:6: func Fprintf is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:37:6: func Log is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:45:6: func Logf is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:54:6: func Logln is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:63:6: func Print is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:71:6: func Printf is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:80:6: func Println is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:88:6: func Sprintf is unused
|
|
||||||
/home/dominikh/prj/src/github.com/kr/pretty/pretty.go:92:6: func wrap is unused
|
|
||||||
unused -exported github.com/kr/pretty/... 1.23s user 0.19s system 253% cpu 0.558 total
|
|
||||||
```
|
|
78
vendor/honnef.co/go/tools/cmd/unused/main.go
vendored
78
vendor/honnef.co/go/tools/cmd/unused/main.go
vendored
|
@ -1,78 +0,0 @@
|
||||||
// unused reports unused identifiers (types, functions, ...) in your
|
|
||||||
// code.
|
|
||||||
package main // import "honnef.co/go/tools/cmd/unused"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"honnef.co/go/tools/lint/lintutil"
|
|
||||||
"honnef.co/go/tools/unused"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
fConstants bool
|
|
||||||
fFields bool
|
|
||||||
fFunctions bool
|
|
||||||
fTypes bool
|
|
||||||
fVariables bool
|
|
||||||
fDebug string
|
|
||||||
fWholeProgram bool
|
|
||||||
fReflection bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func newChecker(mode unused.CheckMode) *unused.Checker {
|
|
||||||
checker := unused.NewChecker(mode)
|
|
||||||
|
|
||||||
if fDebug != "" {
|
|
||||||
debug, err := os.Create(fDebug)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("couldn't open debug file:", err)
|
|
||||||
}
|
|
||||||
checker.Debug = debug
|
|
||||||
}
|
|
||||||
|
|
||||||
checker.WholeProgram = fWholeProgram
|
|
||||||
checker.ConsiderReflection = fReflection
|
|
||||||
return checker
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
fs := lintutil.FlagSet("unused")
|
|
||||||
fs.BoolVar(&fConstants, "consts", true, "Report unused constants")
|
|
||||||
fs.BoolVar(&fFields, "fields", true, "Report unused fields")
|
|
||||||
fs.BoolVar(&fFunctions, "funcs", true, "Report unused functions and methods")
|
|
||||||
fs.BoolVar(&fTypes, "types", true, "Report unused types")
|
|
||||||
fs.BoolVar(&fVariables, "vars", true, "Report unused variables")
|
|
||||||
fs.StringVar(&fDebug, "debug", "", "Write a debug graph to `file`. Existing files will be overwritten.")
|
|
||||||
fs.BoolVar(&fWholeProgram, "exported", false, "Treat arguments as a program and report unused exported identifiers")
|
|
||||||
fs.BoolVar(&fReflection, "reflect", true, "Consider identifiers as used when it's likely they'll be accessed via reflection")
|
|
||||||
fs.Parse(os.Args[1:])
|
|
||||||
|
|
||||||
var mode unused.CheckMode
|
|
||||||
if fConstants {
|
|
||||||
mode |= unused.CheckConstants
|
|
||||||
}
|
|
||||||
if fFields {
|
|
||||||
mode |= unused.CheckFields
|
|
||||||
}
|
|
||||||
if fFunctions {
|
|
||||||
mode |= unused.CheckFunctions
|
|
||||||
}
|
|
||||||
if fTypes {
|
|
||||||
mode |= unused.CheckTypes
|
|
||||||
}
|
|
||||||
if fVariables {
|
|
||||||
mode |= unused.CheckVariables
|
|
||||||
}
|
|
||||||
|
|
||||||
checker := newChecker(mode)
|
|
||||||
l := unused.NewLintChecker(checker)
|
|
||||||
cfg := lintutil.CheckerConfig{
|
|
||||||
Checker: l,
|
|
||||||
ExitNonZero: true,
|
|
||||||
}
|
|
||||||
lintutil.ProcessFlagSet([]lintutil.CheckerConfig{cfg}, fs)
|
|
||||||
}
|
|
162
vendor/honnef.co/go/tools/config/config.go
vendored
Normal file
162
vendor/honnef.co/go/tools/config/config.go
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mergeLists(a, b []string) []string {
|
||||||
|
out := make([]string, 0, len(a)+len(b))
|
||||||
|
for _, el := range b {
|
||||||
|
if el == "inherit" {
|
||||||
|
out = append(out, a...)
|
||||||
|
} else {
|
||||||
|
out = append(out, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeList(list []string) []string {
|
||||||
|
if len(list) > 1 {
|
||||||
|
nlist := make([]string, 0, len(list))
|
||||||
|
nlist = append(nlist, list[0])
|
||||||
|
for i, el := range list[1:] {
|
||||||
|
if el != list[i] {
|
||||||
|
nlist = append(nlist, el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list = nlist
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, el := range list {
|
||||||
|
if el == "inherit" {
|
||||||
|
// This should never happen, because the default config
|
||||||
|
// should not use "inherit"
|
||||||
|
panic(`unresolved "inherit"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) Merge(ocfg Config) Config {
|
||||||
|
if ocfg.Checks != nil {
|
||||||
|
cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks)
|
||||||
|
}
|
||||||
|
if ocfg.Initialisms != nil {
|
||||||
|
cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms)
|
||||||
|
}
|
||||||
|
if ocfg.DotImportWhitelist != nil {
|
||||||
|
cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist)
|
||||||
|
}
|
||||||
|
if ocfg.HTTPStatusCodeWhitelist != nil {
|
||||||
|
cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist)
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
// TODO(dh): this implementation makes it impossible for external
|
||||||
|
// clients to add their own checkers with configuration. At the
|
||||||
|
// moment, we don't really care about that; we don't encourage
|
||||||
|
// that people use this package. In the future, we may. The
|
||||||
|
// obvious solution would be using map[string]interface{}, but
|
||||||
|
// that's obviously subpar.
|
||||||
|
|
||||||
|
Checks []string `toml:"checks"`
|
||||||
|
Initialisms []string `toml:"initialisms"`
|
||||||
|
DotImportWhitelist []string `toml:"dot_import_whitelist"`
|
||||||
|
HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultConfig = Config{
|
||||||
|
Checks: []string{"all", "-ST1000", "-ST1003", "-ST1016"},
|
||||||
|
Initialisms: []string{
|
||||||
|
"ACL", "API", "ASCII", "CPU", "CSS", "DNS",
|
||||||
|
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
|
||||||
|
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
|
||||||
|
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
|
||||||
|
"UDP", "UI", "GID", "UID", "UUID", "URI",
|
||||||
|
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
|
||||||
|
"XSS",
|
||||||
|
},
|
||||||
|
DotImportWhitelist: []string{},
|
||||||
|
HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"},
|
||||||
|
}
|
||||||
|
|
||||||
|
const configName = "staticcheck.conf"
|
||||||
|
|
||||||
|
func parseConfigs(dir string) ([]Config, error) {
|
||||||
|
var out []Config
|
||||||
|
|
||||||
|
// TODO(dh): consider stopping at the GOPATH/module boundary
|
||||||
|
for dir != "" {
|
||||||
|
f, err := os.Open(filepath.Join(dir, configName))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
ndir := filepath.Dir(dir)
|
||||||
|
if ndir == dir {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dir = ndir
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var cfg Config
|
||||||
|
_, err = toml.DecodeReader(f, &cfg)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, cfg)
|
||||||
|
ndir := filepath.Dir(dir)
|
||||||
|
if ndir == dir {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dir = ndir
|
||||||
|
}
|
||||||
|
out = append(out, defaultConfig)
|
||||||
|
if len(out) < 2 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
for i := 0; i < len(out)/2; i++ {
|
||||||
|
out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i]
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeConfigs(confs []Config) Config {
|
||||||
|
if len(confs) == 0 {
|
||||||
|
// This shouldn't happen because we always have at least a
|
||||||
|
// default config.
|
||||||
|
panic("trying to merge zero configs")
|
||||||
|
}
|
||||||
|
if len(confs) == 1 {
|
||||||
|
return confs[0]
|
||||||
|
}
|
||||||
|
conf := confs[0]
|
||||||
|
for _, oconf := range confs[1:] {
|
||||||
|
conf = conf.Merge(oconf)
|
||||||
|
}
|
||||||
|
return conf
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(dir string) (Config, error) {
|
||||||
|
confs, err := parseConfigs(dir)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
conf := mergeConfigs(confs)
|
||||||
|
|
||||||
|
conf.Checks = normalizeList(conf.Checks)
|
||||||
|
conf.Initialisms = normalizeList(conf.Initialisms)
|
||||||
|
conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist)
|
||||||
|
conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist)
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
|
}
|
10
vendor/honnef.co/go/tools/config/example.conf
vendored
Normal file
10
vendor/honnef.co/go/tools/config/example.conf
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
checks = ["all", "-ST1003", "-ST1014"]
|
||||||
|
initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS",
|
||||||
|
"EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID",
|
||||||
|
"IP", "JSON", "QPS", "RAM", "RPC", "SLA",
|
||||||
|
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL",
|
||||||
|
"UDP", "UI", "GID", "UID", "UUID", "URI",
|
||||||
|
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF",
|
||||||
|
"XSS"]
|
||||||
|
dot_import_whitelist = []
|
||||||
|
http_status_code_whitelist = ["200", "400", "404", "500"]
|
38
vendor/honnef.co/go/tools/lint/generated.go
vendored
Normal file
38
vendor/honnef.co/go/tools/lint/generated.go
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package lint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// used by cgo before Go 1.11
|
||||||
|
oldCgo = []byte("// Created by cgo - DO NOT EDIT")
|
||||||
|
prefix = []byte("// Code generated ")
|
||||||
|
suffix = []byte(" DO NOT EDIT.")
|
||||||
|
nl = []byte("\n")
|
||||||
|
crnl = []byte("\r\n")
|
||||||
|
)
|
||||||
|
|
||||||
|
func isGenerated(r io.Reader) bool {
|
||||||
|
br := bufio.NewReader(r)
|
||||||
|
for {
|
||||||
|
s, err := br.ReadBytes('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s = bytes.TrimSuffix(s, crnl)
|
||||||
|
s = bytes.TrimSuffix(s, nl)
|
||||||
|
if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if bytes.Equal(s, oldCgo) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
507
vendor/honnef.co/go/tools/lint/lint.go
vendored
507
vendor/honnef.co/go/tools/lint/lint.go
vendored
|
@ -1,25 +1,22 @@
|
||||||
// Copyright (c) 2013 The Go Authors. All rights reserved.
|
// Package lint provides the foundation for tools like staticcheck
|
||||||
//
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file or at
|
|
||||||
// https://developers.google.com/open-source/licenses/bsd.
|
|
||||||
|
|
||||||
// Package lint provides the foundation for tools like gosimple.
|
|
||||||
package lint // import "honnef.co/go/tools/lint"
|
package lint // import "honnef.co/go/tools/lint"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/packages"
|
||||||
|
"honnef.co/go/tools/config"
|
||||||
"honnef.co/go/tools/ssa"
|
"honnef.co/go/tools/ssa"
|
||||||
"honnef.co/go/tools/ssa/ssautil"
|
"honnef.co/go/tools/ssa/ssautil"
|
||||||
)
|
)
|
||||||
|
@ -28,8 +25,10 @@ type Job struct {
|
||||||
Program *Program
|
Program *Program
|
||||||
|
|
||||||
checker string
|
checker string
|
||||||
check string
|
check Check
|
||||||
problems []Problem
|
problems []Problem
|
||||||
|
|
||||||
|
duration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ignore interface {
|
type Ignore interface {
|
||||||
|
@ -89,7 +88,7 @@ type GlobIgnore struct {
|
||||||
|
|
||||||
func (gi *GlobIgnore) Match(p Problem) bool {
|
func (gi *GlobIgnore) Match(p Problem) bool {
|
||||||
if gi.Pattern != "*" {
|
if gi.Pattern != "*" {
|
||||||
pkgpath := p.Package.Path()
|
pkgpath := p.Package.Types.Path()
|
||||||
if strings.HasSuffix(pkgpath, "_test") {
|
if strings.HasSuffix(pkgpath, "_test") {
|
||||||
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
|
pkgpath = pkgpath[:len(pkgpath)-len("_test")]
|
||||||
}
|
}
|
||||||
|
@ -108,30 +107,43 @@ func (gi *GlobIgnore) Match(p Problem) bool {
|
||||||
|
|
||||||
type Program struct {
|
type Program struct {
|
||||||
SSA *ssa.Program
|
SSA *ssa.Program
|
||||||
Prog *loader.Program
|
InitialPackages []*Pkg
|
||||||
// TODO(dh): Rename to InitialPackages?
|
|
||||||
Packages []*Pkg
|
|
||||||
InitialFunctions []*ssa.Function
|
InitialFunctions []*ssa.Function
|
||||||
|
AllPackages []*packages.Package
|
||||||
AllFunctions []*ssa.Function
|
AllFunctions []*ssa.Function
|
||||||
Files []*ast.File
|
Files []*ast.File
|
||||||
Info *types.Info
|
|
||||||
GoVersion int
|
GoVersion int
|
||||||
|
|
||||||
tokenFileMap map[*token.File]*ast.File
|
tokenFileMap map[*token.File]*ast.File
|
||||||
astFileMap map[*ast.File]*Pkg
|
astFileMap map[*ast.File]*Pkg
|
||||||
|
packagesMap map[string]*packages.Package
|
||||||
|
|
||||||
|
genMu sync.RWMutex
|
||||||
|
generatedMap map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prog *Program) Fset() *token.FileSet {
|
||||||
|
return prog.InitialPackages[0].Fset
|
||||||
}
|
}
|
||||||
|
|
||||||
type Func func(*Job)
|
type Func func(*Job)
|
||||||
|
|
||||||
|
type Severity uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
Error Severity = iota
|
||||||
|
Warning
|
||||||
|
Ignored
|
||||||
|
)
|
||||||
|
|
||||||
// Problem represents a problem in some source code.
|
// Problem represents a problem in some source code.
|
||||||
type Problem struct {
|
type Problem struct {
|
||||||
pos token.Pos
|
|
||||||
Position token.Position // position in source file
|
Position token.Position // position in source file
|
||||||
Text string // the prose that describes the problem
|
Text string // the prose that describes the problem
|
||||||
Check string
|
Check string
|
||||||
Checker string
|
Checker string
|
||||||
Package *types.Package
|
Package *Pkg
|
||||||
Ignored bool
|
Severity Severity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Problem) String() string {
|
func (p *Problem) String() string {
|
||||||
|
@ -145,15 +157,25 @@ type Checker interface {
|
||||||
Name() string
|
Name() string
|
||||||
Prefix() string
|
Prefix() string
|
||||||
Init(*Program)
|
Init(*Program)
|
||||||
Funcs() map[string]Func
|
Checks() []Check
|
||||||
|
}
|
||||||
|
|
||||||
|
type Check struct {
|
||||||
|
Fn Func
|
||||||
|
ID string
|
||||||
|
FilterGenerated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Linter lints Go source code.
|
// A Linter lints Go source code.
|
||||||
type Linter struct {
|
type Linter struct {
|
||||||
Checker Checker
|
Checkers []Checker
|
||||||
Ignores []Ignore
|
Ignores []Ignore
|
||||||
GoVersion int
|
GoVersion int
|
||||||
ReturnIgnored bool
|
ReturnIgnored bool
|
||||||
|
Config config.Config
|
||||||
|
|
||||||
|
MaxConcurrentJobs int
|
||||||
|
PrintStats bool
|
||||||
|
|
||||||
automaticIgnores []Ignore
|
automaticIgnores []Ignore
|
||||||
}
|
}
|
||||||
|
@ -191,36 +213,6 @@ func (j *Job) File(node Positioner) *ast.File {
|
||||||
return j.Program.File(node)
|
return j.Program.File(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dh): switch to sort.Slice when Go 1.9 lands.
|
|
||||||
type byPosition struct {
|
|
||||||
fset *token.FileSet
|
|
||||||
ps []Problem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps byPosition) Len() int {
|
|
||||||
return len(ps.ps)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps byPosition) Less(i int, j int) bool {
|
|
||||||
pi, pj := ps.ps[i].Position, ps.ps[j].Position
|
|
||||||
|
|
||||||
if pi.Filename != pj.Filename {
|
|
||||||
return pi.Filename < pj.Filename
|
|
||||||
}
|
|
||||||
if pi.Line != pj.Line {
|
|
||||||
return pi.Line < pj.Line
|
|
||||||
}
|
|
||||||
if pi.Column != pj.Column {
|
|
||||||
return pi.Column < pj.Column
|
|
||||||
}
|
|
||||||
|
|
||||||
return ps.ps[i].Text < ps.ps[j].Text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps byPosition) Swap(i int, j int) {
|
|
||||||
ps.ps[i], ps.ps[j] = ps.ps[j], ps.ps[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDirective(s string) (cmd string, args []string) {
|
func parseDirective(s string) (cmd string, args []string) {
|
||||||
if !strings.HasPrefix(s, "//lint:") {
|
if !strings.HasPrefix(s, "//lint:") {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
@ -230,79 +222,131 @@ func parseDirective(s string) (cmd string, args []string) {
|
||||||
return fields[0], fields[1:]
|
return fields[0], fields[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
type PerfStats struct {
|
||||||
ssaprog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
PackageLoading time.Duration
|
||||||
|
SSABuild time.Duration
|
||||||
|
OtherInitWork time.Duration
|
||||||
|
CheckerInits map[string]time.Duration
|
||||||
|
Jobs []JobStat
|
||||||
|
}
|
||||||
|
|
||||||
|
type JobStat struct {
|
||||||
|
Job string
|
||||||
|
Duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stats *PerfStats) Print(w io.Writer) {
|
||||||
|
fmt.Fprintln(w, "Package loading:", stats.PackageLoading)
|
||||||
|
fmt.Fprintln(w, "SSA build:", stats.SSABuild)
|
||||||
|
fmt.Fprintln(w, "Other init work:", stats.OtherInitWork)
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "Checker inits:")
|
||||||
|
for checker, d := range stats.CheckerInits {
|
||||||
|
fmt.Fprintf(w, "\t%s: %s\n", checker, d)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "Jobs:")
|
||||||
|
sort.Slice(stats.Jobs, func(i, j int) bool {
|
||||||
|
return stats.Jobs[i].Duration < stats.Jobs[j].Duration
|
||||||
|
})
|
||||||
|
var total time.Duration
|
||||||
|
for _, job := range stats.Jobs {
|
||||||
|
fmt.Fprintf(w, "\t%s: %s\n", job.Job, job.Duration)
|
||||||
|
total += job.Duration
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\tTotal: %s\n", total)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Linter) Lint(initial []*packages.Package, stats *PerfStats) []Problem {
|
||||||
|
allPkgs := allPackages(initial)
|
||||||
|
t := time.Now()
|
||||||
|
ssaprog, _ := ssautil.Packages(allPkgs, ssa.GlobalDebug)
|
||||||
ssaprog.Build()
|
ssaprog.Build()
|
||||||
|
if stats != nil {
|
||||||
|
stats.SSABuild = time.Since(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
t = time.Now()
|
||||||
pkgMap := map[*ssa.Package]*Pkg{}
|
pkgMap := map[*ssa.Package]*Pkg{}
|
||||||
var pkgs []*Pkg
|
var pkgs []*Pkg
|
||||||
for _, pkginfo := range lprog.InitialPackages() {
|
for _, pkg := range initial {
|
||||||
ssapkg := ssaprog.Package(pkginfo.Pkg)
|
ssapkg := ssaprog.Package(pkg.Types)
|
||||||
var bp *build.Package
|
var cfg config.Config
|
||||||
if len(pkginfo.Files) != 0 {
|
if len(pkg.GoFiles) != 0 {
|
||||||
path := lprog.Fset.Position(pkginfo.Files[0].Pos()).Filename
|
path := pkg.GoFiles[0]
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
var err error
|
var err error
|
||||||
ctx := conf.Build
|
// OPT(dh): we're rebuilding the entire config tree for
|
||||||
if ctx == nil {
|
// each package. for example, if we check a/b/c and
|
||||||
ctx = &build.Default
|
// a/b/c/d, we'll process a, a/b, a/b/c, a, a/b, a/b/c,
|
||||||
}
|
// a/b/c/d – we should cache configs per package and only
|
||||||
bp, err = ctx.ImportDir(dir, 0)
|
// load the new levels.
|
||||||
|
cfg, err = config.Load(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// shouldn't happen
|
// FIXME(dh): we couldn't load the config, what are we
|
||||||
|
// supposed to do? probably tell the user somehow
|
||||||
}
|
}
|
||||||
|
cfg = cfg.Merge(l.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg := &Pkg{
|
pkg := &Pkg{
|
||||||
Package: ssapkg,
|
SSA: ssapkg,
|
||||||
Info: pkginfo,
|
Package: pkg,
|
||||||
BuildPkg: bp,
|
Config: cfg,
|
||||||
}
|
}
|
||||||
pkgMap[ssapkg] = pkg
|
pkgMap[ssapkg] = pkg
|
||||||
pkgs = append(pkgs, pkg)
|
pkgs = append(pkgs, pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
prog := &Program{
|
prog := &Program{
|
||||||
SSA: ssaprog,
|
SSA: ssaprog,
|
||||||
Prog: lprog,
|
InitialPackages: pkgs,
|
||||||
Packages: pkgs,
|
AllPackages: allPkgs,
|
||||||
Info: &types.Info{},
|
|
||||||
GoVersion: l.GoVersion,
|
GoVersion: l.GoVersion,
|
||||||
tokenFileMap: map[*token.File]*ast.File{},
|
tokenFileMap: map[*token.File]*ast.File{},
|
||||||
astFileMap: map[*ast.File]*Pkg{},
|
astFileMap: map[*ast.File]*Pkg{},
|
||||||
|
generatedMap: map[string]bool{},
|
||||||
|
}
|
||||||
|
prog.packagesMap = map[string]*packages.Package{}
|
||||||
|
for _, pkg := range allPkgs {
|
||||||
|
prog.packagesMap[pkg.Types.Path()] = pkg
|
||||||
}
|
}
|
||||||
|
|
||||||
initial := map[*types.Package]struct{}{}
|
isInitial := map[*types.Package]struct{}{}
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
initial[pkg.Info.Pkg] = struct{}{}
|
isInitial[pkg.Types] = struct{}{}
|
||||||
}
|
}
|
||||||
for fn := range ssautil.AllFunctions(ssaprog) {
|
for fn := range ssautil.AllFunctions(ssaprog) {
|
||||||
if fn.Pkg == nil {
|
if fn.Pkg == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
prog.AllFunctions = append(prog.AllFunctions, fn)
|
prog.AllFunctions = append(prog.AllFunctions, fn)
|
||||||
if _, ok := initial[fn.Pkg.Pkg]; ok {
|
if _, ok := isInitial[fn.Pkg.Pkg]; ok {
|
||||||
prog.InitialFunctions = append(prog.InitialFunctions, fn)
|
prog.InitialFunctions = append(prog.InitialFunctions, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
prog.Files = append(prog.Files, pkg.Info.Files...)
|
prog.Files = append(prog.Files, pkg.Syntax...)
|
||||||
|
|
||||||
ssapkg := ssaprog.Package(pkg.Info.Pkg)
|
ssapkg := ssaprog.Package(pkg.Types)
|
||||||
for _, f := range pkg.Info.Files {
|
for _, f := range pkg.Syntax {
|
||||||
prog.astFileMap[f] = pkgMap[ssapkg]
|
prog.astFileMap[f] = pkgMap[ssapkg]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkginfo := range lprog.AllPackages {
|
for _, pkg := range allPkgs {
|
||||||
for _, f := range pkginfo.Files {
|
for _, f := range pkg.Syntax {
|
||||||
tf := lprog.Fset.File(f.Pos())
|
tf := pkg.Fset.File(f.Pos())
|
||||||
prog.tokenFileMap[tf] = f
|
prog.tokenFileMap[tf] = f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var out []Problem
|
var out []Problem
|
||||||
l.automaticIgnores = nil
|
l.automaticIgnores = nil
|
||||||
for _, pkginfo := range lprog.InitialPackages() {
|
for _, pkg := range initial {
|
||||||
for _, f := range pkginfo.Files {
|
for _, f := range pkg.Syntax {
|
||||||
cm := ast.NewCommentMap(lprog.Fset, f, f.Comments)
|
cm := ast.NewCommentMap(pkg.Fset, f, f.Comments)
|
||||||
for node, cgs := range cm {
|
for node, cgs := range cm {
|
||||||
for _, cg := range cgs {
|
for _, cg := range cgs {
|
||||||
for _, c := range cg.List {
|
for _, c := range cg.List {
|
||||||
|
@ -315,11 +359,10 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
// FIXME(dh): this causes duplicated warnings when using megacheck
|
// FIXME(dh): this causes duplicated warnings when using megacheck
|
||||||
p := Problem{
|
p := Problem{
|
||||||
pos: c.Pos(),
|
|
||||||
Position: prog.DisplayPosition(c.Pos()),
|
Position: prog.DisplayPosition(c.Pos()),
|
||||||
Text: "malformed linter directive; missing the required reason field?",
|
Text: "malformed linter directive; missing the required reason field?",
|
||||||
Check: "",
|
Check: "",
|
||||||
Checker: l.Checker.Name(),
|
Checker: "lint",
|
||||||
Package: nil,
|
Package: nil,
|
||||||
}
|
}
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
|
@ -362,75 +405,84 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||||
scopes int
|
scopes int
|
||||||
}{}
|
}{}
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
sizes.types += len(pkg.Info.Info.Types)
|
sizes.types += len(pkg.TypesInfo.Types)
|
||||||
sizes.defs += len(pkg.Info.Info.Defs)
|
sizes.defs += len(pkg.TypesInfo.Defs)
|
||||||
sizes.uses += len(pkg.Info.Info.Uses)
|
sizes.uses += len(pkg.TypesInfo.Uses)
|
||||||
sizes.implicits += len(pkg.Info.Info.Implicits)
|
sizes.implicits += len(pkg.TypesInfo.Implicits)
|
||||||
sizes.selections += len(pkg.Info.Info.Selections)
|
sizes.selections += len(pkg.TypesInfo.Selections)
|
||||||
sizes.scopes += len(pkg.Info.Info.Scopes)
|
sizes.scopes += len(pkg.TypesInfo.Scopes)
|
||||||
}
|
}
|
||||||
prog.Info.Types = make(map[ast.Expr]types.TypeAndValue, sizes.types)
|
|
||||||
prog.Info.Defs = make(map[*ast.Ident]types.Object, sizes.defs)
|
|
||||||
prog.Info.Uses = make(map[*ast.Ident]types.Object, sizes.uses)
|
|
||||||
prog.Info.Implicits = make(map[ast.Node]types.Object, sizes.implicits)
|
|
||||||
prog.Info.Selections = make(map[*ast.SelectorExpr]*types.Selection, sizes.selections)
|
|
||||||
prog.Info.Scopes = make(map[ast.Node]*types.Scope, sizes.scopes)
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
for k, v := range pkg.Info.Info.Types {
|
|
||||||
prog.Info.Types[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range pkg.Info.Info.Defs {
|
|
||||||
prog.Info.Defs[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range pkg.Info.Info.Uses {
|
|
||||||
prog.Info.Uses[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range pkg.Info.Info.Implicits {
|
|
||||||
prog.Info.Implicits[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range pkg.Info.Info.Selections {
|
|
||||||
prog.Info.Selections[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range pkg.Info.Info.Scopes {
|
|
||||||
prog.Info.Scopes[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
l.Checker.Init(prog)
|
|
||||||
|
|
||||||
funcs := l.Checker.Funcs()
|
if stats != nil {
|
||||||
var keys []string
|
stats.OtherInitWork = time.Since(t)
|
||||||
for k := range funcs {
|
}
|
||||||
keys = append(keys, k)
|
|
||||||
|
for _, checker := range l.Checkers {
|
||||||
|
t := time.Now()
|
||||||
|
checker.Init(prog)
|
||||||
|
if stats != nil {
|
||||||
|
stats.CheckerInits[checker.Name()] = time.Since(t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
var jobs []*Job
|
var jobs []*Job
|
||||||
for _, k := range keys {
|
var allChecks []string
|
||||||
|
|
||||||
|
for _, checker := range l.Checkers {
|
||||||
|
checks := checker.Checks()
|
||||||
|
for _, check := range checks {
|
||||||
|
allChecks = append(allChecks, check.ID)
|
||||||
j := &Job{
|
j := &Job{
|
||||||
Program: prog,
|
Program: prog,
|
||||||
checker: l.Checker.Name(),
|
checker: checker.Name(),
|
||||||
check: k,
|
check: check,
|
||||||
}
|
}
|
||||||
jobs = append(jobs, j)
|
jobs = append(jobs, j)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
max := len(jobs)
|
||||||
|
if l.MaxConcurrentJobs > 0 {
|
||||||
|
max = l.MaxConcurrentJobs
|
||||||
|
}
|
||||||
|
|
||||||
|
sem := make(chan struct{}, max)
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(j *Job) {
|
go func(j *Job) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
fn := funcs[j.check]
|
sem <- struct{}{}
|
||||||
|
defer func() { <-sem }()
|
||||||
|
fn := j.check.Fn
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
t := time.Now()
|
||||||
fn(j)
|
fn(j)
|
||||||
|
j.duration = time.Since(t)
|
||||||
}(j)
|
}(j)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
for _, j := range jobs {
|
for _, j := range jobs {
|
||||||
|
if stats != nil {
|
||||||
|
stats.Jobs = append(stats.Jobs, JobStat{j.check.ID, j.duration})
|
||||||
|
}
|
||||||
for _, p := range j.problems {
|
for _, p := range j.problems {
|
||||||
p.Ignored = l.ignore(p)
|
allowedChecks := FilterChecks(allChecks, p.Package.Config.Checks)
|
||||||
if l.ReturnIgnored || !p.Ignored {
|
|
||||||
|
if l.ignore(p) {
|
||||||
|
p.Severity = Ignored
|
||||||
|
}
|
||||||
|
// TODO(dh): support globs in check white/blacklist
|
||||||
|
// OPT(dh): this approach doesn't actually disable checks,
|
||||||
|
// it just discards their results. For the moment, that's
|
||||||
|
// fine. None of our checks are super expensive. In the
|
||||||
|
// future, we may want to provide opt-in expensive
|
||||||
|
// analysis, which shouldn't run at all. It may be easiest
|
||||||
|
// to implement this in the individual checks.
|
||||||
|
if (l.ReturnIgnored || p.Severity != Ignored) && allowedChecks[p.Check] {
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,39 +496,128 @@ func (l *Linter) Lint(lprog *loader.Program, conf *loader.Config) []Problem {
|
||||||
if ig.matched {
|
if ig.matched {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, c := range ig.Checks {
|
|
||||||
idx := strings.IndexFunc(c, func(r rune) bool {
|
couldveMatched := false
|
||||||
return unicode.IsNumber(r)
|
for f, pkg := range prog.astFileMap {
|
||||||
})
|
if prog.Fset().Position(f.Pos()).Filename != ig.File {
|
||||||
if idx == -1 {
|
|
||||||
// malformed check name, backing out
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c[:idx] != l.Checker.Prefix() {
|
allowedChecks := FilterChecks(allChecks, pkg.Config.Checks)
|
||||||
// not for this checker
|
for _, c := range ig.Checks {
|
||||||
|
if !allowedChecks[c] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
couldveMatched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !couldveMatched {
|
||||||
|
// The ignored checks were disabled for the containing package.
|
||||||
|
// Don't flag the ignore for not having matched.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p := Problem{
|
p := Problem{
|
||||||
pos: ig.pos,
|
|
||||||
Position: prog.DisplayPosition(ig.pos),
|
Position: prog.DisplayPosition(ig.pos),
|
||||||
Text: "this linter directive didn't match anything; should it be removed?",
|
Text: "this linter directive didn't match anything; should it be removed?",
|
||||||
Check: "",
|
Check: "",
|
||||||
Checker: l.Checker.Name(),
|
Checker: "lint",
|
||||||
Package: nil,
|
Package: nil,
|
||||||
}
|
}
|
||||||
out = append(out, p)
|
out = append(out, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(out, func(i int, j int) bool {
|
||||||
|
pi, pj := out[i].Position, out[j].Position
|
||||||
|
|
||||||
|
if pi.Filename != pj.Filename {
|
||||||
|
return pi.Filename < pj.Filename
|
||||||
|
}
|
||||||
|
if pi.Line != pj.Line {
|
||||||
|
return pi.Line < pj.Line
|
||||||
|
}
|
||||||
|
if pi.Column != pj.Column {
|
||||||
|
return pi.Column < pj.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(byPosition{lprog.Fset, out})
|
return out[i].Text < out[j].Text
|
||||||
|
})
|
||||||
|
|
||||||
|
if l.PrintStats && stats != nil {
|
||||||
|
stats.Print(os.Stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(out) < 2 {
|
||||||
return out
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq := make([]Problem, 0, len(out))
|
||||||
|
uniq = append(uniq, out[0])
|
||||||
|
prev := out[0]
|
||||||
|
for _, p := range out[1:] {
|
||||||
|
if prev.Position == p.Position && prev.Text == p.Text {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
prev = p
|
||||||
|
uniq = append(uniq, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniq
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterChecks(allChecks []string, checks []string) map[string]bool {
|
||||||
|
// OPT(dh): this entire computation could be cached per package
|
||||||
|
allowedChecks := map[string]bool{}
|
||||||
|
|
||||||
|
for _, check := range checks {
|
||||||
|
b := true
|
||||||
|
if len(check) > 1 && check[0] == '-' {
|
||||||
|
b = false
|
||||||
|
check = check[1:]
|
||||||
|
}
|
||||||
|
if check == "*" || check == "all" {
|
||||||
|
// Match all
|
||||||
|
for _, c := range allChecks {
|
||||||
|
allowedChecks[c] = b
|
||||||
|
}
|
||||||
|
} else if strings.HasSuffix(check, "*") {
|
||||||
|
// Glob
|
||||||
|
prefix := check[:len(check)-1]
|
||||||
|
isCat := strings.IndexFunc(prefix, func(r rune) bool { return unicode.IsNumber(r) }) == -1
|
||||||
|
|
||||||
|
for _, c := range allChecks {
|
||||||
|
idx := strings.IndexFunc(c, func(r rune) bool { return unicode.IsNumber(r) })
|
||||||
|
if isCat {
|
||||||
|
// Glob is S*, which should match S1000 but not SA1000
|
||||||
|
cat := c[:idx]
|
||||||
|
if prefix == cat {
|
||||||
|
allowedChecks[c] = b
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Glob is S1*
|
||||||
|
if strings.HasPrefix(c, prefix) {
|
||||||
|
allowedChecks[c] = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Literal check name
|
||||||
|
allowedChecks[check] = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allowedChecks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (prog *Program) Package(path string) *packages.Package {
|
||||||
|
return prog.packagesMap[path]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pkg represents a package being linted.
|
// Pkg represents a package being linted.
|
||||||
type Pkg struct {
|
type Pkg struct {
|
||||||
*ssa.Package
|
SSA *ssa.Package
|
||||||
Info *loader.PackageInfo
|
*packages.Package
|
||||||
BuildPkg *build.Package
|
Config config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type Positioner interface {
|
type Positioner interface {
|
||||||
|
@ -484,52 +625,61 @@ type Positioner interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (prog *Program) DisplayPosition(p token.Pos) token.Position {
|
func (prog *Program) DisplayPosition(p token.Pos) token.Position {
|
||||||
// The //line compiler directive can be used to change the file
|
// Only use the adjusted position if it points to another Go file.
|
||||||
// name and line numbers associated with code. This can, for
|
// This means we'll point to the original file for cgo files, but
|
||||||
// example, be used by code generation tools. The most prominent
|
// we won't point to a YACC grammar file.
|
||||||
// example is 'go tool cgo', which uses //line directives to refer
|
|
||||||
// back to the original source code.
|
|
||||||
//
|
|
||||||
// In the context of our linters, we need to treat these
|
|
||||||
// directives differently depending on context. For cgo files, we
|
|
||||||
// want to honour the directives, so that line numbers are
|
|
||||||
// adjusted correctly. For all other files, we want to ignore the
|
|
||||||
// directives, so that problems are reported at their actual
|
|
||||||
// position and not, for example, a yacc grammar file. This also
|
|
||||||
// affects the ignore mechanism, since it operates on the position
|
|
||||||
// information stored within problems. With this implementation, a
|
|
||||||
// user will ignore foo.go, not foo.y
|
|
||||||
|
|
||||||
pkg := prog.astFileMap[prog.tokenFileMap[prog.Prog.Fset.File(p)]]
|
pos := prog.Fset().PositionFor(p, false)
|
||||||
bp := pkg.BuildPkg
|
adjPos := prog.Fset().PositionFor(p, true)
|
||||||
adjPos := prog.Prog.Fset.Position(p)
|
|
||||||
if bp == nil {
|
if filepath.Ext(adjPos.Filename) == ".go" {
|
||||||
// couldn't find the package for some reason (deleted? faulty
|
|
||||||
// file system?)
|
|
||||||
return adjPos
|
return adjPos
|
||||||
}
|
}
|
||||||
base := filepath.Base(adjPos.Filename)
|
return pos
|
||||||
for _, f := range bp.CgoFiles {
|
}
|
||||||
if f == base {
|
|
||||||
// this is a cgo file, use the adjusted position
|
func (prog *Program) isGenerated(path string) bool {
|
||||||
return adjPos
|
// This function isn't very efficient in terms of lock contention
|
||||||
|
// and lack of parallelism, but it really shouldn't matter.
|
||||||
|
// Projects consists of thousands of files, and have hundreds of
|
||||||
|
// errors. That's not a lot of calls to isGenerated.
|
||||||
|
|
||||||
|
prog.genMu.RLock()
|
||||||
|
if b, ok := prog.generatedMap[path]; ok {
|
||||||
|
prog.genMu.RUnlock()
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
prog.genMu.RUnlock()
|
||||||
|
prog.genMu.Lock()
|
||||||
|
defer prog.genMu.Unlock()
|
||||||
|
// recheck to avoid doing extra work in case of race
|
||||||
|
if b, ok := prog.generatedMap[path]; ok {
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
// not a cgo file, ignore //line directives
|
|
||||||
return prog.Prog.Fset.PositionFor(p, false)
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
b := isGenerated(f)
|
||||||
|
prog.generatedMap[path] = b
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
|
func (j *Job) Errorf(n Positioner, format string, args ...interface{}) *Problem {
|
||||||
tf := j.Program.SSA.Fset.File(n.Pos())
|
tf := j.Program.SSA.Fset.File(n.Pos())
|
||||||
f := j.Program.tokenFileMap[tf]
|
f := j.Program.tokenFileMap[tf]
|
||||||
pkg := j.Program.astFileMap[f].Pkg
|
pkg := j.Program.astFileMap[f]
|
||||||
|
|
||||||
pos := j.Program.DisplayPosition(n.Pos())
|
pos := j.Program.DisplayPosition(n.Pos())
|
||||||
|
if j.Program.isGenerated(pos.Filename) && j.check.FilterGenerated {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
problem := Problem{
|
problem := Problem{
|
||||||
pos: n.Pos(),
|
|
||||||
Position: pos,
|
Position: pos,
|
||||||
Text: fmt.Sprintf(format, args...),
|
Text: fmt.Sprintf(format, args...),
|
||||||
Check: j.check,
|
Check: j.check.ID,
|
||||||
Checker: j.checker,
|
Checker: j.checker,
|
||||||
Package: pkg,
|
Package: pkg,
|
||||||
}
|
}
|
||||||
|
@ -541,3 +691,16 @@ func (j *Job) NodePackage(node Positioner) *Pkg {
|
||||||
f := j.File(node)
|
f := j.File(node)
|
||||||
return j.Program.astFileMap[f]
|
return j.Program.astFileMap[f]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func allPackages(pkgs []*packages.Package) []*packages.Package {
|
||||||
|
var out []*packages.Package
|
||||||
|
packages.Visit(
|
||||||
|
pkgs,
|
||||||
|
func(pkg *packages.Package) bool {
|
||||||
|
out = append(out, pkg)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
129
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
129
vendor/honnef.co/go/tools/lint/lintdsl/lintdsl.go
vendored
|
@ -103,10 +103,21 @@ func IsZero(expr ast.Expr) bool {
|
||||||
return IsIntLiteral(expr, "0")
|
return IsIntLiteral(expr, "0")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TypeOf(j *lint.Job, expr ast.Expr) types.Type { return j.Program.Info.TypeOf(expr) }
|
func TypeOf(j *lint.Job, expr ast.Expr) types.Type {
|
||||||
|
if expr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return j.NodePackage(expr).TypesInfo.TypeOf(expr)
|
||||||
|
}
|
||||||
|
|
||||||
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
|
func IsOfType(j *lint.Job, expr ast.Expr, name string) bool { return IsType(TypeOf(j, expr), name) }
|
||||||
|
|
||||||
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object { return j.Program.Info.ObjectOf(ident) }
|
func ObjectOf(j *lint.Job, ident *ast.Ident) types.Object {
|
||||||
|
if ident == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return j.NodePackage(ident).TypesInfo.ObjectOf(ident)
|
||||||
|
}
|
||||||
|
|
||||||
func IsInTest(j *lint.Job, node lint.Positioner) bool {
|
func IsInTest(j *lint.Job, node lint.Positioner) bool {
|
||||||
// FIXME(dh): this doesn't work for global variables with
|
// FIXME(dh): this doesn't work for global variables with
|
||||||
|
@ -123,14 +134,15 @@ func IsInMain(j *lint.Job, node lint.Positioner) bool {
|
||||||
if pkg == nil {
|
if pkg == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return pkg.Pkg.Name() == "main"
|
return pkg.Types.Name() == "main"
|
||||||
}
|
}
|
||||||
|
|
||||||
func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
||||||
sel := j.Program.Info.Selections[expr]
|
info := j.NodePackage(expr).TypesInfo
|
||||||
|
sel := info.Selections[expr]
|
||||||
if sel == nil {
|
if sel == nil {
|
||||||
if x, ok := expr.X.(*ast.Ident); ok {
|
if x, ok := expr.X.(*ast.Ident); ok {
|
||||||
pkg, ok := j.Program.Info.ObjectOf(x).(*types.PkgName)
|
pkg, ok := info.ObjectOf(x).(*types.PkgName)
|
||||||
if !ok {
|
if !ok {
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
|
return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
|
||||||
|
@ -143,11 +155,11 @@ func SelectorName(j *lint.Job, expr *ast.SelectorExpr) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsNil(j *lint.Job, expr ast.Expr) bool {
|
func IsNil(j *lint.Job, expr ast.Expr) bool {
|
||||||
return j.Program.Info.Types[expr].IsNil()
|
return j.NodePackage(expr).TypesInfo.Types[expr].IsNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
func BoolConst(j *lint.Job, expr ast.Expr) bool {
|
func BoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||||
val := j.Program.Info.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
val := j.NodePackage(expr).TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
|
||||||
return constant.BoolVal(val)
|
return constant.BoolVal(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +172,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
obj := j.Program.Info.ObjectOf(ident)
|
obj := j.NodePackage(expr).TypesInfo.ObjectOf(ident)
|
||||||
c, ok := obj.(*types.Const)
|
c, ok := obj.(*types.Const)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
|
@ -176,7 +188,7 @@ func IsBoolConst(j *lint.Job, expr ast.Expr) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
|
func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
|
||||||
tv := j.Program.Info.Types[expr]
|
tv := j.NodePackage(expr).TypesInfo.Types[expr]
|
||||||
if tv.Value == nil {
|
if tv.Value == nil {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
@ -187,7 +199,7 @@ func ExprToInt(j *lint.Job, expr ast.Expr) (int64, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExprToString(j *lint.Job, expr ast.Expr) (string, bool) {
|
func ExprToString(j *lint.Job, expr ast.Expr) (string, bool) {
|
||||||
val := j.Program.Info.Types[expr].Value
|
val := j.NodePackage(expr).TypesInfo.Types[expr].Value
|
||||||
if val == nil {
|
if val == nil {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
@ -220,17 +232,35 @@ func IsGoVersion(j *lint.Job, minor int) bool {
|
||||||
return j.Program.GoVersion >= minor
|
return j.Program.GoVersion >= minor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CallNameAST(j *lint.Job, call *ast.CallExpr) string {
|
||||||
|
switch fun := call.Fun.(type) {
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
fn, ok := ObjectOf(j, fun.Sel).(*types.Func)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fn.FullName()
|
||||||
|
case *ast.Ident:
|
||||||
|
obj := ObjectOf(j, fun)
|
||||||
|
switch obj := obj.(type) {
|
||||||
|
case *types.Func:
|
||||||
|
return obj.FullName()
|
||||||
|
case *types.Builtin:
|
||||||
|
return obj.Name()
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func IsCallToAST(j *lint.Job, node ast.Node, name string) bool {
|
func IsCallToAST(j *lint.Job, node ast.Node, name string) bool {
|
||||||
call, ok := node.(*ast.CallExpr)
|
call, ok := node.(*ast.CallExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
sel, ok := call.Fun.(*ast.SelectorExpr)
|
return CallNameAST(j, call) == name
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fn, ok := j.Program.Info.ObjectOf(sel.Sel).(*types.Func)
|
|
||||||
return ok && fn.FullName() == name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
|
func IsCallToAnyAST(j *lint.Job, node ast.Node, names ...string) bool {
|
||||||
|
@ -280,3 +310,70 @@ func Inspect(node ast.Node, fn func(node ast.Node) bool) {
|
||||||
}
|
}
|
||||||
ast.Inspect(node, fn)
|
ast.Inspect(node, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GroupSpecs(j *lint.Job, specs []ast.Spec) [][]ast.Spec {
|
||||||
|
if len(specs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fset := j.Program.SSA.Fset
|
||||||
|
groups := make([][]ast.Spec, 1)
|
||||||
|
groups[0] = append(groups[0], specs[0])
|
||||||
|
|
||||||
|
for _, spec := range specs[1:] {
|
||||||
|
g := groups[len(groups)-1]
|
||||||
|
if fset.PositionFor(spec.Pos(), false).Line-1 !=
|
||||||
|
fset.PositionFor(g[len(g)-1].End(), false).Line {
|
||||||
|
|
||||||
|
groups = append(groups, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
groups[len(groups)-1] = append(groups[len(groups)-1], spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsObject(obj types.Object, name string) bool {
|
||||||
|
var path string
|
||||||
|
if pkg := obj.Pkg(); pkg != nil {
|
||||||
|
path = pkg.Path() + "."
|
||||||
|
}
|
||||||
|
return path+obj.Name() == name
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
Var *types.Var
|
||||||
|
Tag string
|
||||||
|
Path []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlattenFields recursively flattens T and embedded structs,
|
||||||
|
// returning a list of fields. If multiple fields with the same name
|
||||||
|
// exist, all will be returned.
|
||||||
|
func FlattenFields(T *types.Struct) []Field {
|
||||||
|
return flattenFields(T, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field {
|
||||||
|
if seen == nil {
|
||||||
|
seen = map[types.Type]bool{}
|
||||||
|
}
|
||||||
|
if seen[T] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
seen[T] = true
|
||||||
|
var out []Field
|
||||||
|
for i := 0; i < T.NumFields(); i++ {
|
||||||
|
field := T.Field(i)
|
||||||
|
tag := T.Tag(i)
|
||||||
|
np := append(path[:len(path):len(path)], i)
|
||||||
|
if field.Anonymous() {
|
||||||
|
if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok {
|
||||||
|
out = append(out, flattenFields(s, np, seen)...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out = append(out, Field{field, tag, np})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
128
vendor/honnef.co/go/tools/lint/lintutil/format/format.go
vendored
Normal file
128
vendor/honnef.co/go/tools/lint/lintutil/format/format.go
vendored
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
// Package format provides formatters for linter problems.
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint"
|
||||||
|
)
|
||||||
|
|
||||||
|
func shortPath(path string) string {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func relativePositionString(pos token.Position) string {
|
||||||
|
s := shortPath(pos.Filename)
|
||||||
|
if pos.IsValid() {
|
||||||
|
if s != "" {
|
||||||
|
s += ":"
|
||||||
|
}
|
||||||
|
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
||||||
|
}
|
||||||
|
if s == "" {
|
||||||
|
s = "-"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type Statter interface {
|
||||||
|
Stats(total, errors, warnings int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Formatter interface {
|
||||||
|
Format(p lint.Problem)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Text struct {
|
||||||
|
W io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Text) Format(p lint.Problem) {
|
||||||
|
fmt.Fprintf(o.W, "%v: %s\n", relativePositionString(p.Position), p.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
type JSON struct {
|
||||||
|
W io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func severity(s lint.Severity) string {
|
||||||
|
switch s {
|
||||||
|
case lint.Error:
|
||||||
|
return "error"
|
||||||
|
case lint.Warning:
|
||||||
|
return "warning"
|
||||||
|
case lint.Ignored:
|
||||||
|
return "ignored"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JSON) Format(p lint.Problem) {
|
||||||
|
type location struct {
|
||||||
|
File string `json:"file"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
Column int `json:"column"`
|
||||||
|
}
|
||||||
|
jp := struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Severity string `json:"severity,omitempty"`
|
||||||
|
Location location `json:"location"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}{
|
||||||
|
Code: p.Check,
|
||||||
|
Severity: severity(p.Severity),
|
||||||
|
Location: location{
|
||||||
|
File: p.Position.Filename,
|
||||||
|
Line: p.Position.Line,
|
||||||
|
Column: p.Position.Column,
|
||||||
|
},
|
||||||
|
Message: p.Text,
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(o.W).Encode(jp)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stylish struct {
|
||||||
|
W io.Writer
|
||||||
|
|
||||||
|
prevFile string
|
||||||
|
tw *tabwriter.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Stylish) Format(p lint.Problem) {
|
||||||
|
if p.Position.Filename == "" {
|
||||||
|
p.Position.Filename = "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Position.Filename != o.prevFile {
|
||||||
|
if o.prevFile != "" {
|
||||||
|
o.tw.Flush()
|
||||||
|
fmt.Fprintln(o.W)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(o.W, p.Position.Filename)
|
||||||
|
o.prevFile = p.Position.Filename
|
||||||
|
o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(o.tw, " (%d, %d)\t%s\t%s\n", p.Position.Line, p.Position.Column, p.Check, p.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Stylish) Stats(total, errors, warnings int) {
|
||||||
|
if o.tw != nil {
|
||||||
|
o.tw.Flush()
|
||||||
|
fmt.Fprintln(o.W)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings)\n",
|
||||||
|
total, errors, warnings)
|
||||||
|
}
|
380
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
380
vendor/honnef.co/go/tools/lint/lintutil/util.go
vendored
|
@ -8,70 +8,28 @@
|
||||||
package lintutil // import "honnef.co/go/tools/lint/lintutil"
|
package lintutil // import "honnef.co/go/tools/lint/lintutil"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"log"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/config"
|
||||||
"honnef.co/go/tools/lint"
|
"honnef.co/go/tools/lint"
|
||||||
|
"honnef.co/go/tools/lint/lintutil/format"
|
||||||
"honnef.co/go/tools/version"
|
"honnef.co/go/tools/version"
|
||||||
|
|
||||||
"github.com/kisielk/gotool"
|
"golang.org/x/tools/go/packages"
|
||||||
"golang.org/x/tools/go/loader"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OutputFormatter interface {
|
|
||||||
Format(p lint.Problem)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextOutput struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o TextOutput) Format(p lint.Problem) {
|
|
||||||
fmt.Fprintf(o.w, "%v: %s\n", relativePositionString(p.Position), p.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONOutput struct {
|
|
||||||
w io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o JSONOutput) Format(p lint.Problem) {
|
|
||||||
type location struct {
|
|
||||||
File string `json:"file"`
|
|
||||||
Line int `json:"line"`
|
|
||||||
Column int `json:"column"`
|
|
||||||
}
|
|
||||||
jp := struct {
|
|
||||||
Checker string `json:"checker"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
Severity string `json:"severity,omitempty"`
|
|
||||||
Location location `json:"location"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Ignored bool `json:"ignored"`
|
|
||||||
}{
|
|
||||||
p.Checker,
|
|
||||||
p.Check,
|
|
||||||
"", // TODO(dh): support severity
|
|
||||||
location{
|
|
||||||
p.Position.Filename,
|
|
||||||
p.Position.Line,
|
|
||||||
p.Position.Column,
|
|
||||||
},
|
|
||||||
p.Text,
|
|
||||||
p.Ignored,
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(o.w).Encode(jp)
|
|
||||||
}
|
|
||||||
func usage(name string, flags *flag.FlagSet) func() {
|
func usage(name string, flags *flag.FlagSet) func() {
|
||||||
return func() {
|
return func() {
|
||||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
|
fmt.Fprintf(os.Stderr, "Usage of %s:\n", name)
|
||||||
|
@ -84,38 +42,6 @@ func usage(name string, flags *flag.FlagSet) func() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type runner struct {
|
|
||||||
checker lint.Checker
|
|
||||||
tags []string
|
|
||||||
ignores []lint.Ignore
|
|
||||||
version int
|
|
||||||
returnIgnored bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveRelative(importPaths []string, tags []string) (goFiles bool, err error) {
|
|
||||||
if len(importPaths) == 0 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(importPaths[0], ".go") {
|
|
||||||
// User is specifying a package in terms of .go files, don't resolve
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
ctx := build.Default
|
|
||||||
ctx.BuildTags = tags
|
|
||||||
for i, path := range importPaths {
|
|
||||||
bpkg, err := ctx.Import(path, wd, build.FindOnly)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("can't load package %q: %v", path, err)
|
|
||||||
}
|
|
||||||
importPaths[i] = bpkg.ImportPath
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIgnore(s string) ([]lint.Ignore, error) {
|
func parseIgnore(s string) ([]lint.Ignore, error) {
|
||||||
var out []lint.Ignore
|
var out []lint.Ignore
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
|
@ -158,16 +84,41 @@ func (v *versionFlag) Get() interface{} {
|
||||||
return int(*v)
|
return int(*v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type list []string
|
||||||
|
|
||||||
|
func (list *list) String() string {
|
||||||
|
return `"` + strings.Join(*list, ",") + `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *list) Set(s string) error {
|
||||||
|
if s == "" {
|
||||||
|
*list = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
*list = strings.Split(s, ",")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func FlagSet(name string) *flag.FlagSet {
|
func FlagSet(name string) *flag.FlagSet {
|
||||||
flags := flag.NewFlagSet("", flag.ExitOnError)
|
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||||||
flags.Usage = usage(name, flags)
|
flags.Usage = usage(name, flags)
|
||||||
flags.Float64("min_confidence", 0, "Deprecated; use -ignore instead")
|
|
||||||
flags.String("tags", "", "List of `build tags`")
|
flags.String("tags", "", "List of `build tags`")
|
||||||
flags.String("ignore", "", "Space separated list of checks to ignore, in the following format: 'import/path/file.go:Check1,Check2,...' Both the import path and file name sections support globbing, e.g. 'os/exec/*_test.go'")
|
flags.String("ignore", "", "Deprecated: use linter directives instead")
|
||||||
flags.Bool("tests", true, "Include tests")
|
flags.Bool("tests", true, "Include tests")
|
||||||
flags.Bool("version", false, "Print version and exit")
|
flags.Bool("version", false, "Print version and exit")
|
||||||
flags.Bool("show-ignored", false, "Don't filter ignored problems")
|
flags.Bool("show-ignored", false, "Don't filter ignored problems")
|
||||||
flags.String("f", "text", "Output `format` (valid choices are 'text' and 'json')")
|
flags.String("f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')")
|
||||||
|
|
||||||
|
flags.Int("debug.max-concurrent-jobs", 0, "Number of jobs to run concurrently")
|
||||||
|
flags.Bool("debug.print-stats", false, "Print debug statistics")
|
||||||
|
flags.String("debug.cpuprofile", "", "Write CPU profile to `file`")
|
||||||
|
flags.String("debug.memprofile", "", "Write memory profile to `file`")
|
||||||
|
|
||||||
|
checks := list{"inherit"}
|
||||||
|
fail := list{"all"}
|
||||||
|
flags.Var(&checks, "checks", "Comma-separated list of `checks` to enable.")
|
||||||
|
flags.Var(&fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.")
|
||||||
|
|
||||||
tags := build.Default.ReleaseTags
|
tags := build.Default.ReleaseTags
|
||||||
v := tags[len(tags)-1][2:]
|
v := tags[len(tags)-1][2:]
|
||||||
|
@ -180,76 +131,129 @@ func FlagSet(name string) *flag.FlagSet {
|
||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckerConfig struct {
|
func ProcessFlagSet(cs []lint.Checker, fs *flag.FlagSet) {
|
||||||
Checker lint.Checker
|
|
||||||
ExitNonZero bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProcessFlagSet(confs []CheckerConfig, fs *flag.FlagSet) {
|
|
||||||
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
|
tags := fs.Lookup("tags").Value.(flag.Getter).Get().(string)
|
||||||
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
|
ignore := fs.Lookup("ignore").Value.(flag.Getter).Get().(string)
|
||||||
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
|
tests := fs.Lookup("tests").Value.(flag.Getter).Get().(bool)
|
||||||
goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
|
goVersion := fs.Lookup("go").Value.(flag.Getter).Get().(int)
|
||||||
format := fs.Lookup("f").Value.(flag.Getter).Get().(string)
|
formatter := fs.Lookup("f").Value.(flag.Getter).Get().(string)
|
||||||
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
|
printVersion := fs.Lookup("version").Value.(flag.Getter).Get().(bool)
|
||||||
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
|
showIgnored := fs.Lookup("show-ignored").Value.(flag.Getter).Get().(bool)
|
||||||
|
|
||||||
if printVersion {
|
maxConcurrentJobs := fs.Lookup("debug.max-concurrent-jobs").Value.(flag.Getter).Get().(int)
|
||||||
version.Print()
|
printStats := fs.Lookup("debug.print-stats").Value.(flag.Getter).Get().(bool)
|
||||||
os.Exit(0)
|
cpuProfile := fs.Lookup("debug.cpuprofile").Value.(flag.Getter).Get().(string)
|
||||||
|
memProfile := fs.Lookup("debug.memprofile").Value.(flag.Getter).Get().(string)
|
||||||
|
|
||||||
|
cfg := config.Config{}
|
||||||
|
cfg.Checks = *fs.Lookup("checks").Value.(*list)
|
||||||
|
|
||||||
|
exit := func(code int) {
|
||||||
|
if cpuProfile != "" {
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
if memProfile != "" {
|
||||||
|
f, err := os.Create(memProfile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
pprof.WriteHeapProfile(f)
|
||||||
|
}
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
if cpuProfile != "" {
|
||||||
|
f, err := os.Create(cpuProfile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
pprof.StartCPUProfile(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cs []lint.Checker
|
if printVersion {
|
||||||
for _, conf := range confs {
|
version.Print()
|
||||||
cs = append(cs, conf.Checker)
|
exit(0)
|
||||||
}
|
}
|
||||||
pss, err := Lint(cs, fs.Args(), &Options{
|
|
||||||
|
ps, err := Lint(cs, fs.Args(), &Options{
|
||||||
Tags: strings.Fields(tags),
|
Tags: strings.Fields(tags),
|
||||||
LintTests: tests,
|
LintTests: tests,
|
||||||
Ignores: ignore,
|
Ignores: ignore,
|
||||||
GoVersion: goVersion,
|
GoVersion: goVersion,
|
||||||
ReturnIgnored: showIgnored,
|
ReturnIgnored: showIgnored,
|
||||||
|
Config: cfg,
|
||||||
|
|
||||||
|
MaxConcurrentJobs: maxConcurrentJobs,
|
||||||
|
PrintStats: printStats,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ps []lint.Problem
|
var f format.Formatter
|
||||||
for _, p := range pss {
|
switch formatter {
|
||||||
ps = append(ps, p...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var f OutputFormatter
|
|
||||||
switch format {
|
|
||||||
case "text":
|
case "text":
|
||||||
f = TextOutput{os.Stdout}
|
f = format.Text{W: os.Stdout}
|
||||||
|
case "stylish":
|
||||||
|
f = &format.Stylish{W: os.Stdout}
|
||||||
case "json":
|
case "json":
|
||||||
f = JSONOutput{os.Stdout}
|
f = format.JSON{W: os.Stdout}
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", format)
|
fmt.Fprintf(os.Stderr, "unsupported output format %q\n", formatter)
|
||||||
os.Exit(2)
|
exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
total int
|
||||||
|
errors int
|
||||||
|
warnings int
|
||||||
|
)
|
||||||
|
|
||||||
|
fail := *fs.Lookup("fail").Value.(*list)
|
||||||
|
var allChecks []string
|
||||||
for _, p := range ps {
|
for _, p := range ps {
|
||||||
|
allChecks = append(allChecks, p.Check)
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldExit := lint.FilterChecks(allChecks, fail)
|
||||||
|
|
||||||
|
total = len(ps)
|
||||||
|
for _, p := range ps {
|
||||||
|
if shouldExit[p.Check] {
|
||||||
|
errors++
|
||||||
|
} else {
|
||||||
|
p.Severity = lint.Warning
|
||||||
|
warnings++
|
||||||
|
}
|
||||||
f.Format(p)
|
f.Format(p)
|
||||||
}
|
}
|
||||||
for i, p := range pss {
|
if f, ok := f.(format.Statter); ok {
|
||||||
if len(p) != 0 && confs[i].ExitNonZero {
|
f.Stats(total, errors, warnings)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
if errors > 0 {
|
||||||
|
exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
Config config.Config
|
||||||
|
|
||||||
Tags []string
|
Tags []string
|
||||||
LintTests bool
|
LintTests bool
|
||||||
Ignores string
|
Ignores string
|
||||||
GoVersion int
|
GoVersion int
|
||||||
ReturnIgnored bool
|
ReturnIgnored bool
|
||||||
|
|
||||||
|
MaxConcurrentJobs int
|
||||||
|
PrintStats bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, error) {
|
func Lint(cs []lint.Checker, paths []string, opt *Options) ([]lint.Problem, error) {
|
||||||
|
stats := lint.PerfStats{
|
||||||
|
CheckerInits: map[string]time.Duration{},
|
||||||
|
}
|
||||||
|
|
||||||
if opt == nil {
|
if opt == nil {
|
||||||
opt = &Options{}
|
opt = &Options{}
|
||||||
}
|
}
|
||||||
|
@ -257,94 +261,102 @@ func Lint(cs []lint.Checker, pkgs []string, opt *Options) ([][]lint.Problem, err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
paths := gotool.ImportPaths(pkgs)
|
|
||||||
goFiles, err := resolveRelative(paths, opt.Tags)
|
conf := &packages.Config{
|
||||||
if err != nil {
|
Mode: packages.LoadAllSyntax,
|
||||||
return nil, err
|
Tests: opt.LintTests,
|
||||||
}
|
BuildFlags: []string{
|
||||||
ctx := build.Default
|
"-tags=" + strings.Join(opt.Tags, " "),
|
||||||
ctx.BuildTags = opt.Tags
|
|
||||||
hadError := false
|
|
||||||
conf := &loader.Config{
|
|
||||||
Build: &ctx,
|
|
||||||
ParserMode: parser.ParseComments,
|
|
||||||
ImportPkgs: map[string]bool{},
|
|
||||||
TypeChecker: types.Config{
|
|
||||||
Sizes: types.SizesFor(ctx.Compiler, ctx.GOARCH),
|
|
||||||
Error: func(err error) {
|
|
||||||
// Only print the first error found
|
|
||||||
if hadError {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hadError = true
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
|
||||||
if goFiles {
|
|
||||||
conf.CreateFromFilenames("adhoc", paths...)
|
|
||||||
} else {
|
|
||||||
for _, path := range paths {
|
|
||||||
conf.ImportPkgs[path] = opt.LintTests
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lprog, err := conf.Load()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var problems [][]lint.Problem
|
t := time.Now()
|
||||||
for _, c := range cs {
|
if len(paths) == 0 {
|
||||||
runner := &runner{
|
paths = []string{"."}
|
||||||
checker: c,
|
|
||||||
tags: opt.Tags,
|
|
||||||
ignores: ignores,
|
|
||||||
version: opt.GoVersion,
|
|
||||||
returnIgnored: opt.ReturnIgnored,
|
|
||||||
}
|
}
|
||||||
problems = append(problems, runner.lint(lprog, conf))
|
pkgs, err := packages.Load(conf, paths...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
stats.PackageLoading = time.Since(t)
|
||||||
|
|
||||||
|
var problems []lint.Problem
|
||||||
|
workingPkgs := make([]*packages.Package, 0, len(pkgs))
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
if pkg.IllTyped {
|
||||||
|
problems = append(problems, compileErrors(pkg)...)
|
||||||
|
} else {
|
||||||
|
workingPkgs = append(workingPkgs, pkg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(workingPkgs) == 0 {
|
||||||
|
return problems, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &lint.Linter{
|
||||||
|
Checkers: cs,
|
||||||
|
Ignores: ignores,
|
||||||
|
GoVersion: opt.GoVersion,
|
||||||
|
ReturnIgnored: opt.ReturnIgnored,
|
||||||
|
Config: opt.Config,
|
||||||
|
|
||||||
|
MaxConcurrentJobs: opt.MaxConcurrentJobs,
|
||||||
|
PrintStats: opt.PrintStats,
|
||||||
|
}
|
||||||
|
problems = append(problems, l.Lint(workingPkgs, &stats)...)
|
||||||
|
|
||||||
return problems, nil
|
return problems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shortPath(path string) string {
|
var posRe = regexp.MustCompile(`^(.+?):(\d+)(?::(\d+)?)?$`)
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
func parsePos(pos string) token.Position {
|
||||||
return path
|
if pos == "-" || pos == "" {
|
||||||
|
return token.Position{}
|
||||||
}
|
}
|
||||||
if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) {
|
parts := posRe.FindStringSubmatch(pos)
|
||||||
return rel
|
if parts == nil {
|
||||||
|
panic(fmt.Sprintf("internal error: malformed position %q", pos))
|
||||||
|
}
|
||||||
|
file := parts[1]
|
||||||
|
line, _ := strconv.Atoi(parts[2])
|
||||||
|
col, _ := strconv.Atoi(parts[3])
|
||||||
|
return token.Position{
|
||||||
|
Filename: file,
|
||||||
|
Line: line,
|
||||||
|
Column: col,
|
||||||
}
|
}
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func relativePositionString(pos token.Position) string {
|
func compileErrors(pkg *packages.Package) []lint.Problem {
|
||||||
s := shortPath(pos.Filename)
|
if !pkg.IllTyped {
|
||||||
if pos.IsValid() {
|
return nil
|
||||||
if s != "" {
|
|
||||||
s += ":"
|
|
||||||
}
|
}
|
||||||
s += fmt.Sprintf("%d:%d", pos.Line, pos.Column)
|
if len(pkg.Errors) == 0 {
|
||||||
|
// transitively ill-typed
|
||||||
|
var ps []lint.Problem
|
||||||
|
for _, imp := range pkg.Imports {
|
||||||
|
ps = append(ps, compileErrors(imp)...)
|
||||||
}
|
}
|
||||||
if s == "" {
|
return ps
|
||||||
s = "-"
|
|
||||||
}
|
}
|
||||||
return s
|
var ps []lint.Problem
|
||||||
|
for _, err := range pkg.Errors {
|
||||||
|
p := lint.Problem{
|
||||||
|
Position: parsePos(err.Pos),
|
||||||
|
Text: err.Msg,
|
||||||
|
Checker: "compiler",
|
||||||
|
Check: "compile",
|
||||||
|
}
|
||||||
|
ps = append(ps, p)
|
||||||
|
}
|
||||||
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessArgs(name string, cs []CheckerConfig, args []string) {
|
func ProcessArgs(name string, cs []lint.Checker, args []string) {
|
||||||
flags := FlagSet(name)
|
flags := FlagSet(name)
|
||||||
flags.Parse(args)
|
flags.Parse(args)
|
||||||
|
|
||||||
ProcessFlagSet(cs, flags)
|
ProcessFlagSet(cs, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (runner *runner) lint(lprog *loader.Program, conf *loader.Config) []lint.Problem {
|
|
||||||
l := &lint.Linter{
|
|
||||||
Checker: runner.checker,
|
|
||||||
Ignores: runner.ignores,
|
|
||||||
GoVersion: runner.version,
|
|
||||||
ReturnIgnored: runner.returnIgnored,
|
|
||||||
}
|
|
||||||
return l.Lint(lprog, conf)
|
|
||||||
}
|
|
||||||
|
|
514
vendor/honnef.co/go/tools/simple/lint.go
vendored
514
vendor/honnef.co/go/tools/simple/lint.go
vendored
|
@ -9,6 +9,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
. "honnef.co/go/tools/arg"
|
||||||
"honnef.co/go/tools/internal/sharedcheck"
|
"honnef.co/go/tools/internal/sharedcheck"
|
||||||
"honnef.co/go/tools/lint"
|
"honnef.co/go/tools/lint"
|
||||||
. "honnef.co/go/tools/lint/lintdsl"
|
. "honnef.co/go/tools/lint/lintdsl"
|
||||||
|
@ -32,57 +33,40 @@ func (*Checker) Prefix() string { return "S" }
|
||||||
|
|
||||||
func (c *Checker) Init(prog *lint.Program) {}
|
func (c *Checker) Init(prog *lint.Program) {}
|
||||||
|
|
||||||
func (c *Checker) Funcs() map[string]lint.Func {
|
func (c *Checker) Checks() []lint.Check {
|
||||||
return map[string]lint.Func{
|
return []lint.Check{
|
||||||
"S1000": c.LintSingleCaseSelect,
|
{ID: "S1000", FilterGenerated: true, Fn: c.LintSingleCaseSelect},
|
||||||
"S1001": c.LintLoopCopy,
|
{ID: "S1001", FilterGenerated: true, Fn: c.LintLoopCopy},
|
||||||
"S1002": c.LintIfBoolCmp,
|
{ID: "S1002", FilterGenerated: true, Fn: c.LintIfBoolCmp},
|
||||||
"S1003": c.LintStringsContains,
|
{ID: "S1003", FilterGenerated: true, Fn: c.LintStringsContains},
|
||||||
"S1004": c.LintBytesCompare,
|
{ID: "S1004", FilterGenerated: true, Fn: c.LintBytesCompare},
|
||||||
"S1005": c.LintUnnecessaryBlank,
|
{ID: "S1005", FilterGenerated: true, Fn: c.LintUnnecessaryBlank},
|
||||||
"S1006": c.LintForTrue,
|
{ID: "S1006", FilterGenerated: true, Fn: c.LintForTrue},
|
||||||
"S1007": c.LintRegexpRaw,
|
{ID: "S1007", FilterGenerated: true, Fn: c.LintRegexpRaw},
|
||||||
"S1008": c.LintIfReturn,
|
{ID: "S1008", FilterGenerated: true, Fn: c.LintIfReturn},
|
||||||
"S1009": c.LintRedundantNilCheckWithLen,
|
{ID: "S1009", FilterGenerated: true, Fn: c.LintRedundantNilCheckWithLen},
|
||||||
"S1010": c.LintSlicing,
|
{ID: "S1010", FilterGenerated: true, Fn: c.LintSlicing},
|
||||||
"S1011": c.LintLoopAppend,
|
{ID: "S1011", FilterGenerated: true, Fn: c.LintLoopAppend},
|
||||||
"S1012": c.LintTimeSince,
|
{ID: "S1012", FilterGenerated: true, Fn: c.LintTimeSince},
|
||||||
"S1013": nil,
|
{ID: "S1016", FilterGenerated: true, Fn: c.LintSimplerStructConversion},
|
||||||
"S1014": nil,
|
{ID: "S1017", FilterGenerated: true, Fn: c.LintTrim},
|
||||||
"S1015": nil,
|
{ID: "S1018", FilterGenerated: true, Fn: c.LintLoopSlide},
|
||||||
"S1016": c.LintSimplerStructConversion,
|
{ID: "S1019", FilterGenerated: true, Fn: c.LintMakeLenCap},
|
||||||
"S1017": c.LintTrim,
|
{ID: "S1020", FilterGenerated: true, Fn: c.LintAssertNotNil},
|
||||||
"S1018": c.LintLoopSlide,
|
{ID: "S1021", FilterGenerated: true, Fn: c.LintDeclareAssign},
|
||||||
"S1019": c.LintMakeLenCap,
|
{ID: "S1023", FilterGenerated: true, Fn: c.LintRedundantBreak},
|
||||||
"S1020": c.LintAssertNotNil,
|
{ID: "S1024", FilterGenerated: true, Fn: c.LintTimeUntil},
|
||||||
"S1021": c.LintDeclareAssign,
|
{ID: "S1025", FilterGenerated: true, Fn: c.LintRedundantSprintf},
|
||||||
"S1022": nil,
|
{ID: "S1028", FilterGenerated: true, Fn: c.LintErrorsNewSprintf},
|
||||||
"S1023": c.LintRedundantBreak,
|
{ID: "S1029", FilterGenerated: false, Fn: c.LintRangeStringRunes},
|
||||||
"S1024": c.LintTimeUntil,
|
{ID: "S1030", FilterGenerated: true, Fn: c.LintBytesBufferConversions},
|
||||||
"S1025": c.LintRedundantSprintf,
|
{ID: "S1031", FilterGenerated: true, Fn: c.LintNilCheckAroundRange},
|
||||||
"S1026": nil,
|
{ID: "S1032", FilterGenerated: true, Fn: c.LintSortHelpers},
|
||||||
"S1027": nil,
|
{ID: "S1033", FilterGenerated: true, Fn: c.LintGuardedDelete},
|
||||||
"S1028": c.LintErrorsNewSprintf,
|
{ID: "S1034", FilterGenerated: true, Fn: c.LintSimplifyTypeSwitch},
|
||||||
"S1029": c.LintRangeStringRunes,
|
|
||||||
"S1030": c.LintBytesBufferConversions,
|
|
||||||
"S1031": c.LintNilCheckAroundRange,
|
|
||||||
"S1032": c.LintSortHelpers,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) filterGenerated(files []*ast.File) []*ast.File {
|
|
||||||
if c.CheckGenerated {
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
var out []*ast.File
|
|
||||||
for _, f := range files {
|
|
||||||
if !IsGenerated(f) {
|
|
||||||
out = append(out, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Checker) LintSingleCaseSelect(j *lint.Job) {
|
func (c *Checker) LintSingleCaseSelect(j *lint.Job) {
|
||||||
isSingleSelect := func(node ast.Node) bool {
|
isSingleSelect := func(node ast.Node) bool {
|
||||||
v, ok := node.(*ast.SelectStmt)
|
v, ok := node.(*ast.SelectStmt)
|
||||||
|
@ -120,7 +104,7 @@ func (c *Checker) LintSingleCaseSelect(j *lint.Job) {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +133,8 @@ func (c *Checker) LintLoopCopy(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := j.Program.Info.TypeOf(lhs.X).(*types.Slice); !ok {
|
|
||||||
|
if _, ok := TypeOf(j, lhs.X).(*types.Slice); !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
lidx, ok := lhs.Index.(*ast.Ident)
|
lidx, ok := lhs.Index.(*ast.Ident)
|
||||||
|
@ -160,16 +145,16 @@ func (c *Checker) LintLoopCopy(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if j.Program.Info.TypeOf(lhs) == nil || j.Program.Info.TypeOf(stmt.Rhs[0]) == nil {
|
if TypeOf(j, lhs) == nil || TypeOf(j, stmt.Rhs[0]) == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if j.Program.Info.ObjectOf(lidx) != j.Program.Info.ObjectOf(key) {
|
if ObjectOf(j, lidx) != ObjectOf(j, key) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !types.Identical(j.Program.Info.TypeOf(lhs), j.Program.Info.TypeOf(stmt.Rhs[0])) {
|
if !types.Identical(TypeOf(j, lhs), TypeOf(j, stmt.Rhs[0])) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := j.Program.Info.TypeOf(loop.X).(*types.Slice); !ok {
|
if _, ok := TypeOf(j, loop.X).(*types.Slice); !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,7 +168,7 @@ func (c *Checker) LintLoopCopy(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if j.Program.Info.ObjectOf(ridx) != j.Program.Info.ObjectOf(key) {
|
if ObjectOf(j, ridx) != ObjectOf(j, key) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if rhs, ok := stmt.Rhs[0].(*ast.Ident); ok {
|
} else if rhs, ok := stmt.Rhs[0].(*ast.Ident); ok {
|
||||||
|
@ -191,7 +176,7 @@ func (c *Checker) LintLoopCopy(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if j.Program.Info.ObjectOf(rhs) != j.Program.Info.ObjectOf(value) {
|
if ObjectOf(j, rhs) != ObjectOf(j, value) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -200,7 +185,7 @@ func (c *Checker) LintLoopCopy(j *lint.Job) {
|
||||||
j.Errorf(loop, "should use copy() instead of a loop")
|
j.Errorf(loop, "should use copy() instead of a loop")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,7 +210,7 @@ func (c *Checker) LintIfBoolCmp(j *lint.Job) {
|
||||||
val = BoolConst(j, expr.Y)
|
val = BoolConst(j, expr.Y)
|
||||||
other = expr.X
|
other = expr.X
|
||||||
}
|
}
|
||||||
basic, ok := j.Program.Info.TypeOf(other).Underlying().(*types.Basic)
|
basic, ok := TypeOf(j, other).Underlying().(*types.Basic)
|
||||||
if !ok || basic.Kind() != types.Bool {
|
if !ok || basic.Kind() != types.Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -239,10 +224,13 @@ func (c *Checker) LintIfBoolCmp(j *lint.Job) {
|
||||||
if (l1-len(r))%2 == 1 {
|
if (l1-len(r))%2 == 1 {
|
||||||
r = "!" + r
|
r = "!" + r
|
||||||
}
|
}
|
||||||
|
if IsInTest(j, node) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
j.Errorf(expr, "should omit comparison to bool constant, can be simplified to %s", r)
|
j.Errorf(expr, "should omit comparison to bool constant, can be simplified to %s", r)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,7 +251,7 @@ func (c *Checker) LintBytesBufferConversions(j *lint.Job) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
typ := j.Program.Info.TypeOf(call.Fun)
|
typ := TypeOf(j, call.Fun)
|
||||||
if typ == types.Universe.Lookup("string").Type() && IsCallToAST(j, call.Args[0], "(*bytes.Buffer).Bytes") {
|
if typ == types.Universe.Lookup("string").Type() && IsCallToAST(j, call.Args[0], "(*bytes.Buffer).Bytes") {
|
||||||
j.Errorf(call, "should use %v.String() instead of %v", Render(j, sel.X), Render(j, call))
|
j.Errorf(call, "should use %v.String() instead of %v", Render(j, sel.X), Render(j, call))
|
||||||
} else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && IsCallToAST(j, call.Args[0], "(*bytes.Buffer).String") {
|
} else if typ, ok := typ.(*types.Slice); ok && typ.Elem() == types.Universe.Lookup("byte").Type() && IsCallToAST(j, call.Args[0], "(*bytes.Buffer).String") {
|
||||||
|
@ -272,7 +260,7 @@ func (c *Checker) LintBytesBufferConversions(j *lint.Job) {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,7 +332,7 @@ func (c *Checker) LintStringsContains(j *lint.Job) {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,7 +365,7 @@ func (c *Checker) LintBytesCompare(j *lint.Job) {
|
||||||
j.Errorf(node, "should use %sbytes.Equal(%s) instead", prefix, args)
|
j.Errorf(node, "should use %sbytes.Equal(%s) instead", prefix, args)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,7 +385,7 @@ func (c *Checker) LintForTrue(j *lint.Job) {
|
||||||
j.Errorf(loop, "should use for {} instead of for true {}")
|
j.Errorf(loop, "should use for {} instead of for true {}")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -420,7 +408,7 @@ func (c *Checker) LintRegexpRaw(j *lint.Job) {
|
||||||
// invalid function call
|
// invalid function call
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
lit, ok := call.Args[0].(*ast.BasicLit)
|
lit, ok := call.Args[Arg("regexp.Compile.expr")].(*ast.BasicLit)
|
||||||
if !ok {
|
if !ok {
|
||||||
// TODO(dominikh): support string concat, maybe support constants
|
// TODO(dominikh): support string concat, maybe support constants
|
||||||
return true
|
return true
|
||||||
|
@ -437,6 +425,9 @@ func (c *Checker) LintRegexpRaw(j *lint.Job) {
|
||||||
if !strings.Contains(val, `\\`) {
|
if !strings.Contains(val, `\\`) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if strings.Contains(val, "`") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
bs := false
|
bs := false
|
||||||
for _, c := range val {
|
for _, c := range val {
|
||||||
|
@ -457,7 +448,7 @@ func (c *Checker) LintRegexpRaw(j *lint.Job) {
|
||||||
j.Errorf(call, "should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name)
|
j.Errorf(call, "should use raw string (`...`) with regexp.%s to avoid having to escape twice", sel.Sel.Name)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -524,7 +515,7 @@ func (c *Checker) LintIfReturn(j *lint.Job) {
|
||||||
j.Errorf(n1, "should use 'return <expr>' instead of 'if <expr> { return <bool> }; return <bool>'")
|
j.Errorf(n1, "should use 'return <expr>' instead of 'if <expr> { return <bool> }; return <bool>'")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,7 +538,7 @@ func (c *Checker) LintRedundantNilCheckWithLen(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
c, ok := j.Program.Info.ObjectOf(id).(*types.Const)
|
c, ok := ObjectOf(j, id).(*types.Const)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
@ -600,7 +591,7 @@ func (c *Checker) LintRedundantNilCheckWithLen(j *lint.Job) {
|
||||||
if !ok || yxFun.Name != "len" || len(yx.Args) != 1 {
|
if !ok || yxFun.Name != "len" || len(yx.Args) != 1 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
yxArg, ok := yx.Args[0].(*ast.Ident)
|
yxArg, ok := yx.Args[Arg("len.v")].(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -643,7 +634,7 @@ func (c *Checker) LintRedundantNilCheckWithLen(j *lint.Job) {
|
||||||
// finally check that xx type is one of array, slice, map or chan
|
// finally check that xx type is one of array, slice, map or chan
|
||||||
// this is to prevent false positive in case if xx is a pointer to an array
|
// this is to prevent false positive in case if xx is a pointer to an array
|
||||||
var nilType string
|
var nilType string
|
||||||
switch j.Program.Info.TypeOf(xx).(type) {
|
switch TypeOf(j, xx).(type) {
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
nilType = "nil slices"
|
nilType = "nil slices"
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
|
@ -656,7 +647,7 @@ func (c *Checker) LintRedundantNilCheckWithLen(j *lint.Job) {
|
||||||
j.Errorf(expr, "should omit nil check; len() for %s is defined as zero", nilType)
|
j.Errorf(expr, "should omit nil check; len() for %s is defined as zero", nilType)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -682,29 +673,29 @@ func (c *Checker) LintSlicing(j *lint.Job) {
|
||||||
if !ok || fun.Name != "len" {
|
if !ok || fun.Name != "len" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := j.Program.Info.ObjectOf(fun).(*types.Builtin); !ok {
|
if _, ok := ObjectOf(j, fun).(*types.Builtin); !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
arg, ok := call.Args[0].(*ast.Ident)
|
arg, ok := call.Args[Arg("len.v")].(*ast.Ident)
|
||||||
if !ok || arg.Obj != s.Obj {
|
if !ok || arg.Obj != s.Obj {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
j.Errorf(n, "should omit second index in slice, s[a:len(s)] is identical to s[a:]")
|
j.Errorf(n, "should omit second index in slice, s[a:len(s)] is identical to s[a:]")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refersTo(info *types.Info, expr ast.Expr, ident *ast.Ident) bool {
|
func refersTo(j *lint.Job, expr ast.Expr, ident *ast.Ident) bool {
|
||||||
found := false
|
found := false
|
||||||
fn := func(node ast.Node) bool {
|
fn := func(node ast.Node) bool {
|
||||||
ident2, ok := node.(*ast.Ident)
|
ident2, ok := node.(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if info.ObjectOf(ident) == info.ObjectOf(ident2) {
|
if ObjectOf(j, ident) == ObjectOf(j, ident2) {
|
||||||
found = true
|
found = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -737,7 +728,7 @@ func (c *Checker) LintLoopAppend(j *lint.Job) {
|
||||||
if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 {
|
if stmt.Tok != token.ASSIGN || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if refersTo(j.Program.Info, stmt.Lhs[0], val) {
|
if refersTo(j, stmt.Lhs[0], val) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
call, ok := stmt.Rhs[0].(*ast.CallExpr)
|
call, ok := stmt.Rhs[0].(*ast.CallExpr)
|
||||||
|
@ -751,14 +742,14 @@ func (c *Checker) LintLoopAppend(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
obj := j.Program.Info.ObjectOf(fun)
|
obj := ObjectOf(j, fun)
|
||||||
fn, ok := obj.(*types.Builtin)
|
fn, ok := obj.(*types.Builtin)
|
||||||
if !ok || fn.Name() != "append" {
|
if !ok || fn.Name() != "append" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
src := j.Program.Info.TypeOf(loop.X)
|
src := TypeOf(j, loop.X)
|
||||||
dst := j.Program.Info.TypeOf(call.Args[0])
|
dst := TypeOf(j, call.Args[Arg("append.slice")])
|
||||||
// TODO(dominikh) remove nil check once Go issue #15173 has
|
// TODO(dominikh) remove nil check once Go issue #15173 has
|
||||||
// been fixed
|
// been fixed
|
||||||
if src == nil {
|
if src == nil {
|
||||||
|
@ -768,22 +759,22 @@ func (c *Checker) LintLoopAppend(j *lint.Job) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if Render(j, stmt.Lhs[0]) != Render(j, call.Args[0]) {
|
if Render(j, stmt.Lhs[0]) != Render(j, call.Args[Arg("append.slice")]) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
el, ok := call.Args[1].(*ast.Ident)
|
el, ok := call.Args[Arg("append.elems")].(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if j.Program.Info.ObjectOf(val) != j.Program.Info.ObjectOf(el) {
|
if ObjectOf(j, val) != ObjectOf(j, el) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
j.Errorf(loop, "should replace loop with %s = append(%s, %s...)",
|
j.Errorf(loop, "should replace loop with %s = append(%s, %s...)",
|
||||||
Render(j, stmt.Lhs[0]), Render(j, call.Args[0]), Render(j, loop.X))
|
Render(j, stmt.Lhs[0]), Render(j, call.Args[Arg("append.slice")]), Render(j, loop.X))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -807,7 +798,7 @@ func (c *Checker) LintTimeSince(j *lint.Job) {
|
||||||
j.Errorf(call, "should use time.Since instead of time.Now().Sub")
|
j.Errorf(call, "should use time.Since instead of time.Now().Sub")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -824,13 +815,13 @@ func (c *Checker) LintTimeUntil(j *lint.Job) {
|
||||||
if !IsCallToAST(j, call, "(time.Time).Sub") {
|
if !IsCallToAST(j, call, "(time.Time).Sub") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !IsCallToAST(j, call.Args[0], "time.Now") {
|
if !IsCallToAST(j, call.Args[Arg("(time.Time).Sub.u")], "time.Now") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
j.Errorf(call, "should use time.Until instead of t.Sub(time.Now())")
|
j.Errorf(call, "should use time.Until instead of t.Sub(time.Now())")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -851,7 +842,7 @@ func (c *Checker) LintUnnecessaryBlank(j *lint.Job) {
|
||||||
case *ast.IndexExpr:
|
case *ast.IndexExpr:
|
||||||
// The type-checker should make sure that it's a map, but
|
// The type-checker should make sure that it's a map, but
|
||||||
// let's be safe.
|
// let's be safe.
|
||||||
if _, ok := j.Program.Info.TypeOf(rhs.X).Underlying().(*types.Map); !ok {
|
if _, ok := TypeOf(j, rhs.X).Underlying().(*types.Map); !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case *ast.UnaryExpr:
|
case *ast.UnaryExpr:
|
||||||
|
@ -914,7 +905,7 @@ func (c *Checker) LintUnnecessaryBlank(j *lint.Job) {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -938,7 +929,7 @@ func (c *Checker) LintSimplerStructConversion(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
typ1, _ := j.Program.Info.TypeOf(lit.Type).(*types.Named)
|
typ1, _ := TypeOf(j, lit.Type).(*types.Named)
|
||||||
if typ1 == nil {
|
if typ1 == nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -958,7 +949,7 @@ func (c *Checker) LintSimplerStructConversion(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
typ := j.Program.Info.TypeOf(sel.X)
|
typ := TypeOf(j, sel.X)
|
||||||
return typ, ident, typ != nil
|
return typ, ident, typ != nil
|
||||||
}
|
}
|
||||||
if len(lit.Elts) == 0 {
|
if len(lit.Elts) == 0 {
|
||||||
|
@ -1026,14 +1017,20 @@ func (c *Checker) LintSimplerStructConversion(j *lint.Job) {
|
||||||
if typ1 == typ2 {
|
if typ1 == typ2 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !structsIdentical(s1, s2) {
|
if IsGoVersion(j, 8) {
|
||||||
|
if !types.IdenticalIgnoreTags(s1, s2) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if !types.Identical(s1, s2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
j.Errorf(node, "should convert %s (type %s) to %s instead of using struct literal",
|
j.Errorf(node, "should convert %s (type %s) to %s instead of using struct literal",
|
||||||
ident.Name, typ2.Obj().Name(), typ1.Obj().Name())
|
ident.Name, typ2.Obj().Name(), typ1.Obj().Name())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1066,7 +1063,7 @@ func (c *Checker) LintTrim(j *lint.Job) {
|
||||||
if len(call.Args) != 1 {
|
if len(call.Args) != 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return sameNonDynamic(call.Args[0], ident)
|
return sameNonDynamic(call.Args[Arg("len.v")], ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := func(node ast.Node) bool {
|
fn := func(node ast.Node) bool {
|
||||||
|
@ -1090,22 +1087,26 @@ func (c *Checker) LintTrim(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
call, ok := condCall.Fun.(*ast.SelectorExpr)
|
switch {
|
||||||
if !ok {
|
case IsCallToAST(j, condCall, "strings.HasPrefix"):
|
||||||
return true
|
|
||||||
}
|
|
||||||
if IsIdent(call.X, "strings") {
|
|
||||||
pkg = "strings"
|
pkg = "strings"
|
||||||
} else if IsIdent(call.X, "bytes") {
|
|
||||||
pkg = "bytes"
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if IsIdent(call.Sel, "HasPrefix") {
|
|
||||||
fun = "HasPrefix"
|
fun = "HasPrefix"
|
||||||
} else if IsIdent(call.Sel, "HasSuffix") {
|
case IsCallToAST(j, condCall, "strings.HasSuffix"):
|
||||||
|
pkg = "strings"
|
||||||
fun = "HasSuffix"
|
fun = "HasSuffix"
|
||||||
} else {
|
case IsCallToAST(j, condCall, "strings.Contains"):
|
||||||
|
pkg = "strings"
|
||||||
|
fun = "Contains"
|
||||||
|
case IsCallToAST(j, condCall, "bytes.HasPrefix"):
|
||||||
|
pkg = "bytes"
|
||||||
|
fun = "HasPrefix"
|
||||||
|
case IsCallToAST(j, condCall, "bytes.HasSuffix"):
|
||||||
|
pkg = "bytes"
|
||||||
|
fun = "HasSuffix"
|
||||||
|
case IsCallToAST(j, condCall, "bytes.Contains"):
|
||||||
|
pkg = "bytes"
|
||||||
|
fun = "Contains"
|
||||||
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1122,7 +1123,23 @@ func (c *Checker) LintTrim(j *lint.Job) {
|
||||||
if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) {
|
if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
slice, ok := assign.Rhs[0].(*ast.SliceExpr)
|
|
||||||
|
switch rhs := assign.Rhs[0].(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
if len(rhs.Args) < 2 || !sameNonDynamic(condCall.Args[0], rhs.Args[0]) || !sameNonDynamic(condCall.Args[1], rhs.Args[1]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if IsCallToAST(j, condCall, "strings.HasPrefix") && IsCallToAST(j, rhs, "strings.TrimPrefix") ||
|
||||||
|
IsCallToAST(j, condCall, "strings.HasSuffix") && IsCallToAST(j, rhs, "strings.TrimSuffix") ||
|
||||||
|
IsCallToAST(j, condCall, "strings.Contains") && IsCallToAST(j, rhs, "strings.Replace") ||
|
||||||
|
IsCallToAST(j, condCall, "bytes.HasPrefix") && IsCallToAST(j, rhs, "bytes.TrimPrefix") ||
|
||||||
|
IsCallToAST(j, condCall, "bytes.HasSuffix") && IsCallToAST(j, rhs, "bytes.TrimSuffix") ||
|
||||||
|
IsCallToAST(j, condCall, "bytes.Contains") && IsCallToAST(j, rhs, "bytes.Replace") {
|
||||||
|
j.Errorf(ifstmt, "should replace this if statement with an unconditional %s", CallNameAST(j, rhs))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case *ast.SliceExpr:
|
||||||
|
slice := rhs
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1162,7 +1179,7 @@ func (c *Checker) LintTrim(j *lint.Job) {
|
||||||
if len(index.Args) != 1 {
|
if len(index.Args) != 1 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
id3 := index.Args[0]
|
id3 := index.Args[Arg("len.v")]
|
||||||
switch oid3 := condCall.Args[1].(type) {
|
switch oid3 := condCall.Args[1].(type) {
|
||||||
case *ast.BasicLit:
|
case *ast.BasicLit:
|
||||||
if pkg != "strings" {
|
if pkg != "strings" {
|
||||||
|
@ -1218,8 +1235,11 @@ func (c *Checker) LintTrim(j *lint.Job) {
|
||||||
}
|
}
|
||||||
j.Errorf(ifstmt, "should replace this if statement with an unconditional %s.%s", pkg, replacement)
|
j.Errorf(ifstmt, "should replace this if statement with an unconditional %s.%s", pkg, replacement)
|
||||||
return true
|
return true
|
||||||
|
default:
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1258,7 +1278,7 @@ func (c *Checker) LintLoopSlide(j *lint.Job) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
postvar, ok := post.X.(*ast.Ident)
|
postvar, ok := post.X.(*ast.Ident)
|
||||||
if !ok || j.Program.Info.ObjectOf(postvar) != j.Program.Info.ObjectOf(initvar) {
|
if !ok || ObjectOf(j, postvar) != ObjectOf(j, initvar) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
bin, ok := loop.Cond.(*ast.BinaryExpr)
|
bin, ok := loop.Cond.(*ast.BinaryExpr)
|
||||||
|
@ -1266,7 +1286,7 @@ func (c *Checker) LintLoopSlide(j *lint.Job) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
binx, ok := bin.X.(*ast.Ident)
|
binx, ok := bin.X.(*ast.Ident)
|
||||||
if !ok || j.Program.Info.ObjectOf(binx) != j.Program.Info.ObjectOf(initvar) {
|
if !ok || ObjectOf(j, binx) != ObjectOf(j, initvar) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
biny, ok := bin.Y.(*ast.Ident)
|
biny, ok := bin.Y.(*ast.Ident)
|
||||||
|
@ -1295,8 +1315,8 @@ func (c *Checker) LintLoopSlide(j *lint.Job) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
obj1 := j.Program.Info.ObjectOf(bs1)
|
obj1 := ObjectOf(j, bs1)
|
||||||
obj2 := j.Program.Info.ObjectOf(bs2)
|
obj2 := ObjectOf(j, bs2)
|
||||||
if obj1 != obj2 {
|
if obj1 != obj2 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1305,7 +1325,7 @@ func (c *Checker) LintLoopSlide(j *lint.Job) {
|
||||||
}
|
}
|
||||||
|
|
||||||
index1, ok := lhs.Index.(*ast.Ident)
|
index1, ok := lhs.Index.(*ast.Ident)
|
||||||
if !ok || j.Program.Info.ObjectOf(index1) != j.Program.Info.ObjectOf(initvar) {
|
if !ok || ObjectOf(j, index1) != ObjectOf(j, initvar) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
index2, ok := rhs.Index.(*ast.BinaryExpr)
|
index2, ok := rhs.Index.(*ast.BinaryExpr)
|
||||||
|
@ -1317,14 +1337,14 @@ func (c *Checker) LintLoopSlide(j *lint.Job) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
add2, ok := index2.Y.(*ast.Ident)
|
add2, ok := index2.Y.(*ast.Ident)
|
||||||
if !ok || j.Program.Info.ObjectOf(add2) != j.Program.Info.ObjectOf(initvar) {
|
if !ok || ObjectOf(j, add2) != ObjectOf(j, initvar) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
j.Errorf(loop, "should use copy(%s[:%s], %s[%s:]) instead", Render(j, bs1), Render(j, biny), Render(j, bs1), Render(j, add1))
|
j.Errorf(loop, "should use copy(%s[:%s], %s[%s:]) instead", Render(j, bs1), Render(j, biny), Render(j, bs1), Render(j, add1))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1342,21 +1362,23 @@ func (c *Checker) LintMakeLenCap(j *lint.Job) {
|
||||||
switch len(call.Args) {
|
switch len(call.Args) {
|
||||||
case 2:
|
case 2:
|
||||||
// make(T, len)
|
// make(T, len)
|
||||||
if _, ok := j.Program.Info.TypeOf(call.Args[0]).Underlying().(*types.Slice); ok {
|
if _, ok := TypeOf(j, call.Args[Arg("make.t")]).Underlying().(*types.Slice); ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if IsZero(call.Args[1]) {
|
if IsZero(call.Args[Arg("make.size[0]")]) {
|
||||||
j.Errorf(call.Args[1], "should use make(%s) instead", Render(j, call.Args[0]))
|
j.Errorf(call.Args[Arg("make.size[0]")], "should use make(%s) instead", Render(j, call.Args[Arg("make.t")]))
|
||||||
}
|
}
|
||||||
case 3:
|
case 3:
|
||||||
// make(T, len, cap)
|
// make(T, len, cap)
|
||||||
if Render(j, call.Args[1]) == Render(j, call.Args[2]) {
|
if Render(j, call.Args[Arg("make.size[0]")]) == Render(j, call.Args[Arg("make.size[1]")]) {
|
||||||
j.Errorf(call.Args[1], "should use make(%s, %s) instead", Render(j, call.Args[0]), Render(j, call.Args[1]))
|
j.Errorf(call.Args[Arg("make.size[0]")],
|
||||||
|
"should use make(%s, %s) instead",
|
||||||
|
Render(j, call.Args[Arg("make.t")]), Render(j, call.Args[Arg("make.size[0]")]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1383,7 +1405,7 @@ func (c *Checker) LintAssertNotNil(j *lint.Job) {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
fn := func(node ast.Node) bool {
|
fn1 := func(node ast.Node) bool {
|
||||||
ifstmt, ok := node.(*ast.IfStmt)
|
ifstmt, ok := node.(*ast.IfStmt)
|
||||||
if !ok {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
|
@ -1415,7 +1437,72 @@ func (c *Checker) LintAssertNotNil(j *lint.Job) {
|
||||||
j.Errorf(ifstmt, "when %s is true, %s can't be nil", Render(j, assignIdent), Render(j, assertIdent))
|
j.Errorf(ifstmt, "when %s is true, %s can't be nil", Render(j, assignIdent), Render(j, assertIdent))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
fn2 := func(node ast.Node) bool {
|
||||||
|
// Check that outer ifstmt is an 'if x != nil {}'
|
||||||
|
ifstmt, ok := node.(*ast.IfStmt)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ifstmt.Init != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if ifstmt.Else != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(ifstmt.Body.List) != 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
binop, ok := ifstmt.Cond.(*ast.BinaryExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if binop.Op != token.NEQ {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
lhs, ok := binop.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !IsNil(j, binop.Y) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that inner ifstmt is an `if _, ok := x.(T); ok {}`
|
||||||
|
ifstmt, ok = ifstmt.Body.List[0].(*ast.IfStmt)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
assign, ok := ifstmt.Init.(*ast.AssignStmt)
|
||||||
|
if !ok || len(assign.Lhs) != 2 || len(assign.Rhs) != 1 || !IsBlank(assign.Lhs[0]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
assert, ok := assign.Rhs[0].(*ast.TypeAssertExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
assertIdent, ok := assert.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if lhs.Obj != assertIdent.Obj {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
assignIdent, ok := assign.Lhs[1].(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !isOKCheck(assignIdent, ifstmt.Cond) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
j.Errorf(ifstmt, "when %s is true, %s can't be nil", Render(j, assignIdent), Render(j, assertIdent))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
b1 := fn1(node)
|
||||||
|
b2 := fn2(node)
|
||||||
|
return b1 || b2
|
||||||
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1459,14 +1546,14 @@ func (c *Checker) LintDeclareAssign(j *lint.Job) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if refersTo(j.Program.Info, assign.Rhs[0], ident) {
|
if refersTo(j, assign.Rhs[0], ident) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
j.Errorf(decl, "should merge variable declaration with assignment on next line")
|
j.Errorf(decl, "should merge variable declaration with assignment on next line")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1485,7 +1572,6 @@ func (c *Checker) LintRedundantBreak(j *lint.Job) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
j.Errorf(branch, "redundant break statement")
|
j.Errorf(branch, "redundant break statement")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
fn2 := func(node ast.Node) {
|
fn2 := func(node ast.Node) {
|
||||||
var ret *ast.FieldList
|
var ret *ast.FieldList
|
||||||
|
@ -1519,7 +1605,7 @@ func (c *Checker) LintRedundantBreak(j *lint.Job) {
|
||||||
fn2(node)
|
fn2(node)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1534,11 +1620,11 @@ func (c *Checker) Implements(j *lint.Job, typ types.Type, iface string) bool {
|
||||||
ifaceName = iface
|
ifaceName = iface
|
||||||
} else {
|
} else {
|
||||||
pkgName := iface[:idx]
|
pkgName := iface[:idx]
|
||||||
pkg := j.Program.Prog.Package(pkgName)
|
pkg := j.Program.Package(pkgName)
|
||||||
if pkg == nil {
|
if pkg == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
scope = pkg.Pkg.Scope()
|
scope = pkg.Types.Scope()
|
||||||
ifaceName = iface[idx+1:]
|
ifaceName = iface[idx+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1565,12 +1651,11 @@ func (c *Checker) LintRedundantSprintf(j *lint.Job) {
|
||||||
if len(call.Args) != 2 {
|
if len(call.Args) != 2 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if s, ok := ExprToString(j, call.Args[0]); !ok || s != "%s" {
|
if s, ok := ExprToString(j, call.Args[Arg("fmt.Sprintf.format")]); !ok || s != "%s" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
pkg := j.NodePackage(call)
|
arg := call.Args[Arg("fmt.Sprintf.a[0]")]
|
||||||
arg := call.Args[1]
|
typ := TypeOf(j, arg)
|
||||||
typ := pkg.Info.TypeOf(arg)
|
|
||||||
|
|
||||||
if c.Implements(j, typ, "fmt.Stringer") {
|
if c.Implements(j, typ, "fmt.Stringer") {
|
||||||
j.Errorf(call, "should use String() instead of fmt.Sprintf")
|
j.Errorf(call, "should use String() instead of fmt.Sprintf")
|
||||||
|
@ -1586,7 +1671,7 @@ func (c *Checker) LintRedundantSprintf(j *lint.Job) {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1597,13 +1682,13 @@ func (c *Checker) LintErrorsNewSprintf(j *lint.Job) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
call := node.(*ast.CallExpr)
|
call := node.(*ast.CallExpr)
|
||||||
if !IsCallToAST(j, call.Args[0], "fmt.Sprintf") {
|
if !IsCallToAST(j, call.Args[Arg("errors.New.text")], "fmt.Sprintf") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
j.Errorf(node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))")
|
j.Errorf(node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1643,13 +1728,13 @@ func (c *Checker) LintNilCheckAroundRange(j *lint.Job) {
|
||||||
if ifXIdent.Obj != rangeXIdent.Obj {
|
if ifXIdent.Obj != rangeXIdent.Obj {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
switch j.Program.Info.TypeOf(rangeXIdent).(type) {
|
switch TypeOf(j, rangeXIdent).(type) {
|
||||||
case *types.Slice, *types.Map:
|
case *types.Slice, *types.Map:
|
||||||
j.Errorf(node, "unnecessary nil check around range")
|
j.Errorf(node, "unnecessary nil check around range")
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fn)
|
ast.Inspect(f, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1708,7 +1793,7 @@ func (c *Checker) LintSortHelpers(j *lint.Job) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
call := node.(*ast.CallExpr)
|
call := node.(*ast.CallExpr)
|
||||||
typeconv := call.Args[0].(*ast.CallExpr)
|
typeconv := call.Args[Arg("sort.Sort.data")].(*ast.CallExpr)
|
||||||
sel := typeconv.Fun.(*ast.SelectorExpr)
|
sel := typeconv.Fun.(*ast.SelectorExpr)
|
||||||
name := SelectorName(j, sel)
|
name := SelectorName(j, sel)
|
||||||
|
|
||||||
|
@ -1733,7 +1818,150 @@ func (c *Checker) LintSortHelpers(j *lint.Job) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range c.filterGenerated(j.Program.Files) {
|
for _, f := range j.Program.Files {
|
||||||
ast.Inspect(f, fnFuncs)
|
ast.Inspect(f, fnFuncs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Checker) LintGuardedDelete(j *lint.Job) {
|
||||||
|
isCommaOkMapIndex := func(stmt ast.Stmt) (b *ast.Ident, m ast.Expr, key ast.Expr, ok bool) {
|
||||||
|
// Has to be of the form `_, <b:*ast.Ident> = <m:*types.Map>[<key>]
|
||||||
|
|
||||||
|
assign, ok := stmt.(*ast.AssignStmt)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
if len(assign.Lhs) != 2 || len(assign.Rhs) != 1 {
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
if !IsBlank(assign.Lhs[0]) {
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
ident, ok := assign.Lhs[1].(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
index, ok := assign.Rhs[0].(*ast.IndexExpr)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
if _, ok := TypeOf(j, index.X).(*types.Map); !ok {
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
key = index.Index
|
||||||
|
return ident, index.X, key, true
|
||||||
|
}
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
stmt, ok := node.(*ast.IfStmt)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if len(stmt.Body.List) != 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if stmt.Else != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
expr, ok := stmt.Body.List[0].(*ast.ExprStmt)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
call, ok := expr.X.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !IsCallToAST(j, call, "delete") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
b, m, key, ok := isCommaOkMapIndex(stmt.Init)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if cond, ok := stmt.Cond.(*ast.Ident); !ok || ObjectOf(j, cond) != ObjectOf(j, b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if Render(j, call.Args[0]) != Render(j, m) || Render(j, call.Args[1]) != Render(j, key) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
j.Errorf(stmt, "unnecessary guard around call to delete")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
ast.Inspect(f, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) LintSimplifyTypeSwitch(j *lint.Job) {
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
stmt, ok := node.(*ast.TypeSwitchStmt)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if stmt.Init != nil {
|
||||||
|
// bailing out for now, can't anticipate how type switches with initializers are being used
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
expr, ok := stmt.Assign.(*ast.ExprStmt)
|
||||||
|
if !ok {
|
||||||
|
// the user is in fact assigning the result
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
assert := expr.X.(*ast.TypeAssertExpr)
|
||||||
|
ident, ok := assert.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
x := ObjectOf(j, ident)
|
||||||
|
var allOffenders []ast.Node
|
||||||
|
for _, clause := range stmt.Body.List {
|
||||||
|
clause := clause.(*ast.CaseClause)
|
||||||
|
if len(clause.List) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hasUnrelatedAssertion := false
|
||||||
|
var offenders []ast.Node
|
||||||
|
ast.Inspect(clause, func(node ast.Node) bool {
|
||||||
|
assert2, ok := node.(*ast.TypeAssertExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ident, ok := assert2.X.(*ast.Ident)
|
||||||
|
if !ok {
|
||||||
|
hasUnrelatedAssertion = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ObjectOf(j, ident) != x {
|
||||||
|
hasUnrelatedAssertion = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !types.Identical(TypeOf(j, clause.List[0]), TypeOf(j, assert2.Type)) {
|
||||||
|
hasUnrelatedAssertion = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
offenders = append(offenders, assert2)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if !hasUnrelatedAssertion {
|
||||||
|
// don't flag cases that have other type assertions
|
||||||
|
// unrelated to the one in the case clause. often
|
||||||
|
// times, this is done for symmetry, when two
|
||||||
|
// different values have to be asserted to the same
|
||||||
|
// type.
|
||||||
|
allOffenders = append(allOffenders, offenders...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(allOffenders) != 0 {
|
||||||
|
at := ""
|
||||||
|
for _, offender := range allOffenders {
|
||||||
|
pos := j.Program.DisplayPosition(offender.Pos())
|
||||||
|
at += "\n\t" + pos.String()
|
||||||
|
}
|
||||||
|
j.Errorf(expr, "assigning the result of this type assertion to a variable (switch %s := %s.(type)) could eliminate the following type assertions:%s", Render(j, ident), Render(j, ident), at)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
ast.Inspect(f, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
7
vendor/honnef.co/go/tools/simple/lint17.go
vendored
7
vendor/honnef.co/go/tools/simple/lint17.go
vendored
|
@ -1,7 +0,0 @@
|
||||||
// +build !go1.8
|
|
||||||
|
|
||||||
package simple
|
|
||||||
|
|
||||||
import "go/types"
|
|
||||||
|
|
||||||
var structsIdentical = types.Identical
|
|
7
vendor/honnef.co/go/tools/simple/lint18.go
vendored
7
vendor/honnef.co/go/tools/simple/lint18.go
vendored
|
@ -1,7 +0,0 @@
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package simple
|
|
||||||
|
|
||||||
import "go/types"
|
|
||||||
|
|
||||||
var structsIdentical = types.IdenticalIgnoreTags
|
|
48
vendor/honnef.co/go/tools/ssa/ssautil/load.go
vendored
48
vendor/honnef.co/go/tools/ssa/ssautil/load.go
vendored
|
@ -12,9 +12,57 @@ import (
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/loader"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
"honnef.co/go/tools/ssa"
|
"honnef.co/go/tools/ssa"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Packages creates an SSA program for a set of packages loaded from
|
||||||
|
// source syntax using the golang.org/x/tools/go/packages.Load function.
|
||||||
|
// It creates and returns an SSA package for each well-typed package in
|
||||||
|
// the initial list. The resulting list of packages has the same length
|
||||||
|
// as initial, and contains a nil if SSA could not be constructed for
|
||||||
|
// the corresponding initial package.
|
||||||
|
//
|
||||||
|
// Code for bodies of functions is not built until Build is called
|
||||||
|
// on the resulting Program.
|
||||||
|
//
|
||||||
|
// The mode parameter controls diagnostics and checking during SSA construction.
|
||||||
|
//
|
||||||
|
func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) {
|
||||||
|
var fset *token.FileSet
|
||||||
|
if len(initial) > 0 {
|
||||||
|
fset = initial[0].Fset
|
||||||
|
}
|
||||||
|
|
||||||
|
prog := ssa.NewProgram(fset, mode)
|
||||||
|
seen := make(map[*packages.Package]*ssa.Package)
|
||||||
|
var create func(p *packages.Package) *ssa.Package
|
||||||
|
create = func(p *packages.Package) *ssa.Package {
|
||||||
|
ssapkg, ok := seen[p]
|
||||||
|
if !ok {
|
||||||
|
if p.Types == nil || p.IllTyped {
|
||||||
|
// not well typed
|
||||||
|
seen[p] = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ssapkg = prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
|
||||||
|
seen[p] = ssapkg
|
||||||
|
|
||||||
|
for _, imp := range p.Imports {
|
||||||
|
create(imp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ssapkg
|
||||||
|
}
|
||||||
|
|
||||||
|
var ssapkgs []*ssa.Package
|
||||||
|
for _, p := range initial {
|
||||||
|
ssapkgs = append(ssapkgs, create(p))
|
||||||
|
}
|
||||||
|
return prog, ssapkgs
|
||||||
|
}
|
||||||
|
|
||||||
// CreateProgram returns a new program in SSA form, given a program
|
// CreateProgram returns a new program in SSA form, given a program
|
||||||
// loaded from source. An SSA package is created for each transitively
|
// loaded from source. An SSA package is created for each transitively
|
||||||
// error-free package of lprog.
|
// error-free package of lprog.
|
||||||
|
|
41
vendor/honnef.co/go/tools/ssautil/ssautil.go
vendored
Normal file
41
vendor/honnef.co/go/tools/ssautil/ssautil.go
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package ssautil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"honnef.co/go/tools/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Reachable(from, to *ssa.BasicBlock) bool {
|
||||||
|
if from == to {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if from.Dominates(to) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
Walk(from, func(b *ssa.BasicBlock) bool {
|
||||||
|
if b == to {
|
||||||
|
found = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func Walk(b *ssa.BasicBlock, fn func(*ssa.BasicBlock) bool) {
|
||||||
|
seen := map[*ssa.BasicBlock]bool{}
|
||||||
|
wl := []*ssa.BasicBlock{b}
|
||||||
|
for len(wl) > 0 {
|
||||||
|
b := wl[len(wl)-1]
|
||||||
|
wl = wl[:len(wl)-1]
|
||||||
|
if seen[b] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[b] = true
|
||||||
|
if !fn(b) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wl = append(wl, b.Succs...)
|
||||||
|
}
|
||||||
|
}
|
717
vendor/honnef.co/go/tools/staticcheck/lint.go
vendored
717
vendor/honnef.co/go/tools/staticcheck/lint.go
vendored
File diff suppressed because it is too large
Load diff
643
vendor/honnef.co/go/tools/stylecheck/lint.go
vendored
Normal file
643
vendor/honnef.co/go/tools/stylecheck/lint.go
vendored
Normal file
|
@ -0,0 +1,643 @@
|
||||||
|
package stylecheck // import "honnef.co/go/tools/stylecheck"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/constant"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint"
|
||||||
|
. "honnef.co/go/tools/lint/lintdsl"
|
||||||
|
"honnef.co/go/tools/ssa"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/types/typeutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Checker struct {
|
||||||
|
CheckGenerated bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChecker() *Checker {
|
||||||
|
return &Checker{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Checker) Name() string { return "stylecheck" }
|
||||||
|
func (*Checker) Prefix() string { return "ST" }
|
||||||
|
func (c *Checker) Init(prog *lint.Program) {}
|
||||||
|
|
||||||
|
func (c *Checker) Checks() []lint.Check {
|
||||||
|
return []lint.Check{
|
||||||
|
{ID: "ST1000", FilterGenerated: false, Fn: c.CheckPackageComment},
|
||||||
|
{ID: "ST1001", FilterGenerated: true, Fn: c.CheckDotImports},
|
||||||
|
// {ID: "ST1002", FilterGenerated: true, Fn: c.CheckBlankImports},
|
||||||
|
{ID: "ST1003", FilterGenerated: true, Fn: c.CheckNames},
|
||||||
|
// {ID: "ST1004", FilterGenerated: false, Fn: nil, },
|
||||||
|
{ID: "ST1005", FilterGenerated: false, Fn: c.CheckErrorStrings},
|
||||||
|
{ID: "ST1006", FilterGenerated: false, Fn: c.CheckReceiverNames},
|
||||||
|
// {ID: "ST1007", FilterGenerated: true, Fn: c.CheckIncDec},
|
||||||
|
{ID: "ST1008", FilterGenerated: false, Fn: c.CheckErrorReturn},
|
||||||
|
// {ID: "ST1009", FilterGenerated: false, Fn: c.CheckUnexportedReturn},
|
||||||
|
// {ID: "ST1010", FilterGenerated: false, Fn: c.CheckContextFirstArg},
|
||||||
|
{ID: "ST1011", FilterGenerated: false, Fn: c.CheckTimeNames},
|
||||||
|
{ID: "ST1012", FilterGenerated: false, Fn: c.CheckErrorVarNames},
|
||||||
|
{ID: "ST1013", FilterGenerated: true, Fn: c.CheckHTTPStatusCodes},
|
||||||
|
{ID: "ST1015", FilterGenerated: true, Fn: c.CheckDefaultCaseOrder},
|
||||||
|
{ID: "ST1016", FilterGenerated: false, Fn: c.CheckReceiverNamesIdentical},
|
||||||
|
{ID: "ST1017", FilterGenerated: true, Fn: c.CheckYodaConditions},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckPackageComment(j *lint.Job) {
|
||||||
|
// - At least one file in a non-main package should have a package comment
|
||||||
|
//
|
||||||
|
// - The comment should be of the form
|
||||||
|
// "Package x ...". This has a slight potential for false
|
||||||
|
// positives, as multiple files can have package comments, in
|
||||||
|
// which case they get appended. But that doesn't happen a lot in
|
||||||
|
// the real world.
|
||||||
|
|
||||||
|
for _, pkg := range j.Program.InitialPackages {
|
||||||
|
if pkg.Name == "main" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hasDocs := false
|
||||||
|
for _, f := range pkg.Syntax {
|
||||||
|
if IsInTest(j, f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if f.Doc != nil && len(f.Doc.List) > 0 {
|
||||||
|
hasDocs = true
|
||||||
|
prefix := "Package " + f.Name.Name + " "
|
||||||
|
if !strings.HasPrefix(strings.TrimSpace(f.Doc.Text()), prefix) {
|
||||||
|
j.Errorf(f.Doc, `package comment should be of the form "%s..."`, prefix)
|
||||||
|
}
|
||||||
|
f.Doc.Text()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasDocs {
|
||||||
|
for _, f := range pkg.Syntax {
|
||||||
|
if IsInTest(j, f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
j.Errorf(f, "at least one file in a package should have a package comment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckDotImports(j *lint.Job) {
|
||||||
|
for _, pkg := range j.Program.InitialPackages {
|
||||||
|
for _, f := range pkg.Syntax {
|
||||||
|
imports:
|
||||||
|
for _, imp := range f.Imports {
|
||||||
|
path := imp.Path.Value
|
||||||
|
path = path[1 : len(path)-1]
|
||||||
|
for _, w := range pkg.Config.DotImportWhitelist {
|
||||||
|
if w == path {
|
||||||
|
continue imports
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if imp.Name != nil && imp.Name.Name == "." && !IsInTest(j, f) {
|
||||||
|
j.Errorf(imp, "should not use dot imports")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckBlankImports(j *lint.Job) {
|
||||||
|
fset := j.Program.Fset()
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
if IsInMain(j, f) || IsInTest(j, f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect imports of the form `import _ "foo"`, i.e. with no
|
||||||
|
// parentheses, as their comment will be associated with the
|
||||||
|
// (paren-free) GenDecl, not the import spec itself.
|
||||||
|
//
|
||||||
|
// We don't directly process the GenDecl so that we can
|
||||||
|
// correctly handle the following:
|
||||||
|
//
|
||||||
|
// import _ "foo"
|
||||||
|
// import _ "bar"
|
||||||
|
//
|
||||||
|
// where only the first import should get flagged.
|
||||||
|
skip := map[ast.Spec]bool{}
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *ast.File:
|
||||||
|
return true
|
||||||
|
case *ast.GenDecl:
|
||||||
|
if node.Tok != token.IMPORT {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if node.Lparen == token.NoPos && node.Doc != nil {
|
||||||
|
skip[node.Specs[0]] = true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
for i, imp := range f.Imports {
|
||||||
|
pos := fset.Position(imp.Pos())
|
||||||
|
|
||||||
|
if !IsBlank(imp.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Only flag the first blank import in a group of imports,
|
||||||
|
// or don't flag any of them, if the first one is
|
||||||
|
// commented
|
||||||
|
if i > 0 {
|
||||||
|
prev := f.Imports[i-1]
|
||||||
|
prevPos := fset.Position(prev.Pos())
|
||||||
|
if pos.Line-1 == prevPos.Line && IsBlank(prev.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if imp.Doc == nil && imp.Comment == nil && !skip[imp] {
|
||||||
|
j.Errorf(imp, "a blank import should be only in a main or test package, or have a comment justifying it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckIncDec(j *lint.Job) {
|
||||||
|
// TODO(dh): this can be noisy for function bodies that look like this:
|
||||||
|
// x += 3
|
||||||
|
// ...
|
||||||
|
// x += 2
|
||||||
|
// ...
|
||||||
|
// x += 1
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
assign, ok := node.(*ast.AssignStmt)
|
||||||
|
if !ok || (assign.Tok != token.ADD_ASSIGN && assign.Tok != token.SUB_ASSIGN) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (len(assign.Lhs) != 1 || len(assign.Rhs) != 1) ||
|
||||||
|
!IsIntLiteral(assign.Rhs[0], "1") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix := ""
|
||||||
|
switch assign.Tok {
|
||||||
|
case token.ADD_ASSIGN:
|
||||||
|
suffix = "++"
|
||||||
|
case token.SUB_ASSIGN:
|
||||||
|
suffix = "--"
|
||||||
|
}
|
||||||
|
|
||||||
|
j.Errorf(assign, "should replace %s with %s%s", Render(j, assign), Render(j, assign.Lhs[0]), suffix)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
ast.Inspect(f, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckErrorReturn(j *lint.Job) {
|
||||||
|
fnLoop:
|
||||||
|
for _, fn := range j.Program.InitialFunctions {
|
||||||
|
sig := fn.Type().(*types.Signature)
|
||||||
|
rets := sig.Results()
|
||||||
|
if rets == nil || rets.Len() < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rets.At(rets.Len()-1).Type() == types.Universe.Lookup("error").Type() {
|
||||||
|
// Last return type is error. If the function also returns
|
||||||
|
// errors in other positions, that's fine.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := rets.Len() - 2; i >= 0; i-- {
|
||||||
|
if rets.At(i).Type() == types.Universe.Lookup("error").Type() {
|
||||||
|
j.Errorf(rets.At(i), "error should be returned as the last argument")
|
||||||
|
continue fnLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckUnexportedReturn checks that exported functions on exported
|
||||||
|
// types do not return unexported types.
|
||||||
|
func (c *Checker) CheckUnexportedReturn(j *lint.Job) {
|
||||||
|
for _, fn := range j.Program.InitialFunctions {
|
||||||
|
if fn.Synthetic != "" || fn.Parent() != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !ast.IsExported(fn.Name()) || IsInMain(j, fn) || IsInTest(j, fn) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sig := fn.Type().(*types.Signature)
|
||||||
|
if sig.Recv() != nil && !ast.IsExported(Dereference(sig.Recv().Type()).(*types.Named).Obj().Name()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res := sig.Results()
|
||||||
|
for i := 0; i < res.Len(); i++ {
|
||||||
|
if named, ok := DereferenceR(res.At(i).Type()).(*types.Named); ok &&
|
||||||
|
!ast.IsExported(named.Obj().Name()) &&
|
||||||
|
named != types.Universe.Lookup("error").Type() {
|
||||||
|
j.Errorf(fn, "should not return unexported type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckReceiverNames(j *lint.Job) {
|
||||||
|
for _, pkg := range j.Program.InitialPackages {
|
||||||
|
for _, m := range pkg.SSA.Members {
|
||||||
|
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
|
||||||
|
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
|
||||||
|
for _, sel := range ms {
|
||||||
|
fn := sel.Obj().(*types.Func)
|
||||||
|
recv := fn.Type().(*types.Signature).Recv()
|
||||||
|
if Dereference(recv.Type()) != T.Type() {
|
||||||
|
// skip embedded methods
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if recv.Name() == "self" || recv.Name() == "this" {
|
||||||
|
j.Errorf(recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`)
|
||||||
|
}
|
||||||
|
if recv.Name() == "_" {
|
||||||
|
j.Errorf(recv, "receiver name should not be an underscore, omit the name if it is unused")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckReceiverNamesIdentical(j *lint.Job) {
|
||||||
|
for _, pkg := range j.Program.InitialPackages {
|
||||||
|
for _, m := range pkg.SSA.Members {
|
||||||
|
names := map[string]int{}
|
||||||
|
|
||||||
|
var firstFn *types.Func
|
||||||
|
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
|
||||||
|
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
|
||||||
|
for _, sel := range ms {
|
||||||
|
fn := sel.Obj().(*types.Func)
|
||||||
|
recv := fn.Type().(*types.Signature).Recv()
|
||||||
|
if Dereference(recv.Type()) != T.Type() {
|
||||||
|
// skip embedded methods
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if firstFn == nil {
|
||||||
|
firstFn = fn
|
||||||
|
}
|
||||||
|
if recv.Name() != "" && recv.Name() != "_" {
|
||||||
|
names[recv.Name()]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(names) > 1 {
|
||||||
|
var seen []string
|
||||||
|
for name, count := range names {
|
||||||
|
seen = append(seen, fmt.Sprintf("%dx %q", count, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
j.Errorf(firstFn, "methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckContextFirstArg(j *lint.Job) {
|
||||||
|
// TODO(dh): this check doesn't apply to test helpers. Example from the stdlib:
|
||||||
|
// func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
|
||||||
|
fnLoop:
|
||||||
|
for _, fn := range j.Program.InitialFunctions {
|
||||||
|
if fn.Synthetic != "" || fn.Parent() != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
params := fn.Signature.Params()
|
||||||
|
if params.Len() < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if types.TypeString(params.At(0).Type(), nil) == "context.Context" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := 1; i < params.Len(); i++ {
|
||||||
|
param := params.At(i)
|
||||||
|
if types.TypeString(param.Type(), nil) == "context.Context" {
|
||||||
|
j.Errorf(param, "context.Context should be the first argument of a function")
|
||||||
|
continue fnLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckErrorStrings(j *lint.Job) {
|
||||||
|
fnNames := map[*ssa.Package]map[string]bool{}
|
||||||
|
for _, fn := range j.Program.InitialFunctions {
|
||||||
|
m := fnNames[fn.Package()]
|
||||||
|
if m == nil {
|
||||||
|
m = map[string]bool{}
|
||||||
|
fnNames[fn.Package()] = m
|
||||||
|
}
|
||||||
|
m[fn.Name()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range j.Program.InitialFunctions {
|
||||||
|
if IsInTest(j, fn) {
|
||||||
|
// We don't care about malformed error messages in tests;
|
||||||
|
// they're usually for direct human consumption, not part
|
||||||
|
// of an API
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, block := range fn.Blocks {
|
||||||
|
instrLoop:
|
||||||
|
for _, ins := range block.Instrs {
|
||||||
|
call, ok := ins.(*ssa.Call)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !IsCallTo(call.Common(), "errors.New") && !IsCallTo(call.Common(), "fmt.Errorf") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
k, ok := call.Common().Args[0].(*ssa.Const)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s := constant.StringVal(k.Value)
|
||||||
|
if len(s) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch s[len(s)-1] {
|
||||||
|
case '.', ':', '!', '\n':
|
||||||
|
j.Errorf(call, "error strings should not end with punctuation or a newline")
|
||||||
|
}
|
||||||
|
idx := strings.IndexByte(s, ' ')
|
||||||
|
if idx == -1 {
|
||||||
|
// single word error message, probably not a real
|
||||||
|
// error but something used in tests or during
|
||||||
|
// debugging
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
word := s[:idx]
|
||||||
|
first, n := utf8.DecodeRuneInString(word)
|
||||||
|
if !unicode.IsUpper(first) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, c := range word[n:] {
|
||||||
|
if unicode.IsUpper(c) {
|
||||||
|
// Word is probably an initialism or
|
||||||
|
// multi-word function name
|
||||||
|
continue instrLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
|
||||||
|
if fnNames[fn.Package()][word] {
|
||||||
|
// Word is probably the name of a function in this package
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// First word in error starts with a capital
|
||||||
|
// letter, and the word doesn't contain any other
|
||||||
|
// capitals, making it unlikely to be an
|
||||||
|
// initialism or multi-word function name.
|
||||||
|
//
|
||||||
|
// It could still be a proper noun, though.
|
||||||
|
|
||||||
|
j.Errorf(call, "error strings should not be capitalized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckTimeNames(j *lint.Job) {
|
||||||
|
suffixes := []string{
|
||||||
|
"Sec", "Secs", "Seconds",
|
||||||
|
"Msec", "Msecs",
|
||||||
|
"Milli", "Millis", "Milliseconds",
|
||||||
|
"Usec", "Usecs", "Microseconds",
|
||||||
|
"MS", "Ms",
|
||||||
|
}
|
||||||
|
fn := func(T types.Type, names []*ast.Ident) {
|
||||||
|
if !IsType(T, "time.Duration") && !IsType(T, "*time.Duration") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, name := range names {
|
||||||
|
for _, suffix := range suffixes {
|
||||||
|
if strings.HasSuffix(name.Name, suffix) {
|
||||||
|
j.Errorf(name, "var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
T := TypeOf(j, node.Type)
|
||||||
|
fn(T, node.Names)
|
||||||
|
case *ast.FieldList:
|
||||||
|
for _, field := range node.List {
|
||||||
|
T := TypeOf(j, field.Type)
|
||||||
|
fn(T, field.Names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckErrorVarNames(j *lint.Job) {
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
for _, decl := range f.Decls {
|
||||||
|
gen, ok := decl.(*ast.GenDecl)
|
||||||
|
if !ok || gen.Tok != token.VAR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, spec := range gen.Specs {
|
||||||
|
spec := spec.(*ast.ValueSpec)
|
||||||
|
if len(spec.Names) != len(spec.Values) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, name := range spec.Names {
|
||||||
|
val := spec.Values[i]
|
||||||
|
if !IsCallToAST(j, val, "errors.New") && !IsCallToAST(j, val, "fmt.Errorf") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := "err"
|
||||||
|
if name.IsExported() {
|
||||||
|
prefix = "Err"
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(name.Name, prefix) {
|
||||||
|
j.Errorf(name, "error var %s should have name of the form %sFoo", name.Name, prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpStatusCodes = map[int]string{
|
||||||
|
100: "StatusContinue",
|
||||||
|
101: "StatusSwitchingProtocols",
|
||||||
|
102: "StatusProcessing",
|
||||||
|
200: "StatusOK",
|
||||||
|
201: "StatusCreated",
|
||||||
|
202: "StatusAccepted",
|
||||||
|
203: "StatusNonAuthoritativeInfo",
|
||||||
|
204: "StatusNoContent",
|
||||||
|
205: "StatusResetContent",
|
||||||
|
206: "StatusPartialContent",
|
||||||
|
207: "StatusMultiStatus",
|
||||||
|
208: "StatusAlreadyReported",
|
||||||
|
226: "StatusIMUsed",
|
||||||
|
300: "StatusMultipleChoices",
|
||||||
|
301: "StatusMovedPermanently",
|
||||||
|
302: "StatusFound",
|
||||||
|
303: "StatusSeeOther",
|
||||||
|
304: "StatusNotModified",
|
||||||
|
305: "StatusUseProxy",
|
||||||
|
307: "StatusTemporaryRedirect",
|
||||||
|
308: "StatusPermanentRedirect",
|
||||||
|
400: "StatusBadRequest",
|
||||||
|
401: "StatusUnauthorized",
|
||||||
|
402: "StatusPaymentRequired",
|
||||||
|
403: "StatusForbidden",
|
||||||
|
404: "StatusNotFound",
|
||||||
|
405: "StatusMethodNotAllowed",
|
||||||
|
406: "StatusNotAcceptable",
|
||||||
|
407: "StatusProxyAuthRequired",
|
||||||
|
408: "StatusRequestTimeout",
|
||||||
|
409: "StatusConflict",
|
||||||
|
410: "StatusGone",
|
||||||
|
411: "StatusLengthRequired",
|
||||||
|
412: "StatusPreconditionFailed",
|
||||||
|
413: "StatusRequestEntityTooLarge",
|
||||||
|
414: "StatusRequestURITooLong",
|
||||||
|
415: "StatusUnsupportedMediaType",
|
||||||
|
416: "StatusRequestedRangeNotSatisfiable",
|
||||||
|
417: "StatusExpectationFailed",
|
||||||
|
418: "StatusTeapot",
|
||||||
|
422: "StatusUnprocessableEntity",
|
||||||
|
423: "StatusLocked",
|
||||||
|
424: "StatusFailedDependency",
|
||||||
|
426: "StatusUpgradeRequired",
|
||||||
|
428: "StatusPreconditionRequired",
|
||||||
|
429: "StatusTooManyRequests",
|
||||||
|
431: "StatusRequestHeaderFieldsTooLarge",
|
||||||
|
451: "StatusUnavailableForLegalReasons",
|
||||||
|
500: "StatusInternalServerError",
|
||||||
|
501: "StatusNotImplemented",
|
||||||
|
502: "StatusBadGateway",
|
||||||
|
503: "StatusServiceUnavailable",
|
||||||
|
504: "StatusGatewayTimeout",
|
||||||
|
505: "StatusHTTPVersionNotSupported",
|
||||||
|
506: "StatusVariantAlsoNegotiates",
|
||||||
|
507: "StatusInsufficientStorage",
|
||||||
|
508: "StatusLoopDetected",
|
||||||
|
510: "StatusNotExtended",
|
||||||
|
511: "StatusNetworkAuthenticationRequired",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckHTTPStatusCodes(j *lint.Job) {
|
||||||
|
for _, pkg := range j.Program.InitialPackages {
|
||||||
|
whitelist := map[string]bool{}
|
||||||
|
for _, code := range pkg.Config.HTTPStatusCodeWhitelist {
|
||||||
|
whitelist[code] = true
|
||||||
|
}
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
call, ok := node.(*ast.CallExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var arg int
|
||||||
|
switch CallNameAST(j, call) {
|
||||||
|
case "net/http.Error":
|
||||||
|
arg = 2
|
||||||
|
case "net/http.Redirect":
|
||||||
|
arg = 3
|
||||||
|
case "net/http.StatusText":
|
||||||
|
arg = 0
|
||||||
|
case "net/http.RedirectHandler":
|
||||||
|
arg = 1
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
lit, ok := call.Args[arg].(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if whitelist[lit.Value] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(lit.Value)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
s, ok := httpStatusCodes[n]
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
j.Errorf(lit, "should use constant http.%s instead of numeric literal %d", s, n)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range pkg.Syntax {
|
||||||
|
ast.Inspect(f, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckDefaultCaseOrder(j *lint.Job) {
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
stmt, ok := node.(*ast.SwitchStmt)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
list := stmt.Body.List
|
||||||
|
for i, c := range list {
|
||||||
|
if c.(*ast.CaseClause).List == nil && i != 0 && i != len(list)-1 {
|
||||||
|
j.Errorf(c, "default case should be first or last in switch statement")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
ast.Inspect(f, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckYodaConditions(j *lint.Job) {
|
||||||
|
fn := func(node ast.Node) bool {
|
||||||
|
cond, ok := node.(*ast.BinaryExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if cond.Op != token.EQL && cond.Op != token.NEQ {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := cond.X.(*ast.BasicLit); !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, ok := cond.Y.(*ast.BasicLit); ok {
|
||||||
|
// Don't flag lit == lit conditions, just in case
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
j.Errorf(cond, "don't use Yoda conditions")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, f := range j.Program.Files {
|
||||||
|
ast.Inspect(f, fn)
|
||||||
|
}
|
||||||
|
}
|
263
vendor/honnef.co/go/tools/stylecheck/names.go
vendored
Normal file
263
vendor/honnef.co/go/tools/stylecheck/names.go
vendored
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
// Copyright (c) 2013 The Go Authors. All rights reserved.
|
||||||
|
// Copyright (c) 2018 Dominik Honnef. All rights reserved.
|
||||||
|
|
||||||
|
package stylecheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"honnef.co/go/tools/lint"
|
||||||
|
. "honnef.co/go/tools/lint/lintdsl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// knownNameExceptions is a set of names that are known to be exempt from naming checks.
|
||||||
|
// This is usually because they are constrained by having to match names in the
|
||||||
|
// standard library.
|
||||||
|
var knownNameExceptions = map[string]bool{
|
||||||
|
"LastInsertId": true, // must match database/sql
|
||||||
|
"kWh": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckNames(j *lint.Job) {
|
||||||
|
// A large part of this function is copied from
|
||||||
|
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
|
||||||
|
// licensed under the BSD 3-clause license.
|
||||||
|
|
||||||
|
allCaps := func(s string) bool {
|
||||||
|
for _, r := range s {
|
||||||
|
if !((r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
check := func(id *ast.Ident, thing string, initialisms map[string]bool) {
|
||||||
|
if id.Name == "_" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if knownNameExceptions[id.Name] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle two common styles from other languages that don't belong in Go.
|
||||||
|
if len(id.Name) >= 5 && allCaps(id.Name) && strings.Contains(id.Name, "_") {
|
||||||
|
j.Errorf(id, "should not use ALL_CAPS in Go names; use CamelCase instead")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
should := lintName(id.Name, initialisms)
|
||||||
|
if id.Name == should {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(id.Name) > 2 && strings.Contains(id.Name[1:len(id.Name)-1], "_") {
|
||||||
|
j.Errorf(id, "should not use underscores in Go names; %s %s should be %s", thing, id.Name, should)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
j.Errorf(id, "%s %s should be %s", thing, id.Name, should)
|
||||||
|
}
|
||||||
|
checkList := func(fl *ast.FieldList, thing string, initialisms map[string]bool) {
|
||||||
|
if fl == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, f := range fl.List {
|
||||||
|
for _, id := range f.Names {
|
||||||
|
check(id, thing, initialisms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range j.Program.InitialPackages {
|
||||||
|
initialisms := make(map[string]bool, len(pkg.Config.Initialisms))
|
||||||
|
for _, word := range pkg.Config.Initialisms {
|
||||||
|
initialisms[word] = true
|
||||||
|
}
|
||||||
|
for _, f := range pkg.Syntax {
|
||||||
|
// Package names need slightly different handling than other names.
|
||||||
|
if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") {
|
||||||
|
j.Errorf(f, "should not use underscores in package names")
|
||||||
|
}
|
||||||
|
if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 {
|
||||||
|
j.Errorf(f, "should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
ast.Inspect(f, func(node ast.Node) bool {
|
||||||
|
switch v := node.(type) {
|
||||||
|
case *ast.AssignStmt:
|
||||||
|
if v.Tok != token.DEFINE {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, exp := range v.Lhs {
|
||||||
|
if id, ok := exp.(*ast.Ident); ok {
|
||||||
|
check(id, "var", initialisms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
// Functions with no body are defined elsewhere (in
|
||||||
|
// assembly, or via go:linkname). These are likely to
|
||||||
|
// be something very low level (such as the runtime),
|
||||||
|
// where our rules don't apply.
|
||||||
|
if v.Body == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsInTest(j, v) && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
thing := "func"
|
||||||
|
if v.Recv != nil {
|
||||||
|
thing = "method"
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isTechnicallyExported(v) {
|
||||||
|
check(v.Name, thing, initialisms)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkList(v.Type.Params, thing+" parameter", initialisms)
|
||||||
|
checkList(v.Type.Results, thing+" result", initialisms)
|
||||||
|
case *ast.GenDecl:
|
||||||
|
if v.Tok == token.IMPORT {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
var thing string
|
||||||
|
switch v.Tok {
|
||||||
|
case token.CONST:
|
||||||
|
thing = "const"
|
||||||
|
case token.TYPE:
|
||||||
|
thing = "type"
|
||||||
|
case token.VAR:
|
||||||
|
thing = "var"
|
||||||
|
}
|
||||||
|
for _, spec := range v.Specs {
|
||||||
|
switch s := spec.(type) {
|
||||||
|
case *ast.TypeSpec:
|
||||||
|
check(s.Name, thing, initialisms)
|
||||||
|
case *ast.ValueSpec:
|
||||||
|
for _, id := range s.Names {
|
||||||
|
check(id, thing, initialisms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ast.InterfaceType:
|
||||||
|
// Do not check interface method names.
|
||||||
|
// They are often constrainted by the method names of concrete types.
|
||||||
|
for _, x := range v.Methods.List {
|
||||||
|
ft, ok := x.Type.(*ast.FuncType)
|
||||||
|
if !ok { // might be an embedded interface name
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checkList(ft.Params, "interface method parameter", initialisms)
|
||||||
|
checkList(ft.Results, "interface method result", initialisms)
|
||||||
|
}
|
||||||
|
case *ast.RangeStmt:
|
||||||
|
if v.Tok == token.ASSIGN {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if id, ok := v.Key.(*ast.Ident); ok {
|
||||||
|
check(id, "range var", initialisms)
|
||||||
|
}
|
||||||
|
if id, ok := v.Value.(*ast.Ident); ok {
|
||||||
|
check(id, "range var", initialisms)
|
||||||
|
}
|
||||||
|
case *ast.StructType:
|
||||||
|
for _, f := range v.Fields.List {
|
||||||
|
for _, id := range f.Names {
|
||||||
|
check(id, "struct field", initialisms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lintName returns a different name if it should be different.
|
||||||
|
func lintName(name string, initialisms map[string]bool) (should string) {
|
||||||
|
// A large part of this function is copied from
|
||||||
|
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
|
||||||
|
// licensed under the BSD 3-clause license.
|
||||||
|
|
||||||
|
// Fast path for simple cases: "_" and all lowercase.
|
||||||
|
if name == "_" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLower(r) }) == -1 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split camelCase at any lower->upper transition, and split on underscores.
|
||||||
|
// Check each word for common initialisms.
|
||||||
|
runes := []rune(name)
|
||||||
|
w, i := 0, 0 // index of start of word, scan
|
||||||
|
for i+1 <= len(runes) {
|
||||||
|
eow := false // whether we hit the end of a word
|
||||||
|
if i+1 == len(runes) {
|
||||||
|
eow = true
|
||||||
|
} else if runes[i+1] == '_' && i+1 != len(runes)-1 {
|
||||||
|
// underscore; shift the remainder forward over any run of underscores
|
||||||
|
eow = true
|
||||||
|
n := 1
|
||||||
|
for i+n+1 < len(runes) && runes[i+n+1] == '_' {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leave at most one underscore if the underscore is between two digits
|
||||||
|
if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(runes[i+1:], runes[i+n+1:])
|
||||||
|
runes = runes[:len(runes)-n]
|
||||||
|
} else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) {
|
||||||
|
// lower->non-lower
|
||||||
|
eow = true
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if !eow {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// [w,i) is a word.
|
||||||
|
word := string(runes[w:i])
|
||||||
|
if u := strings.ToUpper(word); initialisms[u] {
|
||||||
|
// Keep consistent case, which is lowercase only at the start.
|
||||||
|
if w == 0 && unicode.IsLower(runes[w]) {
|
||||||
|
u = strings.ToLower(u)
|
||||||
|
}
|
||||||
|
// All the common initialisms are ASCII,
|
||||||
|
// so we can replace the bytes exactly.
|
||||||
|
// TODO(dh): this won't be true once we allow custom initialisms
|
||||||
|
copy(runes[w:], []rune(u))
|
||||||
|
} else if w > 0 && strings.ToLower(word) == word {
|
||||||
|
// already all lowercase, and not the first word, so uppercase the first character.
|
||||||
|
runes[w] = unicode.ToUpper(runes[w])
|
||||||
|
}
|
||||||
|
w = i
|
||||||
|
}
|
||||||
|
return string(runes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isTechnicallyExported(f *ast.FuncDecl) bool {
|
||||||
|
if f.Recv != nil || f.Doc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const export = "//export "
|
||||||
|
const linkname = "//go:linkname "
|
||||||
|
for _, c := range f.Doc.List {
|
||||||
|
if strings.HasPrefix(c.Text, export) && len(c.Text) == len(export)+len(f.Name.Name) && c.Text[len(export):] == f.Name.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(c.Text, linkname) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
79
vendor/honnef.co/go/tools/unused/implements.go
vendored
Normal file
79
vendor/honnef.co/go/tools/unused/implements.go
vendored
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package unused
|
||||||
|
|
||||||
|
import "go/types"
|
||||||
|
|
||||||
|
// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
|
||||||
|
func lookupMethod(T *types.Interface, pkg *types.Package, name string) (int, *types.Func) {
|
||||||
|
if name != "_" {
|
||||||
|
for i := 0; i < T.NumMethods(); i++ {
|
||||||
|
m := T.Method(i)
|
||||||
|
if sameId(m, pkg, name) {
|
||||||
|
return i, m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameId(obj types.Object, pkg *types.Package, name string) bool {
|
||||||
|
// spec:
|
||||||
|
// "Two identifiers are different if they are spelled differently,
|
||||||
|
// or if they appear in different packages and are not exported.
|
||||||
|
// Otherwise, they are the same."
|
||||||
|
if name != obj.Name() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// obj.Name == name
|
||||||
|
if obj.Exported() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// not exported, so packages must be the same (pkg == nil for
|
||||||
|
// fields in Universe scope; this can only happen for types
|
||||||
|
// introduced via Eval)
|
||||||
|
if pkg == nil || obj.Pkg() == nil {
|
||||||
|
return pkg == obj.Pkg()
|
||||||
|
}
|
||||||
|
// pkg != nil && obj.pkg != nil
|
||||||
|
return pkg.Path() == obj.Pkg().Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) implements(V types.Type, T *types.Interface) bool {
|
||||||
|
// fast path for common case
|
||||||
|
if T.Empty() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ityp, _ := V.Underlying().(*types.Interface); ityp != nil {
|
||||||
|
for i := 0; i < T.NumMethods(); i++ {
|
||||||
|
m := T.Method(i)
|
||||||
|
_, obj := lookupMethod(ityp, m.Pkg(), m.Name())
|
||||||
|
switch {
|
||||||
|
case obj == nil:
|
||||||
|
return false
|
||||||
|
case !types.Identical(obj.Type(), m.Type()):
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// A concrete type implements T if it implements all methods of T.
|
||||||
|
ms := c.msCache.MethodSet(V)
|
||||||
|
for i := 0; i < T.NumMethods(); i++ {
|
||||||
|
m := T.Method(i)
|
||||||
|
sel := ms.Lookup(m.Pkg(), m.Name())
|
||||||
|
if sel == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
f, _ := sel.Obj().(*types.Func)
|
||||||
|
if f == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !types.Identical(f.Type(), m.Type()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
199
vendor/honnef.co/go/tools/unused/unused.go
vendored
199
vendor/honnef.co/go/tools/unused/unused.go
vendored
|
@ -12,7 +12,7 @@ import (
|
||||||
"honnef.co/go/tools/lint"
|
"honnef.co/go/tools/lint"
|
||||||
. "honnef.co/go/tools/lint/lintdsl"
|
. "honnef.co/go/tools/lint/lintdsl"
|
||||||
|
|
||||||
"golang.org/x/tools/go/loader"
|
"golang.org/x/tools/go/packages"
|
||||||
"golang.org/x/tools/go/types/typeutil"
|
"golang.org/x/tools/go/types/typeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,9 +31,9 @@ func (*LintChecker) Name() string { return "unused" }
|
||||||
func (*LintChecker) Prefix() string { return "U" }
|
func (*LintChecker) Prefix() string { return "U" }
|
||||||
|
|
||||||
func (l *LintChecker) Init(*lint.Program) {}
|
func (l *LintChecker) Init(*lint.Program) {}
|
||||||
func (l *LintChecker) Funcs() map[string]lint.Func {
|
func (l *LintChecker) Checks() []lint.Check {
|
||||||
return map[string]lint.Func{
|
return []lint.Check{
|
||||||
"U1000": l.Lint,
|
{ID: "U1000", FilterGenerated: true, Fn: l.Lint},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ func typString(obj types.Object) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LintChecker) Lint(j *lint.Job) {
|
func (l *LintChecker) Lint(j *lint.Job) {
|
||||||
unused := l.c.Check(j.Program.Prog)
|
unused := l.c.Check(j.Program)
|
||||||
for _, u := range unused {
|
for _, u := range unused {
|
||||||
name := u.Obj.Name()
|
name := u.Obj.Name()
|
||||||
if sig, ok := u.Obj.Type().(*types.Signature); ok && sig.Recv() != nil {
|
if sig, ok := u.Obj.Type().(*types.Signature); ok && sig.Recv() != nil {
|
||||||
|
@ -158,7 +158,7 @@ type Checker struct {
|
||||||
graph *graph
|
graph *graph
|
||||||
|
|
||||||
msCache typeutil.MethodSetCache
|
msCache typeutil.MethodSetCache
|
||||||
lprog *loader.Program
|
prog *lint.Program
|
||||||
topmostCache map[*types.Scope]*types.Scope
|
topmostCache map[*types.Scope]*types.Scope
|
||||||
interfaces []*types.Interface
|
interfaces []*types.Interface
|
||||||
}
|
}
|
||||||
|
@ -199,13 +199,13 @@ func (e Error) Error() string {
|
||||||
return fmt.Sprintf("errors in %d packages", len(e.Errors))
|
return fmt.Sprintf("errors in %d packages", len(e.Errors))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) Check(lprog *loader.Program) []Unused {
|
func (c *Checker) Check(prog *lint.Program) []Unused {
|
||||||
var unused []Unused
|
var unused []Unused
|
||||||
c.lprog = lprog
|
c.prog = prog
|
||||||
if c.WholeProgram {
|
if c.WholeProgram {
|
||||||
c.findExportedInterfaces()
|
c.findExportedInterfaces()
|
||||||
}
|
}
|
||||||
for _, pkg := range c.lprog.InitialPackages() {
|
for _, pkg := range prog.InitialPackages {
|
||||||
c.processDefs(pkg)
|
c.processDefs(pkg)
|
||||||
c.processUses(pkg)
|
c.processUses(pkg)
|
||||||
c.processTypes(pkg)
|
c.processTypes(pkg)
|
||||||
|
@ -231,6 +231,7 @@ func (c *Checker) Check(lprog *loader.Program) []Unused {
|
||||||
}
|
}
|
||||||
markNodesUsed(roots)
|
markNodesUsed(roots)
|
||||||
c.markNodesQuiet()
|
c.markNodesQuiet()
|
||||||
|
c.deduplicate()
|
||||||
|
|
||||||
if c.Debug != nil {
|
if c.Debug != nil {
|
||||||
c.printDebugGraph(c.Debug)
|
c.printDebugGraph(c.Debug)
|
||||||
|
@ -246,8 +247,8 @@ func (c *Checker) Check(lprog *loader.Program) []Unused {
|
||||||
}
|
}
|
||||||
found := false
|
found := false
|
||||||
if !false {
|
if !false {
|
||||||
for _, pkg := range c.lprog.InitialPackages() {
|
for _, pkg := range prog.InitialPackages {
|
||||||
if pkg.Pkg == obj.Pkg() {
|
if pkg.Types == obj.Pkg() {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -257,25 +258,14 @@ func (c *Checker) Check(lprog *loader.Program) []Unused {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pos := c.lprog.Fset.Position(obj.Pos())
|
pos := c.prog.Fset().Position(obj.Pos())
|
||||||
if pos.Filename == "" || filepath.Base(pos.Filename) == "C" {
|
if pos.Filename == "" || filepath.Base(pos.Filename) == "C" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
generated := false
|
|
||||||
for _, file := range c.lprog.Package(obj.Pkg().Path()).Files {
|
|
||||||
if c.lprog.Fset.Position(file.Pos()).Filename != pos.Filename {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(file.Comments) > 0 {
|
|
||||||
generated = isGenerated(file.Comments[0].Text())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if generated {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
unused = append(unused, Unused{Obj: obj, Position: pos})
|
unused = append(unused, Unused{Obj: obj, Position: pos})
|
||||||
}
|
}
|
||||||
|
|
||||||
return unused
|
return unused
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,16 +315,24 @@ func (c *Checker) useNoCopyFields(typ types.Type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) useExportedFields(typ types.Type) {
|
func (c *Checker) useExportedFields(typ types.Type, by types.Type) bool {
|
||||||
|
any := false
|
||||||
if st, ok := typ.Underlying().(*types.Struct); ok {
|
if st, ok := typ.Underlying().(*types.Struct); ok {
|
||||||
n := st.NumFields()
|
n := st.NumFields()
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
field := st.Field(i)
|
field := st.Field(i)
|
||||||
if field.Exported() {
|
if field.Anonymous() {
|
||||||
|
if c.useExportedFields(field.Type(), typ) {
|
||||||
c.graph.markUsedBy(field, typ)
|
c.graph.markUsedBy(field, typ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if field.Exported() {
|
||||||
|
c.graph.markUsedBy(field, by)
|
||||||
|
any = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) useExportedMethods(typ types.Type) {
|
func (c *Checker) useExportedMethods(typ types.Type) {
|
||||||
|
@ -370,8 +368,8 @@ func (c *Checker) useExportedMethods(typ types.Type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
func (c *Checker) processDefs(pkg *lint.Pkg) {
|
||||||
for _, obj := range pkg.Defs {
|
for _, obj := range pkg.TypesInfo.Defs {
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -392,7 +390,7 @@ func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
||||||
// mark them used if an instance of the type was
|
// mark them used if an instance of the type was
|
||||||
// accessible via an interface value.
|
// accessible via an interface value.
|
||||||
if !c.WholeProgram || c.ConsiderReflection {
|
if !c.WholeProgram || c.ConsiderReflection {
|
||||||
c.useExportedFields(obj.Type())
|
c.useExportedFields(obj.Type(), obj.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dh): Traditionally we have not marked all exported
|
// TODO(dh): Traditionally we have not marked all exported
|
||||||
|
@ -420,8 +418,8 @@ func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
||||||
if obj.Name() == "_" {
|
if obj.Name() == "_" {
|
||||||
node := c.graph.getNode(obj)
|
node := c.graph.getNode(obj)
|
||||||
node.quiet = true
|
node.quiet = true
|
||||||
scope := c.topmostScope(pkg.Pkg.Scope().Innermost(obj.Pos()), pkg.Pkg)
|
scope := c.topmostScope(pkg.Types.Scope().Innermost(obj.Pos()), pkg.Types)
|
||||||
if scope == pkg.Pkg.Scope() {
|
if scope == pkg.Types.Scope() {
|
||||||
c.graph.roots = append(c.graph.roots, node)
|
c.graph.roots = append(c.graph.roots, node)
|
||||||
} else {
|
} else {
|
||||||
c.graph.markUsedBy(obj, scope)
|
c.graph.markUsedBy(obj, scope)
|
||||||
|
@ -471,15 +469,15 @@ func (c *Checker) processDefs(pkg *loader.PackageInfo) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processUses(pkg *loader.PackageInfo) {
|
func (c *Checker) processUses(pkg *lint.Pkg) {
|
||||||
for ident, usedObj := range pkg.Uses {
|
for ident, usedObj := range pkg.TypesInfo.Uses {
|
||||||
if _, ok := usedObj.(*types.PkgName); ok {
|
if _, ok := usedObj.(*types.PkgName); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pos := ident.Pos()
|
pos := ident.Pos()
|
||||||
scope := pkg.Pkg.Scope().Innermost(pos)
|
scope := pkg.Types.Scope().Innermost(pos)
|
||||||
scope = c.topmostScope(scope, pkg.Pkg)
|
scope = c.topmostScope(scope, pkg.Types)
|
||||||
if scope != pkg.Pkg.Scope() {
|
if scope != pkg.Types.Scope() {
|
||||||
c.graph.markUsedBy(usedObj, scope)
|
c.graph.markUsedBy(usedObj, scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,17 +490,17 @@ func (c *Checker) processUses(pkg *loader.PackageInfo) {
|
||||||
|
|
||||||
func (c *Checker) findExportedInterfaces() {
|
func (c *Checker) findExportedInterfaces() {
|
||||||
c.interfaces = []*types.Interface{types.Universe.Lookup("error").Type().(*types.Named).Underlying().(*types.Interface)}
|
c.interfaces = []*types.Interface{types.Universe.Lookup("error").Type().(*types.Named).Underlying().(*types.Interface)}
|
||||||
var pkgs []*loader.PackageInfo
|
var pkgs []*packages.Package
|
||||||
if c.WholeProgram {
|
if c.WholeProgram {
|
||||||
for _, pkg := range c.lprog.AllPackages {
|
pkgs = append(pkgs, c.prog.AllPackages...)
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
pkgs = c.lprog.InitialPackages()
|
for _, pkg := range c.prog.InitialPackages {
|
||||||
|
pkgs = append(pkgs, pkg.Package)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
for _, tv := range pkg.Types {
|
for _, tv := range pkg.TypesInfo.Types {
|
||||||
iface, ok := tv.Type.(*types.Interface)
|
iface, ok := tv.Type.(*types.Interface)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
|
@ -515,10 +513,10 @@ func (c *Checker) findExportedInterfaces() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
func (c *Checker) processTypes(pkg *lint.Pkg) {
|
||||||
named := map[*types.Named]*types.Pointer{}
|
named := map[*types.Named]*types.Pointer{}
|
||||||
var interfaces []*types.Interface
|
var interfaces []*types.Interface
|
||||||
for _, tv := range pkg.Types {
|
for _, tv := range pkg.TypesInfo.Types {
|
||||||
if typ, ok := tv.Type.(interface {
|
if typ, ok := tv.Type.(interface {
|
||||||
Elem() types.Type
|
Elem() types.Type
|
||||||
}); ok {
|
}); ok {
|
||||||
|
@ -536,8 +534,8 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
||||||
}
|
}
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
c.useNoCopyFields(obj)
|
c.useNoCopyFields(obj)
|
||||||
if pkg.Pkg.Name() != "main" && !c.WholeProgram {
|
if pkg.Types.Name() != "main" && !c.WholeProgram {
|
||||||
c.useExportedFields(obj)
|
c.useExportedFields(obj, obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,15 +545,30 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
||||||
//
|
//
|
||||||
// TODO(dh): For normal operations, that's the best we can do, as
|
// TODO(dh): For normal operations, that's the best we can do, as
|
||||||
// we have no idea what external users will do with our types. In
|
// we have no idea what external users will do with our types. In
|
||||||
// whole-program mode, we could be more conservative, in two ways:
|
// whole-program mode, we could be more precise, in two ways:
|
||||||
// 1) Only consider interfaces if a type has been assigned to one
|
// 1) Only consider interfaces if a type has been assigned to one
|
||||||
// 2) Use SSA and flow analysis and determine the exact set of
|
// 2) Use SSA and flow analysis and determine the exact set of
|
||||||
// interfaces that is relevant.
|
// interfaces that is relevant.
|
||||||
fn := func(iface *types.Interface) {
|
fn := func(iface *types.Interface) {
|
||||||
for obj, objPtr := range named {
|
for i := 0; i < iface.NumEmbeddeds(); i++ {
|
||||||
if !types.Implements(obj, iface) && !types.Implements(objPtr, iface) {
|
c.graph.markUsedBy(iface.Embedded(i), iface)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
namedLoop:
|
||||||
|
for obj, objPtr := range named {
|
||||||
|
switch obj.Underlying().(type) {
|
||||||
|
case *types.Interface:
|
||||||
|
// pointers to interfaces have no methods, only checking non-pointer
|
||||||
|
if !c.implements(obj, iface) {
|
||||||
|
continue namedLoop
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// pointer receivers include the method set of non-pointer receivers,
|
||||||
|
// only checking pointer
|
||||||
|
if !c.implements(objPtr, iface) {
|
||||||
|
continue namedLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ifaceMethods := make(map[string]struct{}, iface.NumMethods())
|
ifaceMethods := make(map[string]struct{}, iface.NumMethods())
|
||||||
n := iface.NumMethods()
|
n := iface.NumMethods()
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
|
@ -591,23 +604,23 @@ func (c *Checker) processTypes(pkg *loader.PackageInfo) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processSelections(pkg *loader.PackageInfo) {
|
func (c *Checker) processSelections(pkg *lint.Pkg) {
|
||||||
fn := func(expr *ast.SelectorExpr, sel *types.Selection, offset int) {
|
fn := func(expr *ast.SelectorExpr, sel *types.Selection, offset int) {
|
||||||
scope := pkg.Pkg.Scope().Innermost(expr.Pos())
|
scope := pkg.Types.Scope().Innermost(expr.Pos())
|
||||||
c.graph.markUsedBy(expr.X, c.topmostScope(scope, pkg.Pkg))
|
c.graph.markUsedBy(sel, c.topmostScope(scope, pkg.Types))
|
||||||
c.graph.markUsedBy(sel.Obj(), expr.X)
|
c.graph.markUsedBy(sel.Obj(), sel)
|
||||||
if len(sel.Index()) > 1 {
|
if len(sel.Index()) > 1 {
|
||||||
typ := sel.Recv()
|
typ := sel.Recv()
|
||||||
indices := sel.Index()
|
indices := sel.Index()
|
||||||
for _, idx := range indices[:len(indices)-offset] {
|
for _, idx := range indices[:len(indices)-offset] {
|
||||||
obj := getField(typ, idx)
|
obj := getField(typ, idx)
|
||||||
typ = obj.Type()
|
typ = obj.Type()
|
||||||
c.graph.markUsedBy(obj, expr.X)
|
c.graph.markUsedBy(obj, sel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for expr, sel := range pkg.Selections {
|
for expr, sel := range pkg.TypesInfo.Selections {
|
||||||
switch sel.Kind() {
|
switch sel.Kind() {
|
||||||
case types.FieldVal:
|
case types.FieldVal:
|
||||||
fn(expr, sel, 0)
|
fn(expr, sel, 0)
|
||||||
|
@ -625,9 +638,9 @@ func dereferenceType(typ types.Type) types.Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
// processConversion marks fields as used if they're part of a type conversion.
|
// processConversion marks fields as used if they're part of a type conversion.
|
||||||
func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
func (c *Checker) processConversion(pkg *lint.Pkg, node ast.Node) {
|
||||||
if node, ok := node.(*ast.CallExpr); ok {
|
if node, ok := node.(*ast.CallExpr); ok {
|
||||||
callTyp := pkg.TypeOf(node.Fun)
|
callTyp := pkg.TypesInfo.TypeOf(node.Fun)
|
||||||
var typDst *types.Struct
|
var typDst *types.Struct
|
||||||
var ok bool
|
var ok bool
|
||||||
switch typ := callTyp.(type) {
|
switch typ := callTyp.(type) {
|
||||||
|
@ -642,7 +655,7 @@ func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if typ, ok := pkg.TypeOf(node.Args[0]).(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
|
if typ, ok := pkg.TypesInfo.TypeOf(node.Args[0]).(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
|
||||||
// This is an unsafe conversion. Assume that all the
|
// This is an unsafe conversion. Assume that all the
|
||||||
// fields are relevant (they are, because of memory
|
// fields are relevant (they are, because of memory
|
||||||
// layout)
|
// layout)
|
||||||
|
@ -653,7 +666,7 @@ func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
typSrc, ok := dereferenceType(pkg.TypeOf(node.Args[0])).Underlying().(*types.Struct)
|
typSrc, ok := dereferenceType(pkg.TypesInfo.TypeOf(node.Args[0])).Underlying().(*types.Struct)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -683,10 +696,10 @@ func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
|
||||||
|
|
||||||
// processCompositeLiteral marks fields as used if the struct is used
|
// processCompositeLiteral marks fields as used if the struct is used
|
||||||
// in a composite literal.
|
// in a composite literal.
|
||||||
func (c *Checker) processCompositeLiteral(pkg *loader.PackageInfo, node ast.Node) {
|
func (c *Checker) processCompositeLiteral(pkg *lint.Pkg, node ast.Node) {
|
||||||
// XXX how does this actually work? wouldn't it match t{}?
|
// XXX how does this actually work? wouldn't it match t{}?
|
||||||
if node, ok := node.(*ast.CompositeLit); ok {
|
if node, ok := node.(*ast.CompositeLit); ok {
|
||||||
typ := pkg.TypeOf(node)
|
typ := pkg.TypesInfo.TypeOf(node)
|
||||||
if _, ok := typ.(*types.Named); ok {
|
if _, ok := typ.(*types.Named); ok {
|
||||||
typ = typ.Underlying()
|
typ = typ.Underlying()
|
||||||
}
|
}
|
||||||
|
@ -702,7 +715,7 @@ func (c *Checker) processCompositeLiteral(pkg *loader.PackageInfo, node ast.Node
|
||||||
|
|
||||||
// processCgoExported marks functions as used if they're being
|
// processCgoExported marks functions as used if they're being
|
||||||
// exported to cgo.
|
// exported to cgo.
|
||||||
func (c *Checker) processCgoExported(pkg *loader.PackageInfo, node ast.Node) {
|
func (c *Checker) processCgoExported(pkg *lint.Pkg, node ast.Node) {
|
||||||
if node, ok := node.(*ast.FuncDecl); ok {
|
if node, ok := node.(*ast.FuncDecl); ok {
|
||||||
if node.Doc == nil {
|
if node.Doc == nil {
|
||||||
return
|
return
|
||||||
|
@ -711,13 +724,13 @@ func (c *Checker) processCgoExported(pkg *loader.PackageInfo, node ast.Node) {
|
||||||
if !strings.HasPrefix(cmt.Text, "//go:cgo_export_") {
|
if !strings.HasPrefix(cmt.Text, "//go:cgo_export_") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
obj := pkg.ObjectOf(node.Name)
|
obj := pkg.TypesInfo.ObjectOf(node.Name)
|
||||||
c.graph.roots = append(c.graph.roots, c.graph.getNode(obj))
|
c.graph.roots = append(c.graph.roots, c.graph.getNode(obj))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.Node) {
|
func (c *Checker) processVariableDeclaration(pkg *lint.Pkg, node ast.Node) {
|
||||||
if decl, ok := node.(*ast.GenDecl); ok {
|
if decl, ok := node.(*ast.GenDecl); ok {
|
||||||
for _, spec := range decl.Specs {
|
for _, spec := range decl.Specs {
|
||||||
spec, ok := spec.(*ast.ValueSpec)
|
spec, ok := spec.(*ast.ValueSpec)
|
||||||
|
@ -731,11 +744,11 @@ func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.N
|
||||||
value := spec.Values[i]
|
value := spec.Values[i]
|
||||||
fn := func(node ast.Node) bool {
|
fn := func(node ast.Node) bool {
|
||||||
if node3, ok := node.(*ast.Ident); ok {
|
if node3, ok := node.(*ast.Ident); ok {
|
||||||
obj := pkg.ObjectOf(node3)
|
obj := pkg.TypesInfo.ObjectOf(node3)
|
||||||
if _, ok := obj.(*types.PkgName); ok {
|
if _, ok := obj.(*types.PkgName); ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
c.graph.markUsedBy(obj, pkg.ObjectOf(name))
|
c.graph.markUsedBy(obj, pkg.TypesInfo.ObjectOf(name))
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -745,17 +758,17 @@ func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.N
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processArrayConstants(pkg *loader.PackageInfo, node ast.Node) {
|
func (c *Checker) processArrayConstants(pkg *lint.Pkg, node ast.Node) {
|
||||||
if decl, ok := node.(*ast.ArrayType); ok {
|
if decl, ok := node.(*ast.ArrayType); ok {
|
||||||
ident, ok := decl.Len.(*ast.Ident)
|
ident, ok := decl.Len.(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.graph.markUsedBy(pkg.ObjectOf(ident), pkg.TypeOf(decl))
|
c.graph.markUsedBy(pkg.TypesInfo.ObjectOf(ident), pkg.TypesInfo.TypeOf(decl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node ast.Node) {
|
func (c *Checker) processKnownReflectMethodCallers(pkg *lint.Pkg, node ast.Node) {
|
||||||
call, ok := node.(*ast.CallExpr)
|
call, ok := node.(*ast.CallExpr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
|
@ -764,12 +777,12 @@ func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !IsType(pkg.TypeOf(sel.X), "*net/rpc.Server") {
|
if !IsType(pkg.TypesInfo.TypeOf(sel.X), "*net/rpc.Server") {
|
||||||
x, ok := sel.X.(*ast.Ident)
|
x, ok := sel.X.(*ast.Ident)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pkgname, ok := pkg.ObjectOf(x).(*types.PkgName)
|
pkgname, ok := pkg.TypesInfo.ObjectOf(x).(*types.PkgName)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -791,14 +804,14 @@ func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node
|
||||||
}
|
}
|
||||||
arg = call.Args[1]
|
arg = call.Args[1]
|
||||||
}
|
}
|
||||||
typ := pkg.TypeOf(arg)
|
typ := pkg.TypesInfo.TypeOf(arg)
|
||||||
ms := types.NewMethodSet(typ)
|
ms := types.NewMethodSet(typ)
|
||||||
for i := 0; i < ms.Len(); i++ {
|
for i := 0; i < ms.Len(); i++ {
|
||||||
c.graph.markUsedBy(ms.At(i).Obj(), typ)
|
c.graph.markUsedBy(ms.At(i).Obj(), typ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) processAST(pkg *loader.PackageInfo) {
|
func (c *Checker) processAST(pkg *lint.Pkg) {
|
||||||
fn := func(node ast.Node) bool {
|
fn := func(node ast.Node) bool {
|
||||||
c.processConversion(pkg, node)
|
c.processConversion(pkg, node)
|
||||||
c.processKnownReflectMethodCallers(pkg, node)
|
c.processKnownReflectMethodCallers(pkg, node)
|
||||||
|
@ -808,7 +821,7 @@ func (c *Checker) processAST(pkg *loader.PackageInfo) {
|
||||||
c.processArrayConstants(pkg, node)
|
c.processArrayConstants(pkg, node)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, file := range pkg.Files {
|
for _, file := range pkg.Syntax {
|
||||||
ast.Inspect(file, fn)
|
ast.Inspect(file, fn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -914,7 +927,7 @@ func (c *Checker) isRoot(obj types.Object) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if obj.Exported() {
|
if obj.Exported() {
|
||||||
f := c.lprog.Fset.Position(obj.Pos()).Filename
|
f := c.prog.Fset().Position(obj.Pos()).Filename
|
||||||
if strings.HasSuffix(f, "_test.go") {
|
if strings.HasSuffix(f, "_test.go") {
|
||||||
return strings.HasPrefix(obj.Name(), "Test") ||
|
return strings.HasPrefix(obj.Name(), "Test") ||
|
||||||
strings.HasPrefix(obj.Name(), "Benchmark") ||
|
strings.HasPrefix(obj.Name(), "Benchmark") ||
|
||||||
|
@ -939,6 +952,33 @@ func markNodesUsed(nodes map[*graphNode]struct{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deduplicate merges objects based on their positions. This is done
|
||||||
|
// to work around packages existing multiple times in go/packages.
|
||||||
|
func (c *Checker) deduplicate() {
|
||||||
|
m := map[token.Position]struct{ used, quiet bool }{}
|
||||||
|
for _, node := range c.graph.nodes {
|
||||||
|
obj, ok := node.obj.(types.Object)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pos := c.prog.Fset().Position(obj.Pos())
|
||||||
|
m[pos] = struct{ used, quiet bool }{
|
||||||
|
m[pos].used || node.used,
|
||||||
|
m[pos].quiet || node.quiet,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range c.graph.nodes {
|
||||||
|
obj, ok := node.obj.(types.Object)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pos := c.prog.Fset().Position(obj.Pos())
|
||||||
|
node.used = m[pos].used
|
||||||
|
node.quiet = m[pos].quiet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Checker) markNodesQuiet() {
|
func (c *Checker) markNodesQuiet() {
|
||||||
for _, node := range c.graph.nodes {
|
for _, node := range c.graph.nodes {
|
||||||
if node.used {
|
if node.used {
|
||||||
|
@ -1058,8 +1098,3 @@ func (c *Checker) printDebugGraph(w io.Writer) {
|
||||||
}
|
}
|
||||||
fmt.Fprintln(w, "}")
|
fmt.Fprintln(w, "}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGenerated(comment string) bool {
|
|
||||||
return strings.Contains(comment, "Code generated by") ||
|
|
||||||
strings.Contains(comment, "DO NOT EDIT")
|
|
||||||
}
|
|
||||||
|
|
24
vendor/modules.txt
vendored
24
vendor/modules.txt
vendored
|
@ -1,6 +1,8 @@
|
||||||
# code.vikunja.io/web v0.0.0-20190123142349-c30ef6073334
|
# code.vikunja.io/web v0.0.0-20190123142349-c30ef6073334
|
||||||
code.vikunja.io/web
|
code.vikunja.io/web
|
||||||
code.vikunja.io/web/handler
|
code.vikunja.io/web/handler
|
||||||
|
# github.com/BurntSushi/toml v0.3.1
|
||||||
|
github.com/BurntSushi/toml
|
||||||
# github.com/PuerkitoBio/purell v1.1.0
|
# github.com/PuerkitoBio/purell v1.1.0
|
||||||
github.com/PuerkitoBio/purell
|
github.com/PuerkitoBio/purell
|
||||||
# github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
|
# github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578
|
||||||
|
@ -77,9 +79,6 @@ github.com/jgautheron/goconst/cmd/goconst
|
||||||
github.com/jgautheron/goconst
|
github.com/jgautheron/goconst
|
||||||
# github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
# github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
||||||
github.com/karalabe/xgo
|
github.com/karalabe/xgo
|
||||||
# github.com/kisielk/gotool v1.0.0
|
|
||||||
github.com/kisielk/gotool
|
|
||||||
github.com/kisielk/gotool/internal/load
|
|
||||||
# github.com/labstack/echo v3.3.5+incompatible
|
# github.com/labstack/echo v3.3.5+incompatible
|
||||||
github.com/labstack/echo
|
github.com/labstack/echo
|
||||||
github.com/labstack/echo/middleware
|
github.com/labstack/echo/middleware
|
||||||
|
@ -173,10 +172,14 @@ golang.org/x/text/unicode/bidi
|
||||||
golang.org/x/tools/go/loader
|
golang.org/x/tools/go/loader
|
||||||
golang.org/x/tools/go/ast/astutil
|
golang.org/x/tools/go/ast/astutil
|
||||||
golang.org/x/tools/go/gcexportdata
|
golang.org/x/tools/go/gcexportdata
|
||||||
|
golang.org/x/tools/go/packages
|
||||||
golang.org/x/tools/go/types/typeutil
|
golang.org/x/tools/go/types/typeutil
|
||||||
golang.org/x/tools/go/buildutil
|
golang.org/x/tools/go/buildutil
|
||||||
golang.org/x/tools/go/internal/cgo
|
golang.org/x/tools/go/internal/cgo
|
||||||
golang.org/x/tools/go/internal/gcimporter
|
golang.org/x/tools/go/internal/gcimporter
|
||||||
|
golang.org/x/tools/internal/gopathwalk
|
||||||
|
golang.org/x/tools/internal/semver
|
||||||
|
golang.org/x/tools/internal/fastwalk
|
||||||
# google.golang.org/appengine v1.3.0
|
# google.golang.org/appengine v1.3.0
|
||||||
google.golang.org/appengine/cloudsql
|
google.golang.org/appengine/cloudsql
|
||||||
# gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc
|
# gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc
|
||||||
|
@ -187,22 +190,25 @@ gopkg.in/gomail.v2
|
||||||
gopkg.in/testfixtures.v2
|
gopkg.in/testfixtures.v2
|
||||||
# gopkg.in/yaml.v2 v2.2.2
|
# gopkg.in/yaml.v2 v2.2.2
|
||||||
gopkg.in/yaml.v2
|
gopkg.in/yaml.v2
|
||||||
# honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3
|
# honnef.co/go/tools v0.0.0-20190215041234-466a0476246c
|
||||||
honnef.co/go/tools/cmd/gosimple
|
|
||||||
honnef.co/go/tools/cmd/staticcheck
|
honnef.co/go/tools/cmd/staticcheck
|
||||||
honnef.co/go/tools/cmd/unused
|
honnef.co/go/tools/lint
|
||||||
honnef.co/go/tools/lint/lintutil
|
honnef.co/go/tools/lint/lintutil
|
||||||
honnef.co/go/tools/simple
|
honnef.co/go/tools/simple
|
||||||
honnef.co/go/tools/staticcheck
|
honnef.co/go/tools/staticcheck
|
||||||
|
honnef.co/go/tools/stylecheck
|
||||||
honnef.co/go/tools/unused
|
honnef.co/go/tools/unused
|
||||||
honnef.co/go/tools/lint
|
honnef.co/go/tools/config
|
||||||
|
honnef.co/go/tools/ssa
|
||||||
|
honnef.co/go/tools/ssa/ssautil
|
||||||
|
honnef.co/go/tools/lint/lintutil/format
|
||||||
honnef.co/go/tools/version
|
honnef.co/go/tools/version
|
||||||
|
honnef.co/go/tools/arg
|
||||||
honnef.co/go/tools/internal/sharedcheck
|
honnef.co/go/tools/internal/sharedcheck
|
||||||
honnef.co/go/tools/lint/lintdsl
|
honnef.co/go/tools/lint/lintdsl
|
||||||
honnef.co/go/tools/deprecated
|
honnef.co/go/tools/deprecated
|
||||||
honnef.co/go/tools/functions
|
honnef.co/go/tools/functions
|
||||||
honnef.co/go/tools/ssa
|
honnef.co/go/tools/ssautil
|
||||||
honnef.co/go/tools/staticcheck/vrp
|
honnef.co/go/tools/staticcheck/vrp
|
||||||
honnef.co/go/tools/ssa/ssautil
|
|
||||||
honnef.co/go/tools/callgraph
|
honnef.co/go/tools/callgraph
|
||||||
honnef.co/go/tools/callgraph/static
|
honnef.co/go/tools/callgraph/static
|
||||||
|
|
Loading…
Reference in a new issue