Skip to content

Commit ee31994

Browse files
committed
feat: Dispatch Hover and Definition to values handlers by FileType
Extend the Hover and Definition switch to route FileTypeValues through the new values handlers. - hoverValues builds markdown popups for variables and dependency references (newValuesVariableHoverResponse / newValuesDependencyHoverResponse). - definitionValues resolves dependency references to the matching terragrunt.hcl via ResolveValuesDependencyPath. - Rewrite TestState_Hover_ValuesFile to assert real popups and add TestState_Definition_ValuesFile covering dependency navigation.
1 parent 466bdee commit ee31994

2 files changed

Lines changed: 159 additions & 6 deletions

File tree

internal/tg/state.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ func (s *State) Hover(l logger.Logger, id int, docURI protocol.DocumentURI, posi
131131
return s.hoverUnit(l, id, st, position)
132132
case store.FileTypeStack:
133133
return s.hoverStack(l, id, st, position)
134-
case store.FileTypeUnknown, store.FileTypeValues:
134+
case store.FileTypeValues:
135+
return s.hoverValues(l, id, st, position)
136+
case store.FileTypeUnknown:
135137
return newEmptyHoverResponse(id)
136138
}
137139

@@ -267,6 +269,53 @@ func newStackBlockHoverResponse(id int, stackName string) lsp.HoverResponse {
267269
}
268270
}
269271

272+
func (s *State) hoverValues(l logger.Logger, id int, st store.Store, position protocol.Position) lsp.HoverResponse {
273+
target, context := hover.GetValuesHoverTargetWithContext(l, st, position)
274+
275+
l.Debug(
276+
"Values hover with context",
277+
"target", target,
278+
"context", context,
279+
)
280+
281+
if target == "" {
282+
return newEmptyHoverResponse(id)
283+
}
284+
285+
switch context {
286+
case hover.HoverContextValuesVariable:
287+
return newValuesVariableHoverResponse(id, target)
288+
case hover.HoverContextValuesDependency:
289+
return newValuesDependencyHoverResponse(id, target)
290+
}
291+
292+
return newEmptyHoverResponse(id)
293+
}
294+
295+
func newValuesVariableHoverResponse(id int, variable string) lsp.HoverResponse {
296+
return lsp.HoverResponse{
297+
Response: lsp.Response{RPC: lsp.RPCVersion, ID: &id},
298+
Result: lsp.HoverResult{
299+
Contents: protocol.MarkupContent{
300+
Kind: protocol.Markdown,
301+
Value: "**Variable: `" + variable + "`**\n\nThis appears to be a variable defined in the values block.\n\nValues files are used to define dynamic input values for units in Terragrunt stacks.",
302+
},
303+
},
304+
}
305+
}
306+
307+
func newValuesDependencyHoverResponse(id int, dependency string) lsp.HoverResponse {
308+
return lsp.HoverResponse{
309+
Response: lsp.Response{RPC: lsp.RPCVersion, ID: &id},
310+
Result: lsp.HoverResult{
311+
Contents: protocol.MarkupContent{
312+
Kind: protocol.Markdown,
313+
Value: "**Dependency: `" + dependency + "`**\n\nThis is a reference to a dependency unit defined elsewhere in your Terragrunt configuration.\n\nThe dependency must be declared before it can be referenced in a values file.",
314+
},
315+
},
316+
}
317+
}
318+
270319
func newEmptyHoverResponse(id int) lsp.HoverResponse {
271320
return lsp.HoverResponse{
272321
Response: lsp.Response{
@@ -294,7 +343,9 @@ func (s *State) Definition(l logger.Logger, id int, docURI protocol.DocumentURI,
294343
return s.definitionUnit(l, id, st, docURI, position)
295344
case store.FileTypeStack:
296345
return s.definitionStack(l, id, st, docURI, position)
297-
case store.FileTypeUnknown, store.FileTypeValues:
346+
case store.FileTypeValues:
347+
return s.definitionValues(l, id, st, docURI, position)
348+
case store.FileTypeUnknown:
298349
return newEmptyDefinitionResponse(id, docURI, position)
299350
}
300351

@@ -488,6 +539,32 @@ func (s *State) definitionStack(l logger.Logger, id int, st store.Store, docURI
488539
return newEmptyDefinitionResponse(id, docURI, position)
489540
}
490541

542+
func (s *State) definitionValues(l logger.Logger, id int, st store.Store, docURI protocol.DocumentURI, position protocol.Position) lsp.DefinitionResponse {
543+
target, context := definition.GetValuesDefinitionTargetWithContext(l, st, position)
544+
545+
l.Debug(
546+
"Values definition discovered",
547+
"target", target,
548+
"context", context,
549+
)
550+
551+
if target == "" {
552+
return newEmptyDefinitionResponse(id, docURI, position)
553+
}
554+
555+
//nolint:gocritic
556+
switch context {
557+
case definition.DefinitionContextValuesDependency:
558+
if resolved, ok := definition.ResolveValuesDependencyPath(target, docURI.Filename()); ok {
559+
return newStackDefinitionResponse(id, resolved)
560+
}
561+
562+
l.Debug("Could not resolve values dependency path", "dependency", target)
563+
}
564+
565+
return newEmptyDefinitionResponse(id, docURI, position)
566+
}
567+
491568
func newStackDefinitionResponse(id int, resolved string) lsp.DefinitionResponse {
492569
return lsp.DefinitionResponse{
493570
Response: lsp.Response{RPC: lsp.RPCVersion, ID: &id},

internal/tg/state_test.go

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -670,17 +670,93 @@ func TestState_Hover_StackFile(t *testing.T) {
670670
func TestState_Hover_ValuesFile(t *testing.T) {
671671
t.Parallel()
672672

673+
tc := []struct {
674+
name string
675+
document string
676+
expectFragment string
677+
position protocol.Position
678+
expectNonEmpty bool
679+
}{
680+
{
681+
name: "plain variable",
682+
document: `some_var = "hello"`,
683+
position: protocol.Position{Line: 0, Character: 2},
684+
expectNonEmpty: true,
685+
expectFragment: "Variable",
686+
},
687+
{
688+
name: "dependency reference",
689+
document: `vpc_id = dependency.vpc.outputs.id`,
690+
position: protocol.Position{Line: 0, Character: 22},
691+
expectNonEmpty: true,
692+
expectFragment: "Dependency",
693+
},
694+
}
695+
696+
for _, tt := range tc {
697+
t.Run(tt.name, func(t *testing.T) {
698+
t.Parallel()
699+
700+
tmpDir := t.TempDir()
701+
valuesPath := filepath.Join(tmpDir, "terragrunt.values.hcl")
702+
valuesURI := uri.File(valuesPath)
703+
704+
state := tg.NewState()
705+
l := testutils.NewTestLogger(t)
706+
707+
_ = state.OpenDocument(t.Context(), l, valuesURI, tt.document)
708+
709+
hover := state.Hover(l, 1, valuesURI, tt.position)
710+
711+
if tt.expectNonEmpty {
712+
assert.NotEmpty(t, hover.Result.Contents.Value)
713+
assert.Contains(t, hover.Result.Contents.Value, tt.expectFragment)
714+
} else {
715+
assert.Empty(t, hover.Result.Contents.Value)
716+
}
717+
})
718+
}
719+
}
720+
721+
func TestState_Definition_ValuesFile(t *testing.T) {
722+
t.Parallel()
723+
724+
// Seed a fake dependency unit directory structure:
725+
// tmp/
726+
// values/terragrunt.values.hcl (the file under test)
727+
// vpc/terragrunt.hcl (the dependency)
673728
tmpDir := t.TempDir()
674-
valuesPath := filepath.Join(tmpDir, "terragrunt.values.hcl")
729+
valuesDir := filepath.Join(tmpDir, "values")
730+
require.NoError(t, os.MkdirAll(valuesDir, 0o755))
731+
depDir := filepath.Join(tmpDir, "vpc")
732+
require.NoError(t, os.MkdirAll(depDir, 0o755))
733+
depFile := filepath.Join(depDir, "terragrunt.hcl")
734+
require.NoError(t, os.WriteFile(depFile, []byte(""), 0o644))
735+
736+
valuesPath := filepath.Join(valuesDir, "terragrunt.values.hcl")
675737
valuesURI := uri.File(valuesPath)
676738

677739
state := tg.NewState()
678740
l := testutils.NewTestLogger(t)
679741

680-
_ = state.OpenDocument(t.Context(), l, valuesURI, `some_var = "hello"`)
742+
_ = state.OpenDocument(t.Context(), l, valuesURI, `vpc_id = dependency.vpc.outputs.id`)
681743

682-
hover := state.Hover(l, 1, valuesURI, protocol.Position{Line: 0, Character: 0})
683-
assert.Empty(t, hover.Result.Contents.Value)
744+
t.Run("dependency reference jumps to terragrunt.hcl", func(t *testing.T) {
745+
t.Parallel()
746+
747+
pos := protocol.Position{Line: 0, Character: 22}
748+
def := state.Definition(l, 1, valuesURI, pos)
749+
assert.Equal(t, uri.File(depFile), def.Result.URI)
750+
})
751+
752+
t.Run("plain variable returns empty definition", func(t *testing.T) {
753+
t.Parallel()
754+
755+
pos := protocol.Position{Line: 0, Character: 2}
756+
def := state.Definition(l, 1, valuesURI, pos)
757+
assert.Equal(t, valuesURI, def.Result.URI)
758+
assert.Equal(t, pos, def.Result.Range.Start)
759+
})
684760
}
685761

686762
func TestState_Definition_StackFile(t *testing.T) {

0 commit comments

Comments
 (0)