internal/astutil: add EqualSyntax, moved from modernize

Change-Id: Ia08518c031dce2f82d17049f911946fc03356014
Reviewed-on: https://go-review.googlesource.com/c/tools/+/708915
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/analysis/passes/modernize/bloop.go b/go/analysis/passes/modernize/bloop.go
index 5d7c9e5..9578468 100644
--- a/go/analysis/passes/modernize/bloop.go
+++ b/go/analysis/passes/modernize/bloop.go
@@ -18,6 +18,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/moreiters"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
 )
@@ -241,7 +242,7 @@
 		isZeroIntLiteral(info, assign.Rhs[0]) &&
 		is[*ast.IncDecStmt](loop.Post) &&
 		loop.Post.(*ast.IncDecStmt).Tok == token.INC &&
-		equalSyntax(loop.Post.(*ast.IncDecStmt).X, assign.Lhs[0]) {
+		astutil.EqualSyntax(loop.Post.(*ast.IncDecStmt).X, assign.Lhs[0]) {
 		return info.Defs[assign.Lhs[0].(*ast.Ident)].(*types.Var)
 	}
 	return nil
diff --git a/go/analysis/passes/modernize/forvar.go b/go/analysis/passes/modernize/forvar.go
index 2c3cd29..9a24856 100644
--- a/go/analysis/passes/modernize/forvar.go
+++ b/go/analysis/passes/modernize/forvar.go
@@ -13,6 +13,7 @@
 	"golang.org/x/tools/go/ast/inspector"
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
+	"golang.org/x/tools/internal/astutil"
 )
 
 var ForVarAnalyzer = &analysis.Analyzer{
@@ -52,8 +53,8 @@
 			}
 			isLoopVarRedecl := func(assign *ast.AssignStmt) bool {
 				for i, lhs := range assign.Lhs {
-					if !(equalSyntax(lhs, assign.Rhs[i]) &&
-						(equalSyntax(lhs, loop.Key) || equalSyntax(lhs, loop.Value))) {
+					if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
+						(astutil.EqualSyntax(lhs, loop.Key) || astutil.EqualSyntax(lhs, loop.Value))) {
 						return false
 					}
 				}
diff --git a/go/analysis/passes/modernize/maps.go b/go/analysis/passes/modernize/maps.go
index 16747ed..d6a77c4 100644
--- a/go/analysis/passes/modernize/maps.go
+++ b/go/analysis/passes/modernize/maps.go
@@ -17,6 +17,7 @@
 	"golang.org/x/tools/go/ast/inspector"
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typeparams"
 )
 
@@ -99,7 +100,7 @@
 			if assign, ok := curPrev.Node().(*ast.AssignStmt); ok &&
 				len(assign.Lhs) == 1 &&
 				len(assign.Rhs) == 1 &&
-				equalSyntax(assign.Lhs[0], m) {
+				astutil.EqualSyntax(assign.Lhs[0], m) {
 
 				// Have: m = rhs; for k, v := range x { m[k] = v }
 				var newMap bool
@@ -235,8 +236,8 @@
 
 				assign := rng.Body.List[0].(*ast.AssignStmt)
 				if index, ok := assign.Lhs[0].(*ast.IndexExpr); ok &&
-					equalSyntax(rng.Key, index.Index) &&
-					equalSyntax(rng.Value, assign.Rhs[0]) &&
+					astutil.EqualSyntax(rng.Key, index.Index) &&
+					astutil.EqualSyntax(rng.Value, assign.Rhs[0]) &&
 					is[*types.Map](typeparams.CoreType(info.TypeOf(index.X))) &&
 					types.Identical(info.TypeOf(index), info.TypeOf(rng.Value)) { // m[k], v
 
diff --git a/go/analysis/passes/modernize/minmax.go b/go/analysis/passes/modernize/minmax.go
index 350a3c4..3896b28 100644
--- a/go/analysis/passes/modernize/minmax.go
+++ b/go/analysis/passes/modernize/minmax.go
@@ -18,6 +18,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
 )
@@ -92,10 +93,10 @@
 			// For pattern 1, check that:
 			// - lhs = lhs2
 			// - {rhs,rhs2} = {a,b}
-			if equalSyntax(lhs, lhs2) {
-				if equalSyntax(rhs, a) && equalSyntax(rhs2, b) {
+			if astutil.EqualSyntax(lhs, lhs2) {
+				if astutil.EqualSyntax(rhs, a) && astutil.EqualSyntax(rhs2, b) {
 					sign = +sign
-				} else if equalSyntax(rhs2, a) && equalSyntax(rhs, b) {
+				} else if astutil.EqualSyntax(rhs2, a) && astutil.EqualSyntax(rhs, b) {
 					sign = -sign
 				} else {
 					return
@@ -149,10 +150,10 @@
 			lhs0 := fassign.Lhs[0]
 			rhs0 := fassign.Rhs[0]
 
-			if equalSyntax(lhs, lhs0) {
-				if equalSyntax(rhs, a) && (equalSyntax(rhs0, b) || equalSyntax(lhs0, b)) {
+			if astutil.EqualSyntax(lhs, lhs0) {
+				if astutil.EqualSyntax(rhs, a) && (astutil.EqualSyntax(rhs0, b) || astutil.EqualSyntax(lhs0, b)) {
 					sign = +sign
-				} else if (equalSyntax(rhs0, a) || equalSyntax(lhs0, a)) && equalSyntax(rhs, b) {
+				} else if (astutil.EqualSyntax(rhs0, a) || astutil.EqualSyntax(lhs0, a)) && astutil.EqualSyntax(rhs, b) {
 					sign = -sign
 				} else {
 					return
@@ -166,9 +167,9 @@
 				// Permit lhs0 to stand for rhs0 in the matching,
 				// but don't actually reduce to lhs0 = min(lhs0, rhs)
 				// since the "=" could be a ":=". Use min(rhs0, rhs).
-				if equalSyntax(lhs0, a) {
+				if astutil.EqualSyntax(lhs0, a) {
 					a = rhs0
-				} else if equalSyntax(lhs0, b) {
+				} else if astutil.EqualSyntax(lhs0, b) {
 					b = rhs0
 				}
 
@@ -411,9 +412,9 @@
 	y := cmp.Y              // right operand
 
 	// Check operand order and adjust sign accordingly
-	if equalSyntax(t, x) && equalSyntax(f, y) {
+	if astutil.EqualSyntax(t, x) && astutil.EqualSyntax(f, y) {
 		sign = +sign
-	} else if equalSyntax(t, y) && equalSyntax(f, x) {
+	} else if astutil.EqualSyntax(t, y) && astutil.EqualSyntax(f, x) {
 		sign = -sign
 	} else {
 		return false
diff --git a/go/analysis/passes/modernize/modernize.go b/go/analysis/passes/modernize/modernize.go
index 58814f0..6367012 100644
--- a/go/analysis/passes/modernize/modernize.go
+++ b/go/analysis/passes/modernize/modernize.go
@@ -20,7 +20,6 @@
 	"golang.org/x/tools/go/ast/inspector"
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
-	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/moreiters"
 	"golang.org/x/tools/internal/stdlib"
 	"golang.org/x/tools/internal/versions"
@@ -67,12 +66,6 @@
 	}
 }
 
-// equalSyntax reports whether x and y are syntactically equal (ignoring comments).
-func equalSyntax(x, y ast.Expr) bool {
-	sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name }
-	return astutil.Equal(x, y, sameName)
-}
-
 // formatExprs formats a comma-separated list of expressions.
 func formatExprs(fset *token.FileSet, exprs []ast.Expr) string {
 	var buf strings.Builder
diff --git a/go/analysis/passes/modernize/rangeint.go b/go/analysis/passes/modernize/rangeint.go
index 3e07f7b..62c9740 100644
--- a/go/analysis/passes/modernize/rangeint.go
+++ b/go/analysis/passes/modernize/rangeint.go
@@ -18,6 +18,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typesinternal"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
 )
@@ -85,7 +86,7 @@
 
 				if compare, ok := loop.Cond.(*ast.BinaryExpr); ok &&
 					compare.Op == token.LSS &&
-					equalSyntax(compare.X, init.Lhs[0]) {
+					astutil.EqualSyntax(compare.X, init.Lhs[0]) {
 					// Have: for i = 0; i < limit; ... {}
 
 					limit := compare.Y
@@ -126,7 +127,7 @@
 
 					if inc, ok := loop.Post.(*ast.IncDecStmt); ok &&
 						inc.Tok == token.INC &&
-						equalSyntax(compare.X, inc.X) {
+						astutil.EqualSyntax(compare.X, inc.X) {
 						// Have: for i = 0; i < limit; i++ {}
 
 						// Find references to i within the loop body.
diff --git a/go/analysis/passes/modernize/slices.go b/go/analysis/passes/modernize/slices.go
index e2dc6f8..42b01d2 100644
--- a/go/analysis/passes/modernize/slices.go
+++ b/go/analysis/passes/modernize/slices.go
@@ -17,6 +17,7 @@
 	"golang.org/x/tools/go/types/typeutil"
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
+	"golang.org/x/tools/internal/astutil"
 )
 
 // Warning: this analyzer is not safe to enable by default.
@@ -261,12 +262,12 @@
 	switch e := e.(type) {
 	case *ast.SliceExpr:
 		// x[:0:0], x[:len(x):len(x)], x[:k:k]
-		if e.Slice3 && e.High != nil && e.Max != nil && equalSyntax(e.High, e.Max) { // x[:k:k]
+		if e.Slice3 && e.High != nil && e.Max != nil && astutil.EqualSyntax(e.High, e.Max) { // x[:k:k]
 			res = e
 			empty = isZeroIntLiteral(info, e.High) // x[:0:0]
 			if call, ok := e.High.(*ast.CallExpr); ok &&
 				typeutil.Callee(info, call) == builtinLen &&
-				equalSyntax(call.Args[0], e.X) {
+				astutil.EqualSyntax(call.Args[0], e.X) {
 				res = e.X // x[:len(x):len(x)] -> x
 			}
 			return
diff --git a/go/analysis/passes/modernize/slicescontains.go b/go/analysis/passes/modernize/slicescontains.go
index 447e30b..5480cd9 100644
--- a/go/analysis/passes/modernize/slicescontains.go
+++ b/go/analysis/passes/modernize/slicescontains.go
@@ -17,6 +17,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
 )
@@ -87,12 +88,12 @@
 		// isSliceElem reports whether e denotes the
 		// current slice element (elem or s[i]).
 		isSliceElem := func(e ast.Expr) bool {
-			if rng.Value != nil && equalSyntax(e, rng.Value) {
+			if rng.Value != nil && astutil.EqualSyntax(e, rng.Value) {
 				return true // "elem"
 			}
 			if x, ok := e.(*ast.IndexExpr); ok &&
-				equalSyntax(x.X, rng.X) &&
-				equalSyntax(x.Index, rng.Key) {
+				astutil.EqualSyntax(x.X, rng.X) &&
+				astutil.EqualSyntax(x.Index, rng.Key) {
 				return true // "s[i]"
 			}
 			return false
@@ -322,7 +323,7 @@
 					if prevAssign, ok := prevStmt.(*ast.AssignStmt); ok &&
 						len(prevAssign.Lhs) == 1 &&
 						len(prevAssign.Rhs) == 1 &&
-						equalSyntax(prevAssign.Lhs[0], assign.Lhs[0]) &&
+						astutil.EqualSyntax(prevAssign.Lhs[0], assign.Lhs[0]) &&
 						is[*ast.Ident](assign.Rhs[0]) &&
 						info.Uses[assign.Rhs[0].(*ast.Ident)] == builtinTrue {
 
diff --git a/go/analysis/passes/modernize/slicesdelete.go b/go/analysis/passes/modernize/slicesdelete.go
index f3c5504..6310372 100644
--- a/go/analysis/passes/modernize/slicesdelete.go
+++ b/go/analysis/passes/modernize/slicesdelete.go
@@ -15,6 +15,7 @@
 	"golang.org/x/tools/go/ast/inspector"
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
+	"golang.org/x/tools/internal/astutil"
 )
 
 // Warning: this analyzer is not safe to enable by default (not nil-preserving).
@@ -140,7 +141,7 @@
 					slice2, ok2 := call.Args[1].(*ast.SliceExpr)
 					if ok1 && slice1.Low == nil && !slice1.Slice3 &&
 						ok2 && slice2.High == nil && !slice2.Slice3 &&
-						equalSyntax(slice1.X, slice2.X) && noEffects(info, slice1.X) &&
+						astutil.EqualSyntax(slice1.X, slice2.X) && noEffects(info, slice1.X) &&
 						increasingSliceIndices(info, slice1.High, slice2.Low) {
 						// Have append(s[:a], s[b:]...) where we can verify a < b.
 						report(file, call, slice1, slice2)
@@ -176,5 +177,5 @@
 
 	ai, ak := split(a)
 	bi, bk := split(b)
-	return equalSyntax(ai, bi) && constant.Compare(ak, token.LSS, bk)
+	return astutil.EqualSyntax(ai, bi) && constant.Compare(ak, token.LSS, bk)
 }
diff --git a/go/analysis/passes/modernize/sortslice.go b/go/analysis/passes/modernize/sortslice.go
index 3950fc9..3b6b8da 100644
--- a/go/analysis/passes/modernize/sortslice.go
+++ b/go/analysis/passes/modernize/sortslice.go
@@ -14,6 +14,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
 )
 
@@ -79,7 +80,7 @@
 					isIndex := func(e ast.Expr, v *types.Var) bool {
 						index, ok := e.(*ast.IndexExpr)
 						return ok &&
-							equalSyntax(index.X, s) &&
+							astutil.EqualSyntax(index.X, s) &&
 							is[*ast.Ident](index.Index) &&
 							info.Uses[index.Index.(*ast.Ident)] == v
 					}
diff --git a/go/analysis/passes/modernize/stditerators.go b/go/analysis/passes/modernize/stditerators.go
index 1af3a59..e32c297 100644
--- a/go/analysis/passes/modernize/stditerators.go
+++ b/go/analysis/passes/modernize/stditerators.go
@@ -17,6 +17,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/goplsexport"
 	"golang.org/x/tools/internal/stdlib"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
@@ -133,7 +134,7 @@
 					// call to x.At(i)?
 					if call, ok := assign.Rhs[0].(*ast.CallExpr); ok &&
 						typeutil.Callee(info, call) == atMethod &&
-						equalSyntax(ast.Unparen(call.Fun).(*ast.SelectorExpr).X, x) &&
+						astutil.EqualSyntax(ast.Unparen(call.Fun).(*ast.SelectorExpr).X, x) &&
 						is[*ast.Ident](call.Args[0]) &&
 						info.Uses[call.Args[0].(*ast.Ident)] == i {
 						// Have: { elem := x.At(i); ... }
@@ -282,7 +283,7 @@
 				atSel := ast.Unparen(atCall.Fun).(*ast.SelectorExpr)
 
 				// Check receivers of Len, At calls match (syntactically).
-				if !equalSyntax(lenSel.X, atSel.X) {
+				if !astutil.EqualSyntax(lenSel.X, atSel.X) {
 					continue nextCall
 				}
 
diff --git a/go/analysis/passes/modernize/stringscutprefix.go b/go/analysis/passes/modernize/stringscutprefix.go
index 199c0b5..4422bdd 100644
--- a/go/analysis/passes/modernize/stringscutprefix.go
+++ b/go/analysis/passes/modernize/stringscutprefix.go
@@ -17,6 +17,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
 )
 
@@ -123,7 +124,7 @@
 
 					// check whether the obj1 uses the exact the same argument with strings.HasPrefix
 					// shadow variables won't be valid because we only access the first statement (ditto Suffix).
-					if equalSyntax(s0, s) && equalSyntax(pre0, pre) {
+					if astutil.EqualSyntax(s0, s) && astutil.EqualSyntax(pre0, pre) {
 						after := analysisinternal.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), varName)
 						_, prefix, importEdits := analysisinternal.AddImport(
 							info,
@@ -201,8 +202,8 @@
 						fixMessage = "Replace TrimSuffix with CutSuffix"
 					}
 
-					if equalSyntax(lhs, bin.X) && equalSyntax(call.Args[0], bin.Y) ||
-						(equalSyntax(lhs, bin.Y) && equalSyntax(call.Args[0], bin.X)) {
+					if astutil.EqualSyntax(lhs, bin.X) && astutil.EqualSyntax(call.Args[0], bin.Y) ||
+						(astutil.EqualSyntax(lhs, bin.Y) && astutil.EqualSyntax(call.Args[0], bin.X)) {
 						okVarName := analysisinternal.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
 						// Have one of:
 						//   if rest := TrimPrefix(s, prefix); rest != s { (ditto Suffix)
diff --git a/go/analysis/passes/modernize/waitgroup.go b/go/analysis/passes/modernize/waitgroup.go
index d7290ae..bbae38a 100644
--- a/go/analysis/passes/modernize/waitgroup.go
+++ b/go/analysis/passes/modernize/waitgroup.go
@@ -17,6 +17,7 @@
 	"golang.org/x/tools/internal/analysisinternal"
 	"golang.org/x/tools/internal/analysisinternal/generated"
 	typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typesinternal/typeindex"
 )
 
@@ -107,13 +108,13 @@
 		var doneStmt ast.Stmt
 		if deferStmt, ok := list[0].(*ast.DeferStmt); ok &&
 			typeutil.Callee(info, deferStmt.Call) == syncWaitGroupDone &&
-			equalSyntax(ast.Unparen(deferStmt.Call.Fun).(*ast.SelectorExpr).X, addCallRecv) {
+			astutil.EqualSyntax(ast.Unparen(deferStmt.Call.Fun).(*ast.SelectorExpr).X, addCallRecv) {
 			doneStmt = deferStmt // "defer wg.Done()"
 
 		} else if lastStmt, ok := list[len(list)-1].(*ast.ExprStmt); ok {
 			if doneCall, ok := lastStmt.X.(*ast.CallExpr); ok &&
 				typeutil.Callee(info, doneCall) == syncWaitGroupDone &&
-				equalSyntax(ast.Unparen(doneCall.Fun).(*ast.SelectorExpr).X, addCallRecv) {
+				astutil.EqualSyntax(ast.Unparen(doneCall.Fun).(*ast.SelectorExpr).X, addCallRecv) {
 				doneStmt = lastStmt // "wg.Done()"
 			}
 		}
diff --git a/internal/astutil/equal.go b/internal/astutil/equal.go
index c945de0..210f392 100644
--- a/internal/astutil/equal.go
+++ b/internal/astutil/equal.go
@@ -26,6 +26,14 @@
 	return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical)
 }
 
+// EqualSyntax reports whether x and y are equal.
+// Identifiers are considered equal if they are spelled the same.
+// Comments are ignored.
+func EqualSyntax(x, y ast.Expr) bool {
+	sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name }
+	return Equal(x, y, sameName)
+}
+
 func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool {
 	// Ensure types are the same
 	if x.Type() != y.Type() {