mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-01 07:22:04 +01:00
This migrates an internal tool to open source so that we can run it on the tailscale.com module as well. We add the "util/safediff" also as a dependency of the tool. This PR does not yet set up a CI to run this analyzer. Updates tailscale/corp#791 Signed-off-by: Joe Tsai <joetsai@digital-static.net>
125 lines
4.0 KiB
Go
125 lines
4.0 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// The jsonimports tool formats all Go source files in the repository
|
|
// to enforce that "json" imports are consistent.
|
|
//
|
|
// With Go 1.25, the "encoding/json/v2" and "encoding/json/jsontext"
|
|
// packages are now available under goexperiment.jsonv2.
|
|
// This leads to possible confusion over the following:
|
|
//
|
|
// - "encoding/json"
|
|
// - "encoding/json/v2"
|
|
// - "encoding/json/jsontext"
|
|
// - "github.com/go-json-experiment/json/v1"
|
|
// - "github.com/go-json-experiment/json"
|
|
// - "github.com/go-json-experiment/json/jsontext"
|
|
//
|
|
// In order to enforce consistent usage, we apply the following rules:
|
|
//
|
|
// - Until the Go standard library formally accepts "encoding/json/v2"
|
|
// and "encoding/json/jsontext" into the standard library
|
|
// (i.e., they are no longer considered experimental),
|
|
// we forbid any code from directly importing those packages.
|
|
// Go code should instead import "github.com/go-json-experiment/json"
|
|
// and "github.com/go-json-experiment/json/jsontext".
|
|
// The latter packages contain aliases to the standard library
|
|
// if built on Go 1.25 with the goexperiment.jsonv2 tag specified.
|
|
//
|
|
// - Imports of "encoding/json" or "github.com/go-json-experiment/json/v1"
|
|
// must be explicitly imported under the package name "jsonv1".
|
|
// If both packages need to be imported, then the former should
|
|
// be imported under the package name "jsonv1std".
|
|
//
|
|
// - Imports of "github.com/go-json-experiment/json"
|
|
// must be explicitly imported under the package name "jsonv2".
|
|
//
|
|
// The latter two rules exist to provide clarity when reading code.
|
|
// Without them, it is unclear whether "json.Marshal" refers to v1 or v2.
|
|
// With them, however, it is clear that "jsonv1.Marshal" is calling v1 and
|
|
// that "jsonv2.Marshal" is calling v2.
|
|
//
|
|
// TODO(@joetsai): At this present moment, there is no guidance given on
|
|
// whether to use v1 or v2 for newly written Go source code.
|
|
// I will write a document in the near future providing more guidance.
|
|
// Feel free to continue using v1 "encoding/json" as you are accustomed to.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
|
|
"tailscale.com/syncs"
|
|
"tailscale.com/util/must"
|
|
"tailscale.com/util/safediff"
|
|
)
|
|
|
|
func main() {
|
|
update := flag.Bool("update", false, "update all Go source files")
|
|
flag.Parse()
|
|
|
|
// Change working directory to Git repository root.
|
|
repoRoot := strings.TrimSuffix(string(must.Get(exec.Command(
|
|
"git", "rev-parse", "--show-toplevel",
|
|
).Output())), "\n")
|
|
must.Do(os.Chdir(repoRoot))
|
|
|
|
// Iterate over all indexed files in the Git repository.
|
|
var printMu sync.Mutex
|
|
var group sync.WaitGroup
|
|
sema := syncs.NewSemaphore(runtime.NumCPU())
|
|
var numDiffs int
|
|
files := string(must.Get(exec.Command("git", "ls-files").Output()))
|
|
for file := range strings.Lines(files) {
|
|
sema.Acquire()
|
|
group.Go(func() {
|
|
defer sema.Release()
|
|
|
|
// Ignore non-Go source files.
|
|
file = strings.TrimSuffix(file, "\n")
|
|
if !strings.HasSuffix(file, ".go") {
|
|
return
|
|
}
|
|
|
|
// Format all "json" imports in the Go source file.
|
|
srcIn := must.Get(os.ReadFile(file))
|
|
srcOut := mustFormatFile(srcIn)
|
|
|
|
// Print differences with each formatted file.
|
|
if !bytes.Equal(srcIn, srcOut) {
|
|
numDiffs++
|
|
|
|
printMu.Lock()
|
|
fmt.Println(file)
|
|
lines, _ := safediff.Lines(string(srcIn), string(srcOut), -1)
|
|
for line := range strings.Lines(lines) {
|
|
fmt.Print("\t", line)
|
|
}
|
|
fmt.Println()
|
|
printMu.Unlock()
|
|
|
|
// If -update is specified, write out the changes.
|
|
if *update {
|
|
mode := must.Get(os.Stat(file)).Mode()
|
|
must.Do(os.WriteFile(file, srcOut, mode))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
group.Wait()
|
|
|
|
// Report whether any differences were detected.
|
|
if numDiffs > 0 && !*update {
|
|
fmt.Printf(`%d files with "json" imports that need formatting`+"\n", numDiffs)
|
|
fmt.Println("Please run:")
|
|
fmt.Println("\t./tool/go run tailscale.com/cmd/jsonimports -update")
|
|
os.Exit(1)
|
|
}
|
|
}
|