diff --git a/CHANGELOG.md b/CHANGELOG.md index ba67b38..08c1548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the Docker Language Server will be documented in this fil - Compose - textDocument/documentLink - support recursing into anchors when searching for document links ([#329](https://github.com/docker/docker-language-server/issues/329)) + - return document links for the `file` attribute of a service object's `credential_spec` ([#338](https://github.com/docker/docker-language-server/issues/338)) ### Fixed diff --git a/internal/compose/documentLink.go b/internal/compose/documentLink.go index 6744014..48a41dc 100644 --- a/internal/compose/documentLink.go +++ b/internal/compose/documentLink.go @@ -60,11 +60,11 @@ func stringNode(value ast.Node) *ast.StringNode { return nil } -func createDockerfileLink(u *url.URL, serviceNode *ast.MappingValueNode) *protocol.DocumentLink { - if serviceNode.Key.GetToken().Value == "build" { +func createdNestedLink(u *url.URL, serviceNode *ast.MappingValueNode, parent, child string) *protocol.DocumentLink { + if serviceNode.Key.GetToken().Value == parent { if mappingNode, ok := resolveAnchor(serviceNode.Value).(*ast.MappingNode); ok { for _, buildAttribute := range mappingNode.Values { - if buildAttribute.Key.GetToken().Value == "dockerfile" { + if buildAttribute.Key.GetToken().Value == child { return createFileLink(u, buildAttribute) } } @@ -157,7 +157,12 @@ func scanForLinks(u *url.URL, n *ast.MappingValueNode) []protocol.DocumentLink { links = append(links, *link) } - link = createDockerfileLink(u, serviceAttribute) + link = createdNestedLink(u, serviceAttribute, "build", "dockerfile") + if link != nil { + links = append(links, *link) + } + + link = createdNestedLink(u, serviceAttribute, "credential_spec", "file") if link != nil { links = append(links, *link) } diff --git a/internal/compose/documentLink_test.go b/internal/compose/documentLink_test.go index 3fefb47..edea6d4 100644 --- a/internal/compose/documentLink_test.go +++ b/internal/compose/documentLink_test.go @@ -612,7 +612,7 @@ services: } } -func TestDocumentLink_ServiceDockerfileLinks(t *testing.T) { +func TestDocumentLink_ServiceBuildDockerfileLinks(t *testing.T) { testsFolder := filepath.Join(os.TempDir(), t.Name()) composeStringURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(testsFolder, "compose.yaml")), "/")) @@ -710,6 +710,103 @@ services: } } +func TestDocumentLink_ServiceCredentialSpecFileLinks(t *testing.T) { + testsFolder := filepath.Join(os.TempDir(), t.Name()) + composeStringURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(testsFolder, "compose.yaml")), "/")) + + testCases := []struct { + name string + content string + path string + linkRange protocol.Range + }{ + { + name: "./credential-spec.json", + content: ` +services: + test: + credential_spec: + file: ./credential-spec.json`, + path: filepath.Join(testsFolder, "credential-spec.json"), + linkRange: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 12}, + End: protocol.Position{Line: 4, Character: 34}, + }, + }, + { + name: `"./credential-spec.json"`, + content: ` +services: + test: + credential_spec: + file: "./credential-spec.json"`, + path: filepath.Join(testsFolder, "credential-spec.json"), + linkRange: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 13}, + End: protocol.Position{Line: 4, Character: 35}, + }, + }, + { + name: "anchors and aliases to nothing", + content: ` +secrets: + test: + credential_spec: + file: &credentialSpecFile + test2: + credential_spec: + file: *credentialSpecFile`, + }, + { + name: "anchor has string content", + content: ` +services: + test: + credential_spec: + file: &credentialSpecFile ./credential-spec.json + test2: + credential_spec: + file: *credentialSpecFile`, + path: filepath.Join(testsFolder, "credential-spec.json"), + linkRange: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 32}, + End: protocol.Position{Line: 4, Character: 54}, + }, + }, + { + name: "anchor on the credential_spec object", + content: ` +services: + test: + credential_spec: &anchor + file: ./credential-spec.json`, + path: filepath.Join(testsFolder, "credential-spec.json"), + linkRange: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 12}, + End: protocol.Position{Line: 4, Character: 34}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mgr := document.NewDocumentManager() + doc := document.NewComposeDocument(mgr, "compose.yaml", 1, []byte(tc.content)) + links, err := DocumentLink(context.Background(), composeStringURI, doc) + require.NoError(t, err) + if tc.path == "" { + require.Equal(t, []protocol.DocumentLink{}, links) + } else { + link := protocol.DocumentLink{ + Range: tc.linkRange, + Target: types.CreateStringPointer(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(tc.path), "/"))), + Tooltip: types.CreateStringPointer(tc.path), + } + require.Equal(t, []protocol.DocumentLink{link}, links) + } + }) + } +} func TestDocumentLink_ConfigFileLinks(t *testing.T) { testsFolder := filepath.Join(os.TempDir(), t.Name()) composeStringURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(testsFolder, "compose.yaml")), "/"))