gopls: skip unusedparams for generated files

The unusedparams analyzer makes a lot of noise for some generated files,
notably those generated by protoc-gen-go.
Since we can't actually edit generated files to fix this issue,
it seems better not to report it.

Fixes golang/go#71481

Change-Id: I4e73c74312dfea6ef4ce631cc029764519d6b809
Reviewed-on: https://go-review.googlesource.com/c/tools/+/645575
Reviewed-by: Sam Salisbury <samsalisbury@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Hongxiang Jiang <hxjiang@golang.org>
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index 282a9f4..2749152 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -1000,6 +1000,8 @@
 effects in the argument expressions; see
 https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.
 
+This analyzer ignores generated code.
+
 Default: on.
 
 Package documentation: [unusedparams](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams)
diff --git a/gopls/internal/analysis/unusedparams/doc.go b/gopls/internal/analysis/unusedparams/doc.go
index 07e43c0..16d318e 100644
--- a/gopls/internal/analysis/unusedparams/doc.go
+++ b/gopls/internal/analysis/unusedparams/doc.go
@@ -31,4 +31,6 @@
 // arguments at call sites, while taking care to preserve any side
 // effects in the argument expressions; see
 // https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.
+//
+// This analyzer ignores generated code.
 package unusedparams
diff --git a/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/generatedcode.go b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/generatedcode.go
new file mode 100644
index 0000000..fdbe64d
--- /dev/null
+++ b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/generatedcode.go
@@ -0,0 +1,15 @@
+// Code generated with somegen DO NOT EDIT.
+//
+// Because this file is generated, there should be no diagnostics
+// reported for any unused parameters.
+
+package generatedcode
+
+// generatedInterface exists to ensure that the generated code
+// is considered when determining whether parameters are used
+// in non-generated code.
+type generatedInterface interface{ n(f bool) }
+
+func a(x bool) { println() }
+
+var v = func(x bool) { println() }
diff --git a/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/generatedcode.go.golden b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/generatedcode.go.golden
new file mode 100644
index 0000000..fdbe64d
--- /dev/null
+++ b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/generatedcode.go.golden
@@ -0,0 +1,15 @@
+// Code generated with somegen DO NOT EDIT.
+//
+// Because this file is generated, there should be no diagnostics
+// reported for any unused parameters.
+
+package generatedcode
+
+// generatedInterface exists to ensure that the generated code
+// is considered when determining whether parameters are used
+// in non-generated code.
+type generatedInterface interface{ n(f bool) }
+
+func a(x bool) { println() }
+
+var v = func(x bool) { println() }
diff --git a/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/nongeneratedcode.go b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/nongeneratedcode.go
new file mode 100644
index 0000000..fe0ef94
--- /dev/null
+++ b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/nongeneratedcode.go
@@ -0,0 +1,20 @@
+package generatedcode
+
+// This file does not have the generated code comment.
+// It exists to ensure that generated code is considered
+// when determining whether or not function parameters
+// are used.
+
+type implementsGeneratedInterface struct{}
+
+// The f parameter should not be reported as unused,
+// because this method implements the parent interface defined
+// in the generated code.
+func (implementsGeneratedInterface) n(f bool) {
+	// The body must not be empty, otherwise unusedparams will
+	// not report the unused parameter regardles of the
+	// interface.
+	println()
+}
+
+func b(x bool) { println() } // want "unused parameter: x"
diff --git a/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/nongeneratedcode.go.golden b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/nongeneratedcode.go.golden
new file mode 100644
index 0000000..170dc85
--- /dev/null
+++ b/gopls/internal/analysis/unusedparams/testdata/src/generatedcode/nongeneratedcode.go.golden
@@ -0,0 +1,20 @@
+package generatedcode
+
+// This file does not have the generated code comment.
+// It exists to ensure that generated code is considered
+// when determining whether or not function parameters
+// are used.
+
+type implementsGeneratedInterface struct{}
+
+// The f parameter should not be reported as unused,
+// because this method implements the parent interface defined
+// in the generated code.
+func (implementsGeneratedInterface) n(f bool) {
+	// The body must not be empty, otherwise unusedparams will
+	// not report the unused parameter regardles of the
+	// interface.
+	println()
+}
+
+func b(_ bool) { println() } // want "unused parameter: x"
diff --git a/gopls/internal/analysis/unusedparams/unusedparams.go b/gopls/internal/analysis/unusedparams/unusedparams.go
index ff7fdc4..2986dfd 100644
--- a/gopls/internal/analysis/unusedparams/unusedparams.go
+++ b/gopls/internal/analysis/unusedparams/unusedparams.go
@@ -139,172 +139,193 @@
 		}
 	}
 
-	// Check each non-address-taken function's parameters are all used.
-	filter := []ast.Node{
-		(*ast.FuncDecl)(nil),
-		(*ast.FuncLit)(nil),
-	}
-	cursor.Root(inspect).Inspect(filter, func(c cursor.Cursor, push bool) bool {
-		// (We always return true so that we visit nested FuncLits.)
-
+	// Inspect each file to see if it is generated.
+	//
+	// We do not want to report unused parameters in generated code itself,
+	// however we need to include generated code in the overall analysis as
+	// it may be calling functions in non-generated code.
+	files := []ast.Node{(*ast.File)(nil)}
+	cursor.Root(inspect).Inspect(files, func(c cursor.Cursor, push bool) bool {
 		if !push {
 			return true
 		}
 
-		var (
-			fn    types.Object // function symbol (*Func, possibly *Var for a FuncLit)
-			ftype *ast.FuncType
-			body  *ast.BlockStmt
-		)
-		switch n := c.Node().(type) {
-		case *ast.FuncDecl:
-			// We can't analyze non-Go functions.
-			if n.Body == nil {
+		isGenerated := ast.IsGenerated(c.Node().(*ast.File))
+
+		// Descend into the file, check each non-address-taken function's parameters
+		// are all used.
+		funcs := []ast.Node{
+			(*ast.FuncDecl)(nil),
+			(*ast.FuncLit)(nil),
+		}
+		c.Inspect(funcs, func(c cursor.Cursor, push bool) bool {
+			// (We always return true so that we visit nested FuncLits.)
+			if !push {
 				return true
 			}
 
-			// Ignore exported functions and methods: we
-			// must assume they may be address-taken in
-			// another package.
-			if n.Name.IsExported() {
-				return true
-			}
+			var (
+				fn    types.Object // function symbol (*Func, possibly *Var for a FuncLit)
+				ftype *ast.FuncType
+				body  *ast.BlockStmt
+			)
+			switch n := c.Node().(type) {
+			case *ast.FuncDecl:
+				// We can't analyze non-Go functions.
+				if n.Body == nil {
+					return true
+				}
 
-			// Ignore methods that match the name of any
-			// interface method declared in this package,
-			// as the method's signature may need to conform
-			// to the interface.
-			if n.Recv != nil && unexportedIMethodNames[n.Name.Name] {
-				return true
-			}
+				// Ignore exported functions and methods: we
+				// must assume they may be address-taken in
+				// another package.
+				if n.Name.IsExported() {
+					return true
+				}
 
-			fn = pass.TypesInfo.Defs[n.Name].(*types.Func)
-			ftype, body = n.Type, n.Body
+				// Ignore methods that match the name of any
+				// interface method declared in this package,
+				// as the method's signature may need to conform
+				// to the interface.
+				if n.Recv != nil && unexportedIMethodNames[n.Name.Name] {
+					return true
+				}
 
-		case *ast.FuncLit:
-			// Find the symbol for the variable (if any)
-			// to which the FuncLit is bound.
-			// (We don't bother to allow ParenExprs.)
-			switch parent := c.Parent().Node().(type) {
-			case *ast.AssignStmt:
-				// f  = func() {...}
-				// f := func() {...}
-				if e, idx := c.Edge(); e == edge.AssignStmt_Rhs {
-					// Inv: n == AssignStmt.Rhs[idx]
-					if id, ok := parent.Lhs[idx].(*ast.Ident); ok {
-						fn = pass.TypesInfo.ObjectOf(id)
+				fn = pass.TypesInfo.Defs[n.Name].(*types.Func)
+				ftype, body = n.Type, n.Body
 
-						// Edge case: f = func() {...}
-						// should not count as a use.
-						if pass.TypesInfo.Uses[id] != nil {
-							usesOutsideCall[fn] = moreslices.Remove(usesOutsideCall[fn], id)
+			case *ast.FuncLit:
+				// Find the symbol for the variable (if any)
+				// to which the FuncLit is bound.
+				// (We don't bother to allow ParenExprs.)
+				switch parent := c.Parent().Node().(type) {
+				case *ast.AssignStmt:
+					// f  = func() {...}
+					// f := func() {...}
+					if e, idx := c.Edge(); e == edge.AssignStmt_Rhs {
+						// Inv: n == AssignStmt.Rhs[idx]
+						if id, ok := parent.Lhs[idx].(*ast.Ident); ok {
+							fn = pass.TypesInfo.ObjectOf(id)
+
+							// Edge case: f = func() {...}
+							// should not count as a use.
+							if pass.TypesInfo.Uses[id] != nil {
+								usesOutsideCall[fn] = moreslices.Remove(usesOutsideCall[fn], id)
+							}
+
+							if fn == nil && id.Name == "_" {
+								// Edge case: _ = func() {...}
+								// has no local var. Fake one.
+								v := types.NewVar(id.Pos(), pass.Pkg, id.Name, pass.TypesInfo.TypeOf(n))
+								typesinternal.SetVarKind(v, typesinternal.LocalVar)
+								fn = v
+							}
 						}
+					}
 
-						if fn == nil && id.Name == "_" {
-							// Edge case: _ = func() {...}
-							// has no local var. Fake one.
-							v := types.NewVar(id.Pos(), pass.Pkg, id.Name, pass.TypesInfo.TypeOf(n))
-							typesinternal.SetVarKind(v, typesinternal.LocalVar)
-							fn = v
+				case *ast.ValueSpec:
+					// var f = func() { ... }
+					// (unless f is an exported package-level var)
+					for i, val := range parent.Values {
+						if val == n {
+							v := pass.TypesInfo.Defs[parent.Names[i]]
+							if !(v.Parent() == pass.Pkg.Scope() && v.Exported()) {
+								fn = v
+							}
+							break
 						}
 					}
 				}
 
-			case *ast.ValueSpec:
-				// var f = func() { ... }
-				// (unless f is an exported package-level var)
-				for i, val := range parent.Values {
-					if val == n {
-						v := pass.TypesInfo.Defs[parent.Names[i]]
-						if !(v.Parent() == pass.Pkg.Scope() && v.Exported()) {
-							fn = v
+				ftype, body = n.Type, n.Body
+			}
+
+			// Ignore address-taken functions and methods: unused
+			// parameters may be needed to conform to a func type.
+			if fn == nil || len(usesOutsideCall[fn]) > 0 {
+				return true
+			}
+
+			// If there are no parameters, there are no unused parameters.
+			if ftype.Params.NumFields() == 0 {
+				return true
+			}
+
+			// To reduce false positives, ignore functions with an
+			// empty or panic body.
+			//
+			// We choose not to ignore functions whose body is a
+			// single return statement (as earlier versions did)
+			// 	func f() { return }
+			// 	func f() { return g(...) }
+			// as we suspect that was just heuristic to reduce
+			// false positives in the earlier unsound algorithm.
+			switch len(body.List) {
+			case 0:
+				// Empty body. Although the parameter is
+				// unnecessary, it's pretty obvious to the
+				// reader that that's the case, so we allow it.
+				return true // func f() {}
+			case 1:
+				if stmt, ok := body.List[0].(*ast.ExprStmt); ok {
+					// We allow a panic body, as it is often a
+					// placeholder for a future implementation:
+					// 	func f() { panic(...) }
+					if call, ok := stmt.X.(*ast.CallExpr); ok {
+						if fun, ok := call.Fun.(*ast.Ident); ok && fun.Name == "panic" {
+							return true
 						}
-						break
 					}
 				}
 			}
 
-			ftype, body = n.Type, n.Body
-		}
-
-		// Ignore address-taken functions and methods: unused
-		// parameters may be needed to conform to a func type.
-		if fn == nil || len(usesOutsideCall[fn]) > 0 {
-			return true
-		}
-
-		// If there are no parameters, there are no unused parameters.
-		if ftype.Params.NumFields() == 0 {
-			return true
-		}
-
-		// To reduce false positives, ignore functions with an
-		// empty or panic body.
-		//
-		// We choose not to ignore functions whose body is a
-		// single return statement (as earlier versions did)
-		// 	func f() { return }
-		// 	func f() { return g(...) }
-		// as we suspect that was just heuristic to reduce
-		// false positives in the earlier unsound algorithm.
-		switch len(body.List) {
-		case 0:
-			// Empty body. Although the parameter is
-			// unnecessary, it's pretty obvious to the
-			// reader that that's the case, so we allow it.
-			return true // func f() {}
-		case 1:
-			if stmt, ok := body.List[0].(*ast.ExprStmt); ok {
-				// We allow a panic body, as it is often a
-				// placeholder for a future implementation:
-				// 	func f() { panic(...) }
-				if call, ok := stmt.X.(*ast.CallExpr); ok {
-					if fun, ok := call.Fun.(*ast.Ident); ok && fun.Name == "panic" {
-						return true
-					}
-				}
+			// Don't report diagnostics on generated files.
+			if isGenerated {
+				return true
 			}
-		}
 
-		// Report each unused parameter.
-		for _, field := range ftype.Params.List {
-			for _, id := range field.Names {
-				if id.Name == "_" {
-					continue
-				}
-				param := pass.TypesInfo.Defs[id].(*types.Var)
-				if !usedVars[param] {
-					start, end := field.Pos(), field.End()
-					if len(field.Names) > 1 {
-						start, end = id.Pos(), id.End()
+			// Report each unused parameter.
+			for _, field := range ftype.Params.List {
+				for _, id := range field.Names {
+					if id.Name == "_" {
+						continue
 					}
-					// This diagnostic carries both an edit-based fix to
-					// rename the unused parameter, and a command-based fix
-					// to remove it (see golang.RemoveUnusedParameter).
-					pass.Report(analysis.Diagnostic{
-						Pos:      start,
-						End:      end,
-						Message:  fmt.Sprintf("unused parameter: %s", id.Name),
-						Category: FixCategory,
-						SuggestedFixes: []analysis.SuggestedFix{
-							{
-								Message: `Rename parameter to "_"`,
-								TextEdits: []analysis.TextEdit{{
-									Pos:     id.Pos(),
-									End:     id.End(),
-									NewText: []byte("_"),
-								}},
+					param := pass.TypesInfo.Defs[id].(*types.Var)
+					if !usedVars[param] {
+						start, end := field.Pos(), field.End()
+						if len(field.Names) > 1 {
+							start, end = id.Pos(), id.End()
+						}
+
+						// This diagnostic carries both an edit-based fix to
+						// rename the unused parameter, and a command-based fix
+						// to remove it (see golang.RemoveUnusedParameter).
+						pass.Report(analysis.Diagnostic{
+							Pos:      start,
+							End:      end,
+							Message:  fmt.Sprintf("unused parameter: %s", id.Name),
+							Category: FixCategory,
+							SuggestedFixes: []analysis.SuggestedFix{
+								{
+									Message: `Rename parameter to "_"`,
+									TextEdits: []analysis.TextEdit{{
+										Pos:     id.Pos(),
+										End:     id.End(),
+										NewText: []byte("_"),
+									}},
+								},
+								{
+									Message: fmt.Sprintf("Remove unused parameter %q", id.Name),
+									// No TextEdits => computed by gopls command
+								},
 							},
-							{
-								Message: fmt.Sprintf("Remove unused parameter %q", id.Name),
-								// No TextEdits => computed by gopls command
-							},
-						},
-					})
+						})
+					}
 				}
 			}
-		}
 
+			return true
+		})
 		return true
 	})
 	return nil, nil
diff --git a/gopls/internal/analysis/unusedparams/unusedparams_test.go b/gopls/internal/analysis/unusedparams/unusedparams_test.go
index 1e2d885..e943c20 100644
--- a/gopls/internal/analysis/unusedparams/unusedparams_test.go
+++ b/gopls/internal/analysis/unusedparams/unusedparams_test.go
@@ -13,5 +13,5 @@
 
 func Test(t *testing.T) {
 	testdata := analysistest.TestData()
-	analysistest.RunWithSuggestedFixes(t, testdata, unusedparams.Analyzer, "a", "typeparams")
+	analysistest.RunWithSuggestedFixes(t, testdata, unusedparams.Analyzer, "a", "generatedcode", "typeparams")
 }
diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json
index 83151ae..74a6159 100644
--- a/gopls/internal/doc/api.json
+++ b/gopls/internal/doc/api.json
@@ -635,7 +635,7 @@
 						},
 						{
 							"Name": "\"unusedparams\"",
-							"Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n  - \"address-taken\" functions, that is, functions that are used as\n    a value rather than being called directly; their signatures may\n    be required to conform to a func type.\n  - exported functions or methods, since they may be address-taken\n    in another package.\n  - unexported methods whose name matches an interface method\n    declared in the same package, since the method's signature\n    may be required to conform to the interface type.\n  - functions with empty bodies, or containing just a call to panic.\n  - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.",
+							"Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n  - \"address-taken\" functions, that is, functions that are used as\n    a value rather than being called directly; their signatures may\n    be required to conform to a func type.\n  - exported functions or methods, since they may be address-taken\n    in another package.\n  - unexported methods whose name matches an interface method\n    declared in the same package, since the method's signature\n    may be required to conform to the interface type.\n  - functions with empty bodies, or containing just a call to panic.\n  - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.\n\nThis analyzer ignores generated code.",
 							"Default": "true"
 						},
 						{
@@ -1339,7 +1339,7 @@
 		},
 		{
 			"Name": "unusedparams",
-			"Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n  - \"address-taken\" functions, that is, functions that are used as\n    a value rather than being called directly; their signatures may\n    be required to conform to a func type.\n  - exported functions or methods, since they may be address-taken\n    in another package.\n  - unexported methods whose name matches an interface method\n    declared in the same package, since the method's signature\n    may be required to conform to the interface type.\n  - functions with empty bodies, or containing just a call to panic.\n  - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.",
+			"Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n  - \"address-taken\" functions, that is, functions that are used as\n    a value rather than being called directly; their signatures may\n    be required to conform to a func type.\n  - exported functions or methods, since they may be address-taken\n    in another package.\n  - unexported methods whose name matches an interface method\n    declared in the same package, since the method's signature\n    may be required to conform to the interface type.\n  - functions with empty bodies, or containing just a call to panic.\n  - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.\n\nThis analyzer ignores generated code.",
 			"URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams",
 			"Default": true
 		},