Skip to content

Commit f96dcfa

Browse files
authored
Merge pull request #148 from docker/compose-volume-definition-support
Support textDocument/definition requests for volume references
2 parents 8326ae1 + 869755e commit f96dcfa

File tree

3 files changed

+156
-135
lines changed

3 files changed

+156
-135
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ All notable changes to the Docker Language Server will be documented in this fil
1414
- suggest dependent volume names for the `volumes` attribute ([#133](https://github.com/docker/docker-language-server/issues/133))
1515
- suggest dependent config names for the `configs` attribute ([#134](https://github.com/docker/docker-language-server/issues/134))
1616
- suggest dependent secret names for the `secrets` attribute ([#135](https://github.com/docker/docker-language-server/issues/135))
17+
- textDocument/definition
18+
- support looking up volume references ([#147](https://github.com/docker/docker-language-server/issues/147))
1719
- textDocument/documentHighlight
1820
- support highlighting the short form `depends_on` syntax for services ([#70](https://github.com/docker/docker-language-server/issues/70))
1921
- support highlighting the long form `depends_on` syntax for services ([#71](https://github.com/docker/docker-language-server/issues/71))

internal/compose/definition.go

Lines changed: 26 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -6,155 +6,46 @@ import (
66
"github.com/docker/docker-language-server/internal/pkg/document"
77
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
88
"github.com/docker/docker-language-server/internal/types"
9-
"github.com/goccy/go-yaml/ast"
10-
"github.com/goccy/go-yaml/token"
119
)
1210

13-
func findSequenceDependencyToken(attributeNode *ast.MappingValueNode, attributeName string, line, column int) (string, *token.Token) {
14-
if dependencies, ok := attributeNode.Value.(*ast.SequenceNode); ok {
15-
for _, dependency := range dependencies.Values {
16-
dependencyToken := dependency.GetToken()
17-
if dependencyToken.Position.Line == line && dependencyToken.Position.Column <= column && column <= dependencyToken.Position.Column+len(dependencyToken.Value) {
18-
return attributeName, dependencyToken
19-
}
20-
}
21-
}
22-
return "", nil
11+
func insideRange(rng protocol.Range, line, character protocol.UInteger) bool {
12+
return rng.Start.Line == line && rng.Start.Character <= character && character <= rng.End.Character
2313
}
2414

25-
func findDependencyToken(attributeNode *ast.MappingValueNode, attributeName string, line, column int) (string, *token.Token) {
26-
if attributeNode.Key.GetToken().Value == attributeName {
27-
return findSequenceDependencyToken(attributeNode, attributeName, line, column)
15+
func Definition(ctx context.Context, definitionLinkSupport bool, doc document.ComposeDocument, params *protocol.DefinitionParams) (any, error) {
16+
highlights, err := DocumentHighlight(doc, params.Position)
17+
if err != nil {
18+
return nil, err
2819
}
29-
return "", nil
30-
}
31-
32-
func lookupReference(serviceNode *ast.MappingValueNode, line, column int) (string, *token.Token) {
33-
if serviceAttributes, ok := serviceNode.Value.(*ast.MappingNode); ok {
34-
for _, attributeNode := range serviceAttributes.Values {
35-
if attributeNode.Key.GetToken().Value == "depends_on" {
36-
if _, ok := attributeNode.Value.(*ast.SequenceNode); ok {
37-
reference, dependency := findSequenceDependencyToken(attributeNode, "services", line, column)
38-
if dependency != nil {
39-
return reference, dependency
40-
}
41-
} else if serviceAttributes, ok := attributeNode.Value.(*ast.MappingNode); ok {
42-
for _, dependency := range serviceAttributes.Values {
43-
dependencyToken := dependency.Key.GetToken()
44-
if dependencyToken.Position.Line == line && dependencyToken.Position.Column <= column && column <= dependencyToken.Position.Column+len(dependencyToken.Value) {
45-
return "services", dependencyToken
46-
}
47-
}
48-
}
49-
}
5020

51-
reference, dependency := findDependencyToken(attributeNode, "configs", line, column)
52-
if dependency != nil {
53-
return reference, dependency
54-
}
55-
reference, dependency = findDependencyToken(attributeNode, "networks", line, column)
56-
if dependency != nil {
57-
return reference, dependency
58-
}
59-
reference, dependency = findDependencyToken(attributeNode, "secrets", line, column)
60-
if dependency != nil {
61-
return reference, dependency
21+
var sourceRange *protocol.Range
22+
var definitionRange *protocol.Range
23+
for _, highlight := range highlights {
24+
if *highlight.Kind == protocol.DocumentHighlightKindWrite {
25+
definitionRange = &highlight.Range
26+
if insideRange(highlight.Range, params.Position.Line, params.Position.Character) {
27+
sourceRange = &highlight.Range
28+
break
6229
}
6330
}
6431
}
65-
return "", nil
66-
}
6732

68-
func lookupDependency(node *ast.MappingValueNode, line, column int) (string, *token.Token) {
69-
if s, ok := node.Key.(*ast.StringNode); ok && s.Value == "services" {
70-
if servicesNode, ok := node.Value.(*ast.MappingNode); ok {
71-
for _, serviceNode := range servicesNode.Values {
72-
reference, dependency := lookupReference(serviceNode, line, column)
73-
if dependency != nil {
74-
return reference, dependency
75-
}
33+
for _, highlight := range highlights {
34+
if *highlight.Kind == protocol.DocumentHighlightKindRead {
35+
if insideRange(highlight.Range, params.Position.Line, params.Position.Character) {
36+
sourceRange = &highlight.Range
37+
break
7638
}
77-
} else if valueNode, ok := node.Value.(*ast.MappingValueNode); ok {
78-
return lookupReference(valueNode, line, column)
7939
}
8040
}
81-
return "", nil
82-
}
8341

84-
func findDefinition(node *ast.MappingValueNode, referenceType, referenceName string) *token.Token {
85-
if s, ok := node.Key.(*ast.StringNode); ok && s.Value == referenceType {
86-
if servicesNode, ok := node.Value.(*ast.MappingNode); ok {
87-
for _, serviceNode := range servicesNode.Values {
88-
if serviceNode.Key.GetToken().Value == referenceName {
89-
return serviceNode.Key.GetToken()
90-
}
91-
}
92-
} else if networks, ok := node.Value.(*ast.MappingValueNode); ok {
93-
if networks.Key.GetToken().Value == referenceName {
94-
return networks.Key.GetToken()
95-
}
96-
}
97-
}
98-
return nil
99-
}
100-
101-
func Definition(ctx context.Context, definitionLinkSupport bool, doc document.ComposeDocument, params *protocol.DefinitionParams) (any, error) {
102-
line := int(params.Position.Line) + 1
103-
character := int(params.Position.Character) + 1
104-
105-
file := doc.File()
106-
if file == nil || len(file.Docs) == 0 {
107-
return nil, nil
108-
}
109-
110-
if mappingNode, ok := file.Docs[0].Body.(*ast.MappingNode); ok {
111-
for _, node := range mappingNode.Values {
112-
reference, dependency := lookupDependency(node, line, character)
113-
if dependency != nil {
114-
for _, node := range mappingNode.Values {
115-
referenced := findDefinition(node, reference, dependency.Value)
116-
if referenced != nil {
117-
return dependencyLink(definitionLinkSupport, params, referenced, dependency), nil
118-
}
119-
}
120-
return nil, nil
121-
}
122-
}
123-
} else if mappingNodeValue, ok := file.Docs[0].Body.(*ast.MappingValueNode); ok {
124-
reference, dependency := lookupDependency(mappingNodeValue, line, character)
125-
if dependency != nil {
126-
referenced := findDefinition(mappingNodeValue, reference, dependency.Value)
127-
if referenced != nil {
128-
return dependencyLink(definitionLinkSupport, params, referenced, dependency), nil
129-
}
130-
}
42+
if definitionRange != nil {
43+
return types.CreateDefinitionResult(
44+
definitionLinkSupport,
45+
*definitionRange,
46+
sourceRange,
47+
params.TextDocument.URI,
48+
), nil
13149
}
13250
return nil, nil
13351
}
134-
135-
func dependencyLink(definitionLinkSupport bool, params *protocol.DefinitionParams, referenced, dependency *token.Token) any {
136-
return types.CreateDefinitionResult(
137-
definitionLinkSupport,
138-
protocol.Range{
139-
Start: protocol.Position{
140-
Line: protocol.UInteger(referenced.Position.Line - 1),
141-
Character: protocol.UInteger(referenced.Position.Column - 1),
142-
},
143-
End: protocol.Position{
144-
Line: protocol.UInteger(referenced.Position.Line - 1),
145-
Character: protocol.UInteger(referenced.Position.Column + len(referenced.Value) - 1),
146-
},
147-
},
148-
&protocol.Range{
149-
Start: protocol.Position{
150-
Line: params.Position.Line,
151-
Character: protocol.UInteger(dependency.Position.Column - 1),
152-
},
153-
End: protocol.Position{
154-
Line: params.Position.Line,
155-
Character: protocol.UInteger(dependency.Position.Column + len(dependency.Value) - 1),
156-
},
157-
},
158-
params.TextDocument.URI,
159-
)
160-
}

internal/compose/definition_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,134 @@ secrets:
277277
},
278278
},
279279
},
280+
{
281+
name: "volumes array item reference without a mount path",
282+
content: `
283+
services:
284+
test:
285+
volumes:
286+
- test2
287+
volumes:
288+
test2:`,
289+
line: 4,
290+
character: 10,
291+
locations: []protocol.Location{
292+
{
293+
URI: composeFileURI,
294+
Range: protocol.Range{
295+
Start: protocol.Position{Line: 6, Character: 2},
296+
End: protocol.Position{Line: 6, Character: 7},
297+
},
298+
},
299+
},
300+
links: []protocol.LocationLink{
301+
{
302+
OriginSelectionRange: &protocol.Range{
303+
Start: protocol.Position{Line: 4, Character: 8},
304+
End: protocol.Position{Line: 4, Character: 13},
305+
},
306+
TargetURI: composeFileURI,
307+
TargetRange: protocol.Range{
308+
Start: protocol.Position{Line: 6, Character: 2},
309+
End: protocol.Position{Line: 6, Character: 7},
310+
},
311+
TargetSelectionRange: protocol.Range{
312+
Start: protocol.Position{Line: 6, Character: 2},
313+
End: protocol.Position{Line: 6, Character: 7},
314+
},
315+
},
316+
},
317+
},
318+
{
319+
name: "volumes array item reference with a mount path",
320+
content: `
321+
services:
322+
test:
323+
volumes:
324+
- test2:/mount/path
325+
volumes:
326+
test2:`,
327+
line: 4,
328+
character: 10,
329+
locations: []protocol.Location{
330+
{
331+
URI: composeFileURI,
332+
Range: protocol.Range{
333+
Start: protocol.Position{Line: 6, Character: 2},
334+
End: protocol.Position{Line: 6, Character: 7},
335+
},
336+
},
337+
},
338+
links: []protocol.LocationLink{
339+
{
340+
OriginSelectionRange: &protocol.Range{
341+
Start: protocol.Position{Line: 4, Character: 8},
342+
End: protocol.Position{Line: 4, Character: 13},
343+
},
344+
TargetURI: composeFileURI,
345+
TargetRange: protocol.Range{
346+
Start: protocol.Position{Line: 6, Character: 2},
347+
End: protocol.Position{Line: 6, Character: 7},
348+
},
349+
TargetSelectionRange: protocol.Range{
350+
Start: protocol.Position{Line: 6, Character: 2},
351+
End: protocol.Position{Line: 6, Character: 7},
352+
},
353+
},
354+
},
355+
},
356+
{
357+
name: "volumes array item reference on the mount path itself",
358+
content: `
359+
services:
360+
test:
361+
volumes:
362+
- test2:/mount/path
363+
volumes:
364+
test2:`,
365+
line: 4,
366+
character: 18,
367+
locations: nil,
368+
links: nil,
369+
},
370+
{
371+
name: "volume array item object's with the source attribute",
372+
content: `
373+
services:
374+
test:
375+
volumes:
376+
- source: test2
377+
volumes:
378+
test2:`,
379+
line: 4,
380+
character: 18,
381+
locations: []protocol.Location{
382+
{
383+
URI: composeFileURI,
384+
Range: protocol.Range{
385+
Start: protocol.Position{Line: 6, Character: 2},
386+
End: protocol.Position{Line: 6, Character: 7},
387+
},
388+
},
389+
},
390+
links: []protocol.LocationLink{
391+
{
392+
OriginSelectionRange: &protocol.Range{
393+
Start: protocol.Position{Line: 4, Character: 16},
394+
End: protocol.Position{Line: 4, Character: 21},
395+
},
396+
TargetURI: composeFileURI,
397+
TargetRange: protocol.Range{
398+
Start: protocol.Position{Line: 6, Character: 2},
399+
End: protocol.Position{Line: 6, Character: 7},
400+
},
401+
TargetSelectionRange: protocol.Range{
402+
Start: protocol.Position{Line: 6, Character: 2},
403+
End: protocol.Position{Line: 6, Character: 7},
404+
},
405+
},
406+
},
407+
},
280408
}
281409

282410
for _, tc := range testCases {

0 commit comments

Comments
 (0)