From 77c3cb45dd693fb3286da001efea11eab56b111b Mon Sep 17 00:00:00 2001 From: Remy Suen Date: Mon, 7 Jul 2025 10:35:45 -0400 Subject: [PATCH] Recurse into anchors when returning Compose document links Signed-off-by: Remy Suen --- internal/compose/documentLink.go | 13 +++---- internal/compose/documentLink_test.go | 54 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/internal/compose/documentLink.go b/internal/compose/documentLink.go index 60e8385..6744014 100644 --- a/internal/compose/documentLink.go +++ b/internal/compose/documentLink.go @@ -54,10 +54,7 @@ func createFileLink(u *url.URL, serviceNode *ast.MappingValueNode) *protocol.Doc } func stringNode(value ast.Node) *ast.StringNode { - if anchor, ok := value.(*ast.AnchorNode); ok { - value = anchor.Value - } - if s, ok := value.(*ast.StringNode); ok { + if s, ok := resolveAnchor(value).(*ast.StringNode); ok { return s } return nil @@ -65,7 +62,7 @@ func stringNode(value ast.Node) *ast.StringNode { func createDockerfileLink(u *url.URL, serviceNode *ast.MappingValueNode) *protocol.DocumentLink { if serviceNode.Key.GetToken().Value == "build" { - if mappingNode, ok := serviceNode.Value.(*ast.MappingNode); ok { + if mappingNode, ok := resolveAnchor(serviceNode.Value).(*ast.MappingNode); ok { for _, buildAttribute := range mappingNode.Values { if buildAttribute.Key.GetToken().Value == "dockerfile" { return createFileLink(u, buildAttribute) @@ -153,7 +150,7 @@ func scanForLinks(u *url.URL, n *ast.MappingValueNode) []protocol.DocumentLink { case "services": if mappingNode, ok := n.Value.(*ast.MappingNode); ok { for _, node := range mappingNode.Values { - if serviceAttributes, ok := node.Value.(*ast.MappingNode); ok { + if serviceAttributes, ok := resolveAnchor(node.Value).(*ast.MappingNode); ok { for _, serviceAttribute := range serviceAttributes.Values { link := createImageLink(serviceAttribute) if link != nil { @@ -171,7 +168,7 @@ func scanForLinks(u *url.URL, n *ast.MappingValueNode) []protocol.DocumentLink { case "configs": if mappingNode, ok := n.Value.(*ast.MappingNode); ok { for _, node := range mappingNode.Values { - if configAttributes, ok := node.Value.(*ast.MappingNode); ok { + if configAttributes, ok := resolveAnchor(node.Value).(*ast.MappingNode); ok { for _, configAttribute := range configAttributes.Values { link := createObjectFileLink(u, configAttribute) if link != nil { @@ -184,7 +181,7 @@ func scanForLinks(u *url.URL, n *ast.MappingValueNode) []protocol.DocumentLink { case "secrets": if mappingNode, ok := n.Value.(*ast.MappingNode); ok { for _, node := range mappingNode.Values { - if configAttributes, ok := node.Value.(*ast.MappingNode); ok { + if configAttributes, ok := resolveAnchor(node.Value).(*ast.MappingNode); ok { for _, configAttribute := range configAttributes.Values { link := createObjectFileLink(u, configAttribute) if link != nil { diff --git a/internal/compose/documentLink_test.go b/internal/compose/documentLink_test.go index cd498ed..3fefb47 100644 --- a/internal/compose/documentLink_test.go +++ b/internal/compose/documentLink_test.go @@ -544,6 +544,23 @@ services: }, }, }, + { + name: "anchor on the service object", + content: ` +services: + test: &anchor + image: alpine`, + links: []protocol.DocumentLink{ + { + Range: protocol.Range{ + Start: protocol.Position{Line: 3, Character: 11}, + End: protocol.Position{Line: 3, Character: 17}, + }, + Target: types.CreateStringPointer("https://hub.docker.com/_/alpine"), + Tooltip: types.CreateStringPointer("https://hub.docker.com/_/alpine"), + }, + }, + }, { name: "invalid services", content: ` @@ -658,6 +675,19 @@ services: End: protocol.Position{Line: 4, Character: 37}, }, }, + { + name: "anchor on the build object", + content: ` +services: + test: + build: &anchor + dockerfile: ./Dockerfile2`, + path: filepath.Join(testsFolder, "Dockerfile2"), + linkRange: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 18}, + End: protocol.Position{Line: 4, Character: 31}, + }, + }, } for _, tc := range testCases { @@ -737,6 +767,18 @@ configs: End: protocol.Position{Line: 3, Character: 34}, }, }, + { + name: "anchor on the config object", + content: ` +configs: + test: &anchor + file: ./httpd.conf`, + path: filepath.Join(testsFolder, "httpd.conf"), + linkRange: protocol.Range{ + Start: protocol.Position{Line: 3, Character: 10}, + End: protocol.Position{Line: 3, Character: 22}, + }, + }, } for _, tc := range testCases { @@ -816,6 +858,18 @@ secrets: End: protocol.Position{Line: 3, Character: 35}, }, }, + { + name: "anchor on the secret object", + content: ` +secrets: + test: &anchor + file: ./server.cert`, + path: filepath.Join(testsFolder, "server.cert"), + linkRange: protocol.Range{ + Start: protocol.Position{Line: 3, Character: 10}, + End: protocol.Position{Line: 3, Character: 23}, + }, + }, } for _, tc := range testCases {