Skip to content

Commit

Permalink
lsp/templating: gracefully unknown root
Browse files Browse the repository at this point in the history
This also disables templating for files in the root.

Fixes StyraInc#1164

Related to StyraInc#1141, but this needs
more work

Signed-off-by: Charlie Egan <[email protected]>
  • Loading branch information
charlieegan3 committed Oct 7, 2024
1 parent f70b892 commit 9dbd651
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 15 deletions.
37 changes: 31 additions & 6 deletions internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,10 @@ func (l *LanguageServer) StartDiagnosticsWorker(ctx context.Context) {
case <-ctx.Done():
return
case job := <-workspaceLintRuns:
// if there are no files in the cache, then there is no need to
// run the aggregate report. This can happen if the server is
// very slow to start up.
if len(l.cache.GetAllFiles()) == 0 {
// if there are no parsed modules in the cache, then there is
// no need to run the aggregate report. This can happen if the
// server is very slow to start up.
if len(l.cache.GetAllModules()) == 0 {
continue
}

Expand Down Expand Up @@ -989,7 +989,9 @@ func (l *LanguageServer) StartTemplateWorker(ctx context.Context) {
// determine the new contents for the file, if permitted
newContents, err := l.templateContentsForFile(job.URI)
if err != nil {
l.logError(fmt.Errorf("failed to template new file: %w", err))
if !errors.Is(err, &templatingInRootError{}) {
l.logError(fmt.Errorf("failed to template new file: %w", err))
}

continue
}
Expand Down Expand Up @@ -1083,6 +1085,12 @@ func (l *LanguageServer) StartWebServer(ctx context.Context) {
l.webServer.Start(ctx)
}

type templatingInRootError struct{}

func (*templatingInRootError) Error() string {
return "templating is not supported in the root of the workspace"
}

func (l *LanguageServer) templateContentsForFile(fileURI string) (string, error) {
content, ok := l.cache.GetFileContents(fileURI)
if !ok {
Expand All @@ -1093,6 +1101,11 @@ func (l *LanguageServer) templateContentsForFile(fileURI string) (string, error)
return "", errors.New("file already has contents, templating not allowed")
}

if filepath.Dir(uri.ToPath(l.clientIdentifier, fileURI)) ==
uri.ToPath(l.clientIdentifier, l.workspaceRootURI) {
return "", &templatingInRootError{}
}

diskContent, err := os.ReadFile(uri.ToPath(l.clientIdentifier, fileURI))
if err == nil {
// then we found the file on disk
Expand All @@ -1109,6 +1122,16 @@ func (l *LanguageServer) templateContentsForFile(fileURI string) (string, error)
return "", fmt.Errorf("failed to get potential roots during templating of new file: %w", err)
}

// handle the case where the root is unknown by providing the server's root
// dir as a defacto root. This allows templating of files when there is no
// known root, but the package could be determined based on the file path
// relative to the server's workspace root
if len(roots) == 1 && roots[0] == dir {
roots = []string{uri.ToPath(l.clientIdentifier, l.workspaceRootURI)}
}

roots = append(roots, uri.ToPath(l.clientIdentifier, l.workspaceRootURI))

longestPrefixRoot := ""

for _, root := range roots {
Expand Down Expand Up @@ -2028,7 +2051,9 @@ func (l *LanguageServer) handleTextDocumentFormatting(
// instead
if oldContent == "" {
newContent, err := l.templateContentsForFile(params.TextDocument.URI)
if err != nil {
if err != nil && errors.Is(err, &templatingInRootError{}) {
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("failed to template contents as a templating fallback: %w", err)
}

Expand Down
77 changes: 68 additions & 9 deletions internal/lsp/server_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lsp
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -41,14 +42,6 @@ func TestTemplateContentsForFile(t *testing.T) {
},
ExpectedError: "file on disk already has contents",
},
"empty file is templated as main when no root": {
FileKey: "foo/bar.rego",
CacheFileContents: "",
DiskContents: map[string]string{
"foo/bar.rego": "",
},
ExpectedContents: "package main\n\nimport rego.v1\n",
},
"empty file is templated based on root": {
FileKey: "foo/bar.rego",
CacheFileContents: "",
Expand Down Expand Up @@ -100,7 +93,6 @@ func TestTemplateContentsForFile(t *testing.T) {

ctx := context.Background()

// create a new language server
s := NewLanguageServer(ctx, &LanguageServerOptions{ErrorLog: newTestLogger(t)})
s.workspaceRootURI = uri.FromPath(clients.IdentifierGeneric, td)

Expand All @@ -124,6 +116,73 @@ func TestTemplateContentsForFile(t *testing.T) {
}
}

func TestTemplateContentsForFileInWorkspaceRoot(t *testing.T) {
t.Parallel()

td := t.TempDir()

err := os.MkdirAll(filepath.Join(td, ".regal"), 0o755)
if err != nil {
t.Fatalf("failed to create directory %s: %s", filepath.Join(td, ".regal"), err)
}

err = os.WriteFile(filepath.Join(td, ".regal/config.yaml"), []byte{}, 0o600)
if err != nil {
t.Fatalf("failed to create directory %s: %s", filepath.Join(td, ".regal"), err)
}

ctx := context.Background()

s := NewLanguageServer(ctx, &LanguageServerOptions{ErrorLog: newTestLogger(t)})
s.workspaceRootURI = uri.FromPath(clients.IdentifierGeneric, td)

fileURI := uri.FromPath(clients.IdentifierGeneric, filepath.Join(td, "foo.rego"))

s.cache.SetFileContents(fileURI, "")

_, err = s.templateContentsForFile(fileURI)
if err == nil {
t.Fatalf("expected error")
}

if !errors.Is(err, &templatingInRootError{}) {
t.Fatalf("expected error to be templatingInRootError, got %T", err)
}
}

func TestTemplateContentsForFileWithUnknownRoot(t *testing.T) {
t.Parallel()

td := t.TempDir()

ctx := context.Background()

s := NewLanguageServer(ctx, &LanguageServerOptions{ErrorLog: newTestLogger(t)})
s.workspaceRootURI = uri.FromPath(clients.IdentifierGeneric, td)

err := os.MkdirAll(filepath.Join(td, "foo"), 0o755)
if err != nil {
t.Fatalf("failed to create directory %s: %s", filepath.Join(td, "foo"), err)
}

fileURI := uri.FromPath(clients.IdentifierGeneric, filepath.Join(td, "foo/bar.rego"))

s.cache.SetFileContents(fileURI, "")

newContents, err := s.templateContentsForFile(fileURI)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

exp := `package foo
import rego.v1
`
if exp != newContents {
t.Errorf("unexpected content: %s, want %s", newContents, exp)
}
}

func TestNewFileTemplating(t *testing.T) {
t.Parallel()

Expand Down

0 comments on commit 9dbd651

Please sign in to comment.