diff --git a/go.mod b/go.mod index 2c0056c0..97edbd59 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.3.0 github.com/prometheus/common v0.9.1 - github.com/prometheus/prometheus v1.8.2-0.20200120155032-a6776221848b + github.com/prometheus/prometheus v1.8.2-0.20200213233353-b90be6f32a33 github.com/rakyll/statik v0.1.7-0.20191104211043-6b2f3ee522b6 github.com/spaolacci/murmur3 v1.1.0 // indirect golang.org/x/mod v0.2.0 diff --git a/go.sum b/go.sum index 3a9a7ae4..d314c28e 100644 --- a/go.sum +++ b/go.sum @@ -268,8 +268,8 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/prometheus v0.0.0-20180315085919-58e2a31db8de/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= -github.com/prometheus/prometheus v1.8.2-0.20200120155032-a6776221848b h1:RIICDDarqlBbSRvTgomV+8obUlIj/DFxFgFjDqDLnn0= -github.com/prometheus/prometheus v1.8.2-0.20200120155032-a6776221848b/go.mod h1:fkIPPkuZnkXyopYHmXPxf9rgiPkVgZCN8w9o8+UgBlY= +github.com/prometheus/prometheus v1.8.2-0.20200213233353-b90be6f32a33 h1:HBYrMJj5iosUjUkAK9L5GO+5eEQXbcrzdjkqY9HV5W4= +github.com/prometheus/prometheus v1.8.2-0.20200213233353-b90be6f32a33/go.mod h1:fkIPPkuZnkXyopYHmXPxf9rgiPkVgZCN8w9o8+UgBlY= github.com/rakyll/statik v0.1.7-0.20191104211043-6b2f3ee522b6 h1:aTZBZ+7KwHnr9TPzd4l4PGvUJckC0Vw30gxXBxMGhyo= github.com/rakyll/statik v0.1.7-0.20191104211043-6b2f3ee522b6/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= diff --git a/langserver/ast_test.go b/langserver/ast_test.go deleted file mode 100644 index 212571db..00000000 --- a/langserver/ast_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2019 Tobias Guggenmos -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package langserver - -import ( - "fmt" - "go/token" - "reflect" - "testing" - - "github.com/prometheus-community/promql-langserver/langserver/cache" - "github.com/prometheus/prometheus/promql" -) - -func TestSmallestSurroundingNode(t *testing.T) { - shouldMatchFull := []struct { - input string - pos token.Pos - }{ - { - input: "1", - pos: 1, - }, { - input: "+1 + -2 * 1", - pos: 4, - }, - } - for _, test := range shouldMatchFull { - parseResult, err := promql.ParseExpr(test.input) - if err != nil { - panic("Parser should not have failed on " + test.input) - } - - node := getSmallestSurroundingNode(&cache.CompiledQuery{Ast: parseResult}, test.pos) - - if !reflect.DeepEqual(node, parseResult) { - panic("Whole Expression should have been matched for " + test.input) - } - } - - for _, test := range testExpressions { - parseResult, err := promql.ParseExpr(test) - if err != nil { - // We're currently only interested in correct expressions - continue - } - - for pos := 1; pos <= len(test); pos++ { - node := getSmallestSurroundingNode(&cache.CompiledQuery{Ast: parseResult}, token.Pos(pos)) - // If we are outside the outermost Expression, nothing should be matched - if node == nil && (int(parseResult.PositionRange().Start) > pos || int(parseResult.PositionRange().End) >= pos) { - continue - } - - if int(node.PositionRange().Start) > pos || int(node.PositionRange().End) < pos { - panic("The smallestSurroundingNode is not actually surrounding for input " + test + - " and pos " + fmt.Sprintln(pos) + "Got: " + fmt.Sprintln(node) + - "Pos: " + fmt.Sprintln(node.PositionRange().Start) + "EndPos: " + fmt.Sprintln(node.PositionRange().End)) - } - } - } -} diff --git a/langserver/ast.go b/langserver/cache/ast.go similarity index 79% rename from langserver/ast.go rename to langserver/cache/ast.go index bb12212c..eec577e1 100644 --- a/langserver/ast.go +++ b/langserver/cache/ast.go @@ -11,21 +11,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -package langserver +package cache import ( "go/token" - "github.com/prometheus-community/promql-langserver/langserver/cache" "github.com/prometheus/prometheus/promql" ) -func getSmallestSurroundingNode(query *cache.CompiledQuery, tokenPos token.Pos) promql.Node { +func getSmallestSurroundingNode(query *CompiledQuery, tokenPos token.Pos) promql.Node { ast := query.Ast pos := promql.Pos(tokenPos - query.Pos) - if pos < ast.PositionRange().Start || pos > ast.PositionRange().End { + if ast == nil || pos < ast.PositionRange().Start || pos > ast.PositionRange().End { return nil } diff --git a/langserver/cache/ast_test.go b/langserver/cache/ast_test.go new file mode 100644 index 00000000..2e03bdf3 --- /dev/null +++ b/langserver/cache/ast_test.go @@ -0,0 +1,283 @@ +// Copyright 2019 Tobias Guggenmos +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cache + +import ( + "fmt" + "go/token" + "reflect" + "testing" + + "github.com/prometheus/prometheus/promql" +) + +func TestSmallestSurroundingNode(t *testing.T) { //nolint:funlen + shouldMatchFull := []struct { + input string + pos token.Pos + }{ + { + input: "1", + pos: 1, + }, { + input: "+1 + -2 * 1", + pos: 4, + }, + } + + for _, test := range shouldMatchFull { + parseResult, err := promql.ParseExpr(test.input) + if err != nil { + panic("Parser should not have failed on " + test.input) + } + + node := getSmallestSurroundingNode(&CompiledQuery{Ast: parseResult}, test.pos) + + if !reflect.DeepEqual(node, parseResult) { + panic("Whole Expression should have been matched for " + test.input) + } + } + + var testExpressions = []string{ + "1", + " 1", + "-1", + "+Inf", + "-Inf", + ".5", + "5.", + "123.4567", + "5e-3", + "5e3", + "0xc", + "0755", + "+5.5e-3", + "-0755", + "1 + 1", + "1 - 1", + "1 * 1", + "1 % 1", + "1 / 1", + "1 == bool 1", + "1 != bool 1", + "1 > bool 1", + "1 >= bool 1", + "1 < bool 1", + "1 <= bool 1", + "+1 + -2 * 1", + "1 + 2/(3*1)", + "1 < bool 2 - 1 * 2", + "-some_metric", + "+some_metric", + "", + "# just a comment\n\n", + "1+", + ".", + "2.5.", + "100..4", + "0deadbeef", + "1 /", + "*1", + "(1))", + "((1)", + "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", + "(", + "1 and 1", + "1 == 1", + "1 or 1", + "1 unless 1", + "1 !~ 1", + "1 =~ 1", + `-"string"`, + `-test[5m]`, + `*test`, + "1 offset 1d", + "a - on(b) ignoring(c) d", + "foo * bar", + "foo == 1", + "foo == bool 1", + "2.5 / bar", + "foo and bar", + "foo unless bar", + "foo + bar or bla and blub", + "foo and bar unless baz or qux", + "bar + on(foo) bla / on(baz, buz) group_right(test) blub", + "foo * on(test,blub) bar", + "foo * on(test,blub) group_left bar", + "foo and on(test,blub) bar", + "foo and on() bar", + "foo and ignoring(test,blub) bar", + "foo and ignoring() bar", + "foo unless on(bar) baz", + "foo / on(test,blub) group_left(bar) bar", + "foo / ignoring(test,blub) group_left(blub) bar", + "foo - on(test,blub) group_right(bar,foo) bar", + "foo - ignoring(test,blub) group_right(bar,foo) bar", + "foo and 1", + "1 and foo", + "foo or 1", + "1 or foo", + "foo unless 1", + "1 unless foo", + "1 or on(bar) foo", + "foo == on(bar) 10", + "foo and on(bar) group_left(baz) bar", + "foo and on(bar) group_right(baz) bar", + "foo or on(bar) group_left(baz) bar", + "foo or on(bar) group_right(baz) bar", + "foo unless on(bar) group_left(baz) bar", + "foo unless on(bar) group_right(baz) bar", + `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`, + "foo + bool bar", + "foo + bool 10", + "foo and bool 10", + "foo", + "foo offset 5m", + `foo:bar{a="bc"}`, + `foo{NaN='bc'}`, + `foo{a="b", foo!="bar", test=~"test", bar!~"baz"}`, + `{`, + `}`, + `some{`, + `some}`, + `some_metric{a=b}`, + `some_metric{a:b="b"}`, + `foo{a*"b"}`, + `foo{a>="b"}`, + "some_metric{a=\"\xff\"}", + `foo{gibberish}`, + `foo{1}`, + `{}`, + `{x=""}`, + `{x=~".*"}`, + `{x!~".+"}`, + `{x!="a"}`, + `foo{__name__="bar"}`, + "test[5s]", + "test[5m]", + "test[5h] OFFSET 5m", + "test[5d] OFFSET 10s", + "test[5w] offset 2w", + `test{a="b"}[5y] OFFSET 3d`, + `foo[5mm]`, + `foo[0m]`, + `foo[5m30s]`, + `foo[5m] OFFSET 1h30m`, + `foo["5m"]`, + `foo[]`, + `foo[1]`, + `some_metric[5m] OFFSET 1`, + `some_metric[5m] OFFSET 1mm`, + `some_metric[5m] OFFSET`, + `some_metric OFFSET 1m[5m]`, + `(foo + bar)[5m]`, + "sum by (foo)(some_metric)", + "avg by (foo)(some_metric)", + "max by (foo)(some_metric)", + "sum without (foo) (some_metric)", + "sum (some_metric) without (foo)", + "stddev(some_metric)", + "stdvar by (foo)(some_metric)", + "sum by ()(some_metric)", + "topk(5, some_metric)", + "count_values(\"value\", some_metric)", + "sum without(and, by, avg, count, alert, annotations)(some_metric)", + "sum without(==)(some_metric)", + `sum some_metric by (test)`, + `sum (some_metric) by test`, + `sum (some_metric) by test`, + `sum () by (test)`, + "MIN keep_common (some_metric)", + "MIN (some_metric) keep_common", + `sum (some_metric) without (test) by (test)`, + `sum without (test) (some_metric) by (test)`, + `topk(some_metric)`, + `topk(some_metric, other_metric)`, + `count_values(5, other_metric)`, + "time()", + `floor(some_metric{foo!="bar"})`, + "rate(some_metric[5m])", + "round(some_metric)", + "round(some_metric, 5)", + "floor()", + "floor(some_metric, other_metric)", + "floor(1)", + "non_existent_function_far_bar()", + "rate(some_metric)", + "label_replace(a, `b`, `c\xff`, `d`, `.*`)", + "-=", + "++-++-+-+-<", + "e-+=/(0)", + `"double-quoted string \" with escaped quote"`, + `'single-quoted string \' with escaped quote'`, + "`backtick-quoted string`", + `"\a\b\f\n\r\t\v\\\" - \xFF\377\u1234\U00010111\U0001011111☺"`, + `'\a\b\f\n\r\t\v\\\' - \xFF\377\u1234\U00010111\U0001011111☺'`, + "`" + `\a\b\f\n\r\t\v\\\"\' - \xFF\377\u1234\U00010111\U0001011111☺` + "`", + "`\\``", + `"\`, + `"\c"`, + `"\x."`, + `foo{bar="baz"}[10m:6s]`, + `foo[10m:]`, + `min_over_time(rate(foo{bar="baz"}[2s])[5m:5s])`, + `min_over_time(rate(foo{bar="baz"}[2s])[5m:])[4m:3s]`, + `min_over_time(rate(foo{bar="baz"}[2s])[5m:] offset 4m)[4m:3s]`, + "sum without(and, by, avg, count, alert, annotations)(some_metric) [30m:10s]", + `some_metric OFFSET 1m [10m:5s]`, + `(foo + bar{nm="val"})[5m:]`, + `(foo + bar{nm="val"})[5m:] offset 10m`, + "test[5d] OFFSET 10s [10m:5s]", + `(foo + bar{nm="val"})[5m:][10m:5s]`, + `{} 1 2 3`, + `{a="b"} -1 2 3`, + `my_metric 1 2 3`, + `my_metric{} 1 2 3`, + `my_metric{a="b"} 1 2 3`, + `my_metric{a="b"} 1 2 3-10x4`, + `my_metric{a="b"} 1 2 3-0x4`, + `my_metric{a="b"} 1 3 _ 5 _x4`, + `my_metric{a="b"} 1 3 _ 5 _a4`, + `my_metric{a="b"} 1 -1`, + `my_metric{a="b"} 1 +1`, + `my_metric{a="b"} 1 -1 -3-10x4 7 9 +5`, + `my_metric{a="b"} 1 +1 +4 -6 -2 8`, + `my_metric{a="b"} 1 2 3 `, + `my_metric{a="b"} -3-3 -3`, + `my_metric{a="b"} -3 -3-3`, + `my_metric{a="b"} -3 _-2`, + `my_metric{a="b"} -3 3+3x4-4`, + } + + for _, test := range testExpressions { + parseResult, _ := promql.ParseExpr(test) + + for pos := 1; pos <= len(test)+1; pos++ { + node := getSmallestSurroundingNode(&CompiledQuery{Ast: parseResult}, token.Pos(pos)) + + // If we are outside the outermost Expression, nothing should be matched + if parseResult == nil || int(parseResult.PositionRange().Start) > pos || int(parseResult.PositionRange().End) < pos { + if node != nil { + panic("nothing should have been matched") + } + + continue + } + + if node == nil || int(node.PositionRange().Start) > pos || int(node.PositionRange().End) < pos { + panic("The smallestSurroundingNode is not actually surrounding for input " + test + " and pos " + fmt.Sprintln(pos)) + } + } + } +} diff --git a/langserver/cache/cache_test.go b/langserver/cache/cache_test.go index 1e7f3605..d246a341 100644 --- a/langserver/cache/cache_test.go +++ b/langserver/cache/cache_test.go @@ -15,12 +15,13 @@ package cache import ( "context" + "fmt" "testing" "github.com/prometheus-community/promql-langserver/vendored/go-tools/lsp/protocol" ) -func TestCache(t *testing.T) { +func TestCache(t *testing.T) { // nolint:funlen c := &DocumentCache{} c.Init() @@ -64,6 +65,11 @@ func TestCache(t *testing.T) { panic("Failed to RemoveDocument() from cache") } + err = c.RemoveDocument("test_file") + if err == nil { + panic("should have failed to RemoveDocument() twice") + } + _, err = c.AddDocument( context.Background(), &protocol.TextDocumentItem{ @@ -71,9 +77,207 @@ func TestCache(t *testing.T) { URI: "test_file", LanguageID: "yaml", Version: 0, - Text: "test_text", + Text: "test_text:", }) if err != nil { panic("Should be able to readd document after removing it") } + + tooLongString := string(make([]byte, maxDocumentSize+1)) + + _, err = c.AddDocument( + context.Background(), + &protocol.TextDocumentItem{ + + URI: "long_test_file", + LanguageID: "yaml", + Version: 0, + Text: tooLongString, + }) + if err == nil { + panic("Shouldn't be able to add overlong document") + } + + wrongYaml := "asdf[" + + _, err = c.AddDocument( + context.Background(), + &protocol.TextDocumentItem{ + + URI: "wrong_yaml_file", + LanguageID: "yaml", + Version: 0, + Text: wrongYaml, + }) + if err != nil { + panic("Should be able to handle yaml with syntax errors") + } + + rulesFile := ` +groups: + - name: example + rules: + - record: job:http_inprogress_requests:sum + expr: sum(http_inprogress_requests) by (job) + - record: job:http_inprogress_requests:sum:wrong + expr: + sum(http_inprogress_requests) by (job + - record: job:http_inprogress_requests:sum:quoted + expr: "sum(http_inprogress_requests) by (job)" + - expr: sum(http_inprogress_requests) by (job) + record: job:http_inprogress_requests:sum:2 + - expr: | + sum(http_inprogress_requests) by (job) + record: | + job:http_inprogress_requests:sum:3 + - 1 + - a: + - b :2 +` + + _, err = c.AddDocument( + context.Background(), + &protocol.TextDocumentItem{ + + URI: "rules_file", + LanguageID: "yaml", + Version: 0, + Text: rulesFile, + }) + if err != nil { + panic("adding rules file failed") + } + + doc, err = c.GetDocument("rules_file") + if err != nil { + panic("failed to get rules file") + } + + diagnostics, err := doc.GetDiagnostics() + if err != nil { + panic("failed to get diagnostics for rules file") + } + + if len(diagnostics) != 3 { + fmt.Println(diagnostics) + panic("expected exactly 3 error messages for rules file got " + fmt.Sprint(len(diagnostics))) + } + + queries, err := doc.GetQueries() + if err != nil { + panic("failed to get queries for rules file") + } + + if len(queries) != 4 { + fmt.Println(queries) + panic("expected exactly 4 queries for rules file got " + fmt.Sprint(len(queries))) + } + + _, err = doc.GetQuery(queries[0].Pos - 1) + if err == nil { + panic("should have failed to get query") + } + + _, err = doc.GetQuery(queries[0].Pos) + if err != nil { + panic("failed to get query: " + err.Error()) + } + + _, err = c.Find(&protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: "rules_file", + }, + Position: protocol.Position{ + Line: 5, + Character: 25, + }, + }) + if err != nil { + panic("failed to find query: " + err.Error()) + } + + _, err = c.Find(&protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: "rules_file", + }, + Position: protocol.Position{ + Line: 4, + Character: 25, + }, + }) + if err == nil { + panic("should have failed to find query") + } + + _, err = c.Find(&protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: "rules_file_nonexistent", + }, + Position: protocol.Position{ + Line: 4, + Character: 25, + }, + }) + if err == nil { + panic("should have failed to find query") + } + + expectedNewContent := ` +groups: + - name: example + rules: + - record: job:http_inprogress_requests:sum + expr: sum(http_inprogress_requests) by (job) + - record: job:http_inprogress_requests:sum:wrong + expr: + sum(http_inprogress_requests) by (job + - expr: sum(http_inprogress_requests) by (job) + record: job:http_inprogress_requests:sum:2 + - expr: | + sum(http_inprogress_requests) by (job) + record: | + job:http_inprogress_requests:sum:3 + - 1 + - a: + - b :2 +` + + newContent, err := doc.ApplyIncrementalChanges([]protocol.TextDocumentContentChangeEvent{ + { + Range: &protocol.Range{ + Start: protocol.Position{ + Line: 9.0, + Character: 0.0, + }, + End: EndOfLine(protocol.Position{ + Line: 10.0, + Character: 0.1, + }), + }, + Text: "", + }, + }, 2) + + if err != nil { + panic("Failed to apply incremental changes: " + err.Error()) + } + + if newContent != expectedNewContent { + panic("incremental update did not result in expected content") + } + + _, err = doc.ApplyIncrementalChanges(nil, -1) + if err == nil { + panic("File update without version update should have failed") + } + + err = doc.SetContent(context.Background(), "foo", 2, false) + if err != nil { + panic("file update failed") + } + + err = doc.SetContent(context.Background(), "foo", 2, false) + if err == nil { + panic("File update without version update should have failed") + } } diff --git a/langserver/find.go b/langserver/cache/find.go similarity index 57% rename from langserver/find.go rename to langserver/cache/find.go index 187373f5..09f687b5 100644 --- a/langserver/find.go +++ b/langserver/cache/find.go @@ -11,39 +11,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -package langserver +package cache import ( "go/token" - "github.com/prometheus-community/promql-langserver/langserver/cache" "github.com/prometheus-community/promql-langserver/vendored/go-tools/lsp/protocol" "github.com/prometheus/prometheus/promql" ) -type location struct { - doc *cache.DocumentHandle - pos token.Pos - query *cache.CompiledQuery - node promql.Node +// Location bundles all the context that the cache can provide for a given protocol.Location +type Location struct { + Doc *DocumentHandle + Pos token.Pos + Query *CompiledQuery + Node promql.Node } -func (s *server) find(where *protocol.TextDocumentPositionParams) (there *location, err error) { - there = &location{} +func (c *DocumentCache) Find(where *protocol.TextDocumentPositionParams) (there *Location, err error) { + there = &Location{} - if there.doc, err = s.cache.GetDocument(where.TextDocument.URI); err != nil { + if there.Doc, err = c.GetDocument(where.TextDocument.URI); err != nil { return } - if there.pos, err = there.doc.ProtocolPositionToTokenPos(where.Position); err != nil { + if there.Pos, err = there.Doc.ProtocolPositionToTokenPos(where.Position); err != nil { return } - if there.query, err = there.doc.GetQuery(there.pos); err != nil { + if there.Query, err = there.Doc.GetQuery(there.Pos); err != nil { return } - there.node = getSmallestSurroundingNode(there.query, there.pos) + there.Node = getSmallestSurroundingNode(there.Query, there.Pos) return } diff --git a/langserver/cache/yaml.go b/langserver/cache/yaml.go index 5c3f30b9..8b8ea063 100644 --- a/langserver/cache/yaml.go +++ b/langserver/cache/yaml.go @@ -119,7 +119,7 @@ func (d *DocumentHandle) scanYamlTreeRec(node *yaml.Node, nodeEnd token.Pos, lin if i+1 < len(node.Content) && node.Content[i+1] != nil { next := node.Content[i+1] - childEnd, err = d.YamlPositionToTokenPos(next.Line, next.Column, lineOffset) + childEnd, err = d.YamlPositionToTokenPos(next.Line, 1, lineOffset) if err != nil { return err } diff --git a/langserver/completion.go b/langserver/completion.go index 80daf59b..cd39836c 100644 --- a/langserver/completion.go +++ b/langserver/completion.go @@ -24,6 +24,7 @@ import ( "github.com/pkg/errors" + "github.com/prometheus-community/promql-langserver/langserver/cache" "github.com/prometheus-community/promql-langserver/vendored/go-tools/lsp/protocol" "github.com/prometheus/common/model" "github.com/prometheus/prometheus/promql" @@ -33,7 +34,7 @@ import ( // Completion is required by the protocol.Server interface // nolint: wsl func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (ret *protocol.CompletionList, err error) { - location, err := s.find(¶ms.TextDocumentPositionParams) + location, err := s.cache.Find(¶ms.TextDocumentPositionParams) if err != nil { return nil, nil } @@ -42,13 +43,13 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara completions := &ret.Items - switch n := location.node.(type) { + switch n := location.Node.(type) { case *promql.Call: var name string - name, err = location.doc.GetSubstring( - location.query.Pos+token.Pos(location.node.PositionRange().Start), - location.query.Pos+token.Pos(location.node.PositionRange().End), + name, err = location.Doc.GetSubstring( + location.Query.Pos+token.Pos(location.Node.PositionRange().Start), + location.Query.Pos+token.Pos(location.Node.PositionRange().End), ) i := 0 @@ -76,7 +77,7 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara return } } - if location.query.Pos+token.Pos(location.node.PositionRange().Start)+token.Pos(len(metricName)) >= location.pos { + if location.Query.Pos+token.Pos(location.Node.PositionRange().Start)+token.Pos(len(metricName)) >= location.Pos { if err = s.completeMetricName(ctx, completions, location, metricName); err != nil { return } @@ -95,7 +96,7 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara } // nolint:funlen -func (s *server) completeMetricName(ctx context.Context, completions *[]protocol.CompletionItem, location *location, metricName string) error { +func (s *server) completeMetricName(ctx context.Context, completions *[]protocol.CompletionItem, location *cache.Location, metricName string) error { api := s.getPrometheus() var allNames model.LabelValues @@ -135,7 +136,7 @@ func (s *server) completeMetricName(ctx context.Context, completions *[]protocol } } - queries, err := location.doc.GetQueries() + queries, err := location.Doc.GetQueries() if err != nil { return err } @@ -159,7 +160,7 @@ func (s *server) completeMetricName(ctx context.Context, completions *[]protocol return nil } -func (s *server) completeFunctionName(_ context.Context, completions *[]protocol.CompletionItem, location *location, metricName string) error { +func (s *server) completeFunctionName(_ context.Context, completions *[]protocol.CompletionItem, location *cache.Location, metricName string) error { var err error editRange, err := getEditRange(location, metricName) @@ -222,9 +223,9 @@ var aggregators = map[string]string{ // nolint:gochecknoglobals } // nolint: funlen -func (s *server) completeLabels(ctx context.Context, completions *[]protocol.CompletionItem, location *location, metricName string) error { - offset := location.node.PositionRange().Start - l := promql.Lex(location.query.Content[offset:]) +func (s *server) completeLabels(ctx context.Context, completions *[]protocol.CompletionItem, location *cache.Location, metricName string) error { + offset := location.Node.PositionRange().Start + l := promql.Lex(location.Query.Content[offset:]) var ( lastItem promql.Item @@ -237,14 +238,14 @@ func (s *server) completeLabels(ctx context.Context, completions *[]protocol.Com wantValue bool ) - for token.Pos(item.Pos)+token.Pos(len(item.Val))+token.Pos(offset)+location.query.Pos < location.pos { + for token.Pos(item.Pos)+token.Pos(len(item.Val))+token.Pos(offset)+location.Query.Pos < location.Pos { isLabel = false isValue = false lastItem = item l.NextItem(&item) - if overscan := item.Pos + offset + promql.Pos(location.query.Pos) - promql.Pos(location.pos); overscan >= 0 { + if overscan := item.Pos + offset + promql.Pos(location.Query.Pos) - promql.Pos(location.Pos); overscan >= 0 { item = lastItem break } @@ -287,22 +288,22 @@ func (s *server) completeLabels(ctx context.Context, completions *[]protocol.Com loc := *location if isLabel { - loc.node = &item + loc.Node = &item return s.completeLabel(ctx, completions, &loc, metricName) } if item.Typ == promql.COMMA || item.Typ == promql.LEFT_PAREN || item.Typ == promql.LEFT_BRACE { - loc.node = &promql.Item{Pos: item.Pos + 1} + loc.Node = &promql.Item{Pos: item.Pos + 1} return s.completeLabel(ctx, completions, &loc, metricName) } if isValue && lastLabel != "" { - loc.node = &item + loc.Node = &item return s.completeLabelValue(ctx, completions, &loc, lastLabel) } if item.Typ == promql.EQL || item.Typ == promql.NEQ { - loc.node = &promql.Item{Pos: item.Pos + promql.Pos(len(item.Val))} + loc.Node = &promql.Item{Pos: item.Pos + promql.Pos(len(item.Val))} return s.completeLabelValue(ctx, completions, &loc, lastLabel) } @@ -310,7 +311,7 @@ func (s *server) completeLabels(ctx context.Context, completions *[]protocol.Com } // nolint:funlen, unparam -func (s *server) completeLabel(ctx context.Context, completions *[]protocol.CompletionItem, location *location, metricName string) error { +func (s *server) completeLabel(ctx context.Context, completions *[]protocol.CompletionItem, location *cache.Location, metricName string) error { api := s.getPrometheus() var allNames []string @@ -372,7 +373,7 @@ func (s *server) completeLabel(ctx context.Context, completions *[]protocol.Comp continue } - if strings.HasPrefix(name, location.node.(*promql.Item).Val) { + if strings.HasPrefix(name, location.Node.(*promql.Item).Val) { item := protocol.CompletionItem{ Label: name, Kind: 12, //Value @@ -390,7 +391,7 @@ func (s *server) completeLabel(ctx context.Context, completions *[]protocol.Comp } // nolint: funlen -func (s *server) completeLabelValue(ctx context.Context, completions *[]protocol.CompletionItem, location *location, labelName string) error { +func (s *server) completeLabelValue(ctx context.Context, completions *[]protocol.CompletionItem, location *cache.Location, labelName string) error { var allNames model.LabelValues api := s.getPrometheus() @@ -413,7 +414,7 @@ func (s *server) completeLabelValue(ctx context.Context, completions *[]protocol return err } - quoted := location.node.(*promql.Item).Val + quoted := location.Node.(*promql.Item).Val var quote byte @@ -472,14 +473,14 @@ func (s *server) completeLabelValue(ctx context.Context, completions *[]protocol // getEditRange computes the editRange for a completion. In case the completion area is shorter than // the node, the oldname of the token to be completed must be provided. The latter mechanism only // works if oldname is an ASCII string, which can be safely assumed for metric and function names. -func getEditRange(location *location, oldname string) (editRange protocol.Range, err error) { - editRange.Start, err = location.doc.PosToProtocolPosition(location.query.Pos + token.Pos(location.node.PositionRange().Start)) +func getEditRange(location *cache.Location, oldname string) (editRange protocol.Range, err error) { + editRange.Start, err = location.Doc.PosToProtocolPosition(location.Query.Pos + token.Pos(location.Node.PositionRange().Start)) if err != nil { return } if oldname == "" { - editRange.End, err = location.doc.PosToProtocolPosition(location.query.Pos + token.Pos(location.node.PositionRange().End)) + editRange.End, err = location.Doc.PosToProtocolPosition(location.Query.Pos + token.Pos(location.Node.PositionRange().End)) if err != nil { return } diff --git a/langserver/definition.go b/langserver/definition.go index 1e9010d7..6dfc5f28 100644 --- a/langserver/definition.go +++ b/langserver/definition.go @@ -22,16 +22,16 @@ import ( // Definition is required by the protocol.Server interface func (s *server) Definition(ctx context.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) { - location, err := s.find(¶ms.TextDocumentPositionParams) + location, err := s.cache.Find(¶ms.TextDocumentPositionParams) if err != nil { return nil, nil } defs := []protocol.Location{} - switch n := location.node.(type) { // nolint: gocritic + switch n := location.Node.(type) { // nolint: gocritic case *promql.VectorSelector: - queries, err := location.doc.GetQueries() + queries, err := location.Doc.GetQueries() if err != nil { break } @@ -44,8 +44,8 @@ func (s *server) Definition(ctx context.Context, params *protocol.DefinitionPara defLocation := *location - defLocation.node = q.Ast - defLocation.query = q + defLocation.Node = q.Ast + defLocation.Query = q def.Range, err = getEditRange(&defLocation, "") if err != nil { diff --git a/langserver/hover.go b/langserver/hover.go index ea633fa1..b619c196 100644 --- a/langserver/hover.go +++ b/langserver/hover.go @@ -50,8 +50,8 @@ func initializeFunctionDocumentation() http.FileSystem { // Hover shows documentation on hover // required by the protocol.Server interface func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) { - location, err := s.find(¶ms.TextDocumentPositionParams) - if err != nil || location.node == nil { + location, err := s.cache.Find(¶ms.TextDocumentPositionParams) + if err != nil || location.Node == nil { return nil, nil } @@ -74,10 +74,10 @@ func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (*prot } // nolint:funlen -func (s *server) nodeToDocMarkdown(ctx context.Context, location *location) string { //nolint: golint +func (s *server) nodeToDocMarkdown(ctx context.Context, location *cache.Location) string { //nolint: golint var ret bytes.Buffer - switch n := location.node.(type) { + switch n := location.Node.(type) { case *promql.AggregateExpr: name := strings.ToLower(n.Op.String()) @@ -109,7 +109,7 @@ func (s *server) nodeToDocMarkdown(ctx context.Context, location *location) stri case *promql.VectorSelector: metric := n.Name - doc, err := s.getRecordingRuleDocs(location.doc, metric) + doc, err := s.getRecordingRuleDocs(location.Doc, metric) if err != nil { // nolint: errcheck s.client.LogMessage(s.lifetime, &protocol.LogMessageParams{ @@ -135,7 +135,7 @@ func (s *server) nodeToDocMarkdown(ctx context.Context, location *location) stri default: } - if expr, ok := location.node.(promql.Expr); ok { + if expr, ok := location.Node.(promql.Expr); ok { _, err := ret.WriteString(fmt.Sprintf("\n\n__PromQL Type:__ %v\n\n", expr.Type())) if err != nil { return "" @@ -147,9 +147,9 @@ func (s *server) nodeToDocMarkdown(ctx context.Context, location *location) stri if promURL != "" { loc := *location - loc.node = loc.query.Ast + loc.Node = loc.Query.Ast - qText, err := location.doc.GetSubstring(loc.query.Pos+token.Pos(loc.node.PositionRange().Start), loc.query.Pos+token.Pos(loc.node.PositionRange().End)) + qText, err := location.Doc.GetSubstring(loc.Query.Pos+token.Pos(loc.Node.PositionRange().Start), loc.Query.Pos+token.Pos(loc.Node.PositionRange().End)) if err != nil { return "" } diff --git a/langserver/langserver_test.go b/langserver/langserver_test.go index 968e7707..a886ba43 100644 --- a/langserver/langserver_test.go +++ b/langserver/langserver_test.go @@ -381,6 +381,21 @@ func TestServer(t *testing.T) { //nolint:funlen panic("Failed to close document") } + _, err = s.cache.GetDocument("test.promql") + if err == nil { + panic("getting a closed document should have failed") + } + + // Close a document twice + err = s.DidClose(context.Background(), &protocol.DidCloseTextDocumentParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: "test.promql", + }, + }) + if err == nil { + panic("should have failed to close document") + } + // Reopen a closed document err = s.DidOpen(context.Background(), &protocol.DidOpenTextDocumentParams{ TextDocument: protocol.TextDocumentItem{ diff --git a/langserver/signature.go b/langserver/signature.go index 8664d416..ea162b04 100644 --- a/langserver/signature.go +++ b/langserver/signature.go @@ -23,12 +23,12 @@ import ( // SignatureHelp is required by the protocol.Server interface func (s *server) SignatureHelp(ctx context.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { - location, err := s.find(¶ms.TextDocumentPositionParams) + location, err := s.cache.Find(¶ms.TextDocumentPositionParams) if err != nil { return nil, nil } - call, ok := location.node.(*promql.Call) + call, ok := location.Node.(*promql.Call) if !ok { return nil, nil } diff --git a/langserver/test_expressions.go b/langserver/test_expressions.go deleted file mode 100644 index f5621ad8..00000000 --- a/langserver/test_expressions.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2019 Tobias Guggenmos -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package langserver - -// nolint:gochecknoglobals -var testExpressions = []string{ - "1", - " 1", - "-1", - "+Inf", - "-Inf", - ".5", - "5.", - "123.4567", - "5e-3", - "5e3", - "0xc", - "0755", - "+5.5e-3", - "-0755", - "1 + 1", - "1 - 1", - "1 * 1", - "1 % 1", - "1 / 1", - "1 == bool 1", - "1 != bool 1", - "1 > bool 1", - "1 >= bool 1", - "1 < bool 1", - "1 <= bool 1", - "+1 + -2 * 1", - "1 + 2/(3*1)", - "1 < bool 2 - 1 * 2", - "-some_metric", - "+some_metric", - "", - "# just a comment\n\n", - "1+", - ".", - "2.5.", - "100..4", - "0deadbeef", - "1 /", - "*1", - "(1))", - "((1)", - "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", - "(", - "1 and 1", - "1 == 1", - "1 or 1", - "1 unless 1", - "1 !~ 1", - "1 =~ 1", - `-"string"`, - `-test[5m]`, - `*test`, - "1 offset 1d", - "a - on(b) ignoring(c) d", - "foo * bar", - "foo == 1", - "foo == bool 1", - "2.5 / bar", - "foo and bar", - "foo unless bar", - "foo + bar or bla and blub", - "foo and bar unless baz or qux", - "bar + on(foo) bla / on(baz, buz) group_right(test) blub", - "foo * on(test,blub) bar", - "foo * on(test,blub) group_left bar", - "foo and on(test,blub) bar", - "foo and on() bar", - "foo and ignoring(test,blub) bar", - "foo and ignoring() bar", - "foo unless on(bar) baz", - "foo / on(test,blub) group_left(bar) bar", - "foo / ignoring(test,blub) group_left(blub) bar", - "foo - on(test,blub) group_right(bar,foo) bar", - "foo - ignoring(test,blub) group_right(bar,foo) bar", - "foo and 1", - "1 and foo", - "foo or 1", - "1 or foo", - "foo unless 1", - "1 unless foo", - "1 or on(bar) foo", - "foo == on(bar) 10", - "foo and on(bar) group_left(baz) bar", - "foo and on(bar) group_right(baz) bar", - "foo or on(bar) group_left(baz) bar", - "foo or on(bar) group_right(baz) bar", - "foo unless on(bar) group_left(baz) bar", - "foo unless on(bar) group_right(baz) bar", - `http_requests{group="production"} + on(instance) group_left(job,instance) cpu_count{type="smp"}`, - "foo + bool bar", - "foo + bool 10", - "foo and bool 10", - "foo", - "foo offset 5m", - `foo:bar{a="bc"}`, - `foo{NaN='bc'}`, - `foo{a="b", foo!="bar", test=~"test", bar!~"baz"}`, - `{`, - `}`, - `some{`, - `some}`, - `some_metric{a=b}`, - `some_metric{a:b="b"}`, - `foo{a*"b"}`, - `foo{a>="b"}`, - "some_metric{a=\"\xff\"}", - `foo{gibberish}`, - `foo{1}`, - `{}`, - `{x=""}`, - `{x=~".*"}`, - `{x!~".+"}`, - `{x!="a"}`, - `foo{__name__="bar"}`, - "test[5s]", - "test[5m]", - "test[5h] OFFSET 5m", - "test[5d] OFFSET 10s", - "test[5w] offset 2w", - `test{a="b"}[5y] OFFSET 3d`, - `foo[5mm]`, - `foo[0m]`, - `foo[5m30s]`, - `foo[5m] OFFSET 1h30m`, - `foo["5m"]`, - `foo[]`, - `foo[1]`, - `some_metric[5m] OFFSET 1`, - `some_metric[5m] OFFSET 1mm`, - `some_metric[5m] OFFSET`, - `some_metric OFFSET 1m[5m]`, - `(foo + bar)[5m]`, - "sum by (foo)(some_metric)", - "avg by (foo)(some_metric)", - "max by (foo)(some_metric)", - "sum without (foo) (some_metric)", - "sum (some_metric) without (foo)", - "stddev(some_metric)", - "stdvar by (foo)(some_metric)", - "sum by ()(some_metric)", - "topk(5, some_metric)", - "count_values(\"value\", some_metric)", - "sum without(and, by, avg, count, alert, annotations)(some_metric)", - "sum without(==)(some_metric)", - `sum some_metric by (test)`, - `sum (some_metric) by test`, - `sum (some_metric) by test`, - `sum () by (test)`, - "MIN keep_common (some_metric)", - "MIN (some_metric) keep_common", - `sum (some_metric) without (test) by (test)`, - `sum without (test) (some_metric) by (test)`, - `topk(some_metric)`, - `topk(some_metric, other_metric)`, - `count_values(5, other_metric)`, - "time()", - `floor(some_metric{foo!="bar"})`, - "rate(some_metric[5m])", - "round(some_metric)", - "round(some_metric, 5)", - "floor()", - "floor(some_metric, other_metric)", - "floor(1)", - "non_existent_function_far_bar()", - "rate(some_metric)", - "label_replace(a, `b`, `c\xff`, `d`, `.*`)", - "-=", - "++-++-+-+-<", - "e-+=/(0)", - `"double-quoted string \" with escaped quote"`, - `'single-quoted string \' with escaped quote'`, - "`backtick-quoted string`", - `"\a\b\f\n\r\t\v\\\" - \xFF\377\u1234\U00010111\U0001011111☺"`, - `'\a\b\f\n\r\t\v\\\' - \xFF\377\u1234\U00010111\U0001011111☺'`, - "`" + `\a\b\f\n\r\t\v\\\"\' - \xFF\377\u1234\U00010111\U0001011111☺` + "`", - "`\\``", - `"\`, - `"\c"`, - `"\x."`, - `foo{bar="baz"}[10m:6s]`, - `foo[10m:]`, - `min_over_time(rate(foo{bar="baz"}[2s])[5m:5s])`, - `min_over_time(rate(foo{bar="baz"}[2s])[5m:])[4m:3s]`, - `min_over_time(rate(foo{bar="baz"}[2s])[5m:] offset 4m)[4m:3s]`, - "sum without(and, by, avg, count, alert, annotations)(some_metric) [30m:10s]", - `some_metric OFFSET 1m [10m:5s]`, - `(foo + bar{nm="val"})[5m:]`, - `(foo + bar{nm="val"})[5m:] offset 10m`, - "test[5d] OFFSET 10s [10m:5s]", - `(foo + bar{nm="val"})[5m:][10m:5s]`, - `{} 1 2 3`, - `{a="b"} -1 2 3`, - `my_metric 1 2 3`, - `my_metric{} 1 2 3`, - `my_metric{a="b"} 1 2 3`, - `my_metric{a="b"} 1 2 3-10x4`, - `my_metric{a="b"} 1 2 3-0x4`, - `my_metric{a="b"} 1 3 _ 5 _x4`, - `my_metric{a="b"} 1 3 _ 5 _a4`, - `my_metric{a="b"} 1 -1`, - `my_metric{a="b"} 1 +1`, - `my_metric{a="b"} 1 -1 -3-10x4 7 9 +5`, - `my_metric{a="b"} 1 +1 +4 -6 -2 8`, - `my_metric{a="b"} 1 2 3 `, - `my_metric{a="b"} -3-3 -3`, - `my_metric{a="b"} -3 -3-3`, - `my_metric{a="b"} -3 _-2`, - `my_metric{a="b"} -3 3+3x4-4`, -}