From 988261f4203e504ddfa6d80c37482b6be14f44cf Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Tue, 6 Dec 2022 14:13:24 +0100 Subject: [PATCH 1/7] Bump hcl-lang to 3faa6c --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 922ff3d50..90860047e 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.5.0 - github.com/hashicorp/hcl-lang v0.0.0-20230310081458-97626944945e + github.com/hashicorp/hcl-lang v0.0.0-20230315123252-3faa6c8fba28 github.com/hashicorp/hcl/v2 v2.16.2 github.com/hashicorp/terraform-exec v0.18.1 github.com/hashicorp/terraform-json v0.16.0 diff --git a/go.sum b/go.sum index 965c4205e..3ca11fc39 100644 --- a/go.sum +++ b/go.sum @@ -209,8 +209,8 @@ github.com/hashicorp/hc-install v0.5.0 h1:D9bl4KayIYKEeJ4vUDe9L5huqxZXczKaykSRcm github.com/hashicorp/hc-install v0.5.0/go.mod h1:JyzMfbzfSBSjoDCRPna1vi/24BEDxFaCPfdHtM5SCdo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl-lang v0.0.0-20230310081458-97626944945e h1:3VRs8PujogY3D+hKc9ChpKK7rP+y/WTaDAKjORrOxk8= -github.com/hashicorp/hcl-lang v0.0.0-20230310081458-97626944945e/go.mod h1:m+ZB0CmTRHSo14j2INkDA+QZdzGBJRYTrOzf+4Xuj1k= +github.com/hashicorp/hcl-lang v0.0.0-20230315123252-3faa6c8fba28 h1:7RUI5eMB9ULXn6+V9mNyGyM5o33FBzv8pad/W+aEx04= +github.com/hashicorp/hcl-lang v0.0.0-20230315123252-3faa6c8fba28/go.mod h1:m+ZB0CmTRHSo14j2INkDA+QZdzGBJRYTrOzf+4Xuj1k= github.com/hashicorp/hcl/v2 v2.16.2 h1:mpkHZh/Tv+xet3sy3F9Ld4FyI2tUpWe9x3XtPx9f1a0= github.com/hashicorp/hcl/v2 v2.16.2/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= From c11c3739c2de03b87c41a3c658bf750fb999c1b4 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 15 Mar 2023 17:44:25 +0100 Subject: [PATCH 2/7] Add functions to PathContext --- internal/decoder/decoder.go | 1 + internal/decoder/functions.go | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 internal/decoder/functions.go diff --git a/internal/decoder/decoder.go b/internal/decoder/decoder.go index ad608cc09..365c63624 100644 --- a/internal/decoder/decoder.go +++ b/internal/decoder/decoder.go @@ -26,6 +26,7 @@ func modulePathContext(mod *state.Module, schemaReader state.SchemaReader, modRe ReferenceOrigins: make(reference.Origins, 0), ReferenceTargets: make(reference.Targets, 0), Files: make(map[string]*hcl.File, 0), + Functions: coreFunctions(mod), } for _, origin := range mod.RefOrigins { diff --git a/internal/decoder/functions.go b/internal/decoder/functions.go new file mode 100644 index 000000000..67215e853 --- /dev/null +++ b/internal/decoder/functions.go @@ -0,0 +1,41 @@ +package decoder + +import ( + "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/terraform-ls/internal/state" + tfschema "github.com/hashicorp/terraform-schema/schema" +) + +func coreFunctions(mod *state.Module) map[string]schema.FunctionSignature { + if mod.TerraformVersion != nil { + s, err := tfschema.FunctionsForVersion(mod.TerraformVersion) + if err == nil { + return s + } + if mod.TerraformVersion.LessThan(tfschema.OldestAvailableVersion) { + return mustFunctionsForVersion(tfschema.OldestAvailableVersion) + } + + return mustFunctionsForVersion(tfschema.LatestAvailableVersion) + } + + s, err := tfschema.FunctionsForConstraint(mod.Meta.CoreRequirements) + if err == nil { + return s + } + if mod.Meta.CoreRequirements.Check(tfschema.OldestAvailableVersion) { + return mustFunctionsForVersion(tfschema.OldestAvailableVersion) + } + + return mustFunctionsForVersion(tfschema.LatestAvailableVersion) +} + +func mustFunctionsForVersion(v *version.Version) map[string]schema.FunctionSignature { + s, err := tfschema.FunctionsForVersion(v) + if err != nil { + // this should never happen + panic(err) + } + return s +} From 76fdc012c58eabd65db024d39fa7de46150d8496 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 15 Mar 2023 17:55:12 +0100 Subject: [PATCH 3/7] Add SignatureHelp handler --- internal/langserver/handlers/service.go | 11 ++++++ .../langserver/handlers/signature_help.go | 38 +++++++++++++++++++ internal/lsp/signature.go | 33 ++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 internal/langserver/handlers/signature_help.go create mode 100644 internal/lsp/signature.go diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index 77c9d0526..4044d7d63 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -272,6 +272,17 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { return handle(ctx, req, svc.TextDocumentFormatting) }, + "textDocument/signatureHelp": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { + err := session.CheckInitializationIsConfirmed() + if err != nil { + return nil, err + } + + ctx = ilsp.WithClientCapabilities(ctx, cc) + ctx = ilsp.ContextWithClientName(ctx, &clientName) + + return handle(ctx, req, svc.SignatureHelp) + }, "textDocument/semanticTokens/full": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := session.CheckInitializationIsConfirmed() if err != nil { diff --git a/internal/langserver/handlers/signature_help.go b/internal/langserver/handlers/signature_help.go new file mode 100644 index 000000000..9a6144dd5 --- /dev/null +++ b/internal/langserver/handlers/signature_help.go @@ -0,0 +1,38 @@ +package handlers + +import ( + "context" + + ilsp "github.com/hashicorp/terraform-ls/internal/lsp" + lsp "github.com/hashicorp/terraform-ls/internal/protocol" +) + +func (svc *service) SignatureHelp(ctx context.Context, params lsp.SignatureHelpParams) (*lsp.SignatureHelp, error) { + _, err := ilsp.ClientCapabilities(ctx) + if err != nil { + return nil, err + } + + dh := ilsp.HandleFromDocumentURI(params.TextDocument.URI) + doc, err := svc.stateStore.DocumentStore.GetDocument(dh) + if err != nil { + return nil, err + } + + d, err := svc.decoderForDocument(ctx, doc) + if err != nil { + return nil, err + } + + pos, err := ilsp.HCLPositionFromLspPosition(params.Position, doc) + if err != nil { + return nil, err + } + + sig, err := d.SignatureAtPos(doc.Filename, pos) + if err != nil { + return nil, err + } + + return ilsp.ToSignatureHelp(sig), nil +} diff --git a/internal/lsp/signature.go b/internal/lsp/signature.go new file mode 100644 index 000000000..dbed9883d --- /dev/null +++ b/internal/lsp/signature.go @@ -0,0 +1,33 @@ +package lsp + +import ( + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/terraform-ls/internal/mdplain" + lsp "github.com/hashicorp/terraform-ls/internal/protocol" +) + +func ToSignatureHelp(signature *lang.FunctionSignature) *lsp.SignatureHelp { + if signature == nil { + return nil + } + + parameters := make([]lsp.ParameterInformation, 0) + for _, p := range signature.Parameters { + parameters = append(parameters, lsp.ParameterInformation{ + Label: p.Name, + Documentation: mdplain.Clean(p.Description.Value), + }) + } + + return &lsp.SignatureHelp{ + Signatures: []lsp.SignatureInformation{ + { + Label: signature.Name, + Documentation: mdplain.Clean(signature.Description.Value), + Parameters: parameters, + ActiveParameter: signature.ActiveParameter, + }, + }, + ActiveSignature: 0, + } +} From ba1e8f20495598a711ae30ad624717ccb355bf51 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 15 Mar 2023 18:20:41 +0100 Subject: [PATCH 4/7] Test SignatureHelp handler --- .../handlers/signature_help_test.go | 150 ++++++++++++++++++ internal/lsp/signature_test.go | 126 +++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 internal/langserver/handlers/signature_help_test.go create mode 100644 internal/lsp/signature_test.go diff --git a/internal/langserver/handlers/signature_help_test.go b/internal/langserver/handlers/signature_help_test.go new file mode 100644 index 000000000..8a2e68953 --- /dev/null +++ b/internal/langserver/handlers/signature_help_test.go @@ -0,0 +1,150 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/hashicorp/go-version" + tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-ls/internal/langserver" + "github.com/hashicorp/terraform-ls/internal/langserver/session" + "github.com/hashicorp/terraform-ls/internal/state" + "github.com/hashicorp/terraform-ls/internal/terraform/exec" + "github.com/hashicorp/terraform-ls/internal/walker" + "github.com/stretchr/testify/mock" +) + +func TestSignatureHelp_withoutInitialization(t *testing.T) { + ls := langserver.NewLangServerMock(t, NewMockSession(nil)) + stop := ls.Start(t) + defer stop() + + ls.CallAndExpectError(t, &langserver.CallRequest{ + Method: "textDocument/signatureHelp", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "uri": "%s/main.tf" + }, + "position": { + "character": 0, + "line": 1 + }, + "context": { + "isRetrigger": false, + "triggerCharacter": "(", + "triggerKind": 2 + } + }`, TempDir(t).URI)}, session.SessionNotInitialized.Err()) +} + +func TestSignatureHelp_withValidData(t *testing.T) { + tmpDir := TempDir(t) + InitPluginCache(t, tmpDir.Path()) + + var testSchema tfjson.ProviderSchemas + err := json.Unmarshal([]byte(testModuleSchemaOutput), &testSchema) + if err != nil { + t.Fatal(err) + } + + ss, err := state.NewStateStore() + if err != nil { + t.Fatal(err) + } + wc := walker.NewWalkerCollector() + + ls := langserver.NewLangServerMock(t, NewMockSession(&MockSessionInput{ + TerraformCalls: &exec.TerraformMockCalls{ + PerWorkDir: map[string][]*mock.Call{ + tmpDir.Path(): { + { + Method: "Version", + Repeatability: 1, + Arguments: []interface{}{ + mock.AnythingOfType(""), + }, + ReturnArguments: []interface{}{ + version.Must(version.NewVersion("0.12.0")), + nil, + nil, + }, + }, + { + Method: "GetExecPath", + Repeatability: 1, + ReturnArguments: []interface{}{ + "", + }, + }, + { + Method: "ProviderSchemas", + Repeatability: 1, + Arguments: []interface{}{ + mock.AnythingOfType(""), + }, + ReturnArguments: []interface{}{ + &testSchema, + nil, + }, + }, + }, + }, + }, + StateStore: ss, + WalkerCollector: wc, + })) + stop := ls.Start(t) + defer stop() + + ls.Call(t, &langserver.CallRequest{ + Method: "initialize", + ReqParams: fmt.Sprintf(`{ + "capabilities": {}, + "rootUri": %q, + "processId": 12345 + }`, tmpDir.URI)}) + waitForWalkerPath(t, ss, wc, tmpDir) + ls.Notify(t, &langserver.CallRequest{ + Method: "initialized", + ReqParams: "{}", + }) + ls.Call(t, &langserver.CallRequest{ + Method: "textDocument/didOpen", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "version": 0, + "languageId": "terraform", + "text": "variable \"name\" {\n default = file(\"~/foo\")\n}", + "uri": "%s/main.tf" + } + }`, TempDir(t).URI)}) + waitForAllJobs(t, ss) + + ls.CallAndExpectResponse(t, &langserver.CallRequest{ + Method: "textDocument/signatureHelp", + ReqParams: fmt.Sprintf(`{ + "textDocument": { + "uri": "%s/main.tf" + }, + "position": { + "character": 16, + "line": 1 + }, + "context": { + "isRetrigger": false, + "triggerCharacter": "(", + "triggerKind": 2 + } + }`, TempDir(t).URI)}, `{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "signatures": [{ + "label": "file(path string) string", + "documentation": "file reads the contents of a file at the given path and returns them as a string.", + "parameters": [{"label": "path"}] + }] + } + }`) +} diff --git a/internal/lsp/signature_test.go b/internal/lsp/signature_test.go new file mode 100644 index 000000000..498f86618 --- /dev/null +++ b/internal/lsp/signature_test.go @@ -0,0 +1,126 @@ +package lsp + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + lsp "github.com/hashicorp/terraform-ls/internal/protocol" +) + +func TestToSignatureHelp(t *testing.T) { + testCases := []struct { + name string + signature *lang.FunctionSignature + expectedSignatureHelp *lsp.SignatureHelp + }{ + { + "nil", + nil, + nil, + }, + { + "no parameters", + &lang.FunctionSignature{ + Name: "foo() string", + Description: lang.Markdown("`foo` description"), + }, + &lsp.SignatureHelp{ + Signatures: []lsp.SignatureInformation{ + { + Label: "foo() string", + Documentation: "foo description", + Parameters: []lsp.ParameterInformation{}, + }, + }, + }, + }, + { + "one parameter", + &lang.FunctionSignature{ + Name: "foo(input list of string) map of number", + Description: lang.Markdown("`foo` description"), + Parameters: []lang.FunctionParameter{ + { + Name: "input", + Description: lang.Markdown("`input` description"), + }, + }, + ActiveParameter: 0, + }, + &lsp.SignatureHelp{ + Signatures: []lsp.SignatureInformation{ + { + Label: "foo(input list of string) map of number", + Documentation: "foo description", + Parameters: []lsp.ParameterInformation{ + { + Label: "input", + Documentation: "input description", + }, + }, + ActiveParameter: 0, + }, + }, + ActiveSignature: 0, + }, + }, + { + "multiple parameters", + &lang.FunctionSignature{ + Name: "foo(input string, input2 number, input3 string) number", + Description: lang.Markdown("`foo` description"), + Parameters: []lang.FunctionParameter{ + { + Name: "input", + Description: lang.Markdown("`input` description"), + }, + { + Name: "input2", + Description: lang.Markdown("`input2` description"), + }, + { + Name: "input3", + Description: lang.Markdown("`input3` description"), + }, + }, + ActiveParameter: 1, + }, + &lsp.SignatureHelp{ + Signatures: []lsp.SignatureInformation{ + { + Label: "foo(input string, input2 number, input3 string) number", + Documentation: "foo description", + Parameters: []lsp.ParameterInformation{ + { + Label: "input", + Documentation: "input description", + }, + { + Label: "input2", + Documentation: "input2 description", + }, + { + Label: "input3", + Documentation: "input3 description", + }, + }, + ActiveParameter: 1, + }, + }, + ActiveSignature: 0, + }, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%2d-%s", i, tc.name), func(t *testing.T) { + signature := ToSignatureHelp(tc.signature) + + if diff := cmp.Diff(tc.expectedSignatureHelp, signature); diff != "" { + t.Fatalf("unexpected signature help: %s", diff) + } + }) + } +} From b14102296f1c04685c1f754c36a9f413572486e7 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Wed, 15 Mar 2023 18:20:50 +0100 Subject: [PATCH 5/7] Set signature help trigger characters --- internal/langserver/handlers/handlers_test.go | 4 +++- internal/langserver/handlers/initialize.go | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/langserver/handlers/handlers_test.go b/internal/langserver/handlers/handlers_test.go index f340a39dd..791db0d16 100644 --- a/internal/langserver/handlers/handlers_test.go +++ b/internal/langserver/handlers/handlers_test.go @@ -41,7 +41,9 @@ func initializeResponse(t *testing.T, commandPrefix string) string { "completionItem":{} }, "hoverProvider": true, - "signatureHelpProvider": {}, + "signatureHelpProvider": { + "triggerCharacters":["(",","] + }, "declarationProvider": {}, "definitionProvider": true, "referencesProvider": true, diff --git a/internal/langserver/handlers/initialize.go b/internal/langserver/handlers/initialize.go index 8dc74696e..827f51313 100644 --- a/internal/langserver/handlers/initialize.go +++ b/internal/langserver/handlers/initialize.go @@ -246,6 +246,9 @@ func initializeResult(ctx context.Context) lsp.InitializeResult { ChangeNotifications: "workspace/didChangeWorkspaceFolders", }, }, + SignatureHelpProvider: lsp.SignatureHelpOptions{ + TriggerCharacters: []string{"(", ","}, + }, }, } From 522ac69a3648b4ea1fde24912f91276b33fdeaef Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Thu, 16 Mar 2023 13:03:11 +0100 Subject: [PATCH 6/7] Review feedback --- internal/langserver/handlers/service.go | 1 - internal/lsp/signature.go | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index 4044d7d63..8fcc3b098 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -279,7 +279,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = ilsp.WithClientCapabilities(ctx, cc) - ctx = ilsp.ContextWithClientName(ctx, &clientName) return handle(ctx, req, svc.SignatureHelp) }, diff --git a/internal/lsp/signature.go b/internal/lsp/signature.go index dbed9883d..7afa19f0b 100644 --- a/internal/lsp/signature.go +++ b/internal/lsp/signature.go @@ -11,10 +11,11 @@ func ToSignatureHelp(signature *lang.FunctionSignature) *lsp.SignatureHelp { return nil } - parameters := make([]lsp.ParameterInformation, 0) + parameters := make([]lsp.ParameterInformation, 0, len(signature.Parameters)) for _, p := range signature.Parameters { parameters = append(parameters, lsp.ParameterInformation{ - Label: p.Name, + Label: p.Name, + // TODO: Support markdown per https://github.com/hashicorp/terraform-ls/issues/1212 Documentation: mdplain.Clean(p.Description.Value), }) } @@ -22,7 +23,8 @@ func ToSignatureHelp(signature *lang.FunctionSignature) *lsp.SignatureHelp { return &lsp.SignatureHelp{ Signatures: []lsp.SignatureInformation{ { - Label: signature.Name, + Label: signature.Name, + // TODO: Support markdown per https://github.com/hashicorp/terraform-ls/issues/1212 Documentation: mdplain.Clean(signature.Description.Value), Parameters: parameters, ActiveParameter: signature.ActiveParameter, From f93b02123d68d1db58d226ec976d828ba593e058 Mon Sep 17 00:00:00 2001 From: Daniel Banck Date: Fri, 17 Mar 2023 09:27:11 +0100 Subject: [PATCH 7/7] Move ActiveParameter --- internal/lsp/signature.go | 6 +++--- internal/lsp/signature_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/lsp/signature.go b/internal/lsp/signature.go index 7afa19f0b..ea8678ee9 100644 --- a/internal/lsp/signature.go +++ b/internal/lsp/signature.go @@ -25,11 +25,11 @@ func ToSignatureHelp(signature *lang.FunctionSignature) *lsp.SignatureHelp { { Label: signature.Name, // TODO: Support markdown per https://github.com/hashicorp/terraform-ls/issues/1212 - Documentation: mdplain.Clean(signature.Description.Value), - Parameters: parameters, - ActiveParameter: signature.ActiveParameter, + Documentation: mdplain.Clean(signature.Description.Value), + Parameters: parameters, }, }, + ActiveParameter: signature.ActiveParameter, ActiveSignature: 0, } } diff --git a/internal/lsp/signature_test.go b/internal/lsp/signature_test.go index 498f86618..82a41a857 100644 --- a/internal/lsp/signature_test.go +++ b/internal/lsp/signature_test.go @@ -60,9 +60,9 @@ func TestToSignatureHelp(t *testing.T) { Documentation: "input description", }, }, - ActiveParameter: 0, }, }, + ActiveParameter: 0, ActiveSignature: 0, }, }, @@ -106,9 +106,9 @@ func TestToSignatureHelp(t *testing.T) { Documentation: "input3 description", }, }, - ActiveParameter: 1, }, }, + ActiveParameter: 1, ActiveSignature: 0, }, },