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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ All notable changes to the Docker Language Server will be documented in this fil
- textDocument/documentLink
- improve handling of malformed image attribute values with registry prefixes ([#369](https://github.com/docker/docker-language-server/issues/369))
- convert links properly if a WSL URI with a dollar sign is used ([#366](https://github.com/docker/docker-language-server/issues/366))
- Bake
- textDocument/documentLink
- convert links properly if a WSL URI with a dollar sign is used ([#378](https://github.com/docker/docker-language-server/issues/378))

## [0.14.0] - 2025-07-16

Expand Down
25 changes: 11 additions & 14 deletions internal/bake/hcl/documentLink.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"context"
"errors"
"fmt"
"net/url"
"path/filepath"
"strings"

"github.com/docker/docker-language-server/internal/pkg/document"
Expand All @@ -19,7 +17,8 @@ func DocumentLink(ctx context.Context, documentURI protocol.URI, document docume
if !ok {
return nil, errors.New("unrecognized body in HCL document")
}
url, err := url.Parse(string(documentURI))

d, err := document.DocumentPath()
if err != nil {
return nil, fmt.Errorf("LSP client sent invalid URI: %v", string(documentURI))
}
Expand All @@ -37,17 +36,15 @@ func DocumentLink(ctx context.Context, documentURI protocol.URI, document docume

dockerfilePath = strings.TrimPrefix(dockerfilePath, "\"")
dockerfilePath = strings.TrimSuffix(dockerfilePath, "\"")
dockerfilePath, err = types.AbsolutePath(url, dockerfilePath)
if err == nil {
links = append(links, protocol.DocumentLink{
Range: protocol.Range{
Start: protocol.Position{Line: uint32(v.SrcRange.Start.Line) - 1, Character: uint32(v.Expr.Range().Start.Column)},
End: protocol.Position{Line: uint32(v.SrcRange.Start.Line) - 1, Character: uint32(v.Expr.Range().End.Column - 2)},
},
Target: types.CreateStringPointer(protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/")))),
Tooltip: types.CreateStringPointer(dockerfilePath),
})
}
target, tooltip := types.Concatenate(d.Folder, dockerfilePath, d.WSLDollarSignHost)
links = append(links, protocol.DocumentLink{
Range: protocol.Range{
Start: protocol.Position{Line: uint32(v.SrcRange.Start.Line) - 1, Character: uint32(v.Expr.Range().Start.Column)},
End: protocol.Position{Line: uint32(v.SrcRange.Start.Line) - 1, Character: uint32(v.Expr.Range().End.Column - 2)},
},
Target: types.CreateStringPointer(target),
Tooltip: types.CreateStringPointer(tooltip),
})
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions internal/bake/hcl/documentLink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,61 @@ func TestDocumentLink(t *testing.T) {
})
}
}

func TestDocumentLink_WSL(t *testing.T) {
testCases := []struct {
name string
content string
target string
tooltip string
linkRange protocol.Range
links func(path string) []protocol.DocumentLink
}{
{
name: "Dockerfile",
content: "target \"api\" {\n dockerfile = \"Dockerfile\"\n}",
target: "file://wsl%24/docker-desktop/tmp/Dockerfile",
tooltip: "\\\\wsl%24\\docker-desktop\\tmp\\Dockerfile",
linkRange: protocol.Range{
Start: protocol.Position{Line: 1, Character: 16},
End: protocol.Position{Line: 1, Character: 26},
},
},
{
name: "./Dockerfile",
content: "target \"api\" {\n dockerfile = \"./Dockerfile\"\n}",
target: "file://wsl%24/docker-desktop/tmp/Dockerfile",
tooltip: "\\\\wsl%24\\docker-desktop\\tmp\\Dockerfile",
linkRange: protocol.Range{
Start: protocol.Position{Line: 1, Character: 16},
End: protocol.Position{Line: 1, Character: 28},
},
},
{
name: "../other/Dockerfile",
content: "target \"api\" {\n dockerfile = \"../other/Dockerfile\"\n}",
target: "file://wsl%24/docker-desktop/other/Dockerfile",
tooltip: "\\\\wsl%24\\docker-desktop\\other\\Dockerfile",
linkRange: protocol.Range{
Start: protocol.Position{Line: 1, Character: 16},
End: protocol.Position{Line: 1, Character: 35},
},
},
}

documentStringURI := "file://wsl%24/docker-desktop/tmp/docker-bake.hcl"
documentURI := uri.URI(documentStringURI)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
doc := document.NewBakeHCLDocument(documentURI, 1, []byte(tc.content))
links, err := DocumentLink(context.Background(), documentStringURI, doc)
require.NoError(t, err)
link := protocol.DocumentLink{
Range: tc.linkRange,
Target: types.CreateStringPointer(tc.target),
Tooltip: types.CreateStringPointer(tc.tooltip),
}
require.Equal(t, []protocol.DocumentLink{link}, links)
})
}
}
34 changes: 5 additions & 29 deletions internal/compose/documentLink.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package compose
import (
"context"
"fmt"
"net/url"
"path"
"path/filepath"
"strings"

"github.com/docker/docker-language-server/internal/pkg/document"
Expand Down Expand Up @@ -34,18 +31,11 @@ func createRange(t *token.Token, length int) protocol.Range {

func createLink(folderAbsolutePath string, wslDollarSign bool, node *token.Token) *protocol.DocumentLink {
file := node.Value
if wslDollarSign {
return &protocol.DocumentLink{
Range: createRange(node, len(file)),
Target: types.CreateStringPointer("file://wsl%24" + path.Join(strings.ReplaceAll(folderAbsolutePath, "\\", "/"), file)),
Tooltip: types.CreateStringPointer("\\\\wsl%24" + strings.ReplaceAll(path.Join(folderAbsolutePath, file), "/", "\\")),
}
}
abs := filepath.ToSlash(filepath.Join(folderAbsolutePath, file))
u, path := types.Concatenate(folderAbsolutePath, file, wslDollarSign)
return &protocol.DocumentLink{
Range: createRange(node, len(file)),
Target: types.CreateStringPointer(protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(abs, "/")))),
Tooltip: types.CreateStringPointer(filepath.FromSlash(abs)),
Target: types.CreateStringPointer(u),
Tooltip: types.CreateStringPointer(path),
}
}

Expand Down Expand Up @@ -264,22 +254,8 @@ func scanForLinks(folderAbsolutePath string, wslDollarSign bool, n *ast.MappingV
return nil
}

func documentFolder(documentURI protocol.URI) (string, bool, error) {
url, err := url.Parse(string(documentURI))
if err != nil {
if strings.HasPrefix(documentURI, "file://wsl%24/") {
path := documentURI[len("file://wsl%24"):]
idx := strings.LastIndex(path, "/")
return path[0 : idx+1], true, nil
}
return "", false, fmt.Errorf("LSP client sent invalid URI: %v", string(documentURI))
}
folder, err := types.AbsoluteFolder(url)
return folder, false, err
}

func DocumentLink(ctx context.Context, documentURI protocol.URI, doc document.ComposeDocument) ([]protocol.DocumentLink, error) {
abs, wslDollarSign, err := documentFolder(documentURI)
d, err := doc.DocumentPath()
if err != nil {
return nil, err
}
Expand All @@ -293,7 +269,7 @@ func DocumentLink(ctx context.Context, documentURI protocol.URI, doc document.Co
for _, documentNode := range file.Docs {
if mappingNode, ok := documentNode.Body.(*ast.MappingNode); ok {
for _, node := range mappingNode.Values {
links = append(links, scanForLinks(abs, wslDollarSign, node)...)
links = append(links, scanForLinks(d.Folder, d.WSLDollarSignHost, node)...)
}
}
}
Expand Down
17 changes: 9 additions & 8 deletions internal/compose/documentLink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
"github.com/docker/docker-language-server/internal/types"
"github.com/stretchr/testify/require"
"go.lsp.dev/uri"
)

func documentLinkTooltip(testsFolder, fileName string) *string {
Expand Down Expand Up @@ -69,7 +70,7 @@ func TestDocumentLink_WSL(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
doc := document.NewComposeDocument(document.NewDocumentManager(), "compose.yaml", 1, []byte(tc.content))
doc := document.NewComposeDocument(document.NewDocumentManager(), uri.URI(composeStringURI), 1, []byte(tc.content))
links, err := DocumentLink(context.Background(), composeStringURI, doc)
require.NoError(t, err)
require.Equal(t, tc.links, links)
Expand Down Expand Up @@ -385,7 +386,7 @@ include:

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
doc := document.NewComposeDocument(document.NewDocumentManager(), "docker-compose.yml", 1, []byte(tc.content))
doc := document.NewComposeDocument(document.NewDocumentManager(), uri.URI(composeStringURI), 1, []byte(tc.content))
links, err := DocumentLink(context.Background(), composeStringURI, doc)
require.NoError(t, err)
require.Equal(t, tc.links, links)
Expand Down Expand Up @@ -1158,7 +1159,7 @@ services:
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))
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 == "" {
Expand Down Expand Up @@ -1332,7 +1333,7 @@ services:
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))
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 == "" {
Expand Down Expand Up @@ -1494,7 +1495,7 @@ services:
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))
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 == "" {
Expand Down Expand Up @@ -1707,7 +1708,7 @@ services:
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))
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 == "" {
Expand Down Expand Up @@ -1834,7 +1835,7 @@ configs:
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))
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 == "" {
Expand Down Expand Up @@ -1961,7 +1962,7 @@ secrets:
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))
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 == "" {
Expand Down
7 changes: 6 additions & 1 deletion internal/pkg/document/bakeDocument.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,14 @@ func (d *bakeHCLDocument) extractBakeOutput() {
}
}

dd, err := d.DocumentPath()
if err != nil {
d.bakePrintOutput = nil
return
}
btargets, groups, err := bake.ReadTargets(
context.Background(),
[]bake.File{{Name: d.uri.Filename(), Data: d.Input()}},
[]bake.File{{Name: dd.FileName, Data: d.Input()}},
targets,
nil,
nil,
Expand Down
28 changes: 28 additions & 0 deletions internal/pkg/document/document.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package document

import (
"fmt"
"net/url"
"strings"

"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
"github.com/docker/docker-language-server/internal/types"
"go.lsp.dev/uri"
)

type DocumentPath struct {
Folder string
FileName string
WSLDollarSignHost bool
}

type Document interface {
URI() uri.URI
DocumentPath() (DocumentPath, error)
Copy() Document
Input() []byte
Version() int32
Expand Down Expand Up @@ -56,6 +68,22 @@ func (d *document) URI() uri.URI {
return d.uri
}

func (d *document) DocumentPath() (DocumentPath, error) {
uriString := string(d.uri)
url, err := url.Parse(uriString)
if err != nil {
if strings.HasPrefix(uriString, "file://wsl%24/") {
path := uriString[len("file://wsl%24"):]
idx := strings.LastIndex(path, "/")
return DocumentPath{Folder: path[0:idx], FileName: path[idx+1:], WSLDollarSignHost: true}, nil
}
return DocumentPath{}, fmt.Errorf("Invalid URI: %v", uriString)
}
folder, err := types.AbsoluteFolder(url)
idx := strings.LastIndex(uriString, "/")
return DocumentPath{Folder: folder, FileName: uriString[idx+1:]}, err
}

func (d *document) LanguageIdentifier() protocol.LanguageIdentifier {
return d.identifier
}
Expand Down
Loading
Loading