diff options
author | Sam McCall <sam.mccall@gmail.com> | 2017-12-20 16:06:05 +0000 |
---|---|---|
committer | Sam McCall <sam.mccall@gmail.com> | 2017-12-20 16:06:05 +0000 |
commit | ecb55af7afac66e639fa85982483925eb6a68ca3 (patch) | |
tree | 2c6b7e22ccfcc2a8bdfc8e50b55c38242de2bf8c | |
parent | d1ca8b929f50d952ca6d1650a61cef2b6d2cedf3 (diff) |
[clangd] Switch xrefs and documenthighlight to annotated-code unit tests. NFC
Summary:
The goal here is again to make it easier to read and write the tests.
I've extracted `parseTextMarker` from CodeCompleteTests into an `Annotations`
class, adding features to it:
- as well as points `^s` it allows ranges `[[...]]`
- multiple points and ranges are supported
- points and ranges may be named: `$name^` and `$name[[...]]`
These features are used for the xrefs tests. This also paves the way for
replacing the lit diagnostics.test with more readable unit tests, using named
ranges.
Alternative considered: `TestSelectionRange` in clang-refactor/TestSupport
Main problems were:
- delimiting the end of ranges is awkward, requiring counting
- comment syntax is long and at least as cryptic for most cases
- no separate syntax for point vs range, which keeps xrefs tests concise
- Still need to convert to Position everywhere
- Still need helpers for common case of expecting exactly one point/range
(I'll probably promote the extra `PrintTo`s from some of the core Protocol types
into `operator<<` in `Protocol.h` itself in a separate, prior patch...)
Reviewers: ioeric
Subscribers: klimek, mgorny, ilya-biryukov, cfe-commits
Differential Revision: https://reviews.llvm.org/D41432
git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/trunk@321184 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | test/clangd/definitions.test | 421 | ||||
-rw-r--r-- | test/clangd/documenthighlight.test | 42 | ||||
-rw-r--r-- | test/clangd/xrefs.test | 67 | ||||
-rw-r--r-- | unittests/clangd/Annotations.cpp | 87 | ||||
-rw-r--r-- | unittests/clangd/Annotations.h | 69 | ||||
-rw-r--r-- | unittests/clangd/CMakeLists.txt | 2 | ||||
-rw-r--r-- | unittests/clangd/CodeCompleteTests.cpp | 55 | ||||
-rw-r--r-- | unittests/clangd/Matchers.h | 1 | ||||
-rw-r--r-- | unittests/clangd/XRefsTests.cpp | 218 |
9 files changed, 462 insertions, 500 deletions
diff --git a/test/clangd/definitions.test b/test/clangd/definitions.test deleted file mode 100644 index 9cc26109..00000000 --- a/test/clangd/definitions.test +++ /dev/null @@ -1,421 +0,0 @@ -# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
-# It is absolutely vital that this file has CRLF line endings.
-#
-Content-Length: 125
-
-{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
-
-Content-Length: 172
-
-{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"int main() {\nint a;\na;\n}\n"}}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":0}}}
-# Go to local variable
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 5,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":1}}}
-# Go to local variable, end of token
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 5,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 214
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":2},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo bar = { x : 1 };\n}\n"}]}}
-
-Content-Length: 149
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":14}}}
-# Go to field, GNU old-style field designator
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 5,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 215
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":3},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo baz = { .x = 2 };\n}\n"}]}}
-
-Content-Length: 149
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":15}}}
-# Go to field, field designator
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 5,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 188
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":4},"contentChanges":[{"text":"int main() {\n main();\n return 0;\n}"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":3}}}
-# Go to function declaration, function call
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 1,
-# CHECK-NEXT: "line": 3
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 209
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"struct Foo {\n};\nint main() {\n Foo bar;\n return 0;\n}\n"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":3}}}
-# Go to struct declaration, new struct instance
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 1,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 232
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"namespace n1 {\nstruct Foo {\n};\n}\nint main() {\n n1::Foo bar;\n return 0;\n}\n"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":4}}}
-# Go to struct declaration, new struct instance, qualified name
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 1,
-# CHECK-NEXT: "line": 3
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 216
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":6},"contentChanges":[{"text":"struct Foo {\n int x;\n};\nint main() {\n Foo bar;\n bar.x;\n}\n"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}}
-# Go to field declaration, field reference
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 7,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 2,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 221
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n void x();\n};\nint main() {\n Foo bar;\n bar.x();\n}\n"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}}
-# Go to method declaration, method call
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 10,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 2,
-# CHECK-NEXT: "line": 1
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 241
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n};\ntypedef Foo TypedefFoo;\nint main() {\n TypedefFoo bar;\n return 0;\n}\n"}]}}
-
-Content-Length: 149
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":10}}}
-# Go to typedef
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 22,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 253
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"template <typename MyTemplateParam>\nvoid foo() {\n MyTemplateParam a;\n}\nint main() {\n return 0;\n}\n"}]}}
-
-Content-Length: 149
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":13}}}
-# Go to template type parameter. Fails until clangIndex is modified to handle those.
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": []
-Content-Length: 257
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\nstatic void bar() {}\n};\n}\nint main() {\n ns::Foo::bar();\n return 0;\n}\n"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":6,"character":4}}}
-# Go to namespace, static method call
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 1,
-# CHECK-NEXT: "line": 4
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 0,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 265
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\n int field;\n Foo(int param) : field(param) {}\n};\n}\nint main() {\n return 0;\n}\n"}]}}
-
-Content-Length: 149
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":21}}}
-# Go to field, member initializer
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 11,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 2,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 204
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define MY_MACRO 0\nint main() {\n return MY_MACRO;\n}\n"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":9}}}
-# Go to macro.
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 18,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 8,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 217
-
-{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define FOO 1\nint a = FOO;\n#define FOO 2\nint b = FOO;\n#undef FOO\n"}]}}
-
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":8}}}
-# Go to macro, re-defined later
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 13,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 8,
-# CHECK-NEXT: "line": 0
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":8}}}
-# Go to macro, undefined later
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 13,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 8,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}}
-# Go to macro, being undefined
-# CHECK: "id": 1,
-# CHECK-NEXT: "jsonrpc": "2.0",
-# CHECK-NEXT: "result": [
-# CHECK-NEXT: {
-# CHECK-NEXT: "range": {
-# CHECK-NEXT: "end": {
-# CHECK-NEXT: "character": 13,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: },
-# CHECK-NEXT: "start": {
-# CHECK-NEXT: "character": 8,
-# CHECK-NEXT: "line": 2
-# CHECK-NEXT: }
-# CHECK-NEXT: },
-# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
-# CHECK-NEXT: }
-# CHECK-NEXT: ]
-Content-Length: 156
-
-{"jsonrpc":"2.0","id":2,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///doesnotexist.cpp"},"position":{"line":4,"character":7}}}
-# CHECK: "error": {
-# CHECK-NEXT: "code": -32602,
-# CHECK-NEXT: "message": "findDefinitions called on non-added file"
-# CHECK-NEXT: },
-# CHECK-NEXT: "id": 2,
-# CHECK-NEXT: "jsonrpc": "2.0"
-Content-Length: 48
-
-{"jsonrpc":"2.0","id":10000,"method":"shutdown"}
-Content-Length: 33
-
-{"jsonrpc":"2.0","method":"exit"}
diff --git a/test/clangd/documenthighlight.test b/test/clangd/documenthighlight.test deleted file mode 100644 index c9263324..00000000 --- a/test/clangd/documenthighlight.test +++ /dev/null @@ -1,42 +0,0 @@ -# RUN: clangd -run-synchronously < %s | FileCheck %s -# It is absolutely vital that this file has CRLF line endings. -# -Content-Length: 125 - -{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} - -Content-Length: 479 - -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#define MACRO 1\nnamespace ns1 {\nstruct MyClass {\nint xasd;\nvoid anotherOperation() {\n}\nstatic int foo(MyClass*) {\nreturn 0;\n}\n\n};\nstruct Foo {\nint xasd;\n};\n}\nint main() {\nint bonjour;\nbonjour = 2;\nint test1 = bonjour;\nns1::Foo bar = { xasd : 1};\nbar.xasd = 3;\nns1::MyClass* Params;\nParams->anotherOperation();\n}\n"}}} - -Content-Length: 156 - -{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":2}}} -# Verify local variable -# CHECK: {"id":1,"jsonrpc":"2.0","result":[{"kind":1,"range":{"end":{"character":11,"line":16},"start":{"character":4,"line":16}}},{"kind":3,"range":{"end":{"character":7,"line":17},"start":{"character":0,"line":17}}},{"kind":2,"range":{"end":{"character":19,"line":18},"start":{"character":12,"line":18}}}]} - -Content-Length: 157 - -{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":18,"character":17}}} -# Verify struct highlight -# CHECK: {"id":1,"jsonrpc":"2.0","result":[{"kind":1,"range":{"end":{"character":11,"line":16},"start":{"character":4,"line":16}}},{"kind":3,"range":{"end":{"character":7,"line":17},"start":{"character":0,"line":17}}},{"kind":2,"range":{"end":{"character":19,"line":18},"start":{"character":12,"line":18}}}]} - -Content-Length: 157 - -{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":21,"character":10}}} -# Verify method highlight -# CHECK: {"id":1,"jsonrpc":"2.0","result":[{"kind":1,"range":{"end":{"character":14,"line":2},"start":{"character":7,"line":2}}},{"kind":1,"range":{"end":{"character":22,"line":6},"start":{"character":15,"line":6}}},{"kind":1,"range":{"end":{"character":12,"line":21},"start":{"character":5,"line":21}}}]} - -Content-Length: 157 - -{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":18,"character":14}}} -# Verify Read-access of a symbol (kind = 2) -# CHECK: {"id":1,"jsonrpc":"2.0","result":[{"kind":1,"range":{"end":{"character":11,"line":16},"start":{"character":4,"line":16}}},{"kind":3,"range":{"end":{"character":7,"line":17},"start":{"character":0,"line":17}}},{"kind":2,"range":{"end":{"character":19,"line":18},"start":{"character":12,"line":18}}}]} - -Content-Length: 48 - -{"jsonrpc":"2.0","id":10000,"method":"shutdown"} - -Content-Length: 33 - -{"jsonrpc":"2.0":"method":"exit"}
\ No newline at end of file diff --git a/test/clangd/xrefs.test b/test/clangd/xrefs.test new file mode 100644 index 00000000..ad3c241d --- /dev/null +++ b/test/clangd/xrefs.test @@ -0,0 +1,67 @@ +# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
+# It is absolutely vital that this file has CRLF line endings.
+#
+Content-Length: 125
+
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
+
+Content-Length: 165
+
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"int x = 0;\nint y = x;"}}}
+
+Content-Length: 148
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":8}}}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 9,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+Content-Length: 155
+
+{"jsonrpc":"2.0","id":1,"method":"textDocument/documentHighlight","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":8}}}
+# CHECK: "id": 1
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "kind": 1,
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 5,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 9,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+Content-Length: 48
+
+{"jsonrpc":"2.0","id":10000,"method":"shutdown"}
diff --git a/unittests/clangd/Annotations.cpp b/unittests/clangd/Annotations.cpp new file mode 100644 index 00000000..69532214 --- /dev/null +++ b/unittests/clangd/Annotations.cpp @@ -0,0 +1,87 @@ +//===--- Annotations.cpp - Annotated source code for unit tests -*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +#include "Annotations.h" +#include "SourceCode.h" + +namespace clang { +namespace clangd { +using namespace llvm; + +// Crash if the assertion fails, printing the message and testcase. +// More elegant error handling isn't needed for unit tests. +static void require(bool Assertion, const char *Msg, llvm::StringRef Code) { + if (!Assertion) { + llvm::errs() << "Annotated testcase: " << Msg << "\n" << Code << "\n"; + llvm_unreachable("Annotated testcase assertion failed!"); + } +} + +Annotations::Annotations(StringRef Text) { + auto Here = [this] { return offsetToPosition(Code, Code.size()); }; + auto Require = [this, Text](bool Assertion, const char *Msg) { + require(Assertion, Msg, Text); + }; + Optional<StringRef> Name; + SmallVector<std::pair<StringRef, Position>, 8> OpenRanges; + + Code.reserve(Text.size()); + while (!Text.empty()) { + if (Text.consume_front("^")) { + Points[Name.getValueOr("")].push_back(Here()); + Name = None; + continue; + } + if (Text.consume_front("[[")) { + OpenRanges.emplace_back(Name.getValueOr(""), Here()); + Name = None; + continue; + } + Require(!Name, "$name should be followed by ^ or [["); + if (Text.consume_front("]]")) { + Require(!OpenRanges.empty(), "unmatched ]]"); + Ranges[OpenRanges.back().first].push_back( + {OpenRanges.back().second, Here()}); + OpenRanges.pop_back(); + continue; + } + if (Text.consume_front("$")) { + Name = Text.take_while(llvm::isAlnum); + Text = Text.drop_front(Name->size()); + continue; + } + Code.push_back(Text.front()); + Text = Text.drop_front(); + } + Require(!Name, "unterminated $name"); + Require(OpenRanges.empty(), "unmatched [["); +} + +Position Annotations::point(llvm::StringRef Name) const { + auto I = Points.find(Name); + require(I != Points.end() && I->getValue().size() == 1, + "expected exactly one point", Code); + return I->getValue()[0]; +} +std::vector<Position> Annotations::points(llvm::StringRef Name) const { + auto P = Points.lookup(Name); + return {P.begin(), P.end()}; +} +Range Annotations::range(llvm::StringRef Name) const { + auto I = Ranges.find(Name); + require(I != Ranges.end() && I->getValue().size() == 1, + "expected exactly one range", Code); + return I->getValue()[0]; +} +std::vector<Range> Annotations::ranges(llvm::StringRef Name) const { + auto R = Ranges.lookup(Name); + return {R.begin(), R.end()}; +} + +} // namespace clangd +} // namespace clang diff --git a/unittests/clangd/Annotations.h b/unittests/clangd/Annotations.h new file mode 100644 index 00000000..b376dd3f --- /dev/null +++ b/unittests/clangd/Annotations.h @@ -0,0 +1,69 @@ +//===--- Annotations.h - Annotated source code for tests --------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===---------------------------------------------------------------------===// +// +// Annotations lets you mark points and ranges inside source code, for tests: +// +// Annotations Example(R"cpp( +// int complete() { x.pri^ } // ^ indicates a point +// void err() { [["hello" == 42]]; } // [[this is a range]] +// $definition^class Foo{}; // points can be named: "definition" +// $fail[[static_assert(false, "")]] // ranges can be named too: "fail" +// )cpp"); +// +// StringRef Code = Example.code(); // annotations stripped. +// std::vector<Position> PP = Example.points(); // all unnamed points +// Position P = Example.point(); // there must be exactly one +// Range R = Example.range("fail"); // find named ranges +// +// Points/ranges are coordinates into `code()` which is stripped of annotations. +// +// Ranges may be nested (and points can be inside ranges), but there's no way +// to define general overlapping ranges. +// +//===---------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_ANNOTATIONS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_ANNOTATIONS_H +#include "Protocol.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace clangd { + +class Annotations { +public: + // Parses the annotations from Text. Crashes if it's malformed. + Annotations(llvm::StringRef Text); + + // The input text with all annotations stripped. + // All points and ranges are relative to this stripped text. + llvm::StringRef code() const { return Code; } + + // Returns the position of the point marked by ^ (or $name^) in the text. + // Crashes if there isn't exactly one. + Position point(llvm::StringRef Name = "") const; + // Returns the position of all points marked by ^ (or $name^) in the text. + std::vector<Position> points(llvm::StringRef Name = "") const; + + // Returns the location of the range marked by [[ ]] (or $name[[ ]]). + // Crashes if there isn't exactly one. + Range range(llvm::StringRef Name = "") const; + // Returns the location of all ranges marked by [[ ]] (or $name[[ ]]). + std::vector<Range> ranges(llvm::StringRef Name = "") const; + +private: + std::string Code; + llvm::StringMap<llvm::SmallVector<Position, 1>> Points; + llvm::StringMap<llvm::SmallVector<Range, 1>> Ranges; +}; + +} // namespace clangd +} // namespace clang +#endif diff --git a/unittests/clangd/CMakeLists.txt b/unittests/clangd/CMakeLists.txt index 36853a17..ffe11b77 100644 --- a/unittests/clangd/CMakeLists.txt +++ b/unittests/clangd/CMakeLists.txt @@ -9,6 +9,7 @@ include_directories( ) add_extra_unittest(ClangdTests + Annotations.cpp ClangdTests.cpp CodeCompleteTests.cpp ContextTests.cpp @@ -20,6 +21,7 @@ add_extra_unittest(ClangdTests TraceTests.cpp SourceCodeTests.cpp SymbolCollectorTests.cpp + XRefsTests.cpp ) target_link_libraries(ClangdTests diff --git a/unittests/clangd/CodeCompleteTests.cpp b/unittests/clangd/CodeCompleteTests.cpp index 312725fc..7411e1c3 100644 --- a/unittests/clangd/CodeCompleteTests.cpp +++ b/unittests/clangd/CodeCompleteTests.cpp @@ -7,7 +7,9 @@ // //===----------------------------------------------------------------------===// +#include "Annotations.h" #include "ClangdServer.h" +#include "CodeComplete.h" #include "Compiler.h" #include "Context.h" #include "Matchers.h" @@ -60,27 +62,6 @@ class IgnoreDiagnostics : public DiagnosticsConsumer { PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) override {} }; -struct StringWithPos { - std::string Text; - clangd::Position MarkerPos; -}; - -/// Accepts a source file with a cursor marker ^. -/// Returns the source file with the marker removed, and the marker position. -StringWithPos parseTextMarker(StringRef Text) { - std::size_t MarkerOffset = Text.find('^'); - assert(MarkerOffset != StringRef::npos && "^ wasn't found in Text."); - - std::string WithoutMarker; - WithoutMarker += Text.take_front(MarkerOffset); - WithoutMarker += Text.drop_front(MarkerOffset + 1); - assert(StringRef(WithoutMarker).find('^') == StringRef::npos && - "There were multiple occurences of ^ inside Text"); - - auto MarkerPos = offsetToPosition(WithoutMarker, MarkerOffset); - return {std::move(WithoutMarker), MarkerPos}; -} - // GMock helpers for matching completion items. MATCHER_P(Named, Name, "") { return arg.insertText == Name; } MATCHER_P(Labeled, Label, "") { return arg.label == Label; } @@ -112,9 +93,9 @@ CompletionList completions(StringRef Text, ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); auto File = getVirtualTestFilePath("foo.cpp"); - auto Test = parseTextMarker(Text); - Server.addDocument(Context::empty(), File, Test.Text); - return Server.codeComplete(Context::empty(), File, Test.MarkerPos, Opts) + Annotations Test(Text); + Server.addDocument(Context::empty(), File, Test.code()); + return Server.codeComplete(Context::empty(), File, Test.point(), Opts) .get() .second.Value; } @@ -291,13 +272,13 @@ TEST(CompletionTest, CheckContentsOverride) { auto File = getVirtualTestFilePath("foo.cpp"); Server.addDocument(Context::empty(), File, "ignored text!"); - auto Example = parseTextMarker("int cbc; int b = ^;"); - auto Results = - Server - .codeComplete(Context::empty(), File, Example.MarkerPos, - clangd::CodeCompleteOptions(), StringRef(Example.Text)) - .get() - .second.Value; + Annotations Example("int cbc; int b = ^;"); + auto Results = Server + .codeComplete(Context::empty(), File, Example.point(), + clangd::CodeCompleteOptions(), + StringRef(Example.code())) + .get() + .second.Value; EXPECT_THAT(Results.items, Contains(Named("cbc"))); } @@ -392,9 +373,9 @@ SignatureHelp signatures(StringRef Text) { ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), /*StorePreamblesInMemory=*/true); auto File = getVirtualTestFilePath("foo.cpp"); - auto Test = parseTextMarker(Text); - Server.addDocument(Context::empty(), File, Test.Text); - auto R = Server.signatureHelp(Context::empty(), File, Test.MarkerPos); + Annotations Test(Text); + Server.addDocument(Context::empty(), File, Test.code()); + auto R = Server.signatureHelp(Context::empty(), File, Test.point()); assert(R); return R.get().Value; } @@ -573,13 +554,13 @@ TEST(CompletionTest, ASTIndexMultiFile) { .wait(); auto File = getVirtualTestFilePath("bar.cpp"); - auto Test = parseTextMarker(R"cpp( + Annotations Test(R"cpp( namespace ns { class XXX {}; void fooooo() {} } void f() { ns::^ } )cpp"); - Server.addDocument(Context::empty(), File, Test.Text).wait(); + Server.addDocument(Context::empty(), File, Test.code()).wait(); - auto Results = Server.codeComplete(Context::empty(), File, Test.MarkerPos, {}) + auto Results = Server.codeComplete(Context::empty(), File, Test.point(), {}) .get() .second.Value; // "XYZ" and "foo" are not included in the file being completed but are still diff --git a/unittests/clangd/Matchers.h b/unittests/clangd/Matchers.h index 073a5525..81bc110b 100644 --- a/unittests/clangd/Matchers.h +++ b/unittests/clangd/Matchers.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H #define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_MATCHERS_H +#include "Protocol.h" #include "gmock/gmock.h" namespace clang { diff --git a/unittests/clangd/XRefsTests.cpp b/unittests/clangd/XRefsTests.cpp new file mode 100644 index 00000000..db158e7b --- /dev/null +++ b/unittests/clangd/XRefsTests.cpp @@ -0,0 +1,218 @@ +//===-- XRefsTests.cpp ---------------------------*- C++ -*--------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "Annotations.h" +#include "ClangdUnit.h" +#include "Matchers.h" +#include "XRefs.h" +#include "clang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/PCHContainerOperations.h" +#include "clang/Frontend/Utils.h" +#include "llvm/Support/Path.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +using namespace llvm; + +void PrintTo(const DocumentHighlight &V, std::ostream *O) { + llvm::raw_os_ostream OS(*O); + OS << V.range; + if (V.kind == DocumentHighlightKind::Read) + OS << "(r)"; + if (V.kind == DocumentHighlightKind::Write) + OS << "(w)"; +} + +namespace { +using testing::ElementsAre; +using testing::Field; +using testing::Matcher; +using testing::UnorderedElementsAreArray; + +// FIXME: this is duplicated with FileIndexTests. Share it. +ParsedAST build(StringRef Code) { + auto CI = createInvocationFromCommandLine({"clang", "-xc++", "Foo.cpp"}); + auto Buf = MemoryBuffer::getMemBuffer(Code); + auto AST = ParsedAST::Build( + Context::empty(), std::move(CI), nullptr, std::move(Buf), + std::make_shared<PCHContainerOperations>(), vfs::getRealFileSystem()); + assert(AST.hasValue()); + return std::move(*AST); +} + +// Extracts ranges from an annotated example, and constructs a matcher for a +// highlight set. Ranges should be named $read/$write as appropriate. +Matcher<const std::vector<DocumentHighlight> &> +HighlightsFrom(const Annotations &Test) { + std::vector<DocumentHighlight> Expected; + auto Add = [&](const Range &R, DocumentHighlightKind K) { + Expected.emplace_back(); + Expected.back().range = R; + Expected.back().kind = K; + }; + for (const auto &Range : Test.ranges()) + Add(Range, DocumentHighlightKind::Text); + for (const auto &Range : Test.ranges("read")) + Add(Range, DocumentHighlightKind::Read); + for (const auto &Range : Test.ranges("write")) + Add(Range, DocumentHighlightKind::Write); + return UnorderedElementsAreArray(Expected); +} + +TEST(HighlightsTest, All) { + const char *Tests[] = { + R"cpp(// Local variable + int main() { + int [[bonjour]]; + $write[[^bonjour]] = 2; + int test1 = $read[[bonjour]]; + } + )cpp", + + R"cpp(// Struct + namespace ns1 { + struct [[MyClass]] { + static void foo([[MyClass]]*) {} + }; + } // namespace ns1 + int main() { + ns1::[[My^Class]]* Params; + } + )cpp", + + R"cpp(// Function + int [[^foo]](int) {} + int main() { + [[foo]]([[foo]](42)); + auto *X = &[[foo]]; + } + )cpp", + }; + for (const char *Test : Tests) { + Annotations T(Test); + auto AST = build(T.code()); + EXPECT_THAT(findDocumentHighlights(Context::empty(), AST, T.point()), + HighlightsFrom(T)) + << Test; + } +} + +MATCHER_P(RangeIs, R, "") { return arg.range == R; } + +TEST(GoToDefinition, All) { + const char *Tests[] = { + R"cpp(// Local variable + int main() { + [[int bonjour]]; + ^bonjour = 2; + int test1 = bonjour; + } + )cpp", + + R"cpp(// Struct + namespace ns1 { + [[struct MyClass {}]]; + } // namespace ns1 + int main() { + ns1::My^Class* Params; + } + )cpp", + + R"cpp(// Function definition via pointer + [[int foo(int) {}]] + int main() { + auto *X = &^foo; + } + )cpp", + + R"cpp(// Function declaration via call + [[int foo(int)]]; + int main() { + return ^foo(42); + } + )cpp", + + R"cpp(// Field + struct Foo { [[int x]]; }; + int main() { + Foo bar; + bar.^x; + } + )cpp", + + R"cpp(// Field, member initializer + struct Foo { + [[int x]]; + Foo() : ^x(0) {} + }; + )cpp", + + R"cpp(// Field, GNU old-style field designator + struct Foo { [[int x]]; }; + int main() { + Foo bar = { ^x : 1 }; + } + )cpp", + + R"cpp(// Field, field designator + struct Foo { [[int x]]; }; + int main() { + Foo bar = { .^x = 2 }; + } + )cpp", + + R"cpp(// Method call + struct Foo { [[int x()]]; }; + int main() { + Foo bar; + bar.^x(); + } + )cpp", + + R"cpp(// Typedef + [[typedef int Foo]]; + int main() { + ^Foo bar; + } + )cpp", + + /* FIXME: clangIndex doesn't handle template type parameters + R"cpp(// Template type parameter + template <[[typename T]]> + void foo() { ^T t; } + )cpp", */ + + R"cpp(// Namespace + [[namespace ns { + struct Foo { static void bar(); } + }]] // namespace ns + int main() { ^ns::Foo::bar(); } + )cpp", + + R"cpp(// Macro + #define MACRO 0 + #define [[MACRO 1]] + int main() { return ^MACRO; } + #define MACRO 2 + #undef macro + )cpp", + }; + for (const char *Test : Tests) { + Annotations T(Test); + auto AST = build(T.code()); + EXPECT_THAT(findDefinitions(Context::empty(), AST, T.point()), + ElementsAre(RangeIs(T.range()))) + << Test; + } +} + +} // namespace +} // namespace clangd +} // namespace clang |