diff --git a/.gitignore b/.gitignore index d25c552aa6..94c87c1d15 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ localtests *.iml .teamcity/target .vscode +.DS_Store \ No newline at end of file diff --git a/_fixtures/goptest/a.gop b/_fixtures/goptest/a.gop new file mode 100644 index 0000000000..abb3b861d3 --- /dev/null +++ b/_fixtures/goptest/a.gop @@ -0,0 +1,9 @@ +package main + +func a() { + println("a") +} + +func Ab() { + println("ab") +} diff --git a/_fixtures/goptest/b.gop b/_fixtures/goptest/b.gop new file mode 100644 index 0000000000..4756079752 --- /dev/null +++ b/_fixtures/goptest/b.gop @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + c "gop-sample/cpkag" + ab "gop-sample/cpkag/b" + ss "gop-sample/cpkag/b/ss" + k "gop-sample/cpkag/k" + myutils "gop-sample/cpkag/utils" + g "gop-sample/gpkag" + rootutils "gop-sample/utils" + + remote "github.com/liuscraft/testgomod/utils" +) + +bb := 3 +Ab() +ab.Ab() +ab.Ac() +ss.Ab() +ss.Bs() +fmt.Println(bb) +k.K() +k.Kk() +k.Ab() +remote.TestCsgo() +myutils.TestCsgo() +rootutils.TestCsgo() + +a() +c.P() +c.Gg() +g.G() +fmt.Println("b") +fmt.Println("b") +fmt.Println("b") +fmt.Println("b") +fmt.Println("b") +fmt.Println("b") +fmt.Println("b") diff --git a/_fixtures/goptest/b_test.gop b/_fixtures/goptest/b_test.gop new file mode 100644 index 0000000000..561391d20b --- /dev/null +++ b/_fixtures/goptest/b_test.gop @@ -0,0 +1,8 @@ +import ( + c "gop-sample/cpkag" + "testing" +) + +func TestC(t *testing.T) { + c.P() +} diff --git a/_fixtures/goptest/cpkag/b/ab.gop b/_fixtures/goptest/cpkag/b/ab.gop new file mode 100644 index 0000000000..114172f380 --- /dev/null +++ b/_fixtures/goptest/cpkag/b/ab.gop @@ -0,0 +1,5 @@ +package ab + +func Ab() { + println("ab") +} diff --git a/_fixtures/goptest/cpkag/b/ac.go b/_fixtures/goptest/cpkag/b/ac.go new file mode 100644 index 0000000000..6d37df32aa --- /dev/null +++ b/_fixtures/goptest/cpkag/b/ac.go @@ -0,0 +1,5 @@ +package ab + +func Ac() { + println("ac") +} diff --git a/_fixtures/goptest/cpkag/b/gop_autogen.go b/_fixtures/goptest/cpkag/b/gop_autogen.go new file mode 100644 index 0000000000..79355daafd --- /dev/null +++ b/_fixtures/goptest/cpkag/b/gop_autogen.go @@ -0,0 +1,8 @@ +package ab + +import "fmt" +//line cpkag/b/ab.gop:3:1 +func Ab() { +//line cpkag/b/ab.gop:4:1 + fmt.Println("ab") +} diff --git a/_fixtures/goptest/cpkag/b/ss/b.gop b/_fixtures/goptest/cpkag/b/ss/b.gop new file mode 100644 index 0000000000..edf00b5c81 --- /dev/null +++ b/_fixtures/goptest/cpkag/b/ss/b.gop @@ -0,0 +1,5 @@ +package ss + +func Bs() { + println("bs") +} diff --git a/_fixtures/goptest/cpkag/b/ss/gop_autogen.go b/_fixtures/goptest/cpkag/b/ss/gop_autogen.go new file mode 100644 index 0000000000..ee21849457 --- /dev/null +++ b/_fixtures/goptest/cpkag/b/ss/gop_autogen.go @@ -0,0 +1,13 @@ +package ss + +import "fmt" +//line cpkag/b/ss/b.gop:3:1 +func Bs() { +//line cpkag/b/ss/b.gop:4:1 + fmt.Println("bs") +} +//line cpkag/b/ss/ss.gop:3:1 +func Ab() { +//line cpkag/b/ss/ss.gop:4:1 + fmt.Println("ab") +} diff --git a/_fixtures/goptest/cpkag/b/ss/ss.gop b/_fixtures/goptest/cpkag/b/ss/ss.gop new file mode 100644 index 0000000000..a999b970a1 --- /dev/null +++ b/_fixtures/goptest/cpkag/b/ss/ss.gop @@ -0,0 +1,5 @@ +package ss + +func Ab() { + println("ab") +} diff --git a/_fixtures/goptest/cpkag/c.go b/_fixtures/goptest/cpkag/c.go new file mode 100644 index 0000000000..4fe26bcb2f --- /dev/null +++ b/_fixtures/goptest/cpkag/c.go @@ -0,0 +1,5 @@ +package cpkag + +func P() { + println("c") +} diff --git a/_fixtures/goptest/cpkag/gg.gop b/_fixtures/goptest/cpkag/gg.gop new file mode 100644 index 0000000000..4712843338 --- /dev/null +++ b/_fixtures/goptest/cpkag/gg.gop @@ -0,0 +1,5 @@ +package cpkag + +func Gg(){ + println("gg") +} \ No newline at end of file diff --git a/_fixtures/goptest/cpkag/gop_autogen.go b/_fixtures/goptest/cpkag/gop_autogen.go new file mode 100644 index 0000000000..15e6821219 --- /dev/null +++ b/_fixtures/goptest/cpkag/gop_autogen.go @@ -0,0 +1,8 @@ +package cpkag + +import "fmt" +//line cpkag/gg.gop:3:1 +func Gg() { +//line cpkag/gg.gop:4:1 + fmt.Println("gg") +} diff --git a/_fixtures/goptest/cpkag/k/c.go b/_fixtures/goptest/cpkag/k/c.go new file mode 100644 index 0000000000..0089886b0c --- /dev/null +++ b/_fixtures/goptest/cpkag/k/c.go @@ -0,0 +1,5 @@ +package k + +func K() { + println("k") +} diff --git a/_fixtures/goptest/cpkag/k/gop_autogen.go b/_fixtures/goptest/cpkag/k/gop_autogen.go new file mode 100644 index 0000000000..620bfae55c --- /dev/null +++ b/_fixtures/goptest/cpkag/k/gop_autogen.go @@ -0,0 +1,13 @@ +package k + +import "fmt" +//line cpkag/k/k.gop:3:1 +func Kk() { +//line cpkag/k/k.gop:4:1 + fmt.Println("kk") +} +//line cpkag/k/k.gop:7:1 +func Ab() { +//line cpkag/k/k.gop:8:1 + fmt.Println("ab") +} diff --git a/_fixtures/goptest/cpkag/k/k.gop b/_fixtures/goptest/cpkag/k/k.gop new file mode 100644 index 0000000000..dc3c61cece --- /dev/null +++ b/_fixtures/goptest/cpkag/k/k.gop @@ -0,0 +1,9 @@ +package k + +func Kk() { + println("kk") +} + +func Ab() { + println("ab") +} diff --git a/_fixtures/goptest/cpkag/utils/csgo.gop b/_fixtures/goptest/cpkag/utils/csgo.gop new file mode 100644 index 0000000000..3d77870986 --- /dev/null +++ b/_fixtures/goptest/cpkag/utils/csgo.gop @@ -0,0 +1,7 @@ +package utils + +import "fmt" + +func TestCsgo() { + fmt.Println("my testcsgo") +} diff --git a/_fixtures/goptest/cpkag/utils/gop_autogen.go b/_fixtures/goptest/cpkag/utils/gop_autogen.go new file mode 100644 index 0000000000..62807cf5c0 --- /dev/null +++ b/_fixtures/goptest/cpkag/utils/gop_autogen.go @@ -0,0 +1,8 @@ +package utils + +import "fmt" +//line cpkag/utils/csgo.gop:5:1 +func TestCsgo() { +//line cpkag/utils/csgo.gop:6:1 + fmt.Println("my testcsgo") +} diff --git a/_fixtures/goptest/go.mod b/_fixtures/goptest/go.mod new file mode 100644 index 0000000000..513ca73402 --- /dev/null +++ b/_fixtures/goptest/go.mod @@ -0,0 +1,5 @@ +module gop-sample + +go 1.19 + +require github.com/liuscraft/testgomod v1.0.0 diff --git a/_fixtures/goptest/go.sum b/_fixtures/goptest/go.sum new file mode 100644 index 0000000000..9e32096f1c --- /dev/null +++ b/_fixtures/goptest/go.sum @@ -0,0 +1,2 @@ +github.com/liuscraft/testgomod v1.0.0 h1:OIi5azk6+mETGBp7Imhoj3zNLJrvoWlpMEl3dEN5EMg= +github.com/liuscraft/testgomod v1.0.0/go.mod h1:TFipFjRKhMQLTuF3Ey9qjsBD5z+Zij6w91fOC7vSZtQ= diff --git a/_fixtures/goptest/gop.mod b/_fixtures/goptest/gop.mod new file mode 100644 index 0000000000..1b1598e6a6 --- /dev/null +++ b/_fixtures/goptest/gop.mod @@ -0,0 +1,5 @@ +module gop-sample + +go 1.21 + +gop 1.1 diff --git a/_fixtures/goptest/gop_autogen.go b/_fixtures/goptest/gop_autogen.go new file mode 100644 index 0000000000..651cebcb15 --- /dev/null +++ b/_fixtures/goptest/gop_autogen.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "gop-sample/cpkag" + ab "gop-sample/cpkag/b" + "gop-sample/cpkag/b/ss" + "gop-sample/cpkag/k" + "gop-sample/cpkag/utils" + "gop-sample/gpkag" + utils1 "gop-sample/utils" + utils2 "github.com/liuscraft/testgomod/utils" +) +//line a.gop:3:1 +func a() { +//line a.gop:4:1 + fmt.Println("a") +} +//line a.gop:7:1 +func Ab() { +//line a.gop:8:1 + fmt.Println("ab") +} +//line b.gop:16 +func main() { +//line b.gop:16:1 + bb := 3 +//line b.gop:17:1 + Ab() +//line b.gop:18:1 + ab.Ab() +//line b.gop:19:1 + ab.Ac() +//line b.gop:20:1 + ss.Ab() +//line b.gop:21:1 + ss.Bs() +//line b.gop:22:1 + fmt.Println(bb) +//line b.gop:23:1 + k.K() +//line b.gop:24:1 + k.Kk() +//line b.gop:25:1 + k.Ab() +//line b.gop:26:1 + utils2.TestCsgo() +//line b.gop:27:1 + utils.TestCsgo() +//line b.gop:28:1 + utils1.TestCsgo() +//line b.gop:30:1 + a() +//line b.gop:31:1 + cpkag.P() +//line b.gop:32:1 + cpkag.Gg() +//line b.gop:33:1 + gpkag.G() +//line b.gop:34:1 + fmt.Println("b") +//line b.gop:35:1 + fmt.Println("b") +//line b.gop:36:1 + fmt.Println("b") +//line b.gop:37:1 + fmt.Println("b") +//line b.gop:38:1 + fmt.Println("b") +//line b.gop:39:1 + fmt.Println("b") +//line b.gop:40:1 + fmt.Println("b") +} diff --git a/_fixtures/goptest/gop_autogen2_test.go b/_fixtures/goptest/gop_autogen2_test.go new file mode 100644 index 0000000000..5e3686ab76 --- /dev/null +++ b/_fixtures/goptest/gop_autogen2_test.go @@ -0,0 +1,11 @@ +package main_test + +import ( + "testing" + + "github.com/liuscraft/testgomod/utils" +) + +func TestCsgo1(t *testing.T) { + utils.TestCsgo() +} diff --git a/_fixtures/goptest/gop_autogen_test.go b/_fixtures/goptest/gop_autogen_test.go new file mode 100644 index 0000000000..936c9bc3eb --- /dev/null +++ b/_fixtures/goptest/gop_autogen_test.go @@ -0,0 +1,11 @@ +package main + +import ( + "gop-sample/cpkag" + "testing" +) +//line b_test.gop:6:1 +func TestC(t *testing.T) { +//line b_test.gop:7:1 + cpkag.P() +} diff --git a/_fixtures/goptest/gpkag/g.go b/_fixtures/goptest/gpkag/g.go new file mode 100644 index 0000000000..bc1dd0c8a7 --- /dev/null +++ b/_fixtures/goptest/gpkag/g.go @@ -0,0 +1,7 @@ +package gpkag + +import "fmt" + +func G() { + fmt.Println("g") +} \ No newline at end of file diff --git a/_fixtures/goptest/utils/csgo.gop b/_fixtures/goptest/utils/csgo.gop new file mode 100644 index 0000000000..a3ad462623 --- /dev/null +++ b/_fixtures/goptest/utils/csgo.gop @@ -0,0 +1,7 @@ +package utils + +import "fmt" + +func TestCsgo() { + fmt.Println("root testcsgo") +} diff --git a/_fixtures/goptest/utils/csgo_test.gop b/_fixtures/goptest/utils/csgo_test.gop new file mode 100644 index 0000000000..8b8054347e --- /dev/null +++ b/_fixtures/goptest/utils/csgo_test.gop @@ -0,0 +1,16 @@ +package utils + +import "testing" + +func TestCsgo2(t *testing.T) { + tests := []struct { + name string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + TestCsgo() + }) + } +} diff --git a/_fixtures/goptest/utils/gop_autogen.go b/_fixtures/goptest/utils/gop_autogen.go new file mode 100644 index 0000000000..0b438cc24b --- /dev/null +++ b/_fixtures/goptest/utils/gop_autogen.go @@ -0,0 +1,8 @@ +package utils + +import "fmt" +//line utils/csgo.gop:5:1 +func TestCsgo() { +//line utils/csgo.gop:6:1 + fmt.Println("root testcsgo") +} diff --git a/_fixtures/goptest/utils/gop_autogen_test.go b/_fixtures/goptest/utils/gop_autogen_test.go new file mode 100644 index 0000000000..4e34fbc404 --- /dev/null +++ b/_fixtures/goptest/utils/gop_autogen_test.go @@ -0,0 +1,19 @@ +package utils + +import "testing" +//line utils/csgo_test.gop:5:1 +func TestCsgo2(t *testing.T) { +//line utils/csgo_test.gop:6:1 + tests := []struct { + name string + }{} + for +//line utils/csgo_test.gop:11:1 + _, tt := range tests { +//line utils/csgo_test.gop:12:1 + t.Run(tt.name, func(t *testing.T) { +//line utils/csgo_test.gop:13:1 + TestCsgo() + }) + } +} diff --git a/go.mod b/go.mod index 8a1535c4d5..4c2cf6dbd6 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/go-delve/gore v0.11.6 github.com/go-delve/liner v1.2.3-0.20231231155935-4726ab1d7f62 github.com/google/go-dap v0.11.0 + github.com/goplus/mod v0.12.3 github.com/hashicorp/golang-lru v1.0.2 github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.20 @@ -27,6 +28,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/qiniu/x v1.13.2 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect diff --git a/go.sum b/go.sum index bcc2c0622d..2c609fde04 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-dap v0.11.0 h1:SpAZJL41rOOvd85PuLCCLE1dteTQOyKNnn0H3DBHywo= github.com/google/go-dap v0.11.0/go.mod h1:HAeyoSd2WIfTfg+0GRXcFrb+RnojAtGNh+k+XTIxJDE= +github.com/goplus/mod v0.12.3 h1:qLU5/F27CzUTQhCQRN1WruiCepUn5GdLKQTB41OsYfk= +github.com/goplus/mod v0.12.3/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -39,6 +41,8 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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/qiniu/x v1.13.2 h1:mgWOfB9Rpk6AEtlBoObZVxH+b2FHSntYrxc4KX5Ta98= +github.com/qiniu/x v1.13.2/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= @@ -55,24 +59,66 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.starlark.net v0.0.0-20231101134539-556fd59b42f6 h1:+eC0F/k4aBLC4szgOcjd7bDTEnpxADJyWJE0yowgM3E= go.starlark.net v0.0.0-20231101134539-556fd59b42f6/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index bffd386c2d..98d2bba27a 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -16,6 +16,7 @@ import ( "io" "os" "path/filepath" + "runtime" "sort" "strconv" "strings" @@ -34,6 +35,7 @@ import ( "github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/proc/debuginfod" "github.com/go-delve/gore" + "github.com/goplus/mod/gopmod" "github.com/hashicorp/golang-lru/simplelru" ) @@ -2180,6 +2182,37 @@ func (bi *BinaryInfo) registerTypeToPackageMap(entry *dwarf.Entry) { bi.PackageMap[name] = []string{path} } +// check file is gop or gop classfile +func isGopFile(file string) bool { + if filepath.IsAbs(file) { + return false + } + fileExt := filepath.Ext(file) + return fileExt != "" && fileExt != ".go" && fileExt != ".s" +} + +// gop file relative path to absolute path +func relPathToAbsPathByPackage(imageMod *gopmod.Module, filePakage string, relPath string) string { + absPath := filePakage + if imageMod == nil { + panic("imageMod is nil") + } + if filePakage == "main" { + filePakage = imageMod.Path() + } + if imageMod.PkgType(filePakage) != -1 { + fileMod, err := imageMod.Lookup(filePakage) + if err == nil { + absPath = fileMod.ModDir + } + } + absPath = filepath.Join(absPath, relPath) + if runtime.GOOS == "windows" { + absPath = filepath.ToSlash(absPath) + } + return absPath +} + func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineBytes []byte, wg *sync.WaitGroup, cont func()) { if wg != nil { defer wg.Done() @@ -2207,6 +2240,8 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB reader := image.DwarfReader() + imageMod := gopmod.Default + for { entry, err := reader.Next() if err != nil { @@ -2264,6 +2299,32 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB cu.producer = cu.producer[:semicolon] } } + + if cu.lineInfo != nil { + // load currentImageDir modInfo + imageDir := filepath.Dir(image.Path) + if imageMod.Path() != imageDir { + imageMod, err = gopmod.Load(imageDir) + if err != nil { + imageMod = gopmod.Default + } + } + cuName := cu.name + if runtime.GOOS == "windows" { + cuName = filepath.ToSlash(cuName) + } + // filter test file suffix: support test debug + cuName = strings.TrimSuffix(cuName, "_test") + for _, fileEntry := range cu.lineInfo.FileNames { + if isGopFile(fileEntry.Path) { + filePakage := strings.TrimSuffix(cuName, cu.lineInfo.IncludeDirs[fileEntry.DirIdx]) + absPath := relPathToAbsPathByPackage(imageMod, filePakage, fileEntry.Path) + cu.lineInfo.Lookup[absPath] = fileEntry + delete(cu.lineInfo.Lookup, fileEntry.Path) + fileEntry.Path = absPath + } + } + } gopkg, _ := entry.Val(godwarf.AttrGoPackageName).(string) if cu.isgo && gopkg != "" { bi.PackageMap[gopkg] = append(bi.PackageMap[gopkg], escapePackagePath(strings.ReplaceAll(cu.name, "\\", "/"))) diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index d39d516b35..3d4aeb1d1e 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -333,7 +333,17 @@ func TestStep(t *testing.T) { } }) } +func TestSources(t *testing.T) { + protest.AllowRecording(t) + withTestProcessArgs("goptest/", t, ".", []string{"test"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + for i, v := range p.BinInfo().Sources { + if strings.HasSuffix(v, ".gop") { + t.Log(i, v) + } + } + }) +} func TestBreakpoint(t *testing.T) { protest.AllowRecording(t) withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { diff --git a/vendor/github.com/goplus/mod/.gitignore b/vendor/github.com/goplus/mod/.gitignore new file mode 100644 index 0000000000..8dfd59f02a --- /dev/null +++ b/vendor/github.com/goplus/mod/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +go.work* diff --git a/vendor/github.com/goplus/mod/LICENSE b/vendor/github.com/goplus/mod/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/github.com/goplus/mod/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/goplus/mod/README.md b/vendor/github.com/goplus/mod/README.md new file mode 100644 index 0000000000..df7db41558 --- /dev/null +++ b/vendor/github.com/goplus/mod/README.md @@ -0,0 +1,8 @@ +mod - Module support for Go/Go+ +===== + +[![Build Status](https://github.com/goplus/mod/actions/workflows/go.yml/badge.svg)](https://github.com/goplus/mod/actions/workflows/go.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/goplus/mod)](https://goreportcard.com/report/github.com/goplus/mod) +[![GitHub release](https://img.shields.io/github/v/tag/goplus/mod.svg?label=release)](https://github.com/goplus/mod/releases) +[![Coverage Status](https://codecov.io/gh/goplus/mod/branch/main/graph/badge.svg)](https://codecov.io/gh/goplus/mod) +[![GoDoc](https://pkg.go.dev/badge/github.com/goplus/mod.svg)](https://pkg.go.dev/github.com/goplus/mod) diff --git a/vendor/github.com/goplus/mod/gopmod/classfile.go b/vendor/github.com/goplus/mod/gopmod/classfile.go new file mode 100644 index 0000000000..ae152149ce --- /dev/null +++ b/vendor/github.com/goplus/mod/gopmod/classfile.go @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gopmod + +import ( + "errors" + "syscall" + + "github.com/goplus/mod/modcache" + "github.com/goplus/mod/modfetch" + "github.com/goplus/mod/modfile" + "github.com/goplus/mod/modload" + "golang.org/x/mod/module" +) + +type Class = modfile.Class +type Project = modfile.Project + +var ( + SpxProject = &Project{ + Ext: ".spx", + Class: "Game", + Works: []*Class{{Ext: ".spx", Class: "Sprite"}}, + PkgPaths: []string{"github.com/goplus/spx", "math"}, + } + TestProject = &Project{ + Ext: "_test.gox", + Class: "App", + Works: []*modfile.Class{{Ext: "_test.gox", Class: "Case"}}, + PkgPaths: []string{"github.com/goplus/gop/test", "testing"}, + } +) + +var ( + ErrNotClassFileMod = errors.New("not a classfile module") +) + +// ----------------------------------------------------------------------------- + +// ClassKind checks a fname is a known classfile or not. +// If it is, then it checks the fname is a project file or not. +func (p *Module) ClassKind(fname string) (isProj, ok bool) { + ext := modfile.ClassExt(fname) + if c, ok := p.projects[ext]; ok { + return c.IsProj(ext, fname), true + } + return +} + +// IsClass checks ext is a known classfile or not. +func (p *Module) IsClass(ext string) (ok bool) { + _, ok = p.projects[ext] + return +} + +// LookupClass lookups a classfile by ext. +func (p *Module) LookupClass(ext string) (c *Project, ok bool) { + c, ok = p.projects[ext] + return +} + +// ImportClasses imports all classfiles found in this module (from go.mod/gop.mod). +func (p *Module) ImportClasses(importClass ...func(c *Project)) (err error) { + var impcls func(c *Project) + if importClass != nil { + impcls = importClass[0] + } + p.importClass(TestProject, impcls) + p.importClass(SpxProject, impcls) + p.projects[".gmx"] = SpxProject // old style + opt := p.Opt + for _, c := range opt.Projects { + p.importClass(c, impcls) + } + for _, r := range opt.Import { + if err = p.importMod(r.ClassfileMod, impcls); err != nil { + return + } + } + return +} + +func (p *Module) importMod(modPath string, imcls func(c *Project)) (err error) { + mod, ok := p.LookupDepMod(modPath) + if !ok { + return syscall.ENOENT + } + err = p.importClassFrom(mod, imcls) + if err != syscall.ENOENT { + return + } + mod, err = modfetch.Get(mod.String()) + if err != nil { + return + } + return p.importClassFrom(mod, imcls) +} + +func (p *Module) importClassFrom(modVer module.Version, impcls func(c *Project)) (err error) { + dir, err := modcache.Path(modVer) + if err != nil { + return + } + mod, err := modload.Load(dir) + if err != nil { + return + } + projs := mod.Projects() + if len(projs) == 0 { + return ErrNotClassFileMod + } + for _, c := range projs { + p.importClass(c, impcls) + } + return +} + +func (p *Module) importClass(c *Project, impcls func(c *Project)) { + p.projects[c.Ext] = c + for _, w := range c.Works { + p.projects[w.Ext] = c + } + if impcls != nil { + impcls(c) + } +} + +// ----------------------------------------------------------------------------- diff --git a/vendor/github.com/goplus/mod/gopmod/module.go b/vendor/github.com/goplus/mod/gopmod/module.go new file mode 100644 index 0000000000..88e622e211 --- /dev/null +++ b/vendor/github.com/goplus/mod/gopmod/module.go @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package gopmod + +import ( + "fmt" + "log" + "path/filepath" + "runtime" + "sort" + "strings" + "syscall" + + "github.com/goplus/mod/modcache" + "github.com/goplus/mod/modfetch" + "github.com/goplus/mod/modload" + "github.com/qiniu/x/errors" + "golang.org/x/mod/module" +) + +// ----------------------------------------------------------------------------- + +type depmodInfo struct { + path string + real module.Version +} + +type Module struct { + modload.Module + projects map[string]*Project // ext -> project + depmods []depmodInfo +} + +// PkgType specifies a package type. +type PkgType int + +const ( + PkgtStandard PkgType = iota // a standard Go/Go+ package + PkgtModule // a package in this module (in standard form) + PkgtLocal // a package in this module (in relative path form) + PkgtExtern // an extarnal package + PkgtInvalid = -1 // an invalid package +) + +// IsPkgtStandard checks if a pkgPath is Go standard package or not. +func (p *Module) IsPkgtStandard(pkgPath string) bool { + return p.PkgType(pkgPath) == PkgtStandard +} + +// PkgType returns the package type of specified package. +func (p *Module) PkgType(pkgPath string) PkgType { + if pkgPath == "" { + return PkgtInvalid + } + if isPkgInMod(pkgPath, p.Path()) { + return PkgtModule + } + if pkgPath[0] == '.' { + return PkgtLocal + } + pos := strings.Index(pkgPath, "/") + if pos > 0 { + pkgPath = pkgPath[:pos] + } + if strings.Contains(pkgPath, ".") { + return PkgtExtern + } + return PkgtStandard +} + +func isPkgInMod(pkgPath, modPath string) bool { + if modPath != "" && strings.HasPrefix(pkgPath, modPath) { + suffix := pkgPath[len(modPath):] + return suffix == "" || suffix[0] == '/' + } + return false +} + +type Package struct { + Type PkgType + Dir string + ModDir string + ModPath string + Real module.Version // only when Type == PkgtExtern +} + +func (p *Module) Lookup(pkgPath string) (pkg *Package, err error) { + switch pt := p.PkgType(pkgPath); pt { + case PkgtStandard: + modDir := runtime.GOROOT() + "/src" + pkg = &Package{Type: PkgtStandard, ModDir: modDir, Dir: filepath.Join(modDir, pkgPath)} + case PkgtModule: + modPath := p.Path() + modDir := p.Root() + dir := modDir + pkgPath[len(modPath):] + pkg = &Package{Type: PkgtModule, ModPath: modPath, ModDir: modDir, Dir: dir} + case PkgtExtern: + return p.lookupExternPkg(pkgPath) + default: + log.Panicln("Module.Lookup:", pkgPath, "unsupported pkgType:", pt) + } + return +} + +// lookupExternPkg lookups a external package from depended modules. +// If modVer.Path is replace to be a local path, it will be canonical to an absolute path. +func (p *Module) lookupExternPkg(pkgPath string) (pkg *Package, err error) { + for _, m := range p.depmods { + if isPkgInMod(pkgPath, m.path) { + if modDir, e := modcache.Path(m.real); e == nil { + modPath := m.path + dir := modDir + pkgPath[len(modPath):] + pkg = &Package{Type: PkgtExtern, Real: m.real, ModPath: modPath, ModDir: modDir, Dir: dir} + } else { + err = e + } + return + } + } + err = &MissingError{Path: pkgPath} + return +} + +// LookupDepMod lookups a depended module. +// If modVer.Path is replace to be a local path, it will be canonical to an absolute path. +func (p *Module) LookupDepMod(modPath string) (modVer module.Version, ok bool) { + for _, m := range p.depmods { + if m.path == modPath { + modVer, ok = m.real, true + break + } + } + return +} + +// IsGopMod returns if this module is a Go+ module or not. +func (p *Module) IsGopMod() bool { + const gopPkgPath = "github.com/goplus/gop" + _, file := filepath.Split(p.Modfile()) + if file == "gop.mod" { + return true + } + if _, ok := p.LookupDepMod(gopPkgPath); ok { + return true + } + return p.Path() == gopPkgPath +} + +func getDepMods(mod modload.Module) []depmodInfo { + depmods := mod.DepMods() + ret := make([]depmodInfo, 0, len(depmods)) + for path, m := range depmods { + ret = append(ret, depmodInfo{path: path, real: m}) + } + sort.Slice(ret, func(i, j int) bool { + return ret[i].path > ret[j].path + }) + return ret +} + +// New creates a module from a modload.Module instance. +func New(mod modload.Module) *Module { + projects := make(map[string]*Project) + depmods := getDepMods(mod) + return &Module{projects: projects, depmods: depmods, Module: mod} +} + +// Load loads a module from a local directory. +func Load(dir string) (*Module, error) { + mod, err := modload.Load(dir) + if err != nil { + return nil, errors.NewWith(err, `modload.Load(dir)`, -2, "modload.Load", dir) + } + return New(mod), nil +} + +// LoadFrom loads a module from specified go.mod file and an optional gop.mod file. +func LoadFrom(gomod, gopmod string) (*Module, error) { + mod, err := modload.LoadFrom(gomod, gopmod) + if err != nil { + return nil, errors.NewWith(err, `modload.LoadFrom(gomod, gopmod)`, -2, "modload.LoadFrom", gomod, gopmod) + } + return New(mod), nil +} + +// LoadMod loads a module from a versioned module path. +// If we only want to load a Go modfile, pass env parameter as nil. +func LoadMod(mod module.Version) (p *Module, err error) { + p, err = loadModFrom(mod) + if err != syscall.ENOENT { + return + } + mod, err = modfetch.Get(mod.String()) + if err != nil { + return + } + return loadModFrom(mod) +} + +func loadModFrom(mod module.Version) (p *Module, err error) { + dir, err := modcache.Path(mod) + if err != nil { + return + } + return Load(dir) +} + +type MissingError struct { + Path string +} + +func (e *MissingError) Error() string { + return fmt.Sprintf(`no required module provides package %v; to add it: + gop get %v`, e.Path, e.Path) +} + +// ----------------------------------------------------------------------------- + +// Default represents the default gop.mod object. +var Default = New(modload.Default) + +// ----------------------------------------------------------------------------- diff --git a/vendor/github.com/goplus/mod/mod.go b/vendor/github.com/goplus/mod/mod.go new file mode 100644 index 0000000000..aae3ea952f --- /dev/null +++ b/vendor/github.com/goplus/mod/mod.go @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mod + +import ( + "os" + "path/filepath" + "strings" + "syscall" +) + +// ----------------------------------------------------------------------------- + +func FindGoMod(dirFrom string) (dir, file string, err error) { + if dirFrom == "" { + dirFrom = "." + } + if dir, err = filepath.Abs(dirFrom); err != nil { + return + } + for dir != "" { + file = filepath.Join(dir, "go.mod") + if fi, e := os.Lstat(file); e == nil && !fi.IsDir() { + return + } + if dir, file = filepath.Split(strings.TrimRight(dir, "/\\")); file == "" { + break + } + } + err = syscall.ENOENT + return +} + +func GOMOD(dirFrom string) (file string, err error) { + _, file, err = FindGoMod(dirFrom) + return +} + +func GOPMOD(dirFrom string) (file string, err error) { + dir, _, err := FindGoMod(dirFrom) + if err != nil { + return + } + return filepath.Join(dir, "gop.mod"), nil +} + +// ----------------------------------------------------------------------------- diff --git a/vendor/github.com/goplus/mod/modcache/cache.go b/vendor/github.com/goplus/mod/modcache/cache.go new file mode 100644 index 0000000000..0d45a5ef13 --- /dev/null +++ b/vendor/github.com/goplus/mod/modcache/cache.go @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modcache + +import ( + "bytes" + "errors" + "log" + "os/exec" + "path/filepath" + "strings" + + "golang.org/x/mod/module" +) + +// ----------------------------------------------------------------------------- + +var ( + GOMODCACHE = getGOMODCACHE() +) + +func getGOMODCACHE() string { + var buf bytes.Buffer + var stderr bytes.Buffer + cmd := exec.Command("go", "env", "GOMODCACHE") + cmd.Stdout = &buf + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + log.Panicln("GOMODCACHE not found:", err) + } + return strings.TrimRight(buf.String(), "\n") +} + +// ----------------------------------------------------------------------------- + +var ( + ErrNoNeedToDownload = errors.New("no need to download") +) + +// DownloadCachePath returns download cache path of a versioned module. +func DownloadCachePath(mod module.Version) (string, error) { + if mod.Version == "" { + return mod.Path, ErrNoNeedToDownload + } + encPath, err := module.EscapePath(mod.Path) + if err != nil { + return "", err + } + return filepath.Join(GOMODCACHE, "cache/download", encPath, "@v", mod.Version+".zip"), nil +} + +// Path returns cache dir of a versioned module. +func Path(mod module.Version) (string, error) { + if mod.Version == "" { + return mod.Path, nil + } + encPath, err := module.EscapePath(mod.Path) + if err != nil { + return "", err + } + return filepath.Join(GOMODCACHE, encPath+"@"+mod.Version), nil +} + +// InPath returns if a path is in GOMODCACHE or not. +func InPath(path string) bool { + if strings.HasPrefix(path, GOMODCACHE) { + name := path[len(GOMODCACHE):] + return name == "" || name[0] == '/' || name[0] == '\\' + } + return false +} + +// ----------------------------------------------------------------------------- diff --git a/vendor/github.com/goplus/mod/modfetch/fetch.go b/vendor/github.com/goplus/mod/modfetch/fetch.go new file mode 100644 index 0000000000..57f15bcaa3 --- /dev/null +++ b/vendor/github.com/goplus/mod/modfetch/fetch.go @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modfetch + +import ( + "bytes" + "context" + "errors" + "fmt" + "log" + "net/url" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "github.com/goplus/mod/modcache" + "golang.org/x/mod/module" + "golang.org/x/mod/semver" +) + +type dbgFlags int + +const ( + DbgFlagVerbose dbgFlags = 1 << iota + DbgFlagShowError + DbgFlagAll = DbgFlagShowError | DbgFlagVerbose +) + +var ( + debugVerbose bool +) + +// SetDebug sets debug flags. +func SetDebug(flags dbgFlags) { + debugVerbose = (flags & DbgFlagVerbose) != 0 +} + +// ----------------------------------------------------------------------------- + +// GetPkg downloads the module that contains pkgPath to GOMODCACHE. +func GetPkg(pkgPathVer, modBase string) (modVer module.Version, relPath string, err error) { + var ver string + var pkgPath string = pkgPathVer + if pos := strings.IndexByte(pkgPath, '@'); pos > 0 { + pkgPath, ver = pkgPath[:pos], pkgPath[pos+1:] + } + if debugVerbose { + log.Println("modfetch.GetPkg", pkgPathVer, modBase) + } + semIsValid := semver.IsValid(ver) + if semIsValid { + modVer, relPath, err = lookupListFromCache(pkgPath, "@"+ver) + if err == nil { + return + } + } + var stdout, stderr bytes.Buffer + cmd := exec.Command("go", "install", "-x", pkgPathVer) + if debugVerbose { + log.Println("==>", cmd) + } + cmd.Stdout = &stdout + cmd.Stderr = &stderr + cmd.Run() + proxy, pkg, found := foundBestRepo(stderr.String(), pkgPath) + var foundVer string + if semIsValid { + foundVer = "@" + ver + } + if found { + if !semIsValid { + if rev, err := foundRevInfo(proxy, pkg, ver); err == nil { + foundVer = "@" + rev.Version + } + } + if strings.HasPrefix(pkgPath, pkg+"/") { + relPath = pkgPath[len(pkg)+1:] + } + _, modVer, err = lookupFromCache(pkg + foundVer) + } else { + modVer, relPath, err = lookupListFromCache(pkgPath, foundVer) + } + if debugVerbose { + log.Println("==>", modVer, relPath, err) + } + return +} + +func lookupListFromCache(pkgPath string, ver string) (modVer module.Version, relPath string, err error) { + list := strings.Split(pkgPath, "/") + for i := len(list); i > 0; i-- { + modPath := strings.Join(list[:i], "/") + ver + _, modVer, err = lookupFromCache(modPath) + if err == nil { + encPath, _ := module.EscapePath(modVer.Path) + modRoot := filepath.Join(modcache.GOMODCACHE, encPath+"@"+modVer.Version, filepath.Join(list[i:]...)) + if _, e := os.Stat(modRoot); e != nil { + err = fmt.Errorf("gop: module %v found, but does not contain package %v", modVer.Path, pkgPath) + return + } + relPath = strings.Join(list[i:], "/") + return + } + } + return +} + +// Split splits a pkgPath into modPath and its relPath to module root. +func Split(pkgPath, modBase string) (modPath, relPath string) { + if modBase != "" && strings.HasPrefix(pkgPath, modBase) { + n := len(modBase) + if len(pkgPath) == n { + return modBase, "" + } + if pkgPath[n] == '/' { + return modBase, pkgPath[n+1:] + } + } + parts := strings.SplitN(pkgPath, "/", 4) + if !strings.Contains(parts[0], ".") { // standard package + return "", pkgPath + } + switch parts[0] { + case ".", "..": // local package + return modBase, pkgPath + case "github.com", "golang.org": + if len(parts) > 3 { + relPath = parts[3] + if pos := strings.IndexByte(relPath, '@'); pos > 0 { + parts[2] += relPath[pos:] + relPath = relPath[:pos] + } + modPath = strings.Join(parts[:3], "/") + } else { + modPath = pkgPath + } + return + } + panic("TODO: modfetch.Split - unexpected pkgPath: " + pkgPath) +} + +// ----------------------------------------------------------------------------- + +var ( + errEmptyModPath = errors.New("empty module path") +) + +// Get downloads a modPath to GOMODCACHE. +func Get(modPath string, noCache ...bool) (mod module.Version, err error) { + if debugVerbose { + log.Println("modfetch.Get", modPath) + } + if modPath == "" { + err = errEmptyModPath + return + } + if noCache == nil || !noCache[0] { + mod, err = getFromCache(modPath) + if err != syscall.ENOENT { + return + } + } + var stdout, stderr bytes.Buffer + var modPathVer = modPath + if strings.IndexByte(modPath, '@') < 0 { + modPathVer += "@latest" + } + cmd := exec.Command("go", "get", modPathVer) + if debugVerbose { + log.Println("==>", cmd) + } + cmd.Stdout = &stdout + cmd.Stderr = &stderr + cmd.Run() + if stderr.Len() > 0 { + mod, err = getResult(stderr.String()) + if err != syscall.ENOENT { + if debugVerbose { + log.Println("modfetch.Get ret:", err) + } + return + } + } + return getFromCache(modPath) +} + +func getResult(data string) (mod module.Version, err error) { + if debugVerbose { + log.Println("modfetch.getResult:", data) + } + // go: downloading github.com/xushiwei/foogop v0.1.0 + const downloading = "go: downloading " + if strings.HasPrefix(data, downloading) { + if pos := strings.IndexByte(data, '\n'); pos > 0 { + fmt.Fprintln(os.Stderr, "gop:", data[4:pos]) + } + return getMod(data[len(downloading):], nil) + } + err = syscall.ENOENT + return +} + +func getMod(data string, next *string) (mod module.Version, err error) { + if pos := strings.IndexByte(data, '\n'); pos > 0 { + line := data[:pos] + if next != nil { + *next = data[pos+1:] + } + if pos = strings.IndexByte(line, ' '); pos > 0 { + mod.Path, mod.Version = line[:pos], line[pos+1:] + return + } + } + err = syscall.ENOENT + return +} + +// ----------------------------------------------------------------------------- + +func getFromCache(modPath string) (modVer module.Version, err error) { + _, modVer, err = lookupFromCache(modPath) + return +} + +func lookupFromCache(modPath string) (modRoot string, mod module.Version, err error) { + mod.Path = modPath + pos := strings.IndexByte(modPath, '@') + if pos > 0 { + mod.Path, mod.Version = modPath[:pos], modPath[pos+1:] + } + encPath, err := module.EscapePath(mod.Path) + if err != nil { + return + } + modRoot = filepath.Join(modcache.GOMODCACHE, encPath+"@"+mod.Version) + if pos > 0 { // has version + fi, e := os.Stat(modRoot) + if e != nil || !fi.IsDir() { + err = syscall.ENOENT + } + return + } + dir, fname := filepath.Split(modRoot) + fis, err := os.ReadDir(dir) + if err != nil { + err = errors.Unwrap(err) + return + } + err = syscall.ENOENT + for _, fi := range fis { + if fi.IsDir() { + if name := fi.Name(); strings.HasPrefix(name, fname) { + ver := name[len(fname):] + if semver.Compare(mod.Version, ver) < 0 { + modRoot, mod.Version, err = dir+name, ver, nil + } + } + } + } + return +} + +// check rev info or list +// # get https://goproxy.cn/github.com/goplus/goxls/@v/main.info: 200 OK +// # get https://goproxy.cn/github.com/goplus/goxls/@v/list: 200 OK +// check pkglist match +// # get https://goproxy.cn/github.com/go-playground/validator/@v/list: 200 OK (0.530s) +// # get https://goproxy.cn/github.com/go-playground/validator/v10/@v/list: 200 OK (0.383s) +func foundBestRepo(data string, pkgPath string) (proxy string, best string, found bool) { + for _, line := range strings.Split(data, "\n") { + if strings.HasPrefix(line, "# get ") { + if !strings.Contains(line, ": 200 OK") { + continue + } + if n := strings.Index(line, "/@v/"); n > 0 { + base, err := url.Parse(line[6:n]) + if err != nil { + continue + } + pkg := base.Path[1:] + if strings.HasPrefix(pkgPath, pkg) { + proxy = base.Scheme + "://" + base.Host + found = true + if len(best) < len(pkg) { + best = pkg + } + } + } + } + } + return +} + +func foundRevInfo(proxy string, pkg string, rev string) (*RevInfo, error) { + repo, err := newProxyRepo(proxy, pkg) + if err != nil { + return nil, err + } + if rev == "latest" { + return repo.Latest(context.Background()) + } + return repo.Stat(context.Background(), rev) +} + +// ----------------------------------------------------------------------------- diff --git a/vendor/github.com/goplus/mod/modfetch/proxy.go b/vendor/github.com/goplus/mod/modfetch/proxy.go new file mode 100644 index 0000000000..089688dc16 --- /dev/null +++ b/vendor/github.com/goplus/mod/modfetch/proxy.go @@ -0,0 +1,308 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfetch + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "net/url" + pathpkg "path" + "strings" + "sync" + "time" + + "golang.org/x/mod/module" + "golang.org/x/mod/semver" +) + +// A RevInfo describes a single revision in a module repository. +type RevInfo struct { + Version string // suggested version string for this revision + Time time.Time // commit time + + // These fields are used for Stat of arbitrary rev, + // but they are not recorded when talking about module versions. + Name string `json:"-"` // complete ID in underlying repository + Short string `json:"-"` // shortened ID, for use in pseudo-version +} + +// A Versions describes the available versions in a module repository. +type Versions struct { + List []string // semver versions +} + +// ErrNoCommits is an error equivalent to fs.ErrNotExist indicating that a given +// repository or module contains no commits. +var ErrNoCommits error = noCommitsError{} + +type noCommitsError struct{} + +func (noCommitsError) Error() string { + return "no commits" +} +func (noCommitsError) Is(err error) bool { + return err == fs.ErrNotExist +} + +type proxyRepo struct { + url *url.URL + path string + redactedURL string + + listLatestOnce sync.Once + listLatest *RevInfo + listLatestErr error +} + +func newProxyRepo(baseURL, path string) (*proxyRepo, error) { + base, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + switch base.Scheme { + case "http", "https": + // ok + case "file": + if *base != (url.URL{Scheme: base.Scheme, Path: base.Path, RawPath: base.RawPath}) { + return nil, fmt.Errorf("invalid file:// proxy URL with non-path elements: %s", base.Redacted()) + } + case "": + return nil, fmt.Errorf("invalid proxy URL missing scheme: %s", base.Redacted()) + default: + return nil, fmt.Errorf("invalid proxy URL scheme (must be https, http, file): %s", base.Redacted()) + } + + enc, err := module.EscapePath(path) + if err != nil { + return nil, err + } + redactedURL := base.Redacted() + base.Path = strings.TrimSuffix(base.Path, "/") + "/" + enc + base.RawPath = strings.TrimSuffix(base.RawPath, "/") + "/" + pathEscape(enc) + return &proxyRepo{base, path, redactedURL, sync.Once{}, nil, nil}, nil +} + +func (p *proxyRepo) ModulePath() string { + return p.path +} + +// versionError returns err wrapped in a ModuleError for p.path. +func (p *proxyRepo) versionError(version string, err error) error { + if version != "" && version != module.CanonicalVersion(version) { + return &module.ModuleError{ + Path: p.path, + Err: &module.InvalidVersionError{ + Version: version, + Pseudo: module.IsPseudoVersion(version), + Err: err, + }, + } + } + + return &module.ModuleError{ + Path: p.path, + Version: version, + Err: err, + } +} + +func (p *proxyRepo) getBytes(ctx context.Context, path string) ([]byte, error) { + body, err := p.getBody(ctx, path) + if err != nil { + return nil, err + } + defer body.Close() + + b, err := io.ReadAll(body) + if err != nil { + // net/http doesn't add context to Body errors, so add it here. + // (See https://go.dev/issue/52727.) + return b, &url.Error{Op: "read", URL: strings.TrimSuffix(p.redactedURL, "/") + "/" + path, Err: err} + } + return b, nil +} + +func (p *proxyRepo) getBody(ctx context.Context, path string) (r io.ReadCloser, err error) { + fullPath := pathpkg.Join(p.url.Path, path) + + target := *p.url + target.Path = fullPath + target.RawPath = pathpkg.Join(target.RawPath, pathEscape(path)) + + resp, err := http.Get(target.String()) + if err != nil { + return nil, err + } + return resp.Body, nil +} + +func (p *proxyRepo) Versions(ctx context.Context, prefix string) (*Versions, error) { + data, err := p.getBytes(ctx, "@v/list") + if err != nil { + p.listLatestOnce.Do(func() { + p.listLatest, p.listLatestErr = nil, p.versionError("", err) + }) + return nil, p.versionError("", err) + } + var list []string + allLine := strings.Split(string(data), "\n") + for _, line := range allLine { + f := strings.Fields(line) + if len(f) >= 1 && semver.IsValid(f[0]) && strings.HasPrefix(f[0], prefix) && !module.IsPseudoVersion(f[0]) { + list = append(list, f[0]) + } + } + p.listLatestOnce.Do(func() { + p.listLatest, p.listLatestErr = p.latestFromList(ctx, allLine) + }) + semver.Sort(list) + return &Versions{List: list}, nil +} + +func (p *proxyRepo) latest(ctx context.Context) (*RevInfo, error) { + p.listLatestOnce.Do(func() { + data, err := p.getBytes(ctx, "@v/list") + if err != nil { + p.listLatestErr = p.versionError("", err) + return + } + list := strings.Split(string(data), "\n") + p.listLatest, p.listLatestErr = p.latestFromList(ctx, list) + }) + return p.listLatest, p.listLatestErr +} + +func (p *proxyRepo) latestFromList(ctx context.Context, allLine []string) (*RevInfo, error) { + var ( + bestTime time.Time + bestVersion string + ) + for _, line := range allLine { + f := strings.Fields(line) + if len(f) >= 1 && semver.IsValid(f[0]) { + // If the proxy includes timestamps, prefer the timestamp it reports. + // Otherwise, derive the timestamp from the pseudo-version. + var ( + ft time.Time + ) + if len(f) >= 2 { + ft, _ = time.Parse(time.RFC3339, f[1]) + } else if module.IsPseudoVersion(f[0]) { + ft, _ = module.PseudoVersionTime(f[0]) + } else { + // Repo.Latest promises that this method is only called where there are + // no tagged versions. Ignore any tagged versions that were added in the + // meantime. + continue + } + if bestTime.Before(ft) { + bestTime = ft + bestVersion = f[0] + } + } + } + if bestVersion == "" { + return nil, p.versionError("", ErrNoCommits) + } + + // Call Stat to get all the other fields, including Origin information. + return p.Stat(ctx, bestVersion) +} + +func (p *proxyRepo) Stat(ctx context.Context, rev string) (*RevInfo, error) { + encRev, err := module.EscapeVersion(rev) + if err != nil { + return nil, p.versionError(rev, err) + } + data, err := p.getBytes(ctx, "@v/"+encRev+".info") + if err != nil { + return nil, p.versionError(rev, err) + } + info := new(RevInfo) + if err := json.Unmarshal(data, info); err != nil { + return nil, p.versionError(rev, fmt.Errorf("invalid response from proxy %q: %w", p.redactedURL, err)) + } + if info.Version != rev && rev == module.CanonicalVersion(rev) && module.Check(p.path, rev) == nil { + // If we request a correct, appropriate version for the module path, the + // proxy must return either exactly that version or an error — not some + // arbitrary other version. + return nil, p.versionError(rev, fmt.Errorf("proxy returned info for version %s instead of requested version", info.Version)) + } + return info, nil +} + +func (p *proxyRepo) Latest(ctx context.Context) (*RevInfo, error) { + data, err := p.getBytes(ctx, "@latest") + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return nil, p.versionError("", err) + } + return p.latest(ctx) + } + info := new(RevInfo) + if err := json.Unmarshal(data, info); err != nil { + return nil, p.versionError("", fmt.Errorf("invalid response from proxy %q: %w", p.redactedURL, err)) + } + return info, nil +} + +func (p *proxyRepo) GoMod(ctx context.Context, version string) ([]byte, error) { + if version != module.CanonicalVersion(version) { + return nil, p.versionError(version, fmt.Errorf("internal error: version passed to GoMod is not canonical")) + } + + encVer, err := module.EscapeVersion(version) + if err != nil { + return nil, p.versionError(version, err) + } + data, err := p.getBytes(ctx, "@v/"+encVer+".mod") + if err != nil { + return nil, p.versionError(version, err) + } + return data, nil +} + +const maxZipFile = 500 << 20 // maximum size of downloaded zip file + +func (p *proxyRepo) Zip(ctx context.Context, dst io.Writer, version string) error { + if version != module.CanonicalVersion(version) { + return p.versionError(version, fmt.Errorf("internal error: version passed to Zip is not canonical")) + } + + encVer, err := module.EscapeVersion(version) + if err != nil { + return p.versionError(version, err) + } + path := "@v/" + encVer + ".zip" + body, err := p.getBody(ctx, path) + if err != nil { + return p.versionError(version, err) + } + defer body.Close() + + lr := &io.LimitedReader{R: body, N: maxZipFile + 1} + if _, err := io.Copy(dst, lr); err != nil { + // net/http doesn't add context to Body errors, so add it here. + // (See https://go.dev/issue/52727.) + err = &url.Error{Op: "read", URL: pathpkg.Join(p.redactedURL, path), Err: err} + return p.versionError(version, err) + } + if lr.N <= 0 { + return p.versionError(version, fmt.Errorf("downloaded zip file too large")) + } + return nil +} + +// pathEscape escapes s so it can be used in a path. +// That is, it escapes things like ? and # (which really shouldn't appear anyway). +// It does not escape / to %2F: our REST API is designed so that / can be left as is. +func pathEscape(s string) string { + return strings.ReplaceAll(url.PathEscape(s), "%2F", "/") +} diff --git a/vendor/github.com/goplus/mod/modfile/ext.go b/vendor/github.com/goplus/mod/modfile/ext.go new file mode 100644 index 0000000000..81b308f6db --- /dev/null +++ b/vendor/github.com/goplus/mod/modfile/ext.go @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modfile + +import ( + "fmt" + "path" + "strings" + + "github.com/qiniu/x/errors" +) + +// can be "_[class].gox" or ".[class]" +func isExt(s string) bool { + return len(s) > 1 && (s[0] == '_' || s[0] == '.') +} + +func parseExt(s *string) (t string, err error) { + t, err = parseString(s) + if err != nil { + goto failed + } + if isExt(t) { + return + } + err = errors.New("invalid ext format") +failed: + return "", &InvalidExtError{ + Ext: *s, + Err: err, + } +} + +type InvalidExtError struct { + Ext string + Err error +} + +func (e *InvalidExtError) Error() string { + return fmt.Sprintf("ext %s invalid: %s", e.Ext, e.Err) +} + +func (e *InvalidExtError) Unwrap() error { return e.Err } + +// SplitFname splits fname into (className, classExt). +func SplitFname(fname string) (className, classExt string) { + classExt = path.Ext(fname) + className = fname[:len(fname)-len(classExt)] + if hasGoxExt := (classExt == ".gox"); hasGoxExt { + if n := strings.LastIndexByte(className, '_'); n > 0 { + className, classExt = fname[:n], fname[n:] + } + } + return +} + +// ClassExt returns classExt of specified fname. +func ClassExt(fname string) string { + _, ext := SplitFname(fname) + return ext +} diff --git a/vendor/github.com/goplus/mod/modfile/print.go b/vendor/github.com/goplus/mod/modfile/print.go new file mode 100644 index 0000000000..4cd364d718 --- /dev/null +++ b/vendor/github.com/goplus/mod/modfile/print.go @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modfile + +import "golang.org/x/mod/modfile" + +// Format returns a gop.mod file as a byte slice, formatted in standard style. +func Format(f *FileSyntax) []byte { + return modfile.Format(f) +} diff --git a/vendor/github.com/goplus/mod/modfile/read.go b/vendor/github.com/goplus/mod/modfile/read.go new file mode 100644 index 0000000000..67b9023209 --- /dev/null +++ b/vendor/github.com/goplus/mod/modfile/read.go @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modfile + +import ( + "golang.org/x/mod/modfile" +) + +// A Position describes an arbitrary source position in a file, including the +// file, line, column, and byte offset. +type Position = modfile.Position + +// An Expr represents an input element. +type Expr = modfile.Expr + +// A Comment represents a single // comment. +type Comment = modfile.Comment + +// Comments collects the comments associated with an expression. +type Comments = modfile.Comments + +// A FileSyntax represents an entire gop.mod file. +type FileSyntax = modfile.FileSyntax + +// A CommentBlock represents a top-level block of comments separate +// from any rule. +type CommentBlock = modfile.CommentBlock + +// A Line is a single line of tokens. +type Line = modfile.Line + +// A LineBlock is a factored block of lines, like +// +// require ( +// "x" +// "y" +// ) +// +type LineBlock = modfile.LineBlock + +// An LParen represents the beginning of a parenthesized line block. +// It is a place to store suffix comments. +type LParen = modfile.LParen + +// An RParen represents the end of a parenthesized line block. +// It is a place to store whole-line (before) comments. +type RParen = modfile.RParen + +// ModulePath returns the module path from the gopmod file text. +// If it cannot find a module path, it returns an empty string. +// It is tolerant of unrelated problems in the gop.mod file. +func ModulePath(mod []byte) string { + return modfile.ModulePath(mod) +} diff --git a/vendor/github.com/goplus/mod/modfile/rule.go b/vendor/github.com/goplus/mod/modfile/rule.go new file mode 100644 index 0000000000..af7ffc0d0e --- /dev/null +++ b/vendor/github.com/goplus/mod/modfile/rule.go @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modfile + +import ( + "fmt" + "regexp" + "runtime" + "strconv" + "strings" + + "github.com/qiniu/x/errors" + "golang.org/x/mod/modfile" + "golang.org/x/mod/module" +) + +// A File is the parsed, interpreted form of a gop.mod file. +type File struct { + Gop *Gop + Projects []*Project + Import []*Import + + Syntax *FileSyntax +} + +func (p *File) addProj(proj *Project) { + p.Projects = append(p.Projects, proj) +} + +func (p *File) proj() *Project { // current project + n := len(p.Projects) + if n == 0 { + return nil + } + return p.Projects[n-1] +} + +// A Module is the module statement. +type Module = modfile.Module + +// A Gop is the gop statement. +type Gop = modfile.Go + +// A Import is the import statement. +type Import struct { + ClassfileMod string // module path of classfile + Syntax *Line +} + +// A Project is the project statement. +type Project struct { + Ext string // can be "_[class].gox" or ".[class]", eg "_yap.gox" or ".gmx" + Class string // "Game" + Works []*Class // work class of classfile + PkgPaths []string // package paths of classfile + Syntax *Line +} + +// IsProj checks if a (ext, fname) pair is a project file or not. +func (p *Project) IsProj(ext, fname string) bool { + for _, w := range p.Works { + if w.Ext == ext { + if ext != p.Ext || fname != "main"+ext { + return false + } + break + } + } + return true +} + +// A Class is the work class statement. +type Class struct { + Ext string // can be "_[class].gox" or ".[class]", eg "_yap.gox" or ".spx" + Class string // "Sprite" + Syntax *Line +} + +type VersionFixer = modfile.VersionFixer + +// Parse parses and returns a gop.mod file. +// +// file is the name of the file, used in positions and errors. +// +// data is the content of the file. +// +// fix is an optional function that canonicalizes module versions. +// If fix is nil, all module versions must be canonical (module.CanonicalVersion +// must return the same string). +func Parse(file string, data []byte, fix VersionFixer) (*File, error) { + return parseToFile(file, data, fix, true) +} + +// ParseLax is like Parse but ignores unknown statements. +// It is used when parsing gop.mod files other than the main module, +// under the theory that most statement types we add in the future will +// only apply in the main module, like exclude and replace, +// and so we get better gradual deployments if old go commands +// simply ignore those statements when found in gop.mod files +// in dependencies. +func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { + return parseToFile(file, data, fix, false) +} + +func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) { + f, err := modfile.ParseLax(file, data, fix) + if err != nil { + err = errors.NewWith(err, `modfile.ParseLax(file, data, fix)`, -2, "modfile.ParseLax", file, data, fix) + return + } + parsed = &File{Syntax: f.Syntax} + + var errs ErrorList + var fs = f.Syntax + for _, x := range fs.Stmt { + switch x := x.(type) { + case *Line: + parsed.parseVerb(&errs, x.Token[0], x, x.Token[1:], strict) + case *LineBlock: + verb := x.Token[0] + for _, line := range x.Line { + parsed.parseVerb(&errs, verb, line, line.Token, strict) + } + } + } + if len(errs) > 0 { + return nil, errors.NewWith(errs, `len(errs) > 0`, -1, ">", len(errs), 0) + } + return +} + +func (f *File) parseVerb(errs *ErrorList, verb string, line *Line, args []string, strict bool) { + wrapError1 := func(err error) { + errs.Add(&Error{ + Filename: f.Syntax.Name, + Pos: line.Start, + Err: err, + }) + } + wrapError := func(err error) { + file, line := fileLine(2) + e := errors.NewFrame(err, "", file, line, "wrapError", err) + wrapError1(e) + } + errorf := func(format string, args ...interface{}) { + file, line := fileLine(2) + e := errors.NewFrame(fmt.Errorf(format, args...), "", file, line, "errorf", format, args) + wrapError1(e) + } + switch verb { + case "gop": + if f.Gop != nil { + errorf("repeated gop statement") + return + } + if len(args) != 1 { + errorf("gop directive expects exactly one argument") + return + } else if !modfile.GoVersionRE.MatchString(args[0]) { + errorf("invalid gop version '%s': must match format 1.23", args[0]) + return + } + f.Gop = &Gop{Syntax: line} + f.Gop.Version = args[0] + case "import", "register": // register => import + if len(args) != 1 { + errorf("import directive expects exactly one argument") + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + err = module.CheckPath(s) + if err != nil { + wrapError(err) + return + } + f.Import = append(f.Import, &Import{ + ClassfileMod: s, + Syntax: line, + }) + case "project": + if len(args) < 1 { + errorf("usage: project [.projExt ProjClass] classFilePkgPath ...") + return + } + if isExt(args[0]) { + if len(args) < 3 || strings.Contains(args[1], "/") { + errorf("usage: project [.projExt ProjClass] classFilePkgPath ...") + return + } + ext, err := parseExt(&args[0]) + if err != nil { + wrapError(err) + return + } + class, err := parseSymbol(&args[1]) + if err != nil { + wrapError(err) + return + } + pkgPaths, err := parsePkgPaths(args[2:]) + if err != nil { + wrapError(err) + return + } + f.addProj(&Project{ + Ext: ext, Class: class, PkgPaths: pkgPaths, Syntax: line, + }) + return + } + pkgPaths, err := parsePkgPaths(args) + if err != nil { + wrapError(err) + return + } + f.addProj(&Project{ + PkgPaths: pkgPaths, Syntax: line, + }) + case "class": + proj := f.proj() + if proj == nil { + errorf("work class must declare after a project definition") + return + } + if len(args) < 2 { + errorf("usage: class .workExt WorkClass") + return + } + workExt, err := parseExt(&args[0]) + if err != nil { + wrapError(err) + return + } + class, err := parseSymbol(&args[1]) + if err != nil { + wrapError(err) + return + } + proj.Works = append(proj.Works, &Class{ + Ext: workExt, + Class: class, + Syntax: line, + }) + default: + if strict { + errorf("unknown directive: %s", verb) + } + } +} + +func fileLine(n int) (file string, line int) { + _, file, line, _ = runtime.Caller(n) + return +} + +// IsDirectoryPath reports whether the given path should be interpreted +// as a directory path. Just like on the go command line, relative paths +// and rooted paths are directory paths; the rest are module paths. +func IsDirectoryPath(ns string) bool { + return modfile.IsDirectoryPath(ns) +} + +// MustQuote reports whether s must be quoted in order to appear as +// a single token in a gop.mod line. +func MustQuote(s string) bool { + return modfile.MustQuote(s) +} + +// AutoQuote returns s or, if quoting is required for s to appear in a gop.mod, +// the quotation of s. +func AutoQuote(s string) string { + return modfile.AutoQuote(s) +} + +var ( + symbolRE = regexp.MustCompile("\\*?[A-Z]\\w*") +) + +// TODO: to be optimized +func parseSymbol(s *string) (t string, err error) { + t, err = parseString(s) + if err != nil { + goto failed + } + if symbolRE.MatchString(t) { + return + } + err = errors.New("invalid Go export symbol format") +failed: + return "", &InvalidSymbolError{ + Sym: *s, + Err: err, + } +} + +func parseString(s *string) (string, error) { + t := *s + if strings.HasPrefix(t, `"`) { + var err error + if t, err = strconv.Unquote(t); err != nil { + return "", err + } + } else if strings.ContainsAny(t, "\"'`") { + // Other quotes are reserved both for possible future expansion + // and to avoid confusion. For example if someone types 'x' + // we want that to be a syntax error and not a literal x in literal quotation marks. + return "", fmt.Errorf("unquoted string cannot contain quote") + } + *s = AutoQuote(t) + return t, nil +} + +func parseStrings(args []string) (arr []string, err error) { + arr = make([]string, len(args)) + for i := range args { + if arr[i], err = parseString(&args[i]); err != nil { + return + } + } + return +} + +func parsePkgPaths(args []string) (paths []string, err error) { + if paths, err = parseStrings(args); err != nil { + return nil, fmt.Errorf("invalid quoted string: %v", err) + } + for _, pkg := range paths { + if !isPkgPath(pkg) { + return nil, fmt.Errorf(`"%s" is not a valid package path`, pkg) + } + } + return +} + +func isPkgPath(s string) bool { + return s != "" && (s[0] != '.' && s[0] != '_') +} + +type InvalidSymbolError struct { + Sym string + Err error +} + +func (e *InvalidSymbolError) Error() string { + return fmt.Sprintf("symbol %s invalid: %s", e.Sym, e.Err) +} + +func (e *InvalidSymbolError) Unwrap() error { return e.Err } + +type ErrorList = errors.List +type Error modfile.Error + +func (p *Error) Error() string { + return (*modfile.Error)(p).Error() +} + +func (p *Error) Unwrap() error { + return p.Err +} + +func (p *Error) Summary() string { + cpy := *(*modfile.Error)(p) + cpy.Err = errors.New(errors.Summary(p.Unwrap())) + return cpy.Error() +} + +// ----------------------------------------------------------------------------- + +const ( + directiveInvalid = iota + directiveModule + directiveGop + directiveProject + directiveClass +) + +const ( + directiveLineBlock = 0x80 + iota + directiveImport +) + +var directiveWeights = map[string]int{ + "module": directiveModule, + "gop": directiveGop, + "import": directiveImport, + "register": directiveImport, // register => import + "project": directiveProject, + "class": directiveClass, +} + +func getWeight(e Expr) int { + if line, ok := e.(*Line); ok { + return directiveWeights[line.Token[0]] + } + if w, ok := directiveWeights[e.(*LineBlock).Token[0]]; ok { + return w + } + return directiveLineBlock +} + +func updateLine(line *Line, tokens ...string) { + if line.InBlock { + tokens = tokens[1:] + } + line.Token = tokens +} + +func addLine(x *FileSyntax, tokens ...string) *Line { + new := &Line{Token: tokens} + w := directiveWeights[tokens[0]] + for i, e := range x.Stmt { + w2 := getWeight(e) + if w <= w2 { + x.Stmt = append(x.Stmt, nil) + copy(x.Stmt[i+1:], x.Stmt[i:]) + x.Stmt[i] = new + return new + } + } + x.Stmt = append(x.Stmt, new) + return new +} + +func (f *File) AddGopStmt(version string) error { + if !modfile.GoVersionRE.MatchString(version) { + return fmt.Errorf("invalid language version string %q", version) + } + if f.Gop == nil { + if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + f.Gop = &Gop{ + Version: version, + Syntax: addLine(f.Syntax, "gop", version), + } + } else { + f.Gop.Version = version + updateLine(f.Gop.Syntax, "gop", version) + } + return nil +} + +func (f *File) AddImport(modPath string) { + for _, r := range f.Import { + if r.ClassfileMod == modPath { + return + } + } + f.AddNewImport(modPath) +} + +func (f *File) AddNewImport(modPath string) { + line := addLine(f.Syntax, "import", AutoQuote(modPath)) + r := &Import{ + ClassfileMod: modPath, + Syntax: line, + } + f.Import = append(f.Import, r) +} + +func (f *File) Format() ([]byte, error) { + return modfile.Format(f.Syntax), nil +} + +// ----------------------------------------------------------------------------- diff --git a/vendor/github.com/goplus/mod/modload/module.go b/vendor/github.com/goplus/mod/modload/module.go new file mode 100644 index 0000000000..3d9e82642d --- /dev/null +++ b/vendor/github.com/goplus/mod/modload/module.go @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package modload + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/goplus/mod" + "github.com/goplus/mod/modfile" + "github.com/qiniu/x/errors" + "golang.org/x/mod/module" + + gomodfile "golang.org/x/mod/modfile" +) + +var ( + ErrNoModDecl = errors.New("no module declaration in a .mod file") + ErrNoModRoot = errors.New("go.mod file not found in current directory or any parent directory") + ErrSaveDefault = errors.New("attemp to save default project") +) + +type Module struct { + *gomodfile.File + Opt *modfile.File +} + +// HasModfile returns if this module exists or not. +func (p Module) HasModfile() bool { + return p.Syntax != nil +} + +// Modfile returns absolute path of the module file. +func (p Module) Modfile() string { + if syn := p.Syntax; syn != nil { + return syn.Name + } + return "" +} + +// Root returns absolute root path of this module. +func (p Module) Root() string { + if syn := p.Syntax; syn != nil { + return filepath.Dir(syn.Name) + } + return "" +} + +// Path returns the module path. +func (p Module) Path() string { + if mod := p.Module; mod != nil { + return mod.Mod.Path + } + return "" +} + +// DepMods returns all depended modules. +// If a depended module path is replace to be a local path, it will be canonical to an absolute path. +func (p Module) DepMods() map[string]module.Version { + vers := make(map[string]module.Version) + for _, r := range p.Require { + if r.Mod.Path != "" { + vers[r.Mod.Path] = r.Mod + } + } + for _, r := range p.Replace { + if r.Old.Path != "" { + real := r.New + if real.Version == "" { + if strings.HasPrefix(real.Path, ".") { + dir, _ := filepath.Split(p.Modfile()) + real.Path = dir + real.Path + } + if a, err := filepath.Abs(real.Path); err == nil { + real.Path = a + } + } + vers[r.Old.Path] = real + } + } + return vers +} + +// Create creates a new module in `dir`. +// You should call `Save` manually to save this module. +func Create(dir string, modPath, goVer, gopVer string) (p Module, err error) { + dir, err = filepath.Abs(dir) + if err != nil { + return + } + + gomod := filepath.Join(dir, "go.mod") + if _, err := os.Stat(gomod); err == nil { + return Module{}, fmt.Errorf("gop: %s already exists", gomod) + } + + gopmod := filepath.Join(dir, "gop.mod") + if _, err := os.Stat(gopmod); err == nil { + return Module{}, fmt.Errorf("gop: %s already exists", gopmod) + } + + mod := newGoMod(gomod, modPath, goVer) + opt := newGopMod(gopmod, gopVer) + return Module{mod, opt}, nil +} + +func newGoMod(gomod, modPath, goVer string) *gomodfile.File { + mod := new(gomodfile.File) + mod.AddModuleStmt(modPath) + mod.AddGoStmt(goVer) + mod.Syntax.Name = gomod + return mod +} + +func newGopMod(gopmod, gopVer string) *modfile.File { + opt := new(modfile.File) + opt.AddGopStmt(gopVer) + opt.Syntax.Name = gopmod + return opt +} + +// fixVersion returns a modfile.VersionFixer implemented using the Query function. +// +// It resolves commit hashes and branch names to versions, +// canonicalizes versions that appeared in early vgo drafts, +// and does nothing for versions that already appear to be canonical. +// +// The VersionFixer sets 'fixed' if it ever returns a non-canonical version. +func fixVersion(fixed *bool) modfile.VersionFixer { + return func(path, vers string) (resolved string, err error) { + // do nothing + return vers, nil + } +} + +// Load loads a module from specified directory. +func Load(dir string) (p Module, err error) { + dir, gomod, err := mod.FindGoMod(dir) + if err != nil { + err = errors.NewWith(err, `mod.FindGoMod(dir)`, -2, "mod.FindGoMod", dir) + return + } + return LoadFrom(gomod, filepath.Join(dir, "gop.mod")) +} + +// LoadFrom loads a module from specified go.mod file and an optional gop.mod file. +func LoadFrom(gomod, gopmod string) (p Module, err error) { + return LoadFromEx(gomod, gopmod, os.ReadFile) +} + +// LoadFromEx loads a module from specified go.mod file and an optional gop.mod file. +// It can specify a customized `readFile` to read file content. +func LoadFromEx(gomod, gopmod string, readFile func(string) ([]byte, error)) (p Module, err error) { + data, err := readFile(gomod) + if err != nil { + err = errors.NewWith(err, `readFile(gomod)`, -2, "readFile", gomod) + return + } + + var fixed bool + fix := fixVersion(&fixed) + // it is go.mod file, so we need to use "Parse" parse it + f, err := gomodfile.Parse(gomod, data, fix) + if err != nil { + err = errors.NewWith(err, `gomodfile.Parse(gomod, data, fix)`, -2, "gomodfile.Parse", gomod, data, fix) + return + } + mod := f.Module + if mod == nil { + // No module declaration. Must add module path. + err = errors.NewWith(ErrNoModDecl, `mod == nil`, -2, "==", mod, nil) + return + } + if mod.Mod.Path == "std" { + mod.Mod.Path = "" // the Go std module + } + + var opt *modfile.File + data, err = readFile(gopmod) + if err != nil { + opt = newGopMod(gopmod, defaultGopVer) + } else { + opt, err = modfile.ParseLax(gopmod, data, fix) + if err != nil { + err = errors.NewWith(err, `modfile.Parse(gopmod, data, fix)`, -2, "modfile.Parse", gopmod, data, fix) + return + } + } + importClassfileFromGoMod(opt, f) + return Module{f, opt}, nil +} + +func importClassfileFromGoMod(opt *modfile.File, f *gomodfile.File) { + for _, r := range f.Require { + if isClass(r) { + opt.AddImport(r.Mod.Path) + } + } +} + +func isClass(r *gomodfile.Require) bool { + if line := r.Syntax; line != nil { + for _, c := range line.Suffix { + text := strings.TrimLeft(c.Token[2:], " \t") + if strings.HasPrefix(text, "gop:class") { + return true + } + } + } + return false +} + +// ----------------------------------------------------------------------------- + +func (p Module) Projects() []*modfile.Project { + return p.Opt.Projects +} + +func (p Module) HasProject() bool { + return len(p.Opt.Projects) > 0 +} + +func hasGopExtended(opt *modfile.File) bool { + return len(opt.Projects) > 0 || len(opt.Import) > 0 +} + +// Save saves all changes of this module. +func (p Module) Save() (err error) { + modfile := p.Modfile() + if modfile == "" { + return ErrSaveDefault + } + data, err := p.Format() + if err != nil { + return + } + err = os.WriteFile(modfile, data, 0644) + if err != nil { + return + } + + if opt := p.Opt; hasGopExtended(opt) { + data, err = opt.Format() + if err != nil { + return + } + err = os.WriteFile(opt.Syntax.Name, data, 0644) + } + return +} + +/* +const ( + gopMod = "github.com/goplus/gop" +) + +// UpdateGoMod updates the go.mod file. +func (p Module) UpdateGoMod(env *env.Gop, checkChanged bool) error { + gopmod := p.Modfile() + dir, file := filepath.Split(gopmod) + if file == "go.mod" { + return nil + } + gomod := dir + "go.mod" + if checkChanged && notChanged(gomod, gopmod) { + return nil + } + return p.saveGoMod(gomod, env) +} + +func (p Module) saveGoMod(gomod string, env *env.Gop) error { + gof := p.convToGoMod(env) + data, err := gof.Format() + if err == nil { + err = os.WriteFile(gomod, data, 0644) + } + return err +} + +func (p Module) convToGoMod(env *env.Gop) *gomodfile.File { + copy := p.File.File + copy.Syntax = cloneGoFileSyntax(copy.Syntax) + addRequireIfNotExist(©, gopMod, env.Version) + addReplaceIfNotExist(©, gopMod, "", env.Root, "") + return © +} + +func addRequireIfNotExist(f *gomodfile.File, path, vers string) { + for _, r := range f.Require { + if r.Mod.Path == path { + return + } + } + f.AddNewRequire(path, vers, false) +} + +func addReplaceIfNotExist(f *gomodfile.File, oldPath, oldVers, newPath, newVers string) { + for _, r := range f.Replace { + if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { + return + } + } + f.AddReplace(oldPath, oldVers, newPath, newVers) +} + +func notChanged(target, src string) bool { + fiTarget, err := os.Stat(target) + if err != nil { + return false + } + fiSrc, err := os.Stat(src) + if err != nil { + return false + } + return fiTarget.ModTime().After(fiSrc.ModTime()) +} + +// ----------------------------------------------------------------------------- + +func cloneGoFileSyntax(syn *modfile.FileSyntax) *modfile.FileSyntax { + stmt := make([]modfile.Expr, 0, len(syn.Stmt)) + for _, e := range syn.Stmt { + if isGopOrDeletedExpr(e) { + continue + } + stmt = append(stmt, cloneExpr(e)) + } + return &modfile.FileSyntax{ + Name: syn.Name, + Comments: syn.Comments, + Stmt: stmt, + } +} + +func cloneExpr(e modfile.Expr) modfile.Expr { + if v, ok := e.(*modfile.LineBlock); ok { + copy := *v + return © + } + return e +} + +func isGopOrDeletedExpr(e modfile.Expr) bool { + switch verb := getVerb(e); verb { + case "", "gop", "register", "project", "class": + return true + } + return false +} + +func getVerb(e modfile.Expr) string { + if line, ok := e.(*modfile.Line); ok { + if token := line.Token; len(token) > 0 { + return token[0] + } + return "" // deleted line + } + return e.(*modfile.LineBlock).Token[0] +} +*/ + +// ----------------------------------------------------------------------------- + +const ( + defaultGoVer = "1.18" + defaultGopVer = "1.1" +) + +// Default represents the default gop.mod object. +var Default = Module{ + File: &gomodfile.File{ + Module: &gomodfile.Module{}, + Go: &gomodfile.Go{Version: defaultGoVer}, + }, + Opt: &modfile.File{ + Gop: &modfile.Gop{Version: defaultGopVer}, + }, +} + +// ----------------------------------------------------------------------------- diff --git a/vendor/github.com/qiniu/x/LICENSE b/vendor/github.com/qiniu/x/LICENSE new file mode 100644 index 0000000000..675c2ec958 --- /dev/null +++ b/vendor/github.com/qiniu/x/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/vendor/github.com/qiniu/x/errors/errors.go b/vendor/github.com/qiniu/x/errors/errors.go new file mode 100644 index 0000000000..a9531774b8 --- /dev/null +++ b/vendor/github.com/qiniu/x/errors/errors.go @@ -0,0 +1,331 @@ +/* + Copyright 2022 Qiniu Limited (qiniu.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package errors provides errors stack tracking utilities. +package errors + +import ( + "errors" + "fmt" + "io" + "reflect" + "runtime" + "strconv" + "strings" +) + +// -------------------------------------------------------------------- + +// New returns an error that formats as the given text. +// Each call to New returns a distinct error value even if the text is identical. +func New(msg string) error { + return errors.New(msg) +} + +// Err returns the cause error. +func Err(err error) error { + if e, ok := err.(*Frame); ok { + return Err(e.Err) + } + return err +} + +// Summary returns summary of specified error. +func Summary(err error) string { + e, ok := err.(interface { + Summary() string + }) + if !ok { + return err.Error() + } + return e.Summary() +} + +// -------------------------------------------------------------------- + +// NotFound is a generic NotFound error. +type NotFound struct { + Category string +} + +func (p *NotFound) Error() string { + return p.Category + " not found" +} + +// IsNotFound unwraps err and checks it is a *NotFound object or not. +func IsNotFound(err error) bool { + for { + if e, ok := err.(interface{ Unwrap() error }); ok { + err = e.Unwrap() + } else { + _, ok = err.(*NotFound) + return ok + } + } +} + +// -------------------------------------------------------------------- + +// List represents a list of errors. +type List []error + +// Add adds an error into the error list. +func (p *List) Add(err error) { + if l, ok := err.(List); ok { + *p = append(*p, l...) + return + } + *p = append(*p, err) +} + +// Error returns all errors joined with "\n". +func (p List) Error() string { + n := len(p) + if n >= 2 { + s := make([]string, n) + for i, v := range p { + s[i] = v.Error() + } + return strings.Join(s, "\n") + } + if n == 1 { + return p[0].Error() + } + return "" +} + +// Summary returns summary of all errors. +func (p List) Summary() string { + n := len(p) + if n >= 2 { + s := make([]string, n) + for i, v := range p { + s[i] = Summary(v) + } + return strings.Join(s, "\n") + } + if n == 1 { + return Summary(p[0]) + } + return "" +} + +// ToError converts error list into an error. +// If list length == 0, it returns nil; +// If list length == 1, it returns the list item. +// If list length > 1, it returns the error list itself. +func (p List) ToError() error { + switch len(p) { + case 1: + return p[0] + case 0: + return nil + } + return p +} + +// Format is required by fmt.Formatter +func (p List) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + io.WriteString(s, p.Error()) + case 's': + io.WriteString(s, p.Summary()) + case 'q': + fmt.Fprintf(s, "%q", p.Error()) + } +} + +// -------------------------------------------------------------------- + +// Frame represents an error frame. +type Frame struct { + Err error + Func string + Args []interface{} + Code string + File string + Line int +} + +// NewWith creates a new error frame. +func NewWith(err error, code string, n int, fn string, args ...interface{}) *Frame { + file, line := fileLine() + return &Frame{Err: err, Func: fn, Args: args, Code: code, File: file, Line: line + n} +} + +func fileLine() (file string, line int) { + _, file, line, _ = runtime.Caller(2) + return +} + +// NewFrame creates a new error frame. +func NewFrame(err error, code, file string, line int, fn string, args ...interface{}) *Frame { + return &Frame{Err: err, Func: fn, Args: args, Code: code, File: file, Line: line} +} + +func (p *Frame) Error() string { + return string(errorDetail(make([]byte, 0, 32), p)) +} + +func (p *Frame) Summary() string { + return Summary(p.Err) +} + +func errorDetail(b []byte, p *Frame) []byte { + if f, ok := p.Err.(*Frame); ok { + b = errorDetail(b, f) + } else { + b = append(b, p.Err.Error()...) + b = append(b, "\n\n===> errors stack:\n"...) + } + b = append(b, p.Func...) + b = append(b, '(') + b = argsDetail(b, p.Args) + b = append(b, ")\n\t"...) + b = append(b, p.File...) + b = append(b, ':') + b = strconv.AppendInt(b, int64(p.Line), 10) + b = append(b, ' ') + b = append(b, p.Code...) + b = append(b, '\n') + return b +} + +func argsDetail(b []byte, args []interface{}) []byte { + nlast := len(args) - 1 + for i, arg := range args { + b = appendValue(b, arg) + if i != nlast { + b = append(b, ',', ' ') + } + } + return b +} + +func appendValue(b []byte, arg interface{}) []byte { + if arg == nil { + return append(b, "nil"...) + } + v := reflect.ValueOf(arg) + kind := v.Kind() + if kind >= reflect.Bool && kind <= reflect.Complex128 { + return append(b, fmt.Sprint(arg)...) + } + if kind == reflect.String { + val := arg.(string) + if len(val) > 32 { + val = val[:16] + "..." + val[len(val)-16:] + } + return strconv.AppendQuote(b, val) + } + if kind == reflect.Array { + return append(b, "Array"...) + } + if kind == reflect.Struct { + return append(b, "Struct"...) + } + val := v.Pointer() + b = append(b, '0', 'x') + return strconv.AppendInt(b, int64(val), 16) +} + +// Unwrap provides compatibility for Go 1.13 error chains. +func (p *Frame) Unwrap() error { + return p.Err +} + +// Format is required by fmt.Formatter +func (p *Frame) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + io.WriteString(s, p.Error()) + case 's': + io.WriteString(s, p.Summary()) + case 'q': + fmt.Fprintf(s, "%q", p.Error()) + } +} + +// -------------------------------------------------------------------- + +// CallDetail print a function call shortly. +func CallDetail(msg []byte, fn interface{}, args ...interface{}) []byte { + f := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()) + if f != nil { + msg = append(msg, f.Name()...) + msg = append(msg, '(') + msg = argsDetail(msg, args) + msg = append(msg, ')') + } + return msg +} + +// -------------------------------------------------------------------- + +// ErrorInfo is provided for backward compatibility +// +// Deprecated: Use Frame instead. +type ErrorInfo = Frame + +// Detail is provided for backward compatibility +func (p *ErrorInfo) Detail(err error) *ErrorInfo { + p.Code = err.Error() + return p +} + +// NestedObject is provided for backward compatibility +func (p *ErrorInfo) NestedObject() interface{} { + return p.Err +} + +// ErrorDetail is provided for backward compatibility +func (p *ErrorInfo) ErrorDetail() string { + return p.Error() +} + +// AppendErrorDetail is provided for backward compatibility +func (p *ErrorInfo) AppendErrorDetail(b []byte) []byte { + return errorDetail(b, p) +} + +// SummaryErr is provided for backward compatibility +func (p *ErrorInfo) SummaryErr() error { + return p.Err +} + +// Info is provided for backward compatibility +// +// Deprecated: Use NewWith instead. +func Info(err error, cmd ...interface{}) *ErrorInfo { + return &Frame{Err: err, Args: cmd} +} + +// InfoEx is provided for backward compatibility +// +// Deprecated: Use NewWith instead. +func InfoEx(calldepth int, err error, cmd ...interface{}) *ErrorInfo { + return &Frame{Err: err, Args: cmd} +} + +// Detail is provided for backward compatibility +// +// Deprecated: Use err.Error() instead. +func Detail(err error) string { + return err.Error() +} + +// -------------------------------------------------------------------- diff --git a/vendor/github.com/qiniu/x/errors/go113.go b/vendor/github.com/qiniu/x/errors/go113.go new file mode 100644 index 0000000000..d21c31f7dd --- /dev/null +++ b/vendor/github.com/qiniu/x/errors/go113.go @@ -0,0 +1,74 @@ +//go:build go1.13 +// +build go1.13 + +/* + Copyright 2022 Qiniu Limited (qiniu.com) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package errors + +import ( + "errors" +) + +// -------------------------------------------------------------------- + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + return errors.Unwrap(err) +} + +// Is reports whether any error in err's chain matches target. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +// +// An error type might provide an Is method so it can be treated as equivalent +// to an existing error. For example, if MyError defines +// +// func (m MyError) Is(target error) bool { return target == os.ErrExist } +// +// then Is(MyError{}, os.ErrExist) returns true. See syscall.Errno.Is for +// an example in the standard library. +func Is(err, target error) bool { + return errors.Is(err, target) +} + +// As finds the first error in err's chain that matches target, and if so, sets +// target to that error value and returns true. Otherwise, it returns false. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error matches target if the error's concrete value is assignable to the value +// pointed to by target, or if the error has a method As(interface{}) bool such that +// As(target) returns true. In the latter case, the As method is responsible for +// setting target. +// +// An error type might provide an As method so it can be treated as if it were a +// a different error type. +// +// As panics if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. +func As(err error, target interface{}) bool { + return errors.As(err, target) +} + +// -------------------------------------------------------------------- diff --git a/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go b/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go new file mode 100644 index 0000000000..150f887e7a --- /dev/null +++ b/vendor/golang.org/x/mod/internal/lazyregexp/lazyre.go @@ -0,0 +1,78 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package lazyregexp is a thin wrapper over regexp, allowing the use of global +// regexp variables without forcing them to be compiled at init. +package lazyregexp + +import ( + "os" + "regexp" + "strings" + "sync" +) + +// Regexp is a wrapper around [regexp.Regexp], where the underlying regexp will be +// compiled the first time it is needed. +type Regexp struct { + str string + once sync.Once + rx *regexp.Regexp +} + +func (r *Regexp) re() *regexp.Regexp { + r.once.Do(r.build) + return r.rx +} + +func (r *Regexp) build() { + r.rx = regexp.MustCompile(r.str) + r.str = "" +} + +func (r *Regexp) FindSubmatch(s []byte) [][]byte { + return r.re().FindSubmatch(s) +} + +func (r *Regexp) FindStringSubmatch(s string) []string { + return r.re().FindStringSubmatch(s) +} + +func (r *Regexp) FindStringSubmatchIndex(s string) []int { + return r.re().FindStringSubmatchIndex(s) +} + +func (r *Regexp) ReplaceAllString(src, repl string) string { + return r.re().ReplaceAllString(src, repl) +} + +func (r *Regexp) FindString(s string) string { + return r.re().FindString(s) +} + +func (r *Regexp) FindAllString(s string, n int) []string { + return r.re().FindAllString(s, n) +} + +func (r *Regexp) MatchString(s string) bool { + return r.re().MatchString(s) +} + +func (r *Regexp) SubexpNames() []string { + return r.re().SubexpNames() +} + +var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test") + +// New creates a new lazy regexp, delaying the compiling work until it is first +// needed. If the code is being run as part of tests, the regexp compiling will +// happen immediately. +func New(str string) *Regexp { + lr := &Regexp{str: str} + if inTest { + // In tests, always compile the regexps early. + lr.re() + } + return lr +} diff --git a/vendor/golang.org/x/mod/modfile/print.go b/vendor/golang.org/x/mod/modfile/print.go new file mode 100644 index 0000000000..2a0123d4b9 --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/print.go @@ -0,0 +1,184 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Module file printer. + +package modfile + +import ( + "bytes" + "fmt" + "strings" +) + +// Format returns a go.mod file as a byte slice, formatted in standard style. +func Format(f *FileSyntax) []byte { + pr := &printer{} + pr.file(f) + + // remove trailing blank lines + b := pr.Bytes() + for len(b) > 0 && b[len(b)-1] == '\n' && (len(b) == 1 || b[len(b)-2] == '\n') { + b = b[:len(b)-1] + } + return b +} + +// A printer collects the state during printing of a file or expression. +type printer struct { + bytes.Buffer // output buffer + comment []Comment // pending end-of-line comments + margin int // left margin (indent), a number of tabs +} + +// printf prints to the buffer. +func (p *printer) printf(format string, args ...interface{}) { + fmt.Fprintf(p, format, args...) +} + +// indent returns the position on the current line, in bytes, 0-indexed. +func (p *printer) indent() int { + b := p.Bytes() + n := 0 + for n < len(b) && b[len(b)-1-n] != '\n' { + n++ + } + return n +} + +// newline ends the current line, flushing end-of-line comments. +func (p *printer) newline() { + if len(p.comment) > 0 { + p.printf(" ") + for i, com := range p.comment { + if i > 0 { + p.trim() + p.printf("\n") + for i := 0; i < p.margin; i++ { + p.printf("\t") + } + } + p.printf("%s", strings.TrimSpace(com.Token)) + } + p.comment = p.comment[:0] + } + + p.trim() + if b := p.Bytes(); len(b) == 0 || (len(b) >= 2 && b[len(b)-1] == '\n' && b[len(b)-2] == '\n') { + // skip the blank line at top of file or after a blank line + } else { + p.printf("\n") + } + for i := 0; i < p.margin; i++ { + p.printf("\t") + } +} + +// trim removes trailing spaces and tabs from the current line. +func (p *printer) trim() { + // Remove trailing spaces and tabs from line we're about to end. + b := p.Bytes() + n := len(b) + for n > 0 && (b[n-1] == '\t' || b[n-1] == ' ') { + n-- + } + p.Truncate(n) +} + +// file formats the given file into the print buffer. +func (p *printer) file(f *FileSyntax) { + for _, com := range f.Before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + for i, stmt := range f.Stmt { + switch x := stmt.(type) { + case *CommentBlock: + // comments already handled + p.expr(x) + + default: + p.expr(x) + p.newline() + } + + for _, com := range stmt.Comment().After { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + + if i+1 < len(f.Stmt) { + p.newline() + } + } +} + +func (p *printer) expr(x Expr) { + // Emit line-comments preceding this expression. + if before := x.Comment().Before; len(before) > 0 { + // Want to print a line comment. + // Line comments must be at the current margin. + p.trim() + if p.indent() > 0 { + // There's other text on the line. Start a new line. + p.printf("\n") + } + // Re-indent to margin. + for i := 0; i < p.margin; i++ { + p.printf("\t") + } + for _, com := range before { + p.printf("%s", strings.TrimSpace(com.Token)) + p.newline() + } + } + + switch x := x.(type) { + default: + panic(fmt.Errorf("printer: unexpected type %T", x)) + + case *CommentBlock: + // done + + case *LParen: + p.printf("(") + case *RParen: + p.printf(")") + + case *Line: + p.tokens(x.Token) + + case *LineBlock: + p.tokens(x.Token) + p.printf(" ") + p.expr(&x.LParen) + p.margin++ + for _, l := range x.Line { + p.newline() + p.expr(l) + } + p.margin-- + p.newline() + p.expr(&x.RParen) + } + + // Queue end-of-line comments for printing when we + // reach the end of the line. + p.comment = append(p.comment, x.Comment().Suffix...) +} + +func (p *printer) tokens(tokens []string) { + sep := "" + for _, t := range tokens { + if t == "," || t == ")" || t == "]" || t == "}" { + sep = "" + } + p.printf("%s%s", sep, t) + sep = " " + if t == "(" || t == "[" || t == "{" { + sep = "" + } + } +} diff --git a/vendor/golang.org/x/mod/modfile/read.go b/vendor/golang.org/x/mod/modfile/read.go new file mode 100644 index 0000000000..5b5bb5e115 --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/read.go @@ -0,0 +1,958 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modfile + +import ( + "bytes" + "errors" + "fmt" + "os" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// A Position describes an arbitrary source position in a file, including the +// file, line, column, and byte offset. +type Position struct { + Line int // line in input (starting at 1) + LineRune int // rune in line (starting at 1) + Byte int // byte in input (starting at 0) +} + +// add returns the position at the end of s, assuming it starts at p. +func (p Position) add(s string) Position { + p.Byte += len(s) + if n := strings.Count(s, "\n"); n > 0 { + p.Line += n + s = s[strings.LastIndex(s, "\n")+1:] + p.LineRune = 1 + } + p.LineRune += utf8.RuneCountInString(s) + return p +} + +// An Expr represents an input element. +type Expr interface { + // Span returns the start and end position of the expression, + // excluding leading or trailing comments. + Span() (start, end Position) + + // Comment returns the comments attached to the expression. + // This method would normally be named 'Comments' but that + // would interfere with embedding a type of the same name. + Comment() *Comments +} + +// A Comment represents a single // comment. +type Comment struct { + Start Position + Token string // without trailing newline + Suffix bool // an end of line (not whole line) comment +} + +// Comments collects the comments associated with an expression. +type Comments struct { + Before []Comment // whole-line comments before this expression + Suffix []Comment // end-of-line comments after this expression + + // For top-level expressions only, After lists whole-line + // comments following the expression. + After []Comment +} + +// Comment returns the receiver. This isn't useful by itself, but +// a [Comments] struct is embedded into all the expression +// implementation types, and this gives each of those a Comment +// method to satisfy the Expr interface. +func (c *Comments) Comment() *Comments { + return c +} + +// A FileSyntax represents an entire go.mod file. +type FileSyntax struct { + Name string // file path + Comments + Stmt []Expr +} + +func (x *FileSyntax) Span() (start, end Position) { + if len(x.Stmt) == 0 { + return + } + start, _ = x.Stmt[0].Span() + _, end = x.Stmt[len(x.Stmt)-1].Span() + return start, end +} + +// addLine adds a line containing the given tokens to the file. +// +// If the first token of the hint matches the first token of the +// line, the new line is added at the end of the block containing hint, +// extracting hint into a new block if it is not yet in one. +// +// If the hint is non-nil buts its first token does not match, +// the new line is added after the block containing hint +// (or hint itself, if not in a block). +// +// If no hint is provided, addLine appends the line to the end of +// the last block with a matching first token, +// or to the end of the file if no such block exists. +func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line { + if hint == nil { + // If no hint given, add to the last statement of the given type. + Loop: + for i := len(x.Stmt) - 1; i >= 0; i-- { + stmt := x.Stmt[i] + switch stmt := stmt.(type) { + case *Line: + if stmt.Token != nil && stmt.Token[0] == tokens[0] { + hint = stmt + break Loop + } + case *LineBlock: + if stmt.Token[0] == tokens[0] { + hint = stmt + break Loop + } + } + } + } + + newLineAfter := func(i int) *Line { + new := &Line{Token: tokens} + if i == len(x.Stmt) { + x.Stmt = append(x.Stmt, new) + } else { + x.Stmt = append(x.Stmt, nil) + copy(x.Stmt[i+2:], x.Stmt[i+1:]) + x.Stmt[i+1] = new + } + return new + } + + if hint != nil { + for i, stmt := range x.Stmt { + switch stmt := stmt.(type) { + case *Line: + if stmt == hint { + if stmt.Token == nil || stmt.Token[0] != tokens[0] { + return newLineAfter(i) + } + + // Convert line to line block. + stmt.InBlock = true + block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}} + stmt.Token = stmt.Token[1:] + x.Stmt[i] = block + new := &Line{Token: tokens[1:], InBlock: true} + block.Line = append(block.Line, new) + return new + } + + case *LineBlock: + if stmt == hint { + if stmt.Token[0] != tokens[0] { + return newLineAfter(i) + } + + new := &Line{Token: tokens[1:], InBlock: true} + stmt.Line = append(stmt.Line, new) + return new + } + + for j, line := range stmt.Line { + if line == hint { + if stmt.Token[0] != tokens[0] { + return newLineAfter(i) + } + + // Add new line after hint within the block. + stmt.Line = append(stmt.Line, nil) + copy(stmt.Line[j+2:], stmt.Line[j+1:]) + new := &Line{Token: tokens[1:], InBlock: true} + stmt.Line[j+1] = new + return new + } + } + } + } + } + + new := &Line{Token: tokens} + x.Stmt = append(x.Stmt, new) + return new +} + +func (x *FileSyntax) updateLine(line *Line, tokens ...string) { + if line.InBlock { + tokens = tokens[1:] + } + line.Token = tokens +} + +// markRemoved modifies line so that it (and its end-of-line comment, if any) +// will be dropped by (*FileSyntax).Cleanup. +func (line *Line) markRemoved() { + line.Token = nil + line.Comments.Suffix = nil +} + +// Cleanup cleans up the file syntax x after any edit operations. +// To avoid quadratic behavior, (*Line).markRemoved marks the line as dead +// by setting line.Token = nil but does not remove it from the slice +// in which it appears. After edits have all been indicated, +// calling Cleanup cleans out the dead lines. +func (x *FileSyntax) Cleanup() { + w := 0 + for _, stmt := range x.Stmt { + switch stmt := stmt.(type) { + case *Line: + if stmt.Token == nil { + continue + } + case *LineBlock: + ww := 0 + for _, line := range stmt.Line { + if line.Token != nil { + stmt.Line[ww] = line + ww++ + } + } + if ww == 0 { + continue + } + if ww == 1 { + // Collapse block into single line. + line := &Line{ + Comments: Comments{ + Before: commentsAdd(stmt.Before, stmt.Line[0].Before), + Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix), + After: commentsAdd(stmt.Line[0].After, stmt.After), + }, + Token: stringsAdd(stmt.Token, stmt.Line[0].Token), + } + x.Stmt[w] = line + w++ + continue + } + stmt.Line = stmt.Line[:ww] + } + x.Stmt[w] = stmt + w++ + } + x.Stmt = x.Stmt[:w] +} + +func commentsAdd(x, y []Comment) []Comment { + return append(x[:len(x):len(x)], y...) +} + +func stringsAdd(x, y []string) []string { + return append(x[:len(x):len(x)], y...) +} + +// A CommentBlock represents a top-level block of comments separate +// from any rule. +type CommentBlock struct { + Comments + Start Position +} + +func (x *CommentBlock) Span() (start, end Position) { + return x.Start, x.Start +} + +// A Line is a single line of tokens. +type Line struct { + Comments + Start Position + Token []string + InBlock bool + End Position +} + +func (x *Line) Span() (start, end Position) { + return x.Start, x.End +} + +// A LineBlock is a factored block of lines, like +// +// require ( +// "x" +// "y" +// ) +type LineBlock struct { + Comments + Start Position + LParen LParen + Token []string + Line []*Line + RParen RParen +} + +func (x *LineBlock) Span() (start, end Position) { + return x.Start, x.RParen.Pos.add(")") +} + +// An LParen represents the beginning of a parenthesized line block. +// It is a place to store suffix comments. +type LParen struct { + Comments + Pos Position +} + +func (x *LParen) Span() (start, end Position) { + return x.Pos, x.Pos.add(")") +} + +// An RParen represents the end of a parenthesized line block. +// It is a place to store whole-line (before) comments. +type RParen struct { + Comments + Pos Position +} + +func (x *RParen) Span() (start, end Position) { + return x.Pos, x.Pos.add(")") +} + +// An input represents a single input file being parsed. +type input struct { + // Lexing state. + filename string // name of input file, for errors + complete []byte // entire input + remaining []byte // remaining input + tokenStart []byte // token being scanned to end of input + token token // next token to be returned by lex, peek + pos Position // current input position + comments []Comment // accumulated comments + + // Parser state. + file *FileSyntax // returned top-level syntax tree + parseErrors ErrorList // errors encountered during parsing + + // Comment assignment state. + pre []Expr // all expressions, in preorder traversal + post []Expr // all expressions, in postorder traversal +} + +func newInput(filename string, data []byte) *input { + return &input{ + filename: filename, + complete: data, + remaining: data, + pos: Position{Line: 1, LineRune: 1, Byte: 0}, + } +} + +// parse parses the input file. +func parse(file string, data []byte) (f *FileSyntax, err error) { + // The parser panics for both routine errors like syntax errors + // and for programmer bugs like array index errors. + // Turn both into error returns. Catching bug panics is + // especially important when processing many files. + in := newInput(file, data) + defer func() { + if e := recover(); e != nil && e != &in.parseErrors { + in.parseErrors = append(in.parseErrors, Error{ + Filename: in.filename, + Pos: in.pos, + Err: fmt.Errorf("internal error: %v", e), + }) + } + if err == nil && len(in.parseErrors) > 0 { + err = in.parseErrors + } + }() + + // Prime the lexer by reading in the first token. It will be available + // in the next peek() or lex() call. + in.readToken() + + // Invoke the parser. + in.parseFile() + if len(in.parseErrors) > 0 { + return nil, in.parseErrors + } + in.file.Name = in.filename + + // Assign comments to nearby syntax. + in.assignComments() + + return in.file, nil +} + +// Error is called to report an error. +// Error does not return: it panics. +func (in *input) Error(s string) { + in.parseErrors = append(in.parseErrors, Error{ + Filename: in.filename, + Pos: in.pos, + Err: errors.New(s), + }) + panic(&in.parseErrors) +} + +// eof reports whether the input has reached end of file. +func (in *input) eof() bool { + return len(in.remaining) == 0 +} + +// peekRune returns the next rune in the input without consuming it. +func (in *input) peekRune() int { + if len(in.remaining) == 0 { + return 0 + } + r, _ := utf8.DecodeRune(in.remaining) + return int(r) +} + +// peekPrefix reports whether the remaining input begins with the given prefix. +func (in *input) peekPrefix(prefix string) bool { + // This is like bytes.HasPrefix(in.remaining, []byte(prefix)) + // but without the allocation of the []byte copy of prefix. + for i := 0; i < len(prefix); i++ { + if i >= len(in.remaining) || in.remaining[i] != prefix[i] { + return false + } + } + return true +} + +// readRune consumes and returns the next rune in the input. +func (in *input) readRune() int { + if len(in.remaining) == 0 { + in.Error("internal lexer error: readRune at EOF") + } + r, size := utf8.DecodeRune(in.remaining) + in.remaining = in.remaining[size:] + if r == '\n' { + in.pos.Line++ + in.pos.LineRune = 1 + } else { + in.pos.LineRune++ + } + in.pos.Byte += size + return int(r) +} + +type token struct { + kind tokenKind + pos Position + endPos Position + text string +} + +type tokenKind int + +const ( + _EOF tokenKind = -(iota + 1) + _EOLCOMMENT + _IDENT + _STRING + _COMMENT + + // newlines and punctuation tokens are allowed as ASCII codes. +) + +func (k tokenKind) isComment() bool { + return k == _COMMENT || k == _EOLCOMMENT +} + +// isEOL returns whether a token terminates a line. +func (k tokenKind) isEOL() bool { + return k == _EOF || k == _EOLCOMMENT || k == '\n' +} + +// startToken marks the beginning of the next input token. +// It must be followed by a call to endToken, once the token's text has +// been consumed using readRune. +func (in *input) startToken() { + in.tokenStart = in.remaining + in.token.text = "" + in.token.pos = in.pos +} + +// endToken marks the end of an input token. +// It records the actual token string in tok.text. +// A single trailing newline (LF or CRLF) will be removed from comment tokens. +func (in *input) endToken(kind tokenKind) { + in.token.kind = kind + text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)]) + if kind.isComment() { + if strings.HasSuffix(text, "\r\n") { + text = text[:len(text)-2] + } else { + text = strings.TrimSuffix(text, "\n") + } + } + in.token.text = text + in.token.endPos = in.pos +} + +// peek returns the kind of the next token returned by lex. +func (in *input) peek() tokenKind { + return in.token.kind +} + +// lex is called from the parser to obtain the next input token. +func (in *input) lex() token { + tok := in.token + in.readToken() + return tok +} + +// readToken lexes the next token from the text and stores it in in.token. +func (in *input) readToken() { + // Skip past spaces, stopping at non-space or EOF. + for !in.eof() { + c := in.peekRune() + if c == ' ' || c == '\t' || c == '\r' { + in.readRune() + continue + } + + // Comment runs to end of line. + if in.peekPrefix("//") { + in.startToken() + + // Is this comment the only thing on its line? + // Find the last \n before this // and see if it's all + // spaces from there to here. + i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n")) + suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0 + in.readRune() + in.readRune() + + // Consume comment. + for len(in.remaining) > 0 && in.readRune() != '\n' { + } + + // If we are at top level (not in a statement), hand the comment to + // the parser as a _COMMENT token. The grammar is written + // to handle top-level comments itself. + if !suffix { + in.endToken(_COMMENT) + return + } + + // Otherwise, save comment for later attachment to syntax tree. + in.endToken(_EOLCOMMENT) + in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix}) + return + } + + if in.peekPrefix("/*") { + in.Error("mod files must use // comments (not /* */ comments)") + } + + // Found non-space non-comment. + break + } + + // Found the beginning of the next token. + in.startToken() + + // End of file. + if in.eof() { + in.endToken(_EOF) + return + } + + // Punctuation tokens. + switch c := in.peekRune(); c { + case '\n', '(', ')', '[', ']', '{', '}', ',': + in.readRune() + in.endToken(tokenKind(c)) + return + + case '"', '`': // quoted string + quote := c + in.readRune() + for { + if in.eof() { + in.pos = in.token.pos + in.Error("unexpected EOF in string") + } + if in.peekRune() == '\n' { + in.Error("unexpected newline in string") + } + c := in.readRune() + if c == quote { + break + } + if c == '\\' && quote != '`' { + if in.eof() { + in.pos = in.token.pos + in.Error("unexpected EOF in string") + } + in.readRune() + } + } + in.endToken(_STRING) + return + } + + // Checked all punctuation. Must be identifier token. + if c := in.peekRune(); !isIdent(c) { + in.Error(fmt.Sprintf("unexpected input character %#q", c)) + } + + // Scan over identifier. + for isIdent(in.peekRune()) { + if in.peekPrefix("//") { + break + } + if in.peekPrefix("/*") { + in.Error("mod files must use // comments (not /* */ comments)") + } + in.readRune() + } + in.endToken(_IDENT) +} + +// isIdent reports whether c is an identifier rune. +// We treat most printable runes as identifier runes, except for a handful of +// ASCII punctuation characters. +func isIdent(c int) bool { + switch r := rune(c); r { + case ' ', '(', ')', '[', ']', '{', '}', ',': + return false + default: + return !unicode.IsSpace(r) && unicode.IsPrint(r) + } +} + +// Comment assignment. +// We build two lists of all subexpressions, preorder and postorder. +// The preorder list is ordered by start location, with outer expressions first. +// The postorder list is ordered by end location, with outer expressions last. +// We use the preorder list to assign each whole-line comment to the syntax +// immediately following it, and we use the postorder list to assign each +// end-of-line comment to the syntax immediately preceding it. + +// order walks the expression adding it and its subexpressions to the +// preorder and postorder lists. +func (in *input) order(x Expr) { + if x != nil { + in.pre = append(in.pre, x) + } + switch x := x.(type) { + default: + panic(fmt.Errorf("order: unexpected type %T", x)) + case nil: + // nothing + case *LParen, *RParen: + // nothing + case *CommentBlock: + // nothing + case *Line: + // nothing + case *FileSyntax: + for _, stmt := range x.Stmt { + in.order(stmt) + } + case *LineBlock: + in.order(&x.LParen) + for _, l := range x.Line { + in.order(l) + } + in.order(&x.RParen) + } + if x != nil { + in.post = append(in.post, x) + } +} + +// assignComments attaches comments to nearby syntax. +func (in *input) assignComments() { + const debug = false + + // Generate preorder and postorder lists. + in.order(in.file) + + // Split into whole-line comments and suffix comments. + var line, suffix []Comment + for _, com := range in.comments { + if com.Suffix { + suffix = append(suffix, com) + } else { + line = append(line, com) + } + } + + if debug { + for _, c := range line { + fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) + } + } + + // Assign line comments to syntax immediately following. + for _, x := range in.pre { + start, _ := x.Span() + if debug { + fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte) + } + xcom := x.Comment() + for len(line) > 0 && start.Byte >= line[0].Start.Byte { + if debug { + fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte) + } + xcom.Before = append(xcom.Before, line[0]) + line = line[1:] + } + } + + // Remaining line comments go at end of file. + in.file.After = append(in.file.After, line...) + + if debug { + for _, c := range suffix { + fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte) + } + } + + // Assign suffix comments to syntax immediately before. + for i := len(in.post) - 1; i >= 0; i-- { + x := in.post[i] + + start, end := x.Span() + if debug { + fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte) + } + + // Do not assign suffix comments to end of line block or whole file. + // Instead assign them to the last element inside. + switch x.(type) { + case *FileSyntax: + continue + } + + // Do not assign suffix comments to something that starts + // on an earlier line, so that in + // + // x ( y + // z ) // comment + // + // we assign the comment to z and not to x ( ... ). + if start.Line != end.Line { + continue + } + xcom := x.Comment() + for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte { + if debug { + fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte) + } + xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1]) + suffix = suffix[:len(suffix)-1] + } + } + + // We assigned suffix comments in reverse. + // If multiple suffix comments were appended to the same + // expression node, they are now in reverse. Fix that. + for _, x := range in.post { + reverseComments(x.Comment().Suffix) + } + + // Remaining suffix comments go at beginning of file. + in.file.Before = append(in.file.Before, suffix...) +} + +// reverseComments reverses the []Comment list. +func reverseComments(list []Comment) { + for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { + list[i], list[j] = list[j], list[i] + } +} + +func (in *input) parseFile() { + in.file = new(FileSyntax) + var cb *CommentBlock + for { + switch in.peek() { + case '\n': + in.lex() + if cb != nil { + in.file.Stmt = append(in.file.Stmt, cb) + cb = nil + } + case _COMMENT: + tok := in.lex() + if cb == nil { + cb = &CommentBlock{Start: tok.pos} + } + com := cb.Comment() + com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text}) + case _EOF: + if cb != nil { + in.file.Stmt = append(in.file.Stmt, cb) + } + return + default: + in.parseStmt() + if cb != nil { + in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before + cb = nil + } + } + } +} + +func (in *input) parseStmt() { + tok := in.lex() + start := tok.pos + end := tok.endPos + tokens := []string{tok.text} + for { + tok := in.lex() + switch { + case tok.kind.isEOL(): + in.file.Stmt = append(in.file.Stmt, &Line{ + Start: start, + Token: tokens, + End: end, + }) + return + + case tok.kind == '(': + if next := in.peek(); next.isEOL() { + // Start of block: no more tokens on this line. + in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok)) + return + } else if next == ')' { + rparen := in.lex() + if in.peek().isEOL() { + // Empty block. + in.lex() + in.file.Stmt = append(in.file.Stmt, &LineBlock{ + Start: start, + Token: tokens, + LParen: LParen{Pos: tok.pos}, + RParen: RParen{Pos: rparen.pos}, + }) + return + } + // '( )' in the middle of the line, not a block. + tokens = append(tokens, tok.text, rparen.text) + } else { + // '(' in the middle of the line, not a block. + tokens = append(tokens, tok.text) + } + + default: + tokens = append(tokens, tok.text) + end = tok.endPos + } + } +} + +func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock { + x := &LineBlock{ + Start: start, + Token: token, + LParen: LParen{Pos: lparen.pos}, + } + var comments []Comment + for { + switch in.peek() { + case _EOLCOMMENT: + // Suffix comment, will be attached later by assignComments. + in.lex() + case '\n': + // Blank line. Add an empty comment to preserve it. + in.lex() + if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" { + comments = append(comments, Comment{}) + } + case _COMMENT: + tok := in.lex() + comments = append(comments, Comment{Start: tok.pos, Token: tok.text}) + case _EOF: + in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune)) + case ')': + rparen := in.lex() + x.RParen.Before = comments + x.RParen.Pos = rparen.pos + if !in.peek().isEOL() { + in.Error("syntax error (expected newline after closing paren)") + } + in.lex() + return x + default: + l := in.parseLine() + x.Line = append(x.Line, l) + l.Comment().Before = comments + comments = nil + } + } +} + +func (in *input) parseLine() *Line { + tok := in.lex() + if tok.kind.isEOL() { + in.Error("internal parse error: parseLine at end of line") + } + start := tok.pos + end := tok.endPos + tokens := []string{tok.text} + for { + tok := in.lex() + if tok.kind.isEOL() { + return &Line{ + Start: start, + Token: tokens, + End: end, + InBlock: true, + } + } + tokens = append(tokens, tok.text) + end = tok.endPos + } +} + +var ( + slashSlash = []byte("//") + moduleStr = []byte("module") +) + +// ModulePath returns the module path from the gomod file text. +// If it cannot find a module path, it returns an empty string. +// It is tolerant of unrelated problems in the go.mod file. +func ModulePath(mod []byte) string { + for len(mod) > 0 { + line := mod + mod = nil + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, mod = line[:i], line[i+1:] + } + if i := bytes.Index(line, slashSlash); i >= 0 { + line = line[:i] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, moduleStr) { + continue + } + line = line[len(moduleStr):] + n := len(line) + line = bytes.TrimSpace(line) + if len(line) == n || len(line) == 0 { + continue + } + + if line[0] == '"' || line[0] == '`' { + p, err := strconv.Unquote(string(line)) + if err != nil { + return "" // malformed quoted string or multiline module path + } + return p + } + + return string(line) + } + return "" // missing module path +} diff --git a/vendor/golang.org/x/mod/modfile/rule.go b/vendor/golang.org/x/mod/modfile/rule.go new file mode 100644 index 0000000000..35fd1f534c --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/rule.go @@ -0,0 +1,1663 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package modfile implements a parser and formatter for go.mod files. +// +// The go.mod syntax is described in +// https://pkg.go.dev/cmd/go/#hdr-The_go_mod_file. +// +// The [Parse] and [ParseLax] functions both parse a go.mod file and return an +// abstract syntax tree. ParseLax ignores unknown statements and may be used to +// parse go.mod files that may have been developed with newer versions of Go. +// +// The [File] struct returned by Parse and ParseLax represent an abstract +// go.mod file. File has several methods like [File.AddNewRequire] and +// [File.DropReplace] that can be used to programmatically edit a file. +// +// The [Format] function formats a File back to a byte slice which can be +// written to a file. +package modfile + +import ( + "errors" + "fmt" + "path/filepath" + "sort" + "strconv" + "strings" + "unicode" + + "golang.org/x/mod/internal/lazyregexp" + "golang.org/x/mod/module" + "golang.org/x/mod/semver" +) + +// A File is the parsed, interpreted form of a go.mod file. +type File struct { + Module *Module + Go *Go + Toolchain *Toolchain + Require []*Require + Exclude []*Exclude + Replace []*Replace + Retract []*Retract + + Syntax *FileSyntax +} + +// A Module is the module statement. +type Module struct { + Mod module.Version + Deprecated string + Syntax *Line +} + +// A Go is the go statement. +type Go struct { + Version string // "1.23" + Syntax *Line +} + +// A Toolchain is the toolchain statement. +type Toolchain struct { + Name string // "go1.21rc1" + Syntax *Line +} + +// An Exclude is a single exclude statement. +type Exclude struct { + Mod module.Version + Syntax *Line +} + +// A Replace is a single replace statement. +type Replace struct { + Old module.Version + New module.Version + Syntax *Line +} + +// A Retract is a single retract statement. +type Retract struct { + VersionInterval + Rationale string + Syntax *Line +} + +// A VersionInterval represents a range of versions with upper and lower bounds. +// Intervals are closed: both bounds are included. When Low is equal to High, +// the interval may refer to a single version ('v1.2.3') or an interval +// ('[v1.2.3, v1.2.3]'); both have the same representation. +type VersionInterval struct { + Low, High string +} + +// A Require is a single require statement. +type Require struct { + Mod module.Version + Indirect bool // has "// indirect" comment + Syntax *Line +} + +func (r *Require) markRemoved() { + r.Syntax.markRemoved() + *r = Require{} +} + +func (r *Require) setVersion(v string) { + r.Mod.Version = v + + if line := r.Syntax; len(line.Token) > 0 { + if line.InBlock { + // If the line is preceded by an empty line, remove it; see + // https://golang.org/issue/33779. + if len(line.Comments.Before) == 1 && len(line.Comments.Before[0].Token) == 0 { + line.Comments.Before = line.Comments.Before[:0] + } + if len(line.Token) >= 2 { // example.com v1.2.3 + line.Token[1] = v + } + } else { + if len(line.Token) >= 3 { // require example.com v1.2.3 + line.Token[2] = v + } + } + } +} + +// setIndirect sets line to have (or not have) a "// indirect" comment. +func (r *Require) setIndirect(indirect bool) { + r.Indirect = indirect + line := r.Syntax + if isIndirect(line) == indirect { + return + } + if indirect { + // Adding comment. + if len(line.Suffix) == 0 { + // New comment. + line.Suffix = []Comment{{Token: "// indirect", Suffix: true}} + return + } + + com := &line.Suffix[0] + text := strings.TrimSpace(strings.TrimPrefix(com.Token, string(slashSlash))) + if text == "" { + // Empty comment. + com.Token = "// indirect" + return + } + + // Insert at beginning of existing comment. + com.Token = "// indirect; " + text + return + } + + // Removing comment. + f := strings.TrimSpace(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) + if f == "indirect" { + // Remove whole comment. + line.Suffix = nil + return + } + + // Remove comment prefix. + com := &line.Suffix[0] + i := strings.Index(com.Token, "indirect;") + com.Token = "//" + com.Token[i+len("indirect;"):] +} + +// isIndirect reports whether line has a "// indirect" comment, +// meaning it is in go.mod only for its effect on indirect dependencies, +// so that it can be dropped entirely once the effective version of the +// indirect dependency reaches the given minimum version. +func isIndirect(line *Line) bool { + if len(line.Suffix) == 0 { + return false + } + f := strings.Fields(strings.TrimPrefix(line.Suffix[0].Token, string(slashSlash))) + return (len(f) == 1 && f[0] == "indirect" || len(f) > 1 && f[0] == "indirect;") +} + +func (f *File) AddModuleStmt(path string) error { + if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + if f.Module == nil { + f.Module = &Module{ + Mod: module.Version{Path: path}, + Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)), + } + } else { + f.Module.Mod.Path = path + f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path)) + } + return nil +} + +func (f *File) AddComment(text string) { + if f.Syntax == nil { + f.Syntax = new(FileSyntax) + } + f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{ + Comments: Comments{ + Before: []Comment{ + { + Token: text, + }, + }, + }, + }) +} + +type VersionFixer func(path, version string) (string, error) + +// errDontFix is returned by a VersionFixer to indicate the version should be +// left alone, even if it's not canonical. +var dontFixRetract VersionFixer = func(_, vers string) (string, error) { + return vers, nil +} + +// Parse parses and returns a go.mod file. +// +// file is the name of the file, used in positions and errors. +// +// data is the content of the file. +// +// fix is an optional function that canonicalizes module versions. +// If fix is nil, all module versions must be canonical ([module.CanonicalVersion] +// must return the same string). +func Parse(file string, data []byte, fix VersionFixer) (*File, error) { + return parseToFile(file, data, fix, true) +} + +// ParseLax is like Parse but ignores unknown statements. +// It is used when parsing go.mod files other than the main module, +// under the theory that most statement types we add in the future will +// only apply in the main module, like exclude and replace, +// and so we get better gradual deployments if old go commands +// simply ignore those statements when found in go.mod files +// in dependencies. +func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) { + return parseToFile(file, data, fix, false) +} + +func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (parsed *File, err error) { + fs, err := parse(file, data) + if err != nil { + return nil, err + } + f := &File{ + Syntax: fs, + } + var errs ErrorList + + // fix versions in retract directives after the file is parsed. + // We need the module path to fix versions, and it might be at the end. + defer func() { + oldLen := len(errs) + f.fixRetract(fix, &errs) + if len(errs) > oldLen { + parsed, err = nil, errs + } + }() + + for _, x := range fs.Stmt { + switch x := x.(type) { + case *Line: + f.add(&errs, nil, x, x.Token[0], x.Token[1:], fix, strict) + + case *LineBlock: + if len(x.Token) > 1 { + if strict { + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + } + continue + } + switch x.Token[0] { + default: + if strict { + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + } + continue + case "module", "require", "exclude", "replace", "retract": + for _, l := range x.Line { + f.add(&errs, x, l, x.Token[0], l.Token, fix, strict) + } + } + } + } + + if len(errs) > 0 { + return nil, errs + } + return f, nil +} + +var GoVersionRE = lazyregexp.New(`^([1-9][0-9]*)\.(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))?([a-z]+[0-9]+)?$`) +var laxGoVersionRE = lazyregexp.New(`^v?(([1-9][0-9]*)\.(0|[1-9][0-9]*))([^0-9].*)$`) + +// Toolchains must be named beginning with `go1`, +// like "go1.20.3" or "go1.20.3-gccgo". As a special case, "default" is also permitted. +var ToolchainRE = lazyregexp.New(`^default$|^go1($|\.)`) + +func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, args []string, fix VersionFixer, strict bool) { + // If strict is false, this module is a dependency. + // We ignore all unknown directives as well as main-module-only + // directives like replace and exclude. It will work better for + // forward compatibility if we can depend on modules that have unknown + // statements (presumed relevant only when acting as the main module) + // and simply ignore those statements. + if !strict { + switch verb { + case "go", "module", "retract", "require": + // want these even for dependency go.mods + default: + return + } + } + + wrapModPathError := func(modPath string, err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: line.Start, + ModPath: modPath, + Verb: verb, + Err: err, + }) + } + wrapError := func(err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: line.Start, + Err: err, + }) + } + errorf := func(format string, args ...interface{}) { + wrapError(fmt.Errorf(format, args...)) + } + + switch verb { + default: + errorf("unknown directive: %s", verb) + + case "go": + if f.Go != nil { + errorf("repeated go statement") + return + } + if len(args) != 1 { + errorf("go directive expects exactly one argument") + return + } else if !GoVersionRE.MatchString(args[0]) { + fixed := false + if !strict { + if m := laxGoVersionRE.FindStringSubmatch(args[0]); m != nil { + args[0] = m[1] + fixed = true + } + } + if !fixed { + errorf("invalid go version '%s': must match format 1.23.0", args[0]) + return + } + } + + f.Go = &Go{Syntax: line} + f.Go.Version = args[0] + + case "toolchain": + if f.Toolchain != nil { + errorf("repeated toolchain statement") + return + } + if len(args) != 1 { + errorf("toolchain directive expects exactly one argument") + return + } else if strict && !ToolchainRE.MatchString(args[0]) { + errorf("invalid toolchain version '%s': must match format go1.23.0 or local", args[0]) + return + } + f.Toolchain = &Toolchain{Syntax: line} + f.Toolchain.Name = args[0] + + case "module": + if f.Module != nil { + errorf("repeated module statement") + return + } + deprecated := parseDeprecation(block, line) + f.Module = &Module{ + Syntax: line, + Deprecated: deprecated, + } + if len(args) != 1 { + errorf("usage: module module/path") + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + f.Module.Mod = module.Version{Path: s} + + case "require", "exclude": + if len(args) != 2 { + errorf("usage: %s module/path v1.2.3", verb) + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + v, err := parseVersion(verb, s, &args[1], fix) + if err != nil { + wrapError(err) + return + } + pathMajor, err := modulePathMajor(s) + if err != nil { + wrapError(err) + return + } + if err := module.CheckPathMajor(v, pathMajor); err != nil { + wrapModPathError(s, err) + return + } + if verb == "require" { + f.Require = append(f.Require, &Require{ + Mod: module.Version{Path: s, Version: v}, + Syntax: line, + Indirect: isIndirect(line), + }) + } else { + f.Exclude = append(f.Exclude, &Exclude{ + Mod: module.Version{Path: s, Version: v}, + Syntax: line, + }) + } + + case "replace": + replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) + if wrappederr != nil { + *errs = append(*errs, *wrappederr) + return + } + f.Replace = append(f.Replace, replace) + + case "retract": + rationale := parseDirectiveComment(block, line) + vi, err := parseVersionInterval(verb, "", &args, dontFixRetract) + if err != nil { + if strict { + wrapError(err) + return + } else { + // Only report errors parsing intervals in the main module. We may + // support additional syntax in the future, such as open and half-open + // intervals. Those can't be supported now, because they break the + // go.mod parser, even in lax mode. + return + } + } + if len(args) > 0 && strict { + // In the future, there may be additional information after the version. + errorf("unexpected token after version: %q", args[0]) + return + } + retract := &Retract{ + VersionInterval: vi, + Rationale: rationale, + Syntax: line, + } + f.Retract = append(f.Retract, retract) + } +} + +func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) { + wrapModPathError := func(modPath string, err error) *Error { + return &Error{ + Filename: filename, + Pos: line.Start, + ModPath: modPath, + Verb: verb, + Err: err, + } + } + wrapError := func(err error) *Error { + return &Error{ + Filename: filename, + Pos: line.Start, + Err: err, + } + } + errorf := func(format string, args ...interface{}) *Error { + return wrapError(fmt.Errorf(format, args...)) + } + + arrow := 2 + if len(args) >= 2 && args[1] == "=>" { + arrow = 1 + } + if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" { + return nil, errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb) + } + s, err := parseString(&args[0]) + if err != nil { + return nil, errorf("invalid quoted string: %v", err) + } + pathMajor, err := modulePathMajor(s) + if err != nil { + return nil, wrapModPathError(s, err) + + } + var v string + if arrow == 2 { + v, err = parseVersion(verb, s, &args[1], fix) + if err != nil { + return nil, wrapError(err) + } + if err := module.CheckPathMajor(v, pathMajor); err != nil { + return nil, wrapModPathError(s, err) + } + } + ns, err := parseString(&args[arrow+1]) + if err != nil { + return nil, errorf("invalid quoted string: %v", err) + } + nv := "" + if len(args) == arrow+2 { + if !IsDirectoryPath(ns) { + if strings.Contains(ns, "@") { + return nil, errorf("replacement module must match format 'path version', not 'path@version'") + } + return nil, errorf("replacement module without version must be directory path (rooted or starting with . or ..)") + } + if filepath.Separator == '/' && strings.Contains(ns, `\`) { + return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)") + } + } + if len(args) == arrow+3 { + nv, err = parseVersion(verb, ns, &args[arrow+2], fix) + if err != nil { + return nil, wrapError(err) + } + if IsDirectoryPath(ns) { + return nil, errorf("replacement module directory path %q cannot have version", ns) + } + } + return &Replace{ + Old: module.Version{Path: s, Version: v}, + New: module.Version{Path: ns, Version: nv}, + Syntax: line, + }, nil +} + +// fixRetract applies fix to each retract directive in f, appending any errors +// to errs. +// +// Most versions are fixed as we parse the file, but for retract directives, +// the relevant module path is the one specified with the module directive, +// and that might appear at the end of the file (or not at all). +func (f *File) fixRetract(fix VersionFixer, errs *ErrorList) { + if fix == nil { + return + } + path := "" + if f.Module != nil { + path = f.Module.Mod.Path + } + var r *Retract + wrapError := func(err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: r.Syntax.Start, + Err: err, + }) + } + + for _, r = range f.Retract { + if path == "" { + wrapError(errors.New("no module directive found, so retract cannot be used")) + return // only print the first one of these + } + + args := r.Syntax.Token + if args[0] == "retract" { + args = args[1:] + } + vi, err := parseVersionInterval("retract", path, &args, fix) + if err != nil { + wrapError(err) + } + r.VersionInterval = vi + } +} + +func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) { + wrapError := func(err error) { + *errs = append(*errs, Error{ + Filename: f.Syntax.Name, + Pos: line.Start, + Err: err, + }) + } + errorf := func(format string, args ...interface{}) { + wrapError(fmt.Errorf(format, args...)) + } + + switch verb { + default: + errorf("unknown directive: %s", verb) + + case "go": + if f.Go != nil { + errorf("repeated go statement") + return + } + if len(args) != 1 { + errorf("go directive expects exactly one argument") + return + } else if !GoVersionRE.MatchString(args[0]) { + errorf("invalid go version '%s': must match format 1.23", args[0]) + return + } + + f.Go = &Go{Syntax: line} + f.Go.Version = args[0] + + case "toolchain": + if f.Toolchain != nil { + errorf("repeated toolchain statement") + return + } + if len(args) != 1 { + errorf("toolchain directive expects exactly one argument") + return + } else if !ToolchainRE.MatchString(args[0]) { + errorf("invalid toolchain version '%s': must match format go1.23 or local", args[0]) + return + } + + f.Toolchain = &Toolchain{Syntax: line} + f.Toolchain.Name = args[0] + + case "use": + if len(args) != 1 { + errorf("usage: %s local/dir", verb) + return + } + s, err := parseString(&args[0]) + if err != nil { + errorf("invalid quoted string: %v", err) + return + } + f.Use = append(f.Use, &Use{ + Path: s, + Syntax: line, + }) + + case "replace": + replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix) + if wrappederr != nil { + *errs = append(*errs, *wrappederr) + return + } + f.Replace = append(f.Replace, replace) + } +} + +// IsDirectoryPath reports whether the given path should be interpreted as a directory path. +// Just like on the go command line, relative paths starting with a '.' or '..' path component +// and rooted paths are directory paths; the rest are module paths. +func IsDirectoryPath(ns string) bool { + // Because go.mod files can move from one system to another, + // we check all known path syntaxes, both Unix and Windows. + return ns == "." || strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, `.\`) || + ns == ".." || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, `..\`) || + strings.HasPrefix(ns, "/") || strings.HasPrefix(ns, `\`) || + len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':' +} + +// MustQuote reports whether s must be quoted in order to appear as +// a single token in a go.mod line. +func MustQuote(s string) bool { + for _, r := range s { + switch r { + case ' ', '"', '\'', '`': + return true + + case '(', ')', '[', ']', '{', '}', ',': + if len(s) > 1 { + return true + } + + default: + if !unicode.IsPrint(r) { + return true + } + } + } + return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*") +} + +// AutoQuote returns s or, if quoting is required for s to appear in a go.mod, +// the quotation of s. +func AutoQuote(s string) string { + if MustQuote(s) { + return strconv.Quote(s) + } + return s +} + +func parseVersionInterval(verb string, path string, args *[]string, fix VersionFixer) (VersionInterval, error) { + toks := *args + if len(toks) == 0 || toks[0] == "(" { + return VersionInterval{}, fmt.Errorf("expected '[' or version") + } + if toks[0] != "[" { + v, err := parseVersion(verb, path, &toks[0], fix) + if err != nil { + return VersionInterval{}, err + } + *args = toks[1:] + return VersionInterval{Low: v, High: v}, nil + } + toks = toks[1:] + + if len(toks) == 0 { + return VersionInterval{}, fmt.Errorf("expected version after '['") + } + low, err := parseVersion(verb, path, &toks[0], fix) + if err != nil { + return VersionInterval{}, err + } + toks = toks[1:] + + if len(toks) == 0 || toks[0] != "," { + return VersionInterval{}, fmt.Errorf("expected ',' after version") + } + toks = toks[1:] + + if len(toks) == 0 { + return VersionInterval{}, fmt.Errorf("expected version after ','") + } + high, err := parseVersion(verb, path, &toks[0], fix) + if err != nil { + return VersionInterval{}, err + } + toks = toks[1:] + + if len(toks) == 0 || toks[0] != "]" { + return VersionInterval{}, fmt.Errorf("expected ']' after version") + } + toks = toks[1:] + + *args = toks + return VersionInterval{Low: low, High: high}, nil +} + +func parseString(s *string) (string, error) { + t := *s + if strings.HasPrefix(t, `"`) { + var err error + if t, err = strconv.Unquote(t); err != nil { + return "", err + } + } else if strings.ContainsAny(t, "\"'`") { + // Other quotes are reserved both for possible future expansion + // and to avoid confusion. For example if someone types 'x' + // we want that to be a syntax error and not a literal x in literal quotation marks. + return "", fmt.Errorf("unquoted string cannot contain quote") + } + *s = AutoQuote(t) + return t, nil +} + +var deprecatedRE = lazyregexp.New(`(?s)(?:^|\n\n)Deprecated: *(.*?)(?:$|\n\n)`) + +// parseDeprecation extracts the text of comments on a "module" directive and +// extracts a deprecation message from that. +// +// A deprecation message is contained in a paragraph within a block of comments +// that starts with "Deprecated:" (case sensitive). The message runs until the +// end of the paragraph and does not include the "Deprecated:" prefix. If the +// comment block has multiple paragraphs that start with "Deprecated:", +// parseDeprecation returns the message from the first. +func parseDeprecation(block *LineBlock, line *Line) string { + text := parseDirectiveComment(block, line) + m := deprecatedRE.FindStringSubmatch(text) + if m == nil { + return "" + } + return m[1] +} + +// parseDirectiveComment extracts the text of comments on a directive. +// If the directive's line does not have comments and is part of a block that +// does have comments, the block's comments are used. +func parseDirectiveComment(block *LineBlock, line *Line) string { + comments := line.Comment() + if block != nil && len(comments.Before) == 0 && len(comments.Suffix) == 0 { + comments = block.Comment() + } + groups := [][]Comment{comments.Before, comments.Suffix} + var lines []string + for _, g := range groups { + for _, c := range g { + if !strings.HasPrefix(c.Token, "//") { + continue // blank line + } + lines = append(lines, strings.TrimSpace(strings.TrimPrefix(c.Token, "//"))) + } + } + return strings.Join(lines, "\n") +} + +type ErrorList []Error + +func (e ErrorList) Error() string { + errStrs := make([]string, len(e)) + for i, err := range e { + errStrs[i] = err.Error() + } + return strings.Join(errStrs, "\n") +} + +type Error struct { + Filename string + Pos Position + Verb string + ModPath string + Err error +} + +func (e *Error) Error() string { + var pos string + if e.Pos.LineRune > 1 { + // Don't print LineRune if it's 1 (beginning of line). + // It's always 1 except in scanner errors, which are rare. + pos = fmt.Sprintf("%s:%d:%d: ", e.Filename, e.Pos.Line, e.Pos.LineRune) + } else if e.Pos.Line > 0 { + pos = fmt.Sprintf("%s:%d: ", e.Filename, e.Pos.Line) + } else if e.Filename != "" { + pos = fmt.Sprintf("%s: ", e.Filename) + } + + var directive string + if e.ModPath != "" { + directive = fmt.Sprintf("%s %s: ", e.Verb, e.ModPath) + } else if e.Verb != "" { + directive = fmt.Sprintf("%s: ", e.Verb) + } + + return pos + directive + e.Err.Error() +} + +func (e *Error) Unwrap() error { return e.Err } + +func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) { + t, err := parseString(s) + if err != nil { + return "", &Error{ + Verb: verb, + ModPath: path, + Err: &module.InvalidVersionError{ + Version: *s, + Err: err, + }, + } + } + if fix != nil { + fixed, err := fix(path, t) + if err != nil { + if err, ok := err.(*module.ModuleError); ok { + return "", &Error{ + Verb: verb, + ModPath: path, + Err: err.Err, + } + } + return "", err + } + t = fixed + } else { + cv := module.CanonicalVersion(t) + if cv == "" { + return "", &Error{ + Verb: verb, + ModPath: path, + Err: &module.InvalidVersionError{ + Version: t, + Err: errors.New("must be of the form v1.2.3"), + }, + } + } + t = cv + } + *s = t + return *s, nil +} + +func modulePathMajor(path string) (string, error) { + _, major, ok := module.SplitPathVersion(path) + if !ok { + return "", fmt.Errorf("invalid module path") + } + return major, nil +} + +func (f *File) Format() ([]byte, error) { + return Format(f.Syntax), nil +} + +// Cleanup cleans up the file f after any edit operations. +// To avoid quadratic behavior, modifications like [File.DropRequire] +// clear the entry but do not remove it from the slice. +// Cleanup cleans out all the cleared entries. +func (f *File) Cleanup() { + w := 0 + for _, r := range f.Require { + if r.Mod.Path != "" { + f.Require[w] = r + w++ + } + } + f.Require = f.Require[:w] + + w = 0 + for _, x := range f.Exclude { + if x.Mod.Path != "" { + f.Exclude[w] = x + w++ + } + } + f.Exclude = f.Exclude[:w] + + w = 0 + for _, r := range f.Replace { + if r.Old.Path != "" { + f.Replace[w] = r + w++ + } + } + f.Replace = f.Replace[:w] + + w = 0 + for _, r := range f.Retract { + if r.Low != "" || r.High != "" { + f.Retract[w] = r + w++ + } + } + f.Retract = f.Retract[:w] + + f.Syntax.Cleanup() +} + +func (f *File) AddGoStmt(version string) error { + if !GoVersionRE.MatchString(version) { + return fmt.Errorf("invalid language version %q", version) + } + if f.Go == nil { + var hint Expr + if f.Module != nil && f.Module.Syntax != nil { + hint = f.Module.Syntax + } + f.Go = &Go{ + Version: version, + Syntax: f.Syntax.addLine(hint, "go", version), + } + } else { + f.Go.Version = version + f.Syntax.updateLine(f.Go.Syntax, "go", version) + } + return nil +} + +// DropGoStmt deletes the go statement from the file. +func (f *File) DropGoStmt() { + if f.Go != nil { + f.Go.Syntax.markRemoved() + f.Go = nil + } +} + +// DropToolchainStmt deletes the toolchain statement from the file. +func (f *File) DropToolchainStmt() { + if f.Toolchain != nil { + f.Toolchain.Syntax.markRemoved() + f.Toolchain = nil + } +} + +func (f *File) AddToolchainStmt(name string) error { + if !ToolchainRE.MatchString(name) { + return fmt.Errorf("invalid toolchain name %q", name) + } + if f.Toolchain == nil { + var hint Expr + if f.Go != nil && f.Go.Syntax != nil { + hint = f.Go.Syntax + } else if f.Module != nil && f.Module.Syntax != nil { + hint = f.Module.Syntax + } + f.Toolchain = &Toolchain{ + Name: name, + Syntax: f.Syntax.addLine(hint, "toolchain", name), + } + } else { + f.Toolchain.Name = name + f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) + } + return nil +} + +// AddRequire sets the first require line for path to version vers, +// preserving any existing comments for that line and removing all +// other lines for path. +// +// If no line currently exists for path, AddRequire adds a new line +// at the end of the last require block. +func (f *File) AddRequire(path, vers string) error { + need := true + for _, r := range f.Require { + if r.Mod.Path == path { + if need { + r.Mod.Version = vers + f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers) + need = false + } else { + r.Syntax.markRemoved() + *r = Require{} + } + } + } + + if need { + f.AddNewRequire(path, vers, false) + } + return nil +} + +// AddNewRequire adds a new require line for path at version vers at the end of +// the last require block, regardless of any existing require lines for path. +func (f *File) AddNewRequire(path, vers string, indirect bool) { + line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers) + r := &Require{ + Mod: module.Version{Path: path, Version: vers}, + Syntax: line, + } + r.setIndirect(indirect) + f.Require = append(f.Require, r) +} + +// SetRequire updates the requirements of f to contain exactly req, preserving +// the existing block structure and line comment contents (except for 'indirect' +// markings) for the first requirement on each named module path. +// +// The Syntax field is ignored for the requirements in req. +// +// Any requirements not already present in the file are added to the block +// containing the last require line. +// +// The requirements in req must specify at most one distinct version for each +// module path. +// +// If any existing requirements may be removed, the caller should call +// [File.Cleanup] after all edits are complete. +func (f *File) SetRequire(req []*Require) { + type elem struct { + version string + indirect bool + } + need := make(map[string]elem) + for _, r := range req { + if prev, dup := need[r.Mod.Path]; dup && prev.version != r.Mod.Version { + panic(fmt.Errorf("SetRequire called with conflicting versions for path %s (%s and %s)", r.Mod.Path, prev.version, r.Mod.Version)) + } + need[r.Mod.Path] = elem{r.Mod.Version, r.Indirect} + } + + // Update or delete the existing Require entries to preserve + // only the first for each module path in req. + for _, r := range f.Require { + e, ok := need[r.Mod.Path] + if ok { + r.setVersion(e.version) + r.setIndirect(e.indirect) + } else { + r.markRemoved() + } + delete(need, r.Mod.Path) + } + + // Add new entries in the last block of the file for any paths that weren't + // already present. + // + // This step is nondeterministic, but the final result will be deterministic + // because we will sort the block. + for path, e := range need { + f.AddNewRequire(path, e.version, e.indirect) + } + + f.SortBlocks() +} + +// SetRequireSeparateIndirect updates the requirements of f to contain the given +// requirements. Comment contents (except for 'indirect' markings) are retained +// from the first existing requirement for each module path. Like SetRequire, +// SetRequireSeparateIndirect adds requirements for new paths in req, +// updates the version and "// indirect" comment on existing requirements, +// and deletes requirements on paths not in req. Existing duplicate requirements +// are deleted. +// +// As its name suggests, SetRequireSeparateIndirect puts direct and indirect +// requirements into two separate blocks, one containing only direct +// requirements, and the other containing only indirect requirements. +// SetRequireSeparateIndirect may move requirements between these two blocks +// when their indirect markings change. However, SetRequireSeparateIndirect +// won't move requirements from other blocks, especially blocks with comments. +// +// If the file initially has one uncommented block of requirements, +// SetRequireSeparateIndirect will split it into a direct-only and indirect-only +// block. This aids in the transition to separate blocks. +func (f *File) SetRequireSeparateIndirect(req []*Require) { + // hasComments returns whether a line or block has comments + // other than "indirect". + hasComments := func(c Comments) bool { + return len(c.Before) > 0 || len(c.After) > 0 || len(c.Suffix) > 1 || + (len(c.Suffix) == 1 && + strings.TrimSpace(strings.TrimPrefix(c.Suffix[0].Token, string(slashSlash))) != "indirect") + } + + // moveReq adds r to block. If r was in another block, moveReq deletes + // it from that block and transfers its comments. + moveReq := func(r *Require, block *LineBlock) { + var line *Line + if r.Syntax == nil { + line = &Line{Token: []string{AutoQuote(r.Mod.Path), r.Mod.Version}} + r.Syntax = line + if r.Indirect { + r.setIndirect(true) + } + } else { + line = new(Line) + *line = *r.Syntax + if !line.InBlock && len(line.Token) > 0 && line.Token[0] == "require" { + line.Token = line.Token[1:] + } + r.Syntax.Token = nil // Cleanup will delete the old line. + r.Syntax = line + } + line.InBlock = true + block.Line = append(block.Line, line) + } + + // Examine existing require lines and blocks. + var ( + // We may insert new requirements into the last uncommented + // direct-only and indirect-only blocks. We may also move requirements + // to the opposite block if their indirect markings change. + lastDirectIndex = -1 + lastIndirectIndex = -1 + + // If there are no direct-only or indirect-only blocks, a new block may + // be inserted after the last require line or block. + lastRequireIndex = -1 + + // If there's only one require line or block, and it's uncommented, + // we'll move its requirements to the direct-only or indirect-only blocks. + requireLineOrBlockCount = 0 + + // Track the block each requirement belongs to (if any) so we can + // move them later. + lineToBlock = make(map[*Line]*LineBlock) + ) + for i, stmt := range f.Syntax.Stmt { + switch stmt := stmt.(type) { + case *Line: + if len(stmt.Token) == 0 || stmt.Token[0] != "require" { + continue + } + lastRequireIndex = i + requireLineOrBlockCount++ + if !hasComments(stmt.Comments) { + if isIndirect(stmt) { + lastIndirectIndex = i + } else { + lastDirectIndex = i + } + } + + case *LineBlock: + if len(stmt.Token) == 0 || stmt.Token[0] != "require" { + continue + } + lastRequireIndex = i + requireLineOrBlockCount++ + allDirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) + allIndirect := len(stmt.Line) > 0 && !hasComments(stmt.Comments) + for _, line := range stmt.Line { + lineToBlock[line] = stmt + if hasComments(line.Comments) { + allDirect = false + allIndirect = false + } else if isIndirect(line) { + allDirect = false + } else { + allIndirect = false + } + } + if allDirect { + lastDirectIndex = i + } + if allIndirect { + lastIndirectIndex = i + } + } + } + + oneFlatUncommentedBlock := requireLineOrBlockCount == 1 && + !hasComments(*f.Syntax.Stmt[lastRequireIndex].Comment()) + + // Create direct and indirect blocks if needed. Convert lines into blocks + // if needed. If we end up with an empty block or a one-line block, + // Cleanup will delete it or convert it to a line later. + insertBlock := func(i int) *LineBlock { + block := &LineBlock{Token: []string{"require"}} + f.Syntax.Stmt = append(f.Syntax.Stmt, nil) + copy(f.Syntax.Stmt[i+1:], f.Syntax.Stmt[i:]) + f.Syntax.Stmt[i] = block + return block + } + + ensureBlock := func(i int) *LineBlock { + switch stmt := f.Syntax.Stmt[i].(type) { + case *LineBlock: + return stmt + case *Line: + block := &LineBlock{ + Token: []string{"require"}, + Line: []*Line{stmt}, + } + stmt.Token = stmt.Token[1:] // remove "require" + stmt.InBlock = true + f.Syntax.Stmt[i] = block + return block + default: + panic(fmt.Sprintf("unexpected statement: %v", stmt)) + } + } + + var lastDirectBlock *LineBlock + if lastDirectIndex < 0 { + if lastIndirectIndex >= 0 { + lastDirectIndex = lastIndirectIndex + lastIndirectIndex++ + } else if lastRequireIndex >= 0 { + lastDirectIndex = lastRequireIndex + 1 + } else { + lastDirectIndex = len(f.Syntax.Stmt) + } + lastDirectBlock = insertBlock(lastDirectIndex) + } else { + lastDirectBlock = ensureBlock(lastDirectIndex) + } + + var lastIndirectBlock *LineBlock + if lastIndirectIndex < 0 { + lastIndirectIndex = lastDirectIndex + 1 + lastIndirectBlock = insertBlock(lastIndirectIndex) + } else { + lastIndirectBlock = ensureBlock(lastIndirectIndex) + } + + // Delete requirements we don't want anymore. + // Update versions and indirect comments on requirements we want to keep. + // If a requirement is in last{Direct,Indirect}Block with the wrong + // indirect marking after this, or if the requirement is in an single + // uncommented mixed block (oneFlatUncommentedBlock), move it to the + // correct block. + // + // Some blocks may be empty after this. Cleanup will remove them. + need := make(map[string]*Require) + for _, r := range req { + need[r.Mod.Path] = r + } + have := make(map[string]*Require) + for _, r := range f.Require { + path := r.Mod.Path + if need[path] == nil || have[path] != nil { + // Requirement not needed, or duplicate requirement. Delete. + r.markRemoved() + continue + } + have[r.Mod.Path] = r + r.setVersion(need[path].Mod.Version) + r.setIndirect(need[path].Indirect) + if need[path].Indirect && + (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastDirectBlock) { + moveReq(r, lastIndirectBlock) + } else if !need[path].Indirect && + (oneFlatUncommentedBlock || lineToBlock[r.Syntax] == lastIndirectBlock) { + moveReq(r, lastDirectBlock) + } + } + + // Add new requirements. + for path, r := range need { + if have[path] == nil { + if r.Indirect { + moveReq(r, lastIndirectBlock) + } else { + moveReq(r, lastDirectBlock) + } + f.Require = append(f.Require, r) + } + } + + f.SortBlocks() +} + +func (f *File) DropRequire(path string) error { + for _, r := range f.Require { + if r.Mod.Path == path { + r.Syntax.markRemoved() + *r = Require{} + } + } + return nil +} + +// AddExclude adds a exclude statement to the mod file. Errors if the provided +// version is not a canonical version string +func (f *File) AddExclude(path, vers string) error { + if err := checkCanonicalVersion(path, vers); err != nil { + return err + } + + var hint *Line + for _, x := range f.Exclude { + if x.Mod.Path == path && x.Mod.Version == vers { + return nil + } + if x.Mod.Path == path { + hint = x.Syntax + } + } + + f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)}) + return nil +} + +func (f *File) DropExclude(path, vers string) error { + for _, x := range f.Exclude { + if x.Mod.Path == path && x.Mod.Version == vers { + x.Syntax.markRemoved() + *x = Exclude{} + } + } + return nil +} + +func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error { + return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) +} + +func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error { + need := true + old := module.Version{Path: oldPath, Version: oldVers} + new := module.Version{Path: newPath, Version: newVers} + tokens := []string{"replace", AutoQuote(oldPath)} + if oldVers != "" { + tokens = append(tokens, oldVers) + } + tokens = append(tokens, "=>", AutoQuote(newPath)) + if newVers != "" { + tokens = append(tokens, newVers) + } + + var hint *Line + for _, r := range *replace { + if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) { + if need { + // Found replacement for old; update to use new. + r.New = new + syntax.updateLine(r.Syntax, tokens...) + need = false + continue + } + // Already added; delete other replacements for same. + r.Syntax.markRemoved() + *r = Replace{} + } + if r.Old.Path == oldPath { + hint = r.Syntax + } + } + if need { + *replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)}) + } + return nil +} + +func (f *File) DropReplace(oldPath, oldVers string) error { + for _, r := range f.Replace { + if r.Old.Path == oldPath && r.Old.Version == oldVers { + r.Syntax.markRemoved() + *r = Replace{} + } + } + return nil +} + +// AddRetract adds a retract statement to the mod file. Errors if the provided +// version interval does not consist of canonical version strings +func (f *File) AddRetract(vi VersionInterval, rationale string) error { + var path string + if f.Module != nil { + path = f.Module.Mod.Path + } + if err := checkCanonicalVersion(path, vi.High); err != nil { + return err + } + if err := checkCanonicalVersion(path, vi.Low); err != nil { + return err + } + + r := &Retract{ + VersionInterval: vi, + } + if vi.Low == vi.High { + r.Syntax = f.Syntax.addLine(nil, "retract", AutoQuote(vi.Low)) + } else { + r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]") + } + if rationale != "" { + for _, line := range strings.Split(rationale, "\n") { + com := Comment{Token: "// " + line} + r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com) + } + } + return nil +} + +func (f *File) DropRetract(vi VersionInterval) error { + for _, r := range f.Retract { + if r.VersionInterval == vi { + r.Syntax.markRemoved() + *r = Retract{} + } + } + return nil +} + +func (f *File) SortBlocks() { + f.removeDups() // otherwise sorting is unsafe + + // semanticSortForExcludeVersionV is the Go version (plus leading "v") at which + // lines in exclude blocks start to use semantic sort instead of lexicographic sort. + // See go.dev/issue/60028. + const semanticSortForExcludeVersionV = "v1.21" + useSemanticSortForExclude := f.Go != nil && semver.Compare("v"+f.Go.Version, semanticSortForExcludeVersionV) >= 0 + + for _, stmt := range f.Syntax.Stmt { + block, ok := stmt.(*LineBlock) + if !ok { + continue + } + less := lineLess + if block.Token[0] == "exclude" && useSemanticSortForExclude { + less = lineExcludeLess + } else if block.Token[0] == "retract" { + less = lineRetractLess + } + sort.SliceStable(block.Line, func(i, j int) bool { + return less(block.Line[i], block.Line[j]) + }) + } +} + +// removeDups removes duplicate exclude and replace directives. +// +// Earlier exclude directives take priority. +// +// Later replace directives take priority. +// +// require directives are not de-duplicated. That's left up to higher-level +// logic (MVS). +// +// retract directives are not de-duplicated since comments are +// meaningful, and versions may be retracted multiple times. +func (f *File) removeDups() { + removeDups(f.Syntax, &f.Exclude, &f.Replace) +} + +func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) { + kill := make(map[*Line]bool) + + // Remove duplicate excludes. + if exclude != nil { + haveExclude := make(map[module.Version]bool) + for _, x := range *exclude { + if haveExclude[x.Mod] { + kill[x.Syntax] = true + continue + } + haveExclude[x.Mod] = true + } + var excl []*Exclude + for _, x := range *exclude { + if !kill[x.Syntax] { + excl = append(excl, x) + } + } + *exclude = excl + } + + // Remove duplicate replacements. + // Later replacements take priority over earlier ones. + haveReplace := make(map[module.Version]bool) + for i := len(*replace) - 1; i >= 0; i-- { + x := (*replace)[i] + if haveReplace[x.Old] { + kill[x.Syntax] = true + continue + } + haveReplace[x.Old] = true + } + var repl []*Replace + for _, x := range *replace { + if !kill[x.Syntax] { + repl = append(repl, x) + } + } + *replace = repl + + // Duplicate require and retract directives are not removed. + + // Drop killed statements from the syntax tree. + var stmts []Expr + for _, stmt := range syntax.Stmt { + switch stmt := stmt.(type) { + case *Line: + if kill[stmt] { + continue + } + case *LineBlock: + var lines []*Line + for _, line := range stmt.Line { + if !kill[line] { + lines = append(lines, line) + } + } + stmt.Line = lines + if len(lines) == 0 { + continue + } + } + stmts = append(stmts, stmt) + } + syntax.Stmt = stmts +} + +// lineLess returns whether li should be sorted before lj. It sorts +// lexicographically without assigning any special meaning to tokens. +func lineLess(li, lj *Line) bool { + for k := 0; k < len(li.Token) && k < len(lj.Token); k++ { + if li.Token[k] != lj.Token[k] { + return li.Token[k] < lj.Token[k] + } + } + return len(li.Token) < len(lj.Token) +} + +// lineExcludeLess reports whether li should be sorted before lj for lines in +// an "exclude" block. +func lineExcludeLess(li, lj *Line) bool { + if len(li.Token) != 2 || len(lj.Token) != 2 { + // Not a known exclude specification. + // Fall back to sorting lexicographically. + return lineLess(li, lj) + } + // An exclude specification has two tokens: ModulePath and Version. + // Compare module path by string order and version by semver rules. + if pi, pj := li.Token[0], lj.Token[0]; pi != pj { + return pi < pj + } + return semver.Compare(li.Token[1], lj.Token[1]) < 0 +} + +// lineRetractLess returns whether li should be sorted before lj for lines in +// a "retract" block. It treats each line as a version interval. Single versions +// are compared as if they were intervals with the same low and high version. +// Intervals are sorted in descending order, first by low version, then by +// high version, using semver.Compare. +func lineRetractLess(li, lj *Line) bool { + interval := func(l *Line) VersionInterval { + if len(l.Token) == 1 { + return VersionInterval{Low: l.Token[0], High: l.Token[0]} + } else if len(l.Token) == 5 && l.Token[0] == "[" && l.Token[2] == "," && l.Token[4] == "]" { + return VersionInterval{Low: l.Token[1], High: l.Token[3]} + } else { + // Line in unknown format. Treat as an invalid version. + return VersionInterval{} + } + } + vii := interval(li) + vij := interval(lj) + if cmp := semver.Compare(vii.Low, vij.Low); cmp != 0 { + return cmp > 0 + } + return semver.Compare(vii.High, vij.High) > 0 +} + +// checkCanonicalVersion returns a non-nil error if vers is not a canonical +// version string or does not match the major version of path. +// +// If path is non-empty, the error text suggests a format with a major version +// corresponding to the path. +func checkCanonicalVersion(path, vers string) error { + _, pathMajor, pathMajorOk := module.SplitPathVersion(path) + + if vers == "" || vers != module.CanonicalVersion(vers) { + if pathMajor == "" { + return &module.InvalidVersionError{ + Version: vers, + Err: fmt.Errorf("must be of the form v1.2.3"), + } + } + return &module.InvalidVersionError{ + Version: vers, + Err: fmt.Errorf("must be of the form %s.2.3", module.PathMajorPrefix(pathMajor)), + } + } + + if pathMajorOk { + if err := module.CheckPathMajor(vers, pathMajor); err != nil { + if pathMajor == "" { + // In this context, the user probably wrote "v2.3.4" when they meant + // "v2.3.4+incompatible". Suggest that instead of "v0 or v1". + return &module.InvalidVersionError{ + Version: vers, + Err: fmt.Errorf("should be %s+incompatible (or module %s/%v)", vers, path, semver.Major(vers)), + } + } + return err + } + } + + return nil +} diff --git a/vendor/golang.org/x/mod/modfile/work.go b/vendor/golang.org/x/mod/modfile/work.go new file mode 100644 index 0000000000..d7b99376eb --- /dev/null +++ b/vendor/golang.org/x/mod/modfile/work.go @@ -0,0 +1,285 @@ +// Copyright 2021 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 modfile + +import ( + "fmt" + "sort" + "strings" +) + +// A WorkFile is the parsed, interpreted form of a go.work file. +type WorkFile struct { + Go *Go + Toolchain *Toolchain + Use []*Use + Replace []*Replace + + Syntax *FileSyntax +} + +// A Use is a single directory statement. +type Use struct { + Path string // Use path of module. + ModulePath string // Module path in the comment. + Syntax *Line +} + +// ParseWork parses and returns a go.work file. +// +// file is the name of the file, used in positions and errors. +// +// data is the content of the file. +// +// fix is an optional function that canonicalizes module versions. +// If fix is nil, all module versions must be canonical ([module.CanonicalVersion] +// must return the same string). +func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) { + fs, err := parse(file, data) + if err != nil { + return nil, err + } + f := &WorkFile{ + Syntax: fs, + } + var errs ErrorList + + for _, x := range fs.Stmt { + switch x := x.(type) { + case *Line: + f.add(&errs, x, x.Token[0], x.Token[1:], fix) + + case *LineBlock: + if len(x.Token) > 1 { + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + continue + } + switch x.Token[0] { + default: + errs = append(errs, Error{ + Filename: file, + Pos: x.Start, + Err: fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")), + }) + continue + case "use", "replace": + for _, l := range x.Line { + f.add(&errs, l, x.Token[0], l.Token, fix) + } + } + } + } + + if len(errs) > 0 { + return nil, errs + } + return f, nil +} + +// Cleanup cleans up the file f after any edit operations. +// To avoid quadratic behavior, modifications like [WorkFile.DropRequire] +// clear the entry but do not remove it from the slice. +// Cleanup cleans out all the cleared entries. +func (f *WorkFile) Cleanup() { + w := 0 + for _, r := range f.Use { + if r.Path != "" { + f.Use[w] = r + w++ + } + } + f.Use = f.Use[:w] + + w = 0 + for _, r := range f.Replace { + if r.Old.Path != "" { + f.Replace[w] = r + w++ + } + } + f.Replace = f.Replace[:w] + + f.Syntax.Cleanup() +} + +func (f *WorkFile) AddGoStmt(version string) error { + if !GoVersionRE.MatchString(version) { + return fmt.Errorf("invalid language version %q", version) + } + if f.Go == nil { + stmt := &Line{Token: []string{"go", version}} + f.Go = &Go{ + Version: version, + Syntax: stmt, + } + // Find the first non-comment-only block and add + // the go statement before it. That will keep file comments at the top. + i := 0 + for i = 0; i < len(f.Syntax.Stmt); i++ { + if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok { + break + } + } + f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...) + } else { + f.Go.Version = version + f.Syntax.updateLine(f.Go.Syntax, "go", version) + } + return nil +} + +func (f *WorkFile) AddToolchainStmt(name string) error { + if !ToolchainRE.MatchString(name) { + return fmt.Errorf("invalid toolchain name %q", name) + } + if f.Toolchain == nil { + stmt := &Line{Token: []string{"toolchain", name}} + f.Toolchain = &Toolchain{ + Name: name, + Syntax: stmt, + } + // Find the go line and add the toolchain line after it. + // Or else find the first non-comment-only block and add + // the toolchain line before it. That will keep file comments at the top. + i := 0 + for i = 0; i < len(f.Syntax.Stmt); i++ { + if line, ok := f.Syntax.Stmt[i].(*Line); ok && len(line.Token) > 0 && line.Token[0] == "go" { + i++ + goto Found + } + } + for i = 0; i < len(f.Syntax.Stmt); i++ { + if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok { + break + } + } + Found: + f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...) + } else { + f.Toolchain.Name = name + f.Syntax.updateLine(f.Toolchain.Syntax, "toolchain", name) + } + return nil +} + +// DropGoStmt deletes the go statement from the file. +func (f *WorkFile) DropGoStmt() { + if f.Go != nil { + f.Go.Syntax.markRemoved() + f.Go = nil + } +} + +// DropToolchainStmt deletes the toolchain statement from the file. +func (f *WorkFile) DropToolchainStmt() { + if f.Toolchain != nil { + f.Toolchain.Syntax.markRemoved() + f.Toolchain = nil + } +} + +func (f *WorkFile) AddUse(diskPath, modulePath string) error { + need := true + for _, d := range f.Use { + if d.Path == diskPath { + if need { + d.ModulePath = modulePath + f.Syntax.updateLine(d.Syntax, "use", AutoQuote(diskPath)) + need = false + } else { + d.Syntax.markRemoved() + *d = Use{} + } + } + } + + if need { + f.AddNewUse(diskPath, modulePath) + } + return nil +} + +func (f *WorkFile) AddNewUse(diskPath, modulePath string) { + line := f.Syntax.addLine(nil, "use", AutoQuote(diskPath)) + f.Use = append(f.Use, &Use{Path: diskPath, ModulePath: modulePath, Syntax: line}) +} + +func (f *WorkFile) SetUse(dirs []*Use) { + need := make(map[string]string) + for _, d := range dirs { + need[d.Path] = d.ModulePath + } + + for _, d := range f.Use { + if modulePath, ok := need[d.Path]; ok { + d.ModulePath = modulePath + } else { + d.Syntax.markRemoved() + *d = Use{} + } + } + + // TODO(#45713): Add module path to comment. + + for diskPath, modulePath := range need { + f.AddNewUse(diskPath, modulePath) + } + f.SortBlocks() +} + +func (f *WorkFile) DropUse(path string) error { + for _, d := range f.Use { + if d.Path == path { + d.Syntax.markRemoved() + *d = Use{} + } + } + return nil +} + +func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error { + return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers) +} + +func (f *WorkFile) DropReplace(oldPath, oldVers string) error { + for _, r := range f.Replace { + if r.Old.Path == oldPath && r.Old.Version == oldVers { + r.Syntax.markRemoved() + *r = Replace{} + } + } + return nil +} + +func (f *WorkFile) SortBlocks() { + f.removeDups() // otherwise sorting is unsafe + + for _, stmt := range f.Syntax.Stmt { + block, ok := stmt.(*LineBlock) + if !ok { + continue + } + sort.SliceStable(block.Line, func(i, j int) bool { + return lineLess(block.Line[i], block.Line[j]) + }) + } +} + +// removeDups removes duplicate replace directives. +// +// Later replace directives take priority. +// +// require directives are not de-duplicated. That's left up to higher-level +// logic (MVS). +// +// retract directives are not de-duplicated since comments are +// meaningful, and versions may be retracted multiple times. +func (f *WorkFile) removeDups() { + removeDups(f.Syntax, nil, &f.Replace) +} diff --git a/vendor/golang.org/x/mod/module/module.go b/vendor/golang.org/x/mod/module/module.go new file mode 100644 index 0000000000..2a364b229b --- /dev/null +++ b/vendor/golang.org/x/mod/module/module.go @@ -0,0 +1,841 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package module defines the module.Version type along with support code. +// +// The [module.Version] type is a simple Path, Version pair: +// +// type Version struct { +// Path string +// Version string +// } +// +// There are no restrictions imposed directly by use of this structure, +// but additional checking functions, most notably [Check], verify that +// a particular path, version pair is valid. +// +// # Escaped Paths +// +// Module paths appear as substrings of file system paths +// (in the download cache) and of web server URLs in the proxy protocol. +// In general we cannot rely on file systems to be case-sensitive, +// nor can we rely on web servers, since they read from file systems. +// That is, we cannot rely on the file system to keep rsc.io/QUOTE +// and rsc.io/quote separate. Windows and macOS don't. +// Instead, we must never require two different casings of a file path. +// Because we want the download cache to match the proxy protocol, +// and because we want the proxy protocol to be possible to serve +// from a tree of static files (which might be stored on a case-insensitive +// file system), the proxy protocol must never require two different casings +// of a URL path either. +// +// One possibility would be to make the escaped form be the lowercase +// hexadecimal encoding of the actual path bytes. This would avoid ever +// needing different casings of a file path, but it would be fairly illegible +// to most programmers when those paths appeared in the file system +// (including in file paths in compiler errors and stack traces) +// in web server logs, and so on. Instead, we want a safe escaped form that +// leaves most paths unaltered. +// +// The safe escaped form is to replace every uppercase letter +// with an exclamation mark followed by the letter's lowercase equivalent. +// +// For example, +// +// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. +// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy +// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. +// +// Import paths that avoid upper-case letters are left unchanged. +// Note that because import paths are ASCII-only and avoid various +// problematic punctuation (like : < and >), the escaped form is also ASCII-only +// and avoids the same problematic punctuation. +// +// Import paths have never allowed exclamation marks, so there is no +// need to define how to escape a literal !. +// +// # Unicode Restrictions +// +// Today, paths are disallowed from using Unicode. +// +// Although paths are currently disallowed from using Unicode, +// we would like at some point to allow Unicode letters as well, to assume that +// file systems and URLs are Unicode-safe (storing UTF-8), and apply +// the !-for-uppercase convention for escaping them in the file system. +// But there are at least two subtle considerations. +// +// First, note that not all case-fold equivalent distinct runes +// form an upper/lower pair. +// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) +// are three distinct runes that case-fold to each other. +// When we do add Unicode letters, we must not assume that upper/lower +// are the only case-equivalent pairs. +// Perhaps the Kelvin symbol would be disallowed entirely, for example. +// Or perhaps it would escape as "!!k", or perhaps as "(212A)". +// +// Second, it would be nice to allow Unicode marks as well as letters, +// but marks include combining marks, and then we must deal not +// only with case folding but also normalization: both U+00E9 ('é') +// and U+0065 U+0301 ('e' followed by combining acute accent) +// look the same on the page and are treated by some file systems +// as the same path. If we do allow Unicode marks in paths, there +// must be some kind of normalization to allow only one canonical +// encoding of any character used in an import path. +package module + +// IMPORTANT NOTE +// +// This file essentially defines the set of valid import paths for the go command. +// There are many subtle considerations, including Unicode ambiguity, +// security, network, and file system representations. +// +// This file also defines the set of valid module path and version combinations, +// another topic with many subtle considerations. +// +// Changes to the semantics in this file require approval from rsc. + +import ( + "errors" + "fmt" + "path" + "sort" + "strings" + "unicode" + "unicode/utf8" + + "golang.org/x/mod/semver" +) + +// A Version (for clients, a module.Version) is defined by a module path and version pair. +// These are stored in their plain (unescaped) form. +type Version struct { + // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2". + Path string + + // Version is usually a semantic version in canonical form. + // There are three exceptions to this general rule. + // First, the top-level target of a build has no specific version + // and uses Version = "". + // Second, during MVS calculations the version "none" is used + // to represent the decision to take no version of a given module. + // Third, filesystem paths found in "replace" directives are + // represented by a path with an empty version. + Version string `json:",omitempty"` +} + +// String returns a representation of the Version suitable for logging +// (Path@Version, or just Path if Version is empty). +func (m Version) String() string { + if m.Version == "" { + return m.Path + } + return m.Path + "@" + m.Version +} + +// A ModuleError indicates an error specific to a module. +type ModuleError struct { + Path string + Version string + Err error +} + +// VersionError returns a [ModuleError] derived from a [Version] and error, +// or err itself if it is already such an error. +func VersionError(v Version, err error) error { + var mErr *ModuleError + if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version { + return err + } + return &ModuleError{ + Path: v.Path, + Version: v.Version, + Err: err, + } +} + +func (e *ModuleError) Error() string { + if v, ok := e.Err.(*InvalidVersionError); ok { + return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err) + } + if e.Version != "" { + return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err) + } + return fmt.Sprintf("module %s: %v", e.Path, e.Err) +} + +func (e *ModuleError) Unwrap() error { return e.Err } + +// An InvalidVersionError indicates an error specific to a version, with the +// module path unknown or specified externally. +// +// A [ModuleError] may wrap an InvalidVersionError, but an InvalidVersionError +// must not wrap a ModuleError. +type InvalidVersionError struct { + Version string + Pseudo bool + Err error +} + +// noun returns either "version" or "pseudo-version", depending on whether +// e.Version is a pseudo-version. +func (e *InvalidVersionError) noun() string { + if e.Pseudo { + return "pseudo-version" + } + return "version" +} + +func (e *InvalidVersionError) Error() string { + return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err) +} + +func (e *InvalidVersionError) Unwrap() error { return e.Err } + +// An InvalidPathError indicates a module, import, or file path doesn't +// satisfy all naming constraints. See [CheckPath], [CheckImportPath], +// and [CheckFilePath] for specific restrictions. +type InvalidPathError struct { + Kind string // "module", "import", or "file" + Path string + Err error +} + +func (e *InvalidPathError) Error() string { + return fmt.Sprintf("malformed %s path %q: %v", e.Kind, e.Path, e.Err) +} + +func (e *InvalidPathError) Unwrap() error { return e.Err } + +// Check checks that a given module path, version pair is valid. +// In addition to the path being a valid module path +// and the version being a valid semantic version, +// the two must correspond. +// For example, the path "yaml/v2" only corresponds to +// semantic versions beginning with "v2.". +func Check(path, version string) error { + if err := CheckPath(path); err != nil { + return err + } + if !semver.IsValid(version) { + return &ModuleError{ + Path: path, + Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")}, + } + } + _, pathMajor, _ := SplitPathVersion(path) + if err := CheckPathMajor(version, pathMajor); err != nil { + return &ModuleError{Path: path, Err: err} + } + return nil +} + +// firstPathOK reports whether r can appear in the first element of a module path. +// The first element of the path must be an LDH domain name, at least for now. +// To avoid case ambiguity, the domain name must be entirely lower case. +func firstPathOK(r rune) bool { + return r == '-' || r == '.' || + '0' <= r && r <= '9' || + 'a' <= r && r <= 'z' +} + +// modPathOK reports whether r can appear in a module path element. +// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~. +// +// This matches what "go get" has historically recognized in import paths, +// and avoids confusing sequences like '%20' or '+' that would change meaning +// if used in a URL. +// +// TODO(rsc): We would like to allow Unicode letters, but that requires additional +// care in the safe encoding (see "escaped paths" above). +func modPathOK(r rune) bool { + if r < utf8.RuneSelf { + return r == '-' || r == '.' || r == '_' || r == '~' || + '0' <= r && r <= '9' || + 'A' <= r && r <= 'Z' || + 'a' <= r && r <= 'z' + } + return false +} + +// importPathOK reports whether r can appear in a package import path element. +// +// Import paths are intermediate between module paths and file paths: we allow +// disallow characters that would be confusing or ambiguous as arguments to +// 'go get' (such as '@' and ' ' ), but allow certain characters that are +// otherwise-unambiguous on the command line and historically used for some +// binary names (such as '++' as a suffix for compiler binaries and wrappers). +func importPathOK(r rune) bool { + return modPathOK(r) || r == '+' +} + +// fileNameOK reports whether r can appear in a file name. +// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters. +// If we expand the set of allowed characters here, we have to +// work harder at detecting potential case-folding and normalization collisions. +// See note about "escaped paths" above. +func fileNameOK(r rune) bool { + if r < utf8.RuneSelf { + // Entire set of ASCII punctuation, from which we remove characters: + // ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ + // We disallow some shell special characters: " ' * < > ? ` | + // (Note that some of those are disallowed by the Windows file system as well.) + // We also disallow path separators / : and \ (fileNameOK is only called on path element characters). + // We allow spaces (U+0020) in file names. + const allowed = "!#$%&()+,-.=@[]^_{}~ " + if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' { + return true + } + return strings.ContainsRune(allowed, r) + } + // It may be OK to add more ASCII punctuation here, but only carefully. + // For example Windows disallows < > \, and macOS disallows :, so we must not allow those. + return unicode.IsLetter(r) +} + +// CheckPath checks that a module path is valid. +// A valid module path is a valid import path, as checked by [CheckImportPath], +// with three additional constraints. +// First, the leading path element (up to the first slash, if any), +// by convention a domain name, must contain only lower-case ASCII letters, +// ASCII digits, dots (U+002E), and dashes (U+002D); +// it must contain at least one dot and cannot start with a dash. +// Second, for a final path element of the form /vN, where N looks numeric +// (ASCII digits and dots) must not begin with a leading zero, must not be /v1, +// and must not contain any dots. For paths beginning with "gopkg.in/", +// this second requirement is replaced by a requirement that the path +// follow the gopkg.in server's conventions. +// Third, no path element may begin with a dot. +func CheckPath(path string) (err error) { + defer func() { + if err != nil { + err = &InvalidPathError{Kind: "module", Path: path, Err: err} + } + }() + + if err := checkPath(path, modulePath); err != nil { + return err + } + i := strings.Index(path, "/") + if i < 0 { + i = len(path) + } + if i == 0 { + return fmt.Errorf("leading slash") + } + if !strings.Contains(path[:i], ".") { + return fmt.Errorf("missing dot in first path element") + } + if path[0] == '-' { + return fmt.Errorf("leading dash in first path element") + } + for _, r := range path[:i] { + if !firstPathOK(r) { + return fmt.Errorf("invalid char %q in first path element", r) + } + } + if _, _, ok := SplitPathVersion(path); !ok { + return fmt.Errorf("invalid version") + } + return nil +} + +// CheckImportPath checks that an import path is valid. +// +// A valid import path consists of one or more valid path elements +// separated by slashes (U+002F). (It must not begin with nor end in a slash.) +// +// A valid path element is a non-empty string made up of +// ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~. +// It must not end with a dot (U+002E), nor contain two dots in a row. +// +// The element prefix up to the first dot must not be a reserved file name +// on Windows, regardless of case (CON, com1, NuL, and so on). The element +// must not have a suffix of a tilde followed by one or more ASCII digits +// (to exclude paths elements that look like Windows short-names). +// +// CheckImportPath may be less restrictive in the future, but see the +// top-level package documentation for additional information about +// subtleties of Unicode. +func CheckImportPath(path string) error { + if err := checkPath(path, importPath); err != nil { + return &InvalidPathError{Kind: "import", Path: path, Err: err} + } + return nil +} + +// pathKind indicates what kind of path we're checking. Module paths, +// import paths, and file paths have different restrictions. +type pathKind int + +const ( + modulePath pathKind = iota + importPath + filePath +) + +// checkPath checks that a general path is valid. kind indicates what +// specific constraints should be applied. +// +// checkPath returns an error describing why the path is not valid. +// Because these checks apply to module, import, and file paths, +// and because other checks may be applied, the caller is expected to wrap +// this error with [InvalidPathError]. +func checkPath(path string, kind pathKind) error { + if !utf8.ValidString(path) { + return fmt.Errorf("invalid UTF-8") + } + if path == "" { + return fmt.Errorf("empty string") + } + if path[0] == '-' && kind != filePath { + return fmt.Errorf("leading dash") + } + if strings.Contains(path, "//") { + return fmt.Errorf("double slash") + } + if path[len(path)-1] == '/' { + return fmt.Errorf("trailing slash") + } + elemStart := 0 + for i, r := range path { + if r == '/' { + if err := checkElem(path[elemStart:i], kind); err != nil { + return err + } + elemStart = i + 1 + } + } + if err := checkElem(path[elemStart:], kind); err != nil { + return err + } + return nil +} + +// checkElem checks whether an individual path element is valid. +func checkElem(elem string, kind pathKind) error { + if elem == "" { + return fmt.Errorf("empty path element") + } + if strings.Count(elem, ".") == len(elem) { + return fmt.Errorf("invalid path element %q", elem) + } + if elem[0] == '.' && kind == modulePath { + return fmt.Errorf("leading dot in path element") + } + if elem[len(elem)-1] == '.' { + return fmt.Errorf("trailing dot in path element") + } + for _, r := range elem { + ok := false + switch kind { + case modulePath: + ok = modPathOK(r) + case importPath: + ok = importPathOK(r) + case filePath: + ok = fileNameOK(r) + default: + panic(fmt.Sprintf("internal error: invalid kind %v", kind)) + } + if !ok { + return fmt.Errorf("invalid char %q", r) + } + } + + // Windows disallows a bunch of path elements, sadly. + // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file + short := elem + if i := strings.Index(short, "."); i >= 0 { + short = short[:i] + } + for _, bad := range badWindowsNames { + if strings.EqualFold(bad, short) { + return fmt.Errorf("%q disallowed as path element component on Windows", short) + } + } + + if kind == filePath { + // don't check for Windows short-names in file names. They're + // only an issue for import paths. + return nil + } + + // Reject path components that look like Windows short-names. + // Those usually end in a tilde followed by one or more ASCII digits. + if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 { + suffix := short[tilde+1:] + suffixIsDigits := true + for _, r := range suffix { + if r < '0' || r > '9' { + suffixIsDigits = false + break + } + } + if suffixIsDigits { + return fmt.Errorf("trailing tilde and digits in path element") + } + } + + return nil +} + +// CheckFilePath checks that a slash-separated file path is valid. +// The definition of a valid file path is the same as the definition +// of a valid import path except that the set of allowed characters is larger: +// all Unicode letters, ASCII digits, the ASCII space character (U+0020), +// and the ASCII punctuation characters +// “!#$%&()+,-.=@[]^_{}~”. +// (The excluded punctuation characters, " * < > ? ` ' | / \ and :, +// have special meanings in certain shells or operating systems.) +// +// CheckFilePath may be less restrictive in the future, but see the +// top-level package documentation for additional information about +// subtleties of Unicode. +func CheckFilePath(path string) error { + if err := checkPath(path, filePath); err != nil { + return &InvalidPathError{Kind: "file", Path: path, Err: err} + } + return nil +} + +// badWindowsNames are the reserved file path elements on Windows. +// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file +var badWindowsNames = []string{ + "CON", + "PRN", + "AUX", + "NUL", + "COM1", + "COM2", + "COM3", + "COM4", + "COM5", + "COM6", + "COM7", + "COM8", + "COM9", + "LPT1", + "LPT2", + "LPT3", + "LPT4", + "LPT5", + "LPT6", + "LPT7", + "LPT8", + "LPT9", +} + +// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path +// and version is either empty or "/vN" for N >= 2. +// As a special case, gopkg.in paths are recognized directly; +// they require ".vN" instead of "/vN", and for all N, not just N >= 2. +// SplitPathVersion returns with ok = false when presented with +// a path whose last path element does not satisfy the constraints +// applied by [CheckPath], such as "example.com/pkg/v1" or "example.com/pkg/v1.2". +func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { + if strings.HasPrefix(path, "gopkg.in/") { + return splitGopkgIn(path) + } + + i := len(path) + dot := false + for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') { + if path[i-1] == '.' { + dot = true + } + i-- + } + if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' { + return path, "", true + } + prefix, pathMajor = path[:i-2], path[i-2:] + if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" { + return path, "", false + } + return prefix, pathMajor, true +} + +// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths. +func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { + if !strings.HasPrefix(path, "gopkg.in/") { + return path, "", false + } + i := len(path) + if strings.HasSuffix(path, "-unstable") { + i -= len("-unstable") + } + for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') { + i-- + } + if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' { + // All gopkg.in paths must end in vN for some N. + return path, "", false + } + prefix, pathMajor = path[:i-2], path[i-2:] + if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" { + return path, "", false + } + return prefix, pathMajor, true +} + +// MatchPathMajor reports whether the semantic version v +// matches the path major version pathMajor. +// +// MatchPathMajor returns true if and only if [CheckPathMajor] returns nil. +func MatchPathMajor(v, pathMajor string) bool { + return CheckPathMajor(v, pathMajor) == nil +} + +// CheckPathMajor returns a non-nil error if the semantic version v +// does not match the path major version pathMajor. +func CheckPathMajor(v, pathMajor string) error { + // TODO(jayconrod): return errors or panic for invalid inputs. This function + // (and others) was covered by integration tests for cmd/go, and surrounding + // code protected against invalid inputs like non-canonical versions. + if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { + pathMajor = strings.TrimSuffix(pathMajor, "-unstable") + } + if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" { + // Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1. + // For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405. + return nil + } + m := semver.Major(v) + if pathMajor == "" { + if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" { + return nil + } + pathMajor = "v0 or v1" + } else if pathMajor[0] == '/' || pathMajor[0] == '.' { + if m == pathMajor[1:] { + return nil + } + pathMajor = pathMajor[1:] + } + return &InvalidVersionError{ + Version: v, + Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)), + } +} + +// PathMajorPrefix returns the major-version tag prefix implied by pathMajor. +// An empty PathMajorPrefix allows either v0 or v1. +// +// Note that [MatchPathMajor] may accept some versions that do not actually begin +// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1' +// pathMajor, even though that pathMajor implies 'v1' tagging. +func PathMajorPrefix(pathMajor string) string { + if pathMajor == "" { + return "" + } + if pathMajor[0] != '/' && pathMajor[0] != '.' { + panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator") + } + if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { + pathMajor = strings.TrimSuffix(pathMajor, "-unstable") + } + m := pathMajor[1:] + if m != semver.Major(m) { + panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version") + } + return m +} + +// CanonicalVersion returns the canonical form of the version string v. +// It is the same as [semver.Canonical] except that it preserves the special build suffix "+incompatible". +func CanonicalVersion(v string) string { + cv := semver.Canonical(v) + if semver.Build(v) == "+incompatible" { + cv += "+incompatible" + } + return cv +} + +// Sort sorts the list by Path, breaking ties by comparing [Version] fields. +// The Version fields are interpreted as semantic versions (using [semver.Compare]) +// optionally followed by a tie-breaking suffix introduced by a slash character, +// like in "v0.0.1/go.mod". +func Sort(list []Version) { + sort.Slice(list, func(i, j int) bool { + mi := list[i] + mj := list[j] + if mi.Path != mj.Path { + return mi.Path < mj.Path + } + // To help go.sum formatting, allow version/file. + // Compare semver prefix by semver rules, + // file by string order. + vi := mi.Version + vj := mj.Version + var fi, fj string + if k := strings.Index(vi, "/"); k >= 0 { + vi, fi = vi[:k], vi[k:] + } + if k := strings.Index(vj, "/"); k >= 0 { + vj, fj = vj[:k], vj[k:] + } + if vi != vj { + return semver.Compare(vi, vj) < 0 + } + return fi < fj + }) +} + +// EscapePath returns the escaped form of the given module path. +// It fails if the module path is invalid. +func EscapePath(path string) (escaped string, err error) { + if err := CheckPath(path); err != nil { + return "", err + } + + return escapeString(path) +} + +// EscapeVersion returns the escaped form of the given module version. +// Versions are allowed to be in non-semver form but must be valid file names +// and not contain exclamation marks. +func EscapeVersion(v string) (escaped string, err error) { + if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") { + return "", &InvalidVersionError{ + Version: v, + Err: fmt.Errorf("disallowed version string"), + } + } + return escapeString(v) +} + +func escapeString(s string) (escaped string, err error) { + haveUpper := false + for _, r := range s { + if r == '!' || r >= utf8.RuneSelf { + // This should be disallowed by CheckPath, but diagnose anyway. + // The correctness of the escaping loop below depends on it. + return "", fmt.Errorf("internal error: inconsistency in EscapePath") + } + if 'A' <= r && r <= 'Z' { + haveUpper = true + } + } + + if !haveUpper { + return s, nil + } + + var buf []byte + for _, r := range s { + if 'A' <= r && r <= 'Z' { + buf = append(buf, '!', byte(r+'a'-'A')) + } else { + buf = append(buf, byte(r)) + } + } + return string(buf), nil +} + +// UnescapePath returns the module path for the given escaped path. +// It fails if the escaped path is invalid or describes an invalid path. +func UnescapePath(escaped string) (path string, err error) { + path, ok := unescapeString(escaped) + if !ok { + return "", fmt.Errorf("invalid escaped module path %q", escaped) + } + if err := CheckPath(path); err != nil { + return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err) + } + return path, nil +} + +// UnescapeVersion returns the version string for the given escaped version. +// It fails if the escaped form is invalid or describes an invalid version. +// Versions are allowed to be in non-semver form but must be valid file names +// and not contain exclamation marks. +func UnescapeVersion(escaped string) (v string, err error) { + v, ok := unescapeString(escaped) + if !ok { + return "", fmt.Errorf("invalid escaped version %q", escaped) + } + if err := checkElem(v, filePath); err != nil { + return "", fmt.Errorf("invalid escaped version %q: %v", v, err) + } + return v, nil +} + +func unescapeString(escaped string) (string, bool) { + var buf []byte + + bang := false + for _, r := range escaped { + if r >= utf8.RuneSelf { + return "", false + } + if bang { + bang = false + if r < 'a' || 'z' < r { + return "", false + } + buf = append(buf, byte(r+'A'-'a')) + continue + } + if r == '!' { + bang = true + continue + } + if 'A' <= r && r <= 'Z' { + return "", false + } + buf = append(buf, byte(r)) + } + if bang { + return "", false + } + return string(buf), true +} + +// MatchPrefixPatterns reports whether any path prefix of target matches one of +// the glob patterns (as defined by [path.Match]) in the comma-separated globs +// list. This implements the algorithm used when matching a module path to the +// GOPRIVATE environment variable, as described by 'go help module-private'. +// +// It ignores any empty or malformed patterns in the list. +// Trailing slashes on patterns are ignored. +func MatchPrefixPatterns(globs, target string) bool { + for globs != "" { + // Extract next non-empty glob in comma-separated list. + var glob string + if i := strings.Index(globs, ","); i >= 0 { + glob, globs = globs[:i], globs[i+1:] + } else { + glob, globs = globs, "" + } + glob = strings.TrimSuffix(glob, "/") + if glob == "" { + continue + } + + // A glob with N+1 path elements (N slashes) needs to be matched + // against the first N+1 path elements of target, + // which end just before the N+1'th slash. + n := strings.Count(glob, "/") + prefix := target + // Walk target, counting slashes, truncating at the N+1'th slash. + for i := 0; i < len(target); i++ { + if target[i] == '/' { + if n == 0 { + prefix = target[:i] + break + } + n-- + } + } + if n > 0 { + // Not enough prefix elements. + continue + } + matched, _ := path.Match(glob, prefix) + if matched { + return true + } + } + return false +} diff --git a/vendor/golang.org/x/mod/module/pseudo.go b/vendor/golang.org/x/mod/module/pseudo.go new file mode 100644 index 0000000000..9cf19d3254 --- /dev/null +++ b/vendor/golang.org/x/mod/module/pseudo.go @@ -0,0 +1,250 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Pseudo-versions +// +// Code authors are expected to tag the revisions they want users to use, +// including prereleases. However, not all authors tag versions at all, +// and not all commits a user might want to try will have tags. +// A pseudo-version is a version with a special form that allows us to +// address an untagged commit and order that version with respect to +// other versions we might encounter. +// +// A pseudo-version takes one of the general forms: +// +// (1) vX.0.0-yyyymmddhhmmss-abcdef123456 +// (2) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 +// (3) vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible +// (4) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 +// (5) vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible +// +// If there is no recently tagged version with the right major version vX, +// then form (1) is used, creating a space of pseudo-versions at the bottom +// of the vX version range, less than any tagged version, including the unlikely v0.0.0. +// +// If the most recent tagged version before the target commit is vX.Y.Z or vX.Y.Z+incompatible, +// then the pseudo-version uses form (2) or (3), making it a prerelease for the next +// possible semantic version after vX.Y.Z. The leading 0 segment in the prerelease string +// ensures that the pseudo-version compares less than possible future explicit prereleases +// like vX.Y.(Z+1)-rc1 or vX.Y.(Z+1)-1. +// +// If the most recent tagged version before the target commit is vX.Y.Z-pre or vX.Y.Z-pre+incompatible, +// then the pseudo-version uses form (4) or (5), making it a slightly later prerelease. + +package module + +import ( + "errors" + "fmt" + "strings" + "time" + + "golang.org/x/mod/internal/lazyregexp" + "golang.org/x/mod/semver" +) + +var pseudoVersionRE = lazyregexp.New(`^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$`) + +const PseudoVersionTimestampFormat = "20060102150405" + +// PseudoVersion returns a pseudo-version for the given major version ("v1") +// preexisting older tagged version ("" or "v1.2.3" or "v1.2.3-pre"), revision time, +// and revision identifier (usually a 12-byte commit hash prefix). +func PseudoVersion(major, older string, t time.Time, rev string) string { + if major == "" { + major = "v0" + } + segment := fmt.Sprintf("%s-%s", t.UTC().Format(PseudoVersionTimestampFormat), rev) + build := semver.Build(older) + older = semver.Canonical(older) + if older == "" { + return major + ".0.0-" + segment // form (1) + } + if semver.Prerelease(older) != "" { + return older + ".0." + segment + build // form (4), (5) + } + + // Form (2), (3). + // Extract patch from vMAJOR.MINOR.PATCH + i := strings.LastIndex(older, ".") + 1 + v, patch := older[:i], older[i:] + + // Reassemble. + return v + incDecimal(patch) + "-0." + segment + build +} + +// ZeroPseudoVersion returns a pseudo-version with a zero timestamp and +// revision, which may be used as a placeholder. +func ZeroPseudoVersion(major string) string { + return PseudoVersion(major, "", time.Time{}, "000000000000") +} + +// incDecimal returns the decimal string incremented by 1. +func incDecimal(decimal string) string { + // Scan right to left turning 9s to 0s until you find a digit to increment. + digits := []byte(decimal) + i := len(digits) - 1 + for ; i >= 0 && digits[i] == '9'; i-- { + digits[i] = '0' + } + if i >= 0 { + digits[i]++ + } else { + // digits is all zeros + digits[0] = '1' + digits = append(digits, '0') + } + return string(digits) +} + +// decDecimal returns the decimal string decremented by 1, or the empty string +// if the decimal is all zeroes. +func decDecimal(decimal string) string { + // Scan right to left turning 0s to 9s until you find a digit to decrement. + digits := []byte(decimal) + i := len(digits) - 1 + for ; i >= 0 && digits[i] == '0'; i-- { + digits[i] = '9' + } + if i < 0 { + // decimal is all zeros + return "" + } + if i == 0 && digits[i] == '1' && len(digits) > 1 { + digits = digits[1:] + } else { + digits[i]-- + } + return string(digits) +} + +// IsPseudoVersion reports whether v is a pseudo-version. +func IsPseudoVersion(v string) bool { + return strings.Count(v, "-") >= 2 && semver.IsValid(v) && pseudoVersionRE.MatchString(v) +} + +// IsZeroPseudoVersion returns whether v is a pseudo-version with a zero base, +// timestamp, and revision, as returned by [ZeroPseudoVersion]. +func IsZeroPseudoVersion(v string) bool { + return v == ZeroPseudoVersion(semver.Major(v)) +} + +// PseudoVersionTime returns the time stamp of the pseudo-version v. +// It returns an error if v is not a pseudo-version or if the time stamp +// embedded in the pseudo-version is not a valid time. +func PseudoVersionTime(v string) (time.Time, error) { + _, timestamp, _, _, err := parsePseudoVersion(v) + if err != nil { + return time.Time{}, err + } + t, err := time.Parse("20060102150405", timestamp) + if err != nil { + return time.Time{}, &InvalidVersionError{ + Version: v, + Pseudo: true, + Err: fmt.Errorf("malformed time %q", timestamp), + } + } + return t, nil +} + +// PseudoVersionRev returns the revision identifier of the pseudo-version v. +// It returns an error if v is not a pseudo-version. +func PseudoVersionRev(v string) (rev string, err error) { + _, _, rev, _, err = parsePseudoVersion(v) + return +} + +// PseudoVersionBase returns the canonical parent version, if any, upon which +// the pseudo-version v is based. +// +// If v has no parent version (that is, if it is "vX.0.0-[…]"), +// PseudoVersionBase returns the empty string and a nil error. +func PseudoVersionBase(v string) (string, error) { + base, _, _, build, err := parsePseudoVersion(v) + if err != nil { + return "", err + } + + switch pre := semver.Prerelease(base); pre { + case "": + // vX.0.0-yyyymmddhhmmss-abcdef123456 → "" + if build != "" { + // Pseudo-versions of the form vX.0.0-yyyymmddhhmmss-abcdef123456+incompatible + // are nonsensical: the "vX.0.0-" prefix implies that there is no parent tag, + // but the "+incompatible" suffix implies that the major version of + // the parent tag is not compatible with the module's import path. + // + // There are a few such entries in the index generated by proxy.golang.org, + // but we believe those entries were generated by the proxy itself. + return "", &InvalidVersionError{ + Version: v, + Pseudo: true, + Err: fmt.Errorf("lacks base version, but has build metadata %q", build), + } + } + return "", nil + + case "-0": + // vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z + // vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z+incompatible + base = strings.TrimSuffix(base, pre) + i := strings.LastIndexByte(base, '.') + if i < 0 { + panic("base from parsePseudoVersion missing patch number: " + base) + } + patch := decDecimal(base[i+1:]) + if patch == "" { + // vX.0.0-0 is invalid, but has been observed in the wild in the index + // generated by requests to proxy.golang.org. + // + // NOTE(bcmills): I cannot find a historical bug that accounts for + // pseudo-versions of this form, nor have I seen such versions in any + // actual go.mod files. If we find actual examples of this form and a + // reasonable theory of how they came into existence, it seems fine to + // treat them as equivalent to vX.0.0 (especially since the invalid + // pseudo-versions have lower precedence than the real ones). For now, we + // reject them. + return "", &InvalidVersionError{ + Version: v, + Pseudo: true, + Err: fmt.Errorf("version before %s would have negative patch number", base), + } + } + return base[:i+1] + patch + build, nil + + default: + // vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456 → vX.Y.Z-pre + // vX.Y.Z-pre.0.yyyymmddhhmmss-abcdef123456+incompatible → vX.Y.Z-pre+incompatible + if !strings.HasSuffix(base, ".0") { + panic(`base from parsePseudoVersion missing ".0" before date: ` + base) + } + return strings.TrimSuffix(base, ".0") + build, nil + } +} + +var errPseudoSyntax = errors.New("syntax error") + +func parsePseudoVersion(v string) (base, timestamp, rev, build string, err error) { + if !IsPseudoVersion(v) { + return "", "", "", "", &InvalidVersionError{ + Version: v, + Pseudo: true, + Err: errPseudoSyntax, + } + } + build = semver.Build(v) + v = strings.TrimSuffix(v, build) + j := strings.LastIndex(v, "-") + v, rev = v[:j], v[j+1:] + i := strings.LastIndex(v, "-") + if j := strings.LastIndex(v, "."); j > i { + base = v[:j] // "vX.Y.Z-pre.0" or "vX.Y.(Z+1)-0" + timestamp = v[j+1:] + } else { + base = v[:i] // "vX.0.0" + timestamp = v[i+1:] + } + return base, timestamp, rev, build, nil +} diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 424c1bd368..1f1eade0ac 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -1059,8 +1059,7 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, // Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit // status if there's a dependency on a package that doesn't exist. But it should return // a zero exit status and set an error on that package. - // gopdlv: comment temporarily for Go+ - /* if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { // Don't clobber stdout if `go list` actually returned something. if len(stdout.String()) > 0 { return stdout, nil @@ -1075,7 +1074,7 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, importPath, strings.Trim(stderrStr, "\n")) return bytes.NewBufferString(output), nil - } */ + } // Export mode entails a build. // If that build fails, errors appear on stderr diff --git a/vendor/modules.txt b/vendor/modules.txt index fc1e798b25..365c4f862d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -33,6 +33,14 @@ github.com/go-delve/liner # github.com/google/go-dap v0.11.0 ## explicit; go 1.13 github.com/google/go-dap +# github.com/goplus/mod v0.12.3 +## explicit; go 1.16 +github.com/goplus/mod +github.com/goplus/mod/gopmod +github.com/goplus/mod/modcache +github.com/goplus/mod/modfetch +github.com/goplus/mod/modfile +github.com/goplus/mod/modload # github.com/hashicorp/golang-lru v1.0.2 ## explicit; go 1.12 github.com/hashicorp/golang-lru/simplelru @@ -48,6 +56,9 @@ github.com/mattn/go-isatty # github.com/mattn/go-runewidth v0.0.13 ## explicit; go 1.9 github.com/mattn/go-runewidth +# github.com/qiniu/x v1.13.2 +## explicit; go 1.13 +github.com/qiniu/x/errors # github.com/rivo/uniseg v0.2.0 ## explicit; go 1.12 github.com/rivo/uniseg @@ -85,6 +96,9 @@ golang.org/x/exp/maps golang.org/x/exp/slices # golang.org/x/mod v0.14.0 ## explicit; go 1.18 +golang.org/x/mod/internal/lazyregexp +golang.org/x/mod/modfile +golang.org/x/mod/module golang.org/x/mod/semver # golang.org/x/sys v0.13.0 ## explicit; go 1.17