diff --git a/internal/env/env.go b/internal/env/env.go index d527df0..9b635dc 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -6,6 +6,11 @@ import ( "path/filepath" ) +var GlobalEnv = &Env{ + GNOROOT: "", + GNOHOME: "", +} + type Env struct { GNOROOT string GNOHOME string diff --git a/internal/lsp/cache.go b/internal/lsp/cache.go index 29e11d2..1f2cb89 100644 --- a/internal/lsp/cache.go +++ b/internal/lsp/cache.go @@ -29,8 +29,24 @@ func NewCache() *Cache { } func (s *server) UpdateCache(pkgPath string) { + // TODO: Unify `GetPackageInfo()` and `PackageFromDir()`? pkg, err := PackageFromDir(pkgPath, false) - if err == nil { - s.cache.pkgs.Set(pkgPath, pkg) + if err != nil { + return } + pkginfo, err := GetPackageInfo(pkgPath) + if err != nil { + return + } + + tc, errs := NewTypeCheck() + tc.cfg.Importer = tc // set typeCheck importer + res := pkginfo.TypeCheck(tc) + + // Mutate `res.err` with `errs`, as `res.err` contains + // only the first error found. + res.err = *errs + + pkg.TypeCheckResult = res // set typeCheck result + s.cache.pkgs.Set(pkgPath, pkg) } diff --git a/internal/lsp/check.go b/internal/lsp/check.go index 60a0d89..62f0054 100644 --- a/internal/lsp/check.go +++ b/internal/lsp/check.go @@ -1,16 +1,21 @@ package lsp import ( + "errors" "fmt" "go/ast" "go/parser" "go/token" "go/types" + "log/slog" + "math" "os" "path/filepath" + "strconv" "strings" "github.com/gnolang/gno/gnovm/pkg/gnomod" + "github.com/harry-hov/gnopls/internal/env" "go.uber.org/multierr" ) @@ -28,14 +33,22 @@ type PackageGetter interface { GetPackageInfo(path string) *PackageInfo } +// GetPackageInfo accepts path(abs) or importpath and returns +// PackageInfo if found. +// Note: it doesn't work for relative path func GetPackageInfo(path string) (*PackageInfo, error) { - // TODO: fix - if strings.HasPrefix(path, "/") { - // No op - } else if strings.HasPrefix(path, "gno.land/") { - path = "/Users/harry/Desktop/work/gno/examples/" + path - } else { - path = "/Users/harry/Desktop/work/gno/gnovm/stdlibs/" + path + // if not absolute, assume its import path + if !filepath.IsAbs(path) { + if env.GlobalEnv.GNOROOT == "" { + // if GNOROOT is unknown, we can't locate the + // `examples` and `stdlibs` + return nil, errors.New("GNOROOT not set") + } + if strings.HasPrefix(path, "gno.land/") { // look in `examples` + path = filepath.Join(env.GlobalEnv.GNOROOT, "examples", path) + } else { // look into `stdlibs` + path = filepath.Join(env.GlobalEnv.GNOROOT, "gnovm", "stdlibs", path) + } } return getPackageInfo(path) } @@ -76,26 +89,28 @@ func getPackageInfo(path string) (*PackageInfo, error) { }, nil } -type TypeCheckResult struct { - pkg *types.Package - fset *token.FileSet - files []*ast.File - info *types.Info - err error -} - type TypeCheck struct { cache map[string]*TypeCheckResult cfg *types.Config } -// Unused, but satisfies the Importer interface. +func NewTypeCheck() (*TypeCheck, *error) { + var errs error + return &TypeCheck{ + cache: map[string]*TypeCheckResult{}, + cfg: &types.Config{ + Error: func(err error) { + errs = multierr.Append(errs, err) + }, + }, + }, &errs +} + func (tc *TypeCheck) Import(path string) (*types.Package, error) { return tc.ImportFrom(path, "", 0) } -// ImportFrom returns the imported package for the given import -// path when imported by a package file located in dir. +// ImportFrom returns the imported package for the given import path func (tc *TypeCheck) ImportFrom(path, _ string, _ types.ImportMode) (*types.Package, error) { if pkg, ok := tc.cache[path]; ok { return pkg.pkg, pkg.err @@ -136,8 +151,37 @@ func (pi *PackageInfo) TypeCheck(tc *TypeCheck) *TypeCheckResult { files = append(files, pgf) } pkg, err := tc.cfg.Check(pi.ImportPath, fset, files, info) - if err != nil { - return &TypeCheckResult{err: err} + return &TypeCheckResult{pkg: pkg, fset: fset, files: files, info: info, err: err} +} + +type TypeCheckResult struct { + pkg *types.Package + fset *token.FileSet + files []*ast.File + info *types.Info + err error +} + +func (tcr *TypeCheckResult) Errors() []ErrorInfo { + errs := multierr.Errors(tcr.err) + res := make([]ErrorInfo, 0, len(errs)) + for _, err := range errs { + parts := strings.Split(err.Error(), ":") + if len(parts) < 4 { + slog.Error("TYPECHECK", "skipped", err) + } + filename := strings.TrimSpace(parts[0]) + line, _ := strconv.Atoi(strings.TrimSpace(parts[1])) + col, _ := strconv.Atoi(strings.TrimSpace(parts[2])) + msg := strings.TrimSpace(strings.Join(parts[3:], ":")) + res = append(res, ErrorInfo{ + FileName: filename, + Line: line, + Column: col, + Span: []int{col, math.MaxInt}, + Msg: msg, + Tool: "go/typecheck", + }) } - return &TypeCheckResult{pkg: pkg, fset: fset, files: files, info: info, err: nil} + return res } diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index 737cd07..fc56016 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -17,6 +17,13 @@ func (s *server) publishDiagnostics(ctx context.Context, conn jsonrpc2.Conn, fil if err != nil { return err } + pkg, ok := s.cache.pkgs.Get(filepath.Dir(string(file.URI.Filename()))) + if ok { + errs := pkg.TypeCheckResult.Errors() + if errs != nil { + errors = append(errors, errs...) + } + } mPublishDiagnosticParams := make(map[string]*protocol.PublishDiagnosticsParams) publishDiagnosticParams := make([]*protocol.PublishDiagnosticsParams, 0) diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 937ab23..c967118 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -26,16 +26,16 @@ type server struct { formatOpt tools.FormattingOption } -func BuildServerHandler(conn jsonrpc2.Conn, env *env.Env) jsonrpc2.Handler { +func BuildServerHandler(conn jsonrpc2.Conn, e *env.Env) jsonrpc2.Handler { dirs := []string{} - if env.GNOROOT != "" { - dirs = append(dirs, filepath.Join(env.GNOROOT, "examples")) - dirs = append(dirs, filepath.Join(env.GNOROOT, "gnovm/stdlibs")) + if e.GNOROOT != "" { + dirs = append(dirs, filepath.Join(e.GNOROOT, "examples")) + dirs = append(dirs, filepath.Join(e.GNOROOT, "gnovm/stdlibs")) } server := &server{ conn: conn, - env: env, + env: e, snapshot: NewSnapshot(), completionStore: InitCompletionStore(dirs), @@ -43,7 +43,7 @@ func BuildServerHandler(conn jsonrpc2.Conn, env *env.Env) jsonrpc2.Handler { formatOpt: tools.Gofumpt, } - + env.GlobalEnv = e return jsonrpc2.ReplyHandler(server.ServerHandler) }