Skip to content

Commit 351618a

Browse files
authored
Merge pull request #171 from docker/support-extended-named-service-references
Consider extended services when looking up named service references
2 parents 2577707 + a7485ec commit 351618a

File tree

5 files changed

+194
-1
lines changed

5 files changed

+194
-1
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@ All notable changes to the Docker Language Server will be documented in this fil
2020
### Fixed
2121

2222
- Compose
23-
- suggest completion items for array items that use an object schema directly ([#161](https://github.com/docker/docker-language-server/issues/161))
23+
- textDocument/completion
24+
- suggest completion items for array items that use an object schema directly ([#161](https://github.com/docker/docker-language-server/issues/161))
25+
- textDocument/definition
26+
- consider `extends` when looking up a service reference ([#170](https://github.com/docker/docker-language-server/issues/170))
27+
- textDocument/documentHighlight
28+
- consider `extends` when looking up a service reference ([#170](https://github.com/docker/docker-language-server/issues/170))
29+
- textDocument/prepareRename
30+
- consider `extends` when looking up a service reference ([#170](https://github.com/docker/docker-language-server/issues/170))
31+
- textDocument/rename
32+
- consider `extends` when looking up a service reference ([#170](https://github.com/docker/docker-language-server/issues/170))
2433

2534
## [0.5.0] - 2025-05-05
2635

internal/compose/definition_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,43 @@ volumes:
405405
},
406406
},
407407
},
408+
{
409+
name: "extends as a string attribute",
410+
content: `
411+
services:
412+
test:
413+
image: alpine
414+
test2:
415+
extends: test`,
416+
line: 5,
417+
character: 15,
418+
locations: []protocol.Location{
419+
{
420+
URI: composeFileURI,
421+
Range: protocol.Range{
422+
Start: protocol.Position{Line: 2, Character: 2},
423+
End: protocol.Position{Line: 2, Character: 6},
424+
},
425+
},
426+
},
427+
links: []protocol.LocationLink{
428+
{
429+
OriginSelectionRange: &protocol.Range{
430+
Start: protocol.Position{Line: 5, Character: 13},
431+
End: protocol.Position{Line: 5, Character: 17},
432+
},
433+
TargetURI: composeFileURI,
434+
TargetRange: protocol.Range{
435+
Start: protocol.Position{Line: 2, Character: 2},
436+
End: protocol.Position{Line: 2, Character: 6},
437+
},
438+
TargetSelectionRange: protocol.Range{
439+
Start: protocol.Position{Line: 2, Character: 2},
440+
End: protocol.Position{Line: 2, Character: 6},
441+
},
442+
},
443+
},
444+
},
408445
}
409446

410447
for _, tc := range testCases {

internal/compose/documentHighlight.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,41 @@ func serviceDependencyReferences(node *ast.MappingValueNode, dependencyAttribute
3636
return nil
3737
}
3838

39+
func extendedServiceReferences(node *ast.MappingValueNode) []*token.Token {
40+
if servicesNode, ok := node.Value.(*ast.MappingNode); ok {
41+
tokens := []*token.Token{}
42+
for _, serviceNode := range servicesNode.Values {
43+
if serviceAttributes, ok := serviceNode.Value.(*ast.MappingNode); ok {
44+
for _, attributeNode := range serviceAttributes.Values {
45+
if attributeNode.Key.GetToken().Value == "extends" {
46+
if extendedValue, ok := attributeNode.Value.(*ast.StringNode); ok {
47+
tokens = append(tokens, extendedValue.GetToken())
48+
} else if mappingNode, ok := attributeNode.Value.(*ast.MappingNode); ok {
49+
localService := true
50+
for _, extendsObjectAttribute := range mappingNode.Values {
51+
if extendsObjectAttribute.Key.GetToken().Value == "file" {
52+
localService = false
53+
break
54+
}
55+
}
56+
57+
if localService {
58+
for _, extendsObjectAttribute := range mappingNode.Values {
59+
if extendsObjectAttribute.Key.GetToken().Value == "service" {
60+
tokens = append(tokens, extendsObjectAttribute.Value.GetToken())
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
return tokens
70+
}
71+
return nil
72+
}
73+
3974
func volumeToken(t *token.Token) *token.Token {
4075
idx := strings.Index(t.Value, ":")
4176
if idx != -1 {
@@ -115,6 +150,7 @@ func DocumentHighlight(doc document.ComposeDocument, position protocol.Position)
115150
switch s.Value {
116151
case "services":
117152
refs := serviceDependencyReferences(node, "depends_on", false)
153+
refs = append(refs, extendedServiceReferences(node)...)
118154
decls := declarations(node, "services")
119155
highlights := highlightReferences(refs, decls, line, character)
120156
if len(highlights) > 0 {

internal/compose/documentHighlight_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"slices"
78
"strings"
89
"testing"
910

@@ -247,6 +248,107 @@ services:
247248
End: protocol.Position{Line: 5, Character: 7},
248249
},
249250
},
251+
{
252+
name: "extends as a string attribute",
253+
content: `
254+
services:
255+
test:
256+
image: alpine
257+
test2:
258+
extends: test`,
259+
line: 5,
260+
character: 15,
261+
ranges: []protocol.DocumentHighlight{
262+
documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite),
263+
documentHighlight(5, 13, 5, 17, protocol.DocumentHighlightKindRead),
264+
},
265+
renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit {
266+
return &protocol.WorkspaceEdit{
267+
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
268+
u: {
269+
{
270+
NewText: "newName",
271+
Range: protocol.Range{
272+
Start: protocol.Position{Line: 2, Character: 2},
273+
End: protocol.Position{Line: 2, Character: 6},
274+
},
275+
},
276+
{
277+
NewText: "newName",
278+
Range: protocol.Range{
279+
Start: protocol.Position{Line: 5, Character: 13},
280+
End: protocol.Position{Line: 5, Character: 17},
281+
},
282+
},
283+
},
284+
},
285+
}
286+
},
287+
prepareRename: &protocol.Range{
288+
Start: protocol.Position{Line: 5, Character: 13},
289+
End: protocol.Position{Line: 5, Character: 17},
290+
},
291+
},
292+
{
293+
name: "extends as an object without a file attribute",
294+
content: `
295+
services:
296+
test:
297+
image: alpine
298+
test2:
299+
extends:
300+
service: test`,
301+
line: 6,
302+
character: 17,
303+
ranges: []protocol.DocumentHighlight{
304+
documentHighlight(2, 2, 2, 6, protocol.DocumentHighlightKindWrite),
305+
documentHighlight(6, 15, 6, 19, protocol.DocumentHighlightKindRead),
306+
},
307+
renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit {
308+
return &protocol.WorkspaceEdit{
309+
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
310+
u: {
311+
{
312+
NewText: "newName",
313+
Range: protocol.Range{
314+
Start: protocol.Position{Line: 2, Character: 2},
315+
End: protocol.Position{Line: 2, Character: 6},
316+
},
317+
},
318+
{
319+
NewText: "newName",
320+
Range: protocol.Range{
321+
Start: protocol.Position{Line: 6, Character: 15},
322+
End: protocol.Position{Line: 6, Character: 19},
323+
},
324+
},
325+
},
326+
},
327+
}
328+
},
329+
prepareRename: &protocol.Range{
330+
Start: protocol.Position{Line: 6, Character: 15},
331+
End: protocol.Position{Line: 6, Character: 19},
332+
},
333+
},
334+
{
335+
name: "extends as an object without a file attribute",
336+
content: `
337+
services:
338+
test:
339+
image: alpine
340+
test2:
341+
extends:
342+
service: test
343+
file: non-existent.yaml`,
344+
line: 6,
345+
character: 17,
346+
ranges: nil,
347+
renameEdits: func(u protocol.DocumentUri) *protocol.WorkspaceEdit {
348+
return nil
349+
},
350+
prepareRename: nil,
351+
},
250352
}
251353

252354
func TestDocumentHighlight_Services(t *testing.T) {
@@ -256,6 +358,9 @@ func TestDocumentHighlight_Services(t *testing.T) {
256358
t.Run(tc.name, func(t *testing.T) {
257359
doc := document.NewComposeDocument(u, 1, []byte(tc.content))
258360
ranges, err := DocumentHighlight(doc, protocol.Position{Line: tc.line, Character: tc.character})
361+
slices.SortFunc(ranges, func(a, b protocol.DocumentHighlight) int {
362+
return int(a.Range.Start.Line) - int(b.Range.Start.Line)
363+
})
259364
require.NoError(t, err)
260365
require.Equal(t, tc.ranges, ranges)
261366
})

internal/compose/rename_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"slices"
78
"strings"
89
"testing"
910

@@ -26,6 +27,11 @@ func TestRename_Services(t *testing.T) {
2627
},
2728
NewName: "newName",
2829
})
30+
if edits != nil {
31+
slices.SortFunc(edits.Changes[composeFileURI], func(a, b protocol.TextEdit) int {
32+
return int(a.Range.Start.Line) - int(b.Range.Start.Line)
33+
})
34+
}
2935
require.NoError(t, err)
3036
require.Equal(t, tc.renameEdits(composeFileURI), edits)
3137
})

0 commit comments

Comments
 (0)