Skip to content

Commit 6f0feae

Browse files
Global cache for documents + top level jsonnet objects (#153)
* Global cache for documents + top level jsonnet objects Closes #133 There are two caches currently: - One for protocol documents. This one is instantiated by the server and maintained up-to-date as documents are opened, changed, and closed. - One for jsonnet objects. This one is a global var and is only added to. Modified objects are never removed/modified from the cache. By merging the two caches, we can expand the first cache's behavior to also invalidate modified objects from the global cache when a document is changed. * Simplify processing args (#154) Instead of carrying a `cache` and `vm` around on each function, create a `Processor` struct to contain those * Fix linting
1 parent 4c756e3 commit 6f0feae

15 files changed

+185
-139
lines changed

.golangci.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
[linters]
22
enable = [
3+
"copyloopvar",
34
"dogsled",
4-
"exportloopref",
55
"forcetypeassert",
66
"goconst",
77
"gocritic",
88
"gocyclo",
99
"goimports",
1010
"goprintffuncname",
11-
"gosec",
1211
"gosimple",
1312
"govet",
1413
"ineffassign",

pkg/ast/processing/find_field.go

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import (
55
"reflect"
66
"strings"
77

8-
"github.com/google/go-jsonnet"
98
"github.com/google/go-jsonnet/ast"
109
"github.com/grafana/jsonnet-language-server/pkg/nodestack"
1110
log "github.com/sirupsen/logrus"
1211
)
1312

14-
func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm *jsonnet.VM, partialMatchFields bool) ([]ObjectRange, error) {
13+
func (p *Processor) FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, partialMatchFields bool) ([]ObjectRange, error) {
1514
var foundDesugaredObjects []*ast.DesugaredObject
1615
// First element will be super, self, or var name
1716
start, indexList := indexList[0], indexList[1:]
@@ -31,13 +30,13 @@ func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm
3130
if _, ok := tmpStack.Peek().(*ast.Binary); ok {
3231
tmpStack.Pop()
3332
}
34-
foundDesugaredObjects = filterSelfScope(FindTopLevelObjects(tmpStack, vm))
33+
foundDesugaredObjects = filterSelfScope(p.FindTopLevelObjects(tmpStack))
3534
case start == "std":
3635
return nil, fmt.Errorf("cannot get definition of std lib")
3736
case start == "$":
38-
foundDesugaredObjects = FindTopLevelObjects(nodestack.NewNodeStack(stack.From), vm)
37+
foundDesugaredObjects = p.FindTopLevelObjects(nodestack.NewNodeStack(stack.From))
3938
case strings.Contains(start, "."):
40-
foundDesugaredObjects = FindTopLevelObjectsInFile(vm, start, "")
39+
foundDesugaredObjects = p.FindTopLevelObjectsInFile(start, "")
4140

4241
default:
4342
if strings.Count(start, "(") == 1 && strings.Count(start, ")") == 1 {
@@ -65,15 +64,15 @@ func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm
6564
foundDesugaredObjects = append(foundDesugaredObjects, bodyNode)
6665
case *ast.Self:
6766
tmpStack := nodestack.NewNodeStack(stack.From)
68-
foundDesugaredObjects = FindTopLevelObjects(tmpStack, vm)
67+
foundDesugaredObjects = p.FindTopLevelObjects(tmpStack)
6968
case *ast.Import:
7069
filename := bodyNode.File.Value
71-
foundDesugaredObjects = FindTopLevelObjectsInFile(vm, filename, "")
70+
foundDesugaredObjects = p.FindTopLevelObjectsInFile(filename, "")
7271

7372
case *ast.Index, *ast.Apply:
7473
tempStack := nodestack.NewNodeStack(bodyNode)
7574
indexList = append(tempStack.BuildIndexList(), indexList...)
76-
return FindRangesFromIndexList(stack, indexList, vm, partialMatchFields)
75+
return p.FindRangesFromIndexList(stack, indexList, partialMatchFields)
7776
case *ast.Function:
7877
// If the function's body is an object, it means we can look for indexes within the function
7978
if funcBody := findChildDesugaredObject(bodyNode.Body); funcBody != nil {
@@ -84,10 +83,10 @@ func FindRangesFromIndexList(stack *nodestack.NodeStack, indexList []string, vm
8483
}
8584
}
8685

87-
return extractObjectRangesFromDesugaredObjs(vm, foundDesugaredObjects, indexList, partialMatchFields)
86+
return p.extractObjectRangesFromDesugaredObjs(foundDesugaredObjects, indexList, partialMatchFields)
8887
}
8988

90-
func extractObjectRangesFromDesugaredObjs(vm *jsonnet.VM, desugaredObjs []*ast.DesugaredObject, indexList []string, partialMatchFields bool) ([]ObjectRange, error) {
89+
func (p *Processor) extractObjectRangesFromDesugaredObjs(desugaredObjs []*ast.DesugaredObject, indexList []string, partialMatchFields bool) ([]ObjectRange, error) {
9190
var ranges []ObjectRange
9291
for len(indexList) > 0 {
9392
index := indexList[0]
@@ -111,7 +110,7 @@ func extractObjectRangesFromDesugaredObjs(vm *jsonnet.VM, desugaredObjs []*ast.D
111110
return ranges, nil
112111
}
113112

114-
fieldNodes, err := unpackFieldNodes(vm, foundFields)
113+
fieldNodes, err := p.unpackFieldNodes(foundFields)
115114
if err != nil {
116115
return nil, err
117116
}
@@ -125,7 +124,7 @@ func extractObjectRangesFromDesugaredObjs(vm *jsonnet.VM, desugaredObjs []*ast.D
125124
// The target is a function and will be found by FindVarReference on the next loop
126125
fieldNodes = append(fieldNodes, fieldNode.Target)
127126
case *ast.Var:
128-
varReference, err := FindVarReference(fieldNode, vm)
127+
varReference, err := p.FindVarReference(fieldNode)
129128
if err != nil {
130129
return nil, err
131130
}
@@ -142,11 +141,11 @@ func extractObjectRangesFromDesugaredObjs(vm *jsonnet.VM, desugaredObjs []*ast.D
142141
// if we're trying to find the a definition which is an index,
143142
// we need to find it from itself, meaning that we need to create a stack
144143
// from the index's target and search from there
145-
rootNode, _, _ := vm.ImportAST("", fieldNode.LocRange.FileName)
144+
rootNode, _, _ := p.vm.ImportAST("", fieldNode.LocRange.FileName)
146145
stack, _ := FindNodeByPosition(rootNode, fieldNode.Target.Loc().Begin)
147146
if stack != nil {
148147
additionalIndexList := append(nodestack.NewNodeStack(fieldNode).BuildIndexList(), indexList...)
149-
result, _ := FindRangesFromIndexList(stack, additionalIndexList, vm, partialMatchFields)
148+
result, _ := p.FindRangesFromIndexList(stack, additionalIndexList, partialMatchFields)
150149
if len(result) > 0 {
151150
return result, err
152151
}
@@ -157,7 +156,7 @@ func extractObjectRangesFromDesugaredObjs(vm *jsonnet.VM, desugaredObjs []*ast.D
157156
desugaredObjs = append(desugaredObjs, findChildDesugaredObject(fieldNode.Body))
158157
case *ast.Import:
159158
filename := fieldNode.File.Value
160-
newObjs := FindTopLevelObjectsInFile(vm, filename, string(fieldNode.Loc().File.DiagnosticFileName))
159+
newObjs := p.FindTopLevelObjectsInFile(filename, string(fieldNode.Loc().File.DiagnosticFileName))
161160
desugaredObjs = append(desugaredObjs, newObjs...)
162161
}
163162
i++
@@ -177,13 +176,13 @@ func flattenBinary(node ast.Node) []ast.Node {
177176
// unpackFieldNodes extracts nodes from fields
178177
// - Binary nodes. A field could be either in the left or right side of the binary
179178
// - Self nodes. We want the object self refers to, not the self node itself
180-
func unpackFieldNodes(vm *jsonnet.VM, fields []*ast.DesugaredObjectField) ([]ast.Node, error) {
179+
func (p *Processor) unpackFieldNodes(fields []*ast.DesugaredObjectField) ([]ast.Node, error) {
181180
var fieldNodes []ast.Node
182181
for _, foundField := range fields {
183182
switch fieldNode := foundField.Body.(type) {
184183
case *ast.Self:
185184
filename := fieldNode.LocRange.FileName
186-
rootNode, _, _ := vm.ImportAST("", filename)
185+
rootNode, _, _ := p.vm.ImportAST("", filename)
187186
tmpStack, err := FindNodeByPosition(rootNode, fieldNode.LocRange.Begin)
188187
if err != nil {
189188
return nil, err
@@ -220,7 +219,6 @@ func findObjectFieldsInObject(objectNode *ast.DesugaredObject, index string, par
220219

221220
var matchingFields []*ast.DesugaredObjectField
222221
for _, field := range objectNode.Fields {
223-
field := field
224222
literalString, isString := field.Name.(*ast.LiteralString)
225223
if !isString {
226224
continue
@@ -253,8 +251,8 @@ func findChildDesugaredObject(node ast.Node) *ast.DesugaredObject {
253251

254252
// FindVarReference finds the object that the variable is referencing
255253
// To do so, we get the stack where the var is used and search that stack for the var's definition
256-
func FindVarReference(varNode *ast.Var, vm *jsonnet.VM) (ast.Node, error) {
257-
varFileNode, _, _ := vm.ImportAST("", varNode.LocRange.FileName)
254+
func (p *Processor) FindVarReference(varNode *ast.Var) (ast.Node, error) {
255+
varFileNode, _, _ := p.vm.ImportAST("", varNode.LocRange.FileName)
258256
varStack, err := FindNodeByPosition(varFileNode, varNode.Loc().Begin)
259257
if err != nil {
260258
return nil, fmt.Errorf("got the following error when finding the bind for %s: %w", varNode.Id, err)

pkg/ast/processing/processor.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package processing
2+
3+
import (
4+
"github.com/google/go-jsonnet"
5+
"github.com/grafana/jsonnet-language-server/pkg/cache"
6+
)
7+
8+
type Processor struct {
9+
cache *cache.Cache
10+
vm *jsonnet.VM
11+
}
12+
13+
func NewProcessor(cache *cache.Cache, vm *jsonnet.VM) *Processor {
14+
return &Processor{
15+
cache: cache,
16+
vm: vm,
17+
}
18+
}

pkg/ast/processing/top_level_objects.go

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
package processing
22

33
import (
4-
"github.com/google/go-jsonnet"
54
"github.com/google/go-jsonnet/ast"
65
"github.com/grafana/jsonnet-language-server/pkg/nodestack"
76
log "github.com/sirupsen/logrus"
87
)
98

10-
var fileTopLevelObjectsCache = make(map[string][]*ast.DesugaredObject)
11-
12-
func FindTopLevelObjectsInFile(vm *jsonnet.VM, filename, importedFrom string) []*ast.DesugaredObject {
13-
cacheKey := importedFrom + ":" + filename
14-
if _, ok := fileTopLevelObjectsCache[cacheKey]; !ok {
15-
rootNode, _, _ := vm.ImportAST(importedFrom, filename)
16-
fileTopLevelObjectsCache[cacheKey] = FindTopLevelObjects(nodestack.NewNodeStack(rootNode), vm)
9+
func (p *Processor) FindTopLevelObjectsInFile(filename, importedFrom string) []*ast.DesugaredObject {
10+
v, ok := p.cache.GetTopLevelObject(filename, importedFrom)
11+
if !ok {
12+
rootNode, _, _ := p.vm.ImportAST(importedFrom, filename)
13+
v = p.FindTopLevelObjects(nodestack.NewNodeStack(rootNode))
14+
p.cache.PutTopLevelObject(filename, importedFrom, v)
1715
}
18-
19-
return fileTopLevelObjectsCache[cacheKey]
16+
return v
2017
}
2118

2219
// Find all ast.DesugaredObject's from NodeStack
23-
func FindTopLevelObjects(stack *nodestack.NodeStack, vm *jsonnet.VM) []*ast.DesugaredObject {
20+
func (p *Processor) FindTopLevelObjects(stack *nodestack.NodeStack) []*ast.DesugaredObject {
2421
var objects []*ast.DesugaredObject
2522
for !stack.IsEmpty() {
2623
curr := stack.Pop()
@@ -34,7 +31,7 @@ func FindTopLevelObjects(stack *nodestack.NodeStack, vm *jsonnet.VM) []*ast.Desu
3431
stack.Push(curr.Body)
3532
case *ast.Import:
3633
filename := curr.File.Value
37-
rootNode, _, _ := vm.ImportAST(string(curr.Loc().File.DiagnosticFileName), filename)
34+
rootNode, _, _ := p.vm.ImportAST(string(curr.Loc().File.DiagnosticFileName), filename)
3835
stack.Push(rootNode)
3936
case *ast.Index:
4037
indexValue, indexIsString := curr.Index.(*ast.LiteralString)
@@ -45,7 +42,7 @@ func FindTopLevelObjects(stack *nodestack.NodeStack, vm *jsonnet.VM) []*ast.Desu
4542
var container ast.Node
4643
// If our target is a var, the container for the index is the var ref
4744
if varTarget, targetIsVar := curr.Target.(*ast.Var); targetIsVar {
48-
ref, err := FindVarReference(varTarget, vm)
45+
ref, err := p.FindVarReference(varTarget)
4946
if err != nil {
5047
log.WithError(err).Errorf("Error finding var reference, ignoring this node")
5148
continue
@@ -62,7 +59,7 @@ func FindTopLevelObjects(stack *nodestack.NodeStack, vm *jsonnet.VM) []*ast.Desu
6259
if containerObj, containerIsObj := container.(*ast.DesugaredObject); containerIsObj {
6360
possibleObjects = []*ast.DesugaredObject{containerObj}
6461
} else if containerImport, containerIsImport := container.(*ast.Import); containerIsImport {
65-
possibleObjects = FindTopLevelObjectsInFile(vm, containerImport.File.Value, string(containerImport.Loc().File.DiagnosticFileName))
62+
possibleObjects = p.FindTopLevelObjectsInFile(containerImport.File.Value, string(containerImport.Loc().File.DiagnosticFileName))
6663
}
6764

6865
for _, obj := range possibleObjects {
@@ -71,7 +68,7 @@ func FindTopLevelObjects(stack *nodestack.NodeStack, vm *jsonnet.VM) []*ast.Desu
7168
}
7269
}
7370
case *ast.Var:
74-
varReference, err := FindVarReference(curr, vm)
71+
varReference, err := p.FindVarReference(curr)
7572
if err != nil {
7673
log.WithError(err).Errorf("Error finding var reference, ignoring this node")
7774
continue

pkg/server/cache.go renamed to pkg/cache/cache.go

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,74 @@
1-
package server
1+
package cache
22

33
import (
44
"errors"
55
"fmt"
66
"os"
7+
"path/filepath"
78
"strings"
89
"sync"
910

1011
"github.com/google/go-jsonnet/ast"
1112
"github.com/jdbaldry/go-language-server-protocol/lsp/protocol"
1213
)
1314

14-
type document struct {
15+
type Document struct {
1516
// From DidOpen and DidChange
16-
item protocol.TextDocumentItem
17+
Item protocol.TextDocumentItem
1718

1819
// Contains the last successfully parsed AST. If doc.err is not nil, it's out of date.
19-
ast ast.Node
20-
linesChangedSinceAST map[int]bool
20+
AST ast.Node
21+
LinesChangedSinceAST map[int]bool
2122

2223
// From diagnostics
23-
val string
24-
err error
25-
diagnostics []protocol.Diagnostic
24+
Val string
25+
Err error
26+
Diagnostics []protocol.Diagnostic
2627
}
2728

28-
// newCache returns a document cache.
29-
func newCache() *cache {
30-
return &cache{
31-
mu: sync.RWMutex{},
32-
docs: make(map[protocol.DocumentURI]*document),
33-
diagQueue: make(map[protocol.DocumentURI]struct{}),
34-
}
29+
// Cache caches documents.
30+
type Cache struct {
31+
mu sync.RWMutex
32+
docs map[protocol.DocumentURI]*Document
33+
topLevelObjects map[string][]*ast.DesugaredObject
3534
}
3635

37-
// cache caches documents.
38-
type cache struct {
39-
mu sync.RWMutex
40-
docs map[protocol.DocumentURI]*document
41-
42-
diagMutex sync.RWMutex
43-
diagQueue map[protocol.DocumentURI]struct{}
44-
diagRunning sync.Map
36+
// New returns a document cache.
37+
func New() *Cache {
38+
return &Cache{
39+
mu: sync.RWMutex{},
40+
docs: make(map[protocol.DocumentURI]*Document),
41+
topLevelObjects: make(map[string][]*ast.DesugaredObject),
42+
}
4543
}
4644

47-
// put adds or replaces a document in the cache.
48-
func (c *cache) put(new *document) error {
45+
// Put adds or replaces a document in the cache.
46+
func (c *Cache) Put(new *Document) error {
4947
c.mu.Lock()
5048
defer c.mu.Unlock()
5149

52-
uri := new.item.URI
50+
uri := new.Item.URI
5351
if old, ok := c.docs[uri]; ok {
54-
if old.item.Version > new.item.Version {
52+
if old.Item.Version > new.Item.Version {
5553
return errors.New("newer version of the document is already in the cache")
5654
}
5755
}
5856
c.docs[uri] = new
5957

58+
// Invalidate the TopLevelObject cache
59+
for k := range c.topLevelObjects {
60+
if strings.HasSuffix(k, filepath.Base(uri.SpanURI().Filename())) {
61+
delete(c.topLevelObjects, k)
62+
}
63+
}
64+
6065
return nil
6166
}
6267

63-
// get retrieves a document from the cache.
64-
func (c *cache) get(uri protocol.DocumentURI) (*document, error) {
65-
c.mu.Lock()
66-
defer c.mu.Unlock()
68+
// Get retrieves a document from the cache.
69+
func (c *Cache) Get(uri protocol.DocumentURI) (*Document, error) {
70+
c.mu.RLock()
71+
defer c.mu.RUnlock()
6772

6873
doc, ok := c.docs[uri]
6974
if !ok {
@@ -73,11 +78,11 @@ func (c *cache) get(uri protocol.DocumentURI) (*document, error) {
7378
return doc, nil
7479
}
7580

76-
func (c *cache) getContents(uri protocol.DocumentURI, position protocol.Range) (string, error) {
81+
func (c *Cache) GetContents(uri protocol.DocumentURI, position protocol.Range) (string, error) {
7782
text := ""
78-
doc, err := c.get(uri)
83+
doc, err := c.Get(uri)
7984
if err == nil {
80-
text = doc.item.Text
85+
text = doc.Item.Text
8186
} else {
8287
// Read the file from disk (TODO: cache this)
8388
bytes, err := os.ReadFile(uri.SpanURI().Filename())
@@ -118,3 +123,20 @@ func (c *cache) getContents(uri protocol.DocumentURI, position protocol.Range) (
118123

119124
return contentBuilder.String(), nil
120125
}
126+
127+
func (c *Cache) GetTopLevelObject(filename, importedFrom string) ([]*ast.DesugaredObject, bool) {
128+
c.mu.RLock()
129+
defer c.mu.RUnlock()
130+
131+
cacheKey := importedFrom + ":" + filename
132+
v, ok := c.topLevelObjects[cacheKey]
133+
return v, ok
134+
}
135+
136+
func (c *Cache) PutTopLevelObject(filename, importedFrom string, objects []*ast.DesugaredObject) {
137+
c.mu.Lock()
138+
defer c.mu.Unlock()
139+
140+
cacheKey := importedFrom + ":" + filename
141+
c.topLevelObjects[cacheKey] = objects
142+
}

0 commit comments

Comments
 (0)