feat_: LogOnPanic linter

This commit is contained in:
Igor Sirotin 2024-10-19 22:31:07 +03:00
parent 3179532b64
commit e889114df6
No known key found for this signature in database
GPG Key ID: 425E227CAAB81F95
11 changed files with 741 additions and 1 deletions

View File

@ -407,7 +407,10 @@ canary-test: node-canary
# TODO: uncomment that!
#_assets/scripts/canary_test_mailservers.sh ./config/cli/fleet-eth.prod.json
lint: generate
lint-panics: generate
go run ./cmd/lint-panics --test=false ./...
lint: generate lint-panics
golangci-lint run ./...
ci: generate lint canary-test test-unit test-e2e ##@tests Run all linters and tests at once

View File

@ -0,0 +1,101 @@
package gopls
import (
"context"
"go.lsp.dev/protocol"
"go.uber.org/zap"
)
type DummyClient struct {
logger *zap.Logger
}
func NewDummyClient(logger *zap.Logger) *DummyClient {
return &DummyClient{
logger: nil, //logger.Named("client"),
}
}
func (d *DummyClient) Progress(ctx context.Context, params *protocol.ProgressParams) (err error) {
if d.logger != nil {
d.logger.Debug("client: Progress", zap.Any("params", params))
}
return
}
func (d *DummyClient) WorkDoneProgressCreate(ctx context.Context, params *protocol.WorkDoneProgressCreateParams) (err error) {
if d.logger != nil {
d.logger.Debug("client: WorkDoneProgressCreate")
}
return nil
}
func (d *DummyClient) LogMessage(ctx context.Context, params *protocol.LogMessageParams) (err error) {
if d.logger != nil {
d.logger.Debug("client: LogMessage", zap.Any("message", params))
}
return nil
}
func (d *DummyClient) PublishDiagnostics(ctx context.Context, params *protocol.PublishDiagnosticsParams) (err error) {
if d.logger != nil {
d.logger.Debug("client: PublishDiagnostics")
}
return nil
}
func (d *DummyClient) ShowMessage(ctx context.Context, params *protocol.ShowMessageParams) (err error) {
if d.logger != nil {
d.logger.Debug("client: ShowMessage", zap.Any("message", params))
}
return nil
}
func (d *DummyClient) ShowMessageRequest(ctx context.Context, params *protocol.ShowMessageRequestParams) (result *protocol.MessageActionItem, err error) {
if d.logger != nil {
d.logger.Debug("client: ShowMessageRequest", zap.Any("message", params))
}
return nil, nil
}
func (d *DummyClient) Telemetry(ctx context.Context, params interface{}) (err error) {
if d.logger != nil {
d.logger.Debug("client: Telemetry")
}
return nil
}
func (d *DummyClient) RegisterCapability(ctx context.Context, params *protocol.RegistrationParams) (err error) {
if d.logger != nil {
d.logger.Debug("client: RegisterCapability")
}
return nil
}
func (d *DummyClient) UnregisterCapability(ctx context.Context, params *protocol.UnregistrationParams) (err error) {
if d.logger != nil {
d.logger.Debug("client: UnregisterCapability")
}
return nil
}
func (d *DummyClient) ApplyEdit(ctx context.Context, params *protocol.ApplyWorkspaceEditParams) (result bool, err error) {
if d.logger != nil {
d.logger.Debug("client: ApplyEdit")
}
return false, nil
}
func (d *DummyClient) Configuration(ctx context.Context, params *protocol.ConfigurationParams) (result []interface{}, err error) {
if d.logger != nil {
d.logger.Debug("client: Configuration")
}
return nil, nil
}
func (d *DummyClient) WorkspaceFolders(ctx context.Context) (result []protocol.WorkspaceFolder, err error) {
if d.logger != nil {
d.logger.Debug("client: WorkspaceFolders")
}
return nil, nil
}

View File

@ -0,0 +1,150 @@
package gopls
import (
"os/exec"
"github.com/pkg/errors"
"go.lsp.dev/jsonrpc2"
"go.lsp.dev/protocol"
"context"
"time"
"go.uber.org/zap"
"go.lsp.dev/uri"
)
type Connection struct {
logger *zap.Logger
server protocol.Server
cmd *exec.Cmd
conn jsonrpc2.Conn
}
func NewGoplsClient(ctx context.Context, logger *zap.Logger, rootDir string) *Connection {
var err error
logger.Debug("initializing gopls client")
gopls := &Connection{
logger: logger,
}
client := NewDummyClient(logger)
// Create a JSON-RPC connection using stdin and stdout
gopls.cmd = exec.Command("gopls", "serve")
stdin, err := gopls.cmd.StdinPipe()
if err != nil {
logger.Error("Failed to get stdin pipe", zap.Error(err))
panic(err)
}
stdout, err := gopls.cmd.StdoutPipe()
if err != nil {
logger.Error("Failed to get stdout pipe", zap.Error(err))
panic(err)
}
err = gopls.cmd.Start()
if err != nil {
logger.Error("Failed to start gopls", zap.Error(err))
panic(err)
}
stream := jsonrpc2.NewStream(&CombinedReadWriteCloser{
stdin: stdin,
stdout: stdout,
})
ctx, gopls.conn, gopls.server = protocol.NewClient(ctx, client, stream, logger)
initParams := protocol.InitializeParams{
RootURI: uri.From("file", "", rootDir, "", ""),
InitializationOptions: map[string]interface{}{
"symbolMatcher": "FastFuzzy",
},
}
_, err = gopls.server.Initialize(ctx, &initParams)
if err != nil {
logger.Error("Error during initialize", zap.Error(err))
panic(err)
}
// Step 2: Send 'initialized' notification
err = gopls.server.Initialized(ctx, &protocol.InitializedParams{})
if err != nil {
logger.Error("Error during initialized", zap.Error(err))
panic(err)
}
return gopls
}
func (gopls *Connection) Definition(ctx context.Context, filePath string, lineNumber int, charPosition int) (string, int, error) {
// NOTE: gopls uses 0-based line and column numbers
defFile, defLine, err := gopls.definition(ctx, filePath, lineNumber-1, charPosition-1)
return defFile, defLine + 1, err
}
func (gopls *Connection) definition(ctx context.Context, filePath string, lineNumber int, charPosition int) (string, int, error) {
// Define the file URI and position where the function/method is invoked
fileURI := protocol.DocumentURI("file://" + filePath) // Replace with actual file URI
line := lineNumber // Line number where the function is called
character := charPosition // Character (column) where the function is called
// Send the definition request
params := &protocol.DefinitionParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: fileURI,
},
Position: protocol.Position{
Line: uint32(line),
Character: uint32(character),
},
},
}
// Create context with a timeout to avoid hanging
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
locations, err := gopls.server.Definition(ctx, params)
if err != nil {
return "", 0, errors.Wrap(err, "failed to fetch definition")
}
if len(locations) == 0 {
return "", 0, errors.New("no definition found")
}
location := locations[0]
return location.URI.Filename(), int(location.Range.Start.Line), nil
}
func (gopls *Connection) DidOpen(ctx context.Context, path string, content string, logger *zap.Logger) {
err := gopls.server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
TextDocument: protocol.TextDocumentItem{
URI: protocol.DocumentURI(path),
LanguageID: "go",
Version: 1,
Text: content,
},
})
if err != nil {
logger.Error("failed to call DidOpen", zap.Error(err))
}
}
func (gopls *Connection) DidClose(ctx context.Context, path string, lgoger *zap.Logger) {
err := gopls.server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.DocumentURI(path),
},
})
if err != nil {
lgoger.Error("failed to call DidClose", zap.Error(err))
}
}

View File

@ -0,0 +1,29 @@
package gopls
import "io"
// CombinedReadWriteCloser combines stdin and stdout into one interface.
type CombinedReadWriteCloser struct {
stdin io.WriteCloser
stdout io.ReadCloser
}
// Write writes data to stdin.
func (c *CombinedReadWriteCloser) Write(p []byte) (n int, err error) {
return c.stdin.Write(p)
}
// Read reads data from stdout.
func (c *CombinedReadWriteCloser) Read(p []byte) (n int, err error) {
return c.stdout.Read(p)
}
// Close closes both stdin and stdout.
func (c *CombinedReadWriteCloser) Close() error {
err1 := c.stdin.Close()
err2 := c.stdout.Close()
if err1 != nil {
return err1
}
return err2
}

100
cmd/lint-panics/main.go Normal file
View File

@ -0,0 +1,100 @@
package main
import (
"os"
"time"
"context"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
gopls2 "github.com/status-im/status-go/cmd/lint-panics/gopls"
"github.com/status-im/status-go/cmd/lint-panics/processor"
"golang.org/x/tools/go/analysis/singlechecker"
"golang.org/x/tools/go/analysis"
"go/ast"
"fmt"
"path/filepath"
"strings"
)
func main() {
logger := buildLogger()
if len(os.Args) == 0 {
logger.Error("Usage: go run main.go <directory>")
os.Exit(1)
}
// Dirty hack to get the root directory for gopls
lastArg := os.Args[len(os.Args)-1]
dir, err := getRootAbsolutePath(lastArg)
if err != nil {
logger.Error("failed to get root directory", zap.Error(err))
os.Exit(1)
}
logger.Info("starting analysis...", zap.String("directory", dir))
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
gopls := gopls2.NewGoplsClient(ctx, logger, dir)
analyzer := &analysis.Analyzer{
Name: "logpanics",
Doc: "reports missing defer call to LogOnPanic",
Run: func(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
p := processor.NewProcessor(logger, pass, gopls)
ast.Inspect(file, func(n ast.Node) bool {
p.ProcessNode(ctx, n)
return true
})
}
return nil, nil
},
}
singlechecker.Main(analyzer)
}
func buildLogger() *zap.Logger {
// Initialize logger with colors
loggerConfig := zap.NewDevelopmentConfig()
loggerConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
loggerConfig.Level = zap.NewAtomicLevelAt(zap.ErrorLevel)
loggerConfig.Development = false
loggerConfig.DisableStacktrace = true
logger, err := loggerConfig.Build()
if err != nil {
fmt.Printf("failed to initialize logger: %s", err.Error())
os.Exit(1)
}
return logger.Named("main")
}
func getRootAbsolutePath(path string) (string, error) {
// Get the absolute path of the current working directory
workingDir, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get working directory: %w", err)
}
if strings.HasSuffix(path, "...") {
path = strings.TrimSuffix(path, "...")
}
// Check if the given path is absolute
if !filepath.IsAbs(path) {
// If the path is not absolute, join it with the working directory
path = filepath.Join(workingDir, path)
}
// Convert the path to an absolute path (cleans the result)
absolutePath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("failed to convert to absolute path: %w", err)
}
return absolutePath, nil
}

View File

@ -0,0 +1,46 @@
package main
import (
"testing"
gopls2 "github.com/status-im/status-go/cmd/lint-panics/gopls"
"github.com/status-im/status-go/cmd/lint-panics/processor"
"time"
"context"
"go.uber.org/zap"
"runtime"
"path"
"github.com/stretchr/testify/require"
)
func Test(t *testing.T) {
loggerConfig := zap.NewDevelopmentConfig()
loggerConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
loggerConfig.Development = false
loggerConfig.DisableStacktrace = true
logger := zap.Must(loggerConfig.Build())
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
_, filePath, _, _ := runtime.Caller(0)
testDir := path.Join(path.Dir(filePath), "test")
testFie := path.Join(testDir, "test.go")
gopls := gopls2.NewGoplsClient(ctx, logger, testDir)
parser := processor.NewProcessor(logger, gopls)
result, err := parser.Run(ctx, testFie)
require.NoError(t, err)
paths := result.Paths()
require.Len(t, paths, 5)
expectedPaths := []string{
path.Join(testDir, "test.go:39"),
path.Join(testDir, "test.go:47"),
path.Join(testDir, "test.go:55"),
path.Join(testDir, "test.go:63"),
path.Join(testDir, "test.go:27"),
}
require.EqualValues(t, expectedPaths, paths)
}

View File

@ -0,0 +1,203 @@
package processor
import (
"os"
"go.uber.org/zap"
"fmt"
"go/ast"
gotoken "go/token"
goparser "go/parser"
"github.com/pkg/errors"
"github.com/status-im/status-go/cmd/lint-panics/utils"
"context"
"golang.org/x/tools/go/analysis"
)
const Pattern = "LogOnPanic"
type Processor struct {
logger *zap.Logger
lsp LSP
pass *analysis.Pass
}
type LSP interface {
Definition(context.Context, string, int, int) (string, int, error)
}
func NewProcessor(logger *zap.Logger, pass *analysis.Pass, lsp LSP) *Processor {
return &Processor{
logger: logger.Named("processor"),
pass: pass,
lsp: lsp,
}
}
func (p *Processor) Run(ctx context.Context, path string) error {
logger := p.logger.With(zap.String("file", path))
file, err := p.parseFile(path)
if err != nil {
logger.Error("failed to parse file", zap.Error(err))
return err
}
// Traverse the AST to find goroutines
ast.Inspect(file, func(n ast.Node) bool {
p.ProcessNode(ctx, n)
return true
})
return nil
}
func (p *Processor) ProcessNode(ctx context.Context, n ast.Node) {
// Check if the node is a GoStmt (which represents a 'go' statement)
goStmt, ok := n.(*ast.GoStmt)
if !ok {
return
}
switch fun := goStmt.Call.Fun.(type) {
case *ast.FuncLit:
// anonymous function
pos := p.pass.Fset.Position(fun.Pos())
logger := p.logger.With(
utils.ZapURI(pos.Filename, pos.Line),
zap.Int("column", pos.Column),
)
logger.Debug("found anonymous goroutine")
if err := p.checkGoroutine(fun.Body); err != nil {
p.logLinterError(fun.Pos(), err)
p.pass.Reportf(fun.Pos(), "missing %s()", Pattern)
}
case *ast.SelectorExpr:
// method call
pos := p.pass.Fset.Position(fun.Sel.Pos())
p.logger.Info("found method call as goroutine",
zap.String("methodName", fun.Sel.Name),
utils.ZapURI(pos.Filename, pos.Line),
zap.Int("column", pos.Column),
)
defPos, err := p.checkGoroutineDefinition(ctx, pos)
if err != nil {
p.logLinterError(defPos, err)
p.pass.Reportf(defPos, "missing %s(), goroutine at %s", Pattern, utils.PositionURI(pos))
}
case *ast.Ident:
// function call
pos := p.pass.Fset.Position(fun.Pos())
p.logger.Info("found function call as goroutine",
zap.String("functionName", fun.Name),
utils.ZapURI(pos.Filename, pos.Line),
zap.Int("column", pos.Column),
)
defPos, err := p.checkGoroutineDefinition(ctx, pos)
if err != nil {
p.logLinterError(defPos, err)
p.pass.Reportf(defPos, "missing %s(), called as goroutine at %s", Pattern, utils.PositionURI(pos))
}
default:
p.logger.Error("unexpected goroutine type",
zap.String("type", fmt.Sprintf("%T", fun)),
)
}
return
}
func (p *Processor) parseFile(path string) (*ast.File, error) {
logger := p.logger.With(zap.String("path", path))
src, err := os.ReadFile(path)
if err != nil {
logger.Error("failed to open file", zap.Error(err))
}
file, err := goparser.ParseFile(p.pass.Fset, path, src, 0)
if err != nil {
logger.Error("failed to parse file", zap.Error(err))
return nil, err
}
return file, nil
}
func (p *Processor) checkGoroutine(body *ast.BlockStmt) error {
if body == nil {
p.logger.Warn("missing function body")
return nil
}
if len(body.List) == 0 {
return nil
}
deferStatement, ok := body.List[0].(*ast.DeferStmt)
if !ok {
return errors.New("first statement is not defer")
}
selectorExpr, ok := deferStatement.Call.Fun.(*ast.SelectorExpr)
if !ok {
return errors.New("first statement call is not a selector")
}
firstLineFunName := selectorExpr.Sel.Name
if firstLineFunName != Pattern {
return errors.Errorf("first statement is not %s", Pattern)
}
return nil
}
func (p *Processor) getFunctionBody(node ast.Node, lineNumber int) (body *ast.BlockStmt, pos gotoken.Pos) {
ast.Inspect(node, func(n ast.Node) bool {
// Check if the node is a function declaration
funcDecl, ok := n.(*ast.FuncDecl)
if !ok {
return true
}
if p.pass.Fset.Position(n.Pos()).Line != lineNumber {
return true
}
body = funcDecl.Body
pos = n.Pos()
return false
})
return body, pos
}
func (p *Processor) checkGoroutineDefinition(ctx context.Context, pos gotoken.Position) (gotoken.Pos, error) {
defFilePath, defLineNumber, err := p.lsp.Definition(ctx, pos.Filename, pos.Line, pos.Column)
if err != nil {
p.logger.Error("failed to find function definition", zap.Error(err))
return 0, err
}
file, err := p.parseFile(defFilePath)
if err != nil {
p.logger.Error("failed to parse file", zap.Error(err))
return 0, err
}
body, defPosition := p.getFunctionBody(file, defLineNumber)
return defPosition, p.checkGoroutine(body)
}
func (p *Processor) logLinterError(pos gotoken.Pos, err error) {
position := p.pass.Fset.Position(pos)
message := fmt.Sprintf("missing %s()", Pattern)
p.logger.Warn(message,
utils.ZapURI(position.Filename, position.Line),
zap.String("details", err.Error()))
}

View File

@ -0,0 +1,65 @@
package test
import (
gocommon "github.com/status-im/status-go/common"
"fmt"
)
type Test struct {
}
func init() {
t := Test{}
go t.Empty()
go t.Foo()
go t.FooOK()
go t.FooNotDefer()
go Empty()
go Bar()
go BarOK()
go BarNotDefer()
go func() {
defer gocommon.LogOnPanic()
}()
go func() {
fmt.Println("anon")
}()
go func() {}()
}
func (p *Test) Empty() {
}
func (p *Test) Foo() {
defer fmt.Println("Foo")
}
func (p *Test) FooOK() {
defer gocommon.LogOnPanic()
}
func (p *Test) FooNotDefer() {
gocommon.LogOnPanic()
}
func Empty() {
}
func Bar() {
defer fmt.Println("Bar")
}
func BarOK() {
defer gocommon.LogOnPanic()
}
func BarNotDefer() {
gocommon.LogOnPanic()
}

View File

@ -0,0 +1,24 @@
package utils
import (
"strconv"
"go.uber.org/zap/zapcore"
"go.uber.org/zap"
gotoken "go/token"
)
func PositionURI(pos gotoken.Position) string {
return URI(pos.Filename, pos.Line)
}
func URI(path string, line int) string {
return path + ":" + strconv.Itoa(line)
}
func ZapURI(path string, line int) zap.Field {
return zap.Field{
Type: zapcore.StringType,
Key: "uri",
String: URI(path, line),
}
}

6
go.mod
View File

@ -99,6 +99,9 @@ require (
github.com/wk8/go-ordered-map/v2 v2.1.7
github.com/yeqown/go-qrcode/v2 v2.2.1
github.com/yeqown/go-qrcode/writer/standard v1.2.1
go.lsp.dev/jsonrpc2 v0.10.0
go.lsp.dev/protocol v0.12.0
go.lsp.dev/uri v0.3.0
go.uber.org/mock v0.4.0
go.uber.org/multierr v1.11.0
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
@ -253,6 +256,8 @@ require (
github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 // indirect
github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.3.4 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
@ -275,6 +280,7 @@ require (
github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/dig v1.18.0 // indirect
go.uber.org/fx v1.22.2 // indirect

13
go.sum
View File

@ -1936,6 +1936,10 @@ github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc=
github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM=
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@ -2221,6 +2225,14 @@ go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lL
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.lsp.dev/jsonrpc2 v0.10.0 h1:Pr/YcXJoEOTMc/b6OTmcR1DPJ3mSWl/SWiU1Cct6VmI=
go.lsp.dev/jsonrpc2 v0.10.0/go.mod h1:fmEzIdXPi/rf6d4uFcayi8HpFP1nBF99ERP1htC72Ac=
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2 h1:hCzQgh6UcwbKgNSRurYWSqh8MufqRRPODRBblutn4TE=
go.lsp.dev/pkg v0.0.0-20210717090340-384b27a52fb2/go.mod h1:gtSHRuYfbCT0qnbLnovpie/WEmqyJ7T4n6VXiFMBtcw=
go.lsp.dev/protocol v0.12.0 h1:tNprUI9klQW5FAFVM4Sa+AbPFuVQByWhP1ttNUAjIWg=
go.lsp.dev/protocol v0.12.0/go.mod h1:Qb11/HgZQ72qQbeyPfJbu3hZBH23s1sr4st8czGeDMQ=
go.lsp.dev/uri v0.3.0 h1:KcZJmh6nFIBeJzTugn5JTU6OOyG0lDOo3R9KwTxTYbo=
go.lsp.dev/uri v0.3.0/go.mod h1:P5sbO1IQR+qySTWOCnhnK7phBx+W3zbLqSMDJNTw88I=
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
@ -2677,6 +2689,7 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=