diff --git a/go.mod b/go.mod index 72ae0776b5..5a05318d21 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,7 @@ require ( github.com/tilt-dev/go-get v0.2.2 github.com/tilt-dev/localregistry-go v0.0.0-20200615231835-07e386f4ebd7 github.com/tilt-dev/probe v0.3.1 - github.com/tilt-dev/starlark-lsp v0.0.0-20220415191241-c73ee55518d2 + github.com/tilt-dev/starlark-lsp v0.0.0-20220422161504-c53192ba4695 github.com/tilt-dev/tilt-apiserver v0.6.1 github.com/tilt-dev/wmclient v0.0.0-20201109174454-1839d0355fbc github.com/tonistiigi/fsutil v0.0.0-20210609172227-d72af97c0eaf diff --git a/go.sum b/go.sum index a9dca01cd3..3703da7d7e 100644 --- a/go.sum +++ b/go.sum @@ -1584,8 +1584,8 @@ github.com/tilt-dev/localregistry-go v0.0.0-20200615231835-07e386f4ebd7 h1:ysHGL github.com/tilt-dev/localregistry-go v0.0.0-20200615231835-07e386f4ebd7/go.mod h1:bZecZHy8tHzZWeF+MEQMugKbGhi9cF1A7tuaZNIxoDs= github.com/tilt-dev/probe v0.3.1 h1:PQhXSBkgcGBUU/eKt4vgAUKsAWWjBr2F53xNAc0E7zs= github.com/tilt-dev/probe v0.3.1/go.mod h1:F53NFbqblwu5oyIk2t+BPkswiboxuF8e5D3wbPnY4JA= -github.com/tilt-dev/starlark-lsp v0.0.0-20220415191241-c73ee55518d2 h1:ljcQwpOyVg7Mv8vUrPjTopfL0gl3AoIfq1swgYlC+8w= -github.com/tilt-dev/starlark-lsp v0.0.0-20220415191241-c73ee55518d2/go.mod h1:NxBSH9G8lSfoPERkaqqjCdZQEJOwAKFcnGTh2NzJMrc= +github.com/tilt-dev/starlark-lsp v0.0.0-20220422161504-c53192ba4695 h1:SlEEwdUbOJGQQZ+wxNDc+5z9KrEISxVmfKT0diQVQwY= +github.com/tilt-dev/starlark-lsp v0.0.0-20220422161504-c53192ba4695/go.mod h1:NxBSH9G8lSfoPERkaqqjCdZQEJOwAKFcnGTh2NzJMrc= github.com/tilt-dev/tilt-apiserver v0.6.1 h1:1lJYn6gjnOk5XXA1wsu5JARBnbhRJWCweGPme6dowLo= github.com/tilt-dev/tilt-apiserver v0.6.1/go.mod h1:sjyJCOj1VZlwJ9x8A1ESID7pRsrWIIgElWzkq9yjKEs= github.com/tilt-dev/wmclient v0.0.0-20201109174454-1839d0355fbc h1:wGkAoZhrvnmq93B4W2v+agiPl7xzqUaxXkxmKrwJ6bc= diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.go index a68c656b87..178cc6a148 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.go @@ -7,6 +7,7 @@ import ( "io/fs" "os" "path/filepath" + "reflect" "sort" "strings" @@ -21,21 +22,46 @@ import ( type Builtins struct { Functions map[string]query.Signature - Symbols []protocol.DocumentSymbol + Symbols []query.Symbol + Types map[string]query.Type + Methods map[string]query.Signature + Members []query.Symbol } //go:embed builtins.py var StarlarkBuiltins []byte +func mapKeys(m interface{}) []string { + val := reflect.ValueOf(m) + keys := val.MapKeys() + names := make([]string, len(keys)) + for i, k := range keys { + names[i] = k.String() + } + return names +} + +func symbolNames(ss []query.Symbol) []string { + names := make([]string, len(ss)) + for i, sym := range ss { + names[i] = sym.Name + } + return names +} + func NewBuiltins() *Builtins { return &Builtins{ Functions: make(map[string]query.Signature), - Symbols: []protocol.DocumentSymbol{}, + Symbols: []query.Symbol{}, + Types: make(map[string]query.Type), + Methods: make(map[string]query.Signature), + Members: []query.Symbol{}, } } func (b *Builtins) IsEmpty() bool { - return len(b.Functions) == 0 && len(b.Symbols) == 0 + return len(b.Functions) == 0 && len(b.Symbols) == 0 && + len(b.Methods) == 0 && len(b.Members) == 0 } func (b *Builtins) Update(other *Builtins) { @@ -47,24 +73,19 @@ func (b *Builtins) Update(other *Builtins) { if len(other.Symbols) > 0 { b.Symbols = append(b.Symbols, other.Symbols...) } -} - -func (b *Builtins) FunctionNames() []string { - names := make([]string, len(b.Functions)) - i := 0 - for name := range b.Functions { - names[i] = name - i++ + if len(other.Types) > 0 { + for name, t := range other.Types { + b.Types[name] = t + } } - return names -} - -func (b *Builtins) SymbolNames() []string { - names := make([]string, len(b.Symbols)) - for i, sym := range b.Symbols { - names[i] = sym.Name + if len(other.Methods) > 0 { + for name, fn := range other.Methods { + b.Methods[name] = fn + } + } + if len(other.Members) > 0 { + b.Members = append(b.Members, other.Members...) } - return names } func WithBuiltinPaths(paths []string) AnalyzerOption { @@ -98,7 +119,7 @@ func WithStarlarkBuiltins() AnalyzerOption { return errors.Wrapf(err, "loading builtins from builtins.py") } analyzer.builtins.Update(&Builtins{ - Symbols: []protocol.DocumentSymbol{ + Symbols: []query.Symbol{ {Name: "False", Kind: protocol.SymbolKindBoolean}, {Name: "None", Kind: protocol.SymbolKindNull}, {Name: "True", Kind: protocol.SymbolKindBoolean}, @@ -118,11 +139,33 @@ func LoadBuiltinsFromSource(ctx context.Context, contents []byte, path string) ( doc := document.NewDocument(uri.File(path), contents, tree) functions := doc.Functions() symbols := doc.Symbols() + + types := query.Types(doc, tree.RootNode()) + typeMap := make(map[string]query.Type) + methodMap := make(map[string]query.Signature) + members := []query.Symbol{} + for _, t := range types { + typeMap[t.Name] = t + for _, method := range t.Methods { + methodMap[method.Name] = method + } + members = append(members, t.Members...) + } + doc.Close() + // NewDocument returns these symbols with a location of __init__.py, which isn't helpful to anyone + for i, s := range symbols { + s.Location = protocol.Location{} + symbols[i] = s + } + return &Builtins{ Functions: functions, Symbols: symbols, + Types: typeMap, + Methods: methodMap, + Members: members, }, nil } @@ -226,7 +269,7 @@ func copyBuiltinsToParent(mod, parentMod *Builtins, modName string) { parentMod.Functions[modName+"."+name] = fn } - children := []protocol.DocumentSymbol{} + children := []query.Symbol{} for _, sym := range mod.Symbols { var kind protocol.SymbolKind switch sym.Kind { @@ -252,13 +295,18 @@ func copyBuiltinsToParent(mod, parentMod *Builtins, modName string) { if existingIndex >= 0 { parentMod.Symbols[existingIndex].Children = append(parentMod.Symbols[existingIndex].Children, children...) } else { - parentMod.Symbols = append(parentMod.Symbols, protocol.DocumentSymbol{ + parentMod.Symbols = append(parentMod.Symbols, query.Symbol{ Name: modName, Kind: protocol.SymbolKindVariable, Children: children, }) } } + parentMod.Update(&Builtins{ + Types: mod.Types, + Methods: mod.Methods, + Members: mod.Members, + }) } func LoadBuiltins(ctx context.Context, path string) (*Builtins, error) { diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.py b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.py index 2c6944a4cd..4bafb87c4f 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.py +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/builtins.py @@ -1,35 +1,35 @@ # This file was generated by `make builtins` based on the spec at: # https://raw.githubusercontent.com/bazelbuild/starlark/master/spec.md -def any(x): +def any(x) -> bool: """`any(x)` returns `True` if any element of the iterable sequence x is true. If the iterable is empty, it returns `False`.""" pass -def all(x): +def all(x) -> bool: """`all(x)` returns `False` if any element of the iterable sequence x is false. If the iterable is empty, it returns `True`.""" pass -def bool(x): +def bool(x) -> bool: """`bool(x)` interprets `x` as a Boolean value---`True` or `False`. With no argument, `bool()` returns `False`.""" pass -def bytes(x): +def bytes(x) -> Bytes: """`bytes(x)` converts its argument to a `bytes`.""" pass -def dict(): +def dict() -> Dict: """`dict` creates a dictionary. It accepts up to one positional argument, which is interpreted as an iterable of two-element sequences (pairs), each specifying a key/value pair in the resulting dictionary.""" pass -def dir(x): +def dir(x) -> List[String]: """`dir(x)` returns a new sorted list of the names of the attributes (fields and methods) of its operand. The attributes of a value `x` are the names `f` such that `x.f` is a valid expression.""" pass -def enumerate(x): +def enumerate(x) -> List[Tuple[int, any]]: """`enumerate(x)` returns a list of (index, value) pairs, each containing successive values of the iterable sequence xand the index of the value within the sequence.""" pass -def float(x): +def float(x) -> float: """`float(x)` interprets its argument as a floating-point number.""" pass @@ -41,23 +41,23 @@ def getattr(x, name): """`getattr(x, name[, default])` returns the value of the attribute (field or method) of x named `name` if it exists. If not, it either returns `default` (if specified) or raises an error.""" pass -def hasattr(x, name): +def hasattr(x, name) -> bool: """`hasattr(x, name)` reports whether x has an attribute (field or method) named `name`.""" pass -def hash(x): +def hash(x) -> int: """`hash(x)` returns an integer hash of a string or bytes x such that two equal values have the same hash. In other words `x == y` implies `hash(x) == hash(y)`. Any other type of argument in an error, even if it is suitable as the key of a dict.""" pass -def int(x): +def int(x) -> int: """`int(x[, base])` interprets its argument as an integer.""" pass -def len(x): +def len(x) -> int: """`len(x)` returns the number of elements in its argument.""" pass -def list(): +def list() -> List: """`list` constructs a list.""" pass @@ -73,23 +73,23 @@ def print(*args, sep=" "): """`print(*args, sep=" ")` prints its arguments, followed by a newline. Arguments are formatted as if by `str(x)` and separated with a space, unless an alternative separator is specified by a `sep` named argument.""" pass -def range(): +def range() -> List[int]: """`range` returns an immutable sequence of integers defined by the specified interval and stride.""" pass -def repr(x): +def repr(x) -> String: """`repr(x)` formats its argument as a string.""" pass -def reversed(x): +def reversed(x) -> List: """`reversed(x)` returns a new list containing the elements of the iterable sequence x in reverse order.""" pass -def sorted(x): +def sorted(x) -> List: """`sorted(x)` returns a new list containing the elements of the iterable sequence x, in sorted order. The sort algorithm is stable.""" pass -def str(x): +def str(x) -> String: """`str(x)` formats its argument as a string.""" pass @@ -97,11 +97,11 @@ def tuple(x): """`tuple(x)` returns a tuple containing the elements of the iterable x.""" pass -def type(x): +def type(x) -> String: """`type(x)` returns a string describing the type of its operand.""" pass -def zip(): +def zip() -> List: """`zip()` returns a new list of n-tuples formed from corresponding elements of each of the n iterable sequences provided as arguments to `zip`. That is, the first tuple contains the first element of each of the sequences, the second element contains the second element of each of the sequences, and so on. The result list is only as long as the shortest of the input sequences.""" pass @@ -115,11 +115,11 @@ def get(self, key): """`D.get(key[, default])` returns the dictionary value corresponding to the given key. If the dictionary contains no such value, `get` returns `None`, or the value of the optional `default` parameter if present.""" pass - def items(self): + def items(self) -> List: """`D.items()` returns a new list of key/value pairs, one per element in dictionary D, in the same order as they would be returned by a `for` loop.""" pass - def keys(self): + def keys(self) -> List: """`D.keys()` returns a new list containing the keys of dictionary D, in the same order as they would be returned by a `for` loop.""" pass @@ -135,32 +135,32 @@ def setdefault(self, key): """`D.setdefault(key[, default])` returns the dictionary value corresponding to the given key. If the dictionary contains no such value, `setdefault`, like `get`, returns `None` or the value of the optional `default` parameter if present; `setdefault` additionally inserts the new key/value entry into the dictionary.""" pass - def update(self): + def update(self) -> None: """`D.update([pairs][, name=value[, ...])` makes a sequence of key/value insertions into dictionary D, then returns `None.`""" pass - def values(self): + def values(self) -> List: """`D.values()` returns a new list containing the dictionary's values, in the same order as they would be returned by a `for` loop over the dictionary.""" pass class List: - def append(self, x): + def append(self, x) -> None: """`L.append(x)` appends `x` to the list L, and returns `None`.""" pass - def clear(self): + def clear(self) -> None: """`L.clear()` removes all the elements of the list L and returns `None`. It fails if the list is frozen or if there are active iterators.""" pass - def extend(self, x): + def extend(self, x) -> None: """`L.extend(x)` appends the elements of `x`, which must be iterable, to the list L, and returns `None`.""" pass - def index(self, x): + def index(self, x) -> int: """`L.index(x[, start[, end]])` finds `x` within the list L and returns its index.""" pass - def insert(self, i, x): + def insert(self, i, x) -> None: """`L.insert(i, x)` inserts the value `x` in the list L at index `i`, moving higher-numbered elements along by one. It returns `None`.""" pass @@ -168,16 +168,16 @@ def pop(self): """`L.pop([index])` removes and returns the last element of the list L, or, if the optional index is provided, at that index.""" pass - def remove(self, x): + def remove(self, x) -> None: """`L.remove(x)` removes the first occurrence of the value `x` from the list L, and returns `None`.""" pass class String: - def capitalize(self): + def capitalize(self) -> String: """`S.capitalize()` returns a copy of string S, where the first character (if any) is converted to uppercase; all other characters are converted to lowercase.""" pass - def count(self, sub): + def count(self, sub) -> int: """`S.count(sub[, start[, end]])` returns the number of occurrences of `sub` within the string S, or, if the optional substring indices `start` and `end` are provided, within the designated substring of S. They are interpreted according to Starlark's [indexing conventions](#indexing).""" pass @@ -185,59 +185,59 @@ def elems(self): """`S.elems()` returns an opaque iterable value containing successive 1-element substrings of S. Its type is `"string.elems"`, and its string representation is of the form `"...".elems()`.""" pass - def endswith(self, suffix): + def endswith(self, suffix) -> bool: """`S.endswith(suffix[, start[, end]])` reports whether the string `S[start:end]` has the specified suffix.""" pass - def find(self, sub): + def find(self, sub) -> int: """`S.find(sub[, start[, end]])` returns the index of the first occurrence of the substring `sub` within S.""" pass - def format(self, *args, **kwargs): + def format(self, *args, **kwargs) -> String: """`S.format(*args, **kwargs)` returns a version of the format string S in which bracketed portions `{...}` are replaced by arguments from `args` and `kwargs`.""" pass - def index(self, sub): + def index(self, sub) -> int: """`S.index(sub[, start[, end]])` returns the index of the first occurrence of the substring `sub` within S, like `S.find`, except that if the substring is not found, the operation fails.""" pass - def isalnum(self): + def isalnum(self) -> bool: """`S.isalnum()` reports whether the string S is non-empty and consists only Unicode letters and digits.""" pass - def isalpha(self): + def isalpha(self) -> bool: """`S.isalpha()` reports whether the string S is non-empty and consists only of Unicode letters.""" pass - def isdigit(self): + def isdigit(self) -> bool: """`S.isdigit()` reports whether the string S is non-empty and consists only of Unicode digits.""" pass - def islower(self): + def islower(self) -> bool: """`S.islower()` reports whether the string S contains at least one cased Unicode letter, and all such letters are lowercase.""" pass - def isspace(self): + def isspace(self) -> bool: """`S.isspace()` reports whether the string S is non-empty and consists only of Unicode spaces.""" pass - def istitle(self): + def istitle(self) -> bool: """`S.istitle()` reports whether the string S contains at least one cased Unicode letter, and all such letters that begin a word are in title case.""" pass - def isupper(self): + def isupper(self) -> bool: """`S.isupper()` reports whether the string S contains at least one cased Unicode letter, and all such letters are uppercase.""" pass - def join(self, iterable): + def join(self, iterable) -> String: """`S.join(iterable)` returns the string formed by concatenating each element of its argument, with a copy of the string S between successive elements. The argument must be an iterable whose elements are strings.""" pass - def lower(self): + def lower(self) -> String: """`S.lower()` returns a copy of the string S with letters converted to lowercase.""" pass - def lstrip(self): + def lstrip(self) -> String: """`S.lstrip([cutset])` returns a copy of the string S with leading whitespace removed.""" pass @@ -245,23 +245,23 @@ def partition(self, x): """`S.partition(x)` splits string S into three parts and returns them as a tuple: the portion before the first occurrence of string `x`, `x` itself, and the portion following it. If S does not contain `x`, `partition` returns `(S, "", "")`.""" pass - def removeprefix(self, x): + def removeprefix(self, x) -> String: """`S.removeprefix(x)` removes the prefix `x` from the string S at most once, and returns the rest of the string. If the prefix string is not found then it returns the original string.""" pass - def removesuffix(self, x): + def removesuffix(self, x) -> String: """`S.removesuffix(x)` removes the suffix `x` from the string S at most once, and returns the rest of the string. If the suffix string is not found then it returns the original string.""" pass - def replace(self, old, new): + def replace(self, old, new) -> String: """`S.replace(old, new[, count])` returns a copy of string S with all occurrences of substring `old` replaced by `new`. If the optional argument `count`, which must be an `int`, is non-negative, it specifies a maximum number of occurrences to replace.""" pass - def rfind(self, sub): + def rfind(self, sub) -> int: """`S.rfind(sub[, start[, end]])` returns the index of the substring `sub` within S, like `S.find`, except that `rfind` returns the index of the substring's _last_ occurrence.""" pass - def rindex(self, sub): + def rindex(self, sub) -> int: """`S.rindex(sub[, start[, end]])` returns the index of the substring `sub` within S, like `S.index`, except that `rindex` returns the index of the substring's _last_ occurrence.""" pass @@ -269,35 +269,35 @@ def rpartition(self, x): """`S.rpartition(x)` is like `partition`, but splits `S` at the last occurrence of `x`.""" pass - def rsplit(self): + def rsplit(self) -> List[String]: """`S.rsplit([sep[, maxsplit]])` splits a string into substrings like `S.split`, except that when a maximum number of splits is specified, `rsplit` chooses the rightmost splits.""" pass - def rstrip(self): + def rstrip(self) -> String: """`S.rstrip([cutset])` returns a copy of the string S with trailing whitespace removed.""" pass - def split(self): + def split(self) -> List[String]: """`S.split([sep [, maxsplit]])` returns the list of substrings of S, splitting at occurrences of the delimiter string `sep`.""" pass - def splitlines(self): + def splitlines(self) -> List[String]: """`S.splitlines([keepends])` returns a list whose elements are the successive lines of S, that is, the strings formed by splitting S at line terminators (currently assumed to be `\n`, `\r` and `\r\n`, regardless of platform).""" pass - def startswith(self, prefix): + def startswith(self, prefix) -> bool: """`S.startswith(prefix[, start[, end]])` reports whether the string `S[start:end]` has the specified prefix.""" pass - def strip(self): + def strip(self) -> String: """`S.strip([cutset])` returns a copy of the string S with leading and trailing whitespace removed.""" pass - def title(self): + def title(self) -> String: """`S.title()` returns a copy of the string S with letters converted to titlecase.""" pass - def upper(self): + def upper(self) -> String: """`S.upper()` returns a copy of the string S with letters converted to uppercase.""" pass diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/completion.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/completion.go index 6c04c64427..4358fa8c74 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/completion.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/completion.go @@ -13,20 +13,20 @@ import ( "github.com/tilt-dev/starlark-lsp/pkg/query" ) -func SymbolMatching(symbols []protocol.DocumentSymbol, name string) protocol.DocumentSymbol { +func SymbolMatching(symbols []query.Symbol, name string) query.Symbol { for _, sym := range symbols { if sym.Name == name { return sym } } - return protocol.DocumentSymbol{} + return query.Symbol{} } -func SymbolsStartingWith(symbols []protocol.DocumentSymbol, prefix string) []protocol.DocumentSymbol { +func SymbolsStartingWith(symbols []query.Symbol, prefix string) []query.Symbol { if prefix == "" { return symbols } - result := []protocol.DocumentSymbol{} + result := []query.Symbol{} for _, sym := range symbols { if strings.HasPrefix(sym.Name, prefix) { result = append(result, sym) @@ -51,7 +51,7 @@ func ToCompletionItemKind(k protocol.SymbolKind) protocol.CompletionItemKind { func (a *Analyzer) Completion(doc document.Document, pos protocol.Position) *protocol.CompletionList { pt := query.PositionToPoint(pos) nodes, ok := a.nodesAtPointForCompletion(doc, pt) - symbols := []protocol.DocumentSymbol{} + symbols := []query.Symbol{} if ok { symbols = a.completeExpression(doc, nodes, pt) @@ -85,7 +85,7 @@ func (a *Analyzer) Completion(doc document.Document, pos protocol.Position) *pro return completionList } -func (a *Analyzer) completeExpression(doc document.Document, nodes []*sitter.Node, pt sitter.Point) []protocol.DocumentSymbol { +func (a *Analyzer) completeExpression(doc document.Document, nodes []*sitter.Node, pt sitter.Point) []query.Symbol { var nodeAtPoint *sitter.Node if len(nodes) > 0 { nodeAtPoint = nodes[len(nodes)-1] @@ -123,6 +123,14 @@ func (a *Analyzer) completeExpression(doc document.Document, nodes []*sitter.Nod } } + if len(symbols) == 0 { + lastId := identifiers[len(identifiers)-1] + expr := a.findAttrObjectExpression(nodes, sitter.Point{Row: pt.Row, Column: pt.Column - uint32(len(lastId))}) + if expr != nil { + symbols = append(symbols, SymbolsStartingWith(a.availableMembers(doc, expr), lastId)...) + } + } + return symbols } @@ -132,11 +140,11 @@ func (a *Analyzer) completeExpression(doc document.Document, nodes []*sitter.Nod // level (document symbols), because the document already has those computed // - Add document symbols // - Add builtins -func (a *Analyzer) availableSymbols(doc document.Document, nodeAtPoint *sitter.Node, pt sitter.Point) []protocol.DocumentSymbol { - symbols := []protocol.DocumentSymbol{} +func (a *Analyzer) availableSymbols(doc document.Document, nodeAtPoint *sitter.Node, pt sitter.Point) []query.Symbol { + symbols := []query.Symbol{} if nodeAtPoint != nil { - if fnName, args := keywordArgContext(doc, nodeAtPoint, pt); fnName != "" { - if fn, ok := a.signatureInformation(doc, nodeAtPoint, fnName); ok { + if args := keywordArgContext(doc, nodeAtPoint, pt); args.fnName != "" { + if fn, ok := a.signatureInformation(doc, nodeAtPoint, args); ok { symbols = append(symbols, a.keywordArgSymbols(fn, args)...) } } @@ -259,15 +267,15 @@ func (a *Analyzer) leafNodesForCompletion(doc document.Document, node *sitter.No return nodes, true } -func (a *Analyzer) keywordArgSymbols(fn query.Signature, args callArguments) []protocol.DocumentSymbol { - symbols := []protocol.DocumentSymbol{} +func (a *Analyzer) keywordArgSymbols(fn query.Signature, args callWithArguments) []query.Symbol { + symbols := []query.Symbol{} for i, param := range fn.Params { if i < int(args.positional) { continue } kwarg := param.Name if used := args.keywords[kwarg]; !used { - symbols = append(symbols, protocol.DocumentSymbol{ + symbols = append(symbols, query.Symbol{ Name: kwarg + "=", Detail: param.Content, Kind: protocol.SymbolKindVariable, @@ -277,12 +285,137 @@ func (a *Analyzer) keywordArgSymbols(fn query.Signature, args callArguments) []p return symbols } -func keywordArgContext(doc document.Document, node *sitter.Node, pt sitter.Point) (fnName string, args callArguments) { +// Find the object part of an attribute expression that has a dot '.' immediately before the given point. +func (a *Analyzer) findAttrObjectExpression(nodes []*sitter.Node, pt sitter.Point) *sitter.Node { + if pt.Column == 0 { + return nil + } + + var dot *sitter.Node + searchRange := sitter.Range{StartPoint: sitter.Point{Row: pt.Row, Column: pt.Column - 1}, EndPoint: pt} + var parentNode *sitter.Node + for i := len(nodes) - 1; i >= 0; i-- { + parentNode = nodes[i] + dot = query.FindChildNode(parentNode, func(n *sitter.Node) int { + if query.PointBeforeOrEqual(n.EndPoint(), searchRange.StartPoint) { + return -1 + } + if n.StartPoint() == searchRange.StartPoint && + n.EndPoint() == searchRange.EndPoint && + n.Type() == "." { + return 0 + } + if query.PointBeforeOrEqual(n.StartPoint(), searchRange.StartPoint) && + query.PointAfterOrEqual(n.EndPoint(), searchRange.EndPoint) { + return 0 + } + return 1 + }) + if dot != nil { + break + } + } + if dot != nil { + expr := parentNode.PrevSibling() + for n := dot; n != parentNode; n = n.Parent() { + if n.PrevSibling() != nil { + expr = n.PrevSibling() + break + } + } + + if expr != nil { + a.logger.Debug("dot completion", + zap.String("dot", dot.String()), + zap.String("expr", expr.String())) + return expr + } + } + return nil +} + +// Perform some rudimentary type analysis to determine the Starlark type of the node +func (a *Analyzer) analyzeType(doc document.Document, node *sitter.Node) string { + if node == nil { + return "" + } + switch node.Type() { + case query.NodeTypeString: + return "String" + case query.NodeTypeDictionary: + return "Dict" + case query.NodeTypeList: + return "List" + case query.NodeTypeIdentifier: + sym, found := a.FindDefinition(doc, node, doc.Content(node)) + if found { + switch sym.Kind { + case protocol.SymbolKindString: + return "String" + case protocol.SymbolKindObject: + return "Dict" + case protocol.SymbolKindArray: + return "List" + } + } + case query.NodeTypeCall: + fnName := doc.Content(node.ChildByFieldName("function")) + args := node.ChildByFieldName("arguments") + sig, found := a.signatureInformation(doc, node, callWithArguments{fnName: fnName, argsNode: args}) + if found && sig.ReturnType != "" { + switch strings.ToLower(sig.ReturnType) { + case "str", "string": + return "String" + case "list": + return "List" + case "dict": + return "Dict" + default: + return sig.ReturnType + } + } + } + return "" +} + +func (a *Analyzer) availableMembers(doc document.Document, node *sitter.Node) []query.Symbol { + if t := a.analyzeType(doc, node); t != "" { + if class, found := a.builtins.Types[t]; found { + return class.Members + } + switch t { + case "None", "bool", "int", "float": + return []query.Symbol{} + } + } + return a.builtins.Members +} + +func (a *Analyzer) FindDefinition(doc document.Document, node *sitter.Node, name string) (query.Symbol, bool) { + for _, sym := range query.SymbolsInScope(doc, node) { + if sym.Name == name { + return sym, true + } + } + for _, sym := range doc.Symbols() { + if sym.Name == name { + return sym, true + } + } + for _, sym := range a.builtins.Symbols { + if sym.Name == name { + return sym, true + } + } + return query.Symbol{}, false +} + +func keywordArgContext(doc document.Document, node *sitter.Node, pt sitter.Point) callWithArguments { if node.Type() == "=" || query.HasAncestor(node, func(anc *sitter.Node) bool { return anc.Type() == query.NodeTypeKeywordArgument }) { - return "", callArguments{} + return callWithArguments{} } return possibleCallInfo(doc, node, pt) } diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/definition.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/definition.go new file mode 100644 index 0000000000..5ea871a667 --- /dev/null +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/definition.go @@ -0,0 +1,31 @@ +package analysis + +import ( + "context" + + "go.lsp.dev/protocol" + + "github.com/tilt-dev/starlark-lsp/pkg/document" + "github.com/tilt-dev/starlark-lsp/pkg/query" +) + +func (a *Analyzer) Definition(ctx context.Context, doc document.Document, pos protocol.Position) ([]protocol.Location, error) { + symbol := a.SymbolAtPosition(doc, pos) + + // this might be because no matching symbol was found, or because it's a builtin with no location + if !symbol.HasLocation() { + return nil, nil + } + + pt := query.PositionToPoint(pos) + + // if pos is already on the definition, then don't return a navigation destination + // (it feels weird to get the navigate cursor and click and have it seemingly do nothing) + if symbol.Location.URI == doc.URI() && query.RangeContainsPoint(query.SitterRange(symbol.Location.Range), pt) { + return nil, nil + } + + return []protocol.Location{ + symbol.Location, + }, nil +} diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/hover.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/hover.go index 340d08b062..26648ad934 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/hover.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/hover.go @@ -16,18 +16,7 @@ func (a *Analyzer) Hover(ctx context.Context, doc document.Document, pos protoco return nil } - symbols := a.completeExpression(doc, nodes, pt) - var symbol protocol.DocumentSymbol - limit := nodes[len(nodes)-1].EndPoint() - identifiers := query.ExtractIdentifiers(doc, nodes, &limit) - if len(identifiers) == 0 { - return nil - } - for _, s := range symbols { - if s.Name == identifiers[len(identifiers)-1] { - symbol = s - } - } + symbol := a.SymbolAtPosition(doc, pos) if symbol.Name == "" { return nil diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/signature.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/signature.go index 7d2dc2be2b..fad202639e 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/signature.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/signature.go @@ -1,6 +1,8 @@ package analysis import ( + "strings" + sitter "github.com/smacker/go-tree-sitter" "go.lsp.dev/protocol" @@ -8,9 +10,10 @@ import ( "github.com/tilt-dev/starlark-lsp/pkg/query" ) -func (a *Analyzer) signatureInformation(doc document.Document, node *sitter.Node, fnName string) (query.Signature, bool) { +func (a *Analyzer) signatureInformation(doc document.Document, node *sitter.Node, args callWithArguments) (query.Signature, bool) { var sig query.Signature var found bool + fnName := args.fnName for n := node; n != nil && !query.IsModuleScope(doc, n); n = n.Parent() { sig, found = query.Function(doc, n, fnName) @@ -24,12 +27,39 @@ func (a *Analyzer) signatureInformation(doc document.Document, node *sitter.Node } if !found { - sig = a.builtins.Functions[fnName] + sig, found = a.builtins.Functions[fnName] + } + + if !found && strings.Contains(fnName, ".") { + ind := strings.LastIndex(fnName, ".") + fnName = fnName[ind+1:] + sig = a.builtins.Methods[fnName] + if sig.Name != "" { + meth, ok := a.checkForTypedMethod(doc, node, fnName, args) + if ok { + sig = meth + } + } } return sig, sig.Name != "" } +func (a *Analyzer) checkForTypedMethod(doc document.Document, node *sitter.Node, methodName string, args callWithArguments) (query.Signature, bool) { + afterDot := args.argsNode.StartPoint() + afterDot.Column -= uint32(len(methodName)) + if query.PointAfterOrEqual(node.StartPoint(), afterDot) { + node = node.Parent() + } + expr := a.findAttrObjectExpression([]*sitter.Node{node}, afterDot) + if t := a.analyzeType(doc, expr); t != "" { + if ty, ok := a.builtins.Types[t]; ok { + return ty.FindMethod(methodName) + } + } + return query.Signature{}, false +} + func (a *Analyzer) SignatureHelp(doc document.Document, pos protocol.Position) *protocol.SignatureHelp { pt := query.PositionToPoint(pos) node, ok := query.NodeAtPoint(doc, pt) @@ -37,13 +67,13 @@ func (a *Analyzer) SignatureHelp(doc document.Document, pos protocol.Position) * return nil } - fnName, args := possibleCallInfo(doc, node, pt) - if fnName == "" { + args := possibleCallInfo(doc, node, pt) + if args.fnName == "" { // avoid computing function defs return nil } - sig, ok := a.signatureInformation(doc, node, fnName) + sig, ok := a.signatureInformation(doc, node, args) if !ok { return nil } @@ -72,10 +102,12 @@ func (a *Analyzer) SignatureHelp(doc document.Document, pos protocol.Position) * } } -type callArguments struct { +type callWithArguments struct { + fnName string positional, total uint32 keywords map[string]bool currentKeyword string + argsNode *sitter.Node } // possibleCallInfo attempts to find the name of the function for a @@ -85,12 +117,14 @@ type callArguments struct { // (1) Current node is inside of a `call` // (2) Current node is inside of an ERROR block where first child is an // `identifier` -func possibleCallInfo(doc document.Document, node *sitter.Node, pt sitter.Point) (fnName string, args callArguments) { +func possibleCallInfo(doc document.Document, node *sitter.Node, pt sitter.Point) (args callWithArguments) { for n := node; n != nil; n = n.Parent() { if n.Type() == query.NodeTypeCall { - fnName = doc.Content(n.ChildByFieldName("function")) - args = possibleActiveParam(doc, n.ChildByFieldName("arguments").Child(0), pt) - return fnName, args + argsList := n.ChildByFieldName("arguments") + args = possibleActiveParam(doc, argsList.Child(0), pt) + args.fnName = doc.Content(n.ChildByFieldName("function")) + args.argsNode = argsList + return args } else if n.Type() == query.NodeTypeArgList { continue } else if n.HasError() { @@ -98,21 +132,25 @@ func possibleCallInfo(doc document.Document, node *sitter.Node, pt sitter.Point) // happen if the closing `)` is not (yet) present or if there's // something invalid going on within the args, e.g. `foo(x#)` possibleCall := n.NamedChild(0) - if possibleCall != nil && possibleCall.Type() == query.NodeTypeIdentifier { + if possibleCall != nil { possibleParen := possibleCall.NextSibling() if possibleParen != nil && possibleParen.Type() == "(" { - fnName = doc.Content(possibleCall) - args = possibleActiveParam(doc, possibleParen.NextSibling(), pt) - return fnName, args + switch possibleCall.Type() { + case query.NodeTypeIdentifier, query.NodeTypeAttribute: + args = possibleActiveParam(doc, possibleParen.NextSibling(), pt) + args.argsNode = possibleParen + args.fnName = doc.Content(possibleCall) + return args + } } } } } - return "", callArguments{} + return args } -func possibleActiveParam(doc document.Document, node *sitter.Node, pt sitter.Point) callArguments { - args := callArguments{keywords: make(map[string]bool)} +func possibleActiveParam(doc document.Document, node *sitter.Node, pt sitter.Point) callWithArguments { + args := callWithArguments{keywords: make(map[string]bool)} for n := node; n != nil; n = n.NextSibling() { inRange := query.PointBeforeOrEqual(n.StartPoint(), pt) && query.PointBeforeOrEqual(n.EndPoint(), pt) diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/symbol.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/symbol.go new file mode 100644 index 0000000000..b861c324a3 --- /dev/null +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/symbol.go @@ -0,0 +1,30 @@ +package analysis + +import ( + "go.lsp.dev/protocol" + + "github.com/tilt-dev/starlark-lsp/pkg/document" + "github.com/tilt-dev/starlark-lsp/pkg/query" +) + +func (a Analyzer) SymbolAtPosition(doc document.Document, pos protocol.Position) query.Symbol { + var result query.Symbol + pt := query.PositionToPoint(pos) + nodes, ok := a.nodesAtPointForCompletion(doc, pt) + if !ok { + return result + } + + limit := nodes[len(nodes)-1].EndPoint() + symbols := a.completeExpression(doc, nodes, limit) + identifiers := query.ExtractIdentifiers(doc, nodes, &limit) + if len(identifiers) == 0 { + return result + } + for _, s := range symbols { + if s.Name == identifiers[len(identifiers)-1] { + result = s + } + } + return result +} diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/test/os.py b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/test/os.py index 277fab3206..602e0dbfeb 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/test/os.py +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/analysis/test/os.py @@ -2,3 +2,8 @@ def getcwd(): pass + +class Link(): + url: str + def curl(self) -> str: + pass diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/document/document.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/document/document.go index c1e1e91b3d..104c563356 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/document/document.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/document/document.go @@ -34,9 +34,10 @@ type Document interface { Tree() *sitter.Tree Functions() map[string]query.Signature - Symbols() []protocol.DocumentSymbol + Symbols() []query.Symbol Diagnostics() []protocol.Diagnostic Loads() []LoadStatement + URI() uri.URI Copy() Document @@ -82,7 +83,7 @@ type document struct { tree *sitter.Tree functions map[string]query.Signature - symbols []protocol.DocumentSymbol + symbols []query.Symbol diagnostics []protocol.Diagnostic loads []LoadStatement } @@ -109,7 +110,7 @@ func (d *document) Functions() map[string]query.Signature { return d.functions } -func (d *document) Symbols() []protocol.DocumentSymbol { +func (d *document) Symbols() []query.Symbol { return d.symbols } @@ -121,6 +122,10 @@ func (d *document) Loads() []LoadStatement { return d.loads } +func (d *document) URI() uri.URI { + return d.uri +} + func (d *document) Close() { d.tree.Close() } @@ -135,7 +140,7 @@ func (d *document) Copy() Document { input: d.input, tree: d.tree.Copy(), functions: make(map[string]query.Signature), - symbols: append([]protocol.DocumentSymbol{}, d.symbols...), + symbols: append([]query.Symbol{}, d.symbols...), loads: append([]LoadStatement{}, d.loads...), diagnostics: append([]protocol.Diagnostic{}, d.diagnostics...), } @@ -188,14 +193,13 @@ func (d *document) followLoads(ctx context.Context, m *Manager, parseState Docum func (d *document) processLoad(dep Document, load LoadStatement) { fns := dep.Functions() - symMap := make(map[string]protocol.DocumentSymbol) + symMap := make(map[string]query.Symbol) for _, s := range dep.Symbols() { symMap[s.Name] = s } for _, ls := range load.Symbols { if sym, found := symMap[ls.Name]; found { sym.Name = ls.Alias - sym.Range = ls.Range d.symbols = append(d.symbols, sym) if f, ok := fns[ls.Name]; ok { d.functions[ls.Alias] = f diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/conv.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/conv.go index 441db0506b..209b422b64 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/conv.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/conv.go @@ -3,6 +3,7 @@ package query import ( "fmt" "strconv" + "strings" sitter "github.com/smacker/go-tree-sitter" "go.lsp.dev/protocol" @@ -102,3 +103,26 @@ func nodeTypeToSymbolKind(n *sitter.Node) protocol.SymbolKind { } return 0 } + +func pythonTypeToSymbolKind(doc DocumentContent, n *sitter.Node) protocol.SymbolKind { + // if the type has a subscript like 'List[str]', use 'List' as the type + if n.ChildCount() > 0 && n.Child(0).Type() == "subscript" { + n = n.Child(0).ChildByFieldName("value") + } + t := strings.ToLower(doc.Content(n)) + switch t { + case "str", "string", "bytes": + return protocol.SymbolKindString + case "list", "tuple": + return protocol.SymbolKindArray + case "callable": + return protocol.SymbolKindFunction + case "dict", "any": + return protocol.SymbolKindObject + case "int", "float": + return protocol.SymbolKindNumber + case "bool": + return protocol.SymbolKindBoolean + } + return 0 +} diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/document_content.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/document_content.go index 8ae9ddc2ac..0752c31506 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/document_content.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/document_content.go @@ -2,6 +2,7 @@ package query import ( sitter "github.com/smacker/go-tree-sitter" + "go.lsp.dev/uri" ) type DocumentContent interface { @@ -9,4 +10,5 @@ type DocumentContent interface { Content(n *sitter.Node) string ContentRange(r sitter.Range) string Tree() *sitter.Tree + URI() uri.URI } diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/identifiers.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/identifiers.go index 38c476d6fa..63005a1cc8 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/identifiers.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/identifiers.go @@ -4,16 +4,29 @@ import ( sitter "github.com/smacker/go-tree-sitter" ) +// Extract all identifiers from the subtree. Include an extra empty identifier +// "" if there is an error node with a trailing period. +// +const Identifiers = ` +[(module) @module + (identifier) @id + "." @dot + (ERROR "." @trailing-dot + .) + ] +` + func ExtractIdentifiers(doc DocumentContent, nodes []*sitter.Node, limit *sitter.Point) []string { identifiers := []string{} for i, node := range nodes { switch node.Type() { case ".": - // if first node is a '.' then append as a '.' to indicate an - // attribute expression attached to some other expression - if i == 0 { - identifiers = append(identifiers, ".") + // if we haven't seen any identifiers before this '.', then append + // a "" to indicate an attribute expression attached to some + // other expression + if len(identifiers) == 0 { + identifiers = append(identifiers, "") } // if last node is a '.' then append an empty identifier for attribute matching if i == len(nodes)-1 { @@ -34,6 +47,10 @@ func ExtractIdentifiers(doc DocumentContent, nodes []*sitter.Node, limit *sitter } else { identifiers = append(identifiers, doc.Content(c.Node)) } + case "dot": + if len(identifiers) == 0 { + identifiers = append(identifiers, "") + } case "trailing-dot": identifiers = append(identifiers, "") case "module": diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/identifiers.scm b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/identifiers.scm deleted file mode 100644 index 653051d8bb..0000000000 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/identifiers.scm +++ /dev/null @@ -1,5 +0,0 @@ -[(module) @module - (identifier) @id - (ERROR "." @trailing-dot - .) - ] diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/location.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/location.go index 76e406a15f..7178bfe995 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/location.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/location.go @@ -3,6 +3,7 @@ package query import ( sitter "github.com/smacker/go-tree-sitter" "go.lsp.dev/protocol" + "go.lsp.dev/uri" ) // PositionToPoint converts an LSP protocol file location to a Tree-sitter file location. @@ -21,6 +22,13 @@ func pointToPosition(point sitter.Point) protocol.Position { } } +func NodeLocation(node *sitter.Node, docURI uri.URI) protocol.Location { + return protocol.Location{ + URI: docURI, + Range: NodeRange(node), + } +} + func NodeRange(node *sitter.Node) protocol.Range { return protocol.Range{ Start: pointToPosition(node.StartPoint()), @@ -44,6 +52,17 @@ func NodesRange(nodes []*sitter.Node) protocol.Range { } } +func SitterRange(r protocol.Range) sitter.Range { + return sitter.Range{ + StartPoint: PositionToPoint(r.Start), + EndPoint: PositionToPoint(r.End), + } +} + +func RangeContainsPoint(r sitter.Range, p sitter.Point) bool { + return PointAfterOrEqual(p, r.StartPoint) && PointBeforeOrEqual(p, r.EndPoint) +} + func PointCmp(a, b sitter.Point) int { if a.Row < b.Row { return -1 @@ -132,3 +151,25 @@ func NodeAtPoint(doc DocumentContent, pt sitter.Point) (*sitter.Node, bool) { } return ChildNodeAtPoint(pt, namedNode) } + +// Find the deepest child node for which `compare` returns 0. +// Compare should return: +// - `-1` if the node is located before the range of interest +// - `0` if the node covers the range of interest +// - `1` if the node is located after the range of interest +func FindChildNode(node *sitter.Node, compare func(*sitter.Node) int) *sitter.Node { + childCount := int(node.ChildCount()) + for i := 0; i < childCount; i++ { + child := node.Child(i) + cmp := compare(child) + if cmp == 0 { + if child.ChildCount() == 0 { + return child + } + return FindChildNode(child, compare) + } else if cmp > 0 { + break + } + } + return nil +} diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/node.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/node.go index 204606b143..42b0a6bef7 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/node.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/node.go @@ -14,6 +14,8 @@ const ( NodeTypeAssignment = "assignment" NodeTypeAttribute = "attribute" NodeTypeString = "string" + NodeTypeDictionary = "dictionary" + NodeTypeList = "list" NodeTypeComment = "comment" NodeTypeBlock = "block" NodeTypeERROR = "ERROR" diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/parameters.scm b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/parameters.scm deleted file mode 100644 index f0f2093f27..0000000000 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/parameters.scm +++ /dev/null @@ -1,7 +0,0 @@ -(parameters ([ - (identifier) @name - (_ . - (identifier) @name - type: (type)? @type - value: (expression)? @value) -]) @param) diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/params.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/params.go index 9576653d84..d14fc8a7bd 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/params.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/params.go @@ -3,6 +3,8 @@ package query import ( "fmt" + "go.lsp.dev/uri" + "go.lsp.dev/protocol" sitter "github.com/smacker/go-tree-sitter" @@ -10,12 +12,29 @@ import ( "github.com/tilt-dev/starlark-lsp/pkg/docstring" ) +// FunctionParameters extracts parameters from a function definition and +// supports a mixture of positional parameters, default value parameters, +// typed parameters*, and typed default value parameters*. +// +// * These are not valid Starlark, but we support them to enable using Python +// type-stub files for improved editor experience. +const FunctionParameters = ` +(parameters ([ + (identifier) @name + (_ . + (identifier) @name + type: (type)? @type + value: (expression)? @value) +]) @param) +` + type Parameter struct { Name string TypeHint string DefaultValue string Content string - Node *sitter.Node + DocURI uri.URI + Location protocol.Location } func (p Parameter) ParameterInfo(fnDocs docstring.Parsed) protocol.ParameterInformation { @@ -46,12 +65,12 @@ func (p Parameter) ParameterInfo(fnDocs docstring.Parsed) protocol.ParameterInfo return pi } -func (p Parameter) Symbol() protocol.DocumentSymbol { - return protocol.DocumentSymbol{ - Name: p.Name, - Kind: protocol.SymbolKindVariable, - Detail: p.Content, - Range: NodeRange(p.Node), +func (p Parameter) Symbol() Symbol { + return Symbol{ + Name: p.Name, + Kind: protocol.SymbolKindVariable, + Detail: p.Content, + Location: p.Location, } } @@ -74,7 +93,9 @@ func extractParameters(doc DocumentContent, fnDocs docstring.Parsed, var params []Parameter Query(node, FunctionParameters, func(q *sitter.Query, match *sitter.QueryMatch) bool { - var param Parameter + param := Parameter{ + DocURI: doc.URI(), + } for _, c := range match.Captures { content := doc.Content(c.Node) @@ -87,7 +108,7 @@ func extractParameters(doc DocumentContent, fnDocs docstring.Parsed, param.DefaultValue = content case "param": param.Content = content - param.Node = c.Node + param.Location = NodeLocation(c.Node, param.DocURI) } } diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/queries.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/queries.go index a4adbec7bb..3bc4f80cd6 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/queries.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/queries.go @@ -6,24 +6,9 @@ import ( sitter "github.com/smacker/go-tree-sitter" ) -// FunctionParameters extracts parameters from a function definition and -// supports a mixture of positional parameters, default value parameters, -// typed parameters*, and typed default value parameters*. -// -// * These are not valid Starlark, but we support them to enable using Python -// type-stub files for improved editor experience. -//go:embed parameters.scm -var FunctionParameters []byte - -// Extract all identifiers from the subtree. Include an extra empty identifier -// "" if there is an error node with a trailing period. -// -//go:embed identifiers.scm -var Identifiers []byte - func LeafNodes(node *sitter.Node) []*sitter.Node { nodes := []*sitter.Node{} - Query(node, []byte(`_ @node`), func(q *sitter.Query, match *sitter.QueryMatch) bool { + Query(node, `_ @node`, func(q *sitter.Query, match *sitter.QueryMatch) bool { for _, c := range match.Captures { if c.Node.Type() == NodeTypeIdentifier || (c.Node.ChildCount() == 0 && @@ -38,7 +23,7 @@ func LeafNodes(node *sitter.Node) []*sitter.Node { func LoadStatements(input []byte, tree *sitter.Tree) []*sitter.Node { nodes := []*sitter.Node{} - Query(tree.RootNode(), []byte(`(call) @call`), func(q *sitter.Query, match *sitter.QueryMatch) bool { + Query(tree.RootNode(), `(call) @call`, func(q *sitter.Query, match *sitter.QueryMatch) bool { for _, c := range match.Captures { id := c.Node.ChildByFieldName("function") name := id.Content(input) diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/query.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/query.go index 3d7ddca08d..17b3e7eefa 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/query.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/query.go @@ -19,8 +19,8 @@ type MatchFunc func(q *sitter.Query, match *sitter.QueryMatch) bool // Query executes a Tree-sitter S-expression query against a subtree and invokes // matchFn on each result. -func Query(node *sitter.Node, pattern []byte, matchFn MatchFunc) { - q := MustQuery(pattern, LanguagePython) +func Query(node *sitter.Node, pattern string, matchFn MatchFunc) { + q := MustQuery([]byte(pattern), LanguagePython) qc := sitter.NewQueryCursor() defer qc.Close() diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/signature.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/signature.go index a3a483dc45..acf72c0f38 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/signature.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/signature.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" + "go.lsp.dev/uri" + "go.lsp.dev/protocol" sitter "github.com/smacker/go-tree-sitter" @@ -53,7 +55,8 @@ type Signature struct { Params []Parameter ReturnType string Docs docstring.Parsed - Node *sitter.Node + docURI uri.URI + Range protocol.Range } func (s Signature) SignatureInfo() protocol.SignatureInformation { @@ -95,12 +98,15 @@ func (s Signature) Label() string { return sb.String() } -func (s Signature) Symbol() protocol.DocumentSymbol { - return protocol.DocumentSymbol{ +func (s Signature) Symbol() Symbol { + return Symbol{ Name: s.Name, Kind: protocol.SymbolKindFunction, - Detail: s.Label(), - Range: NodeRange(s.Node), + Detail: s.Docs.Description, + Location: protocol.Location{ + URI: s.docURI, + Range: s.Range, + }, } } @@ -125,7 +131,8 @@ func ExtractSignature(doc DocumentContent, n *sitter.Node) Signature { Params: params, ReturnType: returnType, Docs: fnDocs, - Node: n, + Range: NodeRange(n), + docURI: doc.URI(), } } diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/symbol.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/symbol.go index 72d18cc70f..035f8bd67c 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/symbol.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/symbol.go @@ -1,7 +1,7 @@ package query import ( - "strings" + "fmt" "go.lsp.dev/protocol" @@ -10,40 +10,15 @@ import ( // Get all symbols defined at the same level as the given node. // If before != nil, only include symbols that appear before that node. -func SiblingSymbols(doc DocumentContent, node, before *sitter.Node) []protocol.DocumentSymbol { - var symbols []protocol.DocumentSymbol +func SiblingSymbols(doc DocumentContent, node, before *sitter.Node) []Symbol { + var symbols []Symbol for n := node; n != nil && NodeBefore(n, before); n = n.NextNamedSibling() { - var symbol protocol.DocumentSymbol - - if n.Type() == NodeTypeExpressionStatement { - assignment := n.NamedChild(0) - if assignment == nil || assignment.Type() != "assignment" { - continue - } - symbol.Name = doc.Content(assignment.ChildByFieldName("left")) - val := assignment.ChildByFieldName("right") - var kind protocol.SymbolKind - if val == nil { - // python variable assignment without an initial value - // (https://peps.python.org/pep-0526/); just assume a variable - kind = 0 - } else { - kind = nodeTypeToSymbolKind(val) - } - if kind == 0 { - kind = protocol.SymbolKindVariable - } - symbol.Kind = kind - symbol.Range = NodeRange(n) - // Look for possible docstring for the assigned variable - if n.NextNamedSibling() != nil && n.NextNamedSibling().Type() == NodeTypeExpressionStatement { - if ch := n.NextNamedSibling().NamedChild(0); ch != nil && ch.Type() == NodeTypeString { - symbol.Detail = strings.Trim(doc.Content(ch), `"'`) - } - } - } + var symbol Symbol - if n.Type() == NodeTypeFunctionDef { + switch n.Type() { + case NodeTypeExpressionStatement: + symbol = ExtractVariableAssignment(doc, n) + case NodeTypeFunctionDef: sig := ExtractSignature(doc, n) symbol = sig.Symbol() symbol.Detail = sig.Docs.Description @@ -56,6 +31,43 @@ func SiblingSymbols(doc DocumentContent, node, before *sitter.Node) []protocol.D return symbols } +func ExtractVariableAssignment(doc DocumentContent, n *sitter.Node) Symbol { + if n.Type() != NodeTypeExpressionStatement { + panic(fmt.Errorf("invalid node type: %s", n.Type())) + } + + var symbol Symbol + assignment := n.NamedChild(0) + if assignment == nil || assignment.Type() != "assignment" { + return symbol + } + symbol.Name = doc.Content(assignment.ChildByFieldName("left")) + val := assignment.ChildByFieldName("right") + t := assignment.ChildByFieldName("type") + var kind protocol.SymbolKind + if t != nil { + kind = pythonTypeToSymbolKind(doc, t) + } else if val != nil { + kind = nodeTypeToSymbolKind(val) + } + if kind == 0 { + kind = protocol.SymbolKindVariable + } + symbol.Kind = kind + symbol.Location = protocol.Location{ + Range: NodeRange(n), + URI: doc.URI(), + } + + // Look for possible docstring for the assigned variable + if n.NextNamedSibling() != nil && n.NextNamedSibling().Type() == NodeTypeExpressionStatement { + if ch := n.NextNamedSibling().NamedChild(0); ch != nil && ch.Type() == NodeTypeString { + symbol.Detail = Unquote(doc.Input(), ch) + } + } + return symbol +} + // A node is in the scope of the top level module if there are no function // definitions in the ancestry of the node. func IsModuleScope(doc DocumentContent, node *sitter.Node) bool { @@ -69,8 +81,8 @@ func IsModuleScope(doc DocumentContent, node *sitter.Node) bool { // Get all symbols defined in scopes at or above the level of the given node, // excluding symbols from the top-level module (document symbols). -func SymbolsInScope(doc DocumentContent, node *sitter.Node) []protocol.DocumentSymbol { - var symbols []protocol.DocumentSymbol +func SymbolsInScope(doc DocumentContent, node *sitter.Node) []Symbol { + var symbols []Symbol appendParameters := func(fnNode *sitter.Node) { sig := ExtractSignature(doc, fnNode) @@ -102,7 +114,7 @@ func SymbolsInScope(doc DocumentContent, node *sitter.Node) []protocol.DocumentS } // DocumentSymbols returns all symbols with document-wide visibility. -func DocumentSymbols(doc DocumentContent) []protocol.DocumentSymbol { +func DocumentSymbols(doc DocumentContent) []Symbol { return SiblingSymbols(doc, doc.Tree().RootNode().NamedChild(0), nil) } @@ -120,3 +132,18 @@ func SymbolsBefore(symbols []protocol.DocumentSymbol, before *sitter.Node) []pro } return result } + +type Symbol struct { + Name string + Detail string + Kind protocol.SymbolKind + Tags []protocol.SymbolTag + Location protocol.Location + SelectionRange protocol.Range + Children []Symbol +} + +// builtins (e.g., `False`, `k8s_resource`) have no location +func (s Symbol) HasLocation() bool { + return s.Location.URI != "" +} diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/types.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/types.go new file mode 100644 index 0000000000..039519a946 --- /dev/null +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/query/types.go @@ -0,0 +1,67 @@ +package query + +import ( + "strings" + + sitter "github.com/smacker/go-tree-sitter" +) + +const methodsAndFields = ` +(class_definition + name: (identifier) @name + body: (block ([ + (expression_statement (assignment)) @field + (function_definition) @method + (_) + ])*) +) +` + +type Type struct { + Name string + Methods []Signature + Fields []Symbol + Members []Symbol +} + +func (t Type) FindMethod(name string) (Signature, bool) { + for _, m := range t.Methods { + if m.Name == name { + return m, true + } + } + return Signature{}, false +} + +func Types(doc DocumentContent, node *sitter.Node) []Type { + types := []Type{} + Query(node, methodsAndFields, func(q *sitter.Query, match *sitter.QueryMatch) bool { + curr := Type{} + for _, c := range match.Captures { + switch q.CaptureNameForId(c.Index) { + case "name": + curr.Name = doc.Content(c.Node) + case "field": + field := ExtractVariableAssignment(doc, c.Node) + curr.Fields = append(curr.Fields, field) + curr.Members = append(curr.Members, field) + case "method": + meth := ExtractSignature(doc, c.Node) + // Remove Python "self" parameter if present + if len(meth.Params) > 0 && meth.Params[0].Content == "self" { + meth.Params = meth.Params[1:] + } + if !strings.HasPrefix(meth.Name, "_") { + curr.Methods = append(curr.Methods, meth) + curr.Members = append(curr.Members, meth.Symbol()) + } + } + } + if curr.Name != "" && (len(curr.Methods) > 0 || len(curr.Fields) > 0) { + types = append(types, curr) + } + return true + }) + + return types +} diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/definition.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/definition.go new file mode 100644 index 0000000000..0416027171 --- /dev/null +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/definition.go @@ -0,0 +1,30 @@ +package server + +import ( + "context" + "fmt" + + "go.lsp.dev/protocol" + "go.uber.org/zap" +) + +func (s Server) Definition(ctx context.Context, params *protocol.DefinitionParams) (result []protocol.Location, err error) { + doc, err := s.docs.Read(ctx, params.TextDocument.URI) + if err != nil { + return nil, err + } + defer doc.Close() + + logger := protocol.LoggerFromContext(ctx). + With(textDocumentFields(params.TextDocumentPositionParams)...) + logger.Debug("definition") + + positions, err := s.analyzer.Definition(ctx, doc, params.Position) + if err != nil { + logger.With(zap.Namespace("definition")).Error(fmt.Sprintf("error looking up definition: %v", err)) + } else { + logger.With(zap.Namespace("definition")).Debug(fmt.Sprintf("found definition locations: %v", positions)) + } + + return positions, err +} diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/initialize.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/initialize.go index 7dc84a32c0..2c8b5cfa95 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/initialize.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/initialize.go @@ -31,7 +31,8 @@ func (s *Server) Initialize(ctx context.Context, CompletionProvider: &protocol.CompletionOptions{ TriggerCharacters: []string{"."}, }, - HoverProvider: true, + HoverProvider: true, + DefinitionProvider: true, }, }, nil } diff --git a/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/symbol.go b/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/symbol.go index 8fbb630c6c..136546a74e 100644 --- a/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/symbol.go +++ b/vendor/github.com/tilt-dev/starlark-lsp/pkg/server/symbol.go @@ -3,9 +3,26 @@ package server import ( "context" + "github.com/tilt-dev/starlark-lsp/pkg/query" + "go.lsp.dev/protocol" ) +func toDocumentSymbol(s query.Symbol) protocol.DocumentSymbol { + var children []protocol.DocumentSymbol + for _, c := range s.Children { + children = append(children, toDocumentSymbol(c)) + } + return protocol.DocumentSymbol{ + Name: s.Name, + Detail: s.Detail, + Kind: s.Kind, + Tags: s.Tags, + Range: s.Location.Range, + Children: children, + } +} + func (s *Server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]interface{}, error) { @@ -18,7 +35,7 @@ func (s *Server) DocumentSymbol(ctx context.Context, symbols := doc.Symbols() result := make([]interface{}, len(symbols)) for i := range symbols { - result[i] = symbols[i] + result[i] = toDocumentSymbol(symbols[i]) } return result, nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index 3a702d7958..cd1b79c736 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -670,7 +670,7 @@ github.com/tilt-dev/localregistry-go github.com/tilt-dev/probe/internal/procutil github.com/tilt-dev/probe/pkg/probe github.com/tilt-dev/probe/pkg/prober -# github.com/tilt-dev/starlark-lsp v0.0.0-20220415191241-c73ee55518d2 +# github.com/tilt-dev/starlark-lsp v0.0.0-20220422161504-c53192ba4695 ## explicit; go 1.17 github.com/tilt-dev/starlark-lsp/pkg/analysis github.com/tilt-dev/starlark-lsp/pkg/cli