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 @@ -7,6 +7,8 @@ All notable changes to the Docker Language Server will be documented in this fil
### Fixed

- Compose
- textDocument/completion
- fix build stage lookups for files in a folder under the `\\wsl$` host ([#382](https://github.com/docker/docker-language-server/issues/382))
- 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))
Expand Down
2 changes: 1 addition & 1 deletion internal/bake/hcl/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, manager
continue
}

_, nodes := document.OpenDockerfile(ctx, manager, dockerfilePath)
_, nodes := document.OpenDockerfile(ctx, manager, "", dockerfilePath)
if nodes != nil {
if attribute, ok := attributes["target"]; ok && isInsideRange(attribute.Expr.Range(), params.Position) {
if _, ok := attributes["dockerfile-inline"]; ok {
Expand Down
4 changes: 2 additions & 2 deletions internal/bake/hcl/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
value, _ := literalValueExpr.Value(&hcl.EvalContext{})
target := value.AsString()

bytes, nodes := document.OpenDockerfile(ctx, manager, dockerfilePath)
bytes, nodes := document.OpenDockerfile(ctx, manager, "", dockerfilePath)
lines := strings.Split(string(bytes), "\n")
for _, child := range nodes {
if strings.EqualFold(child.Value, "FROM") {
Expand Down Expand Up @@ -188,7 +188,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
end--
}
arg := string(doc.Input()[start:end])
bytes, nodes := document.OpenDockerfile(ctx, manager, dockerfilePath)
bytes, nodes := document.OpenDockerfile(ctx, manager, "", dockerfilePath)
lines := strings.Split(string(bytes), "\n")
for _, child := range nodes {
if strings.EqualFold(child.Value, "ARG") {
Expand Down
6 changes: 3 additions & 3 deletions internal/bake/hcl/diagnosticsCollector.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func (c *BakeHCLDiagnosticsCollector) CollectDiagnostics(source, workspaceFolder
}
nodes, ok := dockerfileContent[dockerfile]
if !ok {
_, nodes = document.OpenDockerfile(context.Background(), c.docs, dockerfilePath)
_, nodes = document.OpenDockerfile(context.Background(), c.docs, "", dockerfilePath)
dockerfileContent[block.Labels[0]] = nodes
}
diagnostic := c.checkTargetTarget(nodes, expr, literalValueExpr, source)
Expand All @@ -255,7 +255,7 @@ func (c *BakeHCLDiagnosticsCollector) CollectDiagnostics(source, workspaceFolder
}
nodes, ok := dockerfileContent[dockerfile]
if !ok {
_, nodes = document.OpenDockerfile(context.Background(), c.docs, dockerfile)
_, nodes = document.OpenDockerfile(context.Background(), c.docs, "", dockerfile)
dockerfileContent[dockerfile] = nodes
}
c.collectARGs(nodes, args)
Expand All @@ -265,7 +265,7 @@ func (c *BakeHCLDiagnosticsCollector) CollectDiagnostics(source, workspaceFolder

nodes, ok := dockerfileContent[dockerfilePath]
if !ok {
_, nodes = document.OpenDockerfile(context.Background(), c.docs, dockerfilePath)
_, nodes = document.OpenDockerfile(context.Background(), c.docs, "", dockerfilePath)
dockerfileContent[dockerfilePath] = nodes
}
argsDiagnostics := c.checkTargetArgs(nodes, input, expr, source, args)
Expand Down
6 changes: 3 additions & 3 deletions internal/bake/hcl/documentLink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func TestDocumentLink_WSL(t *testing.T) {
name: "Dockerfile",
content: "target \"api\" {\n dockerfile = \"Dockerfile\"\n}",
target: "file://wsl%24/docker-desktop/tmp/Dockerfile",
tooltip: "\\\\wsl%24\\docker-desktop\\tmp\\Dockerfile",
tooltip: "\\\\wsl$\\docker-desktop\\tmp\\Dockerfile",
linkRange: protocol.Range{
Start: protocol.Position{Line: 1, Character: 16},
End: protocol.Position{Line: 1, Character: 26},
Expand All @@ -123,7 +123,7 @@ func TestDocumentLink_WSL(t *testing.T) {
name: "./Dockerfile",
content: "target \"api\" {\n dockerfile = \"./Dockerfile\"\n}",
target: "file://wsl%24/docker-desktop/tmp/Dockerfile",
tooltip: "\\\\wsl%24\\docker-desktop\\tmp\\Dockerfile",
tooltip: "\\\\wsl$\\docker-desktop\\tmp\\Dockerfile",
linkRange: protocol.Range{
Start: protocol.Position{Line: 1, Character: 16},
End: protocol.Position{Line: 1, Character: 28},
Expand All @@ -133,7 +133,7 @@ func TestDocumentLink_WSL(t *testing.T) {
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",
tooltip: "\\\\wsl$\\docker-desktop\\other\\Dockerfile",
linkRange: protocol.Range{
Start: protocol.Position{Line: 1, Character: 16},
End: protocol.Position{Line: 1, Character: 35},
Expand Down
2 changes: 1 addition & 1 deletion internal/bake/hcl/inlayHint.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func InlayHint(docs *document.Manager, doc document.BakeHCLDocument, rng protoco
if expr, ok := attribute.Expr.(*hclsyntax.ObjectConsExpr); ok && len(expr.Items) > 0 {
dockerfilePath, err := doc.DockerfileForTarget(block)
if dockerfilePath != "" && err == nil {
_, nodes := document.OpenDockerfile(context.Background(), docs, dockerfilePath)
_, nodes := document.OpenDockerfile(context.Background(), docs, "", dockerfilePath)
args := map[string]string{}
for _, child := range nodes {
if strings.EqualFold(child.Value, "ARG") {
Expand Down
2 changes: 1 addition & 1 deletion internal/bake/hcl/inlineCompletion.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func InlineCompletion(ctx context.Context, params *protocol.InlineCompletionPara
argNames := []string{}
args := map[string]string{}
targets := []string{}
_, nodes := document.OpenDockerfile(ctx, manager, dockerfilePath)
_, nodes := document.OpenDockerfile(ctx, manager, "", dockerfilePath)
before := true
for _, child := range nodes {
if strings.EqualFold(child.Value, "ARG") && before {
Expand Down
27 changes: 14 additions & 13 deletions internal/compose/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ func extendingCurrentFile(documentPath document.DocumentPath, extendsNode *ast.M
if extends, ok := extendsNode.Value.(*ast.MappingNode); ok {
for _, extendsAttribute := range extends.Values {
if extendsAttribute.Key.GetToken().Value == "file" {
path := filepath.Join(documentPath.Folder, extendsAttribute.Value.GetToken().Value)
if !samePath(filepath.Join(documentPath.Folder, documentPath.FileName), path) {
_, path := types.Concatenate(documentPath.Folder, extendsAttribute.Value.GetToken().Value, documentPath.WSLDollarSignHost)
_, originalPath := types.Concatenate(documentPath.Folder, documentPath.FileName, documentPath.WSLDollarSignHost)
if !samePath(originalPath, path) {
return false
}
break
Expand All @@ -53,8 +54,8 @@ var buildTargetModifier = textEditModifier{
},
modify: func(file *ast.File, manager *document.Manager, documentPath document.DocumentPath, edit protocol.TextEdit, attributeName, spacing string, path []*ast.MappingValueNode) protocol.TextEdit {
if _, ok := path[2].Value.(*ast.NullNode); ok {
dockerfilePath := filepath.Join(documentPath.Folder, "Dockerfile")
stages := findBuildStages(manager, dockerfilePath, "")
dockerfileURI, dockerfilePath := types.Concatenate(documentPath.Folder, "Dockerfile", documentPath.WSLDollarSignHost)
stages := findBuildStages(manager, dockerfileURI, dockerfilePath, "")
if len(stages) > 0 {
edit.NewText = fmt.Sprintf("%v%v", edit.NewText, createChoiceSnippetText(stages))
return edit
Expand All @@ -70,8 +71,8 @@ var buildTargetModifier = textEditModifier{
}
}

dockerfilePath := filepath.Join(documentPath.Folder, dockerfileAttributePath)
stages := findBuildStages(manager, dockerfilePath, "")
dockerfileURI, dockerfilePath := types.Concatenate(documentPath.Folder, dockerfileAttributePath, documentPath.WSLDollarSignHost)
stages := findBuildStages(manager, dockerfileURI, dockerfilePath, "")
if len(stages) > 0 {
edit.NewText = fmt.Sprintf("%v%v", edit.NewText, createChoiceSnippetText(stages))
return edit
Expand Down Expand Up @@ -401,8 +402,8 @@ func findDependencies(file *ast.File, dependencyType string) []string {
return services
}

func findBuildStages(manager *document.Manager, dockerfilePath, prefix string) []completionItemText {
_, nodes := document.OpenDockerfile(context.Background(), manager, dockerfilePath)
func findBuildStages(manager *document.Manager, dockerfileURI, dockerfilePath, prefix string) []completionItemText {
_, nodes := document.OpenDockerfile(context.Background(), manager, dockerfileURI, dockerfilePath)
items := []completionItemText{}
for _, child := range nodes {
if strings.EqualFold(child.Value, "FROM") {
Expand Down Expand Up @@ -434,16 +435,16 @@ func buildTargetCompletionItems(params *protocol.CompletionParams, manager *docu
}
}

dockerfilePath := filepath.Join(documentPath.Folder, dockerfileAttributePath)
dockerfileURI, dockerfilePath := types.Concatenate(documentPath.Folder, dockerfileAttributePath, documentPath.WSLDollarSignHost)
if _, ok := path[3].Value.(*ast.NullNode); ok {
return createBuildStageItems(params, manager, dockerfilePath, "", prefixLength), true
return createBuildStageItems(params, manager, dockerfileURI, dockerfilePath, "", prefixLength), true
} else if prefix, ok := path[3].Value.(*ast.StringNode); ok {
if int(params.Position.Line) == path[3].Value.GetToken().Position.Line-1 {
offset := int(params.Position.Character) - path[3].Value.GetToken().Position.Column + 1
// offset can be greater than the length if there's just empty whitespace after the string value,
// must be non-negative, if negative it suggests the cursor is in the whitespace before the attribute's value
if offset >= 0 && offset <= len(prefix.Value) {
return createBuildStageItems(params, manager, dockerfilePath, prefix.Value[0:offset], prefixLength), true
return createBuildStageItems(params, manager, dockerfileURI, dockerfilePath, prefix.Value[0:offset], prefixLength), true
}
}
}
Expand All @@ -452,9 +453,9 @@ func buildTargetCompletionItems(params *protocol.CompletionParams, manager *docu
return nil, false
}

func createBuildStageItems(params *protocol.CompletionParams, manager *document.Manager, dockerfilePath, prefix string, prefixLength protocol.UInteger) []protocol.CompletionItem {
func createBuildStageItems(params *protocol.CompletionParams, manager *document.Manager, dockerfileURI, dockerfilePath, prefix string, prefixLength protocol.UInteger) []protocol.CompletionItem {
items := []protocol.CompletionItem{}
for _, itemText := range findBuildStages(manager, dockerfilePath, prefix) {
for _, itemText := range findBuildStages(manager, dockerfileURI, dockerfilePath, prefix) {
items = append(items, protocol.CompletionItem{
Label: itemText.label,
Documentation: itemText.documentation,
Expand Down
122 changes: 122 additions & 0 deletions internal/compose/completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4397,6 +4397,128 @@ services:
}
}

func TestCompletion_BuildStageLookups_WSL(t *testing.T) {
testCases := []struct {
name string
dockerfileContent string
content string
line uint32
character uint32
list func() *protocol.CompletionList
}{
{
name: "target attribute is null",
dockerfileContent: "FROM scratch AS base",
content: `
services:
postgres:
build:
target: `,
line: 4,
character: 14,
list: func() *protocol.CompletionList {
return &protocol.CompletionList{
Items: []protocol.CompletionItem{
{
Label: "base",
Documentation: "scratch",
TextEdit: textEdit("base", 4, 14, 0),
},
},
}
},
},
{
name: "target attribute has a valid prefix",
dockerfileContent: "FROM scratch AS base",
content: `
services:
postgres:
build:
target: b`,
line: 4,
character: 15,
list: func() *protocol.CompletionList {
return &protocol.CompletionList{
Items: []protocol.CompletionItem{
{
Label: "base",
Documentation: "scratch",
TextEdit: textEdit("base", 4, 15, 1),
},
},
}
},
},
{
name: "build completion items include autofilled stages when build is empty",
dockerfileContent: "FROM busybox as bstage\nFROM alpine as astage",
content: `
services:
postgres:
build:
`,
line: 4,
character: 6,
list: func() *protocol.CompletionList {
items := serviceBuildProperties(4, 6, 0)
for i := range items {
if items[i].Label == "target" {
items[i].TextEdit = textEdit("target: ${1|bstage,astage|}", 4, 6, 0)
break
}
}
return &protocol.CompletionList{Items: items}
},
},
{
name: "build completion items include autofilled stages when build is empty when build object has other attributes",
dockerfileContent: "FROM busybox as bstage\nFROM alpine as astage",
content: `
services:
postgres:
build:
dockerfile: Dockerfile
`,
line: 5,
character: 6,
list: func() *protocol.CompletionList {
items := serviceBuildProperties(5, 6, 0)
for i := range items {
if items[i].Label == "target" {
items[i].TextEdit = textEdit("target: ${1|bstage,astage|}", 5, 6, 0)
break
}
}
return &protocol.CompletionList{
Items: items,
}
},
},
}

dockerfileURI := "file://wsl%24/docker-desktop/tmp/Dockerfile"
composeFileURI := "file://wsl%24/docker-desktop/tmp/compose.yaml"

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
manager := document.NewDocumentManager()
changed, err := manager.Write(context.Background(), uri.URI(dockerfileURI), protocol.DockerfileLanguage, 1, []byte(tc.dockerfileContent))
require.NoError(t, err)
require.True(t, changed)
doc := document.NewComposeDocument(manager, uri.URI(composeFileURI), 1, []byte(tc.content))
list, err := Completion(context.Background(), &protocol.CompletionParams{
TextDocumentPositionParams: protocol.TextDocumentPositionParams{
TextDocument: protocol.TextDocumentIdentifier{URI: composeFileURI},
Position: protocol.Position{Line: tc.line, Character: tc.character},
},
}, manager, doc)
require.NoError(t, err)
require.Equal(t, tc.list(), list)
})
}
}

func TestCompletion_CustomServiceProvider(t *testing.T) {
testCases := []struct {
name string
Expand Down
6 changes: 3 additions & 3 deletions internal/compose/documentLink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,23 @@ func TestDocumentLink_WSL(t *testing.T) {
End: protocol.Position{Line: 1, Character: 13},
},
Target: types.CreateStringPointer("file://wsl%24/docker-desktop/tmp/file.yaml"),
Tooltip: types.CreateStringPointer("\\\\wsl%24\\docker-desktop\\tmp\\file.yaml"),
Tooltip: types.CreateStringPointer("\\\\wsl$\\docker-desktop\\tmp\\file.yaml"),
},
{
Range: protocol.Range{
Start: protocol.Position{Line: 2, Character: 4},
End: protocol.Position{Line: 2, Character: 16},
},
Target: types.CreateStringPointer("file://wsl%24/docker-desktop/tmp/file2.yaml"),
Tooltip: types.CreateStringPointer("\\\\wsl%24\\docker-desktop\\tmp\\file2.yaml"),
Tooltip: types.CreateStringPointer("\\\\wsl$\\docker-desktop\\tmp\\file2.yaml"),
},
{
Range: protocol.Range{
Start: protocol.Position{Line: 3, Character: 4},
End: protocol.Position{Line: 3, Character: 23},
},
Target: types.CreateStringPointer("file://wsl%24/docker-desktop/other/file3.yaml"),
Tooltip: types.CreateStringPointer("\\\\wsl%24\\docker-desktop\\other\\file3.yaml"),
Tooltip: types.CreateStringPointer("\\\\wsl$\\docker-desktop\\other\\file3.yaml"),
},
},
},
Expand Down
7 changes: 5 additions & 2 deletions internal/pkg/document/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ func parseDockerfile(dockerfilePath string) ([]byte, *parser.Result, error) {
return dockerfileBytes, result, err
}

func OpenDockerfile(ctx context.Context, manager *Manager, path string) ([]byte, []*parser.Node) {
doc := manager.Get(ctx, uri.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(path), "/"))))
func OpenDockerfile(ctx context.Context, manager *Manager, documentURI, path string) ([]byte, []*parser.Node) {
if documentURI == "" {
documentURI = fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(path), "/"))
}
doc := manager.Get(ctx, uri.URI(documentURI))
if doc != nil {
if dockerfile, ok := doc.(DockerfileDocument); ok {
return dockerfile.Input(), dockerfile.Nodes()
Expand Down
2 changes: 1 addition & 1 deletion internal/types/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func AbsoluteFolder(documentURL *url.URL) (string, error) {

func Concatenate(folder, file string, wslDollarSign bool) (uri string, absoluteFilePath string) {
if wslDollarSign {
return "file://wsl%24" + path.Join(strings.ReplaceAll(folder, "\\", "/"), file), "\\\\wsl%24" + strings.ReplaceAll(path.Join(folder, file), "/", "\\")
return "file://wsl%24" + path.Join(strings.ReplaceAll(folder, "\\", "/"), file), "\\\\wsl$" + strings.ReplaceAll(path.Join(folder, file), "/", "\\")
}
abs := filepath.ToSlash(filepath.Join(folder, file))
return fmt.Sprintf("file:///%v", strings.TrimPrefix(abs, "/")), filepath.FromSlash(abs)
Expand Down
Loading