build/analysis: copy cockroachdb code to third_party
We are currently fetching the full cockroach repository just for a hand
full of nogo passes. As the version we use is licensed under Apache 2.0,
we can copy them to third_party, allowing us to modify them and keep them
stable.
Change-Id: Ia28b181296138eef922485b6517d1e0066766715
Reviewed-on: https://review.monogon.dev/c/monogon/+/4486
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/third_party/com_github_cockroachdb_cockroach/passesutil/BUILD.bazel b/third_party/com_github_cockroachdb_cockroach/passesutil/BUILD.bazel
new file mode 100644
index 0000000..3295800
--- /dev/null
+++ b/third_party/com_github_cockroachdb_cockroach/passesutil/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "passesutil",
+ srcs = ["passes_util.go"],
+ importpath = "source.monogon.dev/third_party/com_github_cockroachdb_cockroach/passesutil",
+ visibility = ["//visibility:public"],
+ deps = [
+ "@org_golang_x_tools//go/analysis",
+ "@org_golang_x_tools//go/ast/astutil",
+ ],
+)
diff --git a/third_party/com_github_cockroachdb_cockroach/passesutil/passes_util.go b/third_party/com_github_cockroachdb_cockroach/passesutil/passes_util.go
new file mode 100644
index 0000000..681e1eb
--- /dev/null
+++ b/third_party/com_github_cockroachdb_cockroach/passesutil/passes_util.go
@@ -0,0 +1,127 @@
+// Copyright 2020 The Cockroach Authors.
+// SPDX-License-Identifier: Apache-2.0
+
+// Package passesutil provides useful functionality for implementing passes.
+package passesutil
+
+import (
+ "fmt"
+ "go/ast"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+// FindContainingFile finds the file for an ast Node in a Pass.
+func FindContainingFile(pass *analysis.Pass, n ast.Node) *ast.File {
+ fPos := pass.Fset.File(n.Pos())
+ for _, f := range pass.Files {
+ if pass.Fset.File(f.Pos()) == fPos {
+ return f
+ }
+ }
+ panic(fmt.Errorf("cannot file file for %v", n))
+}
+
+// HasNolintComment returns true if the comments on the passed node have
+// the comment "nolint:<nolintName>" appearing anywhere in it.
+func HasNolintComment(pass *analysis.Pass, n ast.Node, nolintName string) bool {
+ f := FindContainingFile(pass, n)
+ relevant, containing := findNodesInBlock(f, n)
+ cm := ast.NewCommentMap(pass.Fset, containing, f.Comments)
+ // Check to see if any of the relevant ast nodes contain the relevant comment.
+ nolintComment := "nolint:" + nolintName
+ for _, cn := range relevant {
+ // Ident nodes have all comments on the ident in containing. This may be
+ // too many. Imagine there is a comment on the ident somewhere else in the
+ // decl block, we wouldn't want that to affect a later conversion.
+ //
+ // We want to filter this down to all comments on the ident inside
+ // the relevant statement. To do this we reject comments on idents which
+ // have their slash outside the outermost relevant node. We do this
+ // by inspecting the position of the comment relative to the outermost
+ // relevant node. This is sound because we can't have a comment on an ident
+ // alone as an ident is not a statement.
+ _, isIdent := cn.(*ast.Ident)
+ for _, cg := range cm[cn] {
+ for _, c := range cg.List {
+ if !strings.Contains(c.Text, nolintComment) {
+ continue
+ }
+ outermost := relevant[len(relevant)-1]
+ if isIdent && (cg.Pos() < outermost.Pos() || cg.End() > outermost.End()) {
+ continue
+ }
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// findNodesInBlock finds all expressions and statements that occur underneath
+// the block or decl closest to n. The idea is that we want to find comments
+// which occur in in the block or decl which includes the ast node n for
+// filtering. We want to filter the comments down to all comments
+// which are associated with n or any expression up to a statement in the
+// closest enclosing block or decl. This is to deal with multi-line
+// expressions or with cases where the relevant expression occurs in an
+// init clause of an if or for and the comment is on the preceding line.
+//
+// For example, imagine that n is the *ast.CallExpr on the method foo in the
+// following snippet:
+//
+// func nonsense() bool {
+// if v := (g.foo() + 1) > 2; !v {
+// return true
+// }
+// return false
+// }
+//
+// This function would return all of the nodes up to the `IfStmt` as relevant
+// and would return the `BlockStmt` of the function nonsense as containing.
+func findNodesInBlock(f *ast.File, n ast.Node) (relevant []ast.Node, containing ast.Node) {
+ stack, _ := astutil.PathEnclosingInterval(f, n.Pos(), n.End())
+ // Add all of the children of n to the set of relevant nodes.
+ ast.Walk(funcVisitor(func(node ast.Node) {
+ relevant = append(relevant, node)
+ }), n)
+
+ // Nodes were just added with the parent at the beginning and children at the
+ // end. Reverse it.
+ reverseNodes(relevant)
+
+ // Add the parents up to the enclosing block or declaration block and discover
+ // that containing node.
+ containing = f // worst-case
+ for _, n := range stack {
+ switch n.(type) {
+ case *ast.GenDecl, *ast.BlockStmt:
+ containing = n
+ return relevant, containing
+ default:
+ // Add all of the parents of n up to the containing BlockStmt or GenDecl
+ // to the set of relevant nodes.
+ relevant = append(relevant, n)
+ }
+ }
+ return relevant, containing
+}
+
+func reverseNodes(n []ast.Node) {
+ for i := 0; i < len(n)/2; i++ {
+ n[i], n[len(n)-i-1] = n[len(n)-i-1], n[i]
+ }
+}
+
+type funcVisitor func(node ast.Node)
+
+var _ ast.Visitor = (funcVisitor)(nil)
+
+func (f funcVisitor) Visit(node ast.Node) (w ast.Visitor) {
+ if node != nil {
+ f(node)
+ }
+ return f
+}