Skip to content

Commit 053ec0d

Browse files
authored
Enable go to module output (#122)
* Add `ImpliedOrigins` to the body schema * Collect and match `ImpliedOrigin` during reference origin collection We match the implied origins from the schema against each `LocalOrigin` we found. If the address matches, we introduce a new `PathOrigin`, so the user can navigate from this origin to the target of the implied origin. This is primarily used for navigating to output block of modules in Terraform. * Add test case for implied origin reference collection
1 parent ad5748a commit 053ec0d

File tree

4 files changed

+146
-10
lines changed

4 files changed

+146
-10
lines changed

decoder/decoder.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ func mergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (*
4747
if mergedSchema.TargetableAs == nil {
4848
mergedSchema.TargetableAs = make([]*schema.Targetable, 0)
4949
}
50+
if mergedSchema.ImpliedOrigins == nil {
51+
mergedSchema.ImpliedOrigins = make([]schema.ImpliedOrigin, 0)
52+
}
5053

5154
depSchema, _, ok := NewBlockSchema(blockSchema).DependentBodySchema(block)
5255
if ok {
@@ -66,10 +69,9 @@ func mergeBlockBodySchemas(block *hcl.Block, blockSchema *schema.BlockSchema) (*
6669
continue
6770
}
6871
}
69-
for _, tBody := range depSchema.TargetableAs {
70-
mergedSchema.TargetableAs = append(mergedSchema.TargetableAs, tBody)
71-
}
7272

73+
mergedSchema.TargetableAs = append(mergedSchema.TargetableAs, depSchema.TargetableAs...)
74+
mergedSchema.ImpliedOrigins = append(mergedSchema.ImpliedOrigins, depSchema.ImpliedOrigins...)
7375
mergedSchema.Targets = depSchema.Targets.Copy()
7476
mergedSchema.DocsLink = depSchema.DocsLink.Copy()
7577
}

decoder/reference_origins.go

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func (d *Decoder) ReferenceOriginsTargetingPos(path lang.Path, file string, pos
5050

5151
func (d *PathDecoder) CollectReferenceOrigins() (reference.Origins, error) {
5252
refOrigins := make(reference.Origins, 0)
53+
impliedOrigins := make([]schema.ImpliedOrigin, 0)
5354

5455
if d.pathCtx.Schema == nil {
5556
// unable to collect reference origins without schema
@@ -64,7 +65,29 @@ func (d *PathDecoder) CollectReferenceOrigins() (reference.Origins, error) {
6465
continue
6566
}
6667

67-
refOrigins = append(refOrigins, d.referenceOriginsInBody(f.Body, d.pathCtx.Schema)...)
68+
os, ios := d.referenceOriginsInBody(f.Body, d.pathCtx.Schema)
69+
refOrigins = append(refOrigins, os...)
70+
impliedOrigins = append(impliedOrigins, ios...)
71+
}
72+
73+
for _, impliedOrigin := range impliedOrigins {
74+
for _, origin := range refOrigins {
75+
localOrigin, ok := origin.(reference.LocalOrigin)
76+
77+
if ok && localOrigin.Addr.Equals(impliedOrigin.OriginAddress) {
78+
refOrigins = append(refOrigins, reference.PathOrigin{
79+
Range: origin.OriginRange(),
80+
TargetAddr: impliedOrigin.TargetAddress,
81+
TargetPath: impliedOrigin.Path,
82+
Constraints: reference.OriginConstraints{
83+
{
84+
OfScopeId: impliedOrigin.Constraints.ScopeId,
85+
OfType: impliedOrigin.Constraints.Type,
86+
},
87+
},
88+
})
89+
}
90+
}
6891
}
6992

7093
sort.SliceStable(refOrigins, func(i, j int) bool {
@@ -75,13 +98,15 @@ func (d *PathDecoder) CollectReferenceOrigins() (reference.Origins, error) {
7598
return refOrigins, nil
7699
}
77100

78-
func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.BodySchema) reference.Origins {
101+
func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.BodySchema) (reference.Origins, []schema.ImpliedOrigin) {
79102
origins := make(reference.Origins, 0)
103+
impliedOrigins := make([]schema.ImpliedOrigin, 0)
80104

81105
if bodySchema == nil {
82-
return origins
106+
return origins, impliedOrigins
83107
}
84108

109+
impliedOrigins = append(impliedOrigins, bodySchema.ImpliedOrigins...)
85110
content := decodeBody(body, bodySchema)
86111

87112
for _, attr := range content.Attributes {
@@ -133,11 +158,14 @@ func (d *PathDecoder) referenceOriginsInBody(body hcl.Body, bodySchema *schema.B
133158
if err != nil {
134159
continue
135160
}
136-
origins = append(origins, d.referenceOriginsInBody(block.Body, mergedSchema)...)
161+
162+
os, ios := d.referenceOriginsInBody(block.Body, mergedSchema)
163+
origins = append(origins, os...)
164+
impliedOrigins = append(impliedOrigins, ios...)
137165
}
138166
}
139167

140-
return origins
168+
return origins, impliedOrigins
141169
}
142170

143171
func (d *PathDecoder) findOriginsInExpression(expr hcl.Expression, ec schema.ExprConstraints) reference.Origins {

decoder/reference_origins_collect_hcl_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,80 @@ tup = [ var.three ]
941941
},
942942
},
943943
},
944+
{
945+
"schema with implied origin",
946+
&schema.BodySchema{
947+
Attributes: map[string]*schema.AttributeSchema{
948+
"attr": {
949+
Expr: schema.ExprConstraints{
950+
schema.TraversalExpr{},
951+
},
952+
},
953+
},
954+
ImpliedOrigins: schema.ImpliedOrigins{
955+
{
956+
OriginAddress: lang.Address{
957+
lang.RootStep{Name: "module"},
958+
lang.AttrStep{Name: "refname"},
959+
lang.AttrStep{Name: "outname"},
960+
},
961+
TargetAddress: lang.Address{
962+
lang.RootStep{Name: "output"},
963+
lang.AttrStep{Name: "outname"},
964+
},
965+
Path: lang.Path{Path: "./local", LanguageID: "terraform"},
966+
Constraints: schema.Constraints{ScopeId: "output"},
967+
},
968+
},
969+
},
970+
`attr = module.refname.outname`,
971+
reference.Origins{
972+
reference.LocalOrigin{
973+
Addr: lang.Address{
974+
lang.RootStep{Name: "module"},
975+
lang.AttrStep{Name: "refname"},
976+
lang.AttrStep{Name: "outname"},
977+
},
978+
Constraints: reference.OriginConstraints{{}},
979+
Range: hcl.Range{
980+
Filename: "test.tf",
981+
Start: hcl.Pos{
982+
Line: 1,
983+
Column: 8,
984+
Byte: 7,
985+
},
986+
End: hcl.Pos{
987+
Line: 1,
988+
Column: 30,
989+
Byte: 29,
990+
},
991+
},
992+
},
993+
reference.PathOrigin{
994+
TargetAddr: lang.Address{
995+
lang.RootStep{Name: "output"},
996+
lang.AttrStep{Name: "outname"},
997+
},
998+
TargetPath: lang.Path{Path: "./local", LanguageID: "terraform"},
999+
Constraints: reference.OriginConstraints{{
1000+
OfScopeId: "output",
1001+
}},
1002+
Range: hcl.Range{
1003+
Filename: "test.tf",
1004+
Start: hcl.Pos{
1005+
Line: 1,
1006+
Column: 8,
1007+
Byte: 7,
1008+
},
1009+
End: hcl.Pos{
1010+
Line: 1,
1011+
Column: 30,
1012+
Byte: 29,
1013+
},
1014+
},
1015+
},
1016+
},
1017+
},
9441018
}
9451019
for i, tc := range testCases {
9461020
t.Run(fmt.Sprintf("%d/%s", i, tc.name), func(t *testing.T) {

schema/body_schema.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,34 @@ type BodySchema struct {
3232
// if not by its declarable attributes or blocks.
3333
TargetableAs Targetables
3434

35-
// Targets represents a location targeted by the body, when used as a body
36-
// dependent on an attribute (e.g. Terraform module source)
35+
// Targets represent a location targeted by the body; when used as a body
36+
// dependent on an attribute (e.g., Terraform module source)
3737
Targets *Target
38+
39+
// ImpliedOrigins represent a list of origins we should revisit during
40+
// reference origin collection. For example, module outputs can be
41+
// referenced from still unknown locations during the build of the module
42+
// schema.
43+
ImpliedOrigins ImpliedOrigins
44+
}
45+
46+
type ImpliedOrigins []ImpliedOrigin
47+
48+
type ImpliedOrigin struct {
49+
OriginAddress lang.Address
50+
51+
TargetAddress lang.Address
52+
Path lang.Path
53+
Constraints Constraints
54+
}
55+
56+
func (io ImpliedOrigin) Copy() ImpliedOrigin {
57+
return ImpliedOrigin{
58+
OriginAddress: io.OriginAddress,
59+
TargetAddress: io.TargetAddress,
60+
Path: io.Path,
61+
Constraints: io.Constraints,
62+
}
3863
}
3964

4065
type DocsLink struct {
@@ -170,6 +195,13 @@ func (bs *BodySchema) Copy() *BodySchema {
170195
}
171196
}
172197

198+
if bs.ImpliedOrigins != nil {
199+
newBs.ImpliedOrigins = make(ImpliedOrigins, len(bs.ImpliedOrigins))
200+
for id, impliedOrigin := range bs.ImpliedOrigins {
201+
newBs.ImpliedOrigins[id] = impliedOrigin.Copy()
202+
}
203+
}
204+
173205
return newBs
174206
}
175207

0 commit comments

Comments
 (0)