Skip to content

Commit

Permalink
lsp: Rename on template (#1085)
Browse files Browse the repository at this point in the history
* lsp: Rename on template

Fixes #1061

Signed-off-by: Charlie Egan <charlie@styra.com>

* lsp: template edits for rename in one operation

Signed-off-by: Charlie Egan <charlie@styra.com>

* lsp: Include deleting dirs as part of wksp changes

Signed-off-by: Charlie Egan <charlie@styra.com>

* lsp: templating add docs

Signed-off-by: Charlie Egan <charlie@styra.com>

---------

Signed-off-by: Charlie Egan <charlie@styra.com>
  • Loading branch information
charlieegan3 authored Sep 10, 2024
1 parent 3bbfd1e commit 0c1ef19
Show file tree
Hide file tree
Showing 7 changed files with 594 additions and 47 deletions.
12 changes: 9 additions & 3 deletions docs/rules/idiomatic/directory-package-mismatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,17 @@ Editors integrating Regal's [language server](https://docs.styra.com/regal/langu
suggestions for idiomatic package paths based on the directory structure in which a policy resides. The image below
demonstrates a new policy being created inside an `authorization/rbac/roles` directory, and the editor
([via Regal](https://docs.styra.com/regal/language-server#code-completions)) suggesting the package path
`authorization.rbac.roles`.
`authorization.rbac.roles`.

<img
src={require('../../assets/rules/pkg_name_completion.png').default}
alt="Package path auto-completion in VS Code"/>
src={require('../../assets/rules/pkg_name_completion.png').default}
alt="Package path auto-completion in VS Code"/>

In addition, empty files will be be 'formatted' to have the correct package
based on the directory structure. Newly created Rego files are treated in much
the same way. When a new file is created, the server will send a series of edits
back to set the content. If `exclude-test-suffix` is set to `false`, the file
will also be moved if required to the `_test` directory for that package.

## Configuration Options

Expand Down
105 changes: 86 additions & 19 deletions internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,10 +478,7 @@ func (l *LanguageServer) StartCommandWorker(ctx context.Context) { // nolint:mai
break
}

// handle this ourselves as it's a rename and not a content edit
fixed = false

err = l.conn.Call(ctx, methodWorkspaceApplyEdit, renameParams, nil)
err := l.conn.Call(ctx, methodWorkspaceApplyEdit, renameParams, nil)
if err != nil {
l.logError(fmt.Errorf("failed %s notify: %v", methodWorkspaceApplyEdit, err.Error()))
}
Expand All @@ -496,6 +493,8 @@ func (l *LanguageServer) StartCommandWorker(ctx context.Context) { // nolint:mai
}
}

// handle this ourselves as it's a rename and not a content edit
fixed = false
case "regal.eval":
if len(params.Arguments) != 3 {
l.logError(fmt.Errorf("expected three arguments, got %d", len(params.Arguments)))
Expand Down Expand Up @@ -729,36 +728,93 @@ func (l *LanguageServer) StartTemplateWorker(ctx context.Context) {
case <-ctx.Done():
return
case evt := <-l.templateFile:
// determine the new contents for the file, if permitted
newContents, err := l.templateContentsForFile(evt.URI)
if err != nil {
l.logError(fmt.Errorf("failed to template new file: %w", err))

continue
}

// generate the edit params for the templating operation
templateParams := &types.ApplyWorkspaceEditParams{
Label: "Template new Rego file",
Edit: types.WorkspaceEdit{
DocumentChanges: []types.TextDocumentEdit{
{
TextDocument: types.OptionalVersionedTextDocumentIdentifier{URI: evt.URI},
Edits: ComputeEdits("", newContents),
},
// set the contents of the new file in the cache immediately as
// these must be update to date in order for fixRenameParams
// to work
l.cache.SetFileContents(evt.URI, newContents)

var edits []any

edits = append(edits, types.TextDocumentEdit{
TextDocument: types.OptionalVersionedTextDocumentIdentifier{URI: evt.URI},
Edits: ComputeEdits("", newContents),
})

// determine if a rename is needed based on the new file contents
renameParams, err := l.fixRenameParams(
"Rename file to match package path",
&fixes.DirectoryPackageMismatch{},
evt.URI,
)
if err != nil {
l.logError(fmt.Errorf("failed to fix directory package mismatch: %w", err))

continue
}

// move the file and clean up any empty directories ifd required
fileURI := evt.URI

if len(renameParams.Edit.DocumentChanges) > 0 {
edits = append(edits, renameParams.Edit.DocumentChanges[0])
}

// check if there are any dirs to clean
if len(renameParams.Edit.DocumentChanges) > 0 {
dirs, err := util.DirCleanUpPaths(
uri.ToPath(l.clientIdentifier, renameParams.Edit.DocumentChanges[0].OldURI),
[]string{
// stop at the root
l.workspacePath(),
// also preserve any dirs needed for the new file
uri.ToPath(l.clientIdentifier, renameParams.Edit.DocumentChanges[0].NewURI),
},
},
)
if err != nil {
l.logError(fmt.Errorf("failed to delete empty directories: %w", err))

continue
}

for _, dir := range dirs {
edits = append(
edits,
types.DeleteFile{
Kind: "delete",
URI: uri.FromPath(l.clientIdentifier, dir),
Options: &types.DeleteFileOptions{
Recursive: true,
IgnoreIfNotExists: true,
},
},
)
}

l.cache.Delete(renameParams.Edit.DocumentChanges[0].OldURI)
}

err = l.conn.Call(ctx, methodWorkspaceApplyEdit, templateParams, nil)
err = l.conn.Call(ctx, methodWorkspaceApplyEdit, map[string]any{
"label": "Template new Rego file",
"edit": map[string]any{
"documentChanges": edits,
},
}, nil)
if err != nil {
l.logError(fmt.Errorf("failed %s notify: %v", methodWorkspaceApplyEdit, err.Error()))
}

// finally, update the cache contents and run diagnostics to clear
// empty module warning.
// finally, trigger a diagnostics run for the new file
updateEvent := fileUpdateEvent{
Reason: "internal/templateNewFile",
URI: evt.URI,
URI: fileURI,
Content: newContents,
}

Expand Down Expand Up @@ -835,6 +891,10 @@ func (l *LanguageServer) templateContentsForFile(fileURI string) (string, error)
pkg = "main"
}

if strings.HasSuffix(fileURI, "_test.rego") {
pkg += "_test"
}

return fmt.Sprintf("package %s\n\nimport rego.v1\n", pkg), nil
}

Expand Down Expand Up @@ -898,7 +958,7 @@ func (l *LanguageServer) fixRenameParams(
) (types.ApplyWorkspaceRenameEditParams, error) {
contents, ok := l.cache.GetFileContents(fileURL)
if !ok {
return types.ApplyWorkspaceRenameEditParams{}, fmt.Errorf("failed to get module for file %q", fileURL)
return types.ApplyWorkspaceRenameEditParams{}, fmt.Errorf("failed to file contents %q", fileURL)
}

roots, err := config.GetPotentialRoots(l.workspacePath())
Expand All @@ -923,6 +983,13 @@ func (l *LanguageServer) fixRenameParams(
return types.ApplyWorkspaceRenameEditParams{}, fmt.Errorf("failed attempted fix: %w", err)
}

if len(results) == 0 {
return types.ApplyWorkspaceRenameEditParams{
Label: label,
Edit: types.WorkspaceRenameEdit{},
}, nil
}

result := results[0]

renameEdit := types.WorkspaceRenameEdit{
Expand Down
Loading

0 comments on commit 0c1ef19

Please sign in to comment.