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