Skip to content

Commit b68c280

Browse files
authored
Merge pull request #122 from docker/migrate-compose-yaml-symbols
Refactor Compose documentSymbols to goccy/go-yaml
2 parents 051611d + 04938be commit b68c280

File tree

2 files changed

+176
-111
lines changed

2 files changed

+176
-111
lines changed

internal/compose/documentSymbol.go

Lines changed: 82 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -5,129 +5,101 @@ import (
55

66
"github.com/docker/docker-language-server/internal/pkg/document"
77
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
8-
"gopkg.in/yaml.v3"
8+
"github.com/goccy/go-yaml/ast"
9+
"github.com/goccy/go-yaml/token"
910
)
1011

11-
func DocumentSymbol(ctx context.Context, doc document.ComposeDocument) (result []any, err error) {
12-
root := doc.RootNode()
13-
if len(root.Content) > 0 {
14-
for i := range root.Content[0].Content {
15-
switch root.Content[0].Content[i].Value {
16-
case "services":
17-
symbols := createSymbol(root.Content[0].Content, i+1, protocol.SymbolKindClass)
18-
result = append(result, symbols...)
19-
case "networks":
20-
symbols := createSymbol(root.Content[0].Content, i+1, protocol.SymbolKindInterface)
21-
result = append(result, symbols...)
22-
case "volumes":
23-
symbols := createSymbol(root.Content[0].Content, i+1, protocol.SymbolKindFile)
24-
result = append(result, symbols...)
25-
case "configs":
26-
symbols := createSymbol(root.Content[0].Content, i+1, protocol.SymbolKindVariable)
27-
result = append(result, symbols...)
28-
case "secrets":
29-
symbols := createSymbol(root.Content[0].Content, i+1, protocol.SymbolKindKey)
30-
result = append(result, symbols...)
31-
case "include":
32-
for _, included := range root.Content[0].Content[i+1].Content {
33-
switch included.Kind {
34-
case yaml.MappingNode:
35-
// long syntax with an object
36-
for j := range included.Content {
37-
if included.Content[j].Value == "path" {
38-
switch included.Content[j+1].Kind {
39-
case yaml.SequenceNode:
40-
for _, path := range included.Content[j+1].Content {
41-
character := uint32(path.Column - 1)
42-
rng := protocol.Range{
43-
Start: protocol.Position{
44-
Line: uint32(path.Line - 1),
45-
Character: character,
46-
},
47-
End: protocol.Position{
48-
Line: uint32(path.Line - 1),
49-
Character: character + uint32(len(path.Value)),
50-
},
51-
}
52-
result = append(result, &protocol.DocumentSymbol{
53-
Name: path.Value,
54-
Kind: protocol.SymbolKindModule,
55-
Range: rng,
56-
SelectionRange: rng,
57-
})
58-
}
59-
case yaml.ScalarNode:
60-
character := uint32(included.Content[j+1].Column - 1)
61-
rng := protocol.Range{
62-
Start: protocol.Position{
63-
Line: uint32(included.Content[j+1].Line - 1),
64-
Character: character,
65-
},
66-
End: protocol.Position{
67-
Line: uint32(included.Content[j+1].Line - 1),
68-
Character: character + uint32(len(included.Content[j+1].Value)),
69-
},
70-
}
71-
result = append(result, &protocol.DocumentSymbol{
72-
Name: included.Content[j+1].Value,
73-
Kind: protocol.SymbolKindModule,
74-
Range: rng,
75-
SelectionRange: rng,
76-
})
77-
}
12+
var symbolKinds = map[string]protocol.SymbolKind{
13+
"services": protocol.SymbolKindClass,
14+
"networks": protocol.SymbolKindInterface,
15+
"volumes": protocol.SymbolKindFile,
16+
"configs": protocol.SymbolKindVariable,
17+
"secrets": protocol.SymbolKindKey,
18+
}
19+
20+
func findSymbols(value string, n *ast.MappingValueNode, mapping map[string]protocol.SymbolKind) (result []any) {
21+
if kind, ok := mapping[value]; ok {
22+
if mappingNode, ok := n.Value.(*ast.MappingNode); ok {
23+
for _, service := range mappingNode.Values {
24+
result = append(result, createSymbol(service.Key.GetToken(), kind))
25+
}
26+
} else if n, ok := n.Value.(*ast.MappingValueNode); ok {
27+
result = append(result, createSymbol(n.Key.GetToken(), kind))
28+
}
29+
} else if value == "include" {
30+
if sequenceNode, ok := n.Value.(*ast.SequenceNode); ok {
31+
for _, include := range sequenceNode.Values {
32+
if _, ok := include.(*ast.StringNode); ok {
33+
// include:
34+
// - abc.yml
35+
// - def.yml
36+
result = append(result, createSymbol(include.GetToken(), protocol.SymbolKindModule))
37+
} else if includeNode, ok := include.(*ast.MappingValueNode); ok {
38+
if includeNode.Key.GetToken().Value == "path" {
39+
// include:
40+
// - path:
41+
// - ../commons/compose.yaml
42+
// - ./commons-override.yaml
43+
if included, ok := includeNode.Value.(*ast.SequenceNode); ok {
44+
for _, path := range included.Values {
45+
result = append(result, createSymbol(path.GetToken(), protocol.SymbolKindModule))
7846
}
7947
}
80-
case yaml.ScalarNode:
81-
// include:
82-
// - abc.yml
83-
// - def.yml
84-
character := uint32(included.Column - 1)
85-
rng := protocol.Range{
86-
Start: protocol.Position{
87-
Line: uint32(included.Line - 1),
88-
Character: character,
89-
},
90-
End: protocol.Position{
91-
Line: uint32(included.Line - 1),
92-
Character: character + uint32(len(included.Value)),
93-
},
48+
}
49+
} else if includeNode, ok := include.(*ast.MappingNode); ok {
50+
// include:
51+
// - path: ../commons/compose.yaml
52+
// project_directory: ..
53+
// env_file: ../another/.env
54+
for _, attribute := range includeNode.Values {
55+
if attribute.Key.GetToken().Value == "path" {
56+
result = append(result, createSymbol(attribute.Value.GetToken(), protocol.SymbolKindModule))
9457
}
95-
result = append(result, &protocol.DocumentSymbol{
96-
Name: included.Value,
97-
Kind: protocol.SymbolKindModule,
98-
Range: rng,
99-
SelectionRange: rng,
100-
})
10158
}
10259
}
10360
}
10461
}
10562
}
106-
return result, nil
63+
return result
10764
}
10865

109-
func createSymbol(nodes []*yaml.Node, idx int, kind protocol.SymbolKind) (result []any) {
110-
for i := 0; i < len(nodes[idx].Content); i += 2 {
111-
service := nodes[idx].Content[i]
112-
if service.Value != "" {
113-
character := uint32(service.Column - 1)
114-
rng := protocol.Range{
115-
Start: protocol.Position{
116-
Line: uint32(service.Line - 1),
117-
Character: character,
118-
},
119-
End: protocol.Position{
120-
Line: uint32(service.Line - 1),
121-
Character: character + uint32(len(service.Value)),
122-
},
66+
func DocumentSymbol(ctx context.Context, doc document.ComposeDocument) (result []any, err error) {
67+
file := doc.File()
68+
if file == nil || len(file.Docs) == 0 {
69+
return nil, nil
70+
}
71+
72+
for _, documentNode := range file.Docs {
73+
if n, ok := documentNode.Body.(*ast.MappingValueNode); ok {
74+
if s, ok := n.Key.(*ast.StringNode); ok {
75+
result = append(result, findSymbols(s.Value, n, symbolKinds)...)
76+
}
77+
} else if mappingNode, ok := documentNode.Body.(*ast.MappingNode); ok {
78+
for _, n := range mappingNode.Values {
79+
if s, ok := n.Key.(*ast.StringNode); ok {
80+
result = append(result, findSymbols(s.Value, n, symbolKinds)...)
81+
}
12382
}
124-
result = append(result, &protocol.DocumentSymbol{
125-
Name: service.Value,
126-
Kind: kind,
127-
Range: rng,
128-
SelectionRange: rng,
129-
})
13083
}
13184
}
132-
return result
85+
return result, nil
86+
}
87+
88+
func createSymbol(t *token.Token, kind protocol.SymbolKind) *protocol.DocumentSymbol {
89+
rng := protocol.Range{
90+
Start: protocol.Position{
91+
Line: uint32(t.Position.Line - 1),
92+
Character: uint32(t.Position.Column - 1),
93+
},
94+
End: protocol.Position{
95+
Line: uint32(t.Position.Line - 1),
96+
Character: uint32(t.Position.Column - 1 + len(t.Value)),
97+
},
98+
}
99+
return &protocol.DocumentSymbol{
100+
Name: t.Value,
101+
Kind: kind,
102+
Range: rng,
103+
SelectionRange: rng,
104+
}
133105
}

internal/compose/documentSymbol_test.go

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,41 @@ func TestDocumentSymbol(t *testing.T) {
5959
},
6060
},
6161
},
62+
{
63+
name: "duplicated services block",
64+
content: `services:
65+
web:
66+
build: .
67+
services:
68+
redis:
69+
image: "redis:alpine"`,
70+
symbols: []*protocol.DocumentSymbol{
71+
{
72+
Name: "web",
73+
Kind: protocol.SymbolKindClass,
74+
Range: protocol.Range{
75+
Start: protocol.Position{Line: 1, Character: 2},
76+
End: protocol.Position{Line: 1, Character: 5},
77+
},
78+
SelectionRange: protocol.Range{
79+
Start: protocol.Position{Line: 1, Character: 2},
80+
End: protocol.Position{Line: 1, Character: 5},
81+
},
82+
},
83+
{
84+
Name: "redis",
85+
Kind: protocol.SymbolKindClass,
86+
Range: protocol.Range{
87+
Start: protocol.Position{Line: 4, Character: 2},
88+
End: protocol.Position{Line: 4, Character: 7},
89+
},
90+
SelectionRange: protocol.Range{
91+
Start: protocol.Position{Line: 4, Character: 2},
92+
End: protocol.Position{Line: 4, Character: 7},
93+
},
94+
},
95+
},
96+
},
6297
{
6398
name: "services block with a piped scalar value",
6499
content: `services:
@@ -177,7 +212,7 @@ func TestDocumentSymbol(t *testing.T) {
177212
{
178213
name: "include array, path with list of strings",
179214
content: `include:
180-
- path:
215+
- path:
181216
- ../commons/compose.yaml
182217
- ./commons-override.yaml`,
183218
symbols: []*protocol.DocumentSymbol{
@@ -207,6 +242,14 @@ func TestDocumentSymbol(t *testing.T) {
207242
},
208243
},
209244
},
245+
{
246+
name: "include array, wrong name with list of strings",
247+
content: `include:
248+
- path2:
249+
- ../commons/compose.yaml
250+
- ./commons-override.yaml`,
251+
symbols: []*protocol.DocumentSymbol{},
252+
},
210253
{
211254
name: "include array, long syntax",
212255
content: `include:
@@ -228,6 +271,56 @@ func TestDocumentSymbol(t *testing.T) {
228271
},
229272
},
230273
},
274+
{
275+
name: "regular file",
276+
content: `
277+
services:
278+
web:
279+
build: .
280+
redis:
281+
image: redis
282+
283+
networks:
284+
testNetwork:`,
285+
symbols: []*protocol.DocumentSymbol{
286+
{
287+
Name: "web",
288+
Kind: protocol.SymbolKindClass,
289+
Range: protocol.Range{
290+
Start: protocol.Position{Line: 2, Character: 2},
291+
End: protocol.Position{Line: 2, Character: 5},
292+
},
293+
SelectionRange: protocol.Range{
294+
Start: protocol.Position{Line: 2, Character: 2},
295+
End: protocol.Position{Line: 2, Character: 5},
296+
},
297+
},
298+
{
299+
Name: "redis",
300+
Kind: protocol.SymbolKindClass,
301+
Range: protocol.Range{
302+
Start: protocol.Position{Line: 4, Character: 2},
303+
End: protocol.Position{Line: 4, Character: 7},
304+
},
305+
SelectionRange: protocol.Range{
306+
Start: protocol.Position{Line: 4, Character: 2},
307+
End: protocol.Position{Line: 4, Character: 7},
308+
},
309+
},
310+
{
311+
Name: "testNetwork",
312+
Kind: protocol.SymbolKindInterface,
313+
Range: protocol.Range{
314+
Start: protocol.Position{Line: 8, Character: 2},
315+
End: protocol.Position{Line: 8, Character: 13},
316+
},
317+
SelectionRange: protocol.Range{
318+
Start: protocol.Position{Line: 8, Character: 2},
319+
End: protocol.Position{Line: 8, Character: 13},
320+
},
321+
},
322+
},
323+
},
231324
}
232325

233326
for _, tc := range testCases {

0 commit comments

Comments
 (0)