From a4dca571cec9e5734e9b28f3b90fbb3359aebeb6 Mon Sep 17 00:00:00 2001 From: Remy Suen Date: Fri, 22 Aug 2025 07:14:29 -0400 Subject: [PATCH] Support searching for service references in volumes_from Signed-off-by: Remy Suen --- CHANGELOG.md | 12 + internal/compose/documentHighlight.go | 19 +- internal/compose/documentHighlight_test.go | 316 +++++++++++++++++++++ 3 files changed, 346 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e861d14..2cfff2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to the Docker Language Server will be documented in this fil ## [Unreleased] +### Added + +- Compose + - textDocument/definition + - support jumping to service references in the `volumes_from` attribute of a service object ([#452](https://github.com/docker/docker-language-server/issues/452)) + - textDocument/documentHighlight + - support highlighting service references in the `volumes_from` attribute of a service object ([#452](https://github.com/docker/docker-language-server/issues/452)) + - textDocument/prepareRename + - support preparing renames for services in the `volumes_from` attribute of a service object ([#452](https://github.com/docker/docker-language-server/issues/452)) + - textDocument/rename + - support renaming service referencesin the `volumes_from` attribute of a service object ([#452](https://github.com/docker/docker-language-server/issues/452)) + ### Fixed - Compose diff --git a/internal/compose/documentHighlight.go b/internal/compose/documentHighlight.go index 1939757..21275d4 100644 --- a/internal/compose/documentHighlight.go +++ b/internal/compose/documentHighlight.go @@ -45,7 +45,8 @@ func extendedServiceReferences(servicesNode *ast.MappingNode) []*token.Token { for _, serviceNode := range servicesNode.Values { if serviceAttributes, ok := resolveAnchor(serviceNode.Value).(*ast.MappingNode); ok { for _, attributeNode := range serviceAttributes.Values { - if resolveAnchor(attributeNode.Key).GetToken().Value == "extends" { + switch resolveAnchor(attributeNode.Key).GetToken().Value { + case "extends": attributeNodeValue := resolveAnchor(attributeNode.Value) if extendedValue, ok := attributeNodeValue.(*ast.StringNode); ok { tokens = append(tokens, extendedValue.GetToken()) @@ -66,6 +67,22 @@ func extendedServiceReferences(servicesNode *ast.MappingNode) []*token.Token { } } } + case "volumes_from": + if sequenceNode, ok := resolveAnchor(attributeNode.Value).(*ast.SequenceNode); ok { + for _, volume := range sequenceNode.Values { + volumeToken := resolveAnchor(volume).GetToken() + split := strings.Split(volumeToken.Value, ":") + if len(split) == 1 { + tokens = append(tokens, volumeToken) + } else if len(split[0]) > 0 && split[0] != "container" { + tokens = append(tokens, &token.Token{ + Type: volumeToken.Type, + Value: split[0], + Position: volumeToken.Position, + }) + } + } + } } } } diff --git a/internal/compose/documentHighlight_test.go b/internal/compose/documentHighlight_test.go index a332b12..e058d15 100644 --- a/internal/compose/documentHighlight_test.go +++ b/internal/compose/documentHighlight_test.go @@ -1803,6 +1803,322 @@ services: End: protocol.Position{Line: 6, Character: 8}, }, }, + { + name: "undefined read highlight on a volumes_from array item", + content: ` +services: + test: + volumes_from: + - server`, + line: 4, + character: 10, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 14, protocol.DocumentHighlightKindRead), + }, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + u: { + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + { + name: "undefined read highlight on a volumes_from array item (volumes_from attribute key is anchored)", + content: ` +services: + test: + &anchor volumes_from: + - server`, + line: 4, + character: 10, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 14, protocol.DocumentHighlightKindRead), + }, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + u: { + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + { + name: "undefined read highlight on a volumes_from array item (volumes_from attribute value is anchored)", + content: ` +services: + test: + volumes_from: &anchor + - server`, + line: 4, + character: 10, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 14, protocol.DocumentHighlightKindRead), + }, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + u: { + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + { + name: "undefined read highlight on a volumes_from array item (array item is anchored)", + content: ` +services: + test: + volumes_from: + - &anchor server`, + line: 4, + character: 18, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 16, 4, 22, protocol.DocumentHighlightKindRead), + }, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + u: { + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 16}, + End: protocol.Position{Line: 4, Character: 22}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 16}, + End: protocol.Position{Line: 4, Character: 22}, + }, + }, + { + name: "undefined read highlight on a volumes_from array item with a :ro suffix", + content: ` +services: + test: + volumes_from: + - server:ro`, + line: 4, + character: 10, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 14, protocol.DocumentHighlightKindRead), + }, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + u: { + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + { + name: "read highlight on a volumes_from array item pointing to a service", + content: ` +services: + test: + volumes_from: + - server + server:`, + line: 4, + character: 10, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 14, protocol.DocumentHighlightKindRead), + documentHighlight(5, 2, 5, 8, protocol.DocumentHighlightKindWrite), + }, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + u: { + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + { + name: "write highlight on a volumes_from array item pointing to a service", + content: ` +services: + test: + volumes_from: + - server + server:`, + line: 5, + character: 5, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 14, protocol.DocumentHighlightKindRead), + documentHighlight(5, 2, 5, 8, protocol.DocumentHighlightKindWrite), + }, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + u: { + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 14}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 8}, + }, + }, + { + name: "undefined read highlight on a volumes_from with no service just :ro", + content: ` +services: + test: + volumes_from: + - :ro`, + line: 4, + character: 8, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: nil, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return nil + }, + prepareRename: nil, + }, + { + name: "undefined read highlight on a volumes_from array item pointing to a container", + content: ` +services: + test: + volumes_from: + - container:ro`, + line: 4, + character: 10, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: nil, + renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { + return nil + }, + prepareRename: nil, + }, { name: "invalid services value", content: `