Compare commits

...

1 Commits

Author SHA1 Message Date
kolaente a6fbf85073
Update swag 2020-01-27 18:48:21 +01:00
56 changed files with 1596 additions and 2248 deletions

View File

@ -190,7 +190,7 @@ do-the-swag:
@hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install $(GOFLAGS) github.com/swaggo/swag/cmd/swag; \ go install $(GOFLAGS) github.com/swaggo/swag/cmd/swag; \
fi fi
swag init -g pkg/routes/routes.go -o ./pkg/swagger; swag init --parseVendor --parseDependency -g pkg/routes/routes.go -o ./pkg/swagger;
# Fix the generated swagger file, currently a workaround until swaggo can properly use go mod # Fix the generated swagger file, currently a workaround until swaggo can properly use go mod
sed -i '/"definitions": {/a "code.vikunja.io.web.HTTPError": {"type": "object","properties": {"code": {"type": "integer"},"message": {"type": "string"}}},' pkg/swagger/docs.go; sed -i '/"definitions": {/a "code.vikunja.io.web.HTTPError": {"type": "object","properties": {"code": {"type": "integer"},"message": {"type": "string"}}},' pkg/swagger/docs.go;
sed -i 's/code.vikunja.io\/web.HTTPError/code.vikunja.io.web.HTTPError/g' pkg/swagger/docs.go; sed -i 's/code.vikunja.io\/web.HTTPError/code.vikunja.io.web.HTTPError/g' pkg/swagger/docs.go;

11
go.mod
View File

@ -30,8 +30,8 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
github.com/garyburd/redigo v1.6.0 // indirect github.com/garyburd/redigo v1.6.0 // indirect
github.com/go-openapi/jsonreference v0.19.3 // indirect github.com/go-openapi/spec v0.19.5 // indirect
github.com/go-openapi/spec v0.19.4 // indirect github.com/go-openapi/swag v0.19.7 // indirect
github.com/go-redis/redis v6.15.2+incompatible github.com/go-redis/redis v6.15.2+incompatible
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.4.1
github.com/go-xorm/builder v0.3.4 github.com/go-xorm/builder v0.3.4
@ -68,20 +68,19 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.3.2 github.com/spf13/viper v1.3.2
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0
github.com/swaggo/swag v1.6.3 github.com/swaggo/swag v1.6.5
github.com/ulule/limiter/v3 v3.3.0 github.com/ulule/limiter/v3 v3.3.0
github.com/urfave/cli v1.22.2 // indirect
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad golang.org/x/crypto v0.0.0-20200117160349-530e935923ad
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 golang.org/x/lint v0.0.0-20190409202823-959b441ac422
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d // indirect golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825 // indirect
google.golang.org/appengine v1.5.0 // indirect google.golang.org/appengine v1.5.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/d4l3k/messagediff.v1 v1.2.1 gopkg.in/d4l3k/messagediff.v1 v1.2.1
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.7 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4 src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c

21
go.sum
View File

@ -77,10 +77,14 @@ github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw=
github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.7 h1:VRuXN2EnMSsZdauzdss6JBC29YotDqG59BZ+tdlIL1s=
github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
@ -260,8 +264,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.6.3 h1:N+uVPGP4H2hXoss2pt5dctoSUPKKRInr6qcTMOm0usI= github.com/swaggo/swag v1.6.5 h1:2C+t+xyK6p1sujqncYO/VnMvPZcBJjNdKKyxbOdAW8o=
github.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio= github.com/swaggo/swag v1.6.5/go.mod h1:Y7ZLSS0d0DdxhWGVhQdu+Bu1QhaF5k0RD7FKdiAykeY=
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
@ -291,11 +295,13 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE= golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
@ -352,9 +358,9 @@ golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqWWzTvgRCj0MxaUVfxEG1k= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqWWzTvgRCj0MxaUVfxEG1k=
golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d h1:/iIZNFGxc/a7C3yWjGcnboV+Tkc7mxr+p6fDztwoxuM= golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825 h1:aNQeSIHKi0RWpKA5NO0CqyLjx6Beh5l0LLUEnndEjz0=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -384,8 +390,9 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4 h1:DZKMg4qnT7UIyB5ZaC6ZqltF2K5KhA1oQ2PdxOLZ3jg= src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4 h1:DZKMg4qnT7UIyB5ZaC6ZqltF2K5KhA1oQ2PdxOLZ3jg=

View File

@ -38,7 +38,7 @@ type LinkShareToken struct {
// @Produce json // @Produce json
// @Param share path string true "The share hash" // @Param share path string true "The share hash"
// @Success 200 {object} v1.Token "The valid jwt auth token." // @Success 200 {object} v1.Token "The valid jwt auth token."
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid link share object provided." // @Failure 400 {object} web.HTTPError "Invalid link share object provided."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /shares/{share}/auth [post] // @Router /shares/{share}/auth [post]
func AuthenticateLinkShare(c echo.Context) error { func AuthenticateLinkShare(c echo.Context) error {

View File

@ -21,3 +21,8 @@ linters:
- lll - lll
- gochecknoinits - gochecknoinits
- gochecknoglobals - gochecknoglobals
- funlen
- godox
- gocognit
- whitespace
- wsl

View File

@ -452,11 +452,12 @@ func expandPathItem(pathItem *PathItem, resolver *schemaLoader, basePath string)
return err return err
} }
if pathItem.Ref.String() != "" { if pathItem.Ref.String() != "" {
var err error transitiveResolver, err := resolver.transitiveResolver(basePath, pathItem.Ref)
resolver, err = resolver.transitiveResolver(basePath, pathItem.Ref) if transitiveResolver.shouldStopOnError(err) {
if resolver.shouldStopOnError(err) {
return err return err
} }
basePath = transitiveResolver.updateBasePath(resolver, basePath)
resolver = transitiveResolver
} }
pathItem.Ref = Ref{} pathItem.Ref = Ref{}

View File

@ -4,14 +4,9 @@ require (
github.com/go-openapi/jsonpointer v0.19.3 github.com/go-openapi/jsonpointer v0.19.3
github.com/go-openapi/jsonreference v0.19.2 github.com/go-openapi/jsonreference v0.19.2
github.com/go-openapi/swag v0.19.5 github.com/go-openapi/swag v0.19.5
github.com/kr/pty v1.1.5 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 // indirect
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f // indirect gopkg.in/yaml.v2 v2.2.4
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 // indirect
gopkg.in/yaml.v2 v2.2.2
) )
go 1.13 go 1.13

View File

@ -1,5 +1,3 @@
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
@ -7,20 +5,12 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8=
github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0= github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE= github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
@ -28,11 +18,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
@ -40,35 +27,23 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -68,6 +68,7 @@ func (r *Ref) IsValidURI(basepaths ...string) bool {
} }
if r.HasFullURL { if r.HasFullURL {
//#nosec
rr, err := http.Get(v) rr, err := http.Get(v)
if err != nil { if err != nil {
return false return false

View File

@ -86,12 +86,7 @@ func (r *schemaLoader) transitiveResolver(basePath string, ref Ref) (*schemaLoad
newOptions := r.options newOptions := r.options
newOptions.RelativeBase = rootURL.String() newOptions.RelativeBase = rootURL.String()
debugLog("setting new root: %s", newOptions.RelativeBase) debugLog("setting new root: %s", newOptions.RelativeBase)
resolver, err := defaultSchemaLoader(root, newOptions, r.cache, r.context) return defaultSchemaLoader(root, newOptions, r.cache, r.context)
if err != nil {
return nil, err
}
return resolver, nil
} }
func (r *schemaLoader) updateBasePath(transitive *schemaLoader, basePath string) string { func (r *schemaLoader) updateBasePath(transitive *schemaLoader, basePath string) string {

View File

@ -88,7 +88,7 @@ func ConvertFloat64(str string) (float64, error) {
return strconv.ParseFloat(str, 64) return strconv.ParseFloat(str, 64)
} }
// ConvertInt8 turn a string into int8 boolean // ConvertInt8 turn a string into an int8
func ConvertInt8(str string) (int8, error) { func ConvertInt8(str string) (int8, error) {
i, err := strconv.ParseInt(str, 10, 8) i, err := strconv.ParseInt(str, 10, 8)
if err != nil { if err != nil {
@ -97,7 +97,7 @@ func ConvertInt8(str string) (int8, error) {
return int8(i), nil return int8(i), nil
} }
// ConvertInt16 turn a string into a int16 // ConvertInt16 turn a string into an int16
func ConvertInt16(str string) (int16, error) { func ConvertInt16(str string) (int16, error) {
i, err := strconv.ParseInt(str, 10, 16) i, err := strconv.ParseInt(str, 10, 16)
if err != nil { if err != nil {
@ -106,7 +106,7 @@ func ConvertInt16(str string) (int16, error) {
return int16(i), nil return int16(i), nil
} }
// ConvertInt32 turn a string into a int32 // ConvertInt32 turn a string into an int32
func ConvertInt32(str string) (int32, error) { func ConvertInt32(str string) (int32, error) {
i, err := strconv.ParseInt(str, 10, 32) i, err := strconv.ParseInt(str, 10, 32)
if err != nil { if err != nil {
@ -115,12 +115,12 @@ func ConvertInt32(str string) (int32, error) {
return int32(i), nil return int32(i), nil
} }
// ConvertInt64 turn a string into a int64 // ConvertInt64 turn a string into an int64
func ConvertInt64(str string) (int64, error) { func ConvertInt64(str string) (int64, error) {
return strconv.ParseInt(str, 10, 64) return strconv.ParseInt(str, 10, 64)
} }
// ConvertUint8 turn a string into a uint8 // ConvertUint8 turn a string into an uint8
func ConvertUint8(str string) (uint8, error) { func ConvertUint8(str string) (uint8, error) {
i, err := strconv.ParseUint(str, 10, 8) i, err := strconv.ParseUint(str, 10, 8)
if err != nil { if err != nil {
@ -129,7 +129,7 @@ func ConvertUint8(str string) (uint8, error) {
return uint8(i), nil return uint8(i), nil
} }
// ConvertUint16 turn a string into a uint16 // ConvertUint16 turn a string into an uint16
func ConvertUint16(str string) (uint16, error) { func ConvertUint16(str string) (uint16, error) {
i, err := strconv.ParseUint(str, 10, 16) i, err := strconv.ParseUint(str, 10, 16)
if err != nil { if err != nil {
@ -138,7 +138,7 @@ func ConvertUint16(str string) (uint16, error) {
return uint16(i), nil return uint16(i), nil
} }
// ConvertUint32 turn a string into a uint32 // ConvertUint32 turn a string into an uint32
func ConvertUint32(str string) (uint32, error) { func ConvertUint32(str string) (uint32, error) {
i, err := strconv.ParseUint(str, 10, 32) i, err := strconv.ParseUint(str, 10, 32)
if err != nil { if err != nil {
@ -147,7 +147,7 @@ func ConvertUint32(str string) (uint32, error) {
return uint32(i), nil return uint32(i), nil
} }
// ConvertUint64 turn a string into a uint64 // ConvertUint64 turn a string into an uint64
func ConvertUint64(str string) (uint64, error) { func ConvertUint64(str string) (uint64, error) {
return strconv.ParseUint(str, 10, 64) return strconv.ParseUint(str, 10, 64)
} }

View File

@ -181,12 +181,12 @@ func IntValueMap(src map[string]*int) map[string]int {
return dst return dst
} }
// Int32 returns a pointer to of the int64 value passed in. // Int32 returns a pointer to of the int32 value passed in.
func Int32(v int32) *int32 { func Int32(v int32) *int32 {
return &v return &v
} }
// Int32Value returns the value of the int64 pointer passed in or // Int32Value returns the value of the int32 pointer passed in or
// 0 if the pointer is nil. // 0 if the pointer is nil.
func Int32Value(v *int32) int32 { func Int32Value(v *int32) int32 {
if v != nil { if v != nil {
@ -195,7 +195,7 @@ func Int32Value(v *int32) int32 {
return 0 return 0
} }
// Int32Slice converts a slice of int64 values into a slice of // Int32Slice converts a slice of int32 values into a slice of
// int32 pointers // int32 pointers
func Int32Slice(src []int32) []*int32 { func Int32Slice(src []int32) []*int32 {
dst := make([]*int32, len(src)) dst := make([]*int32, len(src))
@ -299,13 +299,13 @@ func Int64ValueMap(src map[string]*int64) map[string]int64 {
return dst return dst
} }
// Uint returns a pouinter to of the uint value passed in. // Uint returns a pointer to of the uint value passed in.
func Uint(v uint) *uint { func Uint(v uint) *uint {
return &v return &v
} }
// UintValue returns the value of the uint pouinter passed in or // UintValue returns the value of the uint pointer passed in or
// 0 if the pouinter is nil. // 0 if the pointer is nil.
func UintValue(v *uint) uint { func UintValue(v *uint) uint {
if v != nil { if v != nil {
return *v return *v
@ -313,8 +313,8 @@ func UintValue(v *uint) uint {
return 0 return 0
} }
// UintSlice converts a slice of uint values uinto a slice of // UintSlice converts a slice of uint values into a slice of
// uint pouinters // uint pointers
func UintSlice(src []uint) []*uint { func UintSlice(src []uint) []*uint {
dst := make([]*uint, len(src)) dst := make([]*uint, len(src))
for i := 0; i < len(src); i++ { for i := 0; i < len(src); i++ {
@ -323,7 +323,7 @@ func UintSlice(src []uint) []*uint {
return dst return dst
} }
// UintValueSlice converts a slice of uint pouinters uinto a slice of // UintValueSlice converts a slice of uint pointers into a slice of
// uint values // uint values
func UintValueSlice(src []*uint) []uint { func UintValueSlice(src []*uint) []uint {
dst := make([]uint, len(src)) dst := make([]uint, len(src))
@ -335,8 +335,8 @@ func UintValueSlice(src []*uint) []uint {
return dst return dst
} }
// UintMap converts a string map of uint values uinto a string // UintMap converts a string map of uint values into a string
// map of uint pouinters // map of uint pointers
func UintMap(src map[string]uint) map[string]*uint { func UintMap(src map[string]uint) map[string]*uint {
dst := make(map[string]*uint) dst := make(map[string]*uint)
for k, val := range src { for k, val := range src {
@ -346,7 +346,7 @@ func UintMap(src map[string]uint) map[string]*uint {
return dst return dst
} }
// UintValueMap converts a string map of uint pouinters uinto a string // UintValueMap converts a string map of uint pointers into a string
// map of uint values // map of uint values
func UintValueMap(src map[string]*uint) map[string]uint { func UintValueMap(src map[string]*uint) map[string]uint {
dst := make(map[string]uint) dst := make(map[string]uint)
@ -358,13 +358,13 @@ func UintValueMap(src map[string]*uint) map[string]uint {
return dst return dst
} }
// Uint32 returns a pouinter to of the uint64 value passed in. // Uint32 returns a pointer to of the uint32 value passed in.
func Uint32(v uint32) *uint32 { func Uint32(v uint32) *uint32 {
return &v return &v
} }
// Uint32Value returns the value of the uint64 pouinter passed in or // Uint32Value returns the value of the uint32 pointer passed in or
// 0 if the pouinter is nil. // 0 if the pointer is nil.
func Uint32Value(v *uint32) uint32 { func Uint32Value(v *uint32) uint32 {
if v != nil { if v != nil {
return *v return *v
@ -372,8 +372,8 @@ func Uint32Value(v *uint32) uint32 {
return 0 return 0
} }
// Uint32Slice converts a slice of uint64 values uinto a slice of // Uint32Slice converts a slice of uint32 values into a slice of
// uint32 pouinters // uint32 pointers
func Uint32Slice(src []uint32) []*uint32 { func Uint32Slice(src []uint32) []*uint32 {
dst := make([]*uint32, len(src)) dst := make([]*uint32, len(src))
for i := 0; i < len(src); i++ { for i := 0; i < len(src); i++ {
@ -382,7 +382,7 @@ func Uint32Slice(src []uint32) []*uint32 {
return dst return dst
} }
// Uint32ValueSlice converts a slice of uint32 pouinters uinto a slice of // Uint32ValueSlice converts a slice of uint32 pointers into a slice of
// uint32 values // uint32 values
func Uint32ValueSlice(src []*uint32) []uint32 { func Uint32ValueSlice(src []*uint32) []uint32 {
dst := make([]uint32, len(src)) dst := make([]uint32, len(src))
@ -394,8 +394,8 @@ func Uint32ValueSlice(src []*uint32) []uint32 {
return dst return dst
} }
// Uint32Map converts a string map of uint32 values uinto a string // Uint32Map converts a string map of uint32 values into a string
// map of uint32 pouinters // map of uint32 pointers
func Uint32Map(src map[string]uint32) map[string]*uint32 { func Uint32Map(src map[string]uint32) map[string]*uint32 {
dst := make(map[string]*uint32) dst := make(map[string]*uint32)
for k, val := range src { for k, val := range src {
@ -405,7 +405,7 @@ func Uint32Map(src map[string]uint32) map[string]*uint32 {
return dst return dst
} }
// Uint32ValueMap converts a string map of uint32 pouinters uinto a string // Uint32ValueMap converts a string map of uint32 pointers into a string
// map of uint32 values // map of uint32 values
func Uint32ValueMap(src map[string]*uint32) map[string]uint32 { func Uint32ValueMap(src map[string]*uint32) map[string]uint32 {
dst := make(map[string]uint32) dst := make(map[string]uint32)
@ -417,13 +417,13 @@ func Uint32ValueMap(src map[string]*uint32) map[string]uint32 {
return dst return dst
} }
// Uint64 returns a pouinter to of the uint64 value passed in. // Uint64 returns a pointer to of the uint64 value passed in.
func Uint64(v uint64) *uint64 { func Uint64(v uint64) *uint64 {
return &v return &v
} }
// Uint64Value returns the value of the uint64 pouinter passed in or // Uint64Value returns the value of the uint64 pointer passed in or
// 0 if the pouinter is nil. // 0 if the pointer is nil.
func Uint64Value(v *uint64) uint64 { func Uint64Value(v *uint64) uint64 {
if v != nil { if v != nil {
return *v return *v
@ -431,8 +431,8 @@ func Uint64Value(v *uint64) uint64 {
return 0 return 0
} }
// Uint64Slice converts a slice of uint64 values uinto a slice of // Uint64Slice converts a slice of uint64 values into a slice of
// uint64 pouinters // uint64 pointers
func Uint64Slice(src []uint64) []*uint64 { func Uint64Slice(src []uint64) []*uint64 {
dst := make([]*uint64, len(src)) dst := make([]*uint64, len(src))
for i := 0; i < len(src); i++ { for i := 0; i < len(src); i++ {
@ -441,7 +441,7 @@ func Uint64Slice(src []uint64) []*uint64 {
return dst return dst
} }
// Uint64ValueSlice converts a slice of uint64 pouinters uinto a slice of // Uint64ValueSlice converts a slice of uint64 pointers into a slice of
// uint64 values // uint64 values
func Uint64ValueSlice(src []*uint64) []uint64 { func Uint64ValueSlice(src []*uint64) []uint64 {
dst := make([]uint64, len(src)) dst := make([]uint64, len(src))
@ -453,8 +453,8 @@ func Uint64ValueSlice(src []*uint64) []uint64 {
return dst return dst
} }
// Uint64Map converts a string map of uint64 values uinto a string // Uint64Map converts a string map of uint64 values into a string
// map of uint64 pouinters // map of uint64 pointers
func Uint64Map(src map[string]uint64) map[string]*uint64 { func Uint64Map(src map[string]uint64) map[string]*uint64 {
dst := make(map[string]*uint64) dst := make(map[string]*uint64)
for k, val := range src { for k, val := range src {
@ -464,7 +464,7 @@ func Uint64Map(src map[string]uint64) map[string]*uint64 {
return dst return dst
} }
// Uint64ValueMap converts a string map of uint64 pouinters uinto a string // Uint64ValueMap converts a string map of uint64 pointers into a string
// map of uint64 values // map of uint64 values
func Uint64ValueMap(src map[string]*uint64) map[string]uint64 { func Uint64ValueMap(src map[string]*uint64) map[string]uint64 {
dst := make(map[string]uint64) dst := make(map[string]uint64)

View File

@ -6,9 +6,11 @@ require (
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.3.0
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.4
) )
replace github.com/golang/lint => golang.org/x/lint v0.0.0-20190409202823-959b441ac422 replace github.com/golang/lint => golang.org/x/lint v0.0.0-20190409202823-959b441ac422
replace sourcegraph.com/sourcegraph/go-diff => github.com/sourcegraph/go-diff v0.5.1 replace sourcegraph.com/sourcegraph/go-diff => github.com/sourcegraph/go-diff v0.5.1
go 1.13

View File

@ -16,5 +16,5 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -51,7 +51,7 @@ type ejUnmarshaler interface {
UnmarshalEasyJSON(w *jlexer.Lexer) UnmarshalEasyJSON(w *jlexer.Lexer)
} }
// WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaller // WriteJSON writes json data, prefers finding an appropriate interface to short-circuit the marshaler
// so it takes the fastest option available. // so it takes the fastest option available.
func WriteJSON(data interface{}) ([]byte, error) { func WriteJSON(data interface{}) ([]byte, error) {
if d, ok := data.(ejMarshaler); ok { if d, ok := data.(ejMarshaler); ok {
@ -65,8 +65,8 @@ func WriteJSON(data interface{}) ([]byte, error) {
return json.Marshal(data) return json.Marshal(data)
} }
// ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaller // ReadJSON reads json data, prefers finding an appropriate interface to short-circuit the unmarshaler
// so it takes the fastes option available // so it takes the fastest option available
func ReadJSON(data []byte, value interface{}) error { func ReadJSON(data []byte, value interface{}) error {
trimmedData := bytes.Trim(data, "\x00") trimmedData := bytes.Trim(data, "\x00")
if d, ok := value.(ejUnmarshaler); ok { if d, ok := value.(ejUnmarshaler); ok {
@ -189,7 +189,7 @@ func FromDynamicJSON(data, target interface{}) error {
return json.Unmarshal(b, target) return json.Unmarshal(b, target)
} }
// NameProvider represents an object capabale of translating from go property names // NameProvider represents an object capable of translating from go property names
// to json property names // to json property names
// This type is thread-safe. // This type is thread-safe.
type NameProvider struct { type NameProvider struct {

View File

@ -22,3 +22,5 @@ require (
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/stretchr/testify.v1 v1.2.2 gopkg.in/stretchr/testify.v1 v1.2.2
) )
go 1.13

View File

@ -1,3 +1,5 @@
module github.com/hashicorp/hcl module github.com/hashicorp/hcl
require github.com/davecgh/go-spew v1.1.1 require github.com/davecgh/go-spew v1.1.1
go 1.13

View File

@ -1,3 +1,3 @@
module github.com/prometheus/procfs module github.com/prometheus/procfs
go 1.12 go 1.13

View File

@ -22,3 +22,5 @@ require (
golang.org/x/text v0.3.0 // indirect golang.org/x/text v0.3.0 // indirect
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2
) )
go 1.13

View File

@ -2,7 +2,6 @@ language: go
sudo: false sudo: false
go: go:
- 1.10.x
- 1.11.x - 1.11.x
- 1.12.x - 1.12.x

View File

@ -9,6 +9,7 @@ GOTEST:=$(GOCMD) test
GOGET:=$(GOCMD) get GOGET:=$(GOCMD) get
GOLIST:=$(GOCMD) list GOLIST:=$(GOCMD) list
GOVET:=$(GOCMD) vet GOVET:=$(GOCMD) vet
GOPATH:=$(shell $(GOCMD) env GOPATH)
u := $(if $(update),-u) u := $(if $(update),-u)
BINARY_NAME:=swag BINARY_NAME:=swag
@ -53,16 +54,20 @@ clean:
.PHONY: deps .PHONY: deps
deps: deps:
$(GOGET) ${u} -d $(GOGET) github.com/swaggo/cli
$(GOGET) github.com/ghodss/yaml
$(GOGET) github.com/gin-gonic/gin
$(GOGET) github.com/KyleBanks/depth
$(GOGET) github.com/go-openapi/jsonreference
$(GOGET) github.com/go-openapi/spec
$(GOGET) github.com/stretchr/testify/assert $(GOGET) github.com/stretchr/testify/assert
$(GOGET) github.com/alecthomas/template $(GOGET) github.com/alecthomas/template
$(GOGET) golang.org/x/tools/go/loader
.PHONY: devel-deps .PHONY: devel-deps
devel-deps: devel-deps:
GO111MODULE=off $(GOGET) -v -u \ GO111MODULE=off $(GOGET) -v -u \
golang.org/x/lint/golint \ golang.org/x/lint/golint
github.com/swaggo/swag/cmd/swag \
github.com/swaggo/swag/gen
.PHONY: lint .PHONY: lint
lint: devel-deps lint: devel-deps

View File

@ -45,7 +45,7 @@ $ go get -u github.com/swaggo/swag/cmd/swag
``` ```
To build from source you need [Go](https://golang.org/dl/) (1.9 or newer). To build from source you need [Go](https://golang.org/dl/) (1.9 or newer).
Or download the pre-compiled binaries binray form [release page](https://github.com/swaggo/swag/releases). Or download a pre-compiled binary from the [release page](https://github.com/swaggo/swag/releases).
3. Run `swag init` in the project's root folder which contains the `main.go` file. This will parse your comments and generate the required files (`docs` folder and `docs/docs.go`). 3. Run `swag init` in the project's root folder which contains the `main.go` file. This will parse your comments and generate the required files (`docs` folder and `docs/docs.go`).
```sh ```sh
@ -71,7 +71,7 @@ OPTIONS:
--generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go") --generalInfo value, -g value Go file path in which 'swagger general API Info' is written (default: "main.go")
--dir value, -d value Directory you want to parse (default: "./") --dir value, -d value Directory you want to parse (default: "./")
--propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase") --propertyStrategy value, -p value Property Naming Strategy like snakecase,camelcase,pascalcase (default: "camelcase")
--output value, -o value Output directory for al the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs") --output value, -o value Output directory for all the generated files(swagger.json, swagger.yaml and doc.go) (default: "./docs")
--parseVendor Parse go files in 'vendor' folder, disabled by default --parseVendor Parse go files in 'vendor' folder, disabled by default
--parseDependency Parse go files in outside dependency folder, disabled by default --parseDependency Parse go files in outside dependency folder, disabled by default
``` ```
@ -277,7 +277,7 @@ func (c *Controller) ListAccounts(ctx *gin.Context) {
$ swag init $ swag init
``` ```
4.Run your app, and browse to http://localhost:8080/swagger/index.html. You will see Swagger 2.0 Api documents as shown below: 4. Run your app, and browse to http://localhost:8080/swagger/index.html. You will see Swagger 2.0 Api documents as shown below:
![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png) ![swagger_index.html](https://raw.githubusercontent.com/swaggo/swag/master/assets/swagger-image.png)
@ -598,7 +598,7 @@ generated swagger doc as follows:
```go ```go
type Account struct { type Account struct {
ID int `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-" ID string `json:"id" extensions:"x-nullable,x-abc=def"` // extensions fields must start with "x-"
} }
``` ```

View File

@ -10,13 +10,77 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
const searchDirFlag = "dir" const (
const generalInfoFlag = "generalInfo" searchDirFlag = "dir"
const propertyStrategyFlag = "propertyStrategy" generalInfoFlag = "generalInfo"
const outputFlag = "output" propertyStrategyFlag = "propertyStrategy"
const parseVendorFlag = "parseVendor" outputFlag = "output"
const parseDependency = "parseDependency" parseVendorFlag = "parseVendor"
const markdownFilesDirFlag = "markdownFiles" parseDependencyFlag = "parseDependency"
markdownFilesFlag = "markdownFiles"
generatedTimeFlag = "generatedTime"
)
var initFlags = []cli.Flag{
cli.StringFlag{
Name: generalInfoFlag + ", g",
Value: "main.go",
Usage: "Go file path in which 'swagger general API Info' is written",
},
cli.StringFlag{
Name: searchDirFlag + ", d",
Value: "./",
Usage: "Directory you want to parse",
},
cli.StringFlag{
Name: propertyStrategyFlag + ", p",
Value: "camelcase",
Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase",
},
cli.StringFlag{
Name: outputFlag + ", o",
Value: "./docs",
Usage: "Output directory for all the generated files(swagger.json, swagger.yaml and doc.go)",
},
cli.BoolFlag{
Name: parseVendorFlag,
Usage: "Parse go files in 'vendor' folder, disabled by default",
},
cli.BoolFlag{
Name: parseDependencyFlag,
Usage: "Parse go files in outside dependency folder, disabled by default",
},
cli.StringFlag{
Name: markdownFilesFlag + ", md",
Value: "",
Usage: "Parse folder containing markdown files to use as description, disabled by default",
},
cli.BoolTFlag{
Name: "generatedTime",
Usage: "Generate timestamp at the top of docs.go, true by default",
},
}
func initAction(c *cli.Context) error {
strategy := c.String(propertyStrategyFlag)
switch strategy {
case swag.CamelCase, swag.SnakeCase, swag.PascalCase:
default:
return fmt.Errorf("not supported %s propertyStrategy", strategy)
}
return gen.New().Build(&gen.Config{
SearchDir: c.String(searchDirFlag),
MainAPIFile: c.String(generalInfoFlag),
PropNamingStrategy: strategy,
OutputDir: c.String(outputFlag),
ParseVendor: c.Bool(parseVendorFlag),
ParseDependency: c.Bool(parseDependencyFlag),
MarkdownFilesDir: c.String(markdownFilesFlag),
GeneratedTime: c.BoolT(generatedTimeFlag),
})
}
func main() { func main() {
app := cli.NewApp() app := cli.NewApp()
@ -27,69 +91,10 @@ func main() {
Name: "init", Name: "init",
Aliases: []string{"i"}, Aliases: []string{"i"},
Usage: "Create docs.go", Usage: "Create docs.go",
Action: func(c *cli.Context) error { Action: initAction,
searchDir := c.String(searchDirFlag) Flags: initFlags,
mainAPIFile := c.String(generalInfoFlag)
strategy := c.String(propertyStrategyFlag)
outputDir := c.String(outputFlag)
parseVendor := c.Bool(parseVendorFlag)
parseDependency := c.Bool(parseDependency)
markdownFilesDir := c.String(markdownFilesDirFlag)
switch strategy {
case swag.CamelCase, swag.SnakeCase, swag.PascalCase:
default:
return fmt.Errorf("not supported %s propertyStrategy", strategy)
}
return gen.New().Build(&gen.Config{
SearchDir: searchDir,
MainAPIFile: mainAPIFile,
PropNamingStrategy: strategy,
OutputDir: outputDir,
ParseVendor: parseVendor,
ParseDependency: parseDependency,
MarkdownFilesDir: markdownFilesDir,
})
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "generalInfo, g",
Value: "main.go",
Usage: "Go file path in which 'swagger general API Info' is written",
},
cli.StringFlag{
Name: "dir, d",
Value: "./",
Usage: "Directory you want to parse",
},
cli.StringFlag{
Name: "propertyStrategy, p",
Value: "camelcase",
Usage: "Property Naming Strategy like snakecase,camelcase,pascalcase",
},
cli.StringFlag{
Name: "output, o",
Value: "./docs",
Usage: "Output directory for al the generated files(swagger.json, swagger.yaml and doc.go)",
},
cli.BoolFlag{
Name: "parseVendor",
Usage: "Parse go files in 'vendor' folder, disabled by default",
},
cli.BoolFlag{
Name: "parseDependency",
Usage: "Parse go files in outside dependency folder, disabled by default",
},
cli.StringFlag{
Name: "markdownFiles, md",
Value: "",
Usage: "Parse folder containing markdown files to use as description, disabled by default",
},
},
}, },
} }
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -20,11 +20,19 @@ import (
) )
// Gen presents a generate tool for swag. // Gen presents a generate tool for swag.
type Gen struct{} type Gen struct {
jsonIndent func(data interface{}) ([]byte, error)
jsonToYAML func(data []byte) ([]byte, error)
}
// New creates a new Gen. // New creates a new Gen.
func New() *Gen { func New() *Gen {
return &Gen{} return &Gen{
jsonIndent: func(data interface{}) ([]byte, error) {
return json.MarshalIndent(data, "", " ")
},
jsonToYAML: yaml.JSONToYAML,
}
} }
// Config presents Gen configurations. // Config presents Gen configurations.
@ -32,7 +40,7 @@ type Config struct {
// SearchDir the swag would be parse // SearchDir the swag would be parse
SearchDir string SearchDir string
// OutputDir represents the output directory for al the generated files // OutputDir represents the output directory for all the generated files
OutputDir string OutputDir string
// MainAPIFile the Go file path in which 'swagger general API Info' is written // MainAPIFile the Go file path in which 'swagger general API Info' is written
@ -49,6 +57,9 @@ type Config struct {
// MarkdownFilesDir used to find markdownfiles, which can be used for tag descriptions // MarkdownFilesDir used to find markdownfiles, which can be used for tag descriptions
MarkdownFilesDir string MarkdownFilesDir string
// GeneratedTime whether swag should generate the timestamp at the top of docs.go
GeneratedTime bool
} }
// Build builds swagger json file for given searchDir and mainAPIFile. Returns json // Build builds swagger json file for given searchDir and mainAPIFile. Returns json
@ -77,52 +88,53 @@ func (g *Gen) Build(config *Config) error {
return err return err
} }
docs, err := os.Create(path.Join(config.OutputDir, "docs.go")) docFileName := path.Join(config.OutputDir, "docs.go")
jsonFileName := path.Join(config.OutputDir, "swagger.json")
yamlFileName := path.Join(config.OutputDir, "swagger.yaml")
docs, err := os.Create(docFileName)
if err != nil { if err != nil {
return err return err
} }
defer docs.Close() defer docs.Close()
swaggerJSON, err := os.Create(path.Join(config.OutputDir, "swagger.json")) err = g.writeFile(b, jsonFileName)
if err != nil {
return err
}
defer swaggerJSON.Close()
if _, err := swaggerJSON.Write(b); err != nil {
return err
}
swaggerYAML, err := os.Create(path.Join(config.OutputDir, "swagger.yaml"))
if err != nil { if err != nil {
return err return err
} }
defer swaggerYAML.Close() y, err := g.jsonToYAML(b)
y, err := yaml.JSONToYAML(b)
if err != nil { if err != nil {
return fmt.Errorf("cannot covert json to yaml error: %s", err) return fmt.Errorf("cannot covert json to yaml error: %s", err)
} }
if _, err := swaggerYAML.Write(y); err != nil { err = g.writeFile(y, yamlFileName)
return err
}
// Write doc
err = g.writeGoDoc(docs, swagger)
if err != nil { if err != nil {
return err return err
} }
log.Printf("create docs.go at %+v", docs.Name()) // Write doc
log.Printf("create swagger.json at %+v", swaggerJSON.Name()) err = g.writeGoDoc(docs, swagger, config)
log.Printf("create swagger.yaml at %+v", swaggerYAML.Name()) if err != nil {
return err
}
log.Printf("create docs.go at %+v", docFileName)
log.Printf("create swagger.json at %+v", jsonFileName)
log.Printf("create swagger.yaml at %+v", yamlFileName)
return nil return nil
} }
func (g *Gen) jsonIndent(data interface{}) ([]byte, error) { func (g *Gen) writeFile(b []byte, file string) error {
return json.MarshalIndent(data, "", " ") f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(b)
return err
} }
func (g *Gen) formatSource(src []byte) []byte { func (g *Gen) formatSource(src []byte) []byte {
@ -133,7 +145,7 @@ func (g *Gen) formatSource(src []byte) []byte {
return code return code
} }
func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger) error { func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger, config *Config) error {
generator, err := template.New("swagger_info").Funcs(template.FuncMap{ generator, err := template.New("swagger_info").Funcs(template.FuncMap{
"printDoc": func(v string) string { "printDoc": func(v string) string {
@ -186,23 +198,25 @@ func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger) error {
buffer := &bytes.Buffer{} buffer := &bytes.Buffer{}
err = generator.Execute(buffer, struct { err = generator.Execute(buffer, struct {
Timestamp time.Time Timestamp time.Time
Doc string GeneratedTime bool
Host string Doc string
BasePath string Host string
Schemes []string BasePath string
Title string Schemes []string
Description string Title string
Version string Description string
Version string
}{ }{
Timestamp: time.Now(), Timestamp: time.Now(),
Doc: string(buf), GeneratedTime: config.GeneratedTime,
Host: swagger.Host, Doc: string(buf),
BasePath: swagger.BasePath, Host: swagger.Host,
Schemes: swagger.Schemes, BasePath: swagger.BasePath,
Title: swagger.Info.Title, Schemes: swagger.Schemes,
Description: swagger.Info.Description, Title: swagger.Info.Title,
Version: swagger.Info.Version, Description: swagger.Info.Description,
Version: swagger.Info.Version,
}) })
if err != nil { if err != nil {
return err return err
@ -213,12 +227,11 @@ func (g *Gen) writeGoDoc(output io.Writer, swagger *spec.Swagger) error {
// write // write
_, err = output.Write(code) _, err = output.Write(code)
return err return err
} }
var packageTemplate = `// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT var packageTemplate = `// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at // This file was generated by swaggo/swag{{ if .GeneratedTime }} at
// {{ .Timestamp }} // {{ .Timestamp }}{{ end }}
package docs package docs

14
vendor/github.com/swaggo/swag/go.mod generated vendored
View File

@ -4,11 +4,15 @@ require (
github.com/KyleBanks/depth v1.2.1 github.com/KyleBanks/depth v1.2.1
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/go-openapi/jsonreference v0.19.0 github.com/gin-gonic/gin v1.4.0
github.com/go-openapi/spec v0.19.0 github.com/go-openapi/jsonreference v0.19.3
github.com/stretchr/testify v1.3.0 github.com/go-openapi/spec v0.19.4
github.com/satori/go.uuid v1.2.0
github.com/stretchr/testify v1.4.0
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
github.com/swaggo/gin-swagger v1.2.0 github.com/swaggo/gin-swagger v1.2.0
github.com/urfave/cli v1.20.0 github.com/urfave/cli v1.22.2
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59
) )
go 1.13

60
vendor/github.com/swaggo/swag/go.sum generated vendored
View File

@ -1,74 +1,127 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.5-pre h1:jyJKFOSEbdOc2HODrf2qcCkYOdq7zzXqA9bhW5oV4fM=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -76,9 +129,14 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=

View File

@ -144,6 +144,7 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
if strings.HasPrefix(refType, "[]") == true { if strings.HasPrefix(refType, "[]") == true {
objectType = "array" objectType = "array"
refType = strings.TrimPrefix(refType, "[]") refType = strings.TrimPrefix(refType, "[]")
refType = TransToValidSchemeType(refType)
} else if IsPrimitiveType(refType) || } else if IsPrimitiveType(refType) ||
paramType == "formData" && refType == "file" { paramType == "formData" && refType == "file" {
objectType = "primitive" objectType = "primitive"
@ -174,32 +175,96 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
}, },
} }
case "object": case "object":
return fmt.Errorf("%s is not supported type for %s", refType, paramType) refType, typeSpec, err := operation.registerSchemaType(refType, astFile)
if err != nil {
return err
}
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
return fmt.Errorf("%s is not supported type for %s", refType, paramType)
}
refSplit := strings.Split(refType, ".")
schema, err := operation.parser.parseStruct(refSplit[0], structType.Fields)
if err != nil {
return err
}
if len(schema.Properties) == 0 {
return nil
}
find := func(arr []string, target string) bool {
for _, str := range arr {
if str == target {
return true
}
}
return false
}
for name, prop := range schema.Properties {
if len(prop.Type) == 0 {
continue
}
if prop.Type[0] == "array" &&
prop.Items.Schema != nil &&
len(prop.Items.Schema.Type) > 0 &&
IsSimplePrimitiveType(prop.Items.Schema.Type[0]) {
param = createParameter(paramType, prop.Description, name, prop.Type[0], find(schema.Required, name))
param.SimpleSchema.Type = prop.Type[0]
param.SimpleSchema.Items = &spec.Items{
SimpleSchema: spec.SimpleSchema{
Type: prop.Items.Schema.Type[0],
},
}
} else if IsSimplePrimitiveType(prop.Type[0]) {
param = createParameter(paramType, prop.Description, name, prop.Type[0], find(schema.Required, name))
} else {
Println(fmt.Sprintf("skip field [%s] in %s is not supported type for %s", name, refType, paramType))
continue
}
param.CommonValidations.Maximum = prop.Maximum
param.CommonValidations.Minimum = prop.Minimum
param.CommonValidations.ExclusiveMaximum = prop.ExclusiveMaximum
param.CommonValidations.ExclusiveMinimum = prop.ExclusiveMinimum
param.CommonValidations.MaxLength = prop.MaxLength
param.CommonValidations.MinLength = prop.MinLength
param.CommonValidations.Pattern = prop.Pattern
param.CommonValidations.MaxItems = prop.MaxItems
param.CommonValidations.MinItems = prop.MinItems
param.CommonValidations.UniqueItems = prop.UniqueItems
param.CommonValidations.MultipleOf = prop.MultipleOf
param.CommonValidations.Enum = prop.Enum
operation.Operation.Parameters = append(operation.Operation.Parameters, param)
}
return nil
} }
case "body": case "body":
switch objectType { switch objectType {
case "primitive": case "primitive":
param.Schema.Type = spec.StringOrArray{refType} param.Schema.Type = spec.StringOrArray{refType}
case "array": case "array":
param.Schema.Type = spec.StringOrArray{objectType}
param.Schema.Items = &spec.SchemaOrArray{ param.Schema.Items = &spec.SchemaOrArray{
Schema: &spec.Schema{ Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{}, SchemaProps: spec.SchemaProps{},
}, },
} }
// Arrau of Primitive or Object // Array of Primitive or Object
if IsPrimitiveType(refType) { if IsPrimitiveType(refType) {
param.Schema.Items.Schema.Type = spec.StringOrArray{refType} param.Schema.Items.Schema.Type = spec.StringOrArray{refType}
} else { } else {
if err := operation.registerSchemaType(refType, astFile); err != nil { var err error
refType, _, err = operation.registerSchemaType(refType, astFile)
if err != nil {
return err return err
} }
param.Schema.Items.Schema.Ref = spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)} param.Schema.Items.Schema.Ref = spec.Ref{Ref: jsonreference.MustCreateRef("#/definitions/" + refType)}
} }
case "object": case "object":
if err := operation.registerSchemaType(refType, astFile); err != nil { var err error
refType, _, err = operation.registerSchemaType(refType, astFile)
if err != nil {
return err return err
} }
param.Schema.Type = spec.StringOrArray{objectType} param.Schema.Type = []string{}
param.Schema.Ref = spec.Ref{ param.Schema.Ref = spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + refType), Ref: jsonreference.MustCreateRef("#/definitions/" + refType),
} }
@ -215,20 +280,23 @@ func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.F
return nil return nil
} }
func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.File) error { func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.File) (string, *ast.TypeSpec, error) {
refSplit := strings.Split(schemaType, ".") if !strings.ContainsRune(schemaType, '.') {
if len(refSplit) != 2 { if astFile == nil {
return nil return schemaType, nil, fmt.Errorf("no package name for type %s", schemaType)
}
schemaType = astFile.Name.String() + "." + schemaType
} }
refSplit := strings.Split(schemaType, ".")
pkgName := refSplit[0] pkgName := refSplit[0]
typeName := refSplit[1] typeName := refSplit[1]
if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok { if typeSpec, ok := operation.parser.TypeDefinitions[pkgName][typeName]; ok {
operation.parser.registerTypes[schemaType] = typeSpec operation.parser.registerTypes[schemaType] = typeSpec
return nil return schemaType, typeSpec, nil
} }
var typeSpec *ast.TypeSpec var typeSpec *ast.TypeSpec
if astFile == nil { if astFile == nil {
return fmt.Errorf("can not register schema type: %q reason: astFile == nil", schemaType) return schemaType, nil, fmt.Errorf("can not register schema type: %q reason: astFile == nil", schemaType)
} }
for _, imp := range astFile.Imports { for _, imp := range astFile.Imports {
if imp.Name != nil && imp.Name.Name == pkgName { // the import had an alias that matched if imp.Name != nil && imp.Name.Name == pkgName { // the import had an alias that matched
@ -239,14 +307,14 @@ func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.F
var err error var err error
typeSpec, err = findTypeDef(impPath, typeName) typeSpec, err = findTypeDef(impPath, typeName)
if err != nil { if err != nil {
return fmt.Errorf("can not find type def: %q error: %s", schemaType, err) return schemaType, nil, fmt.Errorf("can not find type def: %q error: %s", schemaType, err)
} }
break break
} }
} }
if typeSpec == nil { if typeSpec == nil {
return fmt.Errorf("can not find schema type: %q", schemaType) return schemaType, nil, fmt.Errorf("can not find schema type: %q", schemaType)
} }
if _, ok := operation.parser.TypeDefinitions[pkgName]; !ok { if _, ok := operation.parser.TypeDefinitions[pkgName]; !ok {
@ -255,7 +323,7 @@ func (operation *Operation) registerSchemaType(schemaType string, astFile *ast.F
operation.parser.TypeDefinitions[pkgName][typeName] = typeSpec operation.parser.TypeDefinitions[pkgName][typeName] = typeSpec
operation.parser.registerTypes[schemaType] = typeSpec operation.parser.registerTypes[schemaType] = typeSpec
return nil return schemaType, typeSpec, nil
} }
var regexAttributes = map[string]*regexp.Regexp{ var regexAttributes = map[string]*regexp.Regexp{
@ -437,7 +505,7 @@ func parseMimeTypeList(mimeTypeList string, typeList *[]string, format string) e
return nil return nil
} }
var routerPattern = regexp.MustCompile(`([\w\.\/\-{}\+]+)[^\[]+\[([^\]]+)`) var routerPattern = regexp.MustCompile(`^(/[\w\.\/\-{}\+:]*)[[:blank:]]+\[(\w+)]`)
// ParseRouterComment parses comment for gived `router` comment string. // ParseRouterComment parses comment for gived `router` comment string.
func (operation *Operation) ParseRouterComment(commentLine string) error { func (operation *Operation) ParseRouterComment(commentLine string) error {
@ -563,14 +631,12 @@ func (operation *Operation) ParseResponseComment(commentLine string, astFile *as
schemaType := strings.Trim(matches[2], "{}") schemaType := strings.Trim(matches[2], "{}")
refType := matches[3] refType := matches[3]
if !IsGolangPrimitiveType(refType) && !strings.Contains(refType, ".") { if !IsGolangPrimitiveType(refType) {
currentPkgName := astFile.Name.String() if operation.parser != nil { // checking refType has existing in 'TypeDefinitions'
refType = currentPkgName + "." + refType var err error
} if refType, _, err = operation.registerSchemaType(refType, astFile); err != nil {
return err
if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' }
if err := operation.registerSchemaType(refType, astFile); err != nil {
return err
} }
} }

View File

@ -46,6 +46,9 @@ type Parser struct {
// TypeDefinitions is a map that stores [package name][type name][*ast.TypeSpec] // TypeDefinitions is a map that stores [package name][type name][*ast.TypeSpec]
TypeDefinitions map[string]map[string]*ast.TypeSpec TypeDefinitions map[string]map[string]*ast.TypeSpec
// ImportAliases is map that stores [import name][import package name][*ast.ImportSpec]
ImportAliases map[string]map[string]*ast.ImportSpec
// CustomPrimitiveTypes is a map that stores custom primitive types to actual golang types [type name][string] // CustomPrimitiveTypes is a map that stores custom primitive types to actual golang types [type name][string]
CustomPrimitiveTypes map[string]string CustomPrimitiveTypes map[string]string
@ -85,6 +88,7 @@ func New(options ...func(*Parser)) *Parser {
}, },
files: make(map[string]*ast.File), files: make(map[string]*ast.File),
TypeDefinitions: make(map[string]map[string]*ast.TypeSpec), TypeDefinitions: make(map[string]map[string]*ast.TypeSpec),
ImportAliases: make(map[string]map[string]*ast.ImportSpec),
CustomPrimitiveTypes: make(map[string]string), CustomPrimitiveTypes: make(map[string]string),
registerTypes: make(map[string]*ast.TypeSpec), registerTypes: make(map[string]*ast.TypeSpec),
} }
@ -521,6 +525,23 @@ func (parser *Parser) ParseType(astFile *ast.File) {
} }
} }
} }
for _, importSpec := range astFile.Imports {
if importSpec.Name == nil {
continue
}
alias := importSpec.Name.Name
if _, ok := parser.ImportAliases[alias]; !ok {
parser.ImportAliases[alias] = make(map[string]*ast.ImportSpec)
}
importParts := strings.Split(strings.Trim(importSpec.Path.Value, "\""), "/")
importPkgName := importParts[len(importParts)-1]
parser.ImportAliases[alias][importPkgName] = importSpec
}
} }
func (parser *Parser) isInStructStack(refTypeName string) bool { func (parser *Parser) isInStructStack(refTypeName string) bool {
@ -581,7 +602,7 @@ func (parser *Parser) ParseDefinition(pkgName, typeName string, typeSpec *ast.Ty
if err != nil { if err != nil {
return err return err
} }
parser.swagger.Definitions[refTypeName] = schema parser.swagger.Definitions[refTypeName] = *schema
return nil return nil
} }
@ -605,9 +626,7 @@ func (parser *Parser) collectRequiredFields(pkgName string, properties map[strin
tspec := parser.TypeDefinitions[pkgName][tname] tspec := parser.TypeDefinitions[pkgName][tname]
parser.ParseDefinition(pkgName, tname, tspec) parser.ParseDefinition(pkgName, tname, tspec)
} }
if tname != "object" { requiredFields = append(requiredFields, prop.SchemaProps.Required...)
requiredFields = append(requiredFields, prop.SchemaProps.Required...)
}
properties[k] = prop properties[k] = prop
} }
@ -629,28 +648,40 @@ func fullTypeName(pkgName, typeName string) string {
// parseTypeExpr parses given type expression that corresponds to the type under // parseTypeExpr parses given type expression that corresponds to the type under
// given name and package, and returns swagger schema for it. // given name and package, and returns swagger schema for it.
func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) (spec.Schema, error) { func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr) (*spec.Schema, error) {
//TODO: return pointer to spec.Schema
switch expr := typeExpr.(type) { switch expr := typeExpr.(type) {
// type Foo struct {...} // type Foo struct {...}
case *ast.StructType: case *ast.StructType:
refTypeName := fullTypeName(pkgName, typeName) refTypeName := fullTypeName(pkgName, typeName)
if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed { if schema, isParsed := parser.swagger.Definitions[refTypeName]; isParsed {
return schema, nil return &schema, nil
} }
return parser.parseStruct(pkgName, expr.Fields) return parser.parseStruct(pkgName, expr.Fields)
// type Foo Baz // type Foo Baz
case *ast.Ident: case *ast.Ident:
if IsGolangPrimitiveType(expr.Name) {
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{TransToValidSchemeType(expr.Name)},
},
}, nil
}
refTypeName := fullTypeName(pkgName, expr.Name) refTypeName := fullTypeName(pkgName, expr.Name)
if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed { if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed {
if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok { if typedef, ok := parser.TypeDefinitions[pkgName][expr.Name]; ok {
parser.ParseDefinition(pkgName, expr.Name, typedef) parser.ParseDefinition(pkgName, expr.Name, typedef)
} }
} }
return parser.swagger.Definitions[refTypeName], nil return &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + refTypeName),
},
},
}, nil
// type Foo *Baz // type Foo *Baz
case *ast.StarExpr: case *ast.StarExpr:
@ -660,13 +691,13 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr)
case *ast.ArrayType: case *ast.ArrayType:
itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Elt) itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Elt)
if err != nil { if err != nil {
return spec.Schema{}, err return &spec.Schema{}, err
} }
return spec.Schema{ return &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"array"}, Type: []string{"array"},
Items: &spec.SchemaOrArray{ Items: &spec.SchemaOrArray{
Schema: &itemSchema, Schema: itemSchema,
}, },
}, },
}, nil }, nil
@ -674,28 +705,25 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr)
// type Foo pkg.Bar // type Foo pkg.Bar
case *ast.SelectorExpr: case *ast.SelectorExpr:
if xIdent, ok := expr.X.(*ast.Ident); ok { if xIdent, ok := expr.X.(*ast.Ident); ok {
pkgName = xIdent.Name return parser.parseTypeExpr(xIdent.Name, expr.Sel.Name, expr.Sel)
typeName = expr.Sel.Name
refTypeName := fullTypeName(pkgName, typeName)
if _, isParsed := parser.swagger.Definitions[refTypeName]; !isParsed {
typedef := parser.TypeDefinitions[pkgName][typeName]
parser.ParseDefinition(pkgName, typeName, typedef)
}
return parser.swagger.Definitions[refTypeName], nil
} }
// type Foo map[string]Bar // type Foo map[string]Bar
case *ast.MapType: case *ast.MapType:
itemSchema, err := parser.parseTypeExpr(pkgName, "", expr.Value) var valueSchema spec.SchemaOrBool
if err != nil { if _, ok := expr.Value.(*ast.InterfaceType); ok {
return spec.Schema{}, err valueSchema.Allows = true
} else {
schema, err := parser.parseTypeExpr(pkgName, "", expr.Value)
if err != nil {
return &spec.Schema{}, err
}
valueSchema.Schema = schema
} }
return spec.Schema{ return &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"object"}, Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{ AdditionalProperties: &valueSchema,
Schema: &itemSchema,
},
}, },
}, nil }, nil
// ... // ...
@ -703,21 +731,21 @@ func (parser *Parser) parseTypeExpr(pkgName, typeName string, typeExpr ast.Expr)
Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr) Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
} }
return spec.Schema{ return &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"object"}, Type: []string{"object"},
}, },
}, nil }, nil
} }
func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.Schema, error) { func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (*spec.Schema, error) {
extraRequired := make([]string, 0) extraRequired := make([]string, 0)
properties := make(map[string]spec.Schema) properties := make(map[string]spec.Schema)
for _, field := range fields.List { for _, field := range fields.List {
fieldProps, requiredFromAnon, err := parser.parseStructField(pkgName, field) fieldProps, requiredFromAnon, err := parser.parseStructField(pkgName, field)
if err != nil { if err != nil {
return spec.Schema{}, err return &spec.Schema{}, err
} }
extraRequired = append(extraRequired, requiredFromAnon...) extraRequired = append(extraRequired, requiredFromAnon...)
for k, v := range fieldProps { for k, v := range fieldProps {
@ -730,14 +758,11 @@ func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.S
// unset required from properties because we've collected them // unset required from properties because we've collected them
for k, prop := range properties { for k, prop := range properties {
tname := prop.SchemaProps.Type[0] prop.SchemaProps.Required = make([]string, 0)
if tname != "object" {
prop.SchemaProps.Required = make([]string, 0)
}
properties[k] = prop properties[k] = prop
} }
return spec.Schema{ return &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"object"}, Type: []string{"object"},
Properties: properties, Properties: properties,
@ -747,6 +772,7 @@ func (parser *Parser) parseStruct(pkgName string, fields *ast.FieldList) (spec.S
type structField struct { type structField struct {
name string name string
desc string
schemaType string schemaType string
arrayType string arrayType string
formatType string formatType string
@ -763,6 +789,34 @@ type structField struct {
extensions map[string]interface{} extensions map[string]interface{}
} }
func (sf *structField) toStandardSchema() *spec.Schema {
required := make([]string, 0)
if sf.isRequired {
required = append(required, sf.name)
}
return &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{sf.schemaType},
Description: sf.desc,
Format: sf.formatType,
Required: required,
Maximum: sf.maximum,
Minimum: sf.minimum,
MaxLength: sf.maxLength,
MinLength: sf.minLength,
Enum: sf.enums,
Default: sf.defaultValue,
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: sf.exampleValue,
ReadOnly: sf.readOnly,
},
VendorExtensible: spec.VendorExtensible{
Extensions: sf.extensions,
},
}
}
func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[string]spec.Schema, []string, error) { func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[string]spec.Schema, []string, error) {
properties := map[string]spec.Schema{} properties := map[string]spec.Schema{}
@ -796,7 +850,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
properties[k] = v properties[k] = v
} }
case "array": case "array":
properties[typeName] = schema properties[typeName] = *schema
default: default:
Printf("Can't extract properties from a schema of type '%s'", schemaType) Printf("Can't extract properties from a schema of type '%s'", schemaType)
} }
@ -808,30 +862,51 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
structField, err := parser.parseField(field) structField, err := parser.parseField(field)
if err != nil { if err != nil {
return properties, nil, nil return properties, nil, err
} }
if structField.name == "" { if structField.name == "" {
return properties, nil, nil return properties, nil, nil
} }
var desc string
if field.Doc != nil {
desc = strings.TrimSpace(field.Doc.Text())
}
if desc == "" && field.Comment != nil {
desc = strings.TrimSpace(field.Comment.Text())
}
// TODO: find package of schemaType and/or arrayType // TODO: find package of schemaType and/or arrayType
if structField.crossPkg != "" { if structField.crossPkg != "" {
pkgName = structField.crossPkg pkgName = structField.crossPkg
} }
fillObject := func(src, dest interface{}) error {
bin, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(bin, dest)
}
//for spec.Schema have implemented json.Marshaler, here in another way to convert
fillSchema := func(src, dest *spec.Schema) error {
err = fillObject(&src.SchemaProps, &dest.SchemaProps)
if err != nil {
return err
}
err = fillObject(&src.SwaggerSchemaProps, &dest.SwaggerSchemaProps)
if err != nil {
return err
}
return fillObject(&src.VendorExtensible, &dest.VendorExtensible)
}
if _, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field if _, ok := parser.TypeDefinitions[pkgName][structField.schemaType]; ok { // user type field
// write definition if not yet present // write definition if not yet present
parser.ParseDefinition(pkgName, structField.schemaType, parser.ParseDefinition(pkgName, structField.schemaType,
parser.TypeDefinitions[pkgName][structField.schemaType]) parser.TypeDefinitions[pkgName][structField.schemaType])
required := make([]string, 0)
if structField.isRequired {
required = append(required, structField.name)
}
properties[structField.name] = spec.Schema{ properties[structField.name] = spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"object"}, // to avoid swagger validation error Type: []string{"object"}, // to avoid swagger validation error
Description: desc, Description: structField.desc,
Required: required,
Ref: spec.Ref{ Ref: spec.Ref{
Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType), Ref: jsonreference.MustCreateRef("#/definitions/" + pkgName + "." + structField.schemaType),
}, },
@ -845,10 +920,15 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
if _, ok := parser.TypeDefinitions[pkgName][structField.arrayType]; ok { // user type in array if _, ok := parser.TypeDefinitions[pkgName][structField.arrayType]; ok { // user type in array
parser.ParseDefinition(pkgName, structField.arrayType, parser.ParseDefinition(pkgName, structField.arrayType,
parser.TypeDefinitions[pkgName][structField.arrayType]) parser.TypeDefinitions[pkgName][structField.arrayType])
required := make([]string, 0)
if structField.isRequired {
required = append(required, structField.name)
}
properties[structField.name] = spec.Schema{ properties[structField.name] = spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{structField.schemaType}, Type: []string{structField.schemaType},
Description: desc, Description: structField.desc,
Required: required,
Items: &spec.SchemaOrArray{ Items: &spec.SchemaOrArray{
Schema: &spec.Schema{ Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
@ -881,7 +961,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
properties[structField.name] = spec.Schema{ properties[structField.name] = spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{structField.schemaType}, Type: []string{structField.schemaType},
Description: desc, Description: structField.desc,
Items: &spec.SchemaOrArray{ Items: &spec.SchemaOrArray{
Schema: &spec.Schema{ Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
@ -895,6 +975,36 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
ReadOnly: structField.readOnly, ReadOnly: structField.readOnly,
}, },
} }
} else {
schema, _ := parser.parseTypeExpr(pkgName, "", astTypeArray.Elt)
properties[structField.name] = spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{structField.schemaType},
Description: structField.desc,
Items: &spec.SchemaOrArray{
Schema: schema,
},
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
ReadOnly: structField.readOnly,
},
}
}
}
} else if structField.arrayType == "array" {
if astTypeArray, ok := field.Type.(*ast.ArrayType); ok {
schema, _ := parser.parseTypeExpr(pkgName, "", astTypeArray.Elt)
properties[structField.name] = spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{structField.schemaType},
Description: structField.desc,
Items: &spec.SchemaOrArray{
Schema: schema,
},
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
ReadOnly: structField.readOnly,
},
} }
} }
} else { } else {
@ -907,7 +1017,7 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
properties[structField.name] = spec.Schema{ properties[structField.name] = spec.Schema{
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{structField.schemaType}, Type: []string{structField.schemaType},
Description: desc, Description: structField.desc,
Format: structField.formatType, Format: structField.formatType,
Required: required, Required: required,
Items: &spec.SchemaOrArray{ Items: &spec.SchemaOrArray{
@ -930,47 +1040,36 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
}, },
} }
} }
} else if astTypeMap, ok := field.Type.(*ast.MapType); ok { // if map
stdSchema := structField.toStandardSchema()
mapValueSchema, err := parser.parseTypeExpr(pkgName, "", astTypeMap)
if err != nil {
return properties, nil, err
}
stdSchema.Type = mapValueSchema.Type
stdSchema.AdditionalProperties = mapValueSchema.AdditionalProperties
properties[structField.name] = *stdSchema
} else { } else {
required := make([]string, 0) stdSchema := structField.toStandardSchema()
if structField.isRequired { properties[structField.name] = *stdSchema
required = append(required, structField.name)
}
properties[structField.name] = spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{structField.schemaType},
Description: desc,
Format: structField.formatType,
Required: required,
Maximum: structField.maximum,
Minimum: structField.minimum,
MaxLength: structField.maxLength,
MinLength: structField.minLength,
Enum: structField.enums,
Default: structField.defaultValue,
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: structField.exampleValue,
ReadOnly: structField.readOnly,
},
VendorExtensible: spec.VendorExtensible{
Extensions: structField.extensions,
},
}
if nestStruct, ok := field.Type.(*ast.StarExpr); ok { if nestStar, ok := field.Type.(*ast.StarExpr); ok {
schema, err := parser.parseTypeExpr(pkgName, structField.schemaType, nestStruct.X) if !IsGolangPrimitiveType(structField.schemaType) {
if err != nil { schema, err := parser.parseTypeExpr(pkgName, structField.schemaType, nestStar.X)
return nil, nil, err if err != nil {
return properties, nil, err
}
if len(schema.SchemaProps.Type) > 0 {
err = fillSchema(schema, stdSchema)
if err != nil {
return properties, nil, err
}
properties[structField.name] = *stdSchema
return properties, nil, nil
}
} }
} else if nestStruct, ok := field.Type.(*ast.StructType); ok {
if len(schema.SchemaProps.Type) > 0 {
properties[structField.name] = schema
return properties, nil, nil
}
}
nestStruct, ok := field.Type.(*ast.StructType)
if ok {
props := map[string]spec.Schema{} props := map[string]spec.Schema{}
nestRequired := make([]string, 0) nestRequired := make([]string, 0)
for _, v := range nestStruct.Fields.List { for _, v := range nestStruct.Fields.List {
@ -986,26 +1085,9 @@ func (parser *Parser) parseStructField(pkgName string, field *ast.Field) (map[st
props[k] = v props[k] = v
} }
} }
stdSchema.Properties = props
properties[structField.name] = spec.Schema{ stdSchema.Required = nestRequired
SchemaProps: spec.SchemaProps{ properties[structField.name] = *stdSchema
Type: []string{structField.schemaType},
Description: desc,
Format: structField.formatType,
Properties: props,
Required: nestRequired,
Maximum: structField.maximum,
Minimum: structField.minimum,
MaxLength: structField.maxLength,
MinLength: structField.minLength,
Enum: structField.enums,
Default: structField.defaultValue,
},
SwaggerSchemaProps: spec.SwaggerSchemaProps{
Example: structField.exampleValue,
ReadOnly: structField.readOnly,
},
}
} }
} }
return properties, nil, nil return properties, nil, nil
@ -1069,6 +1151,13 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) {
structField.name = toLowerCamelCase(structField.name) structField.name = toLowerCamelCase(structField.name)
} }
if field.Doc != nil {
structField.desc = strings.TrimSpace(field.Doc.Text())
}
if structField.desc == "" && field.Comment != nil {
structField.desc = strings.TrimSpace(field.Comment.Text())
}
if field.Tag == nil { if field.Tag == nil {
return structField, nil return structField, nil
} }
@ -1098,6 +1187,9 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) {
if len(parts) >= 2 { if len(parts) >= 2 {
if newSchemaType == "array" { if newSchemaType == "array" {
newArrayType = parts[1] newArrayType = parts[1]
if err := CheckSchemaType(newArrayType); err != nil {
return nil, err
}
} else if newSchemaType == "primitive" { } else if newSchemaType == "primitive" {
newSchemaType = parts[1] newSchemaType = parts[1]
newArrayType = parts[1] newArrayType = parts[1]
@ -1107,9 +1199,7 @@ func (parser *Parser) parseField(field *ast.Field) (*structField, error) {
if err := CheckSchemaType(newSchemaType); err != nil { if err := CheckSchemaType(newSchemaType); err != nil {
return nil, err return nil, err
} }
if err := CheckSchemaType(newArrayType); err != nil {
return nil, err
}
structField.schemaType = newSchemaType structField.schemaType = newSchemaType
structField.arrayType = newArrayType structField.arrayType = newArrayType
} }

View File

@ -60,6 +60,19 @@ func parseFieldSelectorExpr(astTypeSelectorExpr *ast.SelectorExpr, parser *Parse
parser.ParseDefinition(pkgName.Name, astTypeSelectorExpr.Sel.Name, typeDefinitions) parser.ParseDefinition(pkgName.Name, astTypeSelectorExpr.Sel.Name, typeDefinitions)
return propertyNewFunc(astTypeSelectorExpr.Sel.Name, pkgName.Name) return propertyNewFunc(astTypeSelectorExpr.Sel.Name, pkgName.Name)
} }
if aliasedNames, ok := parser.ImportAliases[pkgName.Name]; ok {
for aliasedName := range aliasedNames {
if typeDefinitions, ok := parser.TypeDefinitions[aliasedName][astTypeSelectorExpr.Sel.Name]; ok {
if expr, ok := typeDefinitions.Type.(*ast.SelectorExpr); ok {
if primitiveType, err := convertFromSpecificToPrimitive(expr.Sel.Name); err == nil {
return propertyNewFunc(primitiveType, "")
}
}
parser.ParseDefinition(aliasedName, astTypeSelectorExpr.Sel.Name, typeDefinitions)
return propertyNewFunc(astTypeSelectorExpr.Sel.Name, aliasedName)
}
}
}
if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[astTypeSelectorExpr.Sel.Name]; isCustomType { if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[astTypeSelectorExpr.Sel.Name]; isCustomType {
return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType} return propertyName{SchemaType: actualPrimitiveType, ArrayType: actualPrimitiveType}
} }
@ -91,10 +104,7 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) {
} }
if astTypeArray, ok := expr.(*ast.ArrayType); ok { // if array if astTypeArray, ok := expr.(*ast.ArrayType); ok { // if array
if _, ok := astTypeArray.Elt.(*ast.StructType); ok { return getArrayPropertyName(astTypeArray.Elt, parser), nil
return propertyName{SchemaType: "array", ArrayType: "object"}, nil
}
return getArrayPropertyName(astTypeArray, parser), nil
} }
if _, ok := expr.(*ast.MapType); ok { // if map if _, ok := expr.(*ast.MapType); ok { // if map
@ -111,22 +121,27 @@ func getPropertyName(expr ast.Expr, parser *Parser) (propertyName, error) {
return propertyName{}, errors.New("not supported" + fmt.Sprint(expr)) return propertyName{}, errors.New("not supported" + fmt.Sprint(expr))
} }
func getArrayPropertyName(astTypeArray *ast.ArrayType, parser *Parser) propertyName { func getArrayPropertyName(astTypeArrayElt ast.Expr, parser *Parser) propertyName {
if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.SelectorExpr); ok { switch elt := astTypeArrayElt.(type) {
return parseFieldSelectorExpr(astTypeArrayExpr, parser, newArrayProperty) case *ast.StructType, *ast.MapType, *ast.InterfaceType:
} return propertyName{SchemaType: "array", ArrayType: "object"}
if astTypeArrayExpr, ok := astTypeArray.Elt.(*ast.StarExpr); ok { case *ast.ArrayType:
if astTypeArraySel, ok := astTypeArrayExpr.X.(*ast.SelectorExpr); ok { return propertyName{SchemaType: "array", ArrayType: "array"}
return parseFieldSelectorExpr(astTypeArraySel, parser, newArrayProperty) case *ast.StarExpr:
return getArrayPropertyName(elt.X, parser)
case *ast.SelectorExpr:
return parseFieldSelectorExpr(elt, parser, newArrayProperty)
case *ast.Ident:
name := TransToValidSchemeType(elt.Name)
if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType {
name = actualPrimitiveType
} }
if astTypeArrayIdent, ok := astTypeArrayExpr.X.(*ast.Ident); ok { return propertyName{SchemaType: "array", ArrayType: name}
name := TransToValidSchemeType(astTypeArrayIdent.Name) default:
return propertyName{SchemaType: "array", ArrayType: name} name := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArrayElt))
if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[name]; isCustomType {
name = actualPrimitiveType
} }
return propertyName{SchemaType: "array", ArrayType: name}
} }
itemTypeName := TransToValidSchemeType(fmt.Sprintf("%s", astTypeArray.Elt))
if actualPrimitiveType, isCustomType := parser.CustomPrimitiveTypes[itemTypeName]; isCustomType {
itemTypeName = actualPrimitiveType
}
return propertyName{SchemaType: "array", ArrayType: itemTypeName}
} }

View File

@ -10,6 +10,16 @@ func CheckSchemaType(typeName string) error {
return nil return nil
} }
// IsSimplePrimitiveType determine whether the type name is a simple primitive type
func IsSimplePrimitiveType(typeName string) bool {
switch typeName {
case "string", "number", "integer", "boolean":
return true
default:
return false
}
}
// IsPrimitiveType determine whether the type name is a primitive type // IsPrimitiveType determine whether the type name is a primitive type
func IsPrimitiveType(typeName string) bool { func IsPrimitiveType(typeName string) bool {
switch typeName { switch typeName {

View File

@ -1,4 +1,4 @@
package swag package swag
// Version of swag // Version of swag
const Version = "v1.6.3" const Version = "v1.6.5"

View File

@ -275,9 +275,10 @@ func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (del
// We deleted an entry but now there may be // We deleted an entry but now there may be
// a blank line-sized hole where the import was. // a blank line-sized hole where the import was.
if line-lastLine > 1 { if line-lastLine > 1 || !gen.Rparen.IsValid() {
// There was a blank line immediately preceding the deleted import, // There was a blank line immediately preceding the deleted import,
// so there's no need to close the hole. // so there's no need to close the hole. The right parenthesis is
// invalid after AddImport to an import statement without parenthesis.
// Do nothing. // Do nothing.
} else if line != fset.File(gen.Rparen).LineCount() { } else if line != fset.File(gen.Rparen).LineCount() {
// There was no blank line. Close the hole. // There was no blank line. Close the hole.

View File

@ -90,7 +90,7 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
// The types argument, if non-empty, enables type-based filtering of // The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type // events. The function f if is called only for nodes whose type
// matches an element of the types slice. // matches an element of the types slice.
func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (prune bool)) { func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) {
mask := maskOf(types) mask := maskOf(types)
for i := 0; i < len(in.events); { for i := 0; i < len(in.events); {
ev := in.events[i] ev := in.events[i]
@ -114,7 +114,7 @@ func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (prun
// supplies each call to f an additional argument, the current // supplies each call to f an additional argument, the current
// traversal stack. The stack's first element is the outermost node, // traversal stack. The stack's first element is the outermost node,
// an *ast.File; its last is the innermost, n. // an *ast.File; its last is the innermost, n.
func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (prune bool)) { func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) {
mask := maskOf(types) mask := maskOf(types)
var stack []ast.Node var stack []ast.Node
for i := 0; i < len(in.events); { for i := 0; i < len(in.events); {

View File

@ -60,8 +60,7 @@ causes Load to run in LoadFiles mode, collecting minimal information.
See the documentation for type Config for details. See the documentation for type Config for details.
As noted earlier, the Config.Mode controls the amount of detail 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 reported about the loaded packages. See the documentation for type LoadMode
previous mode with some extra added. See the documentation for type LoadMode
for details. for details.
Most tools should pass their command-line arguments (after any flags) Most tools should pass their command-line arguments (after any flags)

View File

@ -84,13 +84,14 @@ func findExternalDriver(cfg *Config) driver {
cmd.Stdin = bytes.NewReader(req) cmd.Stdin = bytes.NewReader(req)
cmd.Stdout = buf cmd.Stdout = buf
cmd.Stderr = stderr cmd.Stderr = stderr
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr)
}
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
} }
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr)
}
var response driverResponse var response driverResponse
if err := json.Unmarshal(buf.Bytes(), &response); err != nil { if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
return nil, err return nil, err

View File

@ -6,17 +6,16 @@ package packages
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"go/types" "go/types"
"io/ioutil"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"regexp"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -24,9 +23,6 @@ import (
"unicode" "unicode"
"golang.org/x/tools/go/internal/packagesdriver" "golang.org/x/tools/go/internal/packagesdriver"
"golang.org/x/tools/internal/gopathwalk"
"golang.org/x/tools/internal/semver"
"golang.org/x/tools/internal/span"
) )
// debug controls verbose logging. // debug controls verbose logging.
@ -45,16 +41,21 @@ type responseDeduper struct {
dr *driverResponse dr *driverResponse
} }
// init fills in r with a driverResponse. func newDeduper() *responseDeduper {
func (r *responseDeduper) init(dr *driverResponse) { return &responseDeduper{
r.dr = dr dr: &driverResponse{},
r.seenRoots = map[string]bool{} seenRoots: map[string]bool{},
r.seenPackages = map[string]*Package{} seenPackages: map[string]*Package{},
}
}
// addAll fills in r with a driverResponse.
func (r *responseDeduper) addAll(dr *driverResponse) {
for _, pkg := range dr.Packages { for _, pkg := range dr.Packages {
r.seenPackages[pkg.ID] = pkg r.addPackage(pkg)
} }
for _, root := range dr.Roots { for _, root := range dr.Roots {
r.seenRoots[root] = true r.addRoot(root)
} }
} }
@ -74,25 +75,47 @@ func (r *responseDeduper) addRoot(id string) {
r.dr.Roots = append(r.dr.Roots, id) r.dr.Roots = append(r.dr.Roots, id)
} }
// goInfo contains global information from the go tool. type golistState struct {
type goInfo struct { cfg *Config
rootDirs map[string]string ctx context.Context
env goEnv
envOnce sync.Once
goEnvError error
goEnv map[string]string
rootsOnce sync.Once
rootDirsError error
rootDirs map[string]string
// vendorDirs caches the (non)existence of vendor directories.
vendorDirs map[string]bool
} }
type goEnv struct { // getEnv returns Go environment variables. Only specific variables are
modulesOn bool // populated -- computing all of them is slow.
func (state *golistState) getEnv() (map[string]string, error) {
state.envOnce.Do(func() {
var b *bytes.Buffer
b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH")
if state.goEnvError != nil {
return
}
state.goEnv = make(map[string]string)
decoder := json.NewDecoder(b)
if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil {
return
}
})
return state.goEnv, state.goEnvError
} }
func determineEnv(cfg *Config) goEnv { // mustGetEnv is a convenience function that can be used if getEnv has already succeeded.
buf, err := invokeGo(cfg, "env", "GOMOD") func (state *golistState) mustGetEnv() map[string]string {
env, err := state.getEnv()
if err != nil { if err != nil {
return goEnv{} panic(fmt.Sprintf("mustGetEnv: %v", err))
} }
gomod := bytes.TrimSpace(buf.Bytes())
env := goEnv{}
env.modulesOn = len(gomod) > 0
return env return env
} }
@ -100,47 +123,38 @@ func determineEnv(cfg *Config) goEnv {
// the build system package structure. // the build system package structure.
// See driver for more details. // See driver for more details.
func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
var sizes types.Sizes // Make sure that any asynchronous go commands are killed when we return.
parentCtx := cfg.Context
if parentCtx == nil {
parentCtx = context.Background()
}
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()
response := newDeduper()
// Fill in response.Sizes asynchronously if necessary.
var sizeserr error var sizeserr error
var sizeswg sync.WaitGroup var sizeswg sync.WaitGroup
if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 {
sizeswg.Add(1) sizeswg.Add(1)
go func() { go func() {
sizes, sizeserr = getSizes(cfg) var sizes types.Sizes
sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg))
// types.SizesFor always returns nil or a *types.StdSizes.
response.dr.Sizes, _ = sizes.(*types.StdSizes)
sizeswg.Done() sizeswg.Done()
}() }()
} }
defer sizeswg.Wait()
// start fetching rootDirs state := &golistState{
var info goInfo cfg: cfg,
var rootDirsReady, envReady = make(chan struct{}), make(chan struct{}) ctx: ctx,
go func() { vendorDirs: map[string]bool{},
info.rootDirs = determineRootDirs(cfg)
close(rootDirsReady)
}()
go func() {
info.env = determineEnv(cfg)
close(envReady)
}()
getGoInfo := func() *goInfo {
<-rootDirsReady
<-envReady
return &info
}
// Ensure that we don't leak goroutines: Load is synchronous, so callers will
// not expect it to access the fields of cfg after the call returns.
defer getGoInfo()
// always pass getGoInfo to golistDriver
golistDriver := func(cfg *Config, patterns ...string) (*driverResponse, error) {
return golistDriver(cfg, getGoInfo, patterns...)
} }
// Determine files requested in contains patterns // Determine files requested in contains patterns
var containFiles []string var containFiles []string
var packagesNamed []string
restPatterns := make([]string, 0, len(patterns)) restPatterns := make([]string, 0, len(patterns))
// Extract file= and other [querytype]= patterns. Report an error if querytype // Extract file= and other [querytype]= patterns. Report an error if querytype
// doesn't exist. // doesn't exist.
@ -156,8 +170,6 @@ extractQueries:
containFiles = append(containFiles, value) containFiles = append(containFiles, value)
case "pattern": case "pattern":
restPatterns = append(restPatterns, value) restPatterns = append(restPatterns, value)
case "iamashamedtousethedisabledqueryname":
packagesNamed = append(packagesNamed, value)
case "": // not a reserved query case "": // not a reserved query
restPatterns = append(restPatterns, pattern) restPatterns = append(restPatterns, pattern)
default: default:
@ -173,52 +185,34 @@ extractQueries:
} }
} }
response := &responseDeduper{}
var err error
// See if we have any patterns to pass through to go list. Zero initial // See if we have any patterns to pass through to go list. Zero initial
// patterns also requires a go list call, since it's the equivalent of // patterns also requires a go list call, since it's the equivalent of
// ".". // ".".
if len(restPatterns) > 0 || len(patterns) == 0 { if len(restPatterns) > 0 || len(patterns) == 0 {
dr, err := golistDriver(cfg, restPatterns...) dr, err := state.createDriverResponse(restPatterns...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
response.init(dr) response.addAll(dr)
} else {
response.init(&driverResponse{})
} }
sizeswg.Wait()
if sizeserr != nil {
return nil, sizeserr
}
// types.SizesFor always returns nil or a *types.StdSizes
response.dr.Sizes, _ = sizes.(*types.StdSizes)
var containsCandidates []string
if len(containFiles) != 0 { if len(containFiles) != 0 {
if err := runContainsQueries(cfg, golistDriver, response, containFiles, getGoInfo); err != nil { if err := state.runContainsQueries(response, containFiles); err != nil {
return nil, err return nil, err
} }
} }
if len(packagesNamed) != 0 { modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
if err := runNamedQueries(cfg, golistDriver, response, packagesNamed); err != nil {
return nil, err
}
}
modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var containsCandidates []string
if len(containFiles) > 0 { if len(containFiles) > 0 {
containsCandidates = append(containsCandidates, modifiedPkgs...) containsCandidates = append(containsCandidates, modifiedPkgs...)
containsCandidates = append(containsCandidates, needPkgs...) containsCandidates = append(containsCandidates, needPkgs...)
} }
if err := addNeededOverlayPackages(cfg, golistDriver, response, needPkgs, getGoInfo); err != nil { if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
return nil, err return nil, err
} }
// Check candidate packages for containFiles. // Check candidate packages for containFiles.
@ -247,33 +241,32 @@ extractQueries:
} }
} }
sizeswg.Wait()
if sizeserr != nil {
return nil, sizeserr
}
return response.dr, nil return response.dr, nil
} }
func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string, getGoInfo func() *goInfo) error { func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error {
if len(pkgs) == 0 { if len(pkgs) == 0 {
return nil return nil
} }
drivercfg := *cfg dr, err := state.createDriverResponse(pkgs...)
if getGoInfo().env.modulesOn {
drivercfg.BuildFlags = append(drivercfg.BuildFlags, "-mod=readonly")
}
dr, err := driver(&drivercfg, pkgs...)
if err != nil { if err != nil {
return err return err
} }
for _, pkg := range dr.Packages { for _, pkg := range dr.Packages {
response.addPackage(pkg) response.addPackage(pkg)
} }
_, needPkgs, err := processGolistOverlay(cfg, response, getGoInfo) _, needPkgs, err := state.processGolistOverlay(response)
if err != nil { if err != nil {
return err return err
} }
return addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo) return state.addNeededOverlayPackages(response, needPkgs)
} }
func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string, goInfo func() *goInfo) error { func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error {
for _, query := range queries { for _, query := range queries {
// TODO(matloob): Do only one query per directory. // TODO(matloob): Do only one query per directory.
fdir := filepath.Dir(query) fdir := filepath.Dir(query)
@ -283,42 +276,16 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
if err != nil { if err != nil {
return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
} }
dirResponse, err := driver(cfg, pattern) dirResponse, err := state.createDriverResponse(pattern)
if err != nil {
// If there was an error loading the package, or the package is returned
// with errors, try to load the file as an ad-hoc package.
// Usually the error will appear in a returned package, but may not if we're
// in module mode and the ad-hoc is located outside a module.
if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 &&
len(dirResponse.Packages[0].Errors) == 1 {
var queryErr error var queryErr error
if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil { if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil {
return err // return the original error
}
}
// `go list` can report errors for files that are not listed as part of a package's GoFiles.
// In the case of an invalid Go file, we should assume that it is part of package if only
// one package is in the response. The file may have valid contents in an overlay.
if len(dirResponse.Packages) == 1 {
pkg := dirResponse.Packages[0]
for i, err := range pkg.Errors {
s := errorSpan(err)
if !s.IsValid() {
break
}
if len(pkg.CompiledGoFiles) == 0 {
break
}
dir := filepath.Dir(pkg.CompiledGoFiles[0])
filename := filepath.Join(dir, filepath.Base(s.URI().Filename()))
if info, err := os.Stat(filename); err != nil || info.IsDir() {
break
}
if !contains(pkg.CompiledGoFiles, filename) {
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename)
pkg.GoFiles = append(pkg.GoFiles, filename)
pkg.Errors = append(pkg.Errors[:i], pkg.Errors[i+1:]...)
}
}
}
// A final attempt to construct an ad-hoc package.
if len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1 {
var queryErr error
if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil {
return err // return the original error return err // return the original error
} }
} }
@ -347,345 +314,47 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
return nil return nil
} }
// adHocPackage attempts to construct an ad-hoc package given a query that failed. // adhocPackage attempts to load or construct an ad-hoc package for a given
func adHocPackage(cfg *Config, driver driver, pattern, query string) (*driverResponse, error) { // query, if the original call to the driver produced inadequate results.
// There was an error loading the package. Try to load the file as an ad-hoc package. func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) {
// Usually the error will appear in a returned package, but may not if we're in modules mode response, err := state.createDriverResponse(query)
// and the ad-hoc is located outside a module.
dirResponse, err := driver(cfg, query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If we get nothing back from `go list`, try to make this file into its own ad-hoc package. // If we get nothing back from `go list`,
if len(dirResponse.Packages) == 0 && err == nil { // try to make this file into its own ad-hoc package.
dirResponse.Packages = append(dirResponse.Packages, &Package{ // TODO(rstambler): Should this check against the original response?
if len(response.Packages) == 0 {
response.Packages = append(response.Packages, &Package{
ID: "command-line-arguments", ID: "command-line-arguments",
PkgPath: query, PkgPath: query,
GoFiles: []string{query}, GoFiles: []string{query},
CompiledGoFiles: []string{query}, CompiledGoFiles: []string{query},
Imports: make(map[string]*Package), Imports: make(map[string]*Package),
}) })
dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments") response.Roots = append(response.Roots, "command-line-arguments")
} }
// Special case to handle issue #33482: // Handle special cases.
// If this is a file= query for ad-hoc packages where the file only exists on an overlay, if len(response.Packages) == 1 {
// and exists outside of a module, add the file in for the package. // golang/go#33482: If this is a file= query for ad-hoc packages where
if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" || // the file only exists on an overlay, and exists outside of a module,
filepath.ToSlash(dirResponse.Packages[0].PkgPath) == filepath.ToSlash(query)) { // add the file to the package and remove the errors.
if len(dirResponse.Packages[0].GoFiles) == 0 { if response.Packages[0].ID == "command-line-arguments" ||
filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) {
// TODO(matloob): check if the file is outside of a root dir? if len(response.Packages[0].GoFiles) == 0 {
for path := range cfg.Overlay { filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
if path == filename { // TODO(matloob): check if the file is outside of a root dir?
dirResponse.Packages[0].Errors = nil for path := range state.cfg.Overlay {
dirResponse.Packages[0].GoFiles = []string{path} if path == filename {
dirResponse.Packages[0].CompiledGoFiles = []string{path} response.Packages[0].Errors = nil
response.Packages[0].GoFiles = []string{path}
response.Packages[0].CompiledGoFiles = []string{path}
}
} }
} }
} }
} }
return dirResponse, nil return response, nil
}
func contains(files []string, filename string) bool {
for _, f := range files {
if f == filename {
return true
}
}
return false
}
// errorSpan attempts to parse a standard `go list` error message
// by stripping off the trailing error message.
//
// It works only on errors whose message is prefixed by colon,
// followed by a space (": "). For example:
//
// attributes.go:13:1: expected 'package', found 'type'
//
func errorSpan(err Error) span.Span {
if err.Pos == "" {
input := strings.TrimSpace(err.Msg)
msgIndex := strings.Index(input, ": ")
if msgIndex < 0 {
return span.Parse(input)
}
return span.Parse(input[:msgIndex])
}
return span.Parse(err.Pos)
}
// modCacheRegexp splits a path in a module cache into module, module version, and package.
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error {
// calling `go env` isn't free; bail out if there's nothing to do.
if len(queries) == 0 {
return nil
}
// Determine which directories are relevant to scan.
roots, modRoot, err := roots(cfg)
if err != nil {
return 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
if dir != root.Path {
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)
}
}
}
startWalk := time.Now()
gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug})
cfg.Logf("%v for walk", time.Since(startWalk))
// Weird special case: the top-level package in a module will be in
// whatever directory the user checked the repository out into. It's
// more reasonable for that to not match the package name. So, if there
// are any Go files in the mod root, query it just to be safe.
if modRoot != "" {
rel, err := filepath.Rel(cfg.Dir, modRoot)
if err != nil {
panic(err) // See above.
}
files, err := ioutil.ReadDir(modRoot)
if err != nil {
panic(err) // See above.
}
for _, f := range files {
if strings.HasSuffix(f.Name(), ".go") {
simpleMatches = append(simpleMatches, rel)
break
}
}
}
addResponse := func(r *driverResponse) {
for _, pkg := range r.Packages {
response.addPackage(pkg)
for _, name := range queries {
if pkg.Name == name {
response.addRoot(pkg.ID)
break
}
}
}
}
if len(simpleMatches) != 0 {
resp, err := driver(cfg, simpleMatches...)
if err != nil {
return 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
// We're only trying to look at stuff in the module cache, so
// disable the network. This should speed things up, and has
// prevented errors in at least one case, #28518.
tmpCfg.Env = append([]string{"GOPROXY=off"}, cfg.Env...)
var err error
tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery")
if err != nil {
return err
}
defer os.RemoveAll(tmpCfg.Dir)
if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil {
return 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 fmt.Errorf("querying module cache matches: %v", err)
}
addResponse(resp)
}
return nil
}
func getSizes(cfg *Config) (types.Sizes, error) {
return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg))
}
// 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, string, error) {
stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD")
if err != nil {
return nil, "", err
}
fields := strings.Split(stdout.String(), "\n")
if len(fields) != 4 || len(fields[3]) != 0 {
return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String())
}
goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2]
var modDir string
if gomod != "" {
modDir = filepath.Dir(gomod)
}
var roots []gopathwalk.Root
// Always add GOROOT.
roots = append(roots, gopathwalk.Root{
Path: filepath.Join(goroot, "/src"),
Type: gopathwalk.RootGOROOT,
})
// If modules are enabled, scan the module dir.
if modDir != "" {
roots = append(roots, gopathwalk.Root{
Path: modDir,
Type: gopathwalk.RootCurrentModule,
})
}
// Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode.
for _, p := range gopath {
if modDir != "" {
roots = append(roots, gopathwalk.Root{
Path: filepath.Join(p, "/pkg/mod"),
Type: gopathwalk.RootModuleCache,
})
} else {
roots = append(roots, gopathwalk.Root{
Path: filepath.Join(p, "/src"),
Type: gopathwalk.RootGOPATH,
})
}
}
return roots, modDir, 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; // Fields must match go list;
@ -730,10 +399,9 @@ 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} return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles}
} }
// golistDriver uses the "go list" command to expand the pattern // createDriverResponse uses the "go list" command to expand the pattern
// words and return metadata for the specified packages. dir may be // words and return a response for the specified packages.
// "" and env may be nil, as per os/exec.Command. func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) {
func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driverResponse, error) {
// go list uses the following identifiers in ImportPath and Imports: // go list uses the following identifiers in ImportPath and Imports:
// //
// "p" -- importable package or main (command) // "p" -- importable package or main (command)
@ -747,7 +415,7 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
// Run "go list" for complete // Run "go list" for complete
// information on the specified packages. // information on the specified packages.
buf, err := invokeGo(cfg, golistargs(cfg, words)...) buf, err := state.invokeGo("list", golistargs(state.cfg, words)...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -782,7 +450,10 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
// contained in a known module or GOPATH entry. This will allow the package to be // contained in a known module or GOPATH entry. This will allow the package to be
// properly "reclaimed" when overlays are processed. // properly "reclaimed" when overlays are processed.
if filepath.IsAbs(p.ImportPath) && p.Error != nil { if filepath.IsAbs(p.ImportPath) && p.Error != nil {
pkgPath, ok := getPkgPath(cfg, p.ImportPath, rootsDirs) pkgPath, ok, err := state.getPkgPath(p.ImportPath)
if err != nil {
return nil, err
}
if ok { if ok {
p.ImportPath = pkgPath p.ImportPath = pkgPath
} }
@ -803,6 +474,7 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles),
CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles),
OtherFiles: absJoin(p.Dir, otherFiles(p)...), OtherFiles: absJoin(p.Dir, otherFiles(p)...),
forTest: p.ForTest,
} }
// Work around https://golang.org/issue/28749: // Work around https://golang.org/issue/28749:
@ -879,9 +551,15 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
} }
if p.Error != nil { if p.Error != nil {
msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363.
// Address golang.org/issue/35964 by appending import stack to error message.
if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 {
msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack)
}
pkg.Errors = append(pkg.Errors, Error{ pkg.Errors = append(pkg.Errors, Error{
Pos: p.Error.Pos, Pos: p.Error.Pos,
Msg: strings.TrimSpace(p.Error.Err), // Trim to work around golang.org/issue/32363. Msg: msg,
Kind: ListError,
}) })
} }
@ -892,22 +570,20 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
} }
// getPkgPath finds the package path of a directory if it's relative to a root directory. // getPkgPath finds the package path of a directory if it's relative to a root directory.
func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) { func (state *golistState) getPkgPath(dir string) (string, bool, error) {
absDir, err := filepath.Abs(dir) absDir, err := filepath.Abs(dir)
if err != nil { if err != nil {
cfg.Logf("error getting absolute path of %s: %v", dir, err) return "", false, err
return "", false
} }
for rdir, rpath := range goInfo().rootDirs { roots, err := state.determineRootDirs()
absRdir, err := filepath.Abs(rdir) if err != nil {
if err != nil { return "", false, err
cfg.Logf("error getting absolute path of %s: %v", rdir, err) }
continue
} for rdir, rpath := range roots {
// Make sure that the directory is in the module, // Make sure that the directory is in the module,
// to avoid creating a path relative to another module. // to avoid creating a path relative to another module.
if !strings.HasPrefix(absDir, absRdir) { if !strings.HasPrefix(absDir, rdir) {
cfg.Logf("%s does not have prefix %s", absDir, absRdir)
continue continue
} }
// TODO(matloob): This doesn't properly handle symlinks. // TODO(matloob): This doesn't properly handle symlinks.
@ -922,11 +598,11 @@ func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) {
// Once the file is saved, gopls, or the next invocation of the tool will get the correct // Once the file is saved, gopls, or the next invocation of the tool will get the correct
// result straight from golist. // result straight from golist.
// TODO(matloob): Implement module tiebreaking? // TODO(matloob): Implement module tiebreaking?
return path.Join(rpath, filepath.ToSlash(r)), true return path.Join(rpath, filepath.ToSlash(r)), true, nil
} }
return filepath.ToSlash(r), true return filepath.ToSlash(r), true, nil
} }
return "", false return "", false, nil
} }
// absJoin absolutizes and flattens the lists of files. // absJoin absolutizes and flattens the lists of files.
@ -945,8 +621,8 @@ func absJoin(dir string, fileses ...[]string) (res []string) {
func golistargs(cfg *Config, words []string) []string { func golistargs(cfg *Config, words []string) []string {
const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo
fullargs := []string{ fullargs := []string{
"list", "-e", "-json", "-e", "-json",
fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0), fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0),
fmt.Sprintf("-test=%t", cfg.Tests), fmt.Sprintf("-test=%t", cfg.Tests),
fmt.Sprintf("-export=%t", usesExportData(cfg)), fmt.Sprintf("-export=%t", usesExportData(cfg)),
fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0),
@ -961,10 +637,17 @@ func golistargs(cfg *Config, words []string) []string {
} }
// invokeGo returns the stdout of a go command invocation. // invokeGo returns the stdout of a go command invocation.
func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
cfg := state.cfg
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
cmd := exec.CommandContext(cfg.Context, "go", args...) goArgs := []string{verb}
if verb != "env" {
goArgs = append(goArgs, cfg.BuildFlags...)
}
goArgs = append(goArgs, args...)
cmd := exec.CommandContext(state.ctx, "go", goArgs...)
// On darwin the cwd gets resolved to the real path, which breaks anything that // 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 // expects the working directory to keep the original path, including the
// go command when dealing with modules. // go command when dealing with modules.
@ -976,7 +659,7 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
cmd.Stdout = stdout cmd.Stdout = stdout
cmd.Stderr = stderr cmd.Stderr = stderr
defer func(start time.Time) { defer func(start time.Time) {
cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr, stdout) cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, goArgs...), stderr, stdout)
}(time.Now()) }(time.Now())
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {

View File

@ -1,11 +1,11 @@
package packages package packages
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"go/parser" "go/parser"
"go/token" "go/token"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -16,7 +16,7 @@ import (
// sometimes incorrect. // sometimes incorrect.
// TODO(matloob): Handle unsupported cases, including the following: // TODO(matloob): Handle unsupported cases, including the following:
// - determining the correct package to add given a new import path // - determining the correct package to add given a new import path
func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func() *goInfo) (modifiedPkgs, needPkgs []string, err error) { func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) {
havePkgs := make(map[string]string) // importPath -> non-test package ID havePkgs := make(map[string]string) // importPath -> non-test package ID
needPkgsSet := make(map[string]bool) needPkgsSet := make(map[string]bool)
modifiedPkgsSet := make(map[string]bool) modifiedPkgsSet := make(map[string]bool)
@ -34,7 +34,7 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
// potentially modifying the transitive set of dependencies). // potentially modifying the transitive set of dependencies).
var overlayAddsImports bool var overlayAddsImports bool
for opath, contents := range cfg.Overlay { for opath, contents := range state.cfg.Overlay {
base := filepath.Base(opath) base := filepath.Base(opath)
dir := filepath.Dir(opath) dir := filepath.Dir(opath)
var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant
@ -86,7 +86,10 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
if pkg == nil { if pkg == nil {
// Try to find the module or gopath dir the file is contained in. // Try to find the module or gopath dir the file is contained in.
// Then for modules, add the module opath to the beginning. // Then for modules, add the module opath to the beginning.
pkgPath, ok := getPkgPath(cfg, dir, rootDirs) pkgPath, ok, err := state.getPkgPath(dir)
if err != nil {
return nil, nil, err
}
if !ok { if !ok {
break break
} }
@ -114,6 +117,11 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
if isTestFile && !isXTest && testVariantOf != nil { if isTestFile && !isXTest && testVariantOf != nil {
pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
// Add the package under test and its imports to the test variant.
pkg.forTest = testVariantOf.PkgPath
for k, v := range testVariantOf.Imports {
pkg.Imports[k] = &Package{ID: v.ID}
}
} }
} }
} }
@ -124,48 +132,57 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath) pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath)
modifiedPkgsSet[pkg.ID] = true modifiedPkgsSet[pkg.ID] = true
} }
// Clear out the package's errors, since we've probably corrected
// them by adding the overlay. This may eliminate some legitimate
// errors, but that's a risk with overlays in general.
pkg.Errors = nil
imports, err := extractImports(opath, contents) imports, err := extractImports(opath, contents)
if err != nil { if err != nil {
// Let the parser or type checker report errors later. // Let the parser or type checker report errors later.
continue continue
} }
for _, imp := range imports { for _, imp := range imports {
_, found := pkg.Imports[imp] if _, found := pkg.Imports[imp]; found {
if !found { continue
overlayAddsImports = true }
// TODO(matloob): Handle cases when the following block isn't correct. overlayAddsImports = true
// These include imports of vendored packages, etc. id, ok := havePkgs[imp]
id, ok := havePkgs[imp] if !ok {
if !ok { var err error
id = imp id, err = state.resolveImport(dir, imp)
} if err != nil {
pkg.Imports[imp] = &Package{ID: id} return nil, nil, err
// Add dependencies to the non-test variant version of this package as wel.
if testVariantOf != nil {
testVariantOf.Imports[imp] = &Package{ID: id}
} }
} }
pkg.Imports[imp] = &Package{ID: id}
// Add dependencies to the non-test variant version of this package as well.
if testVariantOf != nil {
testVariantOf.Imports[imp] = &Package{ID: id}
}
} }
continue
} }
// toPkgPath tries to guess the package path given the id. // toPkgPath guesses the package path given the id.
// This isn't always correct -- it's certainly wrong for toPkgPath := func(sourceDir, id string) (string, error) {
// vendored packages' paths. if i := strings.IndexByte(id, ' '); i >= 0 {
toPkgPath := func(id string) string { return state.resolveImport(sourceDir, id[:i])
// TODO(matloob): Handle vendor paths.
i := strings.IndexByte(id, ' ')
if i >= 0 {
return id[:i]
} }
return id return state.resolveImport(sourceDir, id)
} }
// Do another pass now that new packages have been created to determine the // Now that new packages have been created, do another pass to determine
// set of missing packages. // the new set of missing packages.
for _, pkg := range response.dr.Packages { for _, pkg := range response.dr.Packages {
for _, imp := range pkg.Imports { for _, imp := range pkg.Imports {
pkgPath := toPkgPath(imp.ID) if len(pkg.GoFiles) == 0 {
return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath)
}
pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID)
if err != nil {
return nil, nil, err
}
if _, ok := havePkgs[pkgPath]; !ok { if _, ok := havePkgs[pkgPath]; !ok {
needPkgsSet[pkgPath] = true needPkgsSet[pkgPath] = true
} }
@ -185,6 +202,52 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
return modifiedPkgs, needPkgs, err return modifiedPkgs, needPkgs, err
} }
// resolveImport finds the the ID of a package given its import path.
// In particular, it will find the right vendored copy when in GOPATH mode.
func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) {
env, err := state.getEnv()
if err != nil {
return "", err
}
if env["GOMOD"] != "" {
return importPath, nil
}
searchDir := sourceDir
for {
vendorDir := filepath.Join(searchDir, "vendor")
exists, ok := state.vendorDirs[vendorDir]
if !ok {
info, err := os.Stat(vendorDir)
exists = err == nil && info.IsDir()
state.vendorDirs[vendorDir] = exists
}
if exists {
vendoredPath := filepath.Join(vendorDir, importPath)
if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() {
// We should probably check for .go files here, but shame on anyone who fools us.
path, ok, err := state.getPkgPath(vendoredPath)
if err != nil {
return "", err
}
if ok {
return path, nil
}
}
}
// We know we've hit the top of the filesystem when we Dir / and get /,
// or C:\ and get C:\, etc.
next := filepath.Dir(searchDir)
if next == searchDir {
break
}
searchDir = next
}
return importPath, nil
}
func hasTestFiles(p *Package) bool { func hasTestFiles(p *Package) bool {
for _, f := range p.GoFiles { for _, f := range p.GoFiles {
if strings.HasSuffix(f, "_test.go") { if strings.HasSuffix(f, "_test.go") {
@ -194,44 +257,59 @@ func hasTestFiles(p *Package) bool {
return false return false
} }
// determineRootDirs returns a mapping from directories code can be contained in to the // determineRootDirs returns a mapping from absolute directories that could
// corresponding import path prefixes of those directories. // contain code to their corresponding import path prefixes.
// Its result is used to try to determine the import path for a package containing func (state *golistState) determineRootDirs() (map[string]string, error) {
// an overlay file. env, err := state.getEnv()
func determineRootDirs(cfg *Config) map[string]string {
// Assume modules first:
out, err := invokeGo(cfg, "list", "-m", "-json", "all")
if err != nil { if err != nil {
return determineRootDirsGOPATH(cfg) return nil, err
}
if env["GOMOD"] != "" {
state.rootsOnce.Do(func() {
state.rootDirs, state.rootDirsError = state.determineRootDirsModules()
})
} else {
state.rootsOnce.Do(func() {
state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH()
})
}
return state.rootDirs, state.rootDirsError
}
func (state *golistState) determineRootDirsModules() (map[string]string, error) {
out, err := state.invokeGo("list", "-m", "-json", "all")
if err != nil {
return nil, err
} }
m := map[string]string{} m := map[string]string{}
type jsonMod struct{ Path, Dir string } type jsonMod struct{ Path, Dir string }
for dec := json.NewDecoder(out); dec.More(); { for dec := json.NewDecoder(out); dec.More(); {
mod := new(jsonMod) mod := new(jsonMod)
if err := dec.Decode(mod); err != nil { if err := dec.Decode(mod); err != nil {
return m // Give up and return an empty map. Package won't be found for overlay. return nil, err
} }
if mod.Dir != "" && mod.Path != "" { if mod.Dir != "" && mod.Path != "" {
// This is a valid module; add it to the map. // This is a valid module; add it to the map.
m[mod.Dir] = mod.Path absDir, err := filepath.Abs(mod.Dir)
if err != nil {
return nil, err
}
m[absDir] = mod.Path
} }
} }
return m return m, nil
} }
func determineRootDirsGOPATH(cfg *Config) map[string]string { func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
m := map[string]string{} m := map[string]string{}
out, err := invokeGo(cfg, "env", "GOPATH") for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) {
if err != nil { absDir, err := filepath.Abs(dir)
// Could not determine root dir mapping. Everything is best-effort, so just return an empty map. if err != nil {
// When we try to find the import path for a directory, there will be no root-dir match and return nil, err
// we'll give up. }
return m m[filepath.Join(absDir, "src")] = ""
} }
for _, p := range filepath.SplitList(string(bytes.TrimSpace(out.Bytes()))) { return m, nil
m[filepath.Join(p, "src")] = ""
}
return m
} }
func extractImports(filename string, contents []byte) ([]string, error) { func extractImports(filename string, contents []byte) ([]string, error) {

View File

@ -23,6 +23,7 @@ import (
"sync" "sync"
"golang.org/x/tools/go/gcexportdata" "golang.org/x/tools/go/gcexportdata"
"golang.org/x/tools/internal/packagesinternal"
) )
// A LoadMode controls the amount of detail to return when loading. // A LoadMode controls the amount of detail to return when loading.
@ -34,6 +35,9 @@ import (
// Load may return more information than requested. // Load may return more information than requested.
type LoadMode int type LoadMode int
// TODO(matloob): When a V2 of go/packages is released, rename NeedExportsFile to
// NeedExportFile to make it consistent with the Package field it's adding.
const ( const (
// NeedName adds Name and PkgPath. // NeedName adds Name and PkgPath.
NeedName LoadMode = 1 << iota NeedName LoadMode = 1 << iota
@ -51,7 +55,7 @@ const (
// NeedDeps adds the fields requested by the LoadMode in the packages in Imports. // NeedDeps adds the fields requested by the LoadMode in the packages in Imports.
NeedDeps NeedDeps
// NeedExportsFile adds ExportsFile. // NeedExportsFile adds ExportFile.
NeedExportsFile NeedExportsFile
// NeedTypes adds Types, Fset, and IllTyped. // NeedTypes adds Types, Fset, and IllTyped.
@ -160,7 +164,7 @@ type Config struct {
Tests bool Tests bool
// Overlay provides a mapping of absolute file paths to file contents. // 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 // If the file with the given path already exists, the parser will use the
// alternative file contents provided by the map. // alternative file contents provided by the map.
// //
// Overlays provide incomplete support for when a given file doesn't // Overlays provide incomplete support for when a given file doesn't
@ -292,6 +296,15 @@ type Package struct {
// TypesSizes provides the effective size function for types in TypesInfo. // TypesSizes provides the effective size function for types in TypesInfo.
TypesSizes types.Sizes TypesSizes types.Sizes
// forTest is the package under test, if any.
forTest string
}
func init() {
packagesinternal.GetForTest = func(p interface{}) string {
return p.(*Package).forTest
}
} }
// An Error describes a problem with a package's metadata, syntax, or types. // An Error describes a problem with a package's metadata, syntax, or types.
@ -500,12 +513,23 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) {
if i, found := rootMap[pkg.ID]; found { if i, found := rootMap[pkg.ID]; found {
rootIndex = i rootIndex = i
} }
// Overlays can invalidate export data.
// TODO(matloob): make this check fine-grained based on dependencies on overlaid files
exportDataInvalid := len(ld.Overlay) > 0 || pkg.ExportFile == "" && pkg.PkgPath != "unsafe"
// This package needs type information if the caller requested types and the package is
// either a root, or it's a non-root and the user requested dependencies ...
needtypes := (ld.Mode&NeedTypes|NeedTypesInfo != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0))
// This package needs source if the call requested source (or types info, which implies source)
// and the package is either a root, or itas a non- root and the user requested dependencies...
needsrc := ((ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) ||
// ... or if we need types and the exportData is invalid. We fall back to (incompletely)
// typechecking packages from source if they fail to compile.
(ld.Mode&NeedTypes|NeedTypesInfo != 0 && exportDataInvalid)) && pkg.PkgPath != "unsafe"
lpkg := &loaderPackage{ lpkg := &loaderPackage{
Package: pkg, Package: pkg,
needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0, needtypes: needtypes,
needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && ld.Mode&NeedDeps != 0 && rootIndex < 0) || rootIndex >= 0 || needsrc: needsrc,
len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files
pkg.ExportFile == "" && pkg.PkgPath != "unsafe",
} }
ld.pkgs[lpkg.ID] = lpkg ld.pkgs[lpkg.ID] = lpkg
if rootIndex >= 0 { if rootIndex >= 0 {
@ -713,7 +737,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
// which would then require that such created packages be explicitly // which would then require that such created packages be explicitly
// inserted back into the Import graph as a final step after export data loading. // inserted back into the Import graph as a final step after export data loading.
// The Diamond test exercises this case. // The Diamond test exercises this case.
if !lpkg.needtypes { if !lpkg.needtypes && !lpkg.needsrc {
return return
} }
if !lpkg.needsrc { if !lpkg.needsrc {

View File

@ -4,6 +4,7 @@ package imports // import "golang.org/x/tools/imports"
import ( import (
"go/build" "go/build"
"os"
intimp "golang.org/x/tools/internal/imports" intimp "golang.org/x/tools/internal/imports"
) )
@ -42,6 +43,10 @@ func Process(filename string, src []byte, opt *Options) ([]byte, error) {
Env: &intimp.ProcessEnv{ Env: &intimp.ProcessEnv{
GOPATH: build.Default.GOPATH, GOPATH: build.Default.GOPATH,
GOROOT: build.Default.GOROOT, GOROOT: build.Default.GOROOT,
GOFLAGS: os.Getenv("GOFLAGS"),
GO111MODULE: os.Getenv("GO111MODULE"),
GOPROXY: os.Getenv("GOPROXY"),
GOSUMDB: os.Getenv("GOSUMDB"),
Debug: Debug, Debug: Debug,
LocalPrefix: LocalPrefix, LocalPrefix: LocalPrefix,
}, },

View File

@ -77,6 +77,7 @@ func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root
} }
} }
// walkDir creates a walker and starts fastwalk with this walker.
func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) {
if _, err := os.Stat(root.Path); os.IsNotExist(err) { if _, err := os.Stat(root.Path); os.IsNotExist(err) {
if opts.Debug { if opts.Debug {
@ -114,7 +115,7 @@ type walker struct {
ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files. ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files.
} }
// init initializes the walker based on its Options. // init initializes the walker based on its Options
func (w *walker) init() { func (w *walker) init() {
var ignoredPaths []string var ignoredPaths []string
if w.root.Type == RootModuleCache { if w.root.Type == RootModuleCache {
@ -167,6 +168,7 @@ func (w *walker) getIgnoredDirs(path string) []string {
return ignoredDirs return ignoredDirs
} }
// shouldSkipDir reports whether the file should be skipped or not.
func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool { func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool {
for _, ignoredDir := range w.ignoredDirs { for _, ignoredDir := range w.ignoredDirs {
if os.SameFile(fi, ignoredDir) { if os.SameFile(fi, ignoredDir) {
@ -180,6 +182,7 @@ func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool {
return false return false
} }
// walk walks through the given path.
func (w *walker) walk(path string, typ os.FileMode) error { func (w *walker) walk(path string, typ os.FileMode) error {
dir := filepath.Dir(path) dir := filepath.Dir(path)
if typ.IsRegular() { if typ.IsRegular() {

View File

@ -27,7 +27,6 @@ import (
"unicode/utf8" "unicode/utf8"
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/gopathwalk" "golang.org/x/tools/internal/gopathwalk"
) )
@ -82,7 +81,8 @@ type ImportFix struct {
// IdentName is the identifier that this fix will add or remove. // IdentName is the identifier that this fix will add or remove.
IdentName string IdentName string
// FixType is the type of fix this is (AddImport, DeleteImport, SetImportName). // FixType is the type of fix this is (AddImport, DeleteImport, SetImportName).
FixType ImportFixType FixType ImportFixType
Relevance int // see pkg
} }
// An ImportInfo represents a single import statement. // An ImportInfo represents a single import statement.
@ -585,62 +585,86 @@ func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv
return fixes, nil return fixes, nil
} }
// getCandidatePkgs returns the list of pkgs that are accessible from filename, // Highest relevance, used for the standard library. Chosen arbitrarily to
// optionall filtered to only packages named pkgName. // match pre-existing gopls code.
func getCandidatePkgs(pkgName, filename string, env *ProcessEnv) ([]*pkg, error) { const MaxRelevance = 7
// TODO(heschi): filter out current package. (Don't forget x_test can import x.)
var result []*pkg // getCandidatePkgs works with the passed callback to find all acceptable packages.
// It deduplicates by import path, and uses a cached stdlib rather than reading
// from disk.
func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filename, filePkg string, env *ProcessEnv) error {
notSelf := func(p *pkg) bool {
return p.packageName != filePkg || p.dir != filepath.Dir(filename)
}
// Start off with the standard library. // Start off with the standard library.
for importPath := range stdlib { for importPath, exports := range stdlib {
if pkgName != "" && path.Base(importPath) != pkgName { p := &pkg{
continue
}
result = append(result, &pkg{
dir: filepath.Join(env.GOROOT, "src", importPath), dir: filepath.Join(env.GOROOT, "src", importPath),
importPathShort: importPath, importPathShort: importPath,
packageName: path.Base(importPath), packageName: path.Base(importPath),
relevance: 0, relevance: MaxRelevance,
}) }
} if notSelf(p) && wrappedCallback.packageNameLoaded(p) {
wrappedCallback.exportsLoaded(p, exports)
// Exclude goroot results -- getting them is relatively expensive, not cached, }
// and generally redundant with the in-memory version.
exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
// Only the go/packages resolver uses the first argument, and nobody uses that resolver.
scannedPkgs, err := env.GetResolver().scan(nil, true, exclude)
if err != nil {
return nil, err
} }
var mu sync.Mutex
dupCheck := map[string]struct{}{} dupCheck := map[string]struct{}{}
for _, pkg := range scannedPkgs {
if pkgName != "" && pkg.packageName != pkgName { scanFilter := &scanCallback{
continue rootFound: func(root gopathwalk.Root) bool {
} // Exclude goroot results -- getting them is relatively expensive, not cached,
if !canUse(filename, pkg.dir) { // and generally redundant with the in-memory version.
continue return root.Type != gopathwalk.RootGOROOT && wrappedCallback.rootFound(root)
} },
if _, ok := dupCheck[pkg.importPathShort]; ok { dirFound: wrappedCallback.dirFound,
continue packageNameLoaded: func(pkg *pkg) bool {
} mu.Lock()
dupCheck[pkg.importPathShort] = struct{}{} defer mu.Unlock()
result = append(result, pkg) if _, ok := dupCheck[pkg.importPathShort]; ok {
return false
}
dupCheck[pkg.importPathShort] = struct{}{}
return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg)
},
exportsLoaded: func(pkg *pkg, exports []string) {
// If we're an x_test, load the package under test's test variant.
if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) {
var err error
_, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true)
if err != nil {
return
}
}
wrappedCallback.exportsLoaded(pkg, exports)
},
} }
return env.GetResolver().scan(ctx, scanFilter)
}
// Sort first by relevance, then by package name, with import path as a tiebreaker. func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) map[string]int {
sort.Slice(result, func(i, j int) bool { result := make(map[string]int)
pi, pj := result[i], result[j] for _, path := range paths {
if pi.relevance != pj.relevance { result[path] = env.GetResolver().scoreImportPath(ctx, path)
return pi.relevance < pj.relevance }
} return result
if pi.packageName != pj.packageName { }
return pi.packageName < pj.packageName
}
return pi.importPathShort < pj.importPathShort
})
return result, nil func PrimeCache(ctx context.Context, env *ProcessEnv) error {
// Fully scan the disk for directories, but don't actually read any Go files.
callback := &scanCallback{
rootFound: func(gopathwalk.Root) bool {
return true
},
dirFound: func(pkg *pkg) bool {
return false
},
packageNameLoaded: func(pkg *pkg) bool {
return false
},
}
return getCandidatePkgs(ctx, callback, "", "", env)
} }
func candidateImportName(pkg *pkg) string { func candidateImportName(pkg *pkg) string {
@ -651,23 +675,37 @@ func candidateImportName(pkg *pkg) string {
} }
// getAllCandidates gets all of the candidates to be imported, regardless of if they are needed. // getAllCandidates gets all of the candidates to be imported, regardless of if they are needed.
func getAllCandidates(filename string, env *ProcessEnv) ([]ImportFix, error) { func getAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error {
pkgs, err := getCandidatePkgs("", filename, env) callback := &scanCallback{
if err != nil { rootFound: func(gopathwalk.Root) bool {
return nil, err return true
},
dirFound: func(pkg *pkg) bool {
if !canUse(filename, pkg.dir) {
return false
}
// Try the assumed package name first, then a simpler path match
// in case of packages named vN, which are not uncommon.
return strings.HasPrefix(ImportPathToAssumedName(pkg.importPathShort), searchPrefix) ||
strings.HasPrefix(path.Base(pkg.importPathShort), searchPrefix)
},
packageNameLoaded: func(pkg *pkg) bool {
if !strings.HasPrefix(pkg.packageName, searchPrefix) {
return false
}
wrapped(ImportFix{
StmtInfo: ImportInfo{
ImportPath: pkg.importPathShort,
Name: candidateImportName(pkg),
},
IdentName: pkg.packageName,
FixType: AddImport,
Relevance: pkg.relevance,
})
return false
},
} }
result := make([]ImportFix, 0, len(pkgs)) return getCandidatePkgs(ctx, callback, filename, filePkg, env)
for _, pkg := range pkgs {
result = append(result, ImportFix{
StmtInfo: ImportInfo{
ImportPath: pkg.importPathShort,
Name: candidateImportName(pkg),
},
IdentName: pkg.packageName,
FixType: AddImport,
})
}
return result, nil
} }
// A PackageExport is a package and its exports. // A PackageExport is a package and its exports.
@ -676,42 +714,34 @@ type PackageExport struct {
Exports []string Exports []string
} }
func getPackageExports(completePackage, filename string, env *ProcessEnv) ([]PackageExport, error) { func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error {
pkgs, err := getCandidatePkgs(completePackage, filename, env) callback := &scanCallback{
if err != nil { rootFound: func(gopathwalk.Root) bool {
return nil, err return true
},
dirFound: func(pkg *pkg) bool {
return pkgIsCandidate(filename, references{searchPkg: nil}, pkg)
},
packageNameLoaded: func(pkg *pkg) bool {
return pkg.packageName == searchPkg
},
exportsLoaded: func(pkg *pkg, exports []string) {
sort.Strings(exports)
wrapped(PackageExport{
Fix: &ImportFix{
StmtInfo: ImportInfo{
ImportPath: pkg.importPathShort,
Name: candidateImportName(pkg),
},
IdentName: pkg.packageName,
FixType: AddImport,
Relevance: pkg.relevance,
},
Exports: exports,
})
},
} }
return getCandidatePkgs(ctx, callback, filename, filePkg, env)
results := make([]PackageExport, 0, len(pkgs))
for _, pkg := range pkgs {
fix := &ImportFix{
StmtInfo: ImportInfo{
ImportPath: pkg.importPathShort,
Name: candidateImportName(pkg),
},
IdentName: pkg.packageName,
FixType: AddImport,
}
var exports []string
if e, ok := stdlib[pkg.importPathShort]; ok {
exports = e
} else {
exports, err = loadExportsForPackage(context.Background(), env, completePackage, pkg)
if err != nil {
if env.Debug {
env.Logf("while completing %q, error loading exports from %q: %v", completePackage, pkg.importPathShort, err)
}
continue
}
}
sort.Strings(exports)
results = append(results, PackageExport{
Fix: fix,
Exports: exports,
})
}
return results, nil
} }
// ProcessEnv contains environment variables and settings that affect the use of // ProcessEnv contains environment variables and settings that affect the use of
@ -725,15 +755,19 @@ type ProcessEnv struct {
GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS, GOSUMDB string GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS, GOSUMDB string
WorkingDir string WorkingDir string
// If true, use go/packages regardless of the environment.
ForceGoPackages bool
// Logf is the default logger for the ProcessEnv. // Logf is the default logger for the ProcessEnv.
Logf func(format string, args ...interface{}) Logf func(format string, args ...interface{})
resolver Resolver resolver Resolver
} }
// CopyConfig copies the env's configuration into a new env.
func (e *ProcessEnv) CopyConfig() *ProcessEnv {
copy := *e
copy.resolver = nil
return &copy
}
func (e *ProcessEnv) env() []string { func (e *ProcessEnv) env() []string {
env := os.Environ() env := os.Environ()
add := func(k, v string) { add := func(k, v string) {
@ -757,39 +791,34 @@ func (e *ProcessEnv) GetResolver() Resolver {
if e.resolver != nil { if e.resolver != nil {
return e.resolver return e.resolver
} }
if e.ForceGoPackages {
e.resolver = &goPackagesResolver{env: e}
return e.resolver
}
out, err := e.invokeGo("env", "GOMOD") out, err := e.invokeGo("env", "GOMOD")
if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 { if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 {
e.resolver = &gopathResolver{env: e} e.resolver = newGopathResolver(e)
return e.resolver return e.resolver
} }
e.resolver = &ModuleResolver{env: e} e.resolver = newModuleResolver(e)
return e.resolver return e.resolver
} }
func (e *ProcessEnv) newPackagesConfig(mode packages.LoadMode) *packages.Config {
return &packages.Config{
Mode: mode,
Dir: e.WorkingDir,
Env: e.env(),
}
}
func (e *ProcessEnv) buildContext() *build.Context { func (e *ProcessEnv) buildContext() *build.Context {
ctx := build.Default ctx := build.Default
ctx.GOROOT = e.GOROOT ctx.GOROOT = e.GOROOT
ctx.GOPATH = e.GOPATH ctx.GOPATH = e.GOPATH
// As of Go 1.14, build.Context has a WorkingDir field // As of Go 1.14, build.Context has a Dir field
// (see golang.org/issue/34860). // (see golang.org/issue/34860).
// Populate it only if present. // Populate it only if present.
if wd := reflect.ValueOf(&ctx).Elem().FieldByName("WorkingDir"); wd.IsValid() && wd.Kind() == reflect.String { rc := reflect.ValueOf(&ctx).Elem()
wd.SetString(e.WorkingDir) dir := rc.FieldByName("Dir")
if !dir.IsValid() {
// Working drafts of Go 1.14 named the field "WorkingDir" instead.
// TODO(bcmills): Remove this case after the Go 1.14 beta has been released.
dir = rc.FieldByName("WorkingDir")
} }
if dir.IsValid() && dir.Kind() == reflect.String {
dir.SetString(e.WorkingDir)
}
return &ctx return &ctx
} }
@ -848,94 +877,65 @@ func addStdlibCandidates(pass *pass, refs references) {
type Resolver interface { type Resolver interface {
// loadPackageNames loads the package names in importPaths. // loadPackageNames loads the package names in importPaths.
loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error)
// scan finds (at least) the packages satisfying refs. If loadNames is true, // scan works with callback to search for packages. See scanCallback for details.
// package names will be set on the results, and dirs whose package name scan(ctx context.Context, callback *scanCallback) error
// could not be determined will be excluded.
scan(refs references, loadNames bool, exclude []gopathwalk.RootType) ([]*pkg, error)
// loadExports returns the set of exported symbols in the package at dir. // loadExports returns the set of exported symbols in the package at dir.
// loadExports may be called concurrently. // loadExports may be called concurrently.
loadExports(ctx context.Context, pkg *pkg) (string, []string, error) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error)
// scoreImportPath returns the relevance for an import path.
scoreImportPath(ctx context.Context, path string) int
ClearForNewScan() ClearForNewScan()
} }
// gopackagesResolver implements resolver for GOPATH and module workspaces using go/packages. // A scanCallback controls a call to scan and receives its results.
type goPackagesResolver struct { // In general, minor errors will be silently discarded; a user should not
env *ProcessEnv // expect to receive a full series of calls for everything.
} type scanCallback struct {
// rootFound is called before scanning a new root dir. If it returns true,
func (r *goPackagesResolver) ClearForNewScan() {} // the root will be scanned. Returning false will not necessarily prevent
// directories from that root making it to dirFound.
func (r *goPackagesResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { rootFound func(gopathwalk.Root) bool
if len(importPaths) == 0 { // dirFound is called when a directory is found that is possibly a Go package.
return nil, nil // pkg will be populated with everything except packageName.
} // If it returns true, the package's name will be loaded.
cfg := r.env.newPackagesConfig(packages.LoadFiles) dirFound func(pkg *pkg) bool
pkgs, err := packages.Load(cfg, importPaths...) // packageNameLoaded is called when a package is found and its name is loaded.
if err != nil { // If it returns true, the package's exports will be loaded.
return nil, err packageNameLoaded func(pkg *pkg) bool
} // exportsLoaded is called when a package's exports have been loaded.
names := map[string]string{} exportsLoaded func(pkg *pkg, exports []string)
for _, pkg := range pkgs {
names[VendorlessPath(pkg.PkgPath)] = pkg.Name
}
// We may not have found all the packages. Guess the rest.
for _, path := range importPaths {
if _, ok := names[path]; ok {
continue
}
names[path] = ImportPathToAssumedName(path)
}
return names, nil
}
func (r *goPackagesResolver) scan(refs references, _ bool, _ []gopathwalk.RootType) ([]*pkg, error) {
var loadQueries []string
for pkgName := range refs {
loadQueries = append(loadQueries, "iamashamedtousethedisabledqueryname="+pkgName)
}
sort.Strings(loadQueries)
cfg := r.env.newPackagesConfig(packages.LoadFiles)
goPackages, err := packages.Load(cfg, loadQueries...)
if err != nil {
return nil, err
}
var scan []*pkg
for _, goPackage := range goPackages {
scan = append(scan, &pkg{
dir: filepath.Dir(goPackage.CompiledGoFiles[0]),
importPathShort: VendorlessPath(goPackage.PkgPath),
goPackage: goPackage,
packageName: goPackage.Name,
})
}
return scan, nil
}
func (r *goPackagesResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) {
if pkg.goPackage == nil {
return "", nil, fmt.Errorf("goPackage not set")
}
var exports []string
fset := token.NewFileSet()
for _, fname := range pkg.goPackage.CompiledGoFiles {
f, err := parser.ParseFile(fset, fname, nil, 0)
if err != nil {
return "", nil, fmt.Errorf("parsing %s: %v", fname, err)
}
for name := range f.Scope.Objects {
if ast.IsExported(name) {
exports = append(exports, name)
}
}
}
return pkg.goPackage.Name, exports, nil
} }
func addExternalCandidates(pass *pass, refs references, filename string) error { func addExternalCandidates(pass *pass, refs references, filename string) error {
dirScan, err := pass.env.GetResolver().scan(refs, false, nil) var mu sync.Mutex
found := make(map[string][]pkgDistance)
callback := &scanCallback{
rootFound: func(gopathwalk.Root) bool {
return true // We want everything.
},
dirFound: func(pkg *pkg) bool {
return pkgIsCandidate(filename, refs, pkg)
},
packageNameLoaded: func(pkg *pkg) bool {
if _, want := refs[pkg.packageName]; !want {
return false
}
if pkg.dir == pass.srcDir && pass.f.Name.Name == pkg.packageName {
// The candidate is in the same directory and has the
// same package name. Don't try to import ourselves.
return false
}
if !canUse(filename, pkg.dir) {
return false
}
mu.Lock()
defer mu.Unlock()
found[pkg.packageName] = append(found[pkg.packageName], pkgDistance{pkg, distance(pass.srcDir, pkg.dir)})
return false // We'll do our own loading after we sort.
},
}
err := pass.env.GetResolver().scan(context.Background(), callback)
if err != nil { if err != nil {
return err return err
} }
@ -962,7 +962,7 @@ func addExternalCandidates(pass *pass, refs references, filename string) error {
go func(pkgName string, symbols map[string]bool) { go func(pkgName string, symbols map[string]bool) {
defer wg.Done() defer wg.Done()
found, err := findImport(ctx, pass, dirScan, pkgName, symbols, filename) found, err := findImport(ctx, pass, found[pkgName], pkgName, symbols, filename)
if err != nil { if err != nil {
firstErrOnce.Do(func() { firstErrOnce.Do(func() {
@ -1033,24 +1033,36 @@ func ImportPathToAssumedName(importPath string) string {
// gopathResolver implements resolver for GOPATH workspaces. // gopathResolver implements resolver for GOPATH workspaces.
type gopathResolver struct { type gopathResolver struct {
env *ProcessEnv env *ProcessEnv
cache *dirInfoCache walked bool
cache *dirInfoCache
scanSema chan struct{} // scanSema prevents concurrent scans.
} }
func (r *gopathResolver) init() { func newGopathResolver(env *ProcessEnv) *gopathResolver {
if r.cache == nil { r := &gopathResolver{
r.cache = &dirInfoCache{ env: env,
dirs: map[string]*directoryPackageInfo{}, cache: &dirInfoCache{
} dirs: map[string]*directoryPackageInfo{},
listeners: map[*int]cacheListener{},
},
scanSema: make(chan struct{}, 1),
} }
r.scanSema <- struct{}{}
return r
} }
func (r *gopathResolver) ClearForNewScan() { func (r *gopathResolver) ClearForNewScan() {
r.cache = nil <-r.scanSema
r.cache = &dirInfoCache{
dirs: map[string]*directoryPackageInfo{},
listeners: map[*int]cacheListener{},
}
r.walked = false
r.scanSema <- struct{}{}
} }
func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) {
r.init()
names := map[string]string{} names := map[string]string{}
for _, path := range importPaths { for _, path := range importPaths {
names[path] = importPathToName(r.env, path, srcDir) names[path] = importPathToName(r.env, path, srcDir)
@ -1130,7 +1142,6 @@ func packageDirToName(dir string) (packageName string, err error) {
} }
type pkg struct { type pkg struct {
goPackage *packages.Package
dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http") dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http")
importPathShort string // vendorless import path ("net/http", "a/b") importPathShort string // vendorless import path ("net/http", "a/b")
packageName string // package name loaded from source if requested packageName string // package name loaded from source if requested
@ -1178,8 +1189,7 @@ func distance(basepath, targetpath string) int {
return strings.Count(p, string(filepath.Separator)) + 1 return strings.Count(p, string(filepath.Separator)) + 1
} }
func (r *gopathResolver) scan(_ references, loadNames bool, exclude []gopathwalk.RootType) ([]*pkg, error) { func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error {
r.init()
add := func(root gopathwalk.Root, dir string) { add := func(root gopathwalk.Root, dir string) {
// We assume cached directories have not changed. We can skip them and their // We assume cached directories have not changed. We can skip them and their
// children. // children.
@ -1196,56 +1206,84 @@ func (r *gopathResolver) scan(_ references, loadNames bool, exclude []gopathwalk
} }
r.cache.Store(dir, info) r.cache.Store(dir, info)
} }
roots := filterRoots(gopathwalk.SrcDirsRoots(r.env.buildContext()), exclude) processDir := func(info directoryPackageInfo) {
gopathwalk.Walk(roots, add, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: false}) // Skip this directory if we were not able to get the package information successfully.
var result []*pkg if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
for _, dir := range r.cache.Keys() { return
info, ok := r.cache.Load(dir)
if !ok {
continue
}
if loadNames {
var err error
info, err = r.cache.CachePackageName(info)
if err != nil {
continue
}
} }
p := &pkg{ p := &pkg{
importPathShort: info.nonCanonicalImportPath, importPathShort: info.nonCanonicalImportPath,
dir: dir, dir: info.dir,
relevance: 1, relevance: MaxRelevance - 1,
packageName: info.packageName,
} }
if info.rootType == gopathwalk.RootGOROOT { if info.rootType == gopathwalk.RootGOROOT {
p.relevance = 0 p.relevance = MaxRelevance
}
if !callback.dirFound(p) {
return
}
var err error
p.packageName, err = r.cache.CachePackageName(info)
if err != nil {
return
}
if !callback.packageNameLoaded(p) {
return
}
if _, exports, err := r.loadExports(ctx, p, false); err == nil {
callback.exportsLoaded(p, exports)
} }
result = append(result, p)
} }
return result, nil stop := r.cache.ScanAndListen(ctx, processDir)
defer stop()
// The callback is not necessarily safe to use in the goroutine below. Process roots eagerly.
roots := filterRoots(gopathwalk.SrcDirsRoots(r.env.buildContext()), callback.rootFound)
// We can't cancel walks, because we need them to finish to have a usable
// cache. Instead, run them in a separate goroutine and detach.
scanDone := make(chan struct{})
go func() {
select {
case <-ctx.Done():
return
case <-r.scanSema:
}
defer func() { r.scanSema <- struct{}{} }()
gopathwalk.Walk(roots, add, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: false})
close(scanDone)
}()
select {
case <-ctx.Done():
case <-scanDone:
}
return nil
} }
func filterRoots(roots []gopathwalk.Root, exclude []gopathwalk.RootType) []gopathwalk.Root { func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) int {
if _, ok := stdlib[path]; ok {
return MaxRelevance
}
return MaxRelevance - 1
}
func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root {
var result []gopathwalk.Root var result []gopathwalk.Root
outer:
for _, root := range roots { for _, root := range roots {
for _, i := range exclude { if !include(root) {
if i == root.Type { continue
continue outer
}
} }
result = append(result, root) result = append(result, root)
} }
return result return result
} }
func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) { func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) {
r.init() if info, ok := r.cache.Load(pkg.dir); ok && !includeTest {
if info, ok := r.cache.Load(pkg.dir); ok {
return r.cache.CacheExports(ctx, r.env, info) return r.cache.CacheExports(ctx, r.env, info)
} }
return loadExportsFromFiles(ctx, r.env, pkg.dir) return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest)
} }
// VendorlessPath returns the devendorized version of the import path ipath. // VendorlessPath returns the devendorized version of the import path ipath.
@ -1261,7 +1299,7 @@ func VendorlessPath(ipath string) string {
return ipath return ipath
} }
func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (string, []string, error) { func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []string, error) {
var exports []string var exports []string
// Look for non-test, buildable .go files which could provide exports. // Look for non-test, buildable .go files which could provide exports.
@ -1272,7 +1310,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str
var files []os.FileInfo var files []os.FileInfo
for _, fi := range all { for _, fi := range all {
name := fi.Name() name := fi.Name()
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) {
continue continue
} }
match, err := env.buildContext().MatchFile(dir, fi.Name()) match, err := env.buildContext().MatchFile(dir, fi.Name())
@ -1305,6 +1343,10 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str
// handled by MatchFile above. // handled by MatchFile above.
continue continue
} }
if includeTest && strings.HasSuffix(f.Name.Name, "_test") {
// x_test package. We want internal test files only.
continue
}
pkgName = f.Name.Name pkgName = f.Name.Name
for name := range f.Scope.Objects { for name := range f.Scope.Objects {
if ast.IsExported(name) { if ast.IsExported(name) {
@ -1323,29 +1365,7 @@ func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string) (str
// findImport searches for a package with the given symbols. // findImport searches for a package with the given symbols.
// If no package is found, findImport returns ("", false, nil) // If no package is found, findImport returns ("", false, nil)
func findImport(ctx context.Context, pass *pass, dirScan []*pkg, pkgName string, symbols map[string]bool, filename string) (*pkg, error) { func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgName string, symbols map[string]bool, filename string) (*pkg, error) {
pkgDir, err := filepath.Abs(filename)
if err != nil {
return nil, err
}
pkgDir = filepath.Dir(pkgDir)
// Find candidate packages, looking only at their directory names first.
var candidates []pkgDistance
for _, pkg := range dirScan {
if pkg.dir == pkgDir && pass.f.Name.Name == pkgName {
// The candidate is in the same directory and has the
// same package name. Don't try to import ourselves.
continue
}
if pkgIsCandidate(filename, pkgName, pkg) {
candidates = append(candidates, pkgDistance{
pkg: pkg,
distance: distance(pkgDir, pkg.dir),
})
}
}
// Sort the candidates by their import package length, // Sort the candidates by their import package length,
// assuming that shorter package names are better than long // assuming that shorter package names are better than long
// ones. Note that this sorts by the de-vendored name, so // ones. Note that this sorts by the de-vendored name, so
@ -1358,7 +1378,6 @@ func findImport(ctx context.Context, pass *pass, dirScan []*pkg, pkgName string,
} }
// Collect exports for packages with matching names. // Collect exports for packages with matching names.
rescv := make([]chan *pkg, len(candidates)) rescv := make([]chan *pkg, len(candidates))
for i := range candidates { for i := range candidates {
rescv[i] = make(chan *pkg, 1) rescv[i] = make(chan *pkg, 1)
@ -1393,7 +1412,9 @@ func findImport(ctx context.Context, pass *pass, dirScan []*pkg, pkgName string,
if pass.env.Debug { if pass.env.Debug {
pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName) pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName)
} }
exports, err := loadExportsForPackage(ctx, pass.env, pkgName, c.pkg) // If we're an x_test, load the package under test's test variant.
includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir
_, exports, err := pass.env.GetResolver().loadExports(ctx, c.pkg, includeTest)
if err != nil { if err != nil {
if pass.env.Debug { if pass.env.Debug {
pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err)
@ -1430,17 +1451,6 @@ func findImport(ctx context.Context, pass *pass, dirScan []*pkg, pkgName string,
return nil, nil return nil, nil
} }
func loadExportsForPackage(ctx context.Context, env *ProcessEnv, expectPkg string, pkg *pkg) ([]string, error) {
pkgName, exports, err := env.GetResolver().loadExports(ctx, pkg)
if err != nil {
return nil, err
}
if expectPkg != pkgName {
return nil, fmt.Errorf("dir %v is package %v, wanted %v", pkg.dir, pkgName, expectPkg)
}
return exports, err
}
// pkgIsCandidate reports whether pkg is a candidate for satisfying the // pkgIsCandidate reports whether pkg is a candidate for satisfying the
// finding which package pkgIdent in the file named by filename is trying // finding which package pkgIdent in the file named by filename is trying
// to refer to. // to refer to.
@ -1453,7 +1463,7 @@ func loadExportsForPackage(ctx context.Context, env *ProcessEnv, expectPkg strin
// filename is the file being formatted. // filename is the file being formatted.
// pkgIdent is the package being searched for, like "client" (if // pkgIdent is the package being searched for, like "client" (if
// searching for "client.New") // searching for "client.New")
func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool { func pkgIsCandidate(filename string, refs references, pkg *pkg) bool {
// Check "internal" and "vendor" visibility: // Check "internal" and "vendor" visibility:
if !canUse(filename, pkg.dir) { if !canUse(filename, pkg.dir) {
return false return false
@ -1471,17 +1481,18 @@ func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool {
// "bar", which is strongly discouraged // "bar", which is strongly discouraged
// anyway. There's no reason goimports needs // anyway. There's no reason goimports needs
// to be slow just to accommodate that. // to be slow just to accommodate that.
lastTwo := lastTwoComponents(pkg.importPathShort) for pkgIdent := range refs {
if strings.Contains(lastTwo, pkgIdent) { lastTwo := lastTwoComponents(pkg.importPathShort)
return true
}
if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) {
lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
if strings.Contains(lastTwo, pkgIdent) { if strings.Contains(lastTwo, pkgIdent) {
return true return true
} }
if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) {
lastTwo = lowerASCIIAndRemoveHyphen(lastTwo)
if strings.Contains(lastTwo, pkgIdent) {
return true
}
}
} }
return false return false
} }

View File

@ -11,6 +11,7 @@ package imports
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
@ -21,6 +22,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -83,42 +85,54 @@ func FixImports(filename string, src []byte, opt *Options) (fixes []*ImportFix,
return getFixes(fileSet, file, filename, opt.Env) return getFixes(fileSet, file, filename, opt.Env)
} }
// ApplyFix will apply all of the fixes to the file and format it. // ApplyFixes applies all of the fixes to the file and formats it. extraMode
func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options) (formatted []byte, err error) { // is added in when parsing the file.
func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, extraMode parser.Mode) (formatted []byte, err error) {
src, opt, err = initialize(filename, src, opt) src, opt, err = initialize(filename, src, opt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Don't use parse() -- we don't care about fragments or statement lists
// here, and we need to work with unparseable files.
fileSet := token.NewFileSet() fileSet := token.NewFileSet()
file, adjust, err := parse(fileSet, filename, src, opt) parserMode := parser.Mode(0)
if err != nil { if opt.Comments {
parserMode |= parser.ParseComments
}
if opt.AllErrors {
parserMode |= parser.AllErrors
}
parserMode |= extraMode
file, err := parser.ParseFile(fileSet, filename, src, parserMode)
if file == nil {
return nil, err return nil, err
} }
// Apply the fixes to the file. // Apply the fixes to the file.
apply(fileSet, file, fixes) apply(fileSet, file, fixes)
return formatFile(fileSet, file, src, adjust, opt) return formatFile(fileSet, file, src, nil, opt)
} }
// GetAllCandidates gets all of the standard library candidate packages to import in // GetAllCandidates gets all of the packages starting with prefix that can be
// sorted order on import path. // imported by filename, sorted by import path.
func GetAllCandidates(filename string, opt *Options) (pkgs []ImportFix, err error) { func GetAllCandidates(ctx context.Context, callback func(ImportFix), searchPrefix, filename, filePkg string, opt *Options) error {
_, opt, err = initialize(filename, nil, opt) _, opt, err := initialize(filename, []byte{}, opt)
if err != nil { if err != nil {
return nil, err return err
} }
return getAllCandidates(filename, opt.Env) return getAllCandidates(ctx, callback, searchPrefix, filename, filePkg, opt.Env)
} }
// GetPackageExports returns all known packages with name pkg and their exports. // GetPackageExports returns all known packages with name pkg and their exports.
func GetPackageExports(pkg, filename string, opt *Options) (exports []PackageExport, err error) { func GetPackageExports(ctx context.Context, callback func(PackageExport), searchPkg, filename, filePkg string, opt *Options) error {
_, opt, err = initialize(filename, nil, opt) _, opt, err := initialize(filename, []byte{}, opt)
if err != nil { if err != nil {
return nil, err return err
} }
return getPackageExports(pkg, filename, opt.Env) return getPackageExports(ctx, callback, searchPkg, filename, filePkg, opt.Env)
} }
// initialize sets the values for opt and src. // initialize sets the values for opt and src.
@ -133,8 +147,12 @@ func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, er
// Set the env if the user has not provided it. // Set the env if the user has not provided it.
if opt.Env == nil { if opt.Env == nil {
opt.Env = &ProcessEnv{ opt.Env = &ProcessEnv{
GOPATH: build.Default.GOPATH, GOPATH: build.Default.GOPATH,
GOROOT: build.Default.GOROOT, GOROOT: build.Default.GOROOT,
GOFLAGS: os.Getenv("GOFLAGS"),
GO111MODULE: os.Getenv("GO111MODULE"),
GOPROXY: os.Getenv("GOPROXY"),
GOSUMDB: os.Getenv("GOSUMDB"),
} }
} }

View File

@ -13,7 +13,6 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"golang.org/x/tools/internal/gopathwalk" "golang.org/x/tools/internal/gopathwalk"
"golang.org/x/tools/internal/module" "golang.org/x/tools/internal/module"
@ -26,11 +25,14 @@ type ModuleResolver struct {
env *ProcessEnv env *ProcessEnv
moduleCacheDir string moduleCacheDir string
dummyVendorMod *ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory. dummyVendorMod *ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory.
roots []gopathwalk.Root
scanSema chan struct{} // scanSema prevents concurrent scans and guards scannedRoots.
scannedRoots map[gopathwalk.Root]bool
Initialized bool initialized bool
Main *ModuleJSON main *ModuleJSON
ModsByModPath []*ModuleJSON // All modules, ordered by # of path components in module Path... modsByModPath []*ModuleJSON // All modules, ordered by # of path components in module Path...
ModsByDir []*ModuleJSON // ...or Dir. modsByDir []*ModuleJSON // ...or Dir.
// moduleCacheCache stores information about the module cache. // moduleCacheCache stores information about the module cache.
moduleCacheCache *dirInfoCache moduleCacheCache *dirInfoCache
@ -41,13 +43,23 @@ type ModuleJSON struct {
Path string // module path Path string // module path
Replace *ModuleJSON // replaced by this module Replace *ModuleJSON // replaced by this module
Main bool // is this the main module? Main bool // is this the main module?
Indirect bool // is this module only an indirect dependency of main module?
Dir string // directory holding files for this module, if any Dir string // directory holding files for this module, if any
GoMod string // path to go.mod file for this module, if any GoMod string // path to go.mod file for this module, if any
GoVersion string // go version used in module GoVersion string // go version used in module
} }
func newModuleResolver(e *ProcessEnv) *ModuleResolver {
r := &ModuleResolver{
env: e,
scanSema: make(chan struct{}, 1),
}
r.scanSema <- struct{}{}
return r
}
func (r *ModuleResolver) init() error { func (r *ModuleResolver) init() error {
if r.Initialized { if r.initialized {
return nil return nil
} }
mainMod, vendorEnabled, err := vendorEnabled(r.env) mainMod, vendorEnabled, err := vendorEnabled(r.env)
@ -58,13 +70,13 @@ func (r *ModuleResolver) init() error {
if mainMod != nil && vendorEnabled { if mainMod != nil && vendorEnabled {
// Vendor mode is on, so all the non-Main modules are irrelevant, // Vendor mode is on, so all the non-Main modules are irrelevant,
// and we need to search /vendor for everything. // and we need to search /vendor for everything.
r.Main = mainMod r.main = mainMod
r.dummyVendorMod = &ModuleJSON{ r.dummyVendorMod = &ModuleJSON{
Path: "", Path: "",
Dir: filepath.Join(mainMod.Dir, "vendor"), Dir: filepath.Join(mainMod.Dir, "vendor"),
} }
r.ModsByModPath = []*ModuleJSON{mainMod, r.dummyVendorMod} r.modsByModPath = []*ModuleJSON{mainMod, r.dummyVendorMod}
r.ModsByDir = []*ModuleJSON{mainMod, r.dummyVendorMod} r.modsByDir = []*ModuleJSON{mainMod, r.dummyVendorMod}
} else { } else {
// Vendor mode is off, so run go list -m ... to find everything. // Vendor mode is off, so run go list -m ... to find everything.
r.initAllMods() r.initAllMods()
@ -72,30 +84,64 @@ func (r *ModuleResolver) init() error {
r.moduleCacheDir = filepath.Join(filepath.SplitList(r.env.GOPATH)[0], "/pkg/mod") r.moduleCacheDir = filepath.Join(filepath.SplitList(r.env.GOPATH)[0], "/pkg/mod")
sort.Slice(r.ModsByModPath, func(i, j int) bool { sort.Slice(r.modsByModPath, func(i, j int) bool {
count := func(x int) int { count := func(x int) int {
return strings.Count(r.ModsByModPath[x].Path, "/") return strings.Count(r.modsByModPath[x].Path, "/")
} }
return count(j) < count(i) // descending order return count(j) < count(i) // descending order
}) })
sort.Slice(r.ModsByDir, func(i, j int) bool { sort.Slice(r.modsByDir, func(i, j int) bool {
count := func(x int) int { count := func(x int) int {
return strings.Count(r.ModsByDir[x].Dir, "/") return strings.Count(r.modsByDir[x].Dir, "/")
} }
return count(j) < count(i) // descending order return count(j) < count(i) // descending order
}) })
r.roots = []gopathwalk.Root{
{filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT},
}
if r.main != nil {
r.roots = append(r.roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule})
}
if vendorEnabled {
r.roots = append(r.roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther})
} else {
addDep := func(mod *ModuleJSON) {
if mod.Replace == nil {
// This is redundant with the cache, but we'll skip it cheaply enough.
r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootModuleCache})
} else {
r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther})
}
}
// Walk dependent modules before scanning the full mod cache, direct deps first.
for _, mod := range r.modsByModPath {
if !mod.Indirect && !mod.Main {
addDep(mod)
}
}
for _, mod := range r.modsByModPath {
if mod.Indirect && !mod.Main {
addDep(mod)
}
}
r.roots = append(r.roots, gopathwalk.Root{r.moduleCacheDir, gopathwalk.RootModuleCache})
}
r.scannedRoots = map[gopathwalk.Root]bool{}
if r.moduleCacheCache == nil { if r.moduleCacheCache == nil {
r.moduleCacheCache = &dirInfoCache{ r.moduleCacheCache = &dirInfoCache{
dirs: map[string]*directoryPackageInfo{}, dirs: map[string]*directoryPackageInfo{},
listeners: map[*int]cacheListener{},
} }
} }
if r.otherCache == nil { if r.otherCache == nil {
r.otherCache = &dirInfoCache{ r.otherCache = &dirInfoCache{
dirs: map[string]*directoryPackageInfo{}, dirs: map[string]*directoryPackageInfo{},
listeners: map[*int]cacheListener{},
} }
} }
r.Initialized = true r.initialized = true
return nil return nil
} }
@ -116,27 +162,35 @@ func (r *ModuleResolver) initAllMods() error {
// Can't do anything with a module that's not downloaded. // Can't do anything with a module that's not downloaded.
continue continue
} }
r.ModsByModPath = append(r.ModsByModPath, mod) r.modsByModPath = append(r.modsByModPath, mod)
r.ModsByDir = append(r.ModsByDir, mod) r.modsByDir = append(r.modsByDir, mod)
if mod.Main { if mod.Main {
r.Main = mod r.main = mod
} }
} }
return nil return nil
} }
func (r *ModuleResolver) ClearForNewScan() { func (r *ModuleResolver) ClearForNewScan() {
<-r.scanSema
r.scannedRoots = map[gopathwalk.Root]bool{}
r.otherCache = &dirInfoCache{ r.otherCache = &dirInfoCache{
dirs: map[string]*directoryPackageInfo{}, dirs: map[string]*directoryPackageInfo{},
listeners: map[*int]cacheListener{},
} }
r.scanSema <- struct{}{}
} }
func (r *ModuleResolver) ClearForNewMod() { func (r *ModuleResolver) ClearForNewMod() {
env := r.env <-r.scanSema
*r = ModuleResolver{ *r = ModuleResolver{
env: env, env: r.env,
moduleCacheCache: r.moduleCacheCache,
otherCache: r.otherCache,
scanSema: r.scanSema,
} }
r.init() r.init()
r.scanSema <- struct{}{}
} }
// findPackage returns the module and directory that contains the package at // findPackage returns the module and directory that contains the package at
@ -144,7 +198,7 @@ func (r *ModuleResolver) ClearForNewMod() {
func (r *ModuleResolver) findPackage(importPath string) (*ModuleJSON, string) { func (r *ModuleResolver) findPackage(importPath string) (*ModuleJSON, string) {
// This can't find packages in the stdlib, but that's harmless for all // This can't find packages in the stdlib, but that's harmless for all
// the existing code paths. // the existing code paths.
for _, m := range r.ModsByModPath { for _, m := range r.modsByModPath {
if !strings.HasPrefix(importPath, m.Path) { if !strings.HasPrefix(importPath, m.Path) {
continue continue
} }
@ -211,7 +265,7 @@ func (r *ModuleResolver) cacheKeys() []string {
} }
// cachePackageName caches the package name for a dir already in the cache. // cachePackageName caches the package name for a dir already in the cache.
func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (directoryPackageInfo, error) { func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) {
if info.rootType == gopathwalk.RootModuleCache { if info.rootType == gopathwalk.RootModuleCache {
return r.moduleCacheCache.CachePackageName(info) return r.moduleCacheCache.CachePackageName(info)
} }
@ -238,7 +292,7 @@ func (r *ModuleResolver) findModuleByDir(dir string) *ModuleJSON {
// - in /vendor/ in -mod=vendor mode. // - in /vendor/ in -mod=vendor mode.
// - nested module? Dunno. // - nested module? Dunno.
// Rumor has it that replace targets cannot contain other replace targets. // Rumor has it that replace targets cannot contain other replace targets.
for _, m := range r.ModsByDir { for _, m := range r.modsByDir {
if !strings.HasPrefix(dir, m.Dir) { if !strings.HasPrefix(dir, m.Dir) {
continue continue
} }
@ -333,41 +387,49 @@ func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (
return names, nil return names, nil
} }
func (r *ModuleResolver) scan(_ references, loadNames bool, exclude []gopathwalk.RootType) ([]*pkg, error) { func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error {
if err := r.init(); err != nil { if err := r.init(); err != nil {
return nil, err return err
} }
// Walk GOROOT, GOPATH/pkg/mod, and the main module. processDir := func(info directoryPackageInfo) {
roots := []gopathwalk.Root{ // Skip this directory if we were not able to get the package information successfully.
{filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT}, if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
} return
if r.Main != nil {
roots = append(roots, gopathwalk.Root{r.Main.Dir, gopathwalk.RootCurrentModule})
}
if r.dummyVendorMod != nil {
roots = append(roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther})
} else {
roots = append(roots, gopathwalk.Root{r.moduleCacheDir, gopathwalk.RootModuleCache})
// Walk replace targets, just in case they're not in any of the above.
for _, mod := range r.ModsByModPath {
if mod.Replace != nil {
roots = append(roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther})
}
} }
pkg, err := r.canonicalize(info)
if err != nil {
return
}
if !callback.dirFound(pkg) {
return
}
pkg.packageName, err = r.cachePackageName(info)
if err != nil {
return
}
if !callback.packageNameLoaded(pkg) {
return
}
_, exports, err := r.loadExports(ctx, pkg, false)
if err != nil {
return
}
callback.exportsLoaded(pkg, exports)
} }
roots = filterRoots(roots, exclude) // Start processing everything in the cache, and listen for the new stuff
// we discover in the walk below.
stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir)
defer stop1()
stop2 := r.otherCache.ScanAndListen(ctx, processDir)
defer stop2()
var result []*pkg // We assume cached directories are fully cached, including all their
var mu sync.Mutex // children, and have not changed. We can skip them.
// We assume cached directories have not changed. We can skip them and their
// children.
skip := func(root gopathwalk.Root, dir string) bool { skip := func(root gopathwalk.Root, dir string) bool {
mu.Lock()
defer mu.Unlock()
info, ok := r.cacheLoad(dir) info, ok := r.cacheLoad(dir)
if !ok { if !ok {
return false return false
@ -379,44 +441,64 @@ func (r *ModuleResolver) scan(_ references, loadNames bool, exclude []gopathwalk
return packageScanned return packageScanned
} }
// Add anything new to the cache. We'll process everything in it below. // Add anything new to the cache, and process it if we're still listening.
add := func(root gopathwalk.Root, dir string) { add := func(root gopathwalk.Root, dir string) {
mu.Lock()
defer mu.Unlock()
r.cacheStore(r.scanDirForPackage(root, dir)) r.cacheStore(r.scanDirForPackage(root, dir))
} }
gopathwalk.WalkSkip(roots, add, skip, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: true}) // r.roots and the callback are not necessarily safe to use in the
// goroutine below. Process them eagerly.
// Everything we already had, and everything new, is now in the cache. roots := filterRoots(r.roots, callback.rootFound)
for _, dir := range r.cacheKeys() { // We can't cancel walks, because we need them to finish to have a usable
info, ok := r.cacheLoad(dir) // cache. Instead, run them in a separate goroutine and detach.
if !ok { scanDone := make(chan struct{})
continue go func() {
select {
case <-ctx.Done():
return
case <-r.scanSema:
} }
defer func() { r.scanSema <- struct{}{} }()
// We have the lock on r.scannedRoots, and no other scans can run.
for _, root := range roots {
if ctx.Err() != nil {
return
}
// Skip this directory if we were not able to get the package information successfully. if r.scannedRoots[root] {
if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
continue
}
// If we want package names, make sure the cache has them.
if loadNames {
var err error
if info, err = r.cachePackageName(info); err != nil {
continue continue
} }
gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Debug: r.env.Debug, ModulesEnabled: true})
r.scannedRoots[root] = true
} }
close(scanDone)
res, err := r.canonicalize(info) }()
if err != nil { select {
continue case <-ctx.Done():
} case <-scanDone:
result = append(result, res)
} }
return nil
}
return result, nil func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) int {
if _, ok := stdlib[path]; ok {
return MaxRelevance
}
mod, _ := r.findPackage(path)
return modRelevance(mod)
}
func modRelevance(mod *ModuleJSON) int {
switch {
case mod == nil: // out of scope
return MaxRelevance - 4
case mod.Indirect:
return MaxRelevance - 3
case !mod.Main:
return MaxRelevance - 2
default:
return MaxRelevance - 1 // main module ties with stdlib
}
} }
// canonicalize gets the result of canonicalizing the packages using the results // canonicalize gets the result of canonicalizing the packages using the results
@ -428,15 +510,14 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) {
importPathShort: info.nonCanonicalImportPath, importPathShort: info.nonCanonicalImportPath,
dir: info.dir, dir: info.dir,
packageName: path.Base(info.nonCanonicalImportPath), packageName: path.Base(info.nonCanonicalImportPath),
relevance: 0, relevance: MaxRelevance,
}, nil }, nil
} }
importPath := info.nonCanonicalImportPath importPath := info.nonCanonicalImportPath
relevance := 2 mod := r.findModuleByDir(info.dir)
// Check if the directory is underneath a module that's in scope. // Check if the directory is underneath a module that's in scope.
if mod := r.findModuleByDir(info.dir); mod != nil { if mod != nil {
relevance = 1
// It is. If dir is the target of a replace directive, // It is. If dir is the target of a replace directive,
// our guessed import path is wrong. Use the real one. // our guessed import path is wrong. Use the real one.
if mod.Dir == info.dir { if mod.Dir == info.dir {
@ -445,15 +526,16 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) {
dirInMod := info.dir[len(mod.Dir)+len("/"):] dirInMod := info.dir[len(mod.Dir)+len("/"):]
importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod)) importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod))
} }
} else if info.needsReplace { } else if !strings.HasPrefix(importPath, info.moduleName) {
// The module's name doesn't match the package's import path. It
// probably needs a replace directive we don't have.
return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir) return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir)
} }
res := &pkg{ res := &pkg{
importPathShort: importPath, importPathShort: importPath,
dir: info.dir, dir: info.dir,
packageName: info.packageName, // may not be populated if the caller didn't ask for it relevance: modRelevance(mod),
relevance: relevance,
} }
// We may have discovered a package that has a different version // We may have discovered a package that has a different version
// in scope already. Canonicalize to that one if possible. // in scope already. Canonicalize to that one if possible.
@ -463,14 +545,14 @@ func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) {
return res, nil return res, nil
} }
func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg) (string, []string, error) { func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) {
if err := r.init(); err != nil { if err := r.init(); err != nil {
return "", nil, err return "", nil, err
} }
if info, ok := r.cacheLoad(pkg.dir); ok { if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest {
return r.cacheExports(ctx, r.env, info) return r.cacheExports(ctx, r.env, info)
} }
return loadExportsFromFiles(ctx, r.env, pkg.dir) return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest)
} }
func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo {
@ -488,7 +570,7 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir
} }
switch root.Type { switch root.Type {
case gopathwalk.RootCurrentModule: case gopathwalk.RootCurrentModule:
importPath = path.Join(r.Main.Path, filepath.ToSlash(subdir)) importPath = path.Join(r.main.Path, filepath.ToSlash(subdir))
case gopathwalk.RootModuleCache: case gopathwalk.RootModuleCache:
matches := modCacheRegexp.FindStringSubmatch(subdir) matches := modCacheRegexp.FindStringSubmatch(subdir)
if len(matches) == 0 { if len(matches) == 0 {
@ -516,7 +598,6 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir
dir: dir, dir: dir,
rootType: root.Type, rootType: root.Type,
nonCanonicalImportPath: importPath, nonCanonicalImportPath: importPath,
needsReplace: false,
moduleDir: modDir, moduleDir: modDir,
moduleName: modName, moduleName: modName,
} }
@ -524,14 +605,6 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir
// stdlib packages are always in scope, despite the confusing go.mod // stdlib packages are always in scope, despite the confusing go.mod
return result return result
} }
// Check that this package is not obviously impossible to import.
if !strings.HasPrefix(importPath, modName) {
// The module's declared path does not match
// its expected path. It probably needs a
// replace directive we don't have.
result.needsReplace = true
}
return result return result
} }

View File

@ -49,10 +49,6 @@ type directoryPackageInfo struct {
// nonCanonicalImportPath is the package's expected import path. It may // nonCanonicalImportPath is the package's expected import path. It may
// not actually be importable at that path. // not actually be importable at that path.
nonCanonicalImportPath string nonCanonicalImportPath string
// needsReplace is true if the nonCanonicalImportPath does not match the
// module's declared path, making it impossible to import without a
// replace directive.
needsReplace bool
// Module-related information. // Module-related information.
moduleDir string // The directory that is the module root of this dir. moduleDir string // The directory that is the module root of this dir.
@ -97,15 +93,85 @@ func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (
type dirInfoCache struct { type dirInfoCache struct {
mu sync.Mutex mu sync.Mutex
// dirs stores information about packages in directories, keyed by absolute path. // dirs stores information about packages in directories, keyed by absolute path.
dirs map[string]*directoryPackageInfo dirs map[string]*directoryPackageInfo
listeners map[*int]cacheListener
}
type cacheListener func(directoryPackageInfo)
// ScanAndListen calls listener on all the items in the cache, and on anything
// newly added. The returned stop function waits for all in-flight callbacks to
// finish and blocks new ones.
func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() {
ctx, cancel := context.WithCancel(ctx)
// Flushing out all the callbacks is tricky without knowing how many there
// are going to be. Setting an arbitrary limit makes it much easier.
const maxInFlight = 10
sema := make(chan struct{}, maxInFlight)
for i := 0; i < maxInFlight; i++ {
sema <- struct{}{}
}
cookie := new(int) // A unique ID we can use for the listener.
// We can't hold mu while calling the listener.
d.mu.Lock()
var keys []string
for key := range d.dirs {
keys = append(keys, key)
}
d.listeners[cookie] = func(info directoryPackageInfo) {
select {
case <-ctx.Done():
return
case <-sema:
}
listener(info)
sema <- struct{}{}
}
d.mu.Unlock()
// Process the pre-existing keys.
for _, k := range keys {
select {
case <-ctx.Done():
cancel()
return func() {}
default:
}
if v, ok := d.Load(k); ok {
listener(v)
}
}
return func() {
cancel()
d.mu.Lock()
delete(d.listeners, cookie)
d.mu.Unlock()
for i := 0; i < maxInFlight; i++ {
<-sema
}
}
} }
// Store stores the package info for dir. // Store stores the package info for dir.
func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) { func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) {
d.mu.Lock() d.mu.Lock()
defer d.mu.Unlock() _, old := d.dirs[dir]
stored := info // defensive copy d.dirs[dir] = &info
d.dirs[dir] = &stored var listeners []cacheListener
for _, l := range d.listeners {
listeners = append(listeners, l)
}
d.mu.Unlock()
if !old {
for _, l := range listeners {
l(info)
}
}
} }
// Load returns a copy of the directoryPackageInfo for absolute directory dir. // Load returns a copy of the directoryPackageInfo for absolute directory dir.
@ -129,17 +195,17 @@ func (d *dirInfoCache) Keys() (keys []string) {
return keys return keys
} }
func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (directoryPackageInfo, error) { func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) {
if loaded, err := info.reachedStatus(nameLoaded); loaded { if loaded, err := info.reachedStatus(nameLoaded); loaded {
return info, err return info.packageName, err
} }
if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil {
return info, fmt.Errorf("cannot read package name, scan error: %v", err) return "", fmt.Errorf("cannot read package name, scan error: %v", err)
} }
info.packageName, info.err = packageDirToName(info.dir) info.packageName, info.err = packageDirToName(info.dir)
info.status = nameLoaded info.status = nameLoaded
d.Store(info.dir, info) d.Store(info.dir, info)
return info, info.err return info.packageName, info.err
} }
func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) { func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) {
@ -149,8 +215,8 @@ func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info d
if reached, err := info.reachedStatus(nameLoaded); reached && err != nil { if reached, err := info.reachedStatus(nameLoaded); reached && err != nil {
return "", nil, err return "", nil, err
} }
info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir) info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false)
if info.err == context.Canceled { if info.err == context.Canceled || info.err == context.DeadlineExceeded {
return info.packageName, info.exports, info.err return info.packageName, info.exports, info.err
} }
// The cache structure wants things to proceed linearly. We can skip a // The cache structure wants things to proceed linearly. We can skip a

View File

@ -0,0 +1,4 @@
// Package packagesinternal exposes internal-only fields from go/packages.
package packagesinternal
var GetForTest = func(p interface{}) string { return "" }

View File

@ -1,100 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package span
import (
"strconv"
"strings"
"unicode/utf8"
)
// Parse returns the location represented by the input.
// All inputs are valid locations, as they can always be a pure filename.
// The returned span will be normalized, and thus if printed may produce a
// different string.
func Parse(input string) Span {
// :0:0#0-0:0#0
valid := input
var hold, offset int
hadCol := false
suf := rstripSuffix(input)
if suf.sep == "#" {
offset = suf.num
suf = rstripSuffix(suf.remains)
}
if suf.sep == ":" {
valid = suf.remains
hold = suf.num
hadCol = true
suf = rstripSuffix(suf.remains)
}
switch {
case suf.sep == ":":
return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{})
case suf.sep == "-":
// we have a span, fall out of the case to continue
default:
// separator not valid, rewind to either the : or the start
return New(NewURI(valid), NewPoint(hold, 0, offset), Point{})
}
// only the span form can get here
// at this point we still don't know what the numbers we have mean
// if have not yet seen a : then we might have either a line or a column depending
// on whether start has a column or not
// we build an end point and will fix it later if needed
end := NewPoint(suf.num, hold, offset)
hold, offset = 0, 0
suf = rstripSuffix(suf.remains)
if suf.sep == "#" {
offset = suf.num
suf = rstripSuffix(suf.remains)
}
if suf.sep != ":" {
// turns out we don't have a span after all, rewind
return New(NewURI(valid), end, Point{})
}
valid = suf.remains
hold = suf.num
suf = rstripSuffix(suf.remains)
if suf.sep != ":" {
// line#offset only
return New(NewURI(valid), NewPoint(hold, 0, offset), end)
}
// we have a column, so if end only had one number, it is also the column
if !hadCol {
end = NewPoint(suf.num, end.v.Line, end.v.Offset)
}
return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end)
}
type suffix struct {
remains string
sep string
num int
}
func rstripSuffix(input string) suffix {
if len(input) == 0 {
return suffix{"", "", -1}
}
remains := input
num := -1
// first see if we have a number at the end
last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' })
if last >= 0 && last < len(remains)-1 {
number, err := strconv.ParseInt(remains[last+1:], 10, 64)
if err == nil {
num = int(number)
remains = remains[:last+1]
}
}
// now see if we have a trailing separator
r, w := utf8.DecodeLastRuneInString(remains)
if r != ':' && r != '#' && r == '#' {
return suffix{input, "", -1}
}
remains = remains[:len(remains)-w]
return suffix{remains, string(r), num}
}

View File

@ -1,285 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package span contains support for representing with positions and ranges in
// text files.
package span
import (
"encoding/json"
"fmt"
"path"
)
// Span represents a source code range in standardized form.
type Span struct {
v span
}
// Point represents a single point within a file.
// In general this should only be used as part of a Span, as on its own it
// does not carry enough information.
type Point struct {
v point
}
type span struct {
URI URI `json:"uri"`
Start point `json:"start"`
End point `json:"end"`
}
type point struct {
Line int `json:"line"`
Column int `json:"column"`
Offset int `json:"offset"`
}
// Invalid is a span that reports false from IsValid
var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}}
var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}}
// Converter is the interface to an object that can convert between line:column
// and offset forms for a single file.
type Converter interface {
//ToPosition converts from an offset to a line:column pair.
ToPosition(offset int) (int, int, error)
//ToOffset converts from a line:column pair to an offset.
ToOffset(line, col int) (int, error)
}
func New(uri URI, start Point, end Point) Span {
s := Span{v: span{URI: uri, Start: start.v, End: end.v}}
s.v.clean()
return s
}
func NewPoint(line, col, offset int) Point {
p := Point{v: point{Line: line, Column: col, Offset: offset}}
p.v.clean()
return p
}
func Compare(a, b Span) int {
if r := CompareURI(a.URI(), b.URI()); r != 0 {
return r
}
if r := comparePoint(a.v.Start, b.v.Start); r != 0 {
return r
}
return comparePoint(a.v.End, b.v.End)
}
func ComparePoint(a, b Point) int {
return comparePoint(a.v, b.v)
}
func comparePoint(a, b point) int {
if !a.hasPosition() {
if a.Offset < b.Offset {
return -1
}
if a.Offset > b.Offset {
return 1
}
return 0
}
if a.Line < b.Line {
return -1
}
if a.Line > b.Line {
return 1
}
if a.Column < b.Column {
return -1
}
if a.Column > b.Column {
return 1
}
return 0
}
func (s Span) HasPosition() bool { return s.v.Start.hasPosition() }
func (s Span) HasOffset() bool { return s.v.Start.hasOffset() }
func (s Span) IsValid() bool { return s.v.Start.isValid() }
func (s Span) IsPoint() bool { return s.v.Start == s.v.End }
func (s Span) URI() URI { return s.v.URI }
func (s Span) Start() Point { return Point{s.v.Start} }
func (s Span) End() Point { return Point{s.v.End} }
func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) }
func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) }
func (p Point) HasPosition() bool { return p.v.hasPosition() }
func (p Point) HasOffset() bool { return p.v.hasOffset() }
func (p Point) IsValid() bool { return p.v.isValid() }
func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
func (p Point) Line() int {
if !p.v.hasPosition() {
panic(fmt.Errorf("position not set in %v", p.v))
}
return p.v.Line
}
func (p Point) Column() int {
if !p.v.hasPosition() {
panic(fmt.Errorf("position not set in %v", p.v))
}
return p.v.Column
}
func (p Point) Offset() int {
if !p.v.hasOffset() {
panic(fmt.Errorf("offset not set in %v", p.v))
}
return p.v.Offset
}
func (p point) hasPosition() bool { return p.Line > 0 }
func (p point) hasOffset() bool { return p.Offset >= 0 }
func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() }
func (p point) isZero() bool {
return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0)
}
func (s *span) clean() {
//this presumes the points are already clean
if !s.End.isValid() || (s.End == point{}) {
s.End = s.Start
}
}
func (p *point) clean() {
if p.Line < 0 {
p.Line = 0
}
if p.Column <= 0 {
if p.Line > 0 {
p.Column = 1
} else {
p.Column = 0
}
}
if p.Offset == 0 && (p.Line > 1 || p.Column > 1) {
p.Offset = -1
}
}
// Format implements fmt.Formatter to print the Location in a standard form.
// The format produced is one that can be read back in using Parse.
func (s Span) Format(f fmt.State, c rune) {
fullForm := f.Flag('+')
preferOffset := f.Flag('#')
// we should always have a uri, simplify if it is file format
//TODO: make sure the end of the uri is unambiguous
uri := string(s.v.URI)
if c == 'f' {
uri = path.Base(uri)
} else if !fullForm {
uri = s.v.URI.Filename()
}
fmt.Fprint(f, uri)
if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) {
return
}
// see which bits of start to write
printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition())
printLine := s.HasPosition() && (fullForm || !printOffset)
printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1))
fmt.Fprint(f, ":")
if printLine {
fmt.Fprintf(f, "%d", s.v.Start.Line)
}
if printColumn {
fmt.Fprintf(f, ":%d", s.v.Start.Column)
}
if printOffset {
fmt.Fprintf(f, "#%d", s.v.Start.Offset)
}
// start is written, do we need end?
if s.IsPoint() {
return
}
// we don't print the line if it did not change
printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line)
fmt.Fprint(f, "-")
if printLine {
fmt.Fprintf(f, "%d", s.v.End.Line)
}
if printColumn {
if printLine {
fmt.Fprint(f, ":")
}
fmt.Fprintf(f, "%d", s.v.End.Column)
}
if printOffset {
fmt.Fprintf(f, "#%d", s.v.End.Offset)
}
}
func (s Span) WithPosition(c Converter) (Span, error) {
if err := s.update(c, true, false); err != nil {
return Span{}, err
}
return s, nil
}
func (s Span) WithOffset(c Converter) (Span, error) {
if err := s.update(c, false, true); err != nil {
return Span{}, err
}
return s, nil
}
func (s Span) WithAll(c Converter) (Span, error) {
if err := s.update(c, true, true); err != nil {
return Span{}, err
}
return s, nil
}
func (s *Span) update(c Converter, withPos, withOffset bool) error {
if !s.IsValid() {
return fmt.Errorf("cannot add information to an invalid span")
}
if withPos && !s.HasPosition() {
if err := s.v.Start.updatePosition(c); err != nil {
return err
}
if s.v.End.Offset == s.v.Start.Offset {
s.v.End = s.v.Start
} else if err := s.v.End.updatePosition(c); err != nil {
return err
}
}
if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) {
if err := s.v.Start.updateOffset(c); err != nil {
return err
}
if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column {
s.v.End.Offset = s.v.Start.Offset
} else if err := s.v.End.updateOffset(c); err != nil {
return err
}
}
return nil
}
func (p *point) updatePosition(c Converter) error {
line, col, err := c.ToPosition(p.Offset)
if err != nil {
return err
}
p.Line = line
p.Column = col
return nil
}
func (p *point) updateOffset(c Converter) error {
offset, err := c.ToOffset(p.Line, p.Column)
if err != nil {
return err
}
p.Offset = offset
return nil
}

View File

@ -1,179 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package span
import (
"fmt"
"go/token"
)
// Range represents a source code range in token.Pos form.
// It also carries the FileSet that produced the positions, so that it is
// self contained.
type Range struct {
FileSet *token.FileSet
Start token.Pos
End token.Pos
Converter Converter
}
// TokenConverter is a Converter backed by a token file set and file.
// It uses the file set methods to work out the conversions, which
// makes it fast and does not require the file contents.
type TokenConverter struct {
fset *token.FileSet
file *token.File
}
// NewRange creates a new Range from a FileSet and two positions.
// To represent a point pass a 0 as the end pos.
func NewRange(fset *token.FileSet, start, end token.Pos) Range {
return Range{
FileSet: fset,
Start: start,
End: end,
}
}
// NewTokenConverter returns an implementation of Converter backed by a
// token.File.
func NewTokenConverter(fset *token.FileSet, f *token.File) *TokenConverter {
return &TokenConverter{fset: fset, file: f}
}
// NewContentConverter returns an implementation of Converter for the
// given file content.
func NewContentConverter(filename string, content []byte) *TokenConverter {
fset := token.NewFileSet()
f := fset.AddFile(filename, -1, len(content))
f.SetLinesForContent(content)
return &TokenConverter{fset: fset, file: f}
}
// IsPoint returns true if the range represents a single point.
func (r Range) IsPoint() bool {
return r.Start == r.End
}
// Span converts a Range to a Span that represents the Range.
// It will fill in all the members of the Span, calculating the line and column
// information.
func (r Range) Span() (Span, error) {
f := r.FileSet.File(r.Start)
if f == nil {
return Span{}, fmt.Errorf("file not found in FileSet")
}
var s Span
var err error
var startFilename string
startFilename, s.v.Start.Line, s.v.Start.Column, err = position(f, r.Start)
if err != nil {
return Span{}, err
}
s.v.URI = FileURI(startFilename)
if r.End.IsValid() {
var endFilename string
endFilename, s.v.End.Line, s.v.End.Column, err = position(f, r.End)
if err != nil {
return Span{}, err
}
// In the presence of line directives, a single File can have sections from
// multiple file names.
if endFilename != startFilename {
return Span{}, fmt.Errorf("span begins in file %q but ends in %q", startFilename, endFilename)
}
}
s.v.Start.clean()
s.v.End.clean()
s.v.clean()
if r.Converter != nil {
return s.WithOffset(r.Converter)
}
if startFilename != f.Name() {
return Span{}, fmt.Errorf("must supply Converter for file %q containing lines from %q", f.Name(), startFilename)
}
return s.WithOffset(NewTokenConverter(r.FileSet, f))
}
func position(f *token.File, pos token.Pos) (string, int, int, error) {
off, err := offset(f, pos)
if err != nil {
return "", 0, 0, err
}
return positionFromOffset(f, off)
}
func positionFromOffset(f *token.File, offset int) (string, int, int, error) {
if offset > f.Size() {
return "", 0, 0, fmt.Errorf("offset %v is past the end of the file %v", offset, f.Size())
}
pos := f.Pos(offset)
p := f.Position(pos)
if offset == f.Size() {
return p.Filename, p.Line + 1, 1, nil
}
return p.Filename, p.Line, p.Column, nil
}
// offset is a copy of the Offset function in go/token, but with the adjustment
// that it does not panic on invalid positions.
func offset(f *token.File, pos token.Pos) (int, error) {
if int(pos) < f.Base() || int(pos) > f.Base()+f.Size() {
return 0, fmt.Errorf("invalid pos")
}
return int(pos) - f.Base(), nil
}
// Range converts a Span to a Range that represents the Span for the supplied
// File.
func (s Span) Range(converter *TokenConverter) (Range, error) {
s, err := s.WithOffset(converter)
if err != nil {
return Range{}, err
}
// go/token will panic if the offset is larger than the file's size,
// so check here to avoid panicking.
if s.Start().Offset() > converter.file.Size() {
return Range{}, fmt.Errorf("start offset %v is past the end of the file %v", s.Start(), converter.file.Size())
}
if s.End().Offset() > converter.file.Size() {
return Range{}, fmt.Errorf("end offset %v is past the end of the file %v", s.End(), converter.file.Size())
}
return Range{
FileSet: converter.fset,
Start: converter.file.Pos(s.Start().Offset()),
End: converter.file.Pos(s.End().Offset()),
Converter: converter,
}, nil
}
func (l *TokenConverter) ToPosition(offset int) (int, int, error) {
_, line, col, err := positionFromOffset(l.file, offset)
return line, col, err
}
func (l *TokenConverter) ToOffset(line, col int) (int, error) {
if line < 0 {
return -1, fmt.Errorf("line is not valid")
}
lineMax := l.file.LineCount() + 1
if line > lineMax {
return -1, fmt.Errorf("line is beyond end of file %v", lineMax)
} else if line == lineMax {
if col > 1 {
return -1, fmt.Errorf("column is beyond end of file")
}
// at the end of the file, allowing for a trailing eol
return l.file.Size(), nil
}
pos := lineStart(l.file, line)
if !pos.IsValid() {
return -1, fmt.Errorf("line is not in file")
}
// we assume that column is in bytes here, and that the first byte of a
// line is at column 1
pos += token.Pos(col - 1)
return offset(l.file, pos)
}

View File

@ -1,39 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.12
package span
import (
"go/token"
)
// lineStart is the pre-Go 1.12 version of (*token.File).LineStart. For Go
// versions <= 1.11, we borrow logic from the analysisutil package.
// TODO(rstambler): Delete this file when we no longer support Go 1.11.
func lineStart(f *token.File, line int) token.Pos {
// Use binary search to find the start offset of this line.
min := 0 // inclusive
max := f.Size() // exclusive
for {
offset := (min + max) / 2
pos := f.Pos(offset)
posn := f.Position(pos)
if posn.Line == line {
return pos - (token.Pos(posn.Column) - 1)
}
if min+1 >= max {
return token.NoPos
}
if posn.Line < line {
min = offset
} else {
max = offset
}
}
}

View File

@ -1,16 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.12
package span
import (
"go/token"
)
// TODO(rstambler): Delete this file when we no longer support Go 1.11.
func lineStart(f *token.File, line int) token.Pos {
return f.LineStart(line)
}

View File

@ -1,152 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package span
import (
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"unicode"
)
const fileScheme = "file"
// URI represents the full URI for a file.
type URI string
// Filename returns the file path for the given URI.
// It is an error to call this on a URI that is not a valid filename.
func (uri URI) Filename() string {
filename, err := filename(uri)
if err != nil {
panic(err)
}
return filepath.FromSlash(filename)
}
func filename(uri URI) (string, error) {
if uri == "" {
return "", nil
}
u, err := url.ParseRequestURI(string(uri))
if err != nil {
return "", err
}
if u.Scheme != fileScheme {
return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
}
if isWindowsDriveURI(u.Path) {
u.Path = u.Path[1:]
}
return u.Path, nil
}
// NewURI returns a span URI for the string.
// It will attempt to detect if the string is a file path or uri.
func NewURI(s string) URI {
if u, err := url.PathUnescape(s); err == nil {
s = u
}
if strings.HasPrefix(s, fileScheme+"://") {
return URI(s)
}
return FileURI(s)
}
func CompareURI(a, b URI) int {
if equalURI(a, b) {
return 0
}
if a < b {
return -1
}
return 1
}
func equalURI(a, b URI) bool {
if a == b {
return true
}
// If we have the same URI basename, we may still have the same file URIs.
if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
return false
}
fa, err := filename(a)
if err != nil {
return false
}
fb, err := filename(b)
if err != nil {
return false
}
// Stat the files to check if they are equal.
infoa, err := os.Stat(filepath.FromSlash(fa))
if err != nil {
return false
}
infob, err := os.Stat(filepath.FromSlash(fb))
if err != nil {
return false
}
return os.SameFile(infoa, infob)
}
// FileURI returns a span URI for the supplied file path.
// It will always have the file scheme.
func FileURI(path string) URI {
if path == "" {
return ""
}
// Handle standard library paths that contain the literal "$GOROOT".
// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
const prefix = "$GOROOT"
if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
suffix := path[len(prefix):]
path = runtime.GOROOT() + suffix
}
if !isWindowsDrivePath(path) {
if abs, err := filepath.Abs(path); err == nil {
path = abs
}
}
// Check the file path again, in case it became absolute.
if isWindowsDrivePath(path) {
path = "/" + path
}
path = filepath.ToSlash(path)
u := url.URL{
Scheme: fileScheme,
Path: path,
}
uri := u.String()
if unescaped, err := url.PathUnescape(uri); err == nil {
uri = unescaped
}
return URI(uri)
}
// isWindowsDrivePath returns true if the file path is of the form used by
// Windows. We check if the path begins with a drive letter, followed by a ":".
func isWindowsDrivePath(path string) bool {
if len(path) < 4 {
return false
}
return unicode.IsLetter(rune(path[0])) && path[1] == ':'
}
// isWindowsDriveURI returns true if the file URI is of the format used by
// Windows URIs. The url.Parse package does not specially handle Windows paths
// (see https://golang.org/issue/6027). We check if the URI path has
// a drive prefix (e.g. "/C:"). If so, we trim the leading "/".
func isWindowsDriveURI(uri string) bool {
if len(uri) < 4 {
return false
}
return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
}

View File

@ -1,94 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package span
import (
"fmt"
"unicode/utf16"
"unicode/utf8"
)
// ToUTF16Column calculates the utf16 column expressed by the point given the
// supplied file contents.
// This is used to convert from the native (always in bytes) column
// representation and the utf16 counts used by some editors.
func ToUTF16Column(p Point, content []byte) (int, error) {
if content == nil {
return -1, fmt.Errorf("ToUTF16Column: missing content")
}
if !p.HasPosition() {
return -1, fmt.Errorf("ToUTF16Column: point is missing position")
}
if !p.HasOffset() {
return -1, fmt.Errorf("ToUTF16Column: point is missing offset")
}
offset := p.Offset() // 0-based
colZero := p.Column() - 1 // 0-based
if colZero == 0 {
// 0-based column 0, so it must be chr 1
return 1, nil
} else if colZero < 0 {
return -1, fmt.Errorf("ToUTF16Column: column is invalid (%v)", colZero)
}
// work out the offset at the start of the line using the column
lineOffset := offset - colZero
if lineOffset < 0 || offset > len(content) {
return -1, fmt.Errorf("ToUTF16Column: offsets %v-%v outside file contents (%v)", lineOffset, offset, len(content))
}
// Use the offset to pick out the line start.
// This cannot panic: offset > len(content) and lineOffset < offset.
start := content[lineOffset:]
// Now, truncate down to the supplied column.
start = start[:colZero]
// and count the number of utf16 characters
// in theory we could do this by hand more efficiently...
return len(utf16.Encode([]rune(string(start)))) + 1, nil
}
// FromUTF16Column advances the point by the utf16 character offset given the
// supplied line contents.
// This is used to convert from the utf16 counts used by some editors to the
// native (always in bytes) column representation.
func FromUTF16Column(p Point, chr int, content []byte) (Point, error) {
if !p.HasOffset() {
return Point{}, fmt.Errorf("FromUTF16Column: point is missing offset")
}
// if chr is 1 then no adjustment needed
if chr <= 1 {
return p, nil
}
if p.Offset() >= len(content) {
return p, fmt.Errorf("FromUTF16Column: offset (%v) greater than length of content (%v)", p.Offset(), len(content))
}
remains := content[p.Offset():]
// scan forward the specified number of characters
for count := 1; count < chr; count++ {
if len(remains) <= 0 {
return Point{}, fmt.Errorf("FromUTF16Column: chr goes beyond the content")
}
r, w := utf8.DecodeRune(remains)
if r == '\n' {
// Per the LSP spec:
//
// > If the character value is greater than the line length it
// > defaults back to the line length.
break
}
remains = remains[w:]
if r >= 0x10000 {
// a two point rune
count++
// if we finished in a two point rune, do not advance past the first
if count >= chr {
break
}
}
p.v.Column += w
p.v.Offset += w
}
return p, nil
}

View File

@ -13,3 +13,5 @@ require (
google.golang.org/appengine v1.3.0 // indirect google.golang.org/appengine v1.3.0 // indirect
gopkg.in/yaml.v2 v2.2.1 gopkg.in/yaml.v2 v2.2.1
) )
go 1.13

47
vendor/gopkg.in/yaml.v2/scannerc.go generated vendored
View File

@ -626,32 +626,18 @@ func trace(args ...interface{}) func() {
func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool {
// While we need more tokens to fetch, do it. // While we need more tokens to fetch, do it.
for { for {
// Check if we really need to fetch more tokens. if parser.tokens_head != len(parser.tokens) {
need_more_tokens := false // If queue is non-empty, check if any potential simple key may
// occupy the head position.
if parser.tokens_head == len(parser.tokens) { head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed]
// Queue is empty. if !ok {
need_more_tokens = true break
} else { } else if valid, ok := yaml_simple_key_is_valid(parser, &parser.simple_keys[head_tok_idx]); !ok {
// Check if any potential simple key may occupy the head position. return false
for i := len(parser.simple_keys) - 1; i >= 0; i-- { } else if !valid {
simple_key := &parser.simple_keys[i] break
if simple_key.token_number < parser.tokens_parsed {
break
}
if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok {
return false
} else if valid && simple_key.token_number == parser.tokens_parsed {
need_more_tokens = true
break
}
} }
} }
// We are finished.
if !need_more_tokens {
break
}
// Fetch the next token. // Fetch the next token.
if !yaml_parser_fetch_next_token(parser) { if !yaml_parser_fetch_next_token(parser) {
return false return false
@ -883,6 +869,7 @@ func yaml_parser_save_simple_key(parser *yaml_parser_t) bool {
return false return false
} }
parser.simple_keys[len(parser.simple_keys)-1] = simple_key parser.simple_keys[len(parser.simple_keys)-1] = simple_key
parser.simple_keys_by_tok[simple_key.token_number] = len(parser.simple_keys) - 1
} }
return true return true
} }
@ -897,9 +884,10 @@ func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool {
"while scanning a simple key", parser.simple_keys[i].mark, "while scanning a simple key", parser.simple_keys[i].mark,
"could not find expected ':'") "could not find expected ':'")
} }
// Remove the key from the stack.
parser.simple_keys[i].possible = false
delete(parser.simple_keys_by_tok, parser.simple_keys[i].token_number)
} }
// Remove the key from the stack.
parser.simple_keys[i].possible = false
return true return true
} }
@ -930,7 +918,9 @@ func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool {
func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool {
if parser.flow_level > 0 { if parser.flow_level > 0 {
parser.flow_level-- parser.flow_level--
parser.simple_keys = parser.simple_keys[:len(parser.simple_keys)-1] last := len(parser.simple_keys) - 1
delete(parser.simple_keys_by_tok, parser.simple_keys[last].token_number)
parser.simple_keys = parser.simple_keys[:last]
} }
return true return true
} }
@ -1007,6 +997,8 @@ func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool {
// Initialize the simple key stack. // Initialize the simple key stack.
parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{})
parser.simple_keys_by_tok = make(map[int]int)
// A simple key is allowed at the beginning of the stream. // A simple key is allowed at the beginning of the stream.
parser.simple_key_allowed = true parser.simple_key_allowed = true
@ -1310,6 +1302,7 @@ func yaml_parser_fetch_value(parser *yaml_parser_t) bool {
// Remove the simple key. // Remove the simple key.
simple_key.possible = false simple_key.possible = false
delete(parser.simple_keys_by_tok, simple_key.token_number)
// A simple key cannot follow another simple key. // A simple key cannot follow another simple key.
parser.simple_key_allowed = false parser.simple_key_allowed = false

1
vendor/gopkg.in/yaml.v2/yamlh.go generated vendored
View File

@ -579,6 +579,7 @@ type yaml_parser_t struct {
simple_key_allowed bool // May a simple key occur at the current position? simple_key_allowed bool // May a simple key occur at the current position?
simple_keys []yaml_simple_key_t // The stack of simple keys. simple_keys []yaml_simple_key_t // The stack of simple keys.
simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number
// Parser stuff // Parser stuff

12
vendor/modules.txt vendored
View File

@ -50,9 +50,9 @@ github.com/ghodss/yaml
github.com/go-openapi/jsonpointer github.com/go-openapi/jsonpointer
# github.com/go-openapi/jsonreference v0.19.3 # github.com/go-openapi/jsonreference v0.19.3
github.com/go-openapi/jsonreference github.com/go-openapi/jsonreference
# github.com/go-openapi/spec v0.19.4 # github.com/go-openapi/spec v0.19.5
github.com/go-openapi/spec github.com/go-openapi/spec
# github.com/go-openapi/swag v0.19.5 # github.com/go-openapi/swag v0.19.7
github.com/go-openapi/swag github.com/go-openapi/swag
# github.com/go-redis/redis v6.15.2+incompatible # github.com/go-redis/redis v6.15.2+incompatible
github.com/go-redis/redis github.com/go-redis/redis
@ -184,7 +184,7 @@ github.com/spf13/pflag
github.com/spf13/viper github.com/spf13/viper
# github.com/stretchr/testify v1.4.0 # github.com/stretchr/testify v1.4.0
github.com/stretchr/testify/assert github.com/stretchr/testify/assert
# github.com/swaggo/swag v1.6.3 # github.com/swaggo/swag v1.6.5
github.com/swaggo/swag github.com/swaggo/swag
github.com/swaggo/swag/cmd/swag github.com/swaggo/swag/cmd/swag
github.com/swaggo/swag/gen github.com/swaggo/swag/gen
@ -217,7 +217,7 @@ golang.org/x/text/transform
golang.org/x/text/unicode/bidi golang.org/x/text/unicode/bidi
golang.org/x/text/unicode/norm golang.org/x/text/unicode/norm
golang.org/x/text/width golang.org/x/text/width
# golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d # golang.org/x/tools v0.0.0-20200125223703-d33eef8e6825
golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/ast/astutil
golang.org/x/tools/go/ast/inspector golang.org/x/tools/go/ast/inspector
golang.org/x/tools/go/buildutil golang.org/x/tools/go/buildutil
@ -233,8 +233,8 @@ golang.org/x/tools/internal/fastwalk
golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/gopathwalk
golang.org/x/tools/internal/imports golang.org/x/tools/internal/imports
golang.org/x/tools/internal/module golang.org/x/tools/internal/module
golang.org/x/tools/internal/packagesinternal
golang.org/x/tools/internal/semver golang.org/x/tools/internal/semver
golang.org/x/tools/internal/span
# google.golang.org/appengine v1.5.0 # google.golang.org/appengine v1.5.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
@ -245,7 +245,7 @@ gopkg.in/d4l3k/messagediff.v1
gopkg.in/gomail.v2 gopkg.in/gomail.v2
# gopkg.in/testfixtures.v2 v2.5.3 # gopkg.in/testfixtures.v2 v2.5.3
gopkg.in/testfixtures.v2 gopkg.in/testfixtures.v2
# gopkg.in/yaml.v2 v2.2.7 # gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v2 gopkg.in/yaml.v2
# honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a # honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
honnef.co/go/tools/arg honnef.co/go/tools/arg