github.com/bazelbuild/rules_go@v0.47.2-0.20240515105122-e7ddb9ea474e/go/nogo.rst (about) 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. 27 Please do not rely on it for production use, but feel free to use it and file 28 issues. 29 30 ``nogo`` is a tool that analyzes the source code of Go programs. It runs 31 alongside the Go compiler in the Bazel Go rules and rejects programs that 32 contain disallowed coding patterns. In addition, ``nogo`` may report 33 compiler-like errors. 34 35 ``nogo`` is a powerful tool for preventing bugs and code anti-patterns early 36 in the development process. It may be used to run the same analyses as `vet`_, 37 and you can write new analyses for your own code base. 38 39 .. contents:: . 40 :depth: 2 41 42 ----- 43 44 Setup 45 ----- 46 47 Create a `nogo`_ target in a ``BUILD`` file in your workspace. The ``deps`` 48 attribute of this target must contain labels all the analyzers targets that you 49 want 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 74 Pass a label for your `nogo`_ target to ``go_register_nogo`` in your 75 ``WORKSPACE`` file. When using ``MODULE.bazel``, see the Bzlmod_ documentation 76 instead. 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 90 workspace. Also note that you cannot use this to refer to bzlmod repos, as the labels 91 don't go though repo mapping. 92 93 The `nogo`_ rule will generate a program that executes all the supplied 94 analyzers at build-time. The generated ``nogo`` program will run alongside the 95 compiler when building any Go target (e.g. `go_library`_) within your workspace, 96 even if the target is imported from an external repository. However, ``nogo`` 97 will not run when targets from the current repository are imported into other 98 workspaces and built there. 99 100 To 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 108 To run the analyzers from ``tools_nogo`` together with your own analyzers, use 109 the ``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 132 Usage 133 --------------------------------- 134 135 ``nogo``, upon configured, will be invoked automatically when building any Go target in your 136 workspace. 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. 139 It will also run on targets that are imported from other workspaces by default. You could 140 exclude the external repositories from ``nogo`` by using the `exclude_files` regex in 141 `configuring-analyzers`_. 142 143 Relationship with other linters 144 ~~~~~~~~~~~~~~~~~~~~~ 145 146 In 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 156 To help with the above, Go provides a framework called `analysis`_ that allows 157 you 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 160 For example, `golangci-lint`_ or `staticcheck`_ are popular linters that are composed of multiple 161 analyzers, 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 164 action orchestration framework. In particular, ``nogo`` is run as part of rules_go GoCompilePkg 165 action, and it is run in parallel with the Go compiler. This allows ``nogo`` to benefit from 166 Bazel's incremental build and caching as well as the Remote Build Execution framework. 167 168 There are examples of how to re-use the analyzers from `golangci-lint`_ and `staticcheck`_ in 169 `nogo`_ here: `sluongng/nogo-analyzer`_. 170 171 Should I use ``nogo`` or ``golangci-lint``? 172 ~~~~~~~~~~~~~~~~~~~~~ 173 174 Because ``nogo`` benefits from Bazel's incremental build and caching, it is more suitable for 175 large code bases. If you have a smaller code base, you could use ``golangci-lint`` instead. 176 177 If ``golangci-lint`` takes a really long time to run in your repository, you could try to use 178 ``nogo`` instead. 179 180 As of the moment of this writing, there is no way for ``nogo`` to apply the fixers coupled 181 with the analyzers. So separate linters such as ``golangci-lint`` or ``staticcheck`` are more 182 ergonomic to apply the fixes to the code base. 183 184 Writing and registering analyzers 185 --------------------------------- 186 187 ``nogo`` analyzers are Go packages that declare a variable named ``Analyzer`` 188 of type `Analyzer`_ from package `analysis`_. Each analyzer is invoked once per 189 Go package, and is provided the abstract syntax trees (ASTs) and type 190 information for that package, as well as relevant results of analyzers that have 191 already 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 222 Any diagnostics reported by the analyzer will stop the build. Do not emit 223 diagnostics unless they are severe enough to warrant stopping the build. 224 225 Pass labels for these targets to the ``deps`` attribute of your `nogo`_ target, 226 as described in the `Setup`_ section. 227 228 Configuring analyzers 229 ~~~~~~~~~~~~~~~~~~~~~ 230 231 By default, ``nogo`` analyzers will emit diagnostics for all Go source files 232 built by Bazel. This behavior can be changed with a JSON configuration file. 233 234 The top-level JSON object in the file must be keyed by the name of the analyzer 235 being configured. These names must match the ``Analyzer.Name`` of the registered 236 analysis package. The JSON object's values are themselves objects which may 237 contain 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 272 not explicitly specified called ``_base``. See below for an example of its usage. 273 274 Example 275 ^^^^^^^ 276 277 The following configuration file configures the analyzers named ``importunsafe`` 278 and ``unsafedom``. Since the ``loopclosure`` analyzer is not explicitly 279 configured, it will emit diagnostics for all Go files built by Bazel. 280 ``unsafedom`` will receive a flag equivalent to ``-block-unescaped-html=false`` 281 on 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 311 This 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 327 Running vet 328 ----------- 329 330 `vet`_ is a tool that examines Go source code and reports correctness issues not 331 caught by Go compilers. It is included in the official Go distribution. Vet 332 runs analyses built with the Go `analysis`_ framework. nogo uses the 333 same framework, which means vet checks can be run with nogo. 334 335 You can choose to run a safe subset of vet checks alongside the Go compiler by 336 setting ``vet = True`` in your `nogo`_ target. This will only run vet checks 337 that are believed to be 100% accurate (the same set run by ``go test`` by 338 default). 339 340 .. code:: bzl 341 342 nogo( 343 name = "my_nogo", 344 vet = True, 345 visibility = ["//visibility:public"], 346 ) 347 348 Setting ``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 354 See 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 361 API 362 --- 363 364 nogo 365 ~~~~ 366 367 This generates a program that analyzes the source code of Go programs. It 368 runs alongside the Go compiler in the Bazel Go rules and rejects programs that 369 contain disallowed coding patterns. 370 371 Attributes 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 399 Example 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 )