diff --git a/decoder/attribute_candidates_legacy_test.go b/decoder/attribute_candidates_legacy_test.go deleted file mode 100644 index b176d0f0..00000000 --- a/decoder/attribute_candidates_legacy_test.go +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package decoder - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/hcl-lang/lang" - "github.com/hashicorp/hcl-lang/schema" - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/zclconf/go-cty/cty" -) - -func TestLegacyDecoder_CompletionAtPos_EmptyCompletionData(t *testing.T) { - testCases := []struct { - testName string - attrName string - attrSchema *schema.AttributeSchema - expectedCandidates lang.Candidates - }{ - { - "primitive type", - "primitive", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.String}, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "primitive", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "primitive", - Snippet: `primitive = "${1:value}"`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "map of strings", - "mymap", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.Map(cty.String)}, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "mymap", - Detail: "map of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "mymap", - Snippet: `mymap = { - "${1:name}" = "${2:value}" -}`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "map of numbers", - "mymap", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.Map(cty.Number)}, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "mymap", - Detail: "map of number", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "mymap", - Snippet: `mymap = { - "${1:name}" = ${2:0} -}`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "list of numbers", - "mylist", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.List(cty.Number)}, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "mylist", - Detail: "list of number", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "mylist", - Snippet: `mylist = [ ${1:0} ]`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "list of objects", - "mylistobj", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{ - Type: cty.List(cty.Object(map[string]cty.Type{ - "first": cty.String, - "second": cty.Number, - })), - }, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "mylistobj", - Detail: "list of object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "mylistobj", - Snippet: `mylistobj = [ { - first = "${1:value}" - second = ${2:0} -} ]`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "set of numbers", - "myset", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.Set(cty.Number)}, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "myset", - Detail: "set of number", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "myset", - Snippet: `myset = [ ${1:0} ]`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "object", - "myobj", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{ - Type: cty.Object(map[string]cty.Type{ - "keystr": cty.String, - "keynum": cty.Number, - "keybool": cty.Bool, - }), - }, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "myobj", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "myobj", - Snippet: `myobj = { - keybool = ${1:false} - keynum = ${2:0} - keystr = "${3:value}" -}`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "unknown type", - "mynil", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.DynamicPseudoType}, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "mynil", - Detail: "any type", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "mynil", - Snippet: `mynil = `, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "nested object", - "myobj", - &schema.AttributeSchema{ - Constraint: schema.LiteralType{ - Type: cty.Object(map[string]cty.Type{ - "keystr": cty.String, - "another": cty.Object(map[string]cty.Type{ - "nestedstr": cty.String, - "nested_number": cty.Number, - }), - }), - }, - }, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "myobj", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.InitialPos, - End: hcl.InitialPos, - }, - NewText: "myobj", - Snippet: `myobj = { - another = { - nested_number = ${1:0} - nestedstr = "${2:value}" - } - keystr = "${3:value}" -}`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { - f, _ := hclsyntax.ParseConfig([]byte("\n"), "test.tf", hcl.InitialPos) - d := testPathDecoder(t, &PathContext{ - Schema: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - tc.attrName: tc.attrSchema, - }, - }, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - }) - d.PrefillRequiredFields = true - - ctx := context.Background() - candidates, err := d.CompletionAtPos(ctx, "test.tf", hcl.InitialPos) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(tc.expectedCandidates, candidates); diff != "" { - t.Fatalf("unexpected candidates: %s", diff) - } - }) - } -} diff --git a/decoder/expression_candidates_legacy_test.go b/decoder/expression_candidates_legacy_test.go deleted file mode 100644 index 247afded..00000000 --- a/decoder/expression_candidates_legacy_test.go +++ /dev/null @@ -1,2855 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package decoder - -import ( - "context" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/hcl-lang/lang" - "github.com/hashicorp/hcl-lang/reference" - "github.com/hashicorp/hcl-lang/schema" - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" -) - -func TestLegacyDecoder_CandidateAtPos_expressions(t *testing.T) { - ctx := context.Background() - testCases := []struct { - testName string - attrSchema map[string]*schema.AttributeSchema - cfg string - pos hcl.Pos - expectedCandidates lang.Candidates - }{ - { - "string type", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.String}, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.ZeroCandidates(), - }, - { - "object as value", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralValue{ - Value: cty.ObjectVal(map[string]cty.Value{ - "first": cty.StringVal("blah"), - "second": cty.NumberIntVal(2345), - }), - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "{ first = \"blah\", … }", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `{ - first = "blah" - second = 2345 -}`, - Snippet: `{ - first = "blah" - second = 2345 -}`, - }, - Kind: lang.ObjectCandidateKind, - }, - }), - }, - { - "object as type", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{ - Type: cty.Object(map[string]cty.Type{ - "first": cty.String, - "second": cty.Number, - }), - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "{ first = string, … }", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `{ - first = "" - second = 1 -}`, - Snippet: `{ - first = "${1:value}" - second = ${2:1} -}`, - }, - Kind: lang.ObjectCandidateKind, - }, - }), - }, - { - "object as expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Object{ - Attributes: schema.ObjectAttributes{ - "first": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.String}, - }, - "second": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.Number}, - }, - }, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "{…}", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "{\n \n}", - Snippet: "{\n ${1}\n}", - }, - Kind: lang.ObjectCandidateKind, - TriggerSuggest: true, - }, - }), - }, - { - "object as expression - attribute", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Object{ - Attributes: schema.ObjectAttributes{ - "first": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.String}, - }, - "second": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.Number}, - }, - }, - }, - }, - }, - `attr = { - -} -`, - hcl.Pos{Line: 2, Column: 3, Byte: 11}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "first", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 2, - Column: 3, - Byte: 11, - }, - End: hcl.Pos{ - Line: 2, - Column: 3, - Byte: 11, - }, - }, - NewText: `first`, - Snippet: `first = "${1:value}"`, - }, - Kind: lang.AttributeCandidateKind, - }, - { - Label: "second", - Detail: "number", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 2, - Column: 3, - Byte: 11, - }, - End: hcl.Pos{ - Line: 2, - Column: 3, - Byte: 11, - }, - }, - NewText: "second", - Snippet: "second = ${1:0}", - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "object as expression - attributes partially declared", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Object{ - Attributes: schema.ObjectAttributes{ - "first": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.String}, - }, - "second": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.Number}, - }, - }, - }, - }, - }, - `attr = { - first = "blah" - -} -`, - hcl.Pos{Line: 3, Column: 3, Byte: 28}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "second", - Detail: "number", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 3, - Column: 3, - Byte: 28, - }, - End: hcl.Pos{ - Line: 3, - Column: 3, - Byte: 28, - }, - }, - NewText: "second", - Snippet: "second = ${1:0}", - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "object as expression - attribute key unknown", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Object{ - Attributes: schema.ObjectAttributes{ - "first": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.String}, - }, - "second": &schema.AttributeSchema{ - Constraint: schema.LiteralType{Type: cty.Number}, - }, - }, - }, - }, - }, - `attr = { - first = "blah" - var.test = "foo" - "${var.env}.${another}" = "prod" - -} -`, - hcl.Pos{Line: 5, Column: 3, Byte: 82}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "second", - Detail: "number", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 5, - Column: 3, - Byte: 82, - }, - End: hcl.Pos{ - Line: 5, - Column: 3, - Byte: 82, - }, - }, - NewText: "second", - Snippet: "second = ${1:0}", - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "list as value", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralValue{ - Value: cty.ListVal([]cty.Value{ - cty.StringVal("foo"), - cty.StringVal("bar"), - }), - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: `[ "foo", "bar" ]`, - Detail: "list of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `["foo", "bar"]`, - Snippet: `["foo", "bar"]`, - }, - Kind: lang.ListCandidateKind, - }, - }), - }, - { - "map as type", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.Map(cty.String)}, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: `{ "key" = string }`, - Detail: "map of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `{ - "key" = "" -}`, - Snippet: `{ - "${1:key}" = "${2:value}" -}`, - }, - Kind: lang.MapCandidateKind, - }, - }), - }, - { - "map as value", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralValue{ - Value: cty.MapVal(map[string]cty.Value{ - "foo": cty.StringVal("moo"), - "bar": cty.StringVal("boo"), - }), - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: `{ "bar" = "boo", … }`, - Detail: "map of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `{ - "bar" = "boo" - "foo" = "moo" -}`, - Snippet: `{ - "bar" = "boo" - "foo" = "moo" -}`, - }, - Kind: lang.MapCandidateKind, - }, - }), - }, - { - "bool type", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.Bool}, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "false", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "false", - Snippet: "false", - }, - Kind: lang.BoolCandidateKind, - }, - { - Label: "true", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "true", - Snippet: "true", - }, - Kind: lang.BoolCandidateKind, - }, - }), - }, - { - "string values", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.OneOf{ - schema.LiteralValue{Value: cty.StringVal("first")}, - schema.LiteralValue{Value: cty.StringVal("second")}, - schema.LiteralValue{Value: cty.StringVal("third")}, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "first", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `"first"`, - Snippet: `"first"`}, - Kind: lang.StringCandidateKind, - }, - { - Label: "second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `"second"`, - Snippet: `"second"`}, - Kind: lang.StringCandidateKind, - }, - { - Label: "third", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `"third"`, - Snippet: `"third"`}, - Kind: lang.StringCandidateKind, - }, - }), - }, - { - "deprecated string value", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.OneOf{ - schema.LiteralValue{Value: cty.StringVal("first")}, - schema.LiteralValue{Value: cty.StringVal("second"), IsDeprecated: true}, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "first", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `"first"`, - Snippet: `"first"`}, - Kind: lang.StringCandidateKind, - }, - { - Label: "second", - Detail: "string", - IsDeprecated: true, - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `"second"`, - Snippet: `"second"`}, - Kind: lang.StringCandidateKind, - }, - }), - }, - { - "tuple as list type", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.List(cty.String)}, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "[ string ]", - Detail: "list of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: `[ "" ]`, - Snippet: `[ "${1:value}" ]`, - }, - Kind: lang.ListCandidateKind, - }, - }), - }, - { - "tuple as list type inside", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.List(cty.String)}, - }, - }, - `attr = [ ] -`, - hcl.Pos{Line: 1, Column: 10, Byte: 9}, - lang.ZeroCandidates(), - }, - { - "attribute as list expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.List{ - Elem: schema.LiteralType{Type: cty.String}, - }, - }, - }, - ` -`, - hcl.Pos{Line: 1, Column: 1, Byte: 0}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "attr", - Detail: "list of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - }, - NewText: "attr", - Snippet: `attr = [ "${1:value}" ]`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "list expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.List{ - Elem: schema.LiteralType{Type: cty.String}, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "[ string ]", - Detail: "list of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - }, - NewText: `[ "value" ]`, - Snippet: `[ "${1:value}" ]`, - }, - Kind: lang.ListCandidateKind, - }, - }), - }, - { - "list expression inside", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.List{ - Elem: schema.LiteralType{Type: cty.Bool}, - }, - }, - }, - `attr = [ ] -`, - hcl.Pos{Line: 1, Column: 10, Byte: 9}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "false", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - End: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - }, - NewText: "false", - Snippet: "false", - }, - Kind: lang.BoolCandidateKind, - }, - { - Label: "true", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - End: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - }, - NewText: "true", - Snippet: "true", - }, - Kind: lang.BoolCandidateKind, - }, - }), - }, - { - "attribute as set expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Set{ - Elem: schema.LiteralType{Type: cty.String}, - }, - }, - }, - ` -`, - hcl.Pos{Line: 1, Column: 1, Byte: 0}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "attr", - Detail: "set of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - }, - NewText: "attr", - Snippet: `attr = [ "${1:value}" ]`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "set expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Set{ - Elem: schema.LiteralType{Type: cty.String}, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "[ string ]", - Detail: "set of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - }, - NewText: `[ "value" ]`, - Snippet: `[ "${1:value}" ]`, - }, - Kind: lang.SetCandidateKind, - }, - }), - }, - { - "set expression inside", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Set{ - Elem: schema.LiteralType{Type: cty.Bool}, - }, - }, - }, - `attr = [ ] -`, - hcl.Pos{Line: 1, Column: 10, Byte: 9}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "false", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - End: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - }, - NewText: "false", - Snippet: "false", - }, - Kind: lang.BoolCandidateKind, - }, - { - Label: "true", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - End: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - }, - NewText: "true", - Snippet: "true", - }, - Kind: lang.BoolCandidateKind, - }, - }), - }, - { - "attribute as tuple expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Tuple{ - Elems: []schema.Constraint{ - schema.LiteralType{Type: cty.String}, - schema.LiteralType{Type: cty.Number}, - }, - }, - }, - }, - ` -`, - hcl.Pos{Line: 1, Column: 1, Byte: 0}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "attr", - Detail: "tuple", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - }, - NewText: "attr", - Snippet: `attr = [ "${1:value}", ${2:0} ]`, - }, - Kind: lang.AttributeCandidateKind, - }, - }), - }, - { - "tuple expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Tuple{ - Elems: []schema.Constraint{ - schema.LiteralType{Type: cty.String}, - schema.LiteralType{Type: cty.Number}, - }, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "[ string, number ]", - Detail: "tuple", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - }, - NewText: `[ "value", 0 ]`, - Snippet: `[ "${1:value}", ${2:0} ]`, - }, - Kind: lang.TupleCandidateKind, - }, - }), - }, - { - "tuple expression inside", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Tuple{ - Elems: []schema.Constraint{ - schema.LiteralType{Type: cty.Bool}, - schema.LiteralType{Type: cty.Number}, - }, - }, - }, - }, - `attr = [ ] -`, - hcl.Pos{Line: 1, Column: 10, Byte: 9}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "false", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - End: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - }, - NewText: "false", - Snippet: "false", - }, - Kind: lang.BoolCandidateKind, - }, - { - Label: "true", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - End: hcl.Pos{ - Line: 1, - Column: 10, - Byte: 9, - }, - }, - NewText: "true", - Snippet: "true", - }, - Kind: lang.BoolCandidateKind, - }, - }), - }, - { - "keyword", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Keyword{ - Keyword: "foobar", - Name: "special kw", - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "foobar", - Detail: "special kw", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "foobar", - Snippet: "foobar", - }, - Kind: lang.KeywordCandidateKind, - }, - }), - }, - { - "map expression", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Map{ - Elem: schema.LiteralType{Type: cty.String}, - Name: "map of something", - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: `{ "key" = string }`, - Detail: "map of something", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "{\n \"name\" = \"value\"\n}", - Snippet: "{\n \"${1:name}\" = \"${2:value}\"\n}", - }, - Kind: lang.MapCandidateKind, - }, - }), - }, - { - "literal of dynamic pseudo type", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.DynamicPseudoType}, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.ZeroCandidates(), - }, - { - "type declaration", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.TypeDeclaration{}, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "bool", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "bool", - Snippet: "bool", - }, - Kind: lang.BoolCandidateKind, - }, - { - Label: "number", - Detail: "number", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "number", - Snippet: "number", - }, - Kind: lang.NumberCandidateKind, - }, - { - Label: "string", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "string", - Snippet: "string", - }, - Kind: lang.StringCandidateKind, - }, - { - Label: "list(…)", - Detail: "list", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "list()", - Snippet: "list(${0})", - }, - TriggerSuggest: true, - Kind: lang.ListCandidateKind, - }, - { - Label: "set(…)", - Detail: "set", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "set()", - Snippet: "set(${0})", - }, - TriggerSuggest: true, - Kind: lang.SetCandidateKind, - }, - { - Label: "tuple([…])", - Detail: "tuple", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "tuple([])", - Snippet: "tuple([ ${0} ])", - }, - TriggerSuggest: true, - Kind: lang.TupleCandidateKind, - }, - { - Label: "map(…)", - Detail: "map", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "map()", - Snippet: "map(${0})", - }, - TriggerSuggest: true, - Kind: lang.MapCandidateKind, - }, - { - Label: "object({…})", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "object({\n\n})", - Snippet: "object({\n ${1:name} = ${2}\n})", - }, - Kind: lang.ObjectCandidateKind, - }, - }), - }, - { - "empty list", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.List{}, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "[ ]", - Detail: "list", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - }, - NewText: "[ ]", - Snippet: "[ ${1} ]", - }, - Kind: lang.ListCandidateKind, - }, - }), - }, - { - "multiple traversal constraints in set", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Set{ - Elem: schema.OneOf{ - schema.Reference{OfScopeId: lang.ScopeId("one")}, - schema.Reference{OfScopeId: lang.ScopeId("two")}, - }, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "[ reference ]", - Detail: "set of reference", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - }, - NewText: "[ ]", - Snippet: "[ ${1} ]", - }, - Kind: lang.SetCandidateKind, - TriggerSuggest: true, - }, - }), - }, - } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { - bodySchema := &schema.BodySchema{ - Attributes: tc.attrSchema, - } - - f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) - d := testPathDecoder(t, &PathContext{ - Schema: bodySchema, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - }) - - candidates, err := d.CompletionAtPos(ctx, "test.tf", tc.pos) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(tc.expectedCandidates, candidates); diff != "" { - t.Fatalf("unexpected candidates: %s", diff) - } - }) - } -} - -func TestLegacyDecoder_CandidateAtPos_traversalExpressions(t *testing.T) { - ctx := context.Background() - testCases := []struct { - testName string - bodySchema *schema.BodySchema - builtinRefs reference.Targets - cfg string - pos hcl.Pos - expectedCandidates lang.Candidates - }{ - { - "no references", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{}, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.ZeroCandidates(), - }, - - { - "no matching references", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.List(cty.String)}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.RootStep{Name: "first"}, - }, - Type: cty.Bool, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.RootStep{Name: "second"}, - }, - Type: cty.Number, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.ZeroCandidates(), - }, - { - "type matching references", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.List(cty.Number), - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "reference of any type", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.DynamicPseudoType}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.Bool, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "bool", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "var.first", - Snippet: "var.first", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "reference targets of any type", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.DynamicPseudoType, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.DynamicPseudoType, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "dynamic", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "var.first", - Snippet: "var.first", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.second", - Detail: "dynamic", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "scope matching references", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfScopeId: lang.ScopeId("test")}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - ScopeId: lang.ScopeId("test"), - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - ScopeId: lang.ScopeId("second"), - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "reference", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - }, - NewText: "var.first", - Snippet: "var.first", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "no candidates for addressable traversal", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{ - Address: &schema.ReferenceAddrSchema{ - ScopeId: lang.ScopeId("blah"), - }, - Name: "test", - }, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "zero"}, - }, - Type: cty.Number, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - ScopeId: lang.ScopeId("blah"), - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - ScopeId: lang.ScopeId("another"), - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - lang.ZeroCandidates(), - }, - { - "no candidates for addressable traversal in set", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Set{ - Elem: schema.Reference{ - Address: &schema.ReferenceAddrSchema{ - ScopeId: lang.ScopeId("blah"), - }, - Name: "test", - }, - }, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "zero"}, - }, - Type: cty.Number, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - ScopeId: lang.ScopeId("blah"), - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - ScopeId: lang.ScopeId("another"), - }, - }, - `attr = [ ] -`, - hcl.Pos{Line: 1, Column: 10, Byte: 9}, - lang.ZeroCandidates(), - }, - { - "range filtered references", - &schema.BodySchema{ - Blocks: map[string]*schema.BlockSchema{ - "custom": { - Labels: []*schema.LabelSchema{ - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "greeting": { - Constraint: schema.LiteralType{Type: cty.String}, - IsOptional: true, - }, - "blah": { - Constraint: schema.LiteralType{Type: cty.Bool}, - IsComputed: true, - }, - }, - }, - Address: &schema.BlockAddrSchema{ - Steps: []schema.AddrStep{ - schema.StaticStep{Name: "custom"}, - schema.LabelStep{Index: 0}, - }, - ScopeId: lang.ScopeId("custom"), - BodyAsData: true, - InferBody: true, - }, - }, - "another_block": { - Labels: []*schema.LabelSchema{ - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "one": { - Constraint: schema.Reference{OfType: cty.String}, - IsOptional: true, - }, - }, - }, - }, - }, - }, - reference.Targets{}, - `custom "test" { - greeting = "hello" -} - -another_block "meh" { - one = -} -`, - hcl.Pos{Line: 6, Column: 9, Byte: 70}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "custom.test", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 6, - Column: 9, - Byte: 70, - }, - End: hcl.Pos{ - Line: 6, - Column: 9, - Byte: 70, - }, - }, - NewText: `custom.test`, - Snippet: `custom.test`, - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "partially matching references before dot", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.String, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = var -`, - hcl.Pos{Line: 1, Column: 11, Byte: 10}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 11, - Byte: 10, - }, - }, - NewText: "var.first", - Snippet: "var.first", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 11, - Byte: 10, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "partially matching references after dot", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.String, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = var. -`, - hcl.Pos{Line: 1, Column: 12, Byte: 11}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.first", - Snippet: "var.first", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "step-based completion - top level", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.Object(map[string]cty.Type{ - "nested": cty.String, - }), - NestedTargets: reference.Targets{ - { - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - lang.AttrStep{Name: "nested"}, - }, - Type: cty.String, - }, - }, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = var. -`, - hcl.Pos{Line: 1, Column: 12, Byte: 11}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.first", - Snippet: "var.first", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "step-based completion - inside object", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.Object(map[string]cty.Type{ - "nested": cty.String, - }), - NestedTargets: reference.Targets{ - { - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - lang.AttrStep{Name: "nested"}, - }, - Type: cty.String, - }, - }, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = var.first. -`, - hcl.Pos{Line: 1, Column: 18, Byte: 17}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first.nested", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 18, - Byte: 17, - }, - }, - NewText: "var.first.nested", - Snippet: "var.first.nested", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "step-based completion - inside list", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.List(cty.Object(map[string]cty.Type{ - "nested": cty.String, - })), - NestedTargets: reference.Targets{ - { - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - lang.IndexStep{Key: cty.NumberIntVal(0)}, - }, - Type: cty.Object(map[string]cty.Type{ - "nested": cty.String, - }), - NestedTargets: reference.Targets{ - { - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - lang.IndexStep{Key: cty.NumberIntVal(0)}, - lang.AttrStep{Name: "nested"}, - }, - Type: cty.String, - }, - }, - }, - }, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = var. -`, - hcl.Pos{Line: 1, Column: 12, Byte: 11}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "list of object", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.first", - Snippet: "var.first", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - { - "step-based completion - inside map", - &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - }, - Type: cty.Map(cty.String), - NestedTargets: reference.Targets{ - { - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "first"}, - lang.IndexStep{Key: cty.StringVal("foo")}, - }, - Type: cty.String, - }, - }, - }, - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "second"}, - }, - Type: cty.String, - }, - }, - `attr = var. -`, - hcl.Pos{Line: 1, Column: 12, Byte: 11}, - lang.CompleteCandidates([]lang.Candidate{ - { - Label: "var.first", - Detail: "map of string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.first", - Snippet: `var.first`, - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.second", - Detail: "string", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - NewText: "var.second", - Snippet: "var.second", - }, - Kind: lang.TraversalCandidateKind, - }, - }), - }, - } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { - f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) - - testDir := t.TempDir() - dirReader := &testPathReader{ - paths: map[string]*PathContext{ - testDir: { - Schema: tc.bodySchema, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - ReferenceTargets: tc.builtinRefs, - }, - }, - } - decoder := NewDecoder(dirReader) - d, err := decoder.Path(lang.Path{Path: testDir}) - if err != nil { - t.Fatal(err) - } - refTargets, err := d.CollectReferenceTargets() - if err != nil { - t.Fatal(err) - } - - dirReader.paths[testDir].ReferenceTargets = append(dirReader.paths[testDir].ReferenceTargets, refTargets...) - - candidates, err := d.CompletionAtPos(ctx, "test.tf", tc.pos) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(tc.expectedCandidates, candidates); diff != "" { - t.Fatalf("unexpected candidates: %s", diff) - } - }) - } -} - -func TestLegacyDecoder_CandidateAtPos_expressions_crossFileTraversal(t *testing.T) { - ctx := context.Background() - f1, _ := hclsyntax.ParseConfig([]byte(`variable "aaa" {} -variable "bbb" {} -variable "ccc" {} -`), "test1.tf", hcl.InitialPos) - f2, _ := hclsyntax.ParseConfig([]byte(`value = -`), "test2.tf", hcl.InitialPos) - - bodySchema := &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "value": { - IsOptional: true, - Constraint: schema.Reference{ - OfScopeId: lang.ScopeId("variable"), - OfType: cty.DynamicPseudoType, - }, - }, - }, - Blocks: map[string]*schema.BlockSchema{ - "variable": { - Labels: []*schema.LabelSchema{ - {Name: "name"}, - }, - Address: &schema.BlockAddrSchema{ - Steps: []schema.AddrStep{ - schema.StaticStep{Name: "var"}, - schema.LabelStep{Index: 0}, - }, - FriendlyName: "variable", - ScopeId: lang.ScopeId("variable"), - AsTypeOf: &schema.BlockAsTypeOf{ - AttributeExpr: "type", - }, - }, - }, - }, - } - - testDir := t.TempDir() - dirReader := &testPathReader{ - paths: map[string]*PathContext{ - testDir: { - Schema: bodySchema, - Files: map[string]*hcl.File{ - "test1.tf": f1, - "test2.tf": f2, - }, - ReferenceTargets: reference.Targets{}, - }, - }, - } - decoder := NewDecoder(dirReader) - d, err := decoder.Path(lang.Path{Path: testDir}) - if err != nil { - t.Fatal(err) - } - refTargets, err := d.CollectReferenceTargets() - if err != nil { - t.Fatal(err) - } - - expectedTargets := reference.Targets{ - { - Addr: lang.Address{lang.RootStep{Name: "var"}, lang.AttrStep{Name: "aaa"}}, - ScopeId: lang.ScopeId("variable"), - RangePtr: &hcl.Range{ - Filename: "test1.tf", - Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: hcl.Pos{Line: 1, Column: 18, Byte: 17}, - }, - DefRangePtr: &hcl.Range{ - Filename: "test1.tf", - Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, - End: hcl.Pos{Line: 1, Column: 15, Byte: 14}, - }, - Type: cty.DynamicPseudoType, - }, - { - Addr: lang.Address{lang.RootStep{Name: "var"}, lang.AttrStep{Name: "bbb"}}, - ScopeId: lang.ScopeId("variable"), - RangePtr: &hcl.Range{ - Filename: "test1.tf", - Start: hcl.Pos{Line: 2, Column: 1, Byte: 18}, - End: hcl.Pos{Line: 2, Column: 18, Byte: 35}, - }, - DefRangePtr: &hcl.Range{ - Filename: "test1.tf", - Start: hcl.Pos{Line: 2, Column: 1, Byte: 18}, - End: hcl.Pos{Line: 2, Column: 15, Byte: 32}, - }, - Type: cty.DynamicPseudoType, - }, - { - Addr: lang.Address{lang.RootStep{Name: "var"}, lang.AttrStep{Name: "ccc"}}, - ScopeId: lang.ScopeId("variable"), - RangePtr: &hcl.Range{ - Filename: "test1.tf", - Start: hcl.Pos{Line: 3, Column: 1, Byte: 36}, - End: hcl.Pos{Line: 3, Column: 18, Byte: 53}, - }, - DefRangePtr: &hcl.Range{ - Filename: "test1.tf", - Start: hcl.Pos{Line: 3, Column: 1, Byte: 36}, - End: hcl.Pos{Line: 3, Column: 15, Byte: 50}, - }, - Type: cty.DynamicPseudoType, - }, - } - if diff := cmp.Diff(expectedTargets, refTargets, ctydebug.CmpOptions); diff != "" { - t.Fatalf("unexpected targets: %s", diff) - } - - dirReader.paths[testDir].ReferenceTargets = refTargets - - candidates, err := d.CompletionAtPos(ctx, "test2.tf", hcl.Pos{ - Line: 1, - Column: 9, - Byte: 8, - }) - if err != nil { - t.Fatal(err) - } - - expectedCandidates := lang.Candidates{ - List: []lang.Candidate{ - { - Label: "var.aaa", - Detail: "dynamic", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test2.tf", - Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, - End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, - }, - NewText: "var.aaa", - Snippet: "var.aaa", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.bbb", - Detail: "dynamic", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test2.tf", - Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, - End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, - }, - NewText: "var.bbb", - Snippet: "var.bbb", - }, - Kind: lang.TraversalCandidateKind, - }, - { - Label: "var.ccc", - Detail: "dynamic", - TextEdit: lang.TextEdit{ - Range: hcl.Range{ - Filename: "test2.tf", - Start: hcl.Pos{Line: 1, Column: 9, Byte: 8}, - End: hcl.Pos{Line: 1, Column: 9, Byte: 8}, - }, - NewText: "var.ccc", - Snippet: "var.ccc", - }, - Kind: lang.TraversalCandidateKind, - }, - }, - IsComplete: true, - } - if diff := cmp.Diff(expectedCandidates, candidates); diff != "" { - t.Fatalf("unexpected candidates: %s", diff) - } -} - -func TestLegacyDecoder_CandidateAtPos_expressions_Hooks(t *testing.T) { - ctx := context.Background() - testCases := []struct { - testName string - attrSchema map[string]*schema.AttributeSchema - cfg string - pos hcl.Pos - completionHooks CompletionFuncMap - expectedCandidates lang.Candidates - }{ - { - "simple hook", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.String}, - CompletionHooks: lang.CompletionHooks{ - { - Name: "TestCompletionHook", - }, - }, - }, - }, - `attr = -`, - hcl.Pos{Line: 1, Column: 8, Byte: 7}, - CompletionFuncMap{ - "TestCompletionHook": func(ctx context.Context, value cty.Value) ([]Candidate, error) { - candidates := []Candidate{ - { - Label: "\"label\"", - Detail: "detail", - Kind: lang.StringCandidateKind, - Description: lang.PlainText("description"), - RawInsertText: "\"insertText\"", - }, - } - return candidates, nil - }, - }, - lang.IncompleteCandidates([]lang.Candidate{ - { - Label: "\"label\"", - Detail: "detail", - Kind: lang.StringCandidateKind, - Description: lang.PlainText("description"), - TextEdit: lang.TextEdit{ - NewText: "\"insertText\"", - Snippet: "\"insertText\"", - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - }, - }, - }, - }), - }, - { - "hook with prefix", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.String}, - CompletionHooks: lang.CompletionHooks{ - { - Name: "TestCompletionHook", - }, - }, - }, - }, - `attr = "pa" -`, - hcl.Pos{Line: 1, Column: 11, Byte: 10}, - CompletionFuncMap{ - "TestCompletionHook": func(ctx context.Context, value cty.Value) ([]Candidate, error) { - candidates := []Candidate{ - { - Label: value.AsString(), - Kind: lang.StringCandidateKind, - RawInsertText: value.AsString(), - }, - } - return candidates, nil - }, - }, - lang.IncompleteCandidates([]lang.Candidate{ - { - Label: "pa", - Kind: lang.StringCandidateKind, - TextEdit: lang.TextEdit{ - NewText: "pa", - Snippet: "pa", - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, - }, - }, - }, - }), - }, - { - "incomplete attr value", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.String}, - CompletionHooks: lang.CompletionHooks{ - { - Name: "TestCompletionHook", - }, - }, - }, - }, - `attr = "pa - -`, - hcl.Pos{Line: 1, Column: 11, Byte: 10}, - CompletionFuncMap{ - "TestCompletionHook": func(ctx context.Context, value cty.Value) ([]Candidate, error) { - candidates := []Candidate{ - { - Label: value.AsString(), - Kind: lang.StringCandidateKind, - RawInsertText: value.AsString(), - }, - } - return candidates, nil - }, - }, - lang.IncompleteCandidates([]lang.Candidate{ - { - Label: "pa", - Kind: lang.StringCandidateKind, - TextEdit: lang.TextEdit{ - NewText: "pa", - Snippet: "pa", - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, - End: hcl.Pos{Line: 1, Column: 11, Byte: 10}, - }, - }, - }, - }), - }, - } - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { - bodySchema := &schema.BodySchema{ - Attributes: tc.attrSchema, - } - - // We're ignoring diagnostics here, since some test cases may contain invalid HCL - f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) - d := testPathDecoder(t, &PathContext{ - Schema: bodySchema, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - }) - for n, h := range tc.completionHooks { - d.decoderCtx.CompletionHooks[n] = h - } - - candidates, err := d.CompletionAtPos(ctx, "test.tf", tc.pos) - if err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(tc.expectedCandidates, candidates); diff != "" { - t.Fatalf("unexpected candidates: %s", diff) - } - }) - } -} - -func TestLegacyDecoder_CandidateAtPos_maxCandidates(t *testing.T) { - ctx := context.Background() - bodySchema := &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.LiteralType{Type: cty.String}, - CompletionHooks: lang.CompletionHooks{ - { - Name: "TestCompletionHook50", - }, - { - Name: "TestCompletionHook70", - }, - }, - }, - }, - } - - // We're ignoring diagnostics here, since our config contains invalid HCL - f, _ := hclsyntax.ParseConfig([]byte(`attr = `), "test.tf", hcl.InitialPos) - d := testPathDecoder(t, &PathContext{ - Schema: bodySchema, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - }) - d.decoderCtx.CompletionHooks["TestCompletionHook50"] = func(ctx context.Context, value cty.Value) ([]Candidate, error) { - candidates := make([]Candidate, 0) - for i := 0; i < 50; i++ { - candidates = append(candidates, Candidate{ - Label: fmt.Sprintf("\"Label %d\"", i), - Kind: lang.StringCandidateKind, - }) - } - return candidates, nil - } - d.decoderCtx.CompletionHooks["TestCompletionHook70"] = func(ctx context.Context, value cty.Value) ([]Candidate, error) { - candidates := make([]Candidate, 0) - for i := 0; i < 70; i++ { - candidates = append(candidates, Candidate{ - Label: fmt.Sprintf("\"Label %d\"", i), - Kind: lang.StringCandidateKind, - }) - } - return candidates, nil - } - - candidates, err := d.CompletionAtPos(ctx, "test.tf", hcl.Pos{Line: 1, Column: 8, Byte: 7}) - if err != nil { - t.Fatal(err) - } - count := len(candidates.List) - - if uint(count) != d.maxCandidates { - t.Fatalf("unexpected candidates count: %d", count) - } -} diff --git a/decoder/expression_candidates_test.go b/decoder/expression_candidates_test.go new file mode 100644 index 00000000..c455045c --- /dev/null +++ b/decoder/expression_candidates_test.go @@ -0,0 +1,251 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package decoder + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" +) + +func TestLegacyDecoder_CandidateAtPos_expressions_Hooks(t *testing.T) { + ctx := context.Background() + testCases := []struct { + testName string + attrSchema map[string]*schema.AttributeSchema + cfg string + pos hcl.Pos + completionHooks CompletionFuncMap + expectedCandidates lang.Candidates + }{ + { + "simple hook", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.LiteralType{Type: cty.String}, + CompletionHooks: lang.CompletionHooks{ + { + Name: "TestCompletionHook", + }, + }, + }, + }, + `attr = +`, + hcl.Pos{Line: 1, Column: 8, Byte: 7}, + CompletionFuncMap{ + "TestCompletionHook": func(ctx context.Context, value cty.Value) ([]Candidate, error) { + candidates := []Candidate{ + { + Label: "\"label\"", + Detail: "detail", + Kind: lang.StringCandidateKind, + Description: lang.PlainText("description"), + RawInsertText: "\"insertText\"", + }, + } + return candidates, nil + }, + }, + lang.IncompleteCandidates([]lang.Candidate{ + { + Label: "\"label\"", + Detail: "detail", + Kind: lang.StringCandidateKind, + Description: lang.PlainText("description"), + TextEdit: lang.TextEdit{ + NewText: "\"insertText\"", + Snippet: "\"insertText\"", + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + }, + }, + }, + }), + }, + { + "hook with prefix", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.LiteralType{Type: cty.String}, + CompletionHooks: lang.CompletionHooks{ + { + Name: "TestCompletionHook", + }, + }, + }, + }, + `attr = "pa" +`, + hcl.Pos{Line: 1, Column: 11, Byte: 10}, + CompletionFuncMap{ + "TestCompletionHook": func(ctx context.Context, value cty.Value) ([]Candidate, error) { + candidates := []Candidate{ + { + Label: value.AsString(), + Kind: lang.StringCandidateKind, + RawInsertText: value.AsString(), + }, + } + return candidates, nil + }, + }, + lang.IncompleteCandidates([]lang.Candidate{ + { + Label: "pa", + Kind: lang.StringCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "pa", + Snippet: "pa", + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 12, Byte: 11}, + }, + }, + }, + }), + }, + { + "incomplete attr value", + map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.LiteralType{Type: cty.String}, + CompletionHooks: lang.CompletionHooks{ + { + Name: "TestCompletionHook", + }, + }, + }, + }, + `attr = "pa + +`, + hcl.Pos{Line: 1, Column: 11, Byte: 10}, + CompletionFuncMap{ + "TestCompletionHook": func(ctx context.Context, value cty.Value) ([]Candidate, error) { + candidates := []Candidate{ + { + Label: value.AsString(), + Kind: lang.StringCandidateKind, + RawInsertText: value.AsString(), + }, + } + return candidates, nil + }, + }, + lang.IncompleteCandidates([]lang.Candidate{ + { + Label: "pa", + Kind: lang.StringCandidateKind, + TextEdit: lang.TextEdit{ + NewText: "pa", + Snippet: "pa", + Range: hcl.Range{ + Filename: "test.tf", + Start: hcl.Pos{Line: 1, Column: 8, Byte: 7}, + End: hcl.Pos{Line: 1, Column: 11, Byte: 10}, + }, + }, + }, + }), + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d-%s", i, tc.testName), func(t *testing.T) { + bodySchema := &schema.BodySchema{ + Attributes: tc.attrSchema, + } + + // We're ignoring diagnostics here, since some test cases may contain invalid HCL + f, _ := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + for n, h := range tc.completionHooks { + d.decoderCtx.CompletionHooks[n] = h + } + + candidates, err := d.CompletionAtPos(ctx, "test.tf", tc.pos) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectedCandidates, candidates); diff != "" { + t.Fatalf("unexpected candidates: %s", diff) + } + }) + } +} + +func TestLegacyDecoder_CandidateAtPos_maxCandidates(t *testing.T) { + ctx := context.Background() + bodySchema := &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{ + "attr": { + Constraint: schema.LiteralType{Type: cty.String}, + CompletionHooks: lang.CompletionHooks{ + { + Name: "TestCompletionHook50", + }, + { + Name: "TestCompletionHook70", + }, + }, + }, + }, + } + + // We're ignoring diagnostics here, since our config contains invalid HCL + f, _ := hclsyntax.ParseConfig([]byte(`attr = `), "test.tf", hcl.InitialPos) + d := testPathDecoder(t, &PathContext{ + Schema: bodySchema, + Files: map[string]*hcl.File{ + "test.tf": f, + }, + }) + d.decoderCtx.CompletionHooks["TestCompletionHook50"] = func(ctx context.Context, value cty.Value) ([]Candidate, error) { + candidates := make([]Candidate, 0) + for i := 0; i < 50; i++ { + candidates = append(candidates, Candidate{ + Label: fmt.Sprintf("\"Label %d\"", i), + Kind: lang.StringCandidateKind, + }) + } + return candidates, nil + } + d.decoderCtx.CompletionHooks["TestCompletionHook70"] = func(ctx context.Context, value cty.Value) ([]Candidate, error) { + candidates := make([]Candidate, 0) + for i := 0; i < 70; i++ { + candidates = append(candidates, Candidate{ + Label: fmt.Sprintf("\"Label %d\"", i), + Kind: lang.StringCandidateKind, + }) + } + return candidates, nil + } + + candidates, err := d.CompletionAtPos(ctx, "test.tf", hcl.Pos{Line: 1, Column: 8, Byte: 7}) + if err != nil { + t.Fatal(err) + } + count := len(candidates.List) + + if uint(count) != d.maxCandidates { + t.Fatalf("unexpected candidates count: %d", count) + } +} diff --git a/decoder/hover_expressions_legacy_test.go b/decoder/hover_expressions_legacy_test.go deleted file mode 100644 index 6de5e775..00000000 --- a/decoder/hover_expressions_legacy_test.go +++ /dev/null @@ -1,1699 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package decoder - -import ( - "context" - "errors" - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/hcl-lang/lang" - "github.com/hashicorp/hcl-lang/reference" - "github.com/hashicorp/hcl-lang/schema" - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/zclconf/go-cty-debug/ctydebug" - "github.com/zclconf/go-cty/cty" -) - -func TestLegacyDecoder_HoverAtPos_expressions(t *testing.T) { - testCases := []struct { - name string - attrSchema map[string]*schema.AttributeSchema - cfg string - pos hcl.Pos - expectedData *lang.HoverData - expectedErr interface{} // TODO current comparison with errors.As will assume any not-nil error matches - }{ - { - "string as type", - map[string]*schema.AttributeSchema{ - "str_attr": { - Constraint: schema.LiteralType{Type: cty.String}, - }, - }, - `str_attr = "test"`, - hcl.Pos{Line: 1, Column: 15, Byte: 14}, - &lang.HoverData{ - Content: lang.Markdown("_string_"), - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - End: hcl.Pos{ - Line: 1, - Column: 18, - Byte: 17, - }, - }, - }, - nil, - }, - { - "single-line heredoc string as type", - map[string]*schema.AttributeSchema{ - "str_attr": { - Constraint: schema.LiteralType{Type: cty.String}, - }, - }, - `str_attr = < 0 { - t.Fatal(pDiags) - } - - d := testPathDecoder(t, &PathContext{ - Schema: bodySchema, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - }) - - tokens, err := d.SemanticTokensInFile(ctx, "test.tf") - if err != nil { - t.Fatal(err) - } - - diff := cmp.Diff(tc.expectedTokens, tokens) - if diff != "" { - t.Fatalf("unexpected tokens: %s", diff) - } - }) - } -} - -func TestLegacyDecoder_SemanticTokensInFile_traversalExpression(t *testing.T) { - testCases := []struct { - name string - attrSchema map[string]*schema.AttributeSchema - refs reference.Targets - origins reference.Origins - cfg string - expectedTokens []lang.SemanticToken - }{ - { - "unknown traversal", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - reference.Targets{}, - reference.Origins{ - reference.LocalOrigin{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 7, - Byte: 6, - }, - End: hcl.Pos{ - Line: 1, - Column: 15, - Byte: 14, - }, - }, - }, - }, - `attr = var.blah -`, - []lang.SemanticToken{ - { // attr - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - }, - }, - { - "known mismatching traversal", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - Type: cty.List(cty.Bool), - }, - }, - reference.Origins{ - reference.LocalOrigin{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 7, - Byte: 6, - }, - End: hcl.Pos{ - Line: 1, - Column: 15, - Byte: 14, - }, - }, - Constraints: reference.OriginConstraints{ - reference.OriginConstraint{ - OfType: cty.String, - }, - }, - }, - }, - `attr = var.blah -`, - []lang.SemanticToken{ - { // attr - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - }, - }, - { - "known matching traversal", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - Type: cty.String, - }, - }, - reference.Origins{ - reference.LocalOrigin{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 7, - Byte: 6, - }, - End: hcl.Pos{ - Line: 1, - Column: 15, - Byte: 14, - }, - }, - Constraints: reference.OriginConstraints{ - reference.OriginConstraint{ - OfType: cty.String, - }, - }, - }, - }, - `attr = var.blah -`, - []lang.SemanticToken{ - { // attr - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - { // var - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 11, - Byte: 10, - }, - }, - }, - { // blah - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - End: hcl.Pos{ - Line: 1, - Column: 16, - Byte: 15, - }, - }, - }, - }, - }, - { - "known scope matching traversal", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfScopeId: lang.ScopeId("foo")}, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - ScopeId: lang.ScopeId("foo"), - }, - }, - reference.Origins{ - reference.LocalOrigin{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 7, - Byte: 6, - }, - End: hcl.Pos{ - Line: 1, - Column: 15, - Byte: 14, - }, - }, - Constraints: reference.OriginConstraints{ - reference.OriginConstraint{ - OfScopeId: lang.ScopeId("foo"), - }, - }, - }, - }, - `attr = var.blah -`, - []lang.SemanticToken{ - { // attr - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - { // var - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 11, - Byte: 10, - }, - }, - }, - { // blah - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - End: hcl.Pos{ - Line: 1, - Column: 16, - Byte: 15, - }, - }, - }, - }, - }, - { - "matching traversal with indexes", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{}, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "foo"}, - lang.IndexStep{Key: cty.StringVal("test")}, - lang.AttrStep{Name: "bar"}, - lang.IndexStep{Key: cty.NumberIntVal(4)}, - }, - }, - }, - reference.Origins{ - reference.LocalOrigin{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "foo"}, - lang.IndexStep{Key: cty.StringVal("test")}, - lang.AttrStep{Name: "bar"}, - lang.IndexStep{Key: cty.NumberIntVal(4)}, - }, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 7, - Byte: 6, - }, - End: hcl.Pos{ - Line: 1, - Column: 29, - Byte: 28, - }, - }, - Constraints: reference.OriginConstraints{ - reference.OriginConstraint{}, - }, - }, - }, - `attr = var.foo["test"].bar[4] -`, - []lang.SemanticToken{ - { // attr - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - { // var - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 11, - Byte: 10, - }, - }, - }, - { // foo - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - End: hcl.Pos{ - Line: 1, - Column: 15, - Byte: 14, - }, - }, - }, - { // "test" - Type: lang.TokenMapKey, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 16, - Byte: 15, - }, - End: hcl.Pos{ - Line: 1, - Column: 22, - Byte: 21, - }, - }, - }, - { // bar - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 24, - Byte: 23, - }, - End: hcl.Pos{ - Line: 1, - Column: 27, - Byte: 26, - }, - }, - }, - { // 4 - Type: lang.TokenNumber, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 28, - Byte: 27, - }, - End: hcl.Pos{ - Line: 1, - Column: 29, - Byte: 28, - }, - }, - }, - }, - }, - { - "loosely matching traversal of unknown type", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - reference.Targets{ - { - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "foo"}, - }, - Type: cty.DynamicPseudoType, - }, - }, - reference.Origins{ - reference.LocalOrigin{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "foo"}, - lang.AttrStep{Name: "bar"}, - }, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 7, - Byte: 6, - }, - End: hcl.Pos{ - Line: 1, - Column: 18, - Byte: 17, - }, - }, - Constraints: reference.OriginConstraints{ - reference.OriginConstraint{ - OfType: cty.String, - }, - }, - }, - }, - `attr = var.foo.bar -`, - []lang.SemanticToken{ - { // attr - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - { // var - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 11, - Byte: 10, - }, - }, - }, - { // foo - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - End: hcl.Pos{ - Line: 1, - Column: 15, - Byte: 14, - }, - }, - }, - { // bar - Type: lang.TokenTraversalStep, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 16, - Byte: 15, - }, - End: hcl.Pos{ - Line: 1, - Column: 19, - Byte: 18, - }, - }, - }, - }, - }, - { - "matching target but missing collected origin", - map[string]*schema.AttributeSchema{ - "attr": { - Constraint: schema.Reference{OfType: cty.String}, - }, - }, - reference.Targets{ - reference.Target{ - Addr: lang.Address{ - lang.RootStep{Name: "var"}, - lang.AttrStep{Name: "blah"}, - }, - Type: cty.String, - }, - }, - reference.Origins{}, - `attr = var.blah -`, - []lang.SemanticToken{ - { // attr - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - }, - }, - } - - ctx := context.Background() - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { - bodySchema := &schema.BodySchema{ - Attributes: tc.attrSchema, - } - - f, pDiags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) - if len(pDiags) > 0 { - t.Fatal(pDiags) - } - - d := testPathDecoder(t, &PathContext{ - Schema: bodySchema, - ReferenceTargets: tc.refs, - ReferenceOrigins: tc.origins, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - }) - - tokens, err := d.SemanticTokensInFile(ctx, "test.tf") - if err != nil { - t.Fatal(err) - } - - diff := cmp.Diff(tc.expectedTokens, tokens) - if diff != "" { - t.Fatalf("unexpected tokens: %s", diff) - } - }) - } -} - -func TestLegacyDecoder_SemanticTokensInFile_typeDeclaration(t *testing.T) { - testCases := []struct { - name string - attrSchema map[string]*schema.AttributeSchema - cfg string - expectedTokens []lang.SemanticToken - }{ - { - "known primitive type", - map[string]*schema.AttributeSchema{ - "type": { - Constraint: schema.TypeDeclaration{}, - }, - }, - `type = string`, - []lang.SemanticToken{ - { - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - { - Type: lang.TokenTypePrimitive, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 14, - Byte: 13, - }, - }, - }, - }, - }, - { - "unknown primitive type", - map[string]*schema.AttributeSchema{ - "type": { - Constraint: schema.TypeDeclaration{}, - }, - }, - `type = foobar`, - []lang.SemanticToken{ - { - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - }, - }, - { - "known collection type", - map[string]*schema.AttributeSchema{ - "type": { - Constraint: schema.TypeDeclaration{}, - }, - }, - `type = list(any)`, - []lang.SemanticToken{ - { - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - { - Type: lang.TokenTypeComplex, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 12, - Byte: 11, - }, - }, - }, - { - Type: lang.TokenTypePrimitive, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 13, - Byte: 12, - }, - End: hcl.Pos{ - Line: 1, - Column: 16, - Byte: 15, - }, - }, - }, - }, - }, - { - "unknown collection type", - map[string]*schema.AttributeSchema{ - "type": { - Constraint: schema.TypeDeclaration{}, - }, - }, - `type = foobar(any)`, - []lang.SemanticToken{ - { - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - }, - }, - { - "known object type", - map[string]*schema.AttributeSchema{ - "type": { - Constraint: schema.TypeDeclaration{}, - }, - }, - `type = object({ - enabled = bool -})`, - []lang.SemanticToken{ - { - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - { - Type: lang.TokenTypeComplex, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 8, - Byte: 7, - }, - End: hcl.Pos{ - Line: 1, - Column: 14, - Byte: 13, - }, - }, - }, - { - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 2, - Column: 3, - Byte: 18, - }, - End: hcl.Pos{ - Line: 2, - Column: 10, - Byte: 25, - }, - }, - }, - { - Type: lang.TokenTypePrimitive, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 2, - Column: 13, - Byte: 28, - }, - End: hcl.Pos{ - Line: 2, - Column: 17, - Byte: 32, - }, - }, - }, - }, - }, - { - "unknown object type", - map[string]*schema.AttributeSchema{ - "type": { - Constraint: schema.TypeDeclaration{}, - }, - }, - `type = foobar({ - enabled = bool -})`, - []lang.SemanticToken{ - { - Type: lang.TokenAttrName, - Modifiers: []lang.SemanticTokenModifier{}, - Range: hcl.Range{ - Filename: "test.tf", - Start: hcl.Pos{ - Line: 1, - Column: 1, - Byte: 0, - }, - End: hcl.Pos{ - Line: 1, - Column: 5, - Byte: 4, - }, - }, - }, - }, - }, - } - - ctx := context.Background() - - for i, tc := range testCases { - t.Run(fmt.Sprintf("%d-%s", i, tc.name), func(t *testing.T) { - bodySchema := &schema.BodySchema{ - Attributes: tc.attrSchema, - } - - f, pDiags := hclsyntax.ParseConfig([]byte(tc.cfg), "test.tf", hcl.InitialPos) - if len(pDiags) > 0 { - t.Fatal(pDiags) - } - - d := testPathDecoder(t, &PathContext{ - Schema: bodySchema, - Files: map[string]*hcl.File{ - "test.tf": f, - }, - }) - - tokens, err := d.SemanticTokensInFile(ctx, "test.tf") - if err != nil { - t.Fatal(err) - } - - diff := cmp.Diff(tc.expectedTokens, tokens) - if diff != "" { - t.Fatalf("unexpected tokens: %s", diff) - } - }) - } -}