Skip to content

Commit 5501e18

Browse files
authored
Merge pull request #128 from docker/refactor-compose-completion
Refactor Compose completion code to use goccy/go-yaml
2 parents f8a6c2c + c7f4a06 commit 5501e18

File tree

5 files changed

+96
-87
lines changed

5 files changed

+96
-87
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ require (
1717
github.com/tliron/commonlog v0.2.18
1818
github.com/zclconf/go-cty v1.16.2
1919
go.lsp.dev/uri v0.3.0
20-
gopkg.in/yaml.v3 v3.0.1
2120
)
2221

2322
require (
@@ -170,4 +169,5 @@ require (
170169
google.golang.org/grpc v1.69.4 // indirect
171170
google.golang.org/protobuf v1.35.2 // indirect
172171
gopkg.in/warnings.v0 v0.1.2 // indirect
172+
gopkg.in/yaml.v3 v3.0.1 // indirect
173173
)

internal/compose/completion.go

Lines changed: 74 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import (
1010
"github.com/docker/docker-language-server/internal/pkg/document"
1111
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
1212
"github.com/docker/docker-language-server/internal/types"
13+
"github.com/goccy/go-yaml/ast"
1314
"github.com/santhosh-tekuri/jsonschema/v6"
14-
"gopkg.in/yaml.v3"
1515
)
1616

1717
func Completion(ctx context.Context, params *protocol.CompletionParams, doc document.ComposeDocument) (*protocol.CompletionList, error) {
@@ -32,38 +32,26 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, doc docu
3232

3333
lines := strings.Split(string(doc.Input()), "\n")
3434
lspLine := int(params.Position.Line)
35-
if lspLine >= len(lines) {
36-
return nil, nil
37-
}
38-
3935
if strings.HasPrefix(strings.TrimSpace(lines[lspLine]), "#") {
4036
return nil, nil
4137
}
4238

43-
root := doc.RootNode()
44-
if len(root.Content) == 0 {
39+
file := doc.File()
40+
if file == nil || len(file.Docs) == 0 {
4541
return nil, nil
4642
}
4743

4844
line := int(lspLine) + 1
4945
character := int(params.Position.Character) + 1
50-
topLevel, _, _ := NodeStructure(line, root.Content[0].Content)
51-
if len(topLevel) == 0 {
46+
path := constructCompletionNodePath(file, line)
47+
if len(path) == 1 {
5248
return nil, nil
53-
} else if len(topLevel) == 1 {
54-
return nil, nil
55-
} else if topLevel[1].Column >= character {
56-
return nil, nil
57-
} else if len(topLevel) > 2 && topLevel[1].Column < character && character < topLevel[2].Column {
58-
topLevel = []*yaml.Node{topLevel[0], topLevel[1]}
59-
}
60-
61-
if topLevel[0].Line == line {
49+
} else if path[1].Key.GetToken().Position.Column >= character {
6250
return nil, nil
6351
}
6452

6553
items := []protocol.CompletionItem{}
66-
nodeProps := nodeProperties(topLevel, line, character)
54+
nodeProps := nodeProperties(path, line, character)
6755
if schema, ok := nodeProps.(*jsonschema.Schema); ok {
6856
if schema.Enum != nil {
6957
for _, value := range schema.Enum.Values {
@@ -121,64 +109,87 @@ func Completion(ctx context.Context, params *protocol.CompletionParams, doc docu
121109
return &protocol.CompletionList{Items: items}, nil
122110
}
123111

124-
func NodeStructure(line int, rootNodes []*yaml.Node) ([]*yaml.Node, *yaml.Node, bool) {
112+
func constructCompletionNodePath(file *ast.File, line int) []*ast.MappingValueNode {
113+
for _, documentNode := range file.Docs {
114+
if mappingNode, ok := documentNode.Body.(*ast.MappingNode); ok {
115+
return NodeStructure(line, mappingNode.Values)
116+
}
117+
}
118+
return nil
119+
}
120+
121+
func NodeStructure(line int, rootNodes []*ast.MappingValueNode) []*ast.MappingValueNode {
125122
if len(rootNodes) == 0 {
126-
return nil, nil, false
127-
}
128-
129-
var topLevel *yaml.Node
130-
var content *yaml.Node
131-
for i := 0; i < len(rootNodes); i += 2 {
132-
if rootNodes[i].Line < line {
133-
topLevel = rootNodes[i]
134-
content = rootNodes[i+1]
135-
} else if rootNodes[i].Line == line {
136-
return []*yaml.Node{rootNodes[i]}, rootNodes[i+1], true
137-
} else if line < rootNodes[i].Line {
123+
return nil
124+
}
125+
126+
var candidate *ast.MappingValueNode
127+
for _, node := range rootNodes {
128+
if node.GetToken().Position.Line < line {
129+
candidate = node
130+
} else if node.GetToken().Position.Line == line {
131+
return []*ast.MappingValueNode{node}
132+
} else {
138133
break
139134
}
140135
}
141-
nodes := []*yaml.Node{topLevel}
142-
candidates, subcontent := walkNodes(line, content.Content)
136+
nodes := []*ast.MappingValueNode{candidate}
137+
candidates := walkNodes(line, candidate)
143138
nodes = append(nodes, candidates...)
144-
if subcontent != nil {
145-
content = subcontent
146-
}
147-
return nodes, content, false
139+
return nodes
148140
}
149141

150-
func walkNodes(line int, nodes []*yaml.Node) ([]*yaml.Node, *yaml.Node) {
151-
var candidate *yaml.Node
152-
var candidateContent *yaml.Node
153-
for i := 0; i < len(nodes); i += 2 {
154-
if nodes[i].Line < line {
155-
candidate = nodes[i]
156-
if candidate.Kind == yaml.MappingNode {
157-
return walkNodes(line, candidate.Content)
142+
func walkNodes(line int, node *ast.MappingValueNode) []*ast.MappingValueNode {
143+
var candidate ast.Node
144+
value := node.Value
145+
if mappingNode, ok := value.(*ast.MappingNode); ok {
146+
for _, child := range mappingNode.Values {
147+
if child.GetToken().Position.Line < line {
148+
candidate = child
149+
} else if child.GetToken().Position.Line == line {
150+
candidate = child
151+
break
158152
}
159-
if len(nodes) == i+1 {
160-
return []*yaml.Node{candidate}, nil
153+
}
154+
} else if sequenceNode, ok := value.(*ast.SequenceNode); ok {
155+
for _, child := range sequenceNode.Values {
156+
if child.GetToken().Position.Line < line {
157+
if _, ok := child.(*ast.NullNode); ok {
158+
continue
159+
}
160+
candidate = child
161+
} else if child.GetToken().Position.Line == line {
162+
if _, ok := child.(*ast.NullNode); ok {
163+
break
164+
}
165+
candidate = child
166+
break
161167
}
162-
candidateContent = nodes[i+1]
163-
} else if nodes[i].Line == line {
164-
if nodes[i].Kind == yaml.MappingNode {
165-
return walkNodes(line, nodes[i].Content)
168+
}
169+
}
170+
171+
if mappingNode, ok := candidate.(*ast.MappingNode); ok {
172+
for _, child := range mappingNode.Values {
173+
if child.GetToken().Position.Line < line {
174+
candidate = child
175+
} else if child.GetToken().Position.Line == line {
176+
candidate = child
177+
break
166178
}
167-
return []*yaml.Node{nodes[i]}, nil
168-
} else if line < nodes[i].Line {
169-
break
170179
}
171180
}
172-
if candidateContent == nil {
173-
return []*yaml.Node{}, nil
181+
182+
if candidate == nil {
183+
return []*ast.MappingValueNode{}
174184
}
175-
walked, subcontent := walkNodes(line, candidateContent.Content)
176-
candidates := []*yaml.Node{candidate}
177-
candidates = append(candidates, walked...)
178-
if subcontent != nil {
179-
candidateContent = subcontent
185+
186+
if next, ok := candidate.(*ast.MappingValueNode); ok {
187+
nodes := []*ast.MappingValueNode{next}
188+
candidates := walkNodes(line, next)
189+
nodes = append(nodes, candidates...)
190+
return nodes
180191
}
181-
return candidates, candidateContent
192+
return []*ast.MappingValueNode{}
182193
}
183194

184195
func extractDetail(schema *jsonschema.Schema) *string {

internal/compose/completion_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1684,7 +1684,7 @@ services:
16841684
list: nil,
16851685
},
16861686
{
1687-
name: "bug",
1687+
name: "unexpected array for networks",
16881688
content: `
16891689
networks:
16901690
-

internal/compose/schema.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
_ "embed"
66
"slices"
77

8+
"github.com/goccy/go-yaml/ast"
89
"github.com/santhosh-tekuri/jsonschema/v6"
9-
"gopkg.in/yaml.v3"
1010
)
1111

1212
//go:embed compose-spec.json
@@ -35,11 +35,11 @@ func schemaProperties() map[string]*jsonschema.Schema {
3535
return composeSchema.Properties
3636
}
3737

38-
func nodeProperties(nodes []*yaml.Node, line, column int) any {
38+
func nodeProperties(nodes []*ast.MappingValueNode, line, column int) any {
3939
if composeSchema != nil && slices.Contains(composeSchema.Types.ToStrings(), "object") && composeSchema.Properties != nil {
40-
if prop, ok := composeSchema.Properties[nodes[0].Value]; ok {
40+
if prop, ok := composeSchema.Properties[nodes[0].Key.GetToken().Value]; ok {
4141
for regexp, property := range prop.PatternProperties {
42-
if regexp.MatchString(nodes[1].Value) {
42+
if regexp.MatchString(nodes[1].Key.GetToken().Value) {
4343
if property.Ref != nil {
4444
return recurseNodeProperties(nodes, line, column, 2, property.Ref.Properties)
4545
}
@@ -50,21 +50,26 @@ func nodeProperties(nodes []*yaml.Node, line, column int) any {
5050
return nil
5151
}
5252

53-
func recurseNodeProperties(nodes []*yaml.Node, line, column, nodeOffset int, properties map[string]*jsonschema.Schema) any {
54-
if len(nodes) == nodeOffset || (len(nodes) >= nodeOffset+2 && nodes[nodeOffset].Column <= column && column < nodes[nodeOffset+1].Column) {
53+
func recurseNodeProperties(nodes []*ast.MappingValueNode, line, column, nodeOffset int, properties map[string]*jsonschema.Schema) any {
54+
if len(nodes) == nodeOffset {
5555
return properties
56-
} else if column == nodes[nodeOffset].Column {
56+
}
57+
if len(nodes) >= nodeOffset+2 && nodes[nodeOffset].Key.GetToken().Position.Column <= column && column < nodes[nodeOffset+1].Key.GetToken().Position.Column {
58+
return properties
59+
}
60+
if column == nodes[nodeOffset].Key.GetToken().Position.Column {
5761
return properties
5862
}
5963

60-
value := nodes[nodeOffset].Value
64+
value := nodes[nodeOffset].Key.GetToken().Value
6165
if prop, ok := properties[value]; ok {
6266
if prop.Ref != nil {
6367
if len(prop.Ref.Properties) > 0 {
6468
return recurseNodeProperties(nodes, line, column, nodeOffset+1, prop.Ref.Properties)
6569
}
6670
for regexp, property := range prop.Ref.PatternProperties {
67-
if regexp.MatchString(nodes[nodeOffset+1].Value) {
71+
nextValue := nodes[nodeOffset+1].Key.GetToken().Value
72+
if regexp.MatchString(nextValue) {
6873
for _, nested := range property.OneOf {
6974
if slices.Contains(nested.Types.ToStrings(), "object") {
7075
return recurseNodeProperties(nodes, line, column, nodeOffset+2, nested.Properties)
@@ -94,7 +99,8 @@ func recurseNodeProperties(nodes []*yaml.Node, line, column, nodeOffset int, pro
9499
return nil
95100
}
96101

97-
if regexp.MatchString(nodes[nodeOffset+1].Value) {
102+
nextValue := nodes[nodeOffset+1].Key.GetToken().Value
103+
if regexp.MatchString(nextValue) {
98104
for _, nested := range property.OneOf {
99105
if slices.Contains(nested.Types.ToStrings(), "object") {
100106
return recurseNodeProperties(nodes, line, column, nodeOffset+2, nested.Properties)
@@ -115,8 +121,8 @@ func recurseNodeProperties(nodes []*yaml.Node, line, column, nodeOffset int, pro
115121
}
116122
}
117123

118-
if nodes[nodeOffset].Column < column {
119-
if nodes[nodeOffset].Line == line {
124+
if nodes[nodeOffset].Key.GetToken().Position.Column < column {
125+
if nodes[nodeOffset].Key.GetToken().Position.Line == line {
120126
return prop
121127
}
122128
return recurseNodeProperties(nodes, line, column, nodeOffset+1, prop.Properties)

internal/pkg/document/dockerComposeDocument.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,17 @@ import (
77
"github.com/goccy/go-yaml/ast"
88
"github.com/goccy/go-yaml/parser"
99
"go.lsp.dev/uri"
10-
"gopkg.in/yaml.v3"
1110
)
1211

1312
type ComposeDocument interface {
1413
Document
15-
RootNode() yaml.Node
1614
File() *ast.File
1715
}
1816

1917
type composeDocument struct {
2018
document
21-
mutex sync.Mutex
22-
rootNode yaml.Node
23-
file *ast.File
19+
mutex sync.Mutex
20+
file *ast.File
2421
}
2522

2623
func NewComposeDocument(u uri.URI, version int32, input []byte) ComposeDocument {
@@ -42,7 +39,6 @@ func (d *composeDocument) parse(_ bool) bool {
4239
d.mutex.Lock()
4340
defer d.mutex.Unlock()
4441

45-
_ = yaml.Unmarshal([]byte(d.input), &d.rootNode)
4642
d.file, _ = parser.ParseBytes(d.input, 0)
4743
return true
4844
}
@@ -51,10 +47,6 @@ func (d *composeDocument) copy() Document {
5147
return NewComposeDocument(d.uri, d.version, d.input)
5248
}
5349

54-
func (d *composeDocument) RootNode() yaml.Node {
55-
return d.rootNode
56-
}
57-
5850
func (d *composeDocument) File() *ast.File {
5951
return d.file
6052
}

0 commit comments

Comments
 (0)