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