diff --git a/CHANGELOG.md b/CHANGELOG.md index 751fd16..ec1b3f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,18 @@ All notable changes to the Docker Language Server will be documented in this fil ### Added - Compose + - textDocument/definition + - recurse into anchors when evaluating the cursor's position ([#305](https://github.com/docker/docker-language-server/issues/305)) + - textDocument/documentHighlight + - recurse into anchors when evaluating the cursor's position ([#305](https://github.com/docker/docker-language-server/issues/305)) - textDocument/documentLink - ensure the image attribute is valid before trying to process it for document links ([#306](https://github.com/docker/docker-language-server/issues/306)) - textDocument/hover - resolve anchors when constructing the path of the hovered item ([#303](https://github.com/docker/docker-language-server/issues/303)) + - textDocument/prepareRename + - recurse into anchors when evaluating the cursor's position ([#305](https://github.com/docker/docker-language-server/issues/305)) + - textDocument/rename + - recurse into anchors when evaluating the cursor's position ([#305](https://github.com/docker/docker-language-server/issues/305)) ## [0.10.2] - 2025-06-06 diff --git a/internal/compose/documentHighlight.go b/internal/compose/documentHighlight.go index 0594f7e..1b2f15f 100644 --- a/internal/compose/documentHighlight.go +++ b/internal/compose/documentHighlight.go @@ -14,64 +14,52 @@ type dependencyReference struct { documentHighlights []protocol.DocumentHighlight } -func serviceDependencyReferences(node *ast.MappingValueNode, dependencyAttributeName string, arrayOnly bool) []*token.Token { - if servicesNode, ok := node.Value.(*ast.MappingNode); ok { - tokens := []*token.Token{} - for _, serviceNode := range servicesNode.Values { - serviceNodeValue := serviceNode.Value - if anchor, ok := serviceNode.Value.(*ast.AnchorNode); ok { - serviceNodeValue = anchor.Value - } - if serviceAttributes, ok := serviceNodeValue.(*ast.MappingNode); ok { - for _, attributeNode := range serviceAttributes.Values { - if attributeNode.Key.GetToken().Value == dependencyAttributeName { - if sequenceNode, ok := attributeNode.Value.(*ast.SequenceNode); ok { - for _, service := range sequenceNode.Values { - tokens = append(tokens, service.GetToken()) - } - } else if !arrayOnly { - if mappingNode, ok := attributeNode.Value.(*ast.MappingNode); ok { - for _, dependentService := range mappingNode.Values { - tokens = append(tokens, dependentService.Key.GetToken()) - } +func serviceDependencyReferences(servicesNode *ast.MappingNode, dependencyAttributeName string, arrayOnly bool) []*token.Token { + tokens := []*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 == dependencyAttributeName { + if sequenceNode, ok := resolveAnchor(attributeNode.Value).(*ast.SequenceNode); ok { + for _, service := range sequenceNode.Values { + tokens = append(tokens, resolveAnchor(service).GetToken()) + } + } else if !arrayOnly { + if mappingNode, ok := resolveAnchor(attributeNode.Value).(*ast.MappingNode); ok { + for _, dependentService := range mappingNode.Values { + tokens = append(tokens, resolveAnchor(dependentService.Key).GetToken()) } } } } } } - return tokens } - return nil + return tokens } -func extendedServiceReferences(node *ast.MappingValueNode) []*token.Token { - if servicesNode, ok := node.Value.(*ast.MappingNode); ok { - tokens := []*token.Token{} - for _, serviceNode := range servicesNode.Values { - serviceNodeValue := serviceNode.Value - if anchor, ok := serviceNode.Value.(*ast.AnchorNode); ok { - serviceNodeValue = anchor.Value - } - if serviceAttributes, ok := serviceNodeValue.(*ast.MappingNode); ok { - for _, attributeNode := range serviceAttributes.Values { - if attributeNode.Key.GetToken().Value == "extends" { - if extendedValue, ok := attributeNode.Value.(*ast.StringNode); ok { - tokens = append(tokens, extendedValue.GetToken()) - } else if mappingNode, ok := attributeNode.Value.(*ast.MappingNode); ok { - localService := true - for _, extendsObjectAttribute := range mappingNode.Values { - if extendsObjectAttribute.Key.GetToken().Value == "file" { - localService = false - break - } +func extendedServiceReferences(servicesNode *ast.MappingNode) []*token.Token { + tokens := []*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" { + attributeNodeValue := resolveAnchor(attributeNode.Value) + if extendedValue, ok := attributeNodeValue.(*ast.StringNode); ok { + tokens = append(tokens, extendedValue.GetToken()) + } else if mappingNode, ok := resolveAnchor(attributeNodeValue).(*ast.MappingNode); ok { + localService := true + for _, extendsObjectAttribute := range mappingNode.Values { + if resolveAnchor(extendsObjectAttribute.Key).GetToken().Value == "file" { + localService = false + break } + } - if localService { - for _, extendsObjectAttribute := range mappingNode.Values { - if extendsObjectAttribute.Key.GetToken().Value == "service" { - tokens = append(tokens, extendsObjectAttribute.Value.GetToken()) - } + if localService { + for _, extendsObjectAttribute := range mappingNode.Values { + if resolveAnchor(extendsObjectAttribute.Key).GetToken().Value == "service" { + tokens = append(tokens, resolveAnchor(extendsObjectAttribute.Value).GetToken()) } } } @@ -79,9 +67,8 @@ func extendedServiceReferences(node *ast.MappingValueNode) []*token.Token { } } } - return tokens } - return nil + return tokens } func volumeToken(t *token.Token) *token.Token { @@ -96,78 +83,65 @@ func volumeToken(t *token.Token) *token.Token { return t } -func volumeReferences(node *ast.MappingValueNode) []*token.Token { - if servicesNode, ok := node.Value.(*ast.MappingNode); ok { - tokens := []*token.Token{} - for _, serviceNode := range servicesNode.Values { - serviceNodeValue := serviceNode.Value - if anchor, ok := serviceNode.Value.(*ast.AnchorNode); ok { - serviceNodeValue = anchor.Value - } - if serviceAttributes, ok := serviceNodeValue.(*ast.MappingNode); ok { - for _, attributeNode := range serviceAttributes.Values { - if attributeNode.Key.GetToken().Value == "volumes" { - if sequenceNode, ok := attributeNode.Value.(*ast.SequenceNode); ok { - for _, service := range sequenceNode.Values { - if volumeObjectNode, ok := service.(*ast.MappingNode); ok { - for _, volumeAttribute := range volumeObjectNode.Values { - if volumeAttribute.Key.GetToken().Value == "source" { - tokens = append(tokens, volumeAttribute.Value.GetToken()) - } +func volumeReferences(servicesNode *ast.MappingNode) []*token.Token { + tokens := []*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 == "volumes" { + volumesValue := resolveAnchor(attributeNode.Value) + if sequenceNode, ok := volumesValue.(*ast.SequenceNode); ok { + for _, volume := range sequenceNode.Values { + volumeNode := resolveAnchor(volume) + if volumeObjectNode, ok := volumeNode.(*ast.MappingNode); ok { + for _, volumeAttribute := range volumeObjectNode.Values { + if resolveAnchor(volumeAttribute.Key).GetToken().Value == "source" { + tokens = append(tokens, resolveAnchor(volumeAttribute.Value).GetToken()) } - } else { - tokens = append(tokens, volumeToken(service.GetToken())) } + } else { + tokens = append(tokens, volumeToken(volumeNode.GetToken())) } - } else if mappingNode, ok := attributeNode.Value.(*ast.MappingNode); ok { - for _, dependentService := range mappingNode.Values { - tokens = append(tokens, dependentService.Key.GetToken()) - } + } + } else if mappingNode, ok := volumesValue.(*ast.MappingNode); ok { + for _, dependentService := range mappingNode.Values { + tokens = append(tokens, resolveAnchor(dependentService.Key).GetToken()) } } } } } - return tokens } - return nil + return tokens } -func declarations(node *ast.MappingValueNode, dependencyType string) []*token.Token { - if s, ok := node.Key.(*ast.StringNode); ok && s.Value == dependencyType { - if servicesNode, ok := node.Value.(*ast.MappingNode); ok { - tokens := []*token.Token{} - for _, serviceNode := range servicesNode.Values { - tokens = append(tokens, serviceNode.Key.GetToken()) - } - return tokens - } +func declarations(node *ast.MappingNode) []*token.Token { + tokens := []*token.Token{} + for _, serviceNode := range node.Values { + tokens = append(tokens, resolveAnchor(serviceNode.Key).GetToken()) } - return nil + return tokens } -func findFragments(mappingNode *ast.MappingNode, anchors []*ast.AnchorNode, aliases []*ast.AliasNode) ([]*ast.AnchorNode, []*ast.AliasNode) { - for _, node := range mappingNode.Values { - if anchor, ok := node.Value.(*ast.AnchorNode); ok { - anchors = append(anchors, anchor) - } else if alias, ok := node.Value.(*ast.AliasNode); ok { - aliases = append(aliases, alias) - } else if m, ok := node.Value.(*ast.MappingNode); ok { - otherAnchors, otherAliases := findFragments(m, []*ast.AnchorNode{}, []*ast.AliasNode{}) +func findFragments(node ast.Node, anchors []*ast.AnchorNode, aliases []*ast.AliasNode) ([]*ast.AnchorNode, []*ast.AliasNode) { + if anchor, ok := node.(*ast.AnchorNode); ok { + anchors = append(anchors, anchor) + otherAnchors, otherAliases := findFragments(resolveAnchor(anchor), []*ast.AnchorNode{}, []*ast.AliasNode{}) + anchors = append(anchors, otherAnchors...) + aliases = append(aliases, otherAliases...) + } else if alias, ok := node.(*ast.AliasNode); ok { + aliases = append(aliases, alias) + } else if m, ok := node.(*ast.MappingNode); ok { + for _, v := range m.Values { + otherAnchors, otherAliases := findFragments(v.Value, []*ast.AnchorNode{}, []*ast.AliasNode{}) + anchors = append(anchors, otherAnchors...) + aliases = append(aliases, otherAliases...) + } + } else if s, ok := node.(*ast.SequenceNode); ok { + for _, item := range s.Values { + otherAnchors, otherAliases := findFragments(item, []*ast.AnchorNode{}, []*ast.AliasNode{}) anchors = append(anchors, otherAnchors...) aliases = append(aliases, otherAliases...) - } else if s, ok := node.Value.(*ast.SequenceNode); ok { - for _, item := range s.Values { - if anchor, ok := item.(*ast.AnchorNode); ok { - anchors = append(anchors, anchor) - } else if alias, ok := item.(*ast.AliasNode); ok { - aliases = append(aliases, alias) - } else if m, ok := item.(*ast.MappingNode); ok { - otherAnchors, otherAliases := findFragments(m, []*ast.AnchorNode{}, []*ast.AliasNode{}) - anchors = append(anchors, otherAnchors...) - aliases = append(aliases, otherAliases...) - } - } } } return anchors, aliases @@ -281,6 +255,15 @@ func DocumentHighlight(doc document.ComposeDocument, position protocol.Position) return references.documentHighlights, nil } +func convertTopLevelNode(node *ast.MappingValueNode) (*ast.StringNode, *ast.MappingNode) { + if s, ok := resolveAnchor(node.Key).(*ast.StringNode); ok { + if m, ok := resolveAnchor(node.Value).(*ast.MappingNode); ok { + return s, m + } + } + return nil, nil +} + func DocumentHighlights(doc document.ComposeDocument, position protocol.Position) (string, dependencyReference) { file := doc.File() if file == nil || len(file.Docs) == 0 { @@ -299,29 +282,32 @@ func DocumentHighlights(doc document.ComposeDocument, position protocol.Position var configDeclarations []*token.Token var secretDeclarations []*token.Token for _, node := range mappingNode.Values { - if s, ok := node.Key.(*ast.StringNode); ok { - switch s.Value { - case "services": - refs := serviceDependencyReferences(node, "depends_on", false) - refs = append(refs, extendedServiceReferences(node)...) - decls := declarations(node, "services") - name, highlights := highlightReferences("services", refs, decls, line, character) - if len(highlights.documentHighlights) > 0 { - return name, highlights - } - networkRefs = serviceDependencyReferences(node, "networks", false) - configRefs = serviceDependencyReferences(node, "configs", true) - secretRefs = serviceDependencyReferences(node, "secrets", true) - volumeRefs = volumeReferences(node) - case "networks": - networkDeclarations = declarations(node, "networks") - case "volumes": - volumeDeclarations = declarations(node, "volumes") - case "configs": - configDeclarations = declarations(node, "configs") - case "secrets": - secretDeclarations = declarations(node, "secrets") + name, value := convertTopLevelNode(node) + if name == nil || value == nil { + continue + } + + switch name.Value { + case "services": + refs := serviceDependencyReferences(value, "depends_on", false) + refs = append(refs, extendedServiceReferences(value)...) + decls := declarations(value) + name, highlights := highlightReferences("services", refs, decls, line, character) + if len(highlights.documentHighlights) > 0 { + return name, highlights } + networkRefs = serviceDependencyReferences(value, "networks", false) + configRefs = serviceDependencyReferences(value, "configs", true) + secretRefs = serviceDependencyReferences(value, "secrets", true) + volumeRefs = volumeReferences(value) + case "networks": + networkDeclarations = declarations(value) + case "volumes": + volumeDeclarations = declarations(value) + case "configs": + configDeclarations = declarations(value) + case "secrets": + secretDeclarations = declarations(value) } } name, highlights := highlightReferences("networks", networkRefs, networkDeclarations, line, character) diff --git a/internal/compose/documentHighlight_test.go b/internal/compose/documentHighlight_test.go index b122998..453c337 100644 --- a/internal/compose/documentHighlight_test.go +++ b/internal/compose/documentHighlight_test.go @@ -46,6 +46,96 @@ var serviceReferenceTestCases = []struct { name: "write highlight on a service", content: ` services: + test:`, + line: 2, + character: 4, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + name: "write highlight on a services node itself anchored", + content: ` +&anchor services: + test:`, + line: 2, + character: 4, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + name: "write highlight on a services node's value anchored", + content: ` +services: &anchor test:`, line: 2, character: 4, @@ -121,6 +211,142 @@ services: End: protocol.Position{Line: 4, Character: 13}, }, }, + { + name: "read highlight on an undefined service's depends_on array string with depends_on itself anchored", + content: ` +services: + test: + &anchor depends_on: + - test2`, + 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, 13, 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: 13}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, + }, + { + name: "read highlight on an undefined service's depends_on array string with depends_on's value anchored", + content: ` +services: + test: + depends_on: &anchor + - test2`, + 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, 13, 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: 13}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, + }, + { + name: "read highlight on an undefined service's depends_on array string with depends_on itself anchored", + content: ` +services: + test: + &anchor depends_on: + - test2`, + 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, 13, 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: 13}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, + }, + { + name: "read highlight on an undefined service's depends_on array string with an anchored value", + content: ` +services: + test: + depends_on: + - &anchor test2`, + 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, 21, 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: 21}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 16}, + End: protocol.Position{Line: 4, Character: 21}, + }, + }, { name: "read highlight on an undefined quoted service's depends_on array string", content: ` @@ -213,18 +439,34 @@ services: }, }, { - name: "read highlight on an undefined service object with no properties", + name: "read highlight on a depends_on string service with the declaration anchored", content: ` services: test: depends_on: - test2:`, + - test2 + &anchor test2: + image: redis`, line: 4, - character: 9, - locations: func(u protocol.DocumentUri) any { return nil }, - links: func(u protocol.DocumentUri) any { return nil }, + character: 12, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 10}, + End: protocol.Position{Line: 5, Character: 15}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 10}, + End: protocol.Position{Line: 5, Character: 15}, + }, &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, u) + }, ranges: []protocol.DocumentHighlight{ - documentHighlight(4, 6, 4, 11, protocol.DocumentHighlightKindRead), + documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead), + documentHighlight(5, 10, 5, 15, protocol.DocumentHighlightKindWrite), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -233,8 +475,15 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 4, Character: 6}, - End: protocol.Position{Line: 4, Character: 11}, + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 5, Character: 10}, + End: protocol.Position{Line: 5, Character: 15}, }, }, }, @@ -242,18 +491,17 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 4, Character: 6}, - End: protocol.Position{Line: 4, Character: 11}, + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, }, }, { - name: "read highlight on an undefined service object with properties", + name: "read highlight on an undefined service object with no properties", content: ` services: test: depends_on: - test2: - condition: service_started`, + test2:`, line: 4, character: 9, locations: func(u protocol.DocumentUri) any { return nil }, @@ -282,12 +530,114 @@ services: }, }, { - name: "cursor not on anything meaningful", + name: "read highlight on an undefined service object with no properties with the object value anchored", content: ` services: test: - depends_on: - - test2 + depends_on: &anchor + test2:`, + line: 4, + character: 9, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 6, 4, 11, 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: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, + }, + { + name: "read highlight on an undefined service object with no properties with the dependency's name anchored", + content: ` +services: + test: + depends_on: { &anchor test2: null }`, + line: 3, + character: 28, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(3, 26, 3, 31, 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: 3, Character: 26}, + End: protocol.Position{Line: 3, Character: 31}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 3, Character: 26}, + End: protocol.Position{Line: 3, Character: 31}, + }, + }, + { + name: "read highlight on an undefined service object with properties", + content: ` +services: + test: + depends_on: + test2: + condition: service_started`, + line: 4, + character: 9, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 6, 4, 11, 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: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, + }, + { + name: "cursor not on anything meaningful", + content: ` +services: + test: + depends_on: + - test2 - test2`, line: 3, character: 9, @@ -360,16 +710,309 @@ services: }, 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: 7}, + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 7}, + }, &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead), + documentHighlight(5, 2, 5, 7, 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: 13}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 7}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, + }, + { + name: "read/write highlight on a service's depends_on array string (cursor on write)", + content: ` +services: + test: + depends_on: + - test2 + test2:`, + 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: 7}, + }, 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: 7}, + }, &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 7}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead), + documentHighlight(5, 2, 5, 7, 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: 13}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 7}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 2}, + End: protocol.Position{Line: 5, Character: 7}, + }, + }, + { + name: "short syntax form of depends_on in services finding the right match", + content: ` +services: + web: + build: . + depends_on: + - postgres + - redis + postgres: + image: postgres + redis: + image: redis`, + line: 6, + character: 11, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 9, Character: 2}, + End: protocol.Position{Line: 9, Character: 7}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 9, Character: 2}, + End: protocol.Position{Line: 9, Character: 7}, + }, &protocol.Range{ + Start: protocol.Position{Line: 6, Character: 8}, + End: protocol.Position{Line: 6, Character: 13}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(6, 8, 6, 13, protocol.DocumentHighlightKindRead), + documentHighlight(9, 2, 9, 7, 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: 6, Character: 8}, + End: protocol.Position{Line: 6, Character: 13}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 9, Character: 2}, + End: protocol.Position{Line: 9, Character: 7}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 6, Character: 8}, + End: protocol.Position{Line: 6, Character: 13}, + }, + }, + { + name: "long syntax form of depends_on in services", + content: ` +services: + web: + build: . + depends_on: + db: + condition: service_healthy + restart: true + redis: + condition: service_started + db: + image: postgres + redis: + image: redis`, + line: 8, + character: 9, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 12, Character: 2}, + End: protocol.Position{Line: 12, Character: 7}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 12, Character: 2}, + End: protocol.Position{Line: 12, Character: 7}, + }, &protocol.Range{ + Start: protocol.Position{Line: 8, Character: 6}, + End: protocol.Position{Line: 8, Character: 11}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(8, 6, 8, 11, protocol.DocumentHighlightKindRead), + documentHighlight(12, 2, 12, 7, 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: 8, Character: 6}, + End: protocol.Position{Line: 8, Character: 11}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 12, Character: 2}, + End: protocol.Position{Line: 12, Character: 7}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 8, Character: 6}, + End: protocol.Position{Line: 8, Character: 11}, + }, + }, + { + name: "extends as a string attribute", + content: ` +services: + test: + image: alpine + test2: + extends: test`, + line: 5, + character: 15, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 13}, + End: protocol.Position{Line: 5, Character: 17}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), + documentHighlight(5, 13, 5, 17, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 5, Character: 13}, + End: protocol.Position{Line: 5, Character: 17}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 13}, + End: protocol.Position{Line: 5, Character: 17}, + }, + }, + { + name: "extends (with an anchor) as a string attribute value", + content: ` +services: + test: + image: alpine + test2: + &anchor extends: test`, + line: 5, + character: 23, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, &protocol.Range{ - Start: protocol.Position{Line: 4, Character: 8}, - End: protocol.Position{Line: 4, Character: 13}, + Start: protocol.Position{Line: 5, Character: 21}, + End: protocol.Position{Line: 5, Character: 25}, }, u) }, ranges: []protocol.DocumentHighlight{ - documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead), - documentHighlight(5, 2, 5, 7, protocol.DocumentHighlightKindWrite), + documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), + documentHighlight(5, 21, 5, 25, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -378,15 +1021,15 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 4, Character: 8}, - End: protocol.Position{Line: 4, Character: 13}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, }, { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 5, Character: 2}, - End: protocol.Position{Line: 5, Character: 7}, + Start: protocol.Position{Line: 5, Character: 21}, + End: protocol.Position{Line: 5, Character: 25}, }, }, }, @@ -394,38 +1037,38 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 4, Character: 8}, - End: protocol.Position{Line: 4, Character: 13}, + Start: protocol.Position{Line: 5, Character: 21}, + End: protocol.Position{Line: 5, Character: 25}, }, }, { - name: "read/write highlight on a service's depends_on array string (cursor on write)", + name: "extends as a string attribute value with an anchor", content: ` services: test: - depends_on: - - test2 - test2:`, + image: alpine + test2: + extends: &anchor test`, line: 5, - character: 5, + character: 23, 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: 7}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, 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: 7}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, &protocol.Range{ - Start: protocol.Position{Line: 5, Character: 2}, - End: protocol.Position{Line: 5, Character: 7}, + Start: protocol.Position{Line: 5, Character: 21}, + End: protocol.Position{Line: 5, Character: 25}, }, u) }, ranges: []protocol.DocumentHighlight{ - documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead), - documentHighlight(5, 2, 5, 7, protocol.DocumentHighlightKindWrite), + documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), + documentHighlight(5, 21, 5, 25, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -434,15 +1077,15 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 4, Character: 8}, - End: protocol.Position{Line: 4, Character: 13}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, }, { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 5, Character: 2}, - End: protocol.Position{Line: 5, Character: 7}, + Start: protocol.Position{Line: 5, Character: 21}, + End: protocol.Position{Line: 5, Character: 25}, }, }, }, @@ -450,43 +1093,38 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 5, Character: 2}, - End: protocol.Position{Line: 5, Character: 7}, + Start: protocol.Position{Line: 5, Character: 21}, + End: protocol.Position{Line: 5, Character: 25}, }, }, { - name: "short syntax form of depends_on in services finding the right match", + name: "extends as a quoted string attribute", content: ` services: - web: - build: . - depends_on: - - postgres - - redis - postgres: - image: postgres - redis: - image: redis`, - line: 6, - character: 11, + test: + image: alpine + test2: + extends: "test"`, + line: 5, + character: 15, locations: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(false, protocol.Range{ - Start: protocol.Position{Line: 9, Character: 2}, - End: protocol.Position{Line: 9, Character: 7}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, nil, u) }, links: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(true, protocol.Range{ - Start: protocol.Position{Line: 9, Character: 2}, - End: protocol.Position{Line: 9, Character: 7}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, &protocol.Range{ - Start: protocol.Position{Line: 6, Character: 8}, - End: protocol.Position{Line: 6, Character: 13}, + Start: protocol.Position{Line: 5, Character: 14}, + End: protocol.Position{Line: 5, Character: 18}, }, u) }, ranges: []protocol.DocumentHighlight{ - documentHighlight(6, 8, 6, 13, protocol.DocumentHighlightKindRead), - documentHighlight(9, 2, 9, 7, protocol.DocumentHighlightKindWrite), + documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), + documentHighlight(5, 14, 5, 18, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -495,15 +1133,15 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 6, Character: 8}, - End: protocol.Position{Line: 6, Character: 13}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, }, { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 9, Character: 2}, - End: protocol.Position{Line: 9, Character: 7}, + Start: protocol.Position{Line: 5, Character: 14}, + End: protocol.Position{Line: 5, Character: 18}, }, }, }, @@ -511,46 +1149,39 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 6, Character: 8}, - End: protocol.Position{Line: 6, Character: 13}, + Start: protocol.Position{Line: 5, Character: 14}, + End: protocol.Position{Line: 5, Character: 18}, }, }, { - name: "long syntax form of depends_on in services", + name: "extends as an object without a file attribute", content: ` services: - web: - build: . - depends_on: - db: - condition: service_healthy - restart: true - redis: - condition: service_started - db: - image: postgres - redis: - image: redis`, - line: 8, - character: 9, + test: + image: alpine + test2: + extends: + service: test`, + line: 6, + character: 17, locations: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(false, protocol.Range{ - Start: protocol.Position{Line: 12, Character: 2}, - End: protocol.Position{Line: 12, Character: 7}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, nil, u) }, links: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(true, protocol.Range{ - Start: protocol.Position{Line: 12, Character: 2}, - End: protocol.Position{Line: 12, Character: 7}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, &protocol.Range{ - Start: protocol.Position{Line: 8, Character: 6}, - End: protocol.Position{Line: 8, Character: 11}, + Start: protocol.Position{Line: 6, Character: 15}, + End: protocol.Position{Line: 6, Character: 19}, }, u) }, ranges: []protocol.DocumentHighlight{ - documentHighlight(8, 6, 8, 11, protocol.DocumentHighlightKindRead), - documentHighlight(12, 2, 12, 7, protocol.DocumentHighlightKindWrite), + documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), + documentHighlight(6, 15, 6, 19, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -559,15 +1190,15 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 8, Character: 6}, - End: protocol.Position{Line: 8, Character: 11}, + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, }, }, { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 12, Character: 2}, - End: protocol.Position{Line: 12, Character: 7}, + Start: protocol.Position{Line: 6, Character: 15}, + End: protocol.Position{Line: 6, Character: 19}, }, }, }, @@ -575,20 +1206,21 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 8, Character: 6}, - End: protocol.Position{Line: 8, Character: 11}, + Start: protocol.Position{Line: 6, Character: 15}, + End: protocol.Position{Line: 6, Character: 19}, }, }, { - name: "extends as a string attribute", + name: "extends as an object with its value as an anchor", content: ` services: test: image: alpine test2: - extends: test`, - line: 5, - character: 15, + extends: &anchor + service: test`, + line: 6, + character: 17, locations: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(false, protocol.Range{ Start: protocol.Position{Line: 2, Character: 2}, @@ -600,13 +1232,13 @@ services: Start: protocol.Position{Line: 2, Character: 2}, End: protocol.Position{Line: 2, Character: 6}, }, &protocol.Range{ - Start: protocol.Position{Line: 5, Character: 13}, - End: protocol.Position{Line: 5, Character: 17}, + Start: protocol.Position{Line: 6, Character: 15}, + End: protocol.Position{Line: 6, Character: 19}, }, u) }, ranges: []protocol.DocumentHighlight{ documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), - documentHighlight(5, 13, 5, 17, protocol.DocumentHighlightKindRead), + documentHighlight(6, 15, 6, 19, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -622,8 +1254,8 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 5, Character: 13}, - End: protocol.Position{Line: 5, Character: 17}, + Start: protocol.Position{Line: 6, Character: 15}, + End: protocol.Position{Line: 6, Character: 19}, }, }, }, @@ -631,20 +1263,21 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 5, Character: 13}, - End: protocol.Position{Line: 5, Character: 17}, + Start: protocol.Position{Line: 6, Character: 15}, + End: protocol.Position{Line: 6, Character: 19}, }, }, { - name: "extends as a quoted string attribute", + name: "extends as an object without a file attribute with an anchor on the service attribute's name", content: ` services: test: image: alpine test2: - extends: "test"`, - line: 5, - character: 15, + extends: + &anchor service: test`, + line: 6, + character: 25, locations: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(false, protocol.Range{ Start: protocol.Position{Line: 2, Character: 2}, @@ -656,13 +1289,13 @@ services: Start: protocol.Position{Line: 2, Character: 2}, End: protocol.Position{Line: 2, Character: 6}, }, &protocol.Range{ - Start: protocol.Position{Line: 5, Character: 14}, - End: protocol.Position{Line: 5, Character: 18}, + Start: protocol.Position{Line: 6, Character: 23}, + End: protocol.Position{Line: 6, Character: 27}, }, u) }, ranges: []protocol.DocumentHighlight{ documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), - documentHighlight(5, 14, 5, 18, protocol.DocumentHighlightKindRead), + documentHighlight(6, 23, 6, 27, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -678,8 +1311,8 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 5, Character: 14}, - End: protocol.Position{Line: 5, Character: 18}, + Start: protocol.Position{Line: 6, Character: 23}, + End: protocol.Position{Line: 6, Character: 27}, }, }, }, @@ -687,21 +1320,21 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 5, Character: 14}, - End: protocol.Position{Line: 5, Character: 18}, + Start: protocol.Position{Line: 6, Character: 23}, + End: protocol.Position{Line: 6, Character: 27}, }, }, { - name: "extends as an object without a file attribute", + name: "extends as an object without a file attribute with an anchor on the service attribute's value", content: ` services: test: image: alpine test2: extends: - service: test`, + service: &anchor test`, line: 6, - character: 17, + character: 25, locations: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(false, protocol.Range{ Start: protocol.Position{Line: 2, Character: 2}, @@ -713,13 +1346,13 @@ services: Start: protocol.Position{Line: 2, Character: 2}, End: protocol.Position{Line: 2, Character: 6}, }, &protocol.Range{ - Start: protocol.Position{Line: 6, Character: 15}, - End: protocol.Position{Line: 6, Character: 19}, + Start: protocol.Position{Line: 6, Character: 23}, + End: protocol.Position{Line: 6, Character: 27}, }, u) }, ranges: []protocol.DocumentHighlight{ documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), - documentHighlight(6, 15, 6, 19, protocol.DocumentHighlightKindRead), + documentHighlight(6, 23, 6, 27, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -735,8 +1368,8 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 6, Character: 15}, - End: protocol.Position{Line: 6, Character: 19}, + Start: protocol.Position{Line: 6, Character: 23}, + End: protocol.Position{Line: 6, Character: 27}, }, }, }, @@ -744,8 +1377,8 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 6, Character: 15}, - End: protocol.Position{Line: 6, Character: 19}, + Start: protocol.Position{Line: 6, Character: 23}, + End: protocol.Position{Line: 6, Character: 27}, }, }, { @@ -766,37 +1399,107 @@ services: renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return nil }, - prepareRename: nil, + prepareRename: nil, + }, + { + name: "extends as an object with a file attribute with an anchor that points to a non-existent file", + content: ` +services: + test: + image: alpine + test2: + extends: + service: test + &anchor file: non-existent.yaml`, + line: 6, + character: 17, + 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: "anchor name conflicts with a depends_on service (cursor on anchor)", + content: ` +services: + first: &second + image: scratch + depends_on: + - second + second: + image: scratch`, + line: 2, + character: 13, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 10}, + End: protocol.Position{Line: 2, Character: 16}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 10}, + End: protocol.Position{Line: 2, Character: 16}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 10}, + End: protocol.Position{Line: 2, Character: 16}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 10, 2, 16, 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: 2, Character: 10}, + End: protocol.Position{Line: 2, Character: 16}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 10}, + End: protocol.Position{Line: 2, Character: 16}, + }, }, { - name: "anchor name conflicts with a depends_on service (cursor on anchor)", + name: "nested anchor within an anchored MappingNode", content: ` services: first: &second - image: scratch + image: &another scratch depends_on: - second second: image: scratch`, - line: 2, - character: 13, + line: 3, + character: 15, locations: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(false, protocol.Range{ - Start: protocol.Position{Line: 2, Character: 10}, - End: protocol.Position{Line: 2, Character: 16}, + Start: protocol.Position{Line: 3, Character: 12}, + End: protocol.Position{Line: 3, Character: 19}, }, nil, u) }, links: func(u protocol.DocumentUri) any { return types.CreateDefinitionResult(true, protocol.Range{ - Start: protocol.Position{Line: 2, Character: 10}, - End: protocol.Position{Line: 2, Character: 16}, + Start: protocol.Position{Line: 3, Character: 12}, + End: protocol.Position{Line: 3, Character: 19}, }, &protocol.Range{ - Start: protocol.Position{Line: 2, Character: 10}, - End: protocol.Position{Line: 2, Character: 16}, + Start: protocol.Position{Line: 3, Character: 12}, + End: protocol.Position{Line: 3, Character: 19}, }, u) }, ranges: []protocol.DocumentHighlight{ - documentHighlight(2, 10, 2, 16, protocol.DocumentHighlightKindWrite), + documentHighlight(3, 12, 3, 19, protocol.DocumentHighlightKindWrite), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -805,8 +1508,8 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 2, Character: 10}, - End: protocol.Position{Line: 2, Character: 16}, + Start: protocol.Position{Line: 3, Character: 12}, + End: protocol.Position{Line: 3, Character: 19}, }, }, }, @@ -814,8 +1517,8 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 2, Character: 10}, - End: protocol.Position{Line: 2, Character: 16}, + Start: protocol.Position{Line: 3, Character: 12}, + End: protocol.Position{Line: 3, Character: 19}, }, }, { @@ -1100,6 +1803,18 @@ services: End: protocol.Position{Line: 6, Character: 8}, }, }, + { + name: "invalid services value", + content: ` +services: true`, + line: 1, + character: 12, + 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, + }, } func TestDocumentHighlight_Services(t *testing.T) { @@ -1133,6 +1848,51 @@ var networkReferenceTestCases = []struct { name: "write highlight on a network", content: ` networks: + test:`, + line: 2, + character: 4, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + name: "write highlight on a network with the networks object anchored", + content: ` +networks: &anchor test:`, line: 2, character: 4, @@ -1696,15 +2456,199 @@ networks: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 5, Character: 8}, - End: protocol.Position{Line: 5, Character: 14}, - }, - }, - { - NewText: "newName", - Range: protocol.Range{ - Start: protocol.Position{Line: 7, Character: 2}, - End: protocol.Position{Line: 7, Character: 8}, + Start: protocol.Position{Line: 5, Character: 8}, + End: protocol.Position{Line: 5, Character: 14}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 7, Character: 2}, + End: protocol.Position{Line: 7, Character: 8}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 7, Character: 2}, + End: protocol.Position{Line: 7, Character: 8}, + }, + }, +} + +func TestDocumentHighlight_Networks(t *testing.T) { + composeFileURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(os.TempDir(), "compose.yaml")), "/")) + u := uri.URI(composeFileURI) + for _, tc := range networkReferenceTestCases { + t.Run(tc.name, func(t *testing.T) { + doc := document.NewComposeDocument(document.NewDocumentManager(), u, 1, []byte(tc.content)) + ranges, err := DocumentHighlight(doc, protocol.Position{Line: tc.line, Character: tc.character}) + require.NoError(t, err) + require.Equal(t, tc.ranges, ranges) + }) + } +} + +var volumeReferenceTestCases = []struct { + name string + content string + line protocol.UInteger + character protocol.UInteger + locations func(protocol.DocumentUri) any + links func(protocol.DocumentUri) any + ranges []protocol.DocumentHighlight + renameEdits func(protocol.DocumentUri) *protocol.WorkspaceEdit + prepareRename *protocol.Range +}{ + { + name: "write highlight on a volumes", + content: ` +volumes: + test:`, + line: 2, + character: 4, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + name: "write highlight on a volumes with the volumes object value anchored", + content: ` +volumes: &anchor + test:`, + line: 2, + character: 4, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + name: "read highlight on an undefined volume array item", + content: ` +services: + test: + volumes: + - test2`, + 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, 13, 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: 13}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, + }, + }, + { + name: "read highlight on an undefined volume array item with the volumes array anchored", + content: ` +services: + test: + &anchor volumes: + - test2`, + 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, 13, 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: 13}, }, }, }, @@ -1712,60 +2656,23 @@ networks: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 7, Character: 2}, - End: protocol.Position{Line: 7, Character: 8}, + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, }, }, -} - -func TestDocumentHighlight_Networks(t *testing.T) { - composeFileURI := fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(filepath.Join(os.TempDir(), "compose.yaml")), "/")) - u := uri.URI(composeFileURI) - for _, tc := range networkReferenceTestCases { - t.Run(tc.name, func(t *testing.T) { - doc := document.NewComposeDocument(document.NewDocumentManager(), u, 1, []byte(tc.content)) - ranges, err := DocumentHighlight(doc, protocol.Position{Line: tc.line, Character: tc.character}) - require.NoError(t, err) - require.Equal(t, tc.ranges, ranges) - }) - } -} - -var volumeReferenceTestCases = []struct { - name string - content string - line protocol.UInteger - character protocol.UInteger - locations func(protocol.DocumentUri) any - links func(protocol.DocumentUri) any - ranges []protocol.DocumentHighlight - renameEdits func(protocol.DocumentUri) *protocol.WorkspaceEdit - prepareRename *protocol.Range -}{ { - name: "write highlight on a volumes", + name: "read highlight on an undefined volume array item with an array value anchor", content: ` -volumes: - test:`, - line: 2, - character: 4, - locations: func(u protocol.DocumentUri) any { - return types.CreateDefinitionResult(false, protocol.Range{ - Start: protocol.Position{Line: 2, Character: 2}, - End: protocol.Position{Line: 2, Character: 6}, - }, nil, u) - }, - links: func(u protocol.DocumentUri) any { - return types.CreateDefinitionResult(true, protocol.Range{ - Start: protocol.Position{Line: 2, Character: 2}, - End: protocol.Position{Line: 2, Character: 6}, - }, &protocol.Range{ - Start: protocol.Position{Line: 2, Character: 2}, - End: protocol.Position{Line: 2, Character: 6}, - }, u) - }, +services: + test: + volumes: &anchor + - test2`, + line: 4, + character: 10, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, ranges: []protocol.DocumentHighlight{ - documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite), + documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -1774,8 +2681,8 @@ volumes: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 2, Character: 2}, - End: protocol.Position{Line: 2, Character: 6}, + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, }, }, }, @@ -1783,23 +2690,23 @@ volumes: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 2, Character: 2}, - End: protocol.Position{Line: 2, Character: 6}, + Start: protocol.Position{Line: 4, Character: 8}, + End: protocol.Position{Line: 4, Character: 13}, }, }, { - name: "read highlight on an undefined volume array item", + name: "read highlight on an undefined volume array item with a string anchor", content: ` services: test: volumes: - - test2`, + - &anchor test2`, line: 4, - character: 10, + character: 18, locations: func(u protocol.DocumentUri) any { return nil }, links: func(u protocol.DocumentUri) any { return nil }, ranges: []protocol.DocumentHighlight{ - documentHighlight(4, 8, 4, 13, protocol.DocumentHighlightKindRead), + documentHighlight(4, 16, 4, 21, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -1808,8 +2715,8 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 4, Character: 8}, - End: protocol.Position{Line: 4, Character: 13}, + Start: protocol.Position{Line: 4, Character: 16}, + End: protocol.Position{Line: 4, Character: 21}, }, }, }, @@ -1817,8 +2724,8 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 4, Character: 8}, - End: protocol.Position{Line: 4, Character: 13}, + Start: protocol.Position{Line: 4, Character: 16}, + End: protocol.Position{Line: 4, Character: 21}, }, }, { @@ -1957,6 +2864,131 @@ services: End: protocol.Position{Line: 4, Character: 21}, }, }, + { + name: "read highlight on an undefined volume array item object's source with an anchor attribute name", + content: ` +services: + test: + volumes: + - &anchor source: test2`, + line: 4, + character: 26, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 24, 4, 29, 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: 24}, + End: protocol.Position{Line: 4, Character: 29}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 24}, + End: protocol.Position{Line: 4, Character: 29}, + }, + }, + { + name: "read highlight on an undefined volume array item object's source with an anchor attribute value", + content: ` +services: + test: + volumes: + - source: &anchor test2`, + line: 4, + character: 26, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 24, 4, 29, 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: 24}, + End: protocol.Position{Line: 4, Character: 29}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 24}, + End: protocol.Position{Line: 4, Character: 29}, + }, + }, + { + name: "read highlight on an undefined volume array item object's source with an anchor on the object itself", + content: ` +services: + test: + volumes: + - &anchor { source: vol, target: /mount, type: volume } +volumes: + vol:`, + line: 4, + character: 28, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 6, Character: 2}, + End: protocol.Position{Line: 6, Character: 5}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 6, Character: 2}, + End: protocol.Position{Line: 6, Character: 5}, + }, &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 26}, + End: protocol.Position{Line: 4, Character: 29}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 26, 4, 29, protocol.DocumentHighlightKindRead), + documentHighlight(6, 2, 6, 5, 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: 26}, + End: protocol.Position{Line: 4, Character: 29}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 6, Character: 2}, + End: protocol.Position{Line: 6, Character: 5}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 26}, + End: protocol.Position{Line: 4, Character: 29}, + }, + }, { name: "read/write highlight on an volume array item object's source (cursor on read)", content: ` @@ -2029,21 +3061,88 @@ services: renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return nil }, - prepareRename: nil, + prepareRename: nil, + }, + { + name: "read highlight on an invalid volume object", + content: ` +services: + test: + volumes: + test2:`, + line: 4, + character: 9, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 6, 4, 11, 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: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, + }, + { + name: "read highlight on an invalid volume object with the object itself anchored", + content: ` +services: + test: + volumes: &anchor + test2:`, + line: 4, + character: 9, + locations: func(u protocol.DocumentUri) any { return nil }, + links: func(u protocol.DocumentUri) any { return nil }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 6, 4, 11, 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: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 4, Character: 6}, + End: protocol.Position{Line: 4, Character: 11}, + }, }, { - name: "read highlight on an invalid volume object", + name: "read highlight on an invalid volume object with the volume object itself anchored", content: ` services: test: - volumes: - test2:`, - line: 4, - character: 9, + volumes: { &anchor test2: null }`, + line: 3, + character: 25, locations: func(u protocol.DocumentUri) any { return nil }, links: func(u protocol.DocumentUri) any { return nil }, ranges: []protocol.DocumentHighlight{ - documentHighlight(4, 6, 4, 11, protocol.DocumentHighlightKindRead), + documentHighlight(3, 23, 3, 28, protocol.DocumentHighlightKindRead), }, renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit { return &protocol.WorkspaceEdit{ @@ -2052,8 +3151,8 @@ services: { NewText: "newName", Range: protocol.Range{ - Start: protocol.Position{Line: 4, Character: 6}, - End: protocol.Position{Line: 4, Character: 11}, + Start: protocol.Position{Line: 3, Character: 23}, + End: protocol.Position{Line: 3, Character: 28}, }, }, }, @@ -2061,8 +3160,8 @@ services: } }, prepareRename: &protocol.Range{ - Start: protocol.Position{Line: 4, Character: 6}, - End: protocol.Position{Line: 4, Character: 11}, + Start: protocol.Position{Line: 3, Character: 23}, + End: protocol.Position{Line: 3, Character: 28}, }, }, { @@ -2475,6 +3574,51 @@ var configReferenceTestCases = []struct { name: "write highlight on a configs", content: ` configs: + test:`, + line: 2, + character: 4, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + name: "write highlight on a configs with the configs object value anchored", + content: ` +configs: &anchor test:`, line: 2, character: 4, @@ -2920,6 +4064,51 @@ var secretReferenceTestCases = []struct { name: "write highlight on a secrets", content: ` secrets: + test:`, + line: 2, + character: 4, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(2, 2, 2, 6, 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: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 2, Character: 2}, + End: protocol.Position{Line: 2, Character: 6}, + }, + }, + { + name: "write highlight on a secrets with the secrets object value anchored", + content: ` +secrets: &anchor test:`, line: 2, character: 4, @@ -4653,6 +5842,219 @@ services: End: protocol.Position{Line: 4, Character: 18}, }, }, + { + name: "anchored sequence with an alias anchor in the sequence", + content: ` +services: + test: + networks: + - &anchor test + test2: + networks: &anchor2 + - *anchor +networks: + test: + test2:`, + line: 7, + character: 12, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 4, Character: 9}, + End: protocol.Position{Line: 4, Character: 15}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 4, Character: 9}, + End: protocol.Position{Line: 4, Character: 15}, + }, &protocol.Range{ + Start: protocol.Position{Line: 7, Character: 9}, + End: protocol.Position{Line: 7, Character: 15}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(4, 9, 4, 15, protocol.DocumentHighlightKindWrite), + documentHighlight(7, 9, 7, 15, 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: 9}, + End: protocol.Position{Line: 4, Character: 15}, + }, + }, + { + NewText: "newName", + Range: protocol.Range{ + Start: protocol.Position{Line: 7, Character: 9}, + End: protocol.Position{Line: 7, Character: 15}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 7, Character: 9}, + End: protocol.Position{Line: 7, Character: 15}, + }, + }, + { + name: "write reference on a nested anchor with main anchor on a sequence", + content: ` +services: + test: + image: alpine:3.21 + volumes: &anchor + - source: &anchor2 vol + target: /target + type: volume +volumes: + vol:`, + line: 5, + character: 20, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 17}, + End: protocol.Position{Line: 5, Character: 24}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 17}, + End: protocol.Position{Line: 5, Character: 24}, + }, &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 17}, + End: protocol.Position{Line: 5, Character: 24}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(5, 17, 5, 24, 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: 5, Character: 17}, + End: protocol.Position{Line: 5, Character: 24}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 17}, + End: protocol.Position{Line: 5, Character: 24}, + }, + }, + { + name: "write reference on a nested anchor in a sequence with main anchor", + content: ` +services: + test: + image: alpine:3.21 + volumes: &anchor + - &anchor2 vol +volumes: + vol:`, + line: 5, + character: 14, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 9}, + End: protocol.Position{Line: 5, Character: 16}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 9}, + End: protocol.Position{Line: 5, Character: 16}, + }, &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 9}, + End: protocol.Position{Line: 5, Character: 16}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(5, 9, 5, 16, 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: 5, Character: 9}, + End: protocol.Position{Line: 5, Character: 16}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 9}, + End: protocol.Position{Line: 5, Character: 16}, + }, + }, + { + name: "write reference on a nested anchor with main anchor on an object", + content: ` +services: + test: + image: alpine:3.21 + volumes: + - &anchor { source: &anchor2 vol, target: /target, type: volume } +volumes: + vol:`, + line: 5, + character: 30, + locations: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(false, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 27}, + End: protocol.Position{Line: 5, Character: 34}, + }, nil, u) + }, + links: func(u protocol.DocumentUri) any { + return types.CreateDefinitionResult(true, protocol.Range{ + Start: protocol.Position{Line: 5, Character: 27}, + End: protocol.Position{Line: 5, Character: 34}, + }, &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 27}, + End: protocol.Position{Line: 5, Character: 34}, + }, u) + }, + ranges: []protocol.DocumentHighlight{ + documentHighlight(5, 27, 5, 34, 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: 5, Character: 27}, + End: protocol.Position{Line: 5, Character: 34}, + }, + }, + }, + }, + } + }, + prepareRename: &protocol.Range{ + Start: protocol.Position{Line: 5, Character: 27}, + End: protocol.Position{Line: 5, Character: 34}, + }, + }, } func TestDocumentHighlight_Fragments(t *testing.T) { diff --git a/internal/compose/hover.go b/internal/compose/hover.go index 41f0b0e..3c6996a 100644 --- a/internal/compose/hover.go +++ b/internal/compose/hover.go @@ -379,16 +379,10 @@ func hover(schema *jsonschema.Schema, nodes []ast.Node, line, column, lineLength } func constructNodePath(matches []ast.Node, node ast.Node, line, col int) []ast.Node { - if anchor, ok := node.(*ast.AnchorNode); ok { - node = anchor.Value - } + node = resolveAnchor(node) switch n := node.(type) { case *ast.MappingValueNode: - var nodeKey ast.Node - nodeKey = n.Key - if anchor, ok := nodeKey.(*ast.AnchorNode); ok { - nodeKey = anchor.Value - } + nodeKey := resolveAnchor(n.Key) if m := constructNodePath(matches, nodeKey, line, col); m != nil { matches = append(matches, m...) return matches @@ -420,3 +414,10 @@ func constructNodePath(matches []ast.Node, node ast.Node, line, col int) []ast.N } return nil } + +func resolveAnchor(node ast.Node) ast.Node { + if anchor, ok := node.(*ast.AnchorNode); ok { + return anchor.Value + } + return node +}