diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8d3242 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.idea/ +/.vscode/ +/dist/ +.DS_Store +/*.pprof +/*.txt +/golangci-lint +/golangci-lint.exe +/example.so diff --git a/.golangci.example.yml b/.golangci.example.yml new file mode 100644 index 0000000..5231bd0 --- /dev/null +++ b/.golangci.example.yml @@ -0,0 +1,22 @@ +version: "2" + +linters: + default: none + enable: + - example + + settings: + custom: + example: + # Path is required + path: example.so + # Description is optional + description: The description of the linter. This is optional, but shows up when running `golangci-lint linters`. + # Original-url is optional, and is only used for documentation purposes. + original-url: github.com/golangci/example-plugin-linter + settings: + one: Foo + three: + name: Bar + two: + - name: Bar diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index e02e275..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,15 +0,0 @@ -linters-settings: - custom: - example: -# Path is required - path: example.so -# description is optional - description: The description of the linter. This is optional, but shows up when running `golangci-lint linters`. -# original-url is optional, and is only used for documentation purposes. - original-url: github.com/golangci/example-linter -linters: - disable-all: true - enable: - - example -# Typecheck is how golangci-lint reports compile errors, so should always be enabled. - - typecheck \ No newline at end of file diff --git a/README.md b/README.md index 0d3f21d..118e2e8 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,85 @@ This is an example linter that can be compiled into a plugin for `golangci-lint`. -To use this: - ### Create the Plugin From This Linter -1. Download the source code \* +1. Download the source code 2. From the root project directory, run `go build -buildmode=plugin plugin/example.go`. -3. Copy the generated `example.so` file into your project or to some other known location of your choosing. \** - +3. Copy the generated `example.so` file into your project or to some other known location of your choosing. [^1] ### Create a Copy of `golangci-lint` that Can Run with Plugins -In order to use plugins, you'll need a golangci-lint executable that can run them. Plugin dependencies defined in the -`go.mod` file MUST have a matching version (or hash) as the same dependency in th `golangci-lint` binary if the -dependency is used in both. Because of the high probability of this both using the same dependency, it is recommended -to use a locally built binary. To do so: +In order to use plugins, you'll need a golangci-lint executable that can run them. + +Plugin dependencies defined in the `go.mod` file MUST have a matching version (or hash) as the same dependency in th `golangci-lint` binary if the dependency is used in both. + +Because of the high probability of this both using the same dependency, it is recommended to use a locally built binary. + +To do so: 1. Download [golangci-lint](https://github.com/golangci/golangci-lint) source code -2. From the projects root directory, run `make` +2. From the projects root directory, run `make build` 3. Copy the `golangci-lint` executable that was created to your path, project, or other location ### Configure Your Project for Linting -If you already have a linter plugin available, you can follow these steps to define its usage in a projects -`.golangci.yml` file. If you're looking for instructions on how to configure your own custom linter, they can be found -further down. +If you already have a linter plugin available, you can follow these steps to define its usage in a projects `.golangci.yml` file. -1. If the project you want to lint does not have one already, copy the [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) - to the root directory. -2. Adjust the yaml to appropriate `linters-settings:custom` entries as so: -``` -linters-settings: - custom: - example: - path: /example.so - description: The description of the linter - original-url: github.com/golangci/example-linter -``` +If you're looking for instructions on how to configure your own custom linter, they can be found further down. -That is all the configuration that is required to run a custom linter in your project. Custom linters are enabled by default, -but abide by the same rules as other linters. If the disable all option is specified either on command line or in -`.golang.yml` files `linters:disable-all: true`, custom linters will be disabled; they can be re-enabled by adding them -to the `linters:enable` list, or providing the enabled option on the command line, `golangci-lint run -Eexample`. +1. If the project you want to lint does not have one already, copy the [.golangci.yml](https://github.com/golangci/golangci-lint/blob/master/.golangci.yml) to the root directory. +2. Adjust the YAML to appropriate `linters-settings.custom` entries as so: + ```yaml + linters: + settings: + custom: + example: + path: /example.so + description: The description of the linter + original-url: github.com/golangci/example-linter + settings: # Settings are optional. + one: Foo + two: + - name: Bar + three: + name: Bar + ``` -### To Create Your Own Custom Linter +That is all the configuration that is required to run a custom linter in your project. -Your linter must implement one or more `golang.org/x/tools/go/analysis.Analyzer` structs. -Your project should also use `go.mod`. All versions of libraries that overlap `golangci-lint` (including replaced -libraries) MUST be set to the same version as `golangci-lint`. You can see the versions by running `go version -m golangci-lint`. +Custom linters are enabled by default, but abide by the same rules as other linters. -You'll also need to create a go file like `plugin/example.go`. This MUST be in the package `main`, and define a -variable of name `AnalyzerPlugin`. The `AnalyzerPlugin` instance MUST implement the following interface: -``` -type AnalyzerPlugin interface { - GetAnalyzers() []*analysis.Analyzer +If the disable all option is specified either on command line or in `.golang.yml` files `linters.disable-all: true`, custom linters will be disabled; +they can be re-enabled by adding them to the `linters.enable` list, +or providing the enabled option on the command line, `golangci-lint run -Eexample`. + +The configuration inside the `settings` field of linter have some limitations (there are NOT related to the plugin system itself): +we use Viper to handle the configuration but Viper put all the keys in lowercase, and `.` cannot be used inside a key. + +### To Create Your Own Plugin + +Your linter must provide one or more `golang.org/x/tools/go/analysis.Analyzer` structs. + +Your project should also use `go.mod`. + +All versions of libraries that overlap `golangci-lint` (including replaced libraries) MUST be set to the same version as `golangci-lint`. +You can see the versions by running `go version -m golangci-lint`. + +You'll also need to create a Go file like `plugin/example.go`. + +This file MUST be in the package `main`, and MUST define an exposed function called `New` with the following signature: +```go +func New(conf any) ([]*analysis.Analyzer, error) { + // ... } ``` -The type of `AnalyzerPlugin` is not important, but is by convention `type analyzerPlugin struct {}`. See -[plugin/example.go](https://github.com/golangci/example-plugin-linter/plugin/example.go) for more info. -To build the plugin, from the root project directory, run `go build -buildmode=plugin plugin/example.go`. This will create a plugin `*.so` -file that can be copied into your project or another well known location for usage in golangci-lint. +See [plugin/example.go](https://github.com/golangci/example-plugin-linter/blob/master/plugin/example.go) for more info. + +To build the plugin, from the root project directory, run: +```bash +go build -buildmode=plugin plugin/example.go +``` -\* Sorry, I haven't found a way to enable `go get` functionality for plugins yet. If you know how, let me know! +This will create a plugin `*.so` file that can be copied into your project or another well known location for usage in `golangci-lint`. -\** Alternately, you can use the `-o /path/to/location/example.so` output flag to have it put it there for you. +[^1]: Alternately, you can use the `-o /path/to/location/example.so` output flag to have it put it there for you. diff --git a/example.go b/example.go index 446e1b0..88b0f8e 100644 --- a/example.go +++ b/example.go @@ -2,8 +2,9 @@ package linters import ( "go/ast" - "golang.org/x/tools/go/analysis" "strings" + + "golang.org/x/tools/go/analysis" ) var TodoAnalyzer = &analysis.Analyzer{ @@ -16,8 +17,7 @@ func run(pass *analysis.Pass) (interface{}, error) { for _, file := range pass.Files { ast.Inspect(file, func(n ast.Node) bool { if comment, ok := n.(*ast.Comment); ok { - if strings.HasPrefix(comment.Text, "// TODO:") || - strings.HasPrefix(comment.Text, "// TODO():") { + if strings.HasPrefix(comment.Text, "// TODO:") || strings.HasPrefix(comment.Text, "// TODO():") { pass.Report(analysis.Diagnostic{ Pos: comment.Pos(), End: 0, @@ -27,8 +27,10 @@ func run(pass *analysis.Pass) (interface{}, error) { }) } } + return true }) } + return nil, nil } diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..ce46d93 --- /dev/null +++ b/example_test.go @@ -0,0 +1,25 @@ +package linters + +import ( + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/tools/go/analysis/analysistest" +) + +func TestTodoAnalyzer(t *testing.T) { + analysistest.Run(t, testdataDir(t), TodoAnalyzer, "testlintdata/todo") +} + +func testdataDir(t *testing.T) string { + t.Helper() + + _, testFilename, _, ok := runtime.Caller(1) + if !ok { + require.Fail(t, "unable to get current test filename") + } + + return filepath.Join(filepath.Dir(testFilename), "testdata") +} diff --git a/go.mod b/go.mod index e6152de..7b34bce 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,18 @@ -module github.com/dbraley/example-linter +module github.com/golangci/example-linter // All versions here need to be the same as in golangci-lint/mod.go if present -go 1.16 +go 1.23.0 require ( - github.com/stretchr/testify v1.7.0 - golang.org/x/tools v0.1.9 + github.com/stretchr/testify v1.10.0 + golang.org/x/tools v0.32.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/sync v0.13.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 19b6812..22aa56c 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,22 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -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.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lint_test.go b/lint_test.go deleted file mode 100644 index 71d58a3..0000000 --- a/lint_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package linters - -// Tests for linters. - -import ( - "github.com/stretchr/testify/suite" - "path/filepath" - "runtime" - "testing" - - "golang.org/x/tools/go/analysis/analysistest" -) - -type linterSuite struct { - suite.Suite -} - -func (suite *linterSuite) TestContextLinter() { - analysistest.Run( - suite.T(), TestdataDir(), - TodoAnalyzer, "testlintdata/todo") -} - -func TestLinterSuite(t *testing.T) { - suite.Run(t, new(linterSuite)) -} - -func TestdataDir() string { - _, testFilename, _, ok := runtime.Caller(1) - if !ok { - panic("unable to get current test filename") - } - return filepath.Join(filepath.Dir(testFilename), "testdata") -} diff --git a/plugin/example.go b/plugin/example.go index bf0e687..eef287f 100644 --- a/plugin/example.go +++ b/plugin/example.go @@ -2,18 +2,19 @@ package main import ( - linters "github.com/dbraley/example-linter" + "fmt" + + linters "github.com/golangci/example-linter" "golang.org/x/tools/go/analysis" ) -type analyzerPlugin struct{} +func New(conf any) ([]*analysis.Analyzer, error) { + // TODO: This must be implemented -// This must be implemented -func (*analyzerPlugin) GetAnalyzers() []*analysis.Analyzer { - return []*analysis.Analyzer{ - linters.TodoAnalyzer, - } -} + fmt.Printf("My configuration (%[1]T): %#[1]v\n", conf) -// This must be defined and named 'AnalyzerPlugin' -var AnalyzerPlugin analyzerPlugin + // The configuration type will be map[string]any or []interface, it depends on your configuration. + // You can use https://github.com/go-viper/mapstructure to convert map to struct. + + return []*analysis.Analyzer{linters.TodoAnalyzer}, nil +}