1|logo| nogo build-time code analysis
2====================================
3
4.. _nogo: nogo.rst#nogo
5.. _configuring-analyzers: nogo.rst#configuring-analyzers
6.. _Bzlmod: /docs/go/core/bzlmod.md#configuring-nogo
7.. _go_library: /docs/go/core/rules.md#go_library
8.. _analysis: https://godoc.org/golang.org/x/tools/go/analysis
9.. _Analyzer: https://godoc.org/golang.org/x/tools/go/analysis#Analyzer
10.. _GoLibrary: providers.rst#GoLibrary
11.. _GoSource: providers.rst#GoSource
12.. _GoArchive: providers.rst#GoArchive
13.. _vet: https://golang.org/cmd/vet/
14.. _golangci-lint: https://github.com/golangci/golangci-lint
15.. _staticcheck: https://staticcheck.io/
16.. _sluongng/nogo-analyzer: https://github.com/sluongng/nogo-analyzer
17
18.. role:: param(kbd)
19.. role:: type(emphasis)
20.. role:: value(code)
21.. |mandatory| replace:: **mandatory value**
22.. |logo| image:: nogo_logo.png
23.. footer:: The ``nogo`` logo was derived from the Go gopher, which was designed by Renee French. (http://reneefrench.blogspot.com/) The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: http://blog.golang.org/gopher
24
25
26**WARNING**: This functionality is experimental, so its API might change.
27Please do not rely on it for production use, but feel free to use it and file
28issues.
29
30``nogo`` is a tool that analyzes the source code of Go programs. It runs
31alongside the Go compiler in the Bazel Go rules and rejects programs that
32contain disallowed coding patterns. In addition, ``nogo`` may report
33compiler-like errors.
34
35``nogo`` is a powerful tool for preventing bugs and code anti-patterns early
36in the development process. It may be used to run the same analyses as `vet`_,
37and you can write new analyses for your own code base.
38
39.. contents:: .
40 :depth: 2
41
42-----
43
44Setup
45-----
46
47Create a `nogo`_ target in a ``BUILD`` file in your workspace. The ``deps``
48attribute of this target must contain labels all the analyzers targets that you
49want to run.
50
51.. code:: bzl
52
53 load("@io_bazel_rules_go//go:def.bzl", "nogo")
54
55 nogo(
56 name = "my_nogo",
57 deps = [
58 # analyzer from the local repository
59 ":importunsafe",
60 # analyzer from a remote repository
61 "@org_golang_x_tools//go/analysis/passes/printf:go_default_library",
62 ],
63 visibility = ["//visibility:public"], # must have public visibility
64 )
65
66 go_library(
67 name = "importunsafe",
68 srcs = ["importunsafe.go"],
69 importpath = "importunsafe",
70 deps = ["@org_golang_x_tools//go/analysis:go_default_library"],
71 visibility = ["//visibility:public"],
72 )
73
74Pass a label for your `nogo`_ target to ``go_register_nogo`` in your
75``WORKSPACE`` file. When using ``MODULE.bazel``, see the Bzlmod_ documentation
76instead.
77
78.. code:: bzl
79
80 load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_nogo")
81 go_rules_dependencies()
82 go_register_toolchains(version = "1.20.7")
83 go_register_nogo(
84 nogo = "@//:my_nogo" # my_nogo is in the top-level BUILD file of this workspace
85 includes = ["@//:__subpackages__"], # Labels to lint. By default only lints code in workspace.
86 excludes = ["@//generated:__subpackages__"], # Labels to exclude.
87 )
88
89**NOTE**: You must include ``"@//"`` prefix when referring to targets in the local
90workspace. Also note that you cannot use this to refer to bzlmod repos, as the labels
91don't go though repo mapping.
92
93The `nogo`_ rule will generate a program that executes all the supplied
94analyzers at build-time. The generated ``nogo`` program will run alongside the
95compiler when building any Go target (e.g. `go_library`_) within your workspace,
96even if the target is imported from an external repository. However, ``nogo``
97will not run when targets from the current repository are imported into other
98workspaces and built there.
99
100To run all the ``golang.org/x/tools`` analyzers, use ``@io_bazel_rules_go//:tools_nogo``.
101
102.. code:: bzl
103
104 load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
105 go_rules_dependencies()
106 go_register_toolchains(nogo = "@io_bazel_rules_go//:tools_nogo")
107
108To run the analyzers from ``tools_nogo`` together with your own analyzers, use
109the ``TOOLS_NOGO`` list of dependencies.
110
111.. code:: bzl
112
113 load("@io_bazel_rules_go//go:def.bzl", "nogo", "TOOLS_NOGO")
114
115 nogo(
116 name = "my_nogo",
117 deps = TOOLS_NOGO + [
118 # analyzer from the local repository
119 ":importunsafe",
120 ],
121 visibility = ["//visibility:public"], # must have public visibility
122 )
123
124 go_library(
125 name = "importunsafe",
126 srcs = ["importunsafe.go"],
127 importpath = "importunsafe",
128 deps = ["@org_golang_x_tools//go/analysis:go_library"],
129 visibility = ["//visibility:public"],
130 )
131
132Usage
133---------------------------------
134
135``nogo``, upon configured, will be invoked automatically when building any Go target in your
136workspace. If any of the analyzers reject the program, the build will fail.
137
138``nogo`` will run on all Go targets in your workspace, including tests and binary targets.
139It will also run on targets that are imported from other workspaces by default. You could
140exclude the external repositories from ``nogo`` by using the `exclude_files` regex in
141`configuring-analyzers`_.
142
143Relationship with other linters
144~~~~~~~~~~~~~~~~~~~~~
145
146In Golang, a linter is composed of multiple parts:
147
148- A collection of rules (checks) that define different validations against the source code
149
150- Optionally, each rules could be coupled with a fixer that can automatically fix the code.
151
152- A configuration framework that allows users to enable/disable rules, and configure the rules.
153
154- A runner binary that orchestrate the above components.
155
156To help with the above, Go provides a framework called `analysis`_ that allows
157you to write a linter in a modular way. In which, you could define each rules as a separate
158`Analyzer`_, and then compose them together in a runner binary.
159
160For example, `golangci-lint`_ or `staticcheck`_ are popular linters that are composed of multiple
161analyzers, each of which is a collection of rules.
162
163``nogo`` is a runner binary that runs a collection of analyzers while leveraging Bazel's
164action orchestration framework. In particular, ``nogo`` is run as part of rules_go GoCompilePkg
165action, and it is run in parallel with the Go compiler. This allows ``nogo`` to benefit from
166Bazel's incremental build and caching as well as the Remote Build Execution framework.
167
168There are examples of how to re-use the analyzers from `golangci-lint`_ and `staticcheck`_ in
169`nogo`_ here: `sluongng/nogo-analyzer`_.
170
171Should I use ``nogo`` or ``golangci-lint``?
172~~~~~~~~~~~~~~~~~~~~~
173
174Because ``nogo`` benefits from Bazel's incremental build and caching, it is more suitable for
175large code bases. If you have a smaller code base, you could use ``golangci-lint`` instead.
176
177If ``golangci-lint`` takes a really long time to run in your repository, you could try to use
178``nogo`` instead.
179
180As of the moment of this writing, there is no way for ``nogo`` to apply the fixers coupled
181with the analyzers. So separate linters such as ``golangci-lint`` or ``staticcheck`` are more
182ergonomic to apply the fixes to the code base.
183
184Writing and registering analyzers
185---------------------------------
186
187``nogo`` analyzers are Go packages that declare a variable named ``Analyzer``
188of type `Analyzer`_ from package `analysis`_. Each analyzer is invoked once per
189Go package, and is provided the abstract syntax trees (ASTs) and type
190information for that package, as well as relevant results of analyzers that have
191already been run. For example:
192
193.. code:: go
194
195 // package importunsafe checks whether a Go package imports package unsafe.
196 package importunsafe
197
198 import (
199 "strconv"
200
201 "golang.org/x/tools/go/analysis"
202 )
203
204 var Analyzer = &analysis.Analyzer{
205 Name: "importunsafe",
206 Doc: "reports imports of package unsafe",
207 Run: run,
208 }
209
210 func run(pass *analysis.Pass) (interface{}, error) {
211 for _, f := range pass.Files {
212 for _, imp := range f.Imports {
213 path, err := strconv.Unquote(imp.Path.Value)
214 if err == nil && path == "unsafe" {
215 pass.Reportf(imp.Pos(), "package unsafe must not be imported")
216 }
217 }
218 }
219 return nil, nil
220 }
221
222Any diagnostics reported by the analyzer will stop the build. Do not emit
223diagnostics unless they are severe enough to warrant stopping the build.
224
225Pass labels for these targets to the ``deps`` attribute of your `nogo`_ target,
226as described in the `Setup`_ section.
227
228Configuring analyzers
229~~~~~~~~~~~~~~~~~~~~~
230
231By default, ``nogo`` analyzers will emit diagnostics for all Go source files
232built by Bazel. This behavior can be changed with a JSON configuration file.
233
234The top-level JSON object in the file must be keyed by the name of the analyzer
235being configured. These names must match the ``Analyzer.Name`` of the registered
236analysis package. The JSON object's values are themselves objects which may
237contain the following key-value pairs:
238
239+----------------------------+---------------------------------------------------------------------+
240| **Key** | **Type** |
241+----------------------------+---------------------------------------------------------------------+
242| ``"description"`` | :type:`string` |
243+----------------------------+---------------------------------------------------------------------+
244| Description of this analyzer configuration. |
245+----------------------------+---------------------------------------------------------------------+
246| ``"only_files"`` | :type:`dictionary, string to string` |
247+----------------------------+---------------------------------------------------------------------+
248| Specifies files that this analyzer will emit diagnostics for. |
249| Its keys are regular expression strings matching Go file names, and its values are strings |
250| containing a description of the entry. |
251| If both ``only_files`` and ``exclude_files`` are empty, this analyzer will emit diagnostics for |
252| all Go files built by Bazel. |
253+----------------------------+---------------------------------------------------------------------+
254| ``"exclude_files"`` | :type:`dictionary, string to string` |
255+----------------------------+---------------------------------------------------------------------+
256| Specifies files that this analyzer will not emit diagnostics for. |
257| Its keys and values are strings that have the same semantics as those in ``only_files``. |
258| Keys in ``exclude_files`` override keys in ``only_files``. If a .go file matches a key present |
259| in both ``only_files`` and ``exclude_files``, the analyzer will not emit diagnostics for that |
260| file. |
261+----------------------------+---------------------------------------------------------------------+
262| ``"analyzer_flags"`` | :type:`dictionary, string to string` |
263+----------------------------+---------------------------------------------------------------------+
264| Passes on a set of flags as defined by the Go ``flag`` package to the analyzer via the |
265| ``analysis.Analyzer.Flags`` field. Its keys are the flag names *without* a ``-`` prefix, and its |
266| values are the flag values. nogo will exit with an error upon receiving flags not recognized by |
267| the analyzer or upon receiving ill-formatted flag values as defined by the corresponding |
268| ``flag.Value`` specified by the analyzer. |
269+----------------------------+---------------------------------------------------------------------+
270
271``nogo`` also supports a special key to specify the same config for all analyzers, even if they are
272not explicitly specified called ``_base``. See below for an example of its usage.
273
274Example
275^^^^^^^
276
277The following configuration file configures the analyzers named ``importunsafe``
278and ``unsafedom``. Since the ``loopclosure`` analyzer is not explicitly
279configured, it will emit diagnostics for all Go files built by Bazel.
280``unsafedom`` will receive a flag equivalent to ``-block-unescaped-html=false``
281on a command line driver.
282
283.. code:: json
284
285 {
286 "_base": {
287 "description": "Base config that all subsequent analyzers, even unspecified will inherit.",
288 "exclude_files": {
289 "third_party/": "exclude all third_party code for all analyzers"
290 }
291 },
292 "importunsafe": {
293 "exclude_files": {
294 "src/foo\\.go": "manually verified that behavior is working-as-intended",
295 "src/bar\\.go": "see issue #1337"
296 }
297 },
298 "unsafedom": {
299 "only_files": {
300 "src/js/.*": ""
301 },
302 "exclude_files": {
303 "src/(third_party|vendor)/.*": "enforce DOM safety requirements only on first-party code"
304 },
305 "analyzer_flags": {
306 "block-unescaped-html": "false",
307 },
308 }
309 }
310
311This label referencing this configuration file must be provided as the
312``config`` attribute value of the ``nogo`` rule.
313
314.. code:: bzl
315
316 nogo(
317 name = "my_nogo",
318 deps = [
319 ":importunsafe",
320 ":unsafedom",
321 "@analyzers//:loopclosure",
322 ],
323 config = "config.json",
324 visibility = ["//visibility:public"],
325 )
326
327Running vet
328-----------
329
330`vet`_ is a tool that examines Go source code and reports correctness issues not
331caught by Go compilers. It is included in the official Go distribution. Vet
332runs analyses built with the Go `analysis`_ framework. nogo uses the
333same framework, which means vet checks can be run with nogo.
334
335You can choose to run a safe subset of vet checks alongside the Go compiler by
336setting ``vet = True`` in your `nogo`_ target. This will only run vet checks
337that are believed to be 100% accurate (the same set run by ``go test`` by
338default).
339
340.. code:: bzl
341
342 nogo(
343 name = "my_nogo",
344 vet = True,
345 visibility = ["//visibility:public"],
346 )
347
348Setting ``vet = True`` is equivalent to adding the ``atomic``, ``bools``,
349``buildtag``, ``nilfunc``, and ``printf`` analyzers from
350``@org_golang_x_tools//go/analysis/passes`` to the ``deps`` list of your
351``nogo`` rule.
352
353
354See the full list of available nogo checks:
355
356.. code:: shell
357
358 bazel query 'kind(go_library, @org_golang_x_tools//go/analysis/passes/...)'
359
360
361API
362---
363
364nogo
365~~~~
366
367This generates a program that analyzes the source code of Go programs. It
368runs alongside the Go compiler in the Bazel Go rules and rejects programs that
369contain disallowed coding patterns.
370
371Attributes
372^^^^^^^^^^
373
374+----------------------------+-----------------------------+---------------------------------------+
375| **Name** | **Type** | **Default value** |
376+----------------------------+-----------------------------+---------------------------------------+
377| :param:`name` | :type:`string` | |mandatory| |
378+----------------------------+-----------------------------+---------------------------------------+
379| A unique name for this rule. |
380+----------------------------+-----------------------------+---------------------------------------+
381| :param:`deps` | :type:`label_list` | :value:`None` |
382+----------------------------+-----------------------------+---------------------------------------+
383| List of Go libraries that will be linked to the generated nogo binary. |
384| |
385| These libraries must declare an ``analysis.Analyzer`` variable named `Analyzer` to ensure that |
386| the analyzers they implement are called by nogo. |
387| |
388+----------------------------+-----------------------------+---------------------------------------+
389| :param:`config` | :type:`label` | :value:`None` |
390+----------------------------+-----------------------------+---------------------------------------+
391| JSON configuration file that configures one or more of the analyzers in ``deps``. |
392+----------------------------+-----------------------------+---------------------------------------+
393| :param:`vet` | :type:`bool` | :value:`False` |
394+----------------------------+-----------------------------+---------------------------------------+
395| If true, a safe subset of vet checks will be run by nogo (the same subset run |
396| by ``go test ``). |
397+----------------------------+-----------------------------+---------------------------------------+
398
399Example
400^^^^^^^
401
402.. code:: bzl
403
404 nogo(
405 name = "my_nogo",
406 deps = [
407 ":importunsafe",
408 ":otheranalyzer",
409 "@analyzers//:unsafedom",
410 ],
411 config = ":config.json",
412 vet = True,
413 visibility = ["//visibility:public"],
414 )
View as plain text