diff --git a/go.mod b/go.mod index 975e144b1935..e69f8dd0e15b 100644 --- a/go.mod +++ b/go.mod @@ -81,7 +81,7 @@ require ( github.com/spf13/viper v1.10.1 github.com/ssgreg/nlreturn/v2 v2.2.1 github.com/stretchr/testify v1.7.0 - github.com/sylvia7788/contextcheck v1.0.4 + github.com/sylvia7788/contextcheck v1.0.9 github.com/tdakkota/asciicheck v0.1.1 github.com/tetafro/godot v1.4.11 github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 @@ -94,7 +94,7 @@ require ( github.com/yagipy/maintidx v1.0.0 github.com/yeya24/promlinter v0.1.1-0.20210918184747-d757024714a1 gitlab.com/bosi/decorder v0.2.1 - golang.org/x/tools v0.1.10 + golang.org/x/tools v0.1.12 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b honnef.co/go/tools v0.2.2 mvdan.cc/gofumpt v0.3.0 @@ -156,10 +156,9 @@ require ( github.com/tklauser/numcpus v0.3.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -170,5 +169,5 @@ require github.com/hashicorp/go-version v1.2.1 require ( github.com/hexops/gotextdiff v1.0.3 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect ) diff --git a/go.sum b/go.sum index b0feaff6dc6b..6a16bf41a232 100644 --- a/go.sum +++ b/go.sum @@ -706,21 +706,15 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/sylvia7788/contextcheck v1.0.4 h1:MsiVqROAdr0efZc/fOCt0c235qm9XJqHtWwM+2h2B04= -github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= +github.com/sylvia7788/contextcheck v1.0.9 h1:A+Y3lETi9uVKdMuC3yGdws5ytL0yVJv05j21+WpDHzs= +github.com/sylvia7788/contextcheck v1.0.9/go.mod h1:9XDxwvxyuKD+8N+a7Gs7bfWLityh5t70g/GjdEt2N2M= github.com/tdakkota/asciicheck v0.1.1 h1:PKzG7JUTUmVspQTDqtkX9eSiLGossXTybutHwTXuO0A= github.com/tdakkota/asciicheck v0.1.1/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= -github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= -github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= @@ -769,6 +763,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= gitlab.com/bosi/decorder v0.2.1 h1:ehqZe8hI4w7O4b1vgsDZw1YU1PE7iJXrQWFMsocbQ1w= @@ -814,6 +809,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -852,8 +848,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -903,8 +899,9 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -931,8 +928,9 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1013,10 +1011,13 @@ golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/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-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1118,10 +1119,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -1130,8 +1129,8 @@ golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/golinters/goanalysis/linter.go b/pkg/golinters/goanalysis/linter.go index ef49e4284aa3..36b755452ff7 100644 --- a/pkg/golinters/goanalysis/linter.go +++ b/pkg/golinters/goanalysis/linter.go @@ -10,6 +10,7 @@ import ( "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/lint/linter" + libpackages "github.com/golangci/golangci-lint/pkg/packages" "github.com/golangci/golangci-lint/pkg/result" ) @@ -144,6 +145,31 @@ func (lnt *Linter) configure() error { return nil } +func buildIssuesFromErrorsForTypecheckMode(errs []error, lintCtx *linter.Context) ([]result.Issue, error) { + var issues []result.Issue + uniqReportedIssues := map[string]bool{} + for _, err := range errs { + itErr, ok := errors.Cause(err).(*IllTypedError) + if !ok { + return nil, err + } + for _, err := range libpackages.ExtractErrors(itErr.Pkg) { + i, perr := parseError(err) + if perr != nil { // failed to parse + if uniqReportedIssues[err.Msg] { + continue + } + uniqReportedIssues[err.Msg] = true + lintCtx.Log.Errorf("typechecking error: %s", err.Msg) + } else { + i.Pkg = itErr.Pkg // to save to cache later + issues = append(issues, *i) + } + } + } + return issues, nil +} + func (lnt *Linter) preRun(lintCtx *linter.Context) error { if err := analysis.Validate(lnt.analyzers); err != nil { return errors.Wrap(err, "failed to validate analyzers") diff --git a/pkg/golinters/goanalysis/linter_test.go b/pkg/golinters/goanalysis/linter_test.go index 44ded60043c0..a56e0be12997 100644 --- a/pkg/golinters/goanalysis/linter_test.go +++ b/pkg/golinters/goanalysis/linter_test.go @@ -2,9 +2,14 @@ package goanalysis import ( "fmt" + "go/token" + "reflect" "testing" + "github.com/golangci/golangci-lint/pkg/result" + "github.com/stretchr/testify/assert" + "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/packages" ) @@ -46,3 +51,234 @@ func TestParseError(t *testing.T) { assert.Equal(t, "msg", i.Text) } } + +func Test_buildIssues(t *testing.T) { + type args struct { + diags []Diagnostic + linterNameBuilder func(diag *Diagnostic) string + } + tests := []struct { + name string + args args + want []result.Issue + }{ + { + name: "No Diagnostics", + args: args{ + diags: []Diagnostic{}, + linterNameBuilder: func(*Diagnostic) string { + return "some-linter" + }, + }, + want: []result.Issue(nil), + }, + { + name: "Linter Name is Analyzer Name", + args: args{ + diags: []Diagnostic{ + { + Diagnostic: analysis.Diagnostic{ + Message: "failure message", + }, + Analyzer: &analysis.Analyzer{ + Name: "some-linter", + }, + Position: token.Position{}, + Pkg: nil, + }, + }, + linterNameBuilder: func(*Diagnostic) string { + return "some-linter" + }, + }, + want: []result.Issue{ + { + FromLinter: "some-linter", + Text: "failure message", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := buildIssues(tt.args.diags, tt.args.linterNameBuilder); !reflect.DeepEqual(got, tt.want) { + t.Errorf("buildIssues() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_buildSingleIssue(t *testing.T) { + type args struct { + diag *Diagnostic + linterName string + } + fakePkg := packages.Package{ + Fset: makeFakeFileSet(), + } + tests := []struct { + name string + args args + wantIssue result.Issue + }{ + { + name: "Linter Name is Analyzer Name", + args: args{ + diag: &Diagnostic{ + Diagnostic: analysis.Diagnostic{ + Message: "failure message", + }, + Analyzer: &analysis.Analyzer{ + Name: "some-linter", + }, + Position: token.Position{}, + Pkg: nil, + }, + + linterName: "some-linter", + }, + wantIssue: result.Issue{ + FromLinter: "some-linter", + Text: "failure message", + }, + }, + { + name: "Linter Name is NOT Analyzer Name", + args: args{ + diag: &Diagnostic{ + Diagnostic: analysis.Diagnostic{ + Message: "failure message", + }, + Analyzer: &analysis.Analyzer{ + Name: "some-analyzer", + }, + Position: token.Position{}, + Pkg: nil, + }, + linterName: "some-linter", + }, + wantIssue: result.Issue{ + FromLinter: "some-linter", + Text: "some-analyzer: failure message", + }, + }, + { + name: "Shows issue when suggested edits exist but has no TextEdits", + args: args{ + diag: &Diagnostic{ + Diagnostic: analysis.Diagnostic{ + Message: "failure message", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "fix something", + TextEdits: []analysis.TextEdit{}, + }, + }, + }, + Analyzer: &analysis.Analyzer{ + Name: "some-analyzer", + }, + Position: token.Position{}, + Pkg: nil, + }, + linterName: "some-linter", + }, + wantIssue: result.Issue{ + FromLinter: "some-linter", + Text: "some-analyzer: failure message", + }, + }, + { + name: "Replace Whole Line", + args: args{ + diag: &Diagnostic{ + Diagnostic: analysis.Diagnostic{ + Message: "failure message", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "fix something", + TextEdits: []analysis.TextEdit{ + { + Pos: 101, + End: 201, + NewText: []byte("// Some comment to fix\n"), + }, + }, + }, + }, + }, + Analyzer: &analysis.Analyzer{ + Name: "some-analyzer", + }, + Position: token.Position{}, + Pkg: &fakePkg, + }, + linterName: "some-linter", + }, + wantIssue: result.Issue{ + FromLinter: "some-linter", + Text: "some-analyzer: failure message", + LineRange: &result.Range{ + From: 2, + To: 2, + }, + Replacement: &result.Replacement{ + NeedOnlyDelete: false, + NewLines: []string{ + "// Some comment to fix", + }, + }, + Pkg: &fakePkg, + }, + }, + { + name: "Excludes Replacement if TextEdit doesn't modify only whole lines", + args: args{ + diag: &Diagnostic{ + Diagnostic: analysis.Diagnostic{ + Message: "failure message", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "fix something", + TextEdits: []analysis.TextEdit{ + { + Pos: 101, + End: 151, + NewText: []byte("// Some comment to fix\n"), + }, + }, + }, + }, + }, + Analyzer: &analysis.Analyzer{ + Name: "some-analyzer", + }, + Position: token.Position{}, + Pkg: &fakePkg, + }, + linterName: "some-linter", + }, + wantIssue: result.Issue{ + FromLinter: "some-linter", + Text: "some-analyzer: failure message", + Pkg: &fakePkg, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotIssues := buildSingleIssue(tt.args.diag, tt.args.linterName); !reflect.DeepEqual(gotIssues, tt.wantIssue) { + t.Errorf("buildSingleIssue() = %v, want %v", gotIssues, tt.wantIssue) + } + }) + } +} + +func makeFakeFileSet() *token.FileSet { + fSet := token.NewFileSet() + file := fSet.AddFile("fake.go", 1, 1000) + for i := 100; i < 1000; i += 100 { + file.AddLine(i) + } + return fSet +} diff --git a/pkg/golinters/goanalysis/runners.go b/pkg/golinters/goanalysis/runners.go index 7e4cf902e79c..8277fd5c9298 100644 --- a/pkg/golinters/goanalysis/runners.go +++ b/pkg/golinters/goanalysis/runners.go @@ -2,6 +2,7 @@ package goanalysis import ( "fmt" + "go/token" "runtime" "sort" "strings" @@ -88,34 +89,63 @@ func buildIssues(diags []Diagnostic, linterNameBuilder func(diag *Diagnostic) st var issues []result.Issue for i := range diags { diag := &diags[i] - linterName := linterNameBuilder(diag) + issues = append(issues, buildSingleIssue(diag, linterNameBuilder(diag))) + } + return issues +} - var text string - if diag.Analyzer.Name == linterName { - text = diag.Message - } else { - text = fmt.Sprintf("%s: %s", diag.Analyzer.Name, diag.Message) - } +func buildSingleIssue(diag *Diagnostic, linterName string) result.Issue { + text := generateIssueText(diag, linterName) + issue := result.Issue{ + FromLinter: linterName, + Text: text, + Pos: diag.Position, + Pkg: diag.Pkg, + } + + if len(diag.SuggestedFixes) > 0 { + // Don't really have a better way of picking a best fix right now + chosenFix := diag.SuggestedFixes[0] + + // It could be confusing to return more than one issue per single diagnostic, + // but if we return a subset it might be a partial application of a fix. Don't + // apply a fix unless there is only one for now + if len(chosenFix.TextEdits) == 1 { + edit := chosenFix.TextEdits[0] + + pos := diag.Pkg.Fset.Position(edit.Pos) + end := diag.Pkg.Fset.Position(edit.End) + + newLines := strings.Split(string(edit.NewText), "\n") - issues = append(issues, result.Issue{ - FromLinter: linterName, - Text: text, - Pos: diag.Position, - Pkg: diag.Pkg, - }) - - if len(diag.Related) > 0 { - for _, info := range diag.Related { - issues = append(issues, result.Issue{ - FromLinter: linterName, - Text: fmt.Sprintf("%s(related information): %s", diag.Analyzer.Name, info.Message), - Pos: diag.Pkg.Fset.Position(info.Pos), - Pkg: diag.Pkg, - }) + // This only works if we're only replacing whole lines with brand new lines + if onlyReplacesWholeLines(pos, end, newLines) { + + // both original and new content ends with newline, omit to avoid partial line replacement + newLines = newLines[:len(newLines)-1] + + issue.Replacement = &result.Replacement{NewLines: newLines} + issue.LineRange = &result.Range{From: pos.Line, To: end.Line - 1} + + return issue } } } - return issues + + return issue +} + +func onlyReplacesWholeLines(oPos token.Position, oEnd token.Position, newLines []string) bool { + return oPos.Column == 1 && oEnd.Column == 1 && + oPos.Line < oEnd.Line && // must be replacing at least one line + newLines[len(newLines)-1] == "" // edit.NewText ended with '\n' +} + +func generateIssueText(diag *Diagnostic, linterName string) string { + if diag.Analyzer.Name == linterName { + return diag.Message + } + return fmt.Sprintf("%s: %s", diag.Analyzer.Name, diag.Message) } func getIssuesCacheKey(analyzers []*analysis.Analyzer) string {