many: re-enable staticcheck and fix problems

Most of the checks were about io/ioutil. There were a couple of
other minor ones. I didn't address the check for strings.Title;
instead, I turned off that check globally with a staticcheck.conf
file.

Change-Id: I286a6894fb1fd891818ab9e451c891f52a3828fc
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/412675
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/all.bash b/all.bash
index 7be8835..afb938a 100755
--- a/all.bash
+++ b/all.bash
@@ -135,8 +135,8 @@
 
 # check_unparam runs unparam on source files.
 check_unparam() {
-  echo "unparam disabled until go 1.18"
-# TODO: uncomment when updated to go 1.18
+  echo "unparam disabled until ssa supports generics"
+  # TODO: uncomment when working
   # ensure_go_binary mvdan.cc/unparam
   # runcmd unparam ./...
 }
@@ -148,10 +148,8 @@
 
 # check_staticcheck runs staticcheck on source files.
 check_staticcheck() {
-  echo "staticcheck disabled until go 1.18"
-# TODO: uncomment when updated to go 1.18
-#  ensure_go_binary honnef.co/go/tools/cmd/staticcheck
-#  runcmd staticcheck $(go list ./... | grep -v third_party | grep -v internal/doc | grep -v internal/render)
+ ensure_go_binary honnef.co/go/tools/cmd/staticcheck
+ runcmd staticcheck $(go list ./... | grep -v third_party | grep -v internal/doc | grep -v internal/render)
 }
 
 # check_misspell runs misspell on source files.
diff --git a/cmd/pkgsite/main_test.go b/cmd/pkgsite/main_test.go
index 4dff62b..c78fda7 100644
--- a/cmd/pkgsite/main_test.go
+++ b/cmd/pkgsite/main_test.go
@@ -126,6 +126,9 @@
 	defer teardown()
 
 	getters, err := buildGetters(context.Background(), []string{localModule}, false, cacheDir, nil, prox)
+	if err != nil {
+		t.Fatal(err)
+	}
 	server, err := newServer(getters, prox)
 	if err != nil {
 		t.Fatal(err)
diff --git a/devtools/cmd/csphash/main.go b/devtools/cmd/csphash/main.go
index f50bbfb..1021aa6 100644
--- a/devtools/cmd/csphash/main.go
+++ b/devtools/cmd/csphash/main.go
@@ -13,7 +13,6 @@
 	"encoding/base64"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"regexp"
@@ -87,7 +86,7 @@
 
 // extractHashes scans the given file for CSP-style hashes and returns them.
 func extractHashes(filename string) ([]string, error) {
-	contents, err := ioutil.ReadFile(filename)
+	contents, err := os.ReadFile(filename)
 	if err != nil {
 		return nil, err
 	}
@@ -112,7 +111,7 @@
 
 // scripts returns all the script elements in the given file.
 func scripts(filename string) ([]*script, error) {
-	contents, err := ioutil.ReadFile(filename)
+	contents, err := os.ReadFile(filename)
 	if err != nil {
 		return nil, fmt.Errorf("%s: %v", filename, err)
 	}
diff --git a/devtools/cmd/css/main.go b/devtools/cmd/css/main.go
index 53e517a..187ebe4 100644
--- a/devtools/cmd/css/main.go
+++ b/devtools/cmd/css/main.go
@@ -14,7 +14,6 @@
 	"bufio"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"log"
 	"net/http"
 	"os"
@@ -87,8 +86,8 @@
 		log.Fatal(err)
 	}
 
-	if err := ioutil.WriteFile(cssFile, []byte(copyright), 0644); err != nil {
-		log.Fatalf("ioutil.WriteFile(f, '', 0644): %v", err)
+	if err := os.WriteFile(cssFile, []byte(copyright), 0644); err != nil {
+		log.Fatalf("os.WriteFile(f, '', 0644): %v", err)
 	}
 
 	file, err := os.OpenFile(cssFile, os.O_WRONLY|os.O_APPEND, 0644)
diff --git a/internal/config/config.go b/internal/config/config.go
index 42d264f..621f3c2 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -15,7 +15,6 @@
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math/rand"
 	"net/http"
 	"os"
@@ -540,7 +539,7 @@
 		return nil, err
 	}
 	defer r.Close()
-	return ioutil.ReadAll(r)
+	return io.ReadAll(r)
 }
 
 func processOverrides(ctx context.Context, cfg *Config, bytes []byte) {
@@ -608,9 +607,9 @@
 	if resp.StatusCode != http.StatusOK {
 		return "", fmt.Errorf("bad status: %s", resp.Status)
 	}
-	bytes, err := ioutil.ReadAll(resp.Body)
+	bytes, err := io.ReadAll(resp.Body)
 	if err != nil {
-		return "", fmt.Errorf("ioutil.ReadAll: %v", err)
+		return "", fmt.Errorf("io.ReadAll: %v", err)
 	}
 	return string(bytes), nil
 }
diff --git a/internal/config/dynconfig/dynconfig.go b/internal/config/dynconfig/dynconfig.go
index 5708730..9e3f66c 100644
--- a/internal/config/dynconfig/dynconfig.go
+++ b/internal/config/dynconfig/dynconfig.go
@@ -11,7 +11,6 @@
 	"context"
 	"errors"
 	"io"
-	"io/ioutil"
 	"os"
 	"strings"
 
@@ -63,7 +62,7 @@
 		}
 	}
 	defer r.Close()
-	data, err := ioutil.ReadAll(r)
+	data, err := io.ReadAll(r)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/fetch/fetchdata_test.go b/internal/fetch/fetchdata_test.go
index e74fc6d..f3d5268 100644
--- a/internal/fetch/fetchdata_test.go
+++ b/internal/fetch/fetchdata_test.go
@@ -1366,7 +1366,6 @@
 						"github.com/google/pprof/profile",
 						"golang.org/x/crypto/ssh/terminal",
 						"io",
-						"io/ioutil",
 						"net/http",
 						"net/url",
 						"os",
diff --git a/internal/fetch/gen_zip_signatures.go b/internal/fetch/gen_zip_signatures.go
index 7222d73..0a98f13 100644
--- a/internal/fetch/gen_zip_signatures.go
+++ b/internal/fetch/gen_zip_signatures.go
@@ -19,8 +19,8 @@
 	"fmt"
 	"go/format"
 	"io/fs"
-	"io/ioutil"
 	"log"
+	"os"
 	"sort"
 	"text/template"
 	"time"
@@ -185,7 +185,7 @@
 	if err != nil {
 		return err
 	}
-	return ioutil.WriteFile(filename, src, 0644)
+	return os.WriteFile(filename, src, 0644)
 }
 
 // Template for the generated source file.
diff --git a/internal/fetch/getters.go b/internal/fetch/getters.go
index 8760747..c0037cd 100644
--- a/internal/fetch/getters.go
+++ b/internal/fetch/getters.go
@@ -13,8 +13,8 @@
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -122,7 +122,7 @@
 func NewDirectoryModuleGetter(modulePath, dir string) (*directoryModuleGetter, error) {
 
 	if modulePath == "" {
-		goModBytes, err := ioutil.ReadFile(filepath.Join(dir, "go.mod"))
+		goModBytes, err := os.ReadFile(filepath.Join(dir, "go.mod"))
 		if err != nil {
 			return nil, fmt.Errorf("cannot obtain module path for %q (%v): %w", dir, err, derrors.BadModule)
 		}
@@ -166,7 +166,7 @@
 	if err := g.checkPath(path); err != nil {
 		return nil, err
 	}
-	data, err := ioutil.ReadFile(filepath.Join(g.dir, "go.mod"))
+	data, err := os.ReadFile(filepath.Join(g.dir, "go.mod"))
 	if errors.Is(err, os.ErrNotExist) {
 		return []byte(fmt.Sprintf("module %s\n", g.modulePath)), nil
 	}
@@ -360,7 +360,7 @@
 		return nil, err
 	}
 	defer f.Close()
-	return ioutil.ReadAll(f)
+	return io.ReadAll(f)
 }
 
 func (g *fsProxyModuleGetter) openFile(path, version, suffix string) (_ *os.File, err error) {
diff --git a/internal/fetch/getters_test.go b/internal/fetch/getters_test.go
index 5d3dbb8..c29bd9a 100644
--- a/internal/fetch/getters_test.go
+++ b/internal/fetch/getters_test.go
@@ -7,7 +7,7 @@
 import (
 	"context"
 	"errors"
-	"io/ioutil"
+	"io"
 	"path/filepath"
 	"testing"
 	"time"
@@ -129,7 +129,7 @@
 			t.Fatal(err)
 		}
 		defer f.Close()
-		got, err := ioutil.ReadAll(f)
+		got, err := io.ReadAll(f)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/internal/fetch/load.go b/internal/fetch/load.go
index ab3135b..ea3323e 100644
--- a/internal/fetch/load.go
+++ b/internal/fetch/load.go
@@ -16,7 +16,6 @@
 	"go/token"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"path"
@@ -314,7 +313,7 @@
 
 		JoinPath: path.Join,
 		OpenFile: func(name string) (io.ReadCloser, error) {
-			return ioutil.NopCloser(bytes.NewReader(allFiles[name])), nil
+			return io.NopCloser(bytes.NewReader(allFiles[name])), nil
 		},
 
 		// If left nil, the default implementations of these read from disk,
@@ -353,5 +352,5 @@
 		return nil, err
 	}
 	defer f.Close()
-	return ioutil.ReadAll(io.LimitReader(f, limit))
+	return io.ReadAll(io.LimitReader(f, limit))
 }
diff --git a/internal/frontend/client.go b/internal/frontend/client.go
index a71469d..afb2323 100644
--- a/internal/frontend/client.go
+++ b/internal/frontend/client.go
@@ -7,7 +7,7 @@
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"os"
@@ -79,7 +79,7 @@
 	if r.StatusCode != http.StatusOK {
 		return nil, fmt.Errorf(r.Status)
 	}
-	body, err := ioutil.ReadAll(r.Body)
+	body, err := io.ReadAll(r.Body)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/frontend/depsdev.go b/internal/frontend/depsdev.go
index 99f05d2..1c3e9ad 100644
--- a/internal/frontend/depsdev.go
+++ b/internal/frontend/depsdev.go
@@ -78,10 +78,10 @@
 	switch resp.StatusCode {
 	case http.StatusNotFound:
 		return "", nil // No link to return.
-	default:
-		return "", errors.New(resp.Status)
 	case http.StatusOK:
 		// Handled below.
+	default:
+		return "", errors.New(resp.Status)
 	}
 	var r struct {
 		stem, Name, Version string
diff --git a/internal/frontend/playground_test.go b/internal/frontend/playground_test.go
index 85ae8ad..3911621 100644
--- a/internal/frontend/playground_test.go
+++ b/internal/frontend/playground_test.go
@@ -7,7 +7,6 @@
 import (
 	"flag"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"net/url"
@@ -100,7 +99,7 @@
 			}
 
 			if res.StatusCode >= 200 && res.StatusCode < 300 {
-				body, err := ioutil.ReadAll(res.Body)
+				body, err := io.ReadAll(res.Body)
 				if err != nil {
 					t.Fatal(err)
 				}
diff --git a/internal/frontend/vulns.go b/internal/frontend/vulns.go
index b2c1253..accc343 100644
--- a/internal/frontend/vulns.go
+++ b/internal/frontend/vulns.go
@@ -207,10 +207,9 @@
 		isSemver := r.Type == osv.TypeSemver
 		for _, v := range r.Events {
 			if v.Introduced != "" {
-				if p.intro != "" {
-					// We expected Introduced and Fixed to alternate, but they don't.
-					// Keep going, ignoring the first Introduced.
-				}
+				// We expected Introduced and Fixed to alternate, but if
+				// p.intro != "", then they they don't.
+				// Keep going in that case, ignoring the first Introduced.
 				p.intro = v.Introduced
 				if p.intro == "0" {
 					p.intro = ""
diff --git a/internal/godoc/dochtml/dochtml_test.go b/internal/godoc/dochtml/dochtml_test.go
index 917e1e8..eeaa11a 100644
--- a/internal/godoc/dochtml/dochtml_test.go
+++ b/internal/godoc/dochtml/dochtml_test.go
@@ -10,11 +10,11 @@
 	"context"
 	"flag"
 	"fmt"
+	"os"
 
 	"go/ast"
 	"go/parser"
 	"go/token"
-	"io/ioutil"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -433,7 +433,7 @@
 // Copied from internal/render/render_test.go, with the slight modification of returning the fset.
 func mustLoadPackage(path string) (*token.FileSet, *doc.Package) {
 	srcName := filepath.Base(path) + ".go"
-	code, err := ioutil.ReadFile(filepath.Join("testdata", srcName))
+	code, err := os.ReadFile(filepath.Join("testdata", srcName))
 	if err != nil {
 		panic(err)
 	}
diff --git a/internal/godoc/dochtml/internal/render/render_test.go b/internal/godoc/dochtml/internal/render/render_test.go
index a5dbbbe..26fcda9 100644
--- a/internal/godoc/dochtml/internal/render/render_test.go
+++ b/internal/godoc/dochtml/internal/render/render_test.go
@@ -8,7 +8,7 @@
 	"go/ast"
 	"go/parser"
 	"go/token"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"reflect"
 	"strings"
@@ -38,7 +38,7 @@
 	}
 
 	srcName := filepath.Base(path) + ".go"
-	code, err := ioutil.ReadFile(filepath.Join("testdata", srcName))
+	code, err := os.ReadFile(filepath.Join("testdata", srcName))
 	if err != nil {
 		panic(err)
 	}
diff --git a/internal/licenses/gen_exceptions.go b/internal/licenses/gen_exceptions.go
index 8d8d01e..a9ca7ab 100644
--- a/internal/licenses/gen_exceptions.go
+++ b/internal/licenses/gen_exceptions.go
@@ -19,8 +19,8 @@
 	"flag"
 	"fmt"
 	"go/format"
-	"io/ioutil"
 	"log"
+	"os"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -73,7 +73,7 @@
 
 	src, err := format.Source([]byte(code))
 	if err != nil {
-		fd, err1 := ioutil.TempFile("", "license-data")
+		fd, err1 := io.TempFile("", "license-data")
 		if err1 == nil {
 			_, err1 = fd.Write([]byte(code))
 			if err1 == nil {
@@ -83,7 +83,7 @@
 		}
 		log.Fatal("parsing output:", err)
 	}
-	err = ioutil.WriteFile("exceptions.gen.go", src, 0644)
+	err = os.WriteFile("exceptions.gen.go", src, 0644)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/internal/licenses/licenses.go b/internal/licenses/licenses.go
index d19a726..3cf486f 100644
--- a/internal/licenses/licenses.go
+++ b/internal/licenses/licenses.go
@@ -26,7 +26,6 @@
 	"fmt"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"path"
 	"path/filepath"
 	"sort"
@@ -530,7 +529,7 @@
 	if info.Size() > maxLicenseSize {
 		return nil, fmt.Errorf("file size %d exceeds max license size %d", info.Size(), maxLicenseSize)
 	}
-	return ioutil.ReadAll(io.LimitReader(f, int64(maxLicenseSize)))
+	return io.ReadAll(io.LimitReader(f, int64(maxLicenseSize)))
 }
 
 // DetectFile return the set of license types for the given file contents. It
diff --git a/internal/licenses/licenses_test.go b/internal/licenses/licenses_test.go
index e11c9e0..0899e40 100644
--- a/internal/licenses/licenses_test.go
+++ b/internal/licenses/licenses_test.go
@@ -10,7 +10,6 @@
 	"fmt"
 	"io"
 	"io/fs"
-	"io/ioutil"
 	"log"
 	"math"
 	"os"
@@ -413,7 +412,7 @@
 		{"rocketlaunchr", []string{"MIT"}},
 	} {
 		t.Run(test.file, func(t *testing.T) {
-			data, err := ioutil.ReadFile(filepath.Join("testdata", test.file+".df"))
+			data, err := os.ReadFile(filepath.Join("testdata", test.file+".df"))
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/internal/memory/memory.go b/internal/memory/memory.go
index 2cb1e61..feb9d9a 100644
--- a/internal/memory/memory.go
+++ b/internal/memory/memory.go
@@ -9,7 +9,6 @@
 import (
 	"bufio"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -145,7 +144,7 @@
 	const cgroupMemDir = "/sys/fs/cgroup/memory"
 
 	readUintFile := func(filename string) (uint64, error) {
-		data, err := ioutil.ReadFile(filepath.Join(cgroupMemDir, filename))
+		data, err := os.ReadFile(filepath.Join(cgroupMemDir, filename))
 		if err != nil {
 			return 0, err
 		}
diff --git a/internal/middleware/caching_test.go b/internal/middleware/caching_test.go
index 7e5cf61..385a6b0 100644
--- a/internal/middleware/caching_test.go
+++ b/internal/middleware/caching_test.go
@@ -6,7 +6,7 @@
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"strconv"
@@ -150,7 +150,7 @@
 		if resp.StatusCode != test.wantStatus {
 			t.Errorf("[%s] GET returned status %d, want %d", test.label, resp.StatusCode, test.wantStatus)
 		}
-		body, err := ioutil.ReadAll(resp.Body)
+		body, err := io.ReadAll(resp.Body)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/internal/middleware/middleware_test.go b/internal/middleware/middleware_test.go
index 1e39048..51d838e 100644
--- a/internal/middleware/middleware_test.go
+++ b/internal/middleware/middleware_test.go
@@ -7,7 +7,7 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -45,10 +45,10 @@
 	if err != nil {
 		t.Fatalf("GET got error %v, want nil", err)
 	}
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	resp.Body.Close()
 	if err != nil {
-		t.Fatalf("ioutil.ReadAll(resp.Body): %v", err)
+		t.Fatalf("io.ReadAll(resp.Body): %v", err)
 	}
 
 	// Test that both middleware executed, in the correct order.
diff --git a/internal/middleware/panic_test.go b/internal/middleware/panic_test.go
index 624b5b3..cbe91e7 100644
--- a/internal/middleware/panic_test.go
+++ b/internal/middleware/panic_test.go
@@ -6,7 +6,7 @@
 
 import (
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -47,7 +47,7 @@
 			if resp.StatusCode != test.wantCode {
 				t.Errorf("code=%d, want %d", resp.StatusCode, test.wantCode)
 			}
-			body, err := ioutil.ReadAll(resp.Body)
+			body, err := io.ReadAll(resp.Body)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/internal/middleware/stats_test.go b/internal/middleware/stats_test.go
index fab26f8..3370474 100644
--- a/internal/middleware/stats_test.go
+++ b/internal/middleware/stats_test.go
@@ -7,7 +7,7 @@
 import (
 	"encoding/json"
 	"hash/fnv"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -39,7 +39,7 @@
 	if res.StatusCode != http.StatusOK {
 		t.Fatalf("failed with status %d", res.StatusCode)
 	}
-	gotData, err := ioutil.ReadAll(res.Body)
+	gotData, err := io.ReadAll(res.Body)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/internal/postgres/insert_module_test.go b/internal/postgres/insert_module_test.go
index f4ac477..ee3c25a 100644
--- a/internal/postgres/insert_module_test.go
+++ b/internal/postgres/insert_module_test.go
@@ -9,7 +9,7 @@
 	"database/sql"
 	"errors"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -504,7 +504,7 @@
 	}
 
 	check := func(filename string, okRaw bool) {
-		data, err := ioutil.ReadFile(filepath.Join("testdata", filename))
+		data, err := os.ReadFile(filepath.Join("testdata", filename))
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/internal/postgres/search/gen_query.go b/internal/postgres/search/gen_query.go
index 86eda08..27d96f3 100644
--- a/internal/postgres/search/gen_query.go
+++ b/internal/postgres/search/gen_query.go
@@ -11,7 +11,7 @@
 	"context"
 	"fmt"
 	"go/format"
-	"io/ioutil"
+	"os"
 
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/postgres/search"
@@ -33,8 +33,8 @@
 	if err != nil {
 		return err
 	}
-	if err := ioutil.WriteFile(filename, []byte(content), 0644); err != nil {
-		return fmt.Errorf("ioutil.WriteFile(f, '', 0644): %v", err)
+	if err := os.WriteFile(filename, []byte(content), 0644); err != nil {
+		return fmt.Errorf("os.WriteFile(f, '', 0644): %v", err)
 	}
 	return nil
 }
diff --git a/internal/proxy/client.go b/internal/proxy/client.go
index dfd7179..1ada97c 100644
--- a/internal/proxy/client.go
+++ b/internal/proxy/client.go
@@ -14,7 +14,6 @@
 	"errors"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"strings"
 	"time"
@@ -208,7 +207,7 @@
 	var data []byte
 	err = c.executeRequest(ctx, u, func(body io.Reader) error {
 		var err error
-		data, err = ioutil.ReadAll(body)
+		data, err = io.ReadAll(body)
 		return err
 	})
 	if err != nil {
@@ -285,9 +284,9 @@
 		//
 		// If the Disable-Module-Fetch header was set, use a different
 		// error code so we can tell the difference.
-		data, err := ioutil.ReadAll(r.Body)
+		data, err := io.ReadAll(r.Body)
 		if err != nil {
-			return fmt.Errorf("ioutil.readall: %v", err)
+			return fmt.Errorf("io.ReadAll: %v", err)
 		}
 		d := string(data)
 		switch {
diff --git a/internal/source/source_test.go b/internal/source/source_test.go
index f4fa038..414dd9f 100644
--- a/internal/source/source_test.go
+++ b/internal/source/source_test.go
@@ -9,7 +9,7 @@
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"os"
 	"path/filepath"
@@ -837,7 +837,7 @@
 	}
 	resp := &http.Response{
 		StatusCode: statusCode,
-		Body:       ioutil.NopCloser(strings.NewReader(body)),
+		Body:       io.NopCloser(strings.NewReader(body)),
 	}
 	return resp, nil
 }
diff --git a/internal/stdlib/testdata/v1.12.5/src/cmd/pprof/pprof.go b/internal/stdlib/testdata/v1.12.5/src/cmd/pprof/pprof.go
index 42e3100..89f3969 100644
--- a/internal/stdlib/testdata/v1.12.5/src/cmd/pprof/pprof.go
+++ b/internal/stdlib/testdata/v1.12.5/src/cmd/pprof/pprof.go
@@ -13,7 +13,7 @@
 	"crypto/tls"
 	"debug/dwarf"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"net/url"
 	"os"
@@ -94,7 +94,7 @@
 func statusCodeError(resp *http.Response) error {
 	if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
 		// error is from pprof endpoint
-		if body, err := ioutil.ReadAll(resp.Body); err == nil {
+		if body, err := io.ReadAll(resp.Body); err == nil {
 			return fmt.Errorf("server response: %s - %s", resp.Status, body)
 		}
 	}
diff --git a/internal/stdlib/testdata/v1.12.5/src/encoding/json/bench_test.go b/internal/stdlib/testdata/v1.12.5/src/encoding/json/bench_test.go
index 72cb349..6a72e77 100644
--- a/internal/stdlib/testdata/v1.12.5/src/encoding/json/bench_test.go
+++ b/internal/stdlib/testdata/v1.12.5/src/encoding/json/bench_test.go
@@ -15,7 +15,7 @@
 	"compress/gzip"
 	"fmt"
 	"internal/testenv"
-	"io/ioutil"
+	"io"
 	"os"
 	"reflect"
 	"runtime"
@@ -52,7 +52,7 @@
 	if err != nil {
 		panic(err)
 	}
-	data, err := ioutil.ReadAll(gz)
+	data, err := io.ReadAll(gz)
 	if err != nil {
 		panic(err)
 	}
@@ -88,7 +88,7 @@
 		b.StartTimer()
 	}
 	b.RunParallel(func(pb *testing.PB) {
-		enc := NewEncoder(ioutil.Discard)
+		enc := NewEncoder(io.Discard)
 		for pb.Next() {
 			if err := enc.Encode(&codeStruct); err != nil {
 				b.Fatal("Encode:", err)
diff --git a/internal/stdlib/testdata/v1.12.5/src/encoding/json/stream_test.go b/internal/stdlib/testdata/v1.12.5/src/encoding/json/stream_test.go
index aaf32e0..97eee72 100644
--- a/internal/stdlib/testdata/v1.12.5/src/encoding/json/stream_test.go
+++ b/internal/stdlib/testdata/v1.12.5/src/encoding/json/stream_test.go
@@ -7,7 +7,6 @@
 import (
 	"bytes"
 	"io"
-	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -177,7 +176,7 @@
 	if m.Name != "Gopher" {
 		t.Errorf("Name = %q; want Gopher", m.Name)
 	}
-	rest, err := ioutil.ReadAll(d.Buffered())
+	rest, err := io.ReadAll(d.Buffered())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -280,7 +279,7 @@
 	v := &T{"foo", "bar"}
 	b.RunParallel(func(pb *testing.PB) {
 		for pb.Next() {
-			if err := NewEncoder(ioutil.Discard).Encode(v); err != nil {
+			if err := NewEncoder(io.Discard).Encode(v); err != nil {
 				b.Fatal(err)
 			}
 		}
diff --git a/internal/stdlib/testdata/v1.12.5/src/flag/flag_test.go b/internal/stdlib/testdata/v1.12.5/src/flag/flag_test.go
index 0d9491c..dc1b2b5 100644
--- a/internal/stdlib/testdata/v1.12.5/src/flag/flag_test.go
+++ b/internal/stdlib/testdata/v1.12.5/src/flag/flag_test.go
@@ -9,7 +9,6 @@
 	. "flag"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"os"
 	"sort"
 	"strconv"
@@ -496,7 +495,7 @@
 func TestParseError(t *testing.T) {
 	for _, typ := range []string{"bool", "int", "int64", "uint", "uint64", "float64", "duration"} {
 		fs := NewFlagSet("parse error test", ContinueOnError)
-		fs.SetOutput(ioutil.Discard)
+		fs.SetOutput(io.Discard)
 		_ = fs.Bool("bool", false, "")
 		_ = fs.Int("int", 0, "")
 		_ = fs.Int64("int64", 0, "")
@@ -527,7 +526,7 @@
 	}
 	for _, arg := range bad {
 		fs := NewFlagSet("parse error test", ContinueOnError)
-		fs.SetOutput(ioutil.Discard)
+		fs.SetOutput(io.Discard)
 		_ = fs.Int("int", 0, "")
 		_ = fs.Int64("int64", 0, "")
 		_ = fs.Uint("uint", 0, "")
diff --git a/internal/testing/integration/frontend_doc_render_test.go b/internal/testing/integration/frontend_doc_render_test.go
index cea88e2..3c58658 100644
--- a/internal/testing/integration/frontend_doc_render_test.go
+++ b/internal/testing/integration/frontend_doc_render_test.go
@@ -9,7 +9,7 @@
 	"bytes"
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"strings"
 	"testing"
@@ -54,7 +54,7 @@
 	if resp.StatusCode != http.StatusOK {
 		t.Fatalf("%s: status code %d", url, resp.StatusCode)
 	}
-	content, err := ioutil.ReadAll(resp.Body)
+	content, err := io.ReadAll(resp.Body)
 	if err != nil {
 		t.Fatalf("%s: %v", url, err)
 	}
diff --git a/internal/testing/integration/integration_test.go b/internal/testing/integration/integration_test.go
index b414227..e4f78d6 100644
--- a/internal/testing/integration/integration_test.go
+++ b/internal/testing/integration/integration_test.go
@@ -7,7 +7,7 @@
 import (
 	"context"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
 	"sort"
 	"strings"
@@ -145,9 +145,9 @@
 	if resp.StatusCode != http.StatusOK {
 		return nil, fmt.Errorf("http.Get(%q): status: %d, want %d", url, resp.StatusCode, http.StatusOK)
 	}
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
-		return nil, fmt.Errorf("ioutil.ReadAll(): %v", err)
+		return nil, fmt.Errorf("io.ReadAll(): %v", err)
 	}
 	return body, nil
 }
diff --git a/internal/testing/testhelper/testhelper.go b/internal/testing/testhelper/testhelper.go
index 5492c12..52e7003 100644
--- a/internal/testing/testhelper/testhelper.go
+++ b/internal/testing/testhelper/testhelper.go
@@ -13,7 +13,6 @@
 	"crypto/tls"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/http/httptest"
@@ -117,7 +116,7 @@
 // local fetching, and returns the directory.
 func CreateTestDirectory(files map[string]string) (_ string, err error) {
 	defer derrors.Wrap(&err, "CreateTestDirectory(files)")
-	tempDir, err := ioutil.TempDir("", "")
+	tempDir, err := os.MkdirTemp("", "")
 	if err != nil {
 		return "", err
 	}
@@ -159,14 +158,14 @@
 
 func writeGolden(t *testing.T, name string, data string) {
 	filename := filepath.Join("testdata", name)
-	if err := ioutil.WriteFile(filename, []byte(data), 0644); err != nil {
+	if err := os.WriteFile(filename, []byte(data), 0644); err != nil {
 		t.Fatal(err)
 	}
 	t.Logf("wrote %s", filename)
 }
 
 func readGolden(t *testing.T, name string) string {
-	data, err := ioutil.ReadFile(filepath.Join("testdata", name))
+	data, err := os.ReadFile(filepath.Join("testdata", name))
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/staticcheck.conf b/staticcheck.conf
new file mode 100644
index 0000000..c0e14e7
--- /dev/null
+++ b/staticcheck.conf
@@ -0,0 +1,11 @@
+# This file configures staticheck to ignore SA1019 throughout
+# the pkgsite codebase.
+
+# TODO(jamalcarvalho): fix SA1019.
+# The text for this check reads:
+# strings.Title has been deprecated since Go 1.18 and an alternative
+# has been available since Go 1.0: The rule Title uses for word
+# boundaries does not handle Unicode punctuation properly.
+# Use golang.org/x/text/cases instead.
+
+checks = ["inherit", "-SA1019"]