x/tools: drop go1.18 support

Updates golang/go#64407

Change-Id: I247a7ff7f07613674f8e31e4cb9c5a68762d2203
Reviewed-on: https://go-review.googlesource.com/c/tools/+/567418
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/cmd/bisect/main_test.go b/cmd/bisect/main_test.go
index 7c10ff0..bff1bf2 100644
--- a/cmd/bisect/main_test.go
+++ b/cmd/bisect/main_test.go
@@ -17,7 +17,6 @@
 	"testing"
 
 	"golang.org/x/tools/internal/bisect"
-	"golang.org/x/tools/internal/compat"
 	"golang.org/x/tools/internal/diffp"
 	"golang.org/x/tools/txtar"
 )
@@ -82,7 +81,7 @@
 						have[color] = true
 					}
 					if m.ShouldReport(uint64(i)) {
-						out = compat.Appendf(out, "%s %s\n", color, bisect.Marker(uint64(i)))
+						out = fmt.Appendf(out, "%s %s\n", color, bisect.Marker(uint64(i)))
 					}
 				}
 				err = nil
diff --git a/copyright/copyright.go b/copyright/copyright.go
index b13b56e..556c6e4 100644
--- a/copyright/copyright.go
+++ b/copyright/copyright.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 // Package copyright checks that files have the correct copyright notices.
 package copyright
 
diff --git a/copyright/copyright_test.go b/copyright/copyright_test.go
index 7f78925..947fb10 100644
--- a/copyright/copyright_test.go
+++ b/copyright/copyright_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package copyright
 
 import (
diff --git a/go.mod b/go.mod
index c8e5220..9458c15 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module golang.org/x/tools
 
-go 1.18
+go 1.19
 
 require (
 	github.com/yuin/goldmark v1.4.13
diff --git a/go/analysis/passes/asmdecl/arches_go118.go b/go/analysis/passes/asmdecl/arches_go118.go
deleted file mode 100644
index d8211af..0000000
--- a/go/analysis/passes/asmdecl/arches_go118.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.19
-// +build !go1.19
-
-package asmdecl
-
-func additionalArches() []*asmArch {
-	return nil
-}
diff --git a/go/analysis/passes/asmdecl/arches_go119.go b/go/analysis/passes/asmdecl/arches_go119.go
deleted file mode 100644
index 3018383..0000000
--- a/go/analysis/passes/asmdecl/arches_go119.go
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-// +build go1.19
-
-package asmdecl
-
-var asmArchLoong64 = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true}
-
-func additionalArches() []*asmArch {
-	return []*asmArch{&asmArchLoong64}
-}
diff --git a/go/analysis/passes/asmdecl/asmdecl.go b/go/analysis/passes/asmdecl/asmdecl.go
index e24dac9..f2ca95a 100644
--- a/go/analysis/passes/asmdecl/asmdecl.go
+++ b/go/analysis/passes/asmdecl/asmdecl.go
@@ -96,6 +96,7 @@
 	asmArchRISCV64  = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}}
 	asmArchS390X    = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
 	asmArchWasm     = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false}
+	asmArchLoong64  = asmArch{name: "loong64", bigEndian: false, stack: "R3", lr: true}
 
 	arches = []*asmArch{
 		&asmArch386,
@@ -111,11 +112,11 @@
 		&asmArchRISCV64,
 		&asmArchS390X,
 		&asmArchWasm,
+		&asmArchLoong64,
 	}
 )
 
 func init() {
-	arches = append(arches, additionalArches()...)
 	for _, arch := range arches {
 		arch.sizes = types.SizesFor("gc", arch.name)
 		if arch.sizes == nil {
diff --git a/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go b/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go
index 345db27..fc80410 100644
--- a/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go
+++ b/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go
@@ -4,8 +4,6 @@
 
 // This file contains tests for the useless-assignment checker.
 
-//go:build go1.18
-
 package testdata
 
 import "math/rand"
diff --git a/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go.golden b/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go.golden
index d9384ed..8c8c4b6 100644
--- a/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go.golden
+++ b/go/analysis/passes/assign/testdata/src/typeparams/typeparams.go.golden
@@ -4,8 +4,6 @@
 
 // This file contains tests for the useless-assignment checker.
 
-//go:build go1.18
-
 package testdata
 
 import "math/rand"
diff --git a/go/analysis/passes/bools/testdata/src/typeparams/typeparams.go b/go/analysis/passes/bools/testdata/src/typeparams/typeparams.go
index 7184625..3afb56a 100644
--- a/go/analysis/passes/bools/testdata/src/typeparams/typeparams.go
+++ b/go/analysis/passes/bools/testdata/src/typeparams/typeparams.go
@@ -4,8 +4,6 @@
 
 // This file contains tests for the bool checker.
 
-//go:build go1.18
-
 package typeparams
 
 type T[P interface{ ~int }] struct {
diff --git a/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go b/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go
index 20b652e..00cbd70 100644
--- a/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go
+++ b/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package a
 
 import "testing"
diff --git a/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go.golden b/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go.golden
index 20b652e..00cbd70 100644
--- a/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go.golden
+++ b/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go.golden
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package a
 
 import "testing"
diff --git a/go/analysis/passes/httpresponse/testdata/src/typeparams/typeparams.go b/go/analysis/passes/httpresponse/testdata/src/typeparams/typeparams.go
index 65dd58c..b2515c9 100644
--- a/go/analysis/passes/httpresponse/testdata/src/typeparams/typeparams.go
+++ b/go/analysis/passes/httpresponse/testdata/src/typeparams/typeparams.go
@@ -4,8 +4,6 @@
 
 // This file contains tests for the httpresponse checker.
 
-//go:build go1.18
-
 package typeparams
 
 import (
diff --git a/go/analysis/passes/loopclosure/loopclosure_test.go b/go/analysis/passes/loopclosure/loopclosure_test.go
index c8aab48..683f91e 100644
--- a/go/analysis/passes/loopclosure/loopclosure_test.go
+++ b/go/analysis/passes/loopclosure/loopclosure_test.go
@@ -33,8 +33,6 @@
 }
 
 func TestVersions18(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
-
 	testfile := filepath.Join(analysistest.TestData(), "src", "versions", "go18.txtar")
 	runTxtarFile(t, testfile, loopclosure.Analyzer, "golang.org/fake/versions")
 }
diff --git a/go/analysis/passes/loopclosure/testdata/src/typeparams/typeparams.go b/go/analysis/passes/loopclosure/testdata/src/typeparams/typeparams.go
index ef5b143..8597687 100644
--- a/go/analysis/passes/loopclosure/testdata/src/typeparams/typeparams.go
+++ b/go/analysis/passes/loopclosure/testdata/src/typeparams/typeparams.go
@@ -5,7 +5,7 @@
 // This file contains legacy tests for the loopclosure checker for GoVersion <go1.22.
 // Expectations are incorrect after go1.22.
 
-//go:build go1.18
+//go:build go1.19
 
 package typeparams
 
diff --git a/go/analysis/passes/loopclosure/testdata/src/versions/go18.txtar b/go/analysis/passes/loopclosure/testdata/src/versions/go18.txtar
index abdeb70..edf6e5d 100644
--- a/go/analysis/passes/loopclosure/testdata/src/versions/go18.txtar
+++ b/go/analysis/passes/loopclosure/testdata/src/versions/go18.txtar
@@ -1,11 +1,11 @@
-Test loopclosure at go version go1.18.
+Test loopclosure at go version go1.19.
 
 -- go.mod --
 module golang.org/fake/versions
 
-go 1.18
+go 1.19
 -- pre.go --
-//go:build go1.18
+//go:build go1.19
 
 package versions
 
diff --git a/go/analysis/passes/loopclosure/testdata/src/versions/go22.txtar b/go/analysis/passes/loopclosure/testdata/src/versions/go22.txtar
index 163fb73..eef5408 100644
--- a/go/analysis/passes/loopclosure/testdata/src/versions/go22.txtar
+++ b/go/analysis/passes/loopclosure/testdata/src/versions/go22.txtar
@@ -1,4 +1,7 @@
 Test loopclosure at go version go1.22.
+
+The go1.19 build tag is necessary to force the file version.
+
 -- go.mod --
 module golang.org/fake/versions
 
diff --git a/go/analysis/passes/lostcancel/testdata/src/typeparams/typeparams.go b/go/analysis/passes/lostcancel/testdata/src/typeparams/typeparams.go
index d6d0b5f..1030ba4 100644
--- a/go/analysis/passes/lostcancel/testdata/src/typeparams/typeparams.go
+++ b/go/analysis/passes/lostcancel/testdata/src/typeparams/typeparams.go
@@ -4,8 +4,6 @@
 
 // This file contains tests for the lostcancel checker.
 
-//go:build go1.18
-
 package typeparams
 
 import (
@@ -20,7 +18,7 @@
 	if false {
 		_ = cancel
 	}
-} // want "this return statement may be reached without using the cancel var defined on line 19"
+} // want "this return statement may be reached without using the cancel var defined on line 17"
 
 func _[T any]() {
 	_, cancel := context.WithCancel(bg)
diff --git a/go/analysis/passes/nilfunc/testdata/src/typeparams/typeparams.go b/go/analysis/passes/nilfunc/testdata/src/typeparams/typeparams.go
index 6346609..7aa0ab6 100644
--- a/go/analysis/passes/nilfunc/testdata/src/typeparams/typeparams.go
+++ b/go/analysis/passes/nilfunc/testdata/src/typeparams/typeparams.go
@@ -4,8 +4,6 @@
 
 // This file contains tests for the lostcancel checker.
 
-//go:build go1.18
-
 package typeparams
 
 func f[P any]() {}
@@ -22,7 +20,7 @@
 	g func(P1) P2
 }
 
-func Comparison[P any](f2 func()T1[P]) {
+func Comparison[P any](f2 func() T1[P]) {
 	var t1 T1[P]
 	var t2 T2[P, int]
 	var fn func()
@@ -40,7 +38,7 @@
 	}
 }
 
-func Index[P any](a [](func()P)) {
+func Index[P any](a [](func() P)) {
 	if a[1] == nil {
 		// no error
 	}
@@ -49,4 +47,4 @@
 	if t1[1].f == nil || t2[0][1].g == nil {
 		// no error
 	}
-}
\ No newline at end of file
+}
diff --git a/go/analysis/passes/nilness/nilness_go117_test.go b/go/analysis/passes/nilness/nilness_go117_test.go
deleted file mode 100644
index 1501f4f..0000000
--- a/go/analysis/passes/nilness/nilness_go117_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.17
-// +build go1.17
-
-package nilness_test
-
-import (
-	"testing"
-
-	"golang.org/x/tools/go/analysis/analysistest"
-	"golang.org/x/tools/go/analysis/passes/nilness"
-)
-
-func TestNilnessGo117(t *testing.T) {
-	testdata := analysistest.TestData()
-	analysistest.Run(t, testdata, nilness.Analyzer, "b")
-}
diff --git a/go/analysis/passes/nilness/nilness_test.go b/go/analysis/passes/nilness/nilness_test.go
index d07a930..5c1e05b 100644
--- a/go/analysis/passes/nilness/nilness_test.go
+++ b/go/analysis/passes/nilness/nilness_test.go
@@ -16,6 +16,11 @@
 	analysistest.Run(t, testdata, nilness.Analyzer, "a")
 }
 
+func TestNilness(t *testing.T) {
+	testdata := analysistest.TestData()
+	analysistest.Run(t, testdata, nilness.Analyzer, "b")
+}
+
 func TestInstantiated(t *testing.T) {
 	testdata := analysistest.TestData()
 	analysistest.Run(t, testdata, nilness.Analyzer, "c")
diff --git a/go/analysis/passes/printf/printf_test.go b/go/analysis/passes/printf/printf_test.go
index e206a3f..853d861 100644
--- a/go/analysis/passes/printf/printf_test.go
+++ b/go/analysis/passes/printf/printf_test.go
@@ -9,12 +9,9 @@
 
 	"golang.org/x/tools/go/analysis/analysistest"
 	"golang.org/x/tools/go/analysis/passes/printf"
-	"golang.org/x/tools/internal/testenv"
 )
 
 func Test(t *testing.T) {
-	testenv.NeedsGo1Point(t, 19) // tests use fmt.Appendf
-
 	testdata := analysistest.TestData()
 	printf.Analyzer.Flags.Set("funcs", "Warn,Warnf")
 
diff --git a/go/analysis/passes/printf/testdata/src/typeparams/diagnostics.go b/go/analysis/passes/printf/testdata/src/typeparams/diagnostics.go
index c4d7e53..08bdb47 100644
--- a/go/analysis/passes/printf/testdata/src/typeparams/diagnostics.go
+++ b/go/analysis/passes/printf/testdata/src/typeparams/diagnostics.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package typeparams
 
 import "fmt"
diff --git a/go/analysis/passes/printf/testdata/src/typeparams/wrappers.go b/go/analysis/passes/printf/testdata/src/typeparams/wrappers.go
index df8a6fa..05487ab 100644
--- a/go/analysis/passes/printf/testdata/src/typeparams/wrappers.go
+++ b/go/analysis/passes/printf/testdata/src/typeparams/wrappers.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package typeparams
 
 import "fmt"
diff --git a/go/analysis/passes/stdmethods/testdata/src/a/b.go b/go/analysis/passes/stdmethods/testdata/src/a/b.go
index c0a16fb..9cf3994 100644
--- a/go/analysis/passes/stdmethods/testdata/src/a/b.go
+++ b/go/analysis/passes/stdmethods/testdata/src/a/b.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package a
 
 type H int
diff --git a/go/analysis/passes/testinggoroutine/util.go b/go/analysis/passes/testinggoroutine/util.go
index d156851..ad815f1 100644
--- a/go/analysis/passes/testinggoroutine/util.go
+++ b/go/analysis/passes/testinggoroutine/util.go
@@ -30,7 +30,7 @@
 				}
 			}
 		}
-		// TODO: once we only support go1.19+, set f = f.Origin() here.
+		// TODO: set f = f.Origin() here.
 		return fnDecls[f]
 	}
 }
diff --git a/go/analysis/passes/tests/testdata/src/a/go118_test.go b/go/analysis/passes/tests/testdata/src/a/go118_test.go
index e2bc3f3..a2ed9a4 100644
--- a/go/analysis/passes/tests/testdata/src/a/go118_test.go
+++ b/go/analysis/passes/tests/testdata/src/a/go118_test.go
@@ -1,6 +1,3 @@
-//go:build go1.18
-// +build go1.18
-
 package a
 
 import (
diff --git a/go/analysis/passes/unusedresult/testdata/src/typeparams/typeparams.go b/go/analysis/passes/unusedresult/testdata/src/typeparams/typeparams.go
index 04d0e30..0add516 100644
--- a/go/analysis/passes/unusedresult/testdata/src/typeparams/typeparams.go
+++ b/go/analysis/passes/unusedresult/testdata/src/typeparams/typeparams.go
@@ -1,8 +1,6 @@
 // Copyright 2015 The Go Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
-//
-//go:build go1.18
 
 package typeparams
 
diff --git a/go/analysis/passes/unusedresult/testdata/src/typeparams/userdefs/userdefs.go b/go/analysis/passes/unusedresult/testdata/src/typeparams/userdefs/userdefs.go
index 218cc9a..e31c625 100644
--- a/go/analysis/passes/unusedresult/testdata/src/typeparams/userdefs/userdefs.go
+++ b/go/analysis/passes/unusedresult/testdata/src/typeparams/userdefs/userdefs.go
@@ -1,8 +1,6 @@
 // Copyright 2015 The Go Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
-//
-//go:build go1.18
 
 package userdefs
 
@@ -25,4 +23,4 @@
 
 func (_ *MultiTypeParam[T, U]) String() string {
 	return "MultiTypeParam"
-}
\ No newline at end of file
+}
diff --git a/go/analysis/unitchecker/separate_test.go b/go/analysis/unitchecker/separate_test.go
index cf0143f..37e74e4 100644
--- a/go/analysis/unitchecker/separate_test.go
+++ b/go/analysis/unitchecker/separate_test.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-
 package unitchecker_test
 
 // This file illustrates separate analysis with an example.
diff --git a/go/analysis/unitchecker/unitchecker_test.go b/go/analysis/unitchecker/unitchecker_test.go
index 3611d35..54d8fa8 100644
--- a/go/analysis/unitchecker/unitchecker_test.go
+++ b/go/analysis/unitchecker/unitchecker_test.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-
 package unitchecker_test
 
 import (
diff --git a/go/callgraph/rta/rta.go b/go/callgraph/rta/rta.go
index 3c8dc41..cd3afa0 100644
--- a/go/callgraph/rta/rta.go
+++ b/go/callgraph/rta/rta.go
@@ -46,7 +46,6 @@
 	"golang.org/x/tools/go/ssa"
 	"golang.org/x/tools/go/types/typeutil"
 	"golang.org/x/tools/internal/aliases"
-	"golang.org/x/tools/internal/compat"
 )
 
 // A Result holds the results of Rapid Type Analysis, which includes the
@@ -546,7 +545,7 @@
 	for i := 0; i < mset.Len(); i++ {
 		method := mset.At(i).Obj()
 		sig := method.Type().(*types.Signature)
-		sum := crc32.ChecksumIEEE(compat.Appendf(space[:], "%s/%d/%d",
+		sum := crc32.ChecksumIEEE(fmt.Appendf(space[:], "%s/%d/%d",
 			method.Id(),
 			sig.Params().Len(),
 			sig.Results().Len()))
diff --git a/go/callgraph/vta/vta_go117_test.go b/go/callgraph/vta/vta_go117_test.go
deleted file mode 100644
index 6a5af2c..0000000
--- a/go/callgraph/vta/vta_go117_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.17
-// +build go1.17
-
-package vta
-
-import (
-	"testing"
-
-	"golang.org/x/tools/go/callgraph/cha"
-	"golang.org/x/tools/go/ssa"
-	"golang.org/x/tools/go/ssa/ssautil"
-)
-
-func TestVTACallGraphGo117(t *testing.T) {
-	file := "testdata/src/go117.go"
-	prog, want, err := testProg(file, ssa.BuilderMode(0))
-	if err != nil {
-		t.Fatalf("couldn't load test file '%s': %s", file, err)
-	}
-	if len(want) == 0 {
-		t.Fatalf("couldn't find want in `%s`", file)
-	}
-
-	g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
-	got := vtaGraphStr(g)
-	if diff := setdiff(want, got); len(diff) != 0 {
-		t.Errorf("`%s`: want superset of %v;\n got %v", file, want, got)
-	}
-}
diff --git a/go/callgraph/vta/vta_test.go b/go/callgraph/vta/vta_test.go
index 76c6611..76bd85e 100644
--- a/go/callgraph/vta/vta_test.go
+++ b/go/callgraph/vta/vta_test.go
@@ -144,3 +144,20 @@
 		})
 	}
 }
+
+func TestVTACallGraphGo117(t *testing.T) {
+	file := "testdata/src/go117.go"
+	prog, want, err := testProg(file, ssa.BuilderMode(0))
+	if err != nil {
+		t.Fatalf("couldn't load test file '%s': %s", file, err)
+	}
+	if len(want) == 0 {
+		t.Fatalf("couldn't find want in `%s`", file)
+	}
+
+	g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog))
+	got := vtaGraphStr(g)
+	if diff := setdiff(want, got); len(diff) != 0 {
+		t.Errorf("`%s`: want superset of %v;\n got %v", file, want, got)
+	}
+}
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index e5687ba..f522df1 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -2444,9 +2444,6 @@
 	// causes C++ sources to be inadvertently included in
 	// (*Package).CompiledGoFiles.
 
-	// This is fixed in Go 1.17, but not earlier.
-	testenv.NeedsGo1Point(t, 17)
-
 	if _, err := exec.LookPath("swig"); err != nil {
 		t.Skip("skipping test: swig not available")
 	}
diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go
index 680358c..829ddc7 100644
--- a/go/ssa/builder_test.go
+++ b/go/ssa/builder_test.go
@@ -822,8 +822,6 @@
 // TestTypeparamTest builds SSA over compilable examples in $GOROOT/test/typeparam/*.go.
 
 func TestTypeparamTest(t *testing.T) {
-	testenv.NeedsGo1Point(t, 19) // fails with infinite recursion at 1.18 -- not investigated
-
 	// Tests use a fake goroot to stub out standard libraries with delcarations in
 	// testdata/src. Decreases runtime from ~80s to ~1s.
 
@@ -1000,7 +998,6 @@
 
 func TestIssue58491(t *testing.T) {
 	// Test that a local type reaches type param in instantiation.
-	testenv.NeedsGo1Point(t, 18)
 	src := `
 		package p
 
@@ -1058,7 +1055,6 @@
 
 func TestIssue58491Rec(t *testing.T) {
 	// Roughly the same as TestIssue58491 but with a recursive type.
-	testenv.NeedsGo1Point(t, 18)
 	src := `
 		package p
 
diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go
index 6f8e21a..2cd7ee9 100644
--- a/go/ssa/interp/interp_test.go
+++ b/go/ssa/interp/interp_test.go
@@ -2,10 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// This test fails at Go 1.18 due to infinite recursion in go/types.
-
-//go:build go1.19
-
 package interp_test
 
 // This test runs the SSA interpreter over sample Go programs.
diff --git a/go/types/internal/play/play.go b/go/types/internal/play/play.go
index 382d8ab..845e26c 100644
--- a/go/types/internal/play/play.go
+++ b/go/types/internal/play/play.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-
 // The play program is a playground for go/types: a simple web-based
 // text editor into which the user can enter a Go program, select a
 // region, and see type information about it.
diff --git a/go/types/objectpath/objectpath_go118_test.go b/go/types/objectpath/objectpath_go118_test.go
index bc156e1..f061fd8 100644
--- a/go/types/objectpath/objectpath_go118_test.go
+++ b/go/types/objectpath/objectpath_go118_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package objectpath_test
 
 import (
diff --git a/godoc/godoc.go b/godoc/godoc.go
index dfac211..a9d806f 100644
--- a/godoc/godoc.go
+++ b/godoc/godoc.go
@@ -346,16 +346,9 @@
 }
 
 func comment_htmlFunc(info *PageInfo, comment string) string {
-	var buf bytes.Buffer
 	// TODO(gri) Provide list of words (e.g. function parameters)
 	//           to be emphasized by ToHTML.
-
-	// godocToHTML is:
-	// - buf.Write(info.PDoc.HTML(comment)) on go1.19
-	// - go/doc.ToHTML(&buf, comment, nil) on other versions
-	godocToHTML(&buf, info.PDoc, comment)
-
-	return buf.String()
+	return string(info.PDoc.HTML(comment))
 }
 
 // sanitizeFunc sanitizes the argument src by replacing newlines with
diff --git a/godoc/tohtml_go119.go b/godoc/tohtml_go119.go
deleted file mode 100644
index 6dbf721..0000000
--- a/godoc/tohtml_go119.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-// +build go1.19
-
-package godoc
-
-import (
-	"bytes"
-	"go/doc"
-)
-
-func godocToHTML(buf *bytes.Buffer, pkg *doc.Package, comment string) {
-	buf.Write(pkg.HTML(comment))
-}
diff --git a/godoc/tohtml_other.go b/godoc/tohtml_other.go
deleted file mode 100644
index a1dcf2e..0000000
--- a/godoc/tohtml_other.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.19
-// +build !go1.19
-
-package godoc
-
-import (
-	"bytes"
-	"go/doc"
-)
-
-func godocToHTML(buf *bytes.Buffer, pkg *doc.Package, comment string) {
-	doc.ToHTML(buf, comment, nil)
-}
diff --git a/gopls/api-diff/api_diff.go b/gopls/api-diff/api_diff.go
index dfa1cb6..7194ced 100644
--- a/gopls/api-diff/api_diff.go
+++ b/gopls/api-diff/api_diff.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package main
 
 import (
diff --git a/gopls/go.mod b/gopls/go.mod
index fdf5998..3262b0a 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -1,6 +1,6 @@
 module golang.org/x/tools/gopls
 
-go 1.18
+go 1.19
 
 require (
 	github.com/google/go-cmp v0.6.0
diff --git a/gopls/internal/analysis/deprecated/deprecated_test.go b/gopls/internal/analysis/deprecated/deprecated_test.go
index 0242ef1..89bf3be 100644
--- a/gopls/internal/analysis/deprecated/deprecated_test.go
+++ b/gopls/internal/analysis/deprecated/deprecated_test.go
@@ -8,11 +8,9 @@
 	"testing"
 
 	"golang.org/x/tools/go/analysis/analysistest"
-	"golang.org/x/tools/internal/testenv"
 )
 
 func Test(t *testing.T) {
-	testenv.NeedsGo1Point(t, 19)
 	testdata := analysistest.TestData()
 	analysistest.Run(t, testdata, Analyzer, "a")
 }
diff --git a/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go b/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go
index 69db310..a1a29d4 100644
--- a/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go
+++ b/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go
@@ -1,9 +1,6 @@
 // Copyright 2021 The Go Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
-//
-//go:build go1.18
-// +build go1.18
 
 package testdata
 
diff --git a/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden b/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden
index 99ca9e4..ce425b7 100644
--- a/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden
+++ b/gopls/internal/analysis/simplifyslice/testdata/src/typeparams/typeparams.go.golden
@@ -1,9 +1,6 @@
 // Copyright 2021 The Go Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
-//
-//go:build go1.18
-// +build go1.18
 
 package testdata
 
diff --git a/gopls/internal/cache/port_test.go b/gopls/internal/cache/port_test.go
index 3c38a11..a92056a 100644
--- a/gopls/internal/cache/port_test.go
+++ b/gopls/internal/cache/port_test.go
@@ -94,10 +94,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !windows && !plan9
-// +build !windows,!plan9
-
-// TODO(adonovan): use 'unix' tag when go1.19 can be assumed.
+//go:build unix
+// +build unix
 
 package robustio
 
diff --git a/gopls/internal/cmd/serve.go b/gopls/internal/cmd/serve.go
index a2f9be9..3b79ccb 100644
--- a/gopls/internal/cmd/serve.go
+++ b/gopls/internal/cmd/serve.go
@@ -14,11 +14,11 @@
 	"os"
 	"time"
 
+	"golang.org/x/telemetry/upload"
 	"golang.org/x/tools/gopls/internal/cache"
 	"golang.org/x/tools/gopls/internal/debug"
 	"golang.org/x/tools/gopls/internal/lsprpc"
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/telemetry"
 	"golang.org/x/tools/internal/fakenet"
 	"golang.org/x/tools/internal/jsonrpc2"
 	"golang.org/x/tools/internal/tool"
@@ -78,7 +78,8 @@
 // Run configures a server based on the flags, and then runs it.
 // It blocks until the server shuts down.
 func (s *Serve) Run(ctx context.Context, args ...string) error {
-	telemetry.Upload()
+	// TODO(adonovan): eliminate this once telemetry.Start has this effect.
+	go upload.Run(nil) // start telemetry uploader
 
 	if len(args) > 0 {
 		return tool.CommandLineErrorf("server does not take arguments, got %v", args)
diff --git a/gopls/internal/golang/comment.go b/gopls/internal/golang/comment.go
index ece1238..95f0df9 100644
--- a/gopls/internal/golang/comment.go
+++ b/gopls/internal/golang/comment.go
@@ -1,19 +1,12 @@
-// Copyright 2019 The Go Authors. All rights reserved.
+// Copyright 2022 The Go Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !go1.19
-// +build !go1.19
-
 package golang
 
 import (
-	"bytes"
-	"io"
-	"regexp"
-	"strings"
-	"unicode"
-	"unicode/utf8"
+	"fmt"
+	"go/doc/comment"
 
 	"golang.org/x/tools/gopls/internal/settings"
 )
@@ -23,364 +16,26 @@
 // so it is known not to have leading, trailing blank lines
 // nor to have trailing spaces at the end of lines.
 // The comment markers have already been removed.
-//
-// Each line is converted into a markdown line and empty lines are just converted to
-// newlines. Heading are prefixed with `### ` to make it a markdown heading.
-//
-// A span of indented lines retains a 4 space prefix block, with the common indent
-// prefix removed unless empty, in which case it will be converted to a newline.
-//
-// URLs in the comment text are converted into links.
-func CommentToMarkdown(text string, _ *settings.Options) string {
-	buf := &bytes.Buffer{}
-	commentToMarkdown(buf, text)
-	return buf.String()
-}
-
-var (
-	mdNewline   = []byte("\n")
-	mdHeader    = []byte("### ")
-	mdIndent    = []byte("    ")
-	mdLinkStart = []byte("[")
-	mdLinkDiv   = []byte("](")
-	mdLinkEnd   = []byte(")")
-)
-
-func commentToMarkdown(w io.Writer, text string) {
-	blocks := blocks(text)
-	for i, b := range blocks {
-		switch b.op {
-		case opPara:
-			for _, line := range b.lines {
-				emphasize(w, line, true)
+func CommentToMarkdown(text string, options *settings.Options) string {
+	var p comment.Parser
+	doc := p.Parse(text)
+	var pr comment.Printer
+	// The default produces {#Hdr-...} tags for headings.
+	// vscode displays thems, which is undesirable.
+	// The godoc for comment.Printer says the tags
+	// avoid a security problem.
+	pr.HeadingID = func(*comment.Heading) string { return "" }
+	pr.DocLinkURL = func(link *comment.DocLink) string {
+		msg := fmt.Sprintf("https://%s/%s", options.LinkTarget, link.ImportPath)
+		if link.Name != "" {
+			msg += "#"
+			if link.Recv != "" {
+				msg += link.Recv + "."
 			}
-		case opHead:
-			// The header block can consist of only one line.
-			// However, check the number of lines, just in case.
-			if len(b.lines) == 0 {
-				// Skip this block.
-				continue
-			}
-			header := b.lines[0]
-
-			w.Write(mdHeader)
-			commentEscape(w, header, true)
-			// Header doesn't end with \n unlike the lines of other blocks.
-			w.Write(mdNewline)
-		case opPre:
-			for _, line := range b.lines {
-				if isBlank(line) {
-					w.Write(mdNewline)
-					continue
-				}
-				w.Write(mdIndent)
-				w.Write([]byte(line))
-			}
+			msg += link.Name
 		}
-
-		if i < len(blocks)-1 {
-			w.Write(mdNewline)
-		}
+		return msg
 	}
-}
-
-const (
-	ulquo = "“"
-	urquo = "”"
-)
-
-var (
-	markdownEscape = regexp.MustCompile(`([\\\x60*{}[\]()#+\-.!_>~|"$%&'\/:;<=?@^])`)
-
-	unicodeQuoteReplacer = strings.NewReplacer("``", ulquo, "''", urquo)
-)
-
-// commentEscape escapes comment text for markdown. If nice is set,
-// also turn double ` and ' into “ and ”.
-func commentEscape(w io.Writer, text string, nice bool) {
-	if nice {
-		text = convertQuotes(text)
-	}
-	text = escapeRegex(text)
-	w.Write([]byte(text))
-}
-
-func convertQuotes(text string) string {
-	return unicodeQuoteReplacer.Replace(text)
-}
-
-func escapeRegex(text string) string {
-	return markdownEscape.ReplaceAllString(text, `\$1`)
-}
-
-func emphasize(w io.Writer, line string, nice bool) {
-	for {
-		m := matchRx.FindStringSubmatchIndex(line)
-		if m == nil {
-			break
-		}
-		// m >= 6 (two parenthesized sub-regexps in matchRx, 1st one is urlRx)
-
-		// write text before match
-		commentEscape(w, line[0:m[0]], nice)
-
-		// adjust match for URLs
-		match := line[m[0]:m[1]]
-		if strings.Contains(match, "://") {
-			m0, m1 := m[0], m[1]
-			for _, s := range []string{"()", "{}", "[]"} {
-				open, close := s[:1], s[1:] // E.g., "(" and ")"
-				// require opening parentheses before closing parentheses (#22285)
-				if i := strings.Index(match, close); i >= 0 && i < strings.Index(match, open) {
-					m1 = m0 + i
-					match = line[m0:m1]
-				}
-				// require balanced pairs of parentheses (#5043)
-				for i := 0; strings.Count(match, open) != strings.Count(match, close) && i < 10; i++ {
-					m1 = strings.LastIndexAny(line[:m1], s)
-					match = line[m0:m1]
-				}
-			}
-			if m1 != m[1] {
-				// redo matching with shortened line for correct indices
-				m = matchRx.FindStringSubmatchIndex(line[:m[0]+len(match)])
-			}
-		}
-
-		// Following code has been modified from go/doc since words is always
-		// nil. All html formatting has also been transformed into markdown formatting
-
-		// analyze match
-		url := ""
-		if m[2] >= 0 {
-			url = match
-		}
-
-		// write match
-		if len(url) > 0 {
-			w.Write(mdLinkStart)
-		}
-
-		commentEscape(w, match, nice)
-
-		if len(url) > 0 {
-			w.Write(mdLinkDiv)
-			w.Write([]byte(urlReplacer.Replace(url)))
-			w.Write(mdLinkEnd)
-		}
-
-		// advance
-		line = line[m[1]:]
-	}
-	commentEscape(w, line, nice)
-}
-
-// Everything from here on is a copy of go/doc/comment.go
-
-const (
-	// Regexp for Go identifiers
-	identRx = `[\pL_][\pL_0-9]*`
-
-	// Regexp for URLs
-	// Match parens, and check later for balance - see #5043, #22285
-	// Match .,:;?! within path, but not at end - see #18139, #16565
-	// This excludes some rare yet valid urls ending in common punctuation
-	// in order to allow sentences ending in URLs.
-
-	// protocol (required) e.g. http
-	protoPart = `(https?|ftp|file|gopher|mailto|nntp)`
-	// host (required) e.g. www.example.com or [::1]:8080
-	hostPart = `([a-zA-Z0-9_@\-.\[\]:]+)`
-	// path+query+fragment (optional) e.g. /path/index.html?q=foo#bar
-	pathPart = `([.,:;?!]*[a-zA-Z0-9$'()*+&#=@~_/\-\[\]%])*`
-
-	urlRx = protoPart + `://` + hostPart + pathPart
-)
-
-var (
-	matchRx     = regexp.MustCompile(`(` + urlRx + `)|(` + identRx + `)`)
-	urlReplacer = strings.NewReplacer(`(`, `\(`, `)`, `\)`)
-)
-
-func indentLen(s string) int {
-	i := 0
-	for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
-		i++
-	}
-	return i
-}
-
-func isBlank(s string) bool {
-	return len(s) == 0 || (len(s) == 1 && s[0] == '\n')
-}
-
-func commonPrefix(a, b string) string {
-	i := 0
-	for i < len(a) && i < len(b) && a[i] == b[i] {
-		i++
-	}
-	return a[0:i]
-}
-
-func unindent(block []string) {
-	if len(block) == 0 {
-		return
-	}
-
-	// compute maximum common white prefix
-	prefix := block[0][0:indentLen(block[0])]
-	for _, line := range block {
-		if !isBlank(line) {
-			prefix = commonPrefix(prefix, line)
-		}
-	}
-	n := len(prefix)
-
-	// remove
-	for i, line := range block {
-		if !isBlank(line) {
-			block[i] = line[n:]
-		}
-	}
-}
-
-// heading returns the trimmed line if it passes as a section heading;
-// otherwise it returns the empty string.
-func heading(line string) string {
-	line = strings.TrimSpace(line)
-	if len(line) == 0 {
-		return ""
-	}
-
-	// a heading must start with an uppercase letter
-	r, _ := utf8.DecodeRuneInString(line)
-	if !unicode.IsLetter(r) || !unicode.IsUpper(r) {
-		return ""
-	}
-
-	// it must end in a letter or digit:
-	r, _ = utf8.DecodeLastRuneInString(line)
-	if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
-		return ""
-	}
-
-	// exclude lines with illegal characters. we allow "(),"
-	if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") {
-		return ""
-	}
-
-	// allow "'" for possessive "'s" only
-	for b := line; ; {
-		i := strings.IndexRune(b, '\'')
-		if i < 0 {
-			break
-		}
-		if i+1 >= len(b) || b[i+1] != 's' || (i+2 < len(b) && b[i+2] != ' ') {
-			return "" // not followed by "s "
-		}
-		b = b[i+2:]
-	}
-
-	// allow "." when followed by non-space
-	for b := line; ; {
-		i := strings.IndexRune(b, '.')
-		if i < 0 {
-			break
-		}
-		if i+1 >= len(b) || b[i+1] == ' ' {
-			return "" // not followed by non-space
-		}
-		b = b[i+1:]
-	}
-
-	return line
-}
-
-type op int
-
-const (
-	opPara op = iota
-	opHead
-	opPre
-)
-
-type block struct {
-	op    op
-	lines []string
-}
-
-func blocks(text string) []block {
-	var (
-		out  []block
-		para []string
-
-		lastWasBlank   = false
-		lastWasHeading = false
-	)
-
-	close := func() {
-		if para != nil {
-			out = append(out, block{opPara, para})
-			para = nil
-		}
-	}
-
-	lines := strings.SplitAfter(text, "\n")
-	unindent(lines)
-	for i := 0; i < len(lines); {
-		line := lines[i]
-		if isBlank(line) {
-			// close paragraph
-			close()
-			i++
-			lastWasBlank = true
-			continue
-		}
-		if indentLen(line) > 0 {
-			// close paragraph
-			close()
-
-			// count indented or blank lines
-			j := i + 1
-			for j < len(lines) && (isBlank(lines[j]) || indentLen(lines[j]) > 0) {
-				j++
-			}
-			// but not trailing blank lines
-			for j > i && isBlank(lines[j-1]) {
-				j--
-			}
-			pre := lines[i:j]
-			i = j
-
-			unindent(pre)
-
-			// put those lines in a pre block
-			out = append(out, block{opPre, pre})
-			lastWasHeading = false
-			continue
-		}
-
-		if lastWasBlank && !lastWasHeading && i+2 < len(lines) &&
-			isBlank(lines[i+1]) && !isBlank(lines[i+2]) && indentLen(lines[i+2]) == 0 {
-			// current line is non-blank, surrounded by blank lines
-			// and the next non-blank line is not indented: this
-			// might be a heading.
-			if head := heading(line); head != "" {
-				close()
-				out = append(out, block{opHead, []string{head}})
-				i += 2
-				lastWasHeading = true
-				continue
-			}
-		}
-
-		// open paragraph
-		lastWasBlank = false
-		lastWasHeading = false
-		para = append(para, lines[i])
-		i++
-	}
-	close()
-
-	return out
+	easy := pr.Markdown(doc)
+	return string(easy)
 }
diff --git a/gopls/internal/golang/comment_go118_test.go b/gopls/internal/golang/comment_go118_test.go
deleted file mode 100644
index c38898a..0000000
--- a/gopls/internal/golang/comment_go118_test.go
+++ /dev/null
@@ -1,371 +0,0 @@
-// Copyright 2019 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.19
-// +build !go1.19
-
-package golang
-
-import (
-	"bytes"
-	"reflect"
-	"strings"
-	"testing"
-)
-
-// This file is a copy of go/doc/comment_test.go with the exception for
-// the test cases for TestEmphasize and TestCommentEscape
-
-var headingTests = []struct {
-	line string
-	ok   bool
-}{
-	{"Section", true},
-	{"A typical usage", true},
-	{"ΔΛΞ is Greek", true},
-	{"Foo 42", true},
-	{"", false},
-	{"section", false},
-	{"A typical usage:", false},
-	{"This code:", false},
-	{"δ is Greek", false},
-	{"Foo §", false},
-	{"Fermat's Last Sentence", true},
-	{"Fermat's", true},
-	{"'sX", false},
-	{"Ted 'Too' Bar", false},
-	{"Use n+m", false},
-	{"Scanning:", false},
-	{"N:M", false},
-}
-
-func TestIsHeading(t *testing.T) {
-	for _, tt := range headingTests {
-		if h := heading(tt.line); (len(h) > 0) != tt.ok {
-			t.Errorf("isHeading(%q) = %v, want %v", tt.line, h, tt.ok)
-		}
-	}
-}
-
-var blocksTests = []struct {
-	in   string
-	out  []block
-	text string
-}{
-	{
-		in: `Para 1.
-Para 1 line 2.
-
-Para 2.
-
-Section
-
-Para 3.
-
-	pre
-	pre1
-
-Para 4.
-
-	pre
-	pre1
-
-	pre2
-
-Para 5.
-
-
-	pre
-
-
-	pre1
-	pre2
-
-Para 6.
-	pre
-	pre2
-`,
-		out: []block{
-			{opPara, []string{"Para 1.\n", "Para 1 line 2.\n"}},
-			{opPara, []string{"Para 2.\n"}},
-			{opHead, []string{"Section"}},
-			{opPara, []string{"Para 3.\n"}},
-			{opPre, []string{"pre\n", "pre1\n"}},
-			{opPara, []string{"Para 4.\n"}},
-			{opPre, []string{"pre\n", "pre1\n", "\n", "pre2\n"}},
-			{opPara, []string{"Para 5.\n"}},
-			{opPre, []string{"pre\n", "\n", "\n", "pre1\n", "pre2\n"}},
-			{opPara, []string{"Para 6.\n"}},
-			{opPre, []string{"pre\n", "pre2\n"}},
-		},
-		text: `.   Para 1. Para 1 line 2.
-
-.   Para 2.
-
-
-.   Section
-
-.   Para 3.
-
-$	pre
-$	pre1
-
-.   Para 4.
-
-$	pre
-$	pre1
-
-$	pre2
-
-.   Para 5.
-
-$	pre
-
-
-$	pre1
-$	pre2
-
-.   Para 6.
-
-$	pre
-$	pre2
-`,
-	},
-	{
-		in: "Para.\n\tshould not be ``escaped''",
-		out: []block{
-			{opPara, []string{"Para.\n"}},
-			{opPre, []string{"should not be ``escaped''"}},
-		},
-		text: ".   Para.\n\n$	should not be ``escaped''",
-	},
-	{
-		in: "// A very long line of 46 char for line wrapping.",
-		out: []block{
-			{opPara, []string{"// A very long line of 46 char for line wrapping."}},
-		},
-		text: `.   // A very long line of 46 char for line
-.   // wrapping.
-`,
-	},
-	{
-		in: `/* A very long line of 46 char for line wrapping.
-A very long line of 46 char for line wrapping. */`,
-		out: []block{
-			{opPara, []string{"/* A very long line of 46 char for line wrapping.\n", "A very long line of 46 char for line wrapping. */"}},
-		},
-		text: `.   /* A very long line of 46 char for line
-.   wrapping. A very long line of 46 char
-.   for line wrapping. */
-`,
-	},
-}
-
-func TestBlocks(t *testing.T) {
-	for i, tt := range blocksTests {
-		b := blocks(tt.in)
-		if !reflect.DeepEqual(b, tt.out) {
-			t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, b, tt.out)
-		}
-	}
-}
-
-// This has been modified from go/doc to use markdown links instead of html ones
-// and use markdown escaping instead oh html
-var emphasizeTests = []struct {
-	in, out string
-}{
-	{"", ""},
-	{"http://[::1]:8080/foo.txt", `[http\:\/\/\[\:\:1\]\:8080\/foo\.txt](http://[::1]:8080/foo.txt)`},
-	{"before (https://www.google.com) after", `before \([https\:\/\/www\.google\.com](https://www.google.com)\) after`},
-	{"before https://www.google.com:30/x/y/z:b::c. After", `before [https\:\/\/www\.google\.com\:30\/x\/y\/z\:b\:\:c](https://www.google.com:30/x/y/z:b::c)\. After`},
-	{"http://www.google.com/path/:;!-/?query=%34b#093124", `[http\:\/\/www\.google\.com\/path\/\:\;\!\-\/\?query\=\%34b\#093124](http://www.google.com/path/:;!-/?query=%34b#093124)`},
-	{"http://www.google.com/path/:;!-/?query=%34bar#093124", `[http\:\/\/www\.google\.com\/path\/\:\;\!\-\/\?query\=\%34bar\#093124](http://www.google.com/path/:;!-/?query=%34bar#093124)`},
-	{"http://www.google.com/index.html! After", `[http\:\/\/www\.google\.com\/index\.html](http://www.google.com/index.html)\! After`},
-	{"http://www.google.com/", `[http\:\/\/www\.google\.com\/](http://www.google.com/)`},
-	{"https://www.google.com/", `[https\:\/\/www\.google\.com\/](https://www.google.com/)`},
-	{"http://www.google.com/path.", `[http\:\/\/www\.google\.com\/path](http://www.google.com/path)\.`},
-	{"http://en.wikipedia.org/wiki/Camellia_(cipher)", `[http\:\/\/en\.wikipedia\.org\/wiki\/Camellia\_\(cipher\)](http://en.wikipedia.org/wiki/Camellia_\(cipher\))`},
-	{"(http://www.google.com/)", `\([http\:\/\/www\.google\.com\/](http://www.google.com/)\)`},
-	{"http://gmail.com)", `[http\:\/\/gmail\.com](http://gmail.com)\)`},
-	{"((http://gmail.com))", `\(\([http\:\/\/gmail\.com](http://gmail.com)\)\)`},
-	{"http://gmail.com ((http://gmail.com)) ()", `[http\:\/\/gmail\.com](http://gmail.com) \(\([http\:\/\/gmail\.com](http://gmail.com)\)\) \(\)`},
-	{"Foo bar http://example.com/ quux!", `Foo bar [http\:\/\/example\.com\/](http://example.com/) quux\!`},
-	{"Hello http://example.com/%2f/ /world.", `Hello [http\:\/\/example\.com\/\%2f\/](http://example.com/%2f/) \/world\.`},
-	{"Lorem http: ipsum //host/path", `Lorem http\: ipsum \/\/host\/path`},
-	{"javascript://is/not/linked", `javascript\:\/\/is\/not\/linked`},
-	{"http://foo", `[http\:\/\/foo](http://foo)`},
-	{"art by [[https://www.example.com/person/][Person Name]]", `art by \[\[[https\:\/\/www\.example\.com\/person\/](https://www.example.com/person/)\]\[Person Name\]\]`},
-	{"please visit (http://golang.org/)", `please visit \([http\:\/\/golang\.org\/](http://golang.org/)\)`},
-	{"please visit http://golang.org/hello())", `please visit [http\:\/\/golang\.org\/hello\(\)](http://golang.org/hello\(\))\)`},
-	{"http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD", `[http\:\/\/git\.qemu\.org\/\?p\=qemu\.git\;a\=blob\;f\=qapi\-schema\.json\;hb\=HEAD](http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD)`},
-	{"https://foo.bar/bal/x(])", `[https\:\/\/foo\.bar\/bal\/x\(](https://foo.bar/bal/x\()\]\)`},
-	{"foo [ http://bar(])", `foo \[ [http\:\/\/bar\(](http://bar\()\]\)`},
-}
-
-func TestEmphasize(t *testing.T) {
-	for i, tt := range emphasizeTests {
-		var buf bytes.Buffer
-		emphasize(&buf, tt.in, true)
-		out := buf.String()
-		if out != tt.out {
-			t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, out, tt.out)
-		}
-	}
-}
-
-func TestCommentEscape(t *testing.T) {
-	//ldquo -> ulquo and rdquo -> urquo
-	commentTests := []struct {
-		in, out string
-	}{
-		{"typically invoked as ``go tool asm'',", "typically invoked as " + ulquo + "go tool asm" + urquo + ","},
-		{"For more detail, run ``go help test'' and ``go help testflag''", "For more detail, run " + ulquo + "go help test" + urquo + " and " + ulquo + "go help testflag" + urquo}}
-	for i, tt := range commentTests {
-		var buf strings.Builder
-		commentEscape(&buf, tt.in, true)
-		out := buf.String()
-		if out != tt.out {
-			t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out)
-		}
-	}
-}
-
-func TestCommentToMarkdown(t *testing.T) {
-	tests := []struct {
-		in, out string
-	}{
-		{
-			in:  "F declaration.\n",
-			out: "F declaration\\.\n",
-		},
-		{
-			in: `
-F declaration. Lorem ipsum dolor sit amet.
-Etiam mattis eros at orci mollis molestie.
-`,
-			out: `
-F declaration\. Lorem ipsum dolor sit amet\.
-Etiam mattis eros at orci mollis molestie\.
-`,
-		},
-		{
-			in: `
-F declaration.
-
-Lorem ipsum dolor sit amet.
-Sed id dui turpis.
-
-
-
-
-Aenean tempus velit non auctor eleifend.
-Aenean efficitur a sem id ultricies.
-
-
-Phasellus efficitur mauris et viverra bibendum.
-`,
-			out: `
-F declaration\.
-
-Lorem ipsum dolor sit amet\.
-Sed id dui turpis\.
-
-Aenean tempus velit non auctor eleifend\.
-Aenean efficitur a sem id ultricies\.
-
-Phasellus efficitur mauris et viverra bibendum\.
-`,
-		},
-		{
-			in: `
-F declaration.
-
-Aenean tempus velit non auctor eleifend.
-
-Section
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-
-  func foo() {}
-
-
-  func bar() {}
-
-Fusce lorem lacus.
-
-    func foo() {}
-
-    func bar() {}
-
-Maecenas in lobortis lectus.
-
-	func foo() {}
-
-	func bar() {}
-
-Phasellus efficitur mauris et viverra bibendum.
-`,
-			out: `
-F declaration\.
-
-Aenean tempus velit non auctor eleifend\.
-
-### Section
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit\.
-
-    func foo() {}
-
-
-    func bar() {}
-
-Fusce lorem lacus\.
-
-    func foo() {}
-
-    func bar() {}
-
-Maecenas in lobortis lectus\.
-
-    func foo() {}
-
-    func bar() {}
-
-Phasellus efficitur mauris et viverra bibendum\.
-`,
-		},
-		{
-			in: `
-F declaration.
-
-	func foo() {
-		fmt.Println("foo")
-	}
-	func bar() {
-		fmt.Println("bar")
-	}
-`,
-			out: `
-F declaration\.
-
-    func foo() {
-    	fmt.Println("foo")
-    }
-    func bar() {
-    	fmt.Println("bar")
-    }
-`,
-		},
-	}
-	for i, tt := range tests {
-		// Comments start with new lines for better readability. So, we should trim them.
-		tt.in = strings.TrimPrefix(tt.in, "\n")
-		tt.out = strings.TrimPrefix(tt.out, "\n")
-
-		if out := CommentToMarkdown(tt.in, nil); out != tt.out {
-			t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out)
-		}
-	}
-}
diff --git a/gopls/internal/golang/comment_go119.go b/gopls/internal/golang/comment_go119.go
deleted file mode 100644
index eec338d..0000000
--- a/gopls/internal/golang/comment_go119.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-// +build go1.19
-
-package golang
-
-// Starting with go1.19, the formatting of comments has changed, and there
-// is a new package (go/doc/comment) for processing them.
-// As long as gopls has to compile under earlier versions, tests
-// have to pass with both the old and new code, which produce
-// slightly different results.
-
-// When gopls no longer needs to compile with go1.18, the old comment.go should
-// be replaced by this file, the golden test files should be updated.
-// (and checkSameMarkdown() could be replaced by a simple comparison.)
-
-import (
-	"fmt"
-	"go/doc/comment"
-
-	"golang.org/x/tools/gopls/internal/settings"
-)
-
-// CommentToMarkdown converts comment text to formatted markdown.
-// The comment was prepared by DocReader,
-// so it is known not to have leading, trailing blank lines
-// nor to have trailing spaces at the end of lines.
-// The comment markers have already been removed.
-func CommentToMarkdown(text string, options *settings.Options) string {
-	var p comment.Parser
-	doc := p.Parse(text)
-	var pr comment.Printer
-	// The default produces {#Hdr-...} tags for headings.
-	// vscode displays thems, which is undesirable.
-	// The godoc for comment.Printer says the tags
-	// avoid a security problem.
-	pr.HeadingID = func(*comment.Heading) string { return "" }
-	pr.DocLinkURL = func(link *comment.DocLink) string {
-		msg := fmt.Sprintf("https://%s/%s", options.LinkTarget, link.ImportPath)
-		if link.Name != "" {
-			msg += "#"
-			if link.Recv != "" {
-				msg += link.Recv + "."
-			}
-			msg += link.Name
-		}
-		return msg
-	}
-	easy := pr.Markdown(doc)
-	return string(easy)
-}
diff --git a/gopls/internal/golang/origin.go b/gopls/internal/golang/origin.go
index c5e84db..aa77a9b 100644
--- a/gopls/internal/golang/origin.go
+++ b/gopls/internal/golang/origin.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !go1.19
-// +build !go1.19
-
 package golang
 
 import "go/types"
@@ -13,14 +10,21 @@
 // with the same origin as the provided obj (which may be a synthetic object
 // created during instantiation).
 func containsOrigin(objSet map[types.Object]bool, obj types.Object) bool {
-	if obj == nil {
-		return objSet[obj]
-	}
-	// In Go 1.18, we can't use the types.Var.Origin and types.Func.Origin methods.
+	objOrigin := origin(obj)
 	for target := range objSet {
-		if target.Pkg() == obj.Pkg() && target.Pos() == obj.Pos() && target.Name() == obj.Name() {
+		if origin(target) == objOrigin {
 			return true
 		}
 	}
 	return false
 }
+
+func origin(obj types.Object) types.Object {
+	switch obj := obj.(type) {
+	case *types.Var:
+		return obj.Origin()
+	case *types.Func:
+		return obj.Origin()
+	}
+	return obj
+}
diff --git a/gopls/internal/golang/origin_119.go b/gopls/internal/golang/origin_119.go
deleted file mode 100644
index 16f6ca7..0000000
--- a/gopls/internal/golang/origin_119.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-// +build go1.19
-
-package golang
-
-import "go/types"
-
-// containsOrigin reports whether the provided object set contains an object
-// with the same origin as the provided obj (which may be a synthetic object
-// created during instantiation).
-func containsOrigin(objSet map[types.Object]bool, obj types.Object) bool {
-	objOrigin := origin(obj)
-	for target := range objSet {
-		if origin(target) == objOrigin {
-			return true
-		}
-	}
-	return false
-}
-
-func origin(obj types.Object) types.Object {
-	switch obj := obj.(type) {
-	case *types.Var:
-		return obj.Origin()
-	case *types.Func:
-		return obj.Origin()
-	}
-	return obj
-}
diff --git a/gopls/internal/golang/rename.go b/gopls/internal/golang/rename.go
index 81114fc..8c40079 100644
--- a/gopls/internal/golang/rename.go
+++ b/gopls/internal/golang/rename.go
@@ -68,7 +68,6 @@
 	"golang.org/x/tools/gopls/internal/util/safetoken"
 	"golang.org/x/tools/internal/diff"
 	"golang.org/x/tools/internal/event"
-	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal"
 	"golang.org/x/tools/refactor/satisfy"
 )
@@ -347,8 +346,7 @@
 	var declObjPath objectpath.Path
 	if obj.Exported() {
 		// objectpath.For requires the origin of a generic function or type, not an
-		// instantiation (a bug?). Unfortunately we can't call Func.Origin as this
-		// is not available in go/types@go1.18. So we take a scenic route.
+		// instantiation (a bug?).
 		//
 		// Note that unlike Funcs, TypeNames are always canonical (they are "left"
 		// of the type parameters, unlike methods).
@@ -360,7 +358,7 @@
 				goto skipObjectPath
 			}
 		case *types.Func:
-			obj = funcOrigin(obj.(*types.Func))
+			obj = obj.(*types.Func).Origin()
 		case *types.Var:
 			// TODO(adonovan): do vars need the origin treatment too? (issue #58462)
 
@@ -451,23 +449,6 @@
 	return renameExported(pkgs, declPkgPath, declObjPath, newName)
 }
 
-// funcOrigin is a go1.18-portable implementation of (*types.Func).Origin.
-func funcOrigin(fn *types.Func) *types.Func {
-	// Method?
-	if fn.Type().(*types.Signature).Recv() != nil {
-		return typeparams.OriginMethod(fn)
-	}
-
-	// Package-level function?
-	// (Assume the origin has the same position.)
-	gen := fn.Pkg().Scope().Lookup(fn.Name())
-	if gen != nil && gen.Pos() == fn.Pos() {
-		return gen.(*types.Func)
-	}
-
-	return fn
-}
-
 // typeCheckReverseDependencies returns the type-checked packages for
 // the reverse dependencies of all packages variants containing
 // file declURI. The packages are in some topological order.
diff --git a/gopls/internal/protocol/generate/generate.go b/gopls/internal/protocol/generate/generate.go
index 0496b7d..7418918 100644
--- a/gopls/internal/protocol/generate/generate.go
+++ b/gopls/internal/protocol/generate/generate.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-// +build go1.19
-
 package main
 
 import (
diff --git a/gopls/internal/protocol/generate/main.go b/gopls/internal/protocol/generate/main.go
index f70c581..bdc2728 100644
--- a/gopls/internal/protocol/generate/main.go
+++ b/gopls/internal/protocol/generate/main.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-// +build go1.19
-
 // The generate command generates Go declarations from VSCode's
 // description of the Language Server Protocol.
 //
diff --git a/gopls/internal/protocol/generate/main_test.go b/gopls/internal/protocol/generate/main_test.go
index 5f33669..73c2204 100644
--- a/gopls/internal/protocol/generate/main_test.go
+++ b/gopls/internal/protocol/generate/main_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-// +build go1.19
-
 package main
 
 import (
diff --git a/gopls/internal/protocol/generate/output.go b/gopls/internal/protocol/generate/output.go
index fc64677..4760862 100644
--- a/gopls/internal/protocol/generate/output.go
+++ b/gopls/internal/protocol/generate/output.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-// +build go1.19
-
 package main
 
 import (
diff --git a/gopls/internal/protocol/generate/tables.go b/gopls/internal/protocol/generate/tables.go
index ac428b5..a9207bf 100644
--- a/gopls/internal/protocol/generate/tables.go
+++ b/gopls/internal/protocol/generate/tables.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-// +build go1.19
-
 package main
 
 import "log"
diff --git a/gopls/internal/protocol/generate/typenames.go b/gopls/internal/protocol/generate/typenames.go
index 8bacdd2..83f25a0 100644
--- a/gopls/internal/protocol/generate/typenames.go
+++ b/gopls/internal/protocol/generate/typenames.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-// +build go1.19
-
 package main
 
 import (
diff --git a/gopls/internal/protocol/generate/types.go b/gopls/internal/protocol/generate/types.go
index 0d01ae4..0537748 100644
--- a/gopls/internal/protocol/generate/types.go
+++ b/gopls/internal/protocol/generate/types.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.19
-// +build go1.19
-
 package main
 
 import (
diff --git a/gopls/internal/protocol/protocol.go b/gopls/internal/protocol/protocol.go
index 09bfaca..7cc5589 100644
--- a/gopls/internal/protocol/protocol.go
+++ b/gopls/internal/protocol/protocol.go
@@ -11,7 +11,7 @@
 	"fmt"
 	"io"
 
-	"golang.org/x/tools/gopls/internal/telemetry"
+	"golang.org/x/telemetry/crashmonitor"
 	"golang.org/x/tools/gopls/internal/util/bug"
 	"golang.org/x/tools/internal/event"
 	"golang.org/x/tools/internal/jsonrpc2"
@@ -302,7 +302,7 @@
 	// Report panics in the handler goroutine,
 	// unless we have enabled the monitor,
 	// which reports all crashes.
-	if !telemetry.CrashMonitorSupported() {
+	if !crashmonitor.Supported() {
 		defer func() {
 			if x := recover(); x != nil {
 				bug.Reportf("panic in %s request", method)
diff --git a/gopls/internal/server/command.go b/gopls/internal/server/command.go
index 19ea884..3f5d53c 100644
--- a/gopls/internal/server/command.go
+++ b/gopls/internal/server/command.go
@@ -21,6 +21,7 @@
 	"sync"
 
 	"golang.org/x/mod/modfile"
+	"golang.org/x/telemetry/counter"
 	"golang.org/x/tools/go/ast/astutil"
 	"golang.org/x/tools/gopls/internal/cache"
 	"golang.org/x/tools/gopls/internal/cache/metadata"
@@ -32,7 +33,6 @@
 	"golang.org/x/tools/gopls/internal/protocol"
 	"golang.org/x/tools/gopls/internal/protocol/command"
 	"golang.org/x/tools/gopls/internal/settings"
-	"golang.org/x/tools/gopls/internal/telemetry"
 	"golang.org/x/tools/gopls/internal/util/bug"
 	"golang.org/x/tools/gopls/internal/vulncheck"
 	"golang.org/x/tools/gopls/internal/vulncheck/scan"
@@ -80,7 +80,13 @@
 		return fmt.Errorf("Names and Values must have the same length")
 	}
 	// invalid counter update requests will be silently dropped. (no audience)
-	telemetry.AddForwardedCounters(args.Names, args.Values)
+	for i, n := range args.Names {
+		v := args.Values[i]
+		if n == "" || v < 0 {
+			continue
+		}
+		counter.Add("fwd/"+n, v)
+	}
 	return nil
 }
 
diff --git a/gopls/internal/server/general.go b/gopls/internal/server/general.go
index cdaee1b..9ead470 100644
--- a/gopls/internal/server/general.go
+++ b/gopls/internal/server/general.go
@@ -20,12 +20,12 @@
 	"strings"
 	"sync"
 
+	"golang.org/x/telemetry/counter"
 	"golang.org/x/tools/gopls/internal/cache"
 	"golang.org/x/tools/gopls/internal/debug"
 	"golang.org/x/tools/gopls/internal/file"
 	"golang.org/x/tools/gopls/internal/protocol"
 	"golang.org/x/tools/gopls/internal/settings"
-	"golang.org/x/tools/gopls/internal/telemetry"
 	"golang.org/x/tools/gopls/internal/util/bug"
 	"golang.org/x/tools/gopls/internal/util/goversion"
 	"golang.org/x/tools/gopls/internal/util/maps"
@@ -41,7 +41,7 @@
 	if params != nil && params.ClientInfo != nil {
 		clientName = params.ClientInfo.Name
 	}
-	telemetry.RecordClientInfo(clientName)
+	recordClientInfo(clientName)
 
 	s.stateMu.Lock()
 	if s.state >= serverInitializing {
@@ -249,7 +249,9 @@
 		if oldestVersion == -1 || viewVersion < oldestVersion {
 			oldestVersion, fromBuild = viewVersion, false
 		}
-		telemetry.RecordViewGoVersion(viewVersion)
+		if viewVersion >= 0 {
+			counter.Inc(fmt.Sprintf("gopls/goversion:1.%d", viewVersion))
+		}
 	}
 
 	if msg, isError := goversion.Message(oldestVersion, fromBuild); msg != "" {
@@ -637,3 +639,42 @@
 	// close naturally if needed after the connection is closed.
 	return nil
 }
+
+// recordClientInfo records gopls client info.
+func recordClientInfo(clientName string) {
+	key := "gopls/client:other"
+	switch clientName {
+	case "Visual Studio Code":
+		key = "gopls/client:vscode"
+	case "Visual Studio Code - Insiders":
+		key = "gopls/client:vscode-insiders"
+	case "VSCodium":
+		key = "gopls/client:vscodium"
+	case "code-server":
+		// https://github.com/coder/code-server/blob/3cb92edc76ecc2cfa5809205897d93d4379b16a6/ci/build/build-vscode.sh#L19
+		key = "gopls/client:code-server"
+	case "Eglot":
+		// https://lists.gnu.org/archive/html/bug-gnu-emacs/2023-03/msg00954.html
+		key = "gopls/client:eglot"
+	case "govim":
+		// https://github.com/govim/govim/pull/1189
+		key = "gopls/client:govim"
+	case "Neovim":
+		// https://github.com/neovim/neovim/blob/42333ea98dfcd2994ee128a3467dfe68205154cd/runtime/lua/vim/lsp.lua#L1361
+		key = "gopls/client:neovim"
+	case "coc.nvim":
+		// https://github.com/neoclide/coc.nvim/blob/3dc6153a85ed0f185abec1deb972a66af3fbbfb4/src/language-client/client.ts#L994
+		key = "gopls/client:coc.nvim"
+	case "Sublime Text LSP":
+		// https://github.com/sublimelsp/LSP/blob/e608f878e7e9dd34aabe4ff0462540fadcd88fcc/plugin/core/sessions.py#L493
+		key = "gopls/client:sublimetext"
+	default:
+		// Accumulate at least a local counter for an unknown
+		// client name, but also fall through to count it as
+		// ":other" for collection.
+		if clientName != "" {
+			counter.New(fmt.Sprintf("gopls/client-other:%s", clientName)).Inc()
+		}
+	}
+	counter.Inc(key)
+}
diff --git a/gopls/internal/server/prompt.go b/gopls/internal/server/prompt.go
index 72c5113..7dc16b9 100644
--- a/gopls/internal/server/prompt.go
+++ b/gopls/internal/server/prompt.go
@@ -11,8 +11,8 @@
 	"path/filepath"
 	"time"
 
+	"golang.org/x/telemetry"
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/telemetry"
 	"golang.org/x/tools/internal/event"
 )
 
diff --git a/gopls/internal/telemetry/telemetry.go b/gopls/internal/telemetry/telemetry.go
deleted file mode 100644
index 5808399..0000000
--- a/gopls/internal/telemetry/telemetry.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-// +build go1.19
-
-package telemetry
-
-import (
-	"fmt"
-
-	"golang.org/x/telemetry"
-	"golang.org/x/telemetry/counter"
-	"golang.org/x/telemetry/crashmonitor"
-	"golang.org/x/telemetry/upload"
-)
-
-// Start starts telemetry, including the crash monitor.
-func Start() {
-	telemetry.Start(telemetry.Config{ReportCrashes: true})
-}
-
-// CrashMonitorSupported calls [crashmonitor.Supported].
-func CrashMonitorSupported() bool {
-	return crashmonitor.Supported()
-}
-
-// NewStackCounter calls [counter.NewStack].
-func NewStackCounter(name string, depth int) *counter.StackCounter {
-	return counter.NewStack(name, depth)
-}
-
-// Mode calls x/telemetry.Mode.
-func Mode() string {
-	return telemetry.Mode()
-}
-
-// SetMode calls x/telemetry.SetMode.
-func SetMode(mode string) error {
-	return telemetry.SetMode(mode)
-}
-
-// Upload starts a goroutine for telemetry upload.
-func Upload() {
-	go upload.Run(nil)
-}
-
-// RecordClientInfo records gopls client info.
-func RecordClientInfo(clientName string) {
-	key := "gopls/client:other"
-	switch clientName {
-	case "Visual Studio Code":
-		key = "gopls/client:vscode"
-	case "Visual Studio Code - Insiders":
-		key = "gopls/client:vscode-insiders"
-	case "VSCodium":
-		key = "gopls/client:vscodium"
-	case "code-server":
-		// https://github.com/coder/code-server/blob/3cb92edc76ecc2cfa5809205897d93d4379b16a6/ci/build/build-vscode.sh#L19
-		key = "gopls/client:code-server"
-	case "Eglot":
-		// https://lists.gnu.org/archive/html/bug-gnu-emacs/2023-03/msg00954.html
-		key = "gopls/client:eglot"
-	case "govim":
-		// https://github.com/govim/govim/pull/1189
-		key = "gopls/client:govim"
-	case "Neovim":
-		// https://github.com/neovim/neovim/blob/42333ea98dfcd2994ee128a3467dfe68205154cd/runtime/lua/vim/lsp.lua#L1361
-		key = "gopls/client:neovim"
-	case "coc.nvim":
-		// https://github.com/neoclide/coc.nvim/blob/3dc6153a85ed0f185abec1deb972a66af3fbbfb4/src/language-client/client.ts#L994
-		key = "gopls/client:coc.nvim"
-	case "Sublime Text LSP":
-		// https://github.com/sublimelsp/LSP/blob/e608f878e7e9dd34aabe4ff0462540fadcd88fcc/plugin/core/sessions.py#L493
-		key = "gopls/client:sublimetext"
-	default:
-		// Accumulate at least a local counter for an unknown
-		// client name, but also fall through to count it as
-		// ":other" for collection.
-		if clientName != "" {
-			counter.New(fmt.Sprintf("gopls/client-other:%s", clientName)).Inc()
-		}
-	}
-	counter.Inc(key)
-}
-
-// RecordViewGoVersion records the Go minor version number (1.x) used for a view.
-func RecordViewGoVersion(x int) {
-	if x < 0 {
-		return
-	}
-	name := fmt.Sprintf("gopls/goversion:1.%d", x)
-	counter.Inc(name)
-}
-
-// AddForwardedCounters adds the given counters on behalf of clients.
-// Names and values must have the same length.
-func AddForwardedCounters(names []string, values []int64) {
-	for i, n := range names {
-		v := values[i]
-		if n == "" || v < 0 {
-			continue // Should we report an error? Who is the audience?
-		}
-		counter.Add("fwd/"+n, v)
-	}
-}
diff --git a/gopls/internal/telemetry/telemetry_go118.go b/gopls/internal/telemetry/telemetry_go118.go
deleted file mode 100644
index 3fd1df2..0000000
--- a/gopls/internal/telemetry/telemetry_go118.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.19
-// +build !go1.19
-
-package telemetry
-
-// This file defines dummy implementations of telemetry operations to
-// permit building with go1.18. Until we drop support for go1.18,
-// gopls may not refer to the telemetry module directly, but must go
-// through this file.
-
-func Start() {}
-
-func CrashMonitorSupported() bool { return false }
-
-func NewStackCounter(string, int) dummyCounter { return dummyCounter{} }
-
-type dummyCounter struct{}
-
-func (dummyCounter) Inc() {}
-
-func Mode() string {
-	return "local"
-}
-
-func SetMode(mode string) error {
-	return nil
-}
-
-func Upload() {
-}
-
-func RecordClientInfo(string) {}
-
-func RecordViewGoVersion(x int) {
-}
-
-func AddForwardedCounters(names []string, values []int64) {
-}
diff --git a/gopls/internal/test/integration/completion/completion18_test.go b/gopls/internal/test/integration/completion/completion18_test.go
index 0ca8377..a35061d 100644
--- a/gopls/internal/test/integration/completion/completion18_test.go
+++ b/gopls/internal/test/integration/completion/completion18_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package completion
 
 import (
diff --git a/gopls/internal/test/integration/misc/configuration_test.go b/gopls/internal/test/integration/misc/configuration_test.go
index c6ed180..39980f3 100644
--- a/gopls/internal/test/integration/misc/configuration_test.go
+++ b/gopls/internal/test/integration/misc/configuration_test.go
@@ -15,7 +15,7 @@
 // Test that enabling and disabling produces the expected results of showing
 // and hiding staticcheck analysis results.
 func TestChangeConfiguration(t *testing.T) {
-	// Staticcheck only supports Go versions >= 1.19.
+	// Staticcheck only supports Go versions >= 1.20.
 	// Note: keep this in sync with TestStaticcheckWarning. Below this version we
 	// should get an error when setting staticcheck configuration.
 	testenv.NeedsGo1Point(t, 20)
@@ -164,19 +164,6 @@
 	})
 }
 
-func TestGofumptWarning(t *testing.T) {
-	testenv.SkipAfterGo1Point(t, 17)
-
-	WithOptions(
-		Settings{"gofumpt": true},
-	).Run(t, "", func(t *testing.T, env *Env) {
-		env.OnceMet(
-			InitialWorkspaceLoad,
-			ShownMessage("gofumpt is not supported"),
-		)
-	})
-}
-
 func TestDeprecatedSettings(t *testing.T) {
 	WithOptions(
 		Settings{
diff --git a/gopls/internal/test/integration/misc/vuln_test.go b/gopls/internal/test/integration/misc/vuln_test.go
index f74c1c3..b2bd520 100644
--- a/gopls/internal/test/integration/misc/vuln_test.go
+++ b/gopls/internal/test/integration/misc/vuln_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package misc
 
 import (
@@ -167,7 +164,7 @@
 -- go.mod --
 module mod.com
 
-go 1.18
+go 1.19
 -- main.go --
 package main
 
@@ -192,9 +189,9 @@
 			// Let the analyzer read vulnerabilities data from the testdata/vulndb.
 			"GOVULNDB": db.URI(),
 			// When fetchinging stdlib package vulnerability info,
-			// behave as if our go version is go1.18 for this testing.
+			// behave as if our go version is go1.19 for this testing.
 			// The default behavior is to run `go env GOVERSION` (which isn't mutable env var).
-			cache.GoVersionForVulnTest:        "go1.18",
+			cache.GoVersionForVulnTest:        "go1.19",
 			"_GOPLS_TEST_BINARY_RUN_AS_GOPLS": "true", // needed to run `gopls vulncheck`.
 		},
 		Settings{
diff --git a/gopls/internal/test/marker/marker_test.go b/gopls/internal/test/marker/marker_test.go
index 1652eec..c32b81a 100644
--- a/gopls/internal/test/marker/marker_test.go
+++ b/gopls/internal/test/marker/marker_test.go
@@ -230,19 +230,14 @@
 				if err := os.WriteFile(filename, formatted, 0644); err != nil {
 					t.Error(err)
 				}
-			} else {
-				// On go 1.19 and later, verify that the testdata has not changed.
-				//
-				// On earlier Go versions, the golden test data varies due to different
-				// markdown escaping.
+			} else if !t.Failed() {
+				// Verify that the testdata has not changed.
 				//
 				// Only check this if the test hasn't already failed, otherwise we'd
 				// report duplicate mismatches of golden data.
-				if testenv.Go1Point() >= 19 && !t.Failed() {
-					// Otherwise, verify that formatted content matches.
-					if diff := compare.NamedText("formatted", "on-disk", string(formatted), string(test.content)); diff != "" {
-						t.Errorf("formatted test does not match on-disk content:\n%s", diff)
-					}
+				// Otherwise, verify that formatted content matches.
+				if diff := compare.NamedText("formatted", "on-disk", string(formatted), string(test.content)); diff != "" {
+					t.Errorf("formatted test does not match on-disk content:\n%s", diff)
 				}
 			}
 		})
diff --git a/gopls/internal/test/marker/testdata/definition/embed.txt b/gopls/internal/test/marker/testdata/definition/embed.txt
index 9842c37..bd50386 100644
--- a/gopls/internal/test/marker/testdata/definition/embed.txt
+++ b/gopls/internal/test/marker/testdata/definition/embed.txt
@@ -1,10 +1,5 @@
 This test checks definition and hover operations over embedded fields and methods.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- go.mod --
 module mod.com
 
diff --git a/gopls/internal/test/marker/testdata/definition/import.txt b/gopls/internal/test/marker/testdata/definition/import.txt
index c0e5d76..2ae95a8 100644
--- a/gopls/internal/test/marker/testdata/definition/import.txt
+++ b/gopls/internal/test/marker/testdata/definition/import.txt
@@ -1,10 +1,5 @@
 This test checks definition and hover over imports.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- go.mod --
 module mod.com
 
diff --git a/gopls/internal/test/marker/testdata/definition/misc.txt b/gopls/internal/test/marker/testdata/definition/misc.txt
index c7147a6..82c15b5 100644
--- a/gopls/internal/test/marker/testdata/definition/misc.txt
+++ b/gopls/internal/test/marker/testdata/definition/misc.txt
@@ -1,10 +1,5 @@
 This test exercises miscellaneous definition and hover requests.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- go.mod --
 module mod.com
 
diff --git a/gopls/internal/test/marker/testdata/diagnostics/issue60544.txt b/gopls/internal/test/marker/testdata/diagnostics/issue60544.txt
index b644d45..6b8d6ce 100644
--- a/gopls/internal/test/marker/testdata/diagnostics/issue60544.txt
+++ b/gopls/internal/test/marker/testdata/diagnostics/issue60544.txt
@@ -1,10 +1,6 @@
 This test exercises a crash due to treatment of "comparable" in methodset
 calculation (golang/go#60544).
 
--min_go is 1.19 as the error message changed at this Go version.
--- flags --
--min_go=go1.19
-
 -- main.go --
 package main
 
diff --git a/gopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt b/gopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt
index a60cdee..b14f4df 100644
--- a/gopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt
+++ b/gopls/internal/test/marker/testdata/diagnostics/rundespiteerrors.txt
@@ -1,9 +1,6 @@
 This test verifies that analyzers without RunDespiteErrors are not
 executed on a package containing type errors (see issue #54762).
 
-We require go1.18 because the range of the `1 + ""` go/types error
-changed then, and the new @diag marker is quite particular.
-
 -- go.mod --
 module example.com
 go 1.12
diff --git a/gopls/internal/test/marker/testdata/hover/basiclit.txt b/gopls/internal/test/marker/testdata/hover/basiclit.txt
index 9269c28..9c26b2a 100644
--- a/gopls/internal/test/marker/testdata/hover/basiclit.txt
+++ b/gopls/internal/test/marker/testdata/hover/basiclit.txt
@@ -1,10 +1,5 @@
 This test checks gopls behavior when hovering over basic literals.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- basiclit.go --
 package basiclit
 
diff --git a/gopls/internal/test/marker/testdata/hover/const.txt b/gopls/internal/test/marker/testdata/hover/const.txt
index c5f783c..179ff15 100644
--- a/gopls/internal/test/marker/testdata/hover/const.txt
+++ b/gopls/internal/test/marker/testdata/hover/const.txt
@@ -1,10 +1,5 @@
 This test checks hovering over constants.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- go.mod --
 module mod.com
 
diff --git a/gopls/internal/test/marker/testdata/hover/godef.txt b/gopls/internal/test/marker/testdata/hover/godef.txt
index 2d82ab0..9b2e7ec 100644
--- a/gopls/internal/test/marker/testdata/hover/godef.txt
+++ b/gopls/internal/test/marker/testdata/hover/godef.txt
@@ -3,6 +3,9 @@
 
 Requires go1.19+ for the new go/doc/comment package.
 
+TODO(adonovan): figure out why this test also fails
+without -min_go=go1.20. Or just wait...
+
 -- flags --
 -min_go=go1.19
 
diff --git a/gopls/internal/test/marker/testdata/hover/goprivate.txt b/gopls/internal/test/marker/testdata/hover/goprivate.txt
index f5b7179..7269ba1 100644
--- a/gopls/internal/test/marker/testdata/hover/goprivate.txt
+++ b/gopls/internal/test/marker/testdata/hover/goprivate.txt
@@ -1,10 +1,5 @@
 This test checks that links in hover obey GOPRIVATE.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- env --
 GOPRIVATE=mod.com
 -- go.mod --
diff --git a/gopls/internal/test/marker/testdata/hover/hover.txt b/gopls/internal/test/marker/testdata/hover/hover.txt
index 32a4c19..b5e4a88 100644
--- a/gopls/internal/test/marker/testdata/hover/hover.txt
+++ b/gopls/internal/test/marker/testdata/hover/hover.txt
@@ -1,10 +1,5 @@
 This test demonstrates some features of the new marker test runner.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- a.go --
 package a
 
diff --git a/gopls/internal/test/marker/testdata/hover/linkable.txt b/gopls/internal/test/marker/testdata/hover/linkable.txt
index b77b9e8..7fdb247 100644
--- a/gopls/internal/test/marker/testdata/hover/linkable.txt
+++ b/gopls/internal/test/marker/testdata/hover/linkable.txt
@@ -4,11 +4,6 @@
 We should only produce links that work, meaning the object is reachable via the
 package's public API.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- go.mod --
 module mod.com
 
diff --git a/gopls/internal/test/marker/testdata/hover/linkable_generics.txt b/gopls/internal/test/marker/testdata/hover/linkable_generics.txt
index 1ea009e..0b7ade7 100644
--- a/gopls/internal/test/marker/testdata/hover/linkable_generics.txt
+++ b/gopls/internal/test/marker/testdata/hover/linkable_generics.txt
@@ -1,10 +1,5 @@
 This file contains tests for documentation links to generic code in hover.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- go.mod --
 module mod.com
 
diff --git a/gopls/internal/test/marker/testdata/hover/linkname.txt b/gopls/internal/test/marker/testdata/hover/linkname.txt
index 829097c..8bb2eeb 100644
--- a/gopls/internal/test/marker/testdata/hover/linkname.txt
+++ b/gopls/internal/test/marker/testdata/hover/linkname.txt
@@ -1,10 +1,5 @@
 This test check hover on the 2nd argument in go:linkname directives.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- go.mod --
 module mod.com
 
diff --git a/gopls/internal/test/marker/testdata/hover/std.txt b/gopls/internal/test/marker/testdata/hover/std.txt
index af8f89f..c0db135 100644
--- a/gopls/internal/test/marker/testdata/hover/std.txt
+++ b/gopls/internal/test/marker/testdata/hover/std.txt
@@ -7,11 +7,6 @@
 In the future we may need to limit this test to the latest Go version to avoid
 documentation churn.
 
-Requires go1.19+ for the new go/doc/comment package.
-
--- flags --
--min_go=go1.19
-
 -- settings.json --
 {
 	"hoverKind": "SynopsisDocumentation"
diff --git a/gopls/internal/test/marker/testdata/symbol/generic.txt b/gopls/internal/test/marker/testdata/symbol/generic.txt
index 84cea99..1254851 100644
--- a/gopls/internal/test/marker/testdata/symbol/generic.txt
+++ b/gopls/internal/test/marker/testdata/symbol/generic.txt
@@ -3,9 +3,6 @@
 -- symbol.go --
 //@symbol(want)
 
-//go:build go1.18
-// +build go1.18
-
 package main
 
 type T[P any] struct {
diff --git a/gopls/internal/util/bug/bug.go b/gopls/internal/util/bug/bug.go
index e04b753..dcd242d 100644
--- a/gopls/internal/util/bug/bug.go
+++ b/gopls/internal/util/bug/bug.go
@@ -19,7 +19,7 @@
 	"sync"
 	"time"
 
-	"golang.org/x/tools/gopls/internal/telemetry"
+	"golang.org/x/telemetry/counter"
 )
 
 // PanicOnBugs controls whether to panic when bugs are reported.
@@ -69,7 +69,7 @@
 }
 
 // BugReportCount is a telemetry counter that tracks # of bug reports.
-var BugReportCount = telemetry.NewStackCounter("gopls/bug", 16)
+var BugReportCount = counter.NewStack("gopls/bug", 16)
 
 func report(description string) {
 	_, file, line, ok := runtime.Caller(2) // all exported reporting functions call report directly
diff --git a/gopls/internal/util/frob/frob.go b/gopls/internal/util/frob/frob.go
index 0f3ede2..cd385a9 100644
--- a/gopls/internal/util/frob/frob.go
+++ b/gopls/internal/util/frob/frob.go
@@ -93,7 +93,7 @@
 
 		case reflect.Array,
 			reflect.Slice,
-			reflect.Ptr: // TODO(adonovan): after go1.18, use Pointer
+			reflect.Pointer:
 			fr.addElem(fr.t.Elem())
 
 		case reflect.Map:
@@ -214,7 +214,7 @@
 			}
 		}
 
-	case reflect.Ptr: // TODO(adonovan): after go1.18, use Pointer
+	case reflect.Pointer:
 		if v.IsNil() {
 			out.uint8(0)
 		} else {
@@ -341,7 +341,7 @@
 			}
 		}
 
-	case reflect.Ptr: // TODO(adonovan): after go1.18, use Pointer
+	case reflect.Pointer:
 		isNil := in.uint8() == 0
 		if !isNil {
 			ptr := reflect.New(fr.elems[0].t)
@@ -402,38 +402,7 @@
 type writer struct{ data []byte }
 
 func (w *writer) uint8(v uint8)   { w.data = append(w.data, v) }
-func (w *writer) uint16(v uint16) { w.data = appendUint16(w.data, v) }
-func (w *writer) uint32(v uint32) { w.data = appendUint32(w.data, v) }
-func (w *writer) uint64(v uint64) { w.data = appendUint64(w.data, v) }
+func (w *writer) uint16(v uint16) { w.data = le.AppendUint16(w.data, v) }
+func (w *writer) uint32(v uint32) { w.data = le.AppendUint32(w.data, v) }
+func (w *writer) uint64(v uint64) { w.data = le.AppendUint64(w.data, v) }
 func (w *writer) bytes(v []byte)  { w.data = append(w.data, v...) }
-
-// TODO(adonovan): delete these as in go1.19 they are methods on LittleEndian:
-
-func appendUint16(b []byte, v uint16) []byte {
-	return append(b,
-		byte(v),
-		byte(v>>8),
-	)
-}
-
-func appendUint32(b []byte, v uint32) []byte {
-	return append(b,
-		byte(v),
-		byte(v>>8),
-		byte(v>>16),
-		byte(v>>24),
-	)
-}
-
-func appendUint64(b []byte, v uint64) []byte {
-	return append(b,
-		byte(v),
-		byte(v>>8),
-		byte(v>>16),
-		byte(v>>24),
-		byte(v>>32),
-		byte(v>>40),
-		byte(v>>48),
-		byte(v>>56),
-	)
-}
diff --git a/gopls/internal/util/lru/lru_fuzz_test.go b/gopls/internal/util/lru/lru_fuzz_test.go
index 1733511..b82776b 100644
--- a/gopls/internal/util/lru/lru_fuzz_test.go
+++ b/gopls/internal/util/lru/lru_fuzz_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package lru_test
 
 import (
diff --git a/gopls/internal/util/slices/slices.go b/gopls/internal/util/slices/slices.go
index 4c4fa4d..938ae6c 100644
--- a/gopls/internal/util/slices/slices.go
+++ b/gopls/internal/util/slices/slices.go
@@ -6,14 +6,14 @@
 
 // Clone returns a copy of the slice.
 // The elements are copied using assignment, so this is a shallow clone.
-// TODO(rfindley): use go1.19 slices.Clone.
+// TODO(rfindley): use go1.21 slices.Clone.
 func Clone[S ~[]E, E any](s S) S {
 	// The s[:0:0] preserves nil in case it matters.
 	return append(s[:0:0], s...)
 }
 
 // Contains reports whether x is present in slice.
-// TODO(adonovan): use go1.19 slices.Contains.
+// TODO(adonovan): use go1.21 slices.Contains.
 func Contains[S ~[]E, E comparable](slice S, x E) bool {
 	for _, elem := range slice {
 		if elem == x {
@@ -25,7 +25,7 @@
 
 // IndexFunc returns the first index i satisfying f(s[i]),
 // or -1 if none do.
-// TODO(adonovan): use go1.19 slices.IndexFunc.
+// TODO(adonovan): use go1.21 slices.IndexFunc.
 func IndexFunc[S ~[]E, E any](s S, f func(E) bool) int {
 	for i := range s {
 		if f(s[i]) {
@@ -37,7 +37,7 @@
 
 // ContainsFunc reports whether at least one
 // element e of s satisfies f(e).
-// TODO(adonovan): use go1.19 slices.ContainsFunc.
+// TODO(adonovan): use go1.21 slices.ContainsFunc.
 func ContainsFunc[S ~[]E, E any](s S, f func(E) bool) bool {
 	return IndexFunc(s, f) >= 0
 }
@@ -63,7 +63,7 @@
 // another n elements. After Grow(n), at least n elements can be appended
 // to the slice without another allocation. If n is negative or too large to
 // allocate the memory, Grow panics.
-// TODO(rfindley): use go1.19 slices.Grow.
+// TODO(rfindley): use go1.21 slices.Grow.
 func Grow[S ~[]E, E any](s S, n int) S {
 	if n < 0 {
 		panic("cannot be negative")
diff --git a/gopls/internal/vulncheck/scan/command.go b/gopls/internal/vulncheck/scan/command.go
index 1f53d8b..4ef0050 100644
--- a/gopls/internal/vulncheck/scan/command.go
+++ b/gopls/internal/vulncheck/scan/command.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package scan
 
 import (
diff --git a/gopls/internal/vulncheck/semver/semver.go b/gopls/internal/vulncheck/semver/semver.go
index 67c4fe8..ade710d 100644
--- a/gopls/internal/vulncheck/semver/semver.go
+++ b/gopls/internal/vulncheck/semver/semver.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 // Package semver provides shared utilities for manipulating
 // Go semantic versions.
 package semver
diff --git a/gopls/internal/vulncheck/semver/semver_test.go b/gopls/internal/vulncheck/semver/semver_test.go
index 6daead6..8a46228 100644
--- a/gopls/internal/vulncheck/semver/semver_test.go
+++ b/gopls/internal/vulncheck/semver/semver_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package semver
 
 import (
diff --git a/gopls/internal/vulncheck/vulntest/db.go b/gopls/internal/vulncheck/vulntest/db.go
index 659d2f1..e661b83 100644
--- a/gopls/internal/vulncheck/vulntest/db.go
+++ b/gopls/internal/vulncheck/vulntest/db.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 // Package vulntest provides helpers for vulncheck functionality testing.
 package vulntest
 
diff --git a/gopls/internal/vulncheck/vulntest/db_test.go b/gopls/internal/vulncheck/vulntest/db_test.go
index 2228124..3c34071 100644
--- a/gopls/internal/vulncheck/vulntest/db_test.go
+++ b/gopls/internal/vulncheck/vulntest/db_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package vulntest
 
 import (
diff --git a/gopls/internal/vulncheck/vulntest/report.go b/gopls/internal/vulncheck/vulntest/report.go
index cbfd0ae..b67986c 100644
--- a/gopls/internal/vulncheck/vulntest/report.go
+++ b/gopls/internal/vulncheck/vulntest/report.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package vulntest
 
 import (
diff --git a/gopls/internal/vulncheck/vulntest/report_test.go b/gopls/internal/vulncheck/vulntest/report_test.go
index 31f62ab..b88633c 100644
--- a/gopls/internal/vulncheck/vulntest/report_test.go
+++ b/gopls/internal/vulncheck/vulntest/report_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package vulntest
 
 import (
diff --git a/gopls/internal/vulncheck/vulntest/stdlib.go b/gopls/internal/vulncheck/vulntest/stdlib.go
index 9bf4d4e..57194f7 100644
--- a/gopls/internal/vulncheck/vulntest/stdlib.go
+++ b/gopls/internal/vulncheck/vulntest/stdlib.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package vulntest
 
 import (
diff --git a/gopls/internal/vulncheck/vulntest/stdlib_test.go b/gopls/internal/vulncheck/vulntest/stdlib_test.go
index 8f893f3..7b21297 100644
--- a/gopls/internal/vulncheck/vulntest/stdlib_test.go
+++ b/gopls/internal/vulncheck/vulntest/stdlib_test.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package vulntest
 
 import "testing"
diff --git a/gopls/main.go b/gopls/main.go
index 31c5c5f..9217b27 100644
--- a/gopls/main.go
+++ b/gopls/main.go
@@ -17,9 +17,9 @@
 	"context"
 	"os"
 
+	"golang.org/x/telemetry"
 	"golang.org/x/tools/gopls/internal/cmd"
 	"golang.org/x/tools/gopls/internal/hooks"
-	"golang.org/x/tools/gopls/internal/telemetry"
 	versionpkg "golang.org/x/tools/gopls/internal/version"
 	"golang.org/x/tools/internal/tool"
 )
@@ -29,7 +29,7 @@
 func main() {
 	versionpkg.VersionOverride = version
 
-	telemetry.Start()
+	telemetry.Start(telemetry.Config{ReportCrashes: true})
 	ctx := context.Background()
 	tool.Main(ctx, cmd.New(hooks.Options), os.Args[1:])
 }
diff --git a/internal/compat/appendf.go b/internal/compat/appendf.go
deleted file mode 100644
index 069d517..0000000
--- a/internal/compat/appendf.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-
-package compat
-
-import "fmt"
-
-func Appendf(b []byte, format string, a ...interface{}) []byte {
-	return fmt.Appendf(b, format, a...)
-}
diff --git a/internal/compat/appendf_118.go b/internal/compat/appendf_118.go
deleted file mode 100644
index 29af353..0000000
--- a/internal/compat/appendf_118.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.19
-
-package compat
-
-import "fmt"
-
-func Appendf(b []byte, format string, a ...interface{}) []byte {
-	return append(b, fmt.Sprintf(format, a...)...)
-}
diff --git a/internal/compat/doc.go b/internal/compat/doc.go
deleted file mode 100644
index 59c667a..0000000
--- a/internal/compat/doc.go
+++ /dev/null
@@ -1,7 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// The compat package implements API shims for backward compatibility at older
-// Go versions.
-package compat
diff --git a/internal/gcimporter/gcimporter_test.go b/internal/gcimporter/gcimporter_test.go
index 81d36bd..95cc36c 100644
--- a/internal/gcimporter/gcimporter_test.go
+++ b/internal/gcimporter/gcimporter_test.go
@@ -11,7 +11,6 @@
 	"bytes"
 	"fmt"
 	"go/ast"
-	"go/build"
 	"go/constant"
 	goimporter "go/importer"
 	goparser "go/parser"
@@ -165,8 +164,7 @@
 		t.Skipf("in short mode, skipping test that requires export data for all of std")
 	}
 
-	testenv.NeedsGo1Point(t, 18) // requires generics
-	testenv.NeedsGoBuild(t)      // to find stdlib export data in the build cache
+	testenv.NeedsGoBuild(t) // to find stdlib export data in the build cache
 
 	// This package only handles gc export data.
 	if runtime.Compiler != "gc" {
@@ -406,18 +404,6 @@
 	{"go/types.Type", "type Type interface{String() string; Underlying() Type}"},
 }
 
-// TODO(rsc): Delete this init func after x/tools no longer needs to test successfully with Go 1.17.
-func init() {
-	if build.Default.ReleaseTags[len(build.Default.ReleaseTags)-1] <= "go1.17" {
-		for i := range importedObjectTests {
-			if importedObjectTests[i].name == "context.Context" {
-				// Expand any to interface{}.
-				importedObjectTests[i].want = "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}"
-			}
-		}
-	}
-}
-
 func TestImportedTypes(t *testing.T) {
 	// This package only handles gc export data.
 	needsCompiler(t, "gc")
@@ -739,8 +725,6 @@
 }
 
 func TestIssue51836(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18) // requires generics
-
 	// This package only handles gc export data.
 	needsCompiler(t, "gc")
 
@@ -770,8 +754,6 @@
 }
 
 func TestIssue61561(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18) // requires generics
-
 	const src = `package p
 
 type I[P any] interface {
@@ -836,8 +818,6 @@
 }
 
 func TestIssue57015(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18) // requires generics
-
 	// This package only handles gc export data.
 	needsCompiler(t, "gc")
 
diff --git a/internal/gcimporter/iexport_go118_test.go b/internal/gcimporter/iexport_go118_test.go
index 134c231..c748fb3 100644
--- a/internal/gcimporter/iexport_go118_test.go
+++ b/internal/gcimporter/iexport_go118_test.go
@@ -2,11 +2,10 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package gcimporter_test
 
+// This file defines test of generics features introduce in go1.18.
+
 import (
 	"bytes"
 	"fmt"
diff --git a/internal/gcimporter/support_go117.go b/internal/gcimporter/support_go117.go
deleted file mode 100644
index d892273..0000000
--- a/internal/gcimporter/support_go117.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.18
-// +build !go1.18
-
-package gcimporter
-
-import "go/types"
-
-const iexportVersion = iexportVersionGo1_11
-
-func additionalPredeclared() []types.Type {
-	return nil
-}
diff --git a/internal/gcimporter/support_go118.go b/internal/gcimporter/support_go118.go
index edbe6ea..0cd3b91 100644
--- a/internal/gcimporter/support_go118.go
+++ b/internal/gcimporter/support_go118.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package gcimporter
 
 import "go/types"
diff --git a/internal/gcimporter/unified_no.go b/internal/gcimporter/unified_no.go
index 286bf44..38b624c 100644
--- a/internal/gcimporter/unified_no.go
+++ b/internal/gcimporter/unified_no.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build !(go1.18 && goexperiment.unified)
-// +build !go1.18 !goexperiment.unified
+//go:build !goexperiment.unified
+// +build !goexperiment.unified
 
 package gcimporter
 
diff --git a/internal/gcimporter/unified_yes.go b/internal/gcimporter/unified_yes.go
index b5d69ff..b5118d0 100644
--- a/internal/gcimporter/unified_yes.go
+++ b/internal/gcimporter/unified_yes.go
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18 && goexperiment.unified
-// +build go1.18,goexperiment.unified
+//go:build goexperiment.unified
+// +build goexperiment.unified
 
 package gcimporter
 
diff --git a/internal/gcimporter/ureader_no.go b/internal/gcimporter/ureader_no.go
deleted file mode 100644
index 8eb2072..0000000
--- a/internal/gcimporter/ureader_no.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.18
-// +build !go1.18
-
-package gcimporter
-
-import (
-	"fmt"
-	"go/token"
-	"go/types"
-)
-
-func UImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
-	err = fmt.Errorf("go/tools compiled with a Go version earlier than 1.18 cannot read unified IR export data")
-	return
-}
diff --git a/internal/gcimporter/ureader_yes.go b/internal/gcimporter/ureader_yes.go
index bcf52d9..f4edc46 100644
--- a/internal/gcimporter/ureader_yes.go
+++ b/internal/gcimporter/ureader_yes.go
@@ -4,9 +4,6 @@
 
 // Derived from go/internal/gcimporter/ureader.go
 
-//go:build go1.18
-// +build go1.18
-
 package gcimporter
 
 import (
diff --git a/internal/imports/mod_test.go b/internal/imports/mod_test.go
index c624463..e65104c 100644
--- a/internal/imports/mod_test.go
+++ b/internal/imports/mod_test.go
@@ -559,8 +559,6 @@
 
 // Tests that go.work files are respected.
 func TestModWorkspace(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
-
 	mt := setup(t, nil, `
 -- go.work --
 go 1.18
@@ -595,8 +593,6 @@
 // respected and that a wildcard replace in go.work overrides a versioned replace
 // in go.mod.
 func TestModWorkspaceReplace(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
-
 	mt := setup(t, nil, `
 -- go.work --
 use m
@@ -654,8 +650,6 @@
 // Tests a case where conflicting replaces are overridden by a replace
 // in the go.work file.
 func TestModWorkspaceReplaceOverride(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
-
 	mt := setup(t, nil, `-- go.work --
 use m
 use n
@@ -719,8 +713,6 @@
 // workspaces with module pruning. This is based on the
 // cmd/go mod_prune_all script test.
 func TestModWorkspacePrune(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
-
 	mt := setup(t, nil, `
 -- go.work --
 go 1.18
diff --git a/internal/robustio/robustio_posix.go b/internal/robustio/robustio_posix.go
index 8aa13d0..cf74865 100644
--- a/internal/robustio/robustio_posix.go
+++ b/internal/robustio/robustio_posix.go
@@ -5,8 +5,6 @@
 //go:build !windows && !plan9
 // +build !windows,!plan9
 
-// TODO(adonovan): use 'unix' tag when go1.19 can be assumed.
-
 package robustio
 
 import (
diff --git a/internal/tokeninternal/tokeninternal.go b/internal/tokeninternal/tokeninternal.go
index 7e638ec..ff9437a 100644
--- a/internal/tokeninternal/tokeninternal.go
+++ b/internal/tokeninternal/tokeninternal.go
@@ -34,30 +34,16 @@
 		lines []int
 		_     []struct{}
 	}
-	type tokenFile118 struct {
-		_ *token.FileSet // deleted in go1.19
-		tokenFile119
-	}
 
-	type uP = unsafe.Pointer
-	switch unsafe.Sizeof(*file) {
-	case unsafe.Sizeof(tokenFile118{}):
-		var ptr *tokenFile118
-		*(*uP)(uP(&ptr)) = uP(file)
-		ptr.mu.Lock()
-		defer ptr.mu.Unlock()
-		return ptr.lines
-
-	case unsafe.Sizeof(tokenFile119{}):
-		var ptr *tokenFile119
-		*(*uP)(uP(&ptr)) = uP(file)
-		ptr.mu.Lock()
-		defer ptr.mu.Unlock()
-		return ptr.lines
-
-	default:
+	if unsafe.Sizeof(*file) != unsafe.Sizeof(tokenFile119{}) {
 		panic("unexpected token.File size")
 	}
+	var ptr *tokenFile119
+	type uP = unsafe.Pointer
+	*(*uP)(uP(&ptr)) = uP(file)
+	ptr.mu.Lock()
+	defer ptr.mu.Unlock()
+	return ptr.lines
 }
 
 // AddExistingFiles adds the specified files to the FileSet if they
diff --git a/internal/typeparams/common.go b/internal/typeparams/common.go
index 6679d79..8c3a42d 100644
--- a/internal/typeparams/common.go
+++ b/internal/typeparams/common.go
@@ -2,20 +2,10 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package typeparams contains common utilities for writing tools that interact
-// with generic Go code, as introduced with Go 1.18.
-//
-// Many of the types and functions in this package are proxies for the new APIs
-// introduced in the standard library with Go 1.18. For example, the
-// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec
-// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go
-// versions older than 1.18 these helpers are implemented as stubs, allowing
-// users of this package to write code that handles generic constructs inline,
-// even if the Go version being used to compile does not support generics.
-//
-// Additionally, this package contains common utilities for working with the
-// new generic constructs, to supplement the standard library APIs. Notably,
-// the StructuralTerms API computes a minimal representation of the structural
+// Package typeparams contains common utilities for writing tools that
+// interact with generic Go code, as introduced with Go 1.18. It
+// supplements the standard library APIs. Notably, the StructuralTerms
+// API computes a minimal representation of the structural
 // restrictions on a type parameter.
 //
 // An external version of these APIs is available in the
diff --git a/internal/typeparams/common_test.go b/internal/typeparams/common_test.go
index b6f355c..b095b65 100644
--- a/internal/typeparams/common_test.go
+++ b/internal/typeparams/common_test.go
@@ -11,7 +11,6 @@
 	"go/types"
 	"testing"
 
-	"golang.org/x/tools/internal/testenv"
 	. "golang.org/x/tools/internal/typeparams"
 )
 
@@ -41,7 +40,6 @@
 }
 
 func TestOriginMethodRecursive(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
 	src := `package p
 
 type N[A any] int
@@ -113,7 +111,6 @@
 }
 
 func TestOriginMethodUses(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
 
 	tests := []string{
 		`type T interface { m() }; func _(t T) { t.m() }`,
@@ -209,8 +206,6 @@
 }
 
 func TestGenericAssignableTo(t *testing.T) {
-	testenv.NeedsGo1Point(t, 18)
-
 	tests := []struct {
 		src  string
 		want bool
diff --git a/internal/typesinternal/types_118.go b/internal/typesinternal/types_118.go
index a42b072..ef7ea29 100644
--- a/internal/typesinternal/types_118.go
+++ b/internal/typesinternal/types_118.go
@@ -2,9 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.18
-// +build go1.18
-
 package typesinternal
 
 import (