-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR adds support for inlay hints of function arguments. Similar to the hover feature, only built-in functions are supported at this point. Also did some minor refactoring in the hover function to have it use the same method for collecting built-in functions from a module as the inlay hint system does. Signed-off-by: Anders Eknert <anders@styra.com>
- Loading branch information
1 parent
9081abf
commit 4d7cbe1
Showing
6 changed files
with
246 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package lsp | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/open-policy-agent/opa/ast" | ||
"github.com/open-policy-agent/opa/types" | ||
) | ||
|
||
func createInlayTooltip(named *types.NamedType) string { | ||
if named.Descr == "" { | ||
return fmt.Sprintf("Type: `%s`", named.Type.String()) | ||
} | ||
|
||
return fmt.Sprintf("%s\n\nType: `%s`", named.Descr, named.Type.String()) | ||
} | ||
|
||
func getInlayHints(module *ast.Module) []InlayHint { | ||
inlayHints := make([]InlayHint, 0) | ||
|
||
for _, call := range AllBuiltinCalls(module) { | ||
for i, arg := range call.Builtin.Decl.NamedFuncArgs().Args { | ||
if len(call.Args) <= i { | ||
// avoid panic if provided a builtin function where the args | ||
// have yet to be provided, like if the user types `split()` | ||
continue | ||
} | ||
|
||
if named, ok := arg.(*types.NamedType); ok { | ||
inlayHints = append(inlayHints, InlayHint{ | ||
Position: positionFromLocation(call.Args[i].Location), | ||
Label: named.Name + ":", | ||
Kind: 2, | ||
PaddingLeft: false, | ||
PaddingRight: true, | ||
Tooltip: MarkupContent{ | ||
Kind: "markdown", | ||
Value: createInlayTooltip(named), | ||
}, | ||
}) | ||
} | ||
} | ||
} | ||
|
||
return inlayHints | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package lsp | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/open-policy-agent/opa/ast" | ||
) | ||
|
||
// A function call may either be represented as an ast.Call. | ||
func TestGetInlayHintsAstCall(t *testing.T) { | ||
t.Parallel() | ||
|
||
policy := `package p | ||
r := json.filter({}, [])` | ||
|
||
module := ast.MustParseModule(policy) | ||
|
||
inlayHints := getInlayHints(module) | ||
|
||
if len(inlayHints) != 2 { | ||
t.Errorf("Expected 2 inlay hints, got %d", len(inlayHints)) | ||
} | ||
|
||
if inlayHints[0].Label != "object:" { | ||
t.Errorf("Expected label to be 'object:', got %s", inlayHints[0].Label) | ||
} | ||
|
||
if inlayHints[0].Position.Line != 2 && inlayHints[0].Position.Character != 18 { | ||
t.Errorf("Expected line 2, character 18, got %d, %d", | ||
inlayHints[0].Position.Line, inlayHints[0].Position.Character) | ||
} | ||
|
||
if inlayHints[0].Tooltip.Value != "Type: `object[any: any]`" { | ||
t.Errorf("Expected tooltip to be 'Type: `object[any: any]`, got %s", inlayHints[0].Tooltip.Value) | ||
} | ||
|
||
if inlayHints[1].Label != "paths:" { | ||
t.Errorf("Expected label to be 'paths:', got %s", inlayHints[1].Label) | ||
} | ||
|
||
if inlayHints[1].Position.Line != 2 && inlayHints[1].Position.Character != 22 { | ||
t.Errorf("Expected line 2, character 22, got %d, %d", | ||
inlayHints[1].Position.Line, inlayHints[1].Position.Character) | ||
} | ||
|
||
if inlayHints[1].Tooltip.Value != "JSON string paths\n\nType: `any<array[any<string, array[any]>],"+ | ||
" set[any<string, array[any]>]>`" { | ||
t.Errorf("Expected tooltip to be 'JSON string paths\n\nType: `any<array[any<string, array[any]>], "+ | ||
"set[any<string, array[any]>]>`, got %s", inlayHints[1].Tooltip.Value) | ||
} | ||
} | ||
|
||
// Or a function call may be represented as the terms of an ast.Expr. | ||
func TestGetInlayHintsAstTerms(t *testing.T) { | ||
t.Parallel() | ||
|
||
policy := `package p | ||
import rego.v1 | ||
allow if { | ||
is_string("yes") | ||
}` | ||
|
||
module := ast.MustParseModule(policy) | ||
|
||
inlayHints := getInlayHints(module) | ||
|
||
if len(inlayHints) != 1 { | ||
t.Errorf("Expected 1 inlay hints, got %d", len(inlayHints)) | ||
} | ||
|
||
if inlayHints[0].Label != "x:" { | ||
t.Errorf("Expected label to be 'x:', got %s", inlayHints[0].Label) | ||
} | ||
|
||
if inlayHints[0].Position.Line != 5 && inlayHints[0].Position.Character != 12 { | ||
t.Errorf("Expected line 5, character 12, got %d, %d", | ||
inlayHints[0].Position.Line, inlayHints[0].Position.Character) | ||
} | ||
|
||
if inlayHints[0].Tooltip.Value != "Type: `any`" { | ||
t.Errorf("Expected tooltip to be 'Type: `any`, got %s", inlayHints[0].Tooltip.Value) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package lsp | ||
|
||
import "github.com/open-policy-agent/opa/ast" | ||
|
||
type BuiltInCall struct { | ||
Builtin *ast.Builtin | ||
Location *ast.Location | ||
Args []*ast.Term | ||
} | ||
|
||
func positionFromLocation(loc *ast.Location) Position { | ||
return Position{ | ||
Line: uint(loc.Row - 1), | ||
Character: uint(loc.Col - 1), | ||
} | ||
} | ||
|
||
// AllBuiltinCalls returns all built-in calls in the module, excluding operators | ||
// and any other function identified by an infix. | ||
func AllBuiltinCalls(module *ast.Module) []BuiltInCall { | ||
builtinCalls := make([]BuiltInCall, 0) | ||
|
||
callVisitor := ast.NewGenericVisitor(func(x interface{}) bool { | ||
var terms []*ast.Term | ||
|
||
switch node := x.(type) { | ||
case ast.Call: | ||
terms = node | ||
case *ast.Expr: | ||
if call, ok := node.Terms.([]*ast.Term); ok { | ||
terms = call | ||
} | ||
default: | ||
return false | ||
} | ||
|
||
if len(terms) == 0 { | ||
return false | ||
} | ||
|
||
if b, ok := builtins[terms[0].Value.String()]; ok { | ||
// Exclude operators and similar builtins | ||
if b.Infix != "" { | ||
return false | ||
} | ||
|
||
builtinCalls = append(builtinCalls, BuiltInCall{ | ||
Builtin: b, | ||
Location: terms[0].Location, | ||
Args: terms[1:], | ||
}) | ||
} | ||
|
||
return false | ||
}) | ||
|
||
callVisitor.Walk(module) | ||
|
||
return builtinCalls | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters