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      )