lintpack is a utility for building linters (static analyzers) that are written using the provided API. On the basis of it, the go-critic static analyzer familiar to some is now being rewritten.
Today we will lintpack
in more detail what lintpack
from the user's point of view.
The go-critic began as a pilot project that was a sandbox for prototyping virtually any static analysis ideas for Go.
It was a pleasant surprise that some people actually sent implementations of detectors of various problems in the code. Everything was under control until technical debt began to accumulate, which was virtually no one to eliminate. People came, added checks, and then disappeared. Who then should correct the errors and refine the implementation?
A significant event was the proposal to add checks that require additional configuration, that is, those that depend on local agreements for the project. An example is the detection of the presence of a copyright header in a file (license header) according to a specific pattern or the prohibition of importing some packages with the suggestion of a given alternative.
Another difficulty was extensibility. It’s not convenient for everyone to send their code to someone else’s repository. Some wanted to dynamically connect their checks so that they did not need to modify the source codes of the go-critic
.
Summarizing, here are the problems that stood in the way of the development of the go-critic
:
experimental
meant both "almost ready to use" and "better not to run at all."go-critic
, and rejecting them contradicts the original philosophy of the project.go-critic
differently. Most wanted to have it in the form of a CI linter, which comes in delivery with the gometalinter
.In order to somehow limit the number of discrepancies and mismatched interpretations of the project, a manifesto was written.
If you want an additional historical context and even more thoughts on the categorization of static analyzers, you can listen to the GoCritic recording - a new static analyzer for Go . At that moment, lintpack did not exist yet, but some of the ideas were born on that day, after the report.
But what if we didn’t have to store all the checks in one repository?
go-critic
consists of two main components:
Our goal: to be able to store the checks for the linter in different repositories and put them together when necessary.
lintpack does exactly that. It defines functions that allow you to describe your checks in such a way that they can then be run through the generated linter.
Packages that are implemented usinglintpack
as a framework will be calledlintpack
-compatible orlintpack
-compatible packages.
If the go-critic
was implemented on the basis of lintpack
, all the checks could be divided into several repositories. One of the options for separation may be the following:
The first point is particularly important in connection with the integration of the go-critic into golangci-lint .
If you remain at the go-critic
level, then almost nothing has changed for users. lintpack
creates an almost identical linter, and golangci-lint
encapsulates all the different implementation details.
But something has changed. If new lintpack
are created on the basis of the lintpack
, you will have a richer choice of ready-made diagnostics for the linter generation. Imagine for a moment that this is so, and there are more than 10 different sets of checks in the world.
First you need to install lintpack
itself:
# lintpack `$(go env GOPATH)/bin`. go get -v github.com/go-lintpack/lintpack/...
Create a linter using a lintpack test package:
lintpack build -o mylinter github.com/go-lintpack/lintpack/checkers
The set includes panicNil
, which finds panic(nil)
in the code and panicNil
to perform a replacement with something distinguishable, because otherwise, recover()
cannot tell if panic
was called with the nil
argument, or there was no panic at all.
The code below attempts to describe the value obtained from recover()
:
r := recover() fmt.Printf("%T, %v\n", r, r)
The result will be identical for panic(nil)
and for a program that does not panic(nil)
.
A runable example of the described behavior .
You can run the linter on separate files, type arguments ./...
or packages (by their import path).
./mylinter check bytes $GOROOT/src/bytes/buffer_test.go:276:3: panicNil: panic(nil) calls are discouraged
# , go-lintpack $GOPATH. mylinter=$(pwd)/mylinter cd $(go env GOPATH)/src/github.com/go-lintpack/lintpack/checkers/testdata $mylinter check ./panicNil/ ./panicNil/positive_tests.go:5:3: panicNil: panic(nil) calls are discouraged ./panicNil/positive_tests.go:9:3: panicNil: panic(interface{}(nil)) calls are discouraged
By default, this check also responds to panic(interface{}(nil))
. To override this behavior, you need to set the value of skipNilEfaceLit
to true
. This can be done via the command line:
$mylinter check -@panicNil.skipNilEfaceLit=true ./panicNil/ ./panicNil/positive_tests.go:5:3: panicNil: panic(nil) calls are discouraged
Both lintpack
and the generated linter use the first argument to select a subcommand. A list of available subcommands and examples of their launch can be obtained by calling the utility without arguments.
lintpack not enough arguments, expected sub-command name Supported sub-commands: build - build linter from made of lintpack-compatible packages $ lintpack build -help $ lintpack build -o gocritic github.com/go-critic/checkers $ lintpack build -linter.version=v1.0.0 . version - print lintpack version $ lintpack version
Suppose we called the created linter named gocritic
:
./gocritic not enough arguments, expected sub-command name Supported sub-commands: check - run linter over specified targets $ linter check -help $ linter check -disableTags=none strings bytes $ linter check -enableTags=diagnostic ./... version - print linter version $ linter version doc - get installed checkers documentation $ linter doc -help $ linter doc $ linter doc checkerName
For some subcommands, the -help
flag is available, which provides additional information (I cut some lines that are too wide):
./gocritic check -help # .
The answer to the question "how do I know about that skipNilEfaceLit parameter?" - read the fancy manual (RTFM)!
All documentation for installed checks is inside mylinter
. This documentation is available through the doc
subcommand:
# : $mylinter doc panicNil [diagnostic] # : $mylinter doc panicNil panicNil checker documentation URL: github.com/go-lintpack/lintpack Tags: [diagnostic] Detects panic(nil) calls. Such panic calls are hard to handle during recover. Non-compliant code: panic(nil) Compliant code: panic("something meaningful") Checker parameters: -@panicNil.skipNilEfaceLit bool whether to ignore interface{}(nil) arguments (default false)
Like the template support in go list -f
, you can pass a template string that is responsible for the output format of the documentation, which can be useful when drawing up markdown documents.
To simplify the search for useful sets of checks, there is a centralized list of lintpack
compatible packages: https://go-lintpack.imtqy.com/ .
Here are some of the list:
This list is periodically updated and it is open for applications to add. Any of these packages can be used to create a linter.
The command below creates a linter that contains all the checks from the list above:
# , # Go . go get -v github.com/go-critic/go-critic/checkers go get -v github.com/go-critic/checkers-contrib go get -v github.com/Quasilyte/go-police # build . lintpack build \ github.com/go-critic/go-critic/checkers \ github.com/go-critic/checkers-contrib \ github.com/Quasilyte/go-police
lintpack build
includes all checks at the compilation stage, the resulting linter can be placed in an environment where the source codes for the implementation of the installed diagnostics are missing, as usual with static linking.
In addition to the static build, it is possible to load plugins that provide additional checks.
The peculiarity is that the checker implementation does not know whether it will be used for static compilation or whether it will be loaded as a plugin. No code changes are required.
Suppose we want to add panicNil
to the linter, but we cannot rebuild it from all the sources that were used during the first compilation.
linterPlugin.go
: package main // , // import'. import ( _ "github.com/go-lintpack/lintpack/checkers" )
go build -buildmode=plugin -o linterPlugin.so linterPlugin.go
-pluginPath
parameter: ./linter check -pluginPath=linterPlugin.so bytes
Warning: Support for dynamic modules is implemented through a plugin package that does not work on Windows.
The -verbose
flag can help you figure out which check is on or off, and, most importantly, which filter will turn off the check.
Please note that panicNil
displayed in the list of included checks. If we remove the -pluginPath argument, it will cease to be true.
./linter check -verbose -pluginPath=./linterPlugin.so bytes debug: appendCombine: disabled by tags (-disableTags) debug: boolExprSimplify: disabled by tags (-disableTags) debug: builtinShadow: disabled by tags (-disableTags) debug: commentedOutCode: disabled by tags (-disableTags) debug: deprecatedComment: disabled by tags (-disableTags) debug: docStub: disabled by tags (-disableTags) debug: emptyFallthrough: disabled by tags (-disableTags) debug: hugeParam: disabled by tags (-disableTags) debug: importShadow: disabled by tags (-disableTags) debug: indexAlloc: disabled by tags (-disableTags) debug: methodExprCall: disabled by tags (-disableTags) debug: nilValReturn: disabled by tags (-disableTags) debug: paramTypeCombine: disabled by tags (-disableTags) debug: rangeExprCopy: disabled by tags (-disableTags) debug: rangeValCopy: disabled by tags (-disableTags) debug: sloppyReassign: disabled by tags (-disableTags) debug: typeUnparen: disabled by tags (-disableTags) debug: unlabelStmt: disabled by tags (-disableTags) debug: wrapperFunc: disabled by tags (-disableTags) debug: appendAssign is enabled debug: assignOp is enabled debug: captLocal is enabled debug: caseOrder is enabled debug: defaultCaseOrder is enabled debug: dupArg is enabled debug: dupBranchBody is enabled debug: dupCase is enabled debug: dupSubExpr is enabled debug: elseif is enabled debug: flagDeref is enabled debug: ifElseChain is enabled debug: panicNil is enabled debug: regexpMust is enabled debug: singleCaseSwitch is enabled debug: sloppyLen is enabled debug: switchTrue is enabled debug: typeSwitchVar is enabled debug: underef is enabled debug: unlambda is enabled debug: unslice is enabled # ... .
To avoid confusion, it is worth describing the main differences between the projects.
gometalinter and golangci-lint primarily integrate other, often very differently implemented, linters, provide convenient access to them. They target end users who will use static analyzers.
lintpack simplifies the creation of new linters, provides a framework that makes different packages, implemented on its basis, compatible within the same executable file. These checks (for golangci-lint) or the executable file (for gometalinter) can then be embedded in the above meta-linters.
Suppose one of the lintpack
-compatible checks is part of golangci-lint
. If there is any problem related to the convenience of its use - this may be the responsibility of golangci-lint
, but if we are talking about an error in the implementation of the test itself, then this is the problem of the authors of the test, the lintpack ecosystem.
In other words, these projects solve various problems.
The process of porting a go-critic
to lintpack
is almost complete. work-in-progress can be found in the go-critic / checkers repository. After the transition is completed, the checks will be moved to the go-critic/go-critic/checkers
.
# go-critic : go get -v github.com/go-critic/go-critic/... # go-critic : lintpack -o gocritic github.com/go-critic/go-critic/checkers
There is no big sense in using a go-critic
outside of golangci-lint
, but lintpack
can allow to install those checks that are not included in the go-critic
. For example, it may be a diagnosis written by you.
How to create your own lintpack
compatible checks you will learn in the next article.
In the same place, we will analyze what advantages you get when selling your lintpack based lintpack
compared to selling it from scratch.
I hope you have an appetite for new checks for Go. Let us know how static analysis will become too much, we will quickly solve this problem together.
Source: https://habr.com/ru/post/430196/
All Articles