aboutsummaryrefslogtreecommitdiff
path: root/go/nogo.rst
diff options
context:
space:
mode:
Diffstat (limited to 'go/nogo.rst')
-rw-r--r--go/nogo.rst406
1 files changed, 406 insertions, 0 deletions
diff --git a/go/nogo.rst b/go/nogo.rst
new file mode 100644
index 00000000..7e3183cd
--- /dev/null
+++ b/go/nogo.rst
@@ -0,0 +1,406 @@
+|logo| nogo build-time code analysis
+====================================
+
+.. _nogo: nogo.rst#nogo
+.. _configuring-analyzers: nogo.rst#configuring-analyzers
+.. _go_library: /docs/go/core/rules.md#go_library
+.. _analysis: https://godoc.org/golang.org/x/tools/go/analysis
+.. _Analyzer: https://godoc.org/golang.org/x/tools/go/analysis#Analyzer
+.. _GoLibrary: providers.rst#GoLibrary
+.. _GoSource: providers.rst#GoSource
+.. _GoArchive: providers.rst#GoArchive
+.. _vet: https://golang.org/cmd/vet/
+.. _golangci-lint: https://github.com/golangci/golangci-lint
+.. _staticcheck: https://staticcheck.io/
+.. _sluongng/nogo-analyzer: https://github.com/sluongng/nogo-analyzer
+
+.. role:: param(kbd)
+.. role:: type(emphasis)
+.. role:: value(code)
+.. |mandatory| replace:: **mandatory value**
+.. |logo| image:: nogo_logo.png
+.. 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
+
+
+**WARNING**: This functionality is experimental, so its API might change.
+Please do not rely on it for production use, but feel free to use it and file
+issues.
+
+``nogo`` is a tool that analyzes the source code of Go programs. It runs
+alongside the Go compiler in the Bazel Go rules and rejects programs that
+contain disallowed coding patterns. In addition, ``nogo`` may report
+compiler-like errors.
+
+``nogo`` is a powerful tool for preventing bugs and code anti-patterns early
+in the development process. It may be used to run the same analyses as `vet`_,
+and you can write new analyses for your own code base.
+
+.. contents:: .
+ :depth: 2
+
+-----
+
+Setup
+-----
+
+Create a `nogo`_ target in a ``BUILD`` file in your workspace. The ``deps``
+attribute of this target must contain labels all the analyzers targets that you
+want to run.
+
+.. code:: bzl
+
+ load("@io_bazel_rules_go//go:def.bzl", "nogo")
+
+ nogo(
+ name = "my_nogo",
+ deps = [
+ # analyzer from the local repository
+ ":importunsafe",
+ # analyzer from a remote repository
+ "@org_golang_x_tools//go/analysis/passes/printf:go_default_library",
+ ],
+ visibility = ["//visibility:public"], # must have public visibility
+ )
+
+ go_library(
+ name = "importunsafe",
+ srcs = ["importunsafe.go"],
+ importpath = "importunsafe",
+ deps = ["@org_golang_x_tools//go/analysis:go_default_library"],
+ visibility = ["//visibility:public"],
+ )
+
+Pass a label for your `nogo`_ target to ``go_register_toolchains`` in your
+``WORKSPACE`` file.
+
+.. code:: bzl
+
+ load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
+ go_rules_dependencies()
+ go_register_toolchains(nogo = "@//:my_nogo") # my_nogo is in the top-level BUILD file of this workspace
+
+**NOTE**: You must include ``"@//"`` prefix when referring to targets in the local
+workspace.
+
+The `nogo`_ rule will generate a program that executes all the supplied
+analyzers at build-time. The generated ``nogo`` program will run alongside the
+compiler when building any Go target (e.g. `go_library`_) within your workspace,
+even if the target is imported from an external repository. However, ``nogo``
+will not run when targets from the current repository are imported into other
+workspaces and built there.
+
+To run all the ``golang.org/x/tools`` analyzers, use ``@io_bazel_rules_go//:tools_nogo``.
+
+.. code:: bzl
+
+ load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
+ go_rules_dependencies()
+ go_register_toolchains(nogo = "@io_bazel_rules_go//:tools_nogo")
+
+To run the analyzers from ``tools_nogo`` together with your own analyzers, use
+the ``TOOLS_NOGO`` list of dependencies.
+
+.. code:: bzl
+
+ load("@io_bazel_rules_go//go:def.bzl", "nogo", "TOOLS_NOGO")
+
+ nogo(
+ name = "my_nogo",
+ deps = TOOLS_NOGO + [
+ # analyzer from the local repository
+ ":importunsafe",
+ ],
+ visibility = ["//visibility:public"], # must have public visibility
+ )
+
+ go_library(
+ name = "importunsafe",
+ srcs = ["importunsafe.go"],
+ importpath = "importunsafe",
+ deps = ["@org_golang_x_tools//go/analysis:go_library"],
+ visibility = ["//visibility:public"],
+ )
+
+Usage
+---------------------------------
+
+``nogo``, upon configured, will be invoked automatically when building any Go target in your
+workspace. If any of the analyzers reject the program, the build will fail.
+
+``nogo`` will run on all Go targets in your workspace, including tests and binary targets.
+It will also run on targets that are imported from other workspaces by default. You could
+exclude the external repositories from ``nogo`` by using the `exclude_files` regex in
+`configuring-analyzers`_.
+
+Relationship with other linters
+~~~~~~~~~~~~~~~~~~~~~
+
+In Golang, a linter is composed of multiple parts:
+
+- A collection of rules (checks) that define different validations against the source code
+
+- Optionally, each rules could be coupled with a fixer that can automatically fix the code.
+
+- A configuration framework that allows users to enable/disable rules, and configure the rules.
+
+- A runner binary that orchestrate the above components.
+
+To help with the above, Go provides a framework called `analysis`_ that allows
+you to write a linter in a modular way. In which, you could define each rules as a separate
+`Analyzer`_, and then compose them together in a runner binary.
+
+For example, `golangci-lint`_ or `staticcheck`_ are popular linters that are composed of multiple
+analyzers, each of which is a collection of rules.
+
+``nogo`` is a runner binary that runs a collection of analyzers while leveraging Bazel's
+action orchestration framework. In particular, ``nogo`` is run as part of rules_go GoCompilePkg
+action, and it is run in parallel with the Go compiler. This allows ``nogo`` to benefit from
+Bazel's incremental build and caching as well as the Remote Build Execution framework.
+
+There are examples of how to re-use the analyzers from `golangci-lint`_ and `staticcheck`_ in
+`nogo`_ here: `sluongng/nogo-analyzer`_.
+
+Should I use ``nogo`` or ``golangci-lint``?
+~~~~~~~~~~~~~~~~~~~~~
+
+Because ``nogo`` benefits from Bazel's incremental build and caching, it is more suitable for
+large code bases. If you have a smaller code base, you could use ``golangci-lint`` instead.
+
+If ``golangci-lint`` takes a really long time to run in your repository, you could try to use
+``nogo`` instead.
+
+As of the moment of this writing, there is no way for ``nogo`` to apply the fixers coupled
+with the analyzers. So separate linters such as ``golangci-lint`` or ``staticcheck`` are more
+ergonomic to apply the fixes to the code base.
+
+Writing and registering analyzers
+---------------------------------
+
+``nogo`` analyzers are Go packages that declare a variable named ``Analyzer``
+of type `Analyzer`_ from package `analysis`_. Each analyzer is invoked once per
+Go package, and is provided the abstract syntax trees (ASTs) and type
+information for that package, as well as relevant results of analyzers that have
+already been run. For example:
+
+.. code:: go
+
+ // package importunsafe checks whether a Go package imports package unsafe.
+ package importunsafe
+
+ import (
+ "strconv"
+
+ "golang.org/x/tools/go/analysis"
+ )
+
+ var Analyzer = &analysis.Analyzer{
+ Name: "importunsafe",
+ Doc: "reports imports of package unsafe",
+ Run: run,
+ }
+
+ func run(pass *analysis.Pass) (interface{}, error) {
+ for _, f := range pass.Files {
+ for _, imp := range f.Imports {
+ path, err := strconv.Unquote(imp.Path.Value)
+ if err == nil && path == "unsafe" {
+ pass.Reportf(imp.Pos(), "package unsafe must not be imported")
+ }
+ }
+ }
+ return nil, nil
+ }
+
+Any diagnostics reported by the analyzer will stop the build. Do not emit
+diagnostics unless they are severe enough to warrant stopping the build.
+
+Pass labels for these targets to the ``deps`` attribute of your `nogo`_ target,
+as described in the `Setup`_ section.
+
+Configuring analyzers
+~~~~~~~~~~~~~~~~~~~~~
+
+By default, ``nogo`` analyzers will emit diagnostics for all Go source files
+built by Bazel. This behavior can be changed with a JSON configuration file.
+
+The top-level JSON object in the file must be keyed by the name of the analyzer
+being configured. These names must match the ``Analyzer.Name`` of the registered
+analysis package. The JSON object's values are themselves objects which may
+contain the following key-value pairs:
+
++----------------------------+---------------------------------------------------------------------+
+| **Key** | **Type** |
++----------------------------+---------------------------------------------------------------------+
+| ``"description"`` | :type:`string` |
++----------------------------+---------------------------------------------------------------------+
+| Description of this analyzer configuration. |
++----------------------------+---------------------------------------------------------------------+
+| ``"only_files"`` | :type:`dictionary, string to string` |
++----------------------------+---------------------------------------------------------------------+
+| Specifies files that this analyzer will emit diagnostics for. |
+| Its keys are regular expression strings matching Go file names, and its values are strings |
+| containing a description of the entry. |
+| If both ``only_files`` and ``exclude_files`` are empty, this analyzer will emit diagnostics for |
+| all Go files built by Bazel. |
++----------------------------+---------------------------------------------------------------------+
+| ``"exclude_files"`` | :type:`dictionary, string to string` |
++----------------------------+---------------------------------------------------------------------+
+| Specifies files that this analyzer will not emit diagnostics for. |
+| Its keys and values are strings that have the same semantics as those in ``only_files``. |
+| Keys in ``exclude_files`` override keys in ``only_files``. If a .go file matches a key present |
+| in both ``only_files`` and ``exclude_files``, the analyzer will not emit diagnostics for that |
+| file. |
++----------------------------+---------------------------------------------------------------------+
+| ``"analyzer_flags"`` | :type:`dictionary, string to string` |
++----------------------------+---------------------------------------------------------------------+
+| Passes on a set of flags as defined by the Go ``flag`` package to the analyzer via the |
+| ``analysis.Analyzer.Flags`` field. Its keys are the flag names *without* a ``-`` prefix, and its |
+| values are the flag values. nogo will exit with an error upon receiving flags not recognized by |
+| the analyzer or upon receiving ill-formatted flag values as defined by the corresponding |
+| ``flag.Value`` specified by the analyzer. |
++----------------------------+---------------------------------------------------------------------+
+
+``nogo`` also supports a special key to specify the same config for all analyzers, even if they are
+not explicitly specified called ``_base``. See below for an example of its usage.
+
+Example
+^^^^^^^
+
+The following configuration file configures the analyzers named ``importunsafe``
+and ``unsafedom``. Since the ``loopclosure`` analyzer is not explicitly
+configured, it will emit diagnostics for all Go files built by Bazel.
+``unsafedom`` will receive a flag equivalent to ``-block-unescaped-html=false``
+on a command line driver.
+
+.. code:: json
+
+ {
+ "_base": {
+ "description": "Base config that all subsequent analyzers, even unspecified will inherit.",
+ "exclude_files": {
+ "third_party/": "exclude all third_party code for all analyzers"
+ }
+ },
+ "importunsafe": {
+ "exclude_files": {
+ "src/foo\\.go": "manually verified that behavior is working-as-intended",
+ "src/bar\\.go": "see issue #1337"
+ }
+ },
+ "unsafedom": {
+ "only_files": {
+ "src/js/.*": ""
+ },
+ "exclude_files": {
+ "src/(third_party|vendor)/.*": "enforce DOM safety requirements only on first-party code"
+ },
+ "analyzer_flags": {
+ "block-unescaped-html": "false",
+ },
+ }
+ }
+
+This label referencing this configuration file must be provided as the
+``config`` attribute value of the ``nogo`` rule.
+
+.. code:: bzl
+
+ nogo(
+ name = "my_nogo",
+ deps = [
+ ":importunsafe",
+ ":unsafedom",
+ "@analyzers//:loopclosure",
+ ],
+ config = "config.json",
+ visibility = ["//visibility:public"],
+ )
+
+Running vet
+-----------
+
+`vet`_ is a tool that examines Go source code and reports correctness issues not
+caught by Go compilers. It is included in the official Go distribution. Vet
+runs analyses built with the Go `analysis`_ framework. nogo uses the
+same framework, which means vet checks can be run with nogo.
+
+You can choose to run a safe subset of vet checks alongside the Go compiler by
+setting ``vet = True`` in your `nogo`_ target. This will only run vet checks
+that are believed to be 100% accurate (the same set run by ``go test`` by
+default).
+
+.. code:: bzl
+
+ nogo(
+ name = "my_nogo",
+ vet = True,
+ visibility = ["//visibility:public"],
+ )
+
+Setting ``vet = True`` is equivalent to adding the ``atomic``, ``bools``,
+``buildtag``, ``nilfunc``, and ``printf`` analyzers from
+``@org_golang_x_tools//go/analysis/passes`` to the ``deps`` list of your
+``nogo`` rule.
+
+
+See the full list of available nogo checks:
+
+.. code:: shell
+
+ bazel query 'kind(go_library, @org_golang_x_tools//go/analysis/passes/...)'
+
+
+API
+---
+
+nogo
+~~~~
+
+This generates a program that analyzes the source code of Go programs. It
+runs alongside the Go compiler in the Bazel Go rules and rejects programs that
+contain disallowed coding patterns.
+
+Attributes
+^^^^^^^^^^
+
++----------------------------+-----------------------------+---------------------------------------+
+| **Name** | **Type** | **Default value** |
++----------------------------+-----------------------------+---------------------------------------+
+| :param:`name` | :type:`string` | |mandatory| |
++----------------------------+-----------------------------+---------------------------------------+
+| A unique name for this rule. |
++----------------------------+-----------------------------+---------------------------------------+
+| :param:`deps` | :type:`label_list` | :value:`None` |
++----------------------------+-----------------------------+---------------------------------------+
+| List of Go libraries that will be linked to the generated nogo binary. |
+| |
+| These libraries must declare an ``analysis.Analyzer`` variable named `Analyzer` to ensure that |
+| the analyzers they implement are called by nogo. |
+| |
++----------------------------+-----------------------------+---------------------------------------+
+| :param:`config` | :type:`label` | :value:`None` |
++----------------------------+-----------------------------+---------------------------------------+
+| JSON configuration file that configures one or more of the analyzers in ``deps``. |
++----------------------------+-----------------------------+---------------------------------------+
+| :param:`vet` | :type:`bool` | :value:`False` |
++----------------------------+-----------------------------+---------------------------------------+
+| If true, a safe subset of vet checks will be run by nogo (the same subset run |
+| by ``go test ``). |
++----------------------------+-----------------------------+---------------------------------------+
+
+Example
+^^^^^^^
+
+.. code:: bzl
+
+ nogo(
+ name = "my_nogo",
+ deps = [
+ ":importunsafe",
+ ":otheranalyzer",
+ "@analyzers//:unsafedom",
+ ],
+ config = ":config.json",
+ vet = True,
+ visibility = ["//visibility:public"],
+ )