Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ All notable changes to the Docker Language Server will be documented in this fil
- Compose
- textDocument/completion
- suggest image tags for images from Docker Hub ([#375](https://github.com/docker/docker-language-server/issues/375))
- textDocument/documentLink
- support providing links for the `env_file` attribute of a service object ([#436](https://github.com/docker/docker-language-server/issues/436))
- Bake
- textDocument/completion
- provide local file and folder name suggestions ([#414](https://github.com/docker/docker-language-server/issues/414))
Expand Down
9 changes: 6 additions & 3 deletions internal/compose/documentLink.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ func createImageLink(serviceNode *ast.MappingValueNode) *protocol.DocumentLink {
return nil
}

func createLabelFileLink(folderAbsolutePath string, wslDollarSign bool, serviceNode *ast.MappingValueNode) []protocol.DocumentLink {
if resolveAnchor(serviceNode.Key).GetToken().Value == "label_file" {
func createFileLinks(folderAbsolutePath string, wslDollarSign bool, serviceNode *ast.MappingValueNode, attributeName string) []protocol.DocumentLink {
if resolveAnchor(serviceNode.Key).GetToken().Value == attributeName {
if sequence, ok := resolveAnchor(serviceNode.Value).(*ast.SequenceNode); ok {
links := []protocol.DocumentLink{}
for _, node := range sequence.Values {
Expand Down Expand Up @@ -203,7 +203,10 @@ func scanForLinks(folderAbsolutePath string, wslDollarSign bool, n *ast.MappingV
links = append(links, *link)
}

labelFileLinks := createLabelFileLink(folderAbsolutePath, wslDollarSign, serviceAttribute)
envFileLinks := createFileLinks(folderAbsolutePath, wslDollarSign, serviceAttribute, "env_file")
links = append(links, envFileLinks...)

labelFileLinks := createFileLinks(folderAbsolutePath, wslDollarSign, serviceAttribute, "label_file")
links = append(links, labelFileLinks...)
}
}
Expand Down
214 changes: 214 additions & 0 deletions internal/compose/documentLink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,220 @@ services:
}
}

func TestDocumentLink_ServiceEnvFileLinks(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: "string value app.labels",
content: `
services:
test:
env_file: .env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 14},
End: protocol.Position{Line: 3, Character: 18},
},
},
{
name: "string value ./.env",
content: `
services:
test:
env_file: ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 14},
End: protocol.Position{Line: 3, Character: 20},
},
},
{
name: "quoted string value \"./.env\"",
content: `
services:
test:
env_file: "./.env"`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 15},
End: protocol.Position{Line: 3, Character: 21},
},
},
{
name: "attribute value is null",
content: `
services:
test:
env_file: null`,
},
{
name: "array items",
content: `
services:
test:
env_file:
- ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 4, Character: 8},
End: protocol.Position{Line: 4, Character: 14},
},
},
{
name: "array item is null",
content: `
services:
test:
env_file:
- null`,
},
{
name: "anchors and aliases to nothing",
content: `
services:
test:
env_file: &anchor
test2:
env_file: *anchor`,
},
{
name: "anchor on the services object itself",
content: `
&anchor services:
test:
env_file: ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 14},
End: protocol.Position{Line: 3, Character: 20},
},
},
{
name: "anchor on the services object's value",
content: `
services: &anchor
test:
env_file: ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 14},
End: protocol.Position{Line: 3, Character: 20},
},
},
{
name: "anchor on the service object itself",
content: `
services:
&anchor test:
env_file: ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 14},
End: protocol.Position{Line: 3, Character: 20},
},
},
{
name: "anchor on the service object's value",
content: `
services:
test: &anchor
env_file: ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 14},
End: protocol.Position{Line: 3, Character: 20},
},
},
{
name: "anchor on the service object's value as JSON",
content: `
services:
test: &anchor { env_file: ./.env }`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 2, Character: 28},
End: protocol.Position{Line: 2, Character: 34},
},
},
{
name: "anchor on the env_file string attribute itself",
content: `
services:
test: &anchor
&anchor env_file: ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 22},
End: protocol.Position{Line: 3, Character: 28},
},
},
{
name: "anchor on the env_file string attribute's value",
content: `
services:
test: &anchor
env_file: &anchor ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 3, Character: 22},
End: protocol.Position{Line: 3, Character: 28},
},
},
{
name: "anchor on the env_file array attribute's value",
content: `
services:
test: &anchor
env_file: &anchor
- ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 4, Character: 8},
End: protocol.Position{Line: 4, Character: 14},
},
},
{
name: "anchor on the env_file array item's value",
content: `
services:
test: &anchor
env_file:
- &anchor ./.env`,
path: filepath.Join(testsFolder, "./.env"),
linkRange: protocol.Range{
Start: protocol.Position{Line: 4, Character: 16},
End: protocol.Position{Line: 4, Character: 22},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mgr := document.NewDocumentManager()
doc := document.NewComposeDocument(mgr, uri.URI(composeStringURI), 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(filepath.FromSlash(tc.path)),
}
require.Equal(t, []protocol.DocumentLink{link}, links)
}
})
}
}

func TestDocumentLink_ServiceExtendsFileLinks(t *testing.T) {
testsFolder := filepath.Join(os.TempDir(), t.Name())
composeStringURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(testsFolder, "compose.yaml")), "/"))
Expand Down
Loading