## Summary - The `eventide` dep was renamed to [kotatsu](https://github.com/clice-io/kotatsu) with a broad rename of CMake identifiers, namespaces, header paths, and a few module reorgs (`serde` → `codec`, `reflection` → `meta`, `common` → `support`). Align clice to the new names. - CMake: FetchContent target, option prefix (`ETD_*` → `KOTA_*`, `ETD_SERDE_*` → `KOTA_CODEC_*`), target names (`eventide::{ipc::lsp,serde::toml,deco,zest}` → `kota::{ipc::lsp,codec::toml,deco,zest}`). - Namespaces: `eventide::` → `kota::`, `eventide::serde::` → `kota::codec::`, `eventide::refl::` → `kota::meta::`. The short `et` alias is dropped — all usages now spell `kota::` directly. - Headers: `eventide/*` → `kota/*`, including special cases `serde/serde/raw_value.h` → `codec/raw_value.h`, `ipc/json_codec.h` → `ipc/codec/json.h`, `common/meta.h` → `support/type_traits.h`, `common/ranges.h` → `support/ranges.h`. - Kotatsu split `JsonPeer` / `BincodePeer` out of `ipc/peer.h` into the codec-specific headers; added `kota/ipc/codec/{json,bincode}.h` includes where those types are used. - Depends on clice-io/kotatsu#110 (already merged) to prevent `-Wall -Wextra -Werror` from transitively propagating out of `kota::project_options`. ## Test plan - [x] `pixi run unit-test RelWithDebInfo` — 518/518 pass (9 skipped, unchanged from main) - [x] `pixi run integration-test RelWithDebInfo` — 119/119 pass - [x] `pixi run smoke-test RelWithDebInfo` — 2/2 pass - [x] `pixi run format` clean ## Notes - `tests/smoke/rapid_edit.jsonl` was intentionally left untouched: the embedded `#include "eventide/..."` strings are frozen snapshots of file contents the client sent at record time, not clice source. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Updated internal dependencies from `eventide` to `kota`, including async runtime, IPC transport, serialization codec, and metadata libraries. * Updated build configuration and CMake variables to align with the new dependency. * **Refactor** * Migrated internal implementation to use `kota` namespace and APIs throughout the codebase. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
418 lines
11 KiB
C++
418 lines
11 KiB
C++
#include <algorithm>
|
|
#include <vector>
|
|
|
|
#include "test/annotation.h"
|
|
#include "test/test.h"
|
|
#include "feature/feature.h"
|
|
|
|
namespace clice::testing {
|
|
|
|
namespace {
|
|
|
|
namespace protocol = kota::ipc::protocol;
|
|
|
|
TEST_SUITE(CodeCompletion) {
|
|
|
|
std::vector<protocol::CompletionItem> items;
|
|
llvm::IntrusiveRefCntPtr<TestVFS> vfs;
|
|
std::string main_path;
|
|
|
|
void code_complete(llvm::StringRef code, feature::CodeCompletionOptions options = {}) {
|
|
vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
|
|
|
|
CompilationParams params;
|
|
auto annotation = AnnotatedSource::from(code);
|
|
|
|
vfs->add("main.cpp", annotation.content);
|
|
params.vfs = vfs;
|
|
main_path = TestVFS::path("main.cpp");
|
|
params.arguments =
|
|
{"clang++", "-std=c++20", "-ffreestanding", "-Xclang", "-undef", main_path.c_str()};
|
|
params.completion = {main_path, annotation.offsets.lookup("pos")};
|
|
params.add_remapped_file(main_path, annotation.content);
|
|
|
|
items = feature::code_complete(params, options, feature::PositionEncoding::UTF8);
|
|
}
|
|
|
|
auto find_item(llvm::StringRef label) {
|
|
return std::ranges::find_if(items, [&](const protocol::CompletionItem& item) {
|
|
return item.label == label;
|
|
});
|
|
}
|
|
|
|
TEST_CASE(Score) {
|
|
code_complete(R"cpp(
|
|
int foooo(int x);
|
|
int x = fo$(pos)
|
|
)cpp");
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
ASSERT_TRUE(it->kind.has_value());
|
|
ASSERT_EQ(*it->kind, protocol::CompletionItemKind::Function);
|
|
}
|
|
|
|
TEST_CASE(Signature) {
|
|
code_complete(R"cpp(
|
|
int foooo(int x, float y);
|
|
int x = fo$(pos)
|
|
)cpp");
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
ASSERT_TRUE(it->label_details.has_value());
|
|
// label_details.detail should contain the parameter list.
|
|
ASSERT_TRUE(it->label_details->detail.has_value());
|
|
auto& sig = *it->label_details->detail;
|
|
ASSERT_TRUE(sig.find("int") != std::string::npos);
|
|
ASSERT_TRUE(sig.find("float") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(ReturnType) {
|
|
code_complete(R"cpp(
|
|
double foooo(int x);
|
|
int x = fo$(pos)
|
|
)cpp");
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
ASSERT_TRUE(it->label_details.has_value());
|
|
// label_details.description should contain the return type.
|
|
ASSERT_TRUE(it->label_details->description.has_value());
|
|
auto& ret = *it->label_details->description;
|
|
ASSERT_TRUE(ret.find("double") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(Snippet) {
|
|
code_complete(R"cpp(
|
|
int x = tru$(pos)
|
|
)cpp");
|
|
|
|
ASSERT_TRUE(!items.empty());
|
|
}
|
|
|
|
TEST_CASE(Overload) {
|
|
code_complete(R"cpp(
|
|
int foooo(int x);
|
|
int foooo(int x, int y);
|
|
int x = fooo$(pos)
|
|
)cpp");
|
|
|
|
ASSERT_TRUE(!items.empty());
|
|
// With bundling, there should be exactly one "foooo" item.
|
|
auto count = std::ranges::count_if(items, [](const protocol::CompletionItem& item) {
|
|
return item.label == "foooo";
|
|
});
|
|
ASSERT_EQ(count, 1);
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
// Bundled overload should show count in label_details.detail.
|
|
ASSERT_TRUE(it->label_details.has_value());
|
|
ASSERT_TRUE(it->label_details->detail.has_value());
|
|
auto& detail = *it->label_details->detail;
|
|
ASSERT_TRUE(detail.find("overload") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(FilterUnderscore) {
|
|
code_complete(R"cpp(
|
|
int _private_thing;
|
|
int public_thing;
|
|
int x = pu$(pos)
|
|
)cpp");
|
|
|
|
// _private_thing should be filtered when prefix doesn't start with _.
|
|
auto it = find_item("_private_thing");
|
|
ASSERT_TRUE(it == items.end());
|
|
|
|
auto it2 = find_item("public_thing");
|
|
ASSERT_TRUE(it2 != items.end());
|
|
}
|
|
|
|
TEST_CASE(FilterUnderscoreExplicit) {
|
|
code_complete(R"cpp(
|
|
int _private_thing;
|
|
int x = _p$(pos)
|
|
)cpp");
|
|
|
|
// When user types _, underscore-prefixed symbols should appear.
|
|
auto it = find_item("_private_thing");
|
|
ASSERT_TRUE(it != items.end());
|
|
}
|
|
|
|
TEST_CASE(MethodSignature) {
|
|
code_complete(R"cpp(
|
|
struct Foo {
|
|
int bazzzz(int a, int b);
|
|
};
|
|
|
|
void bar() {
|
|
Foo f;
|
|
f.ba$(pos);
|
|
}
|
|
)cpp");
|
|
|
|
auto it = find_item("bazzzz");
|
|
ASSERT_TRUE(it != items.end());
|
|
ASSERT_TRUE(it->kind.has_value());
|
|
ASSERT_EQ(*it->kind, protocol::CompletionItemKind::Method);
|
|
ASSERT_TRUE(it->label_details.has_value());
|
|
ASSERT_TRUE(it->label_details->detail.has_value());
|
|
auto& sig = *it->label_details->detail;
|
|
ASSERT_TRUE(sig.find("int") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(DeduplicateByLabel) {
|
|
code_complete(R"cpp(
|
|
template <typename T>
|
|
struct Foo {
|
|
Foo() {}
|
|
Foo(T x) {}
|
|
Foo(T x, T y) {}
|
|
};
|
|
|
|
template <typename T>
|
|
Foo(T) -> Foo<T>;
|
|
|
|
void bar() {
|
|
Fo$(pos)
|
|
}
|
|
)cpp");
|
|
|
|
// In bundle mode, "Foo" should appear exactly once (as Class kind),
|
|
// not 3 times (Class + Constructor bundle + deduction guide bundle).
|
|
auto count = std::ranges::count_if(items, [](const protocol::CompletionItem& item) {
|
|
return item.label == "Foo";
|
|
});
|
|
ASSERT_EQ(count, 1);
|
|
|
|
auto it = find_item("Foo");
|
|
ASSERT_TRUE(it != items.end());
|
|
ASSERT_TRUE(it->kind.has_value());
|
|
ASSERT_EQ(*it->kind, protocol::CompletionItemKind::Class);
|
|
}
|
|
|
|
TEST_CASE(ConstructorLabelNoTemplateArgs) {
|
|
// Constructors of class templates should use plain class name as label,
|
|
// not "Foo<T>" or "Foo<_Tp, _Alloc>". This ensures dedup works and
|
|
// insertion text is correct.
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = false;
|
|
code_complete(R"cpp(
|
|
template <typename T, typename U>
|
|
struct Bazzz {
|
|
Bazzz() {}
|
|
Bazzz(T x) {}
|
|
Bazzz(T x, U y) {}
|
|
};
|
|
|
|
template <typename T>
|
|
Bazzz(T) -> Bazzz<T, int>;
|
|
|
|
void bar() {
|
|
Ba$(pos)
|
|
}
|
|
)cpp",
|
|
opts);
|
|
|
|
// Non-bundled mode should produce multiple "Bazzz" items (class + constructors + guide).
|
|
auto count = std::ranges::count_if(items, [](const protocol::CompletionItem& item) {
|
|
return item.label == "Bazzz";
|
|
});
|
|
ASSERT_TRUE(count > 1);
|
|
|
|
// Every item's label must be plain "Bazzz", never "Bazzz<T, U>".
|
|
// And the insertion text must also be "Bazzz" (not the templated form).
|
|
for(auto& item: items) {
|
|
if(item.label.find("Bazzz") != std::string::npos) {
|
|
ASSERT_EQ(item.label, "Bazzz");
|
|
auto& edit = std::get<protocol::TextEdit>(*item.text_edit);
|
|
ASSERT_TRUE(edit.new_text.starts_with("Bazzz"));
|
|
ASSERT_TRUE(edit.new_text.find("<") == std::string::npos);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(NoBundleOverloads) {
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = false;
|
|
code_complete(R"cpp(
|
|
int foooo(int x);
|
|
int foooo(int x, int y);
|
|
double foooo(double d);
|
|
int x = fooo$(pos)
|
|
)cpp",
|
|
opts);
|
|
|
|
// Without bundling, each overload should be a separate item.
|
|
auto count = std::ranges::count_if(items, [](const protocol::CompletionItem& item) {
|
|
return item.label == "foooo";
|
|
});
|
|
ASSERT_TRUE(count >= 3);
|
|
|
|
// Each should have its own signature in label_details.
|
|
for(auto& item: items) {
|
|
if(item.label == "foooo") {
|
|
ASSERT_TRUE(item.label_details.has_value());
|
|
ASSERT_TRUE(item.label_details->detail.has_value());
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE(NoBundleNoDeduplicate) {
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = false;
|
|
code_complete(R"cpp(
|
|
int foooo(int x);
|
|
int foooo(int x, int y);
|
|
double foooo(double d);
|
|
int x = fooo$(pos)
|
|
)cpp",
|
|
opts);
|
|
|
|
// Without bundling, deduplication should NOT apply — each overload
|
|
// should appear as a separate item.
|
|
auto count = std::ranges::count_if(items, [](const protocol::CompletionItem& item) {
|
|
return item.label == "foooo";
|
|
});
|
|
ASSERT_TRUE(count >= 3);
|
|
}
|
|
|
|
TEST_CASE(SnippetFunctionArgs) {
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = false;
|
|
opts.enable_function_arguments_snippet = true;
|
|
code_complete(R"cpp(
|
|
int foooo(int x, float y);
|
|
int z = fo$(pos)
|
|
)cpp",
|
|
opts);
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
// Should have snippet format.
|
|
ASSERT_TRUE(it->insert_text_format.has_value());
|
|
ASSERT_EQ(*it->insert_text_format, protocol::InsertTextFormat::Snippet);
|
|
// textEdit should contain placeholders.
|
|
auto& edit = std::get<protocol::TextEdit>(*it->text_edit);
|
|
ASSERT_TRUE(edit.new_text.find("${1:") != std::string::npos);
|
|
ASSERT_TRUE(edit.new_text.find("${2:") != std::string::npos);
|
|
ASSERT_TRUE(edit.new_text.find("(") != std::string::npos);
|
|
ASSERT_TRUE(edit.new_text.find(")") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(SnippetNoArgs) {
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = false;
|
|
opts.enable_function_arguments_snippet = true;
|
|
code_complete(R"cpp(
|
|
void foooo();
|
|
void bar() { fo$(pos) }
|
|
)cpp",
|
|
opts);
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
// No-arg function should not generate snippet (no placeholders).
|
|
ASSERT_TRUE(!it->insert_text_format.has_value() ||
|
|
*it->insert_text_format == protocol::InsertTextFormat::PlainText);
|
|
}
|
|
|
|
TEST_CASE(SnippetDisabled) {
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = false;
|
|
opts.enable_function_arguments_snippet = false;
|
|
code_complete(R"cpp(
|
|
int foooo(int x, float y);
|
|
int z = fo$(pos)
|
|
)cpp",
|
|
opts);
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
// With snippet disabled, should be plain text.
|
|
ASSERT_TRUE(!it->insert_text_format.has_value() ||
|
|
*it->insert_text_format == protocol::InsertTextFormat::PlainText);
|
|
}
|
|
|
|
TEST_CASE(SnippetBundleMode) {
|
|
// In bundle mode, snippets should NOT be generated even if enabled.
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = true;
|
|
opts.enable_function_arguments_snippet = true;
|
|
code_complete(R"cpp(
|
|
int foooo(int x);
|
|
int foooo(int x, int y);
|
|
int z = fo$(pos)
|
|
)cpp",
|
|
opts);
|
|
|
|
auto it = find_item("foooo");
|
|
ASSERT_TRUE(it != items.end());
|
|
ASSERT_TRUE(!it->insert_text_format.has_value() ||
|
|
*it->insert_text_format == protocol::InsertTextFormat::PlainText);
|
|
}
|
|
|
|
TEST_CASE(SnippetMethod) {
|
|
feature::CodeCompletionOptions opts;
|
|
opts.bundle_overloads = false;
|
|
opts.enable_function_arguments_snippet = true;
|
|
code_complete(R"cpp(
|
|
struct Foo {
|
|
int bazzzz(int a, int b);
|
|
};
|
|
void bar() {
|
|
Foo f;
|
|
f.ba$(pos);
|
|
}
|
|
)cpp",
|
|
opts);
|
|
|
|
auto it = find_item("bazzzz");
|
|
ASSERT_TRUE(it != items.end());
|
|
ASSERT_TRUE(it->insert_text_format.has_value());
|
|
ASSERT_EQ(*it->insert_text_format, protocol::InsertTextFormat::Snippet);
|
|
auto& edit = std::get<protocol::TextEdit>(*it->text_edit);
|
|
ASSERT_TRUE(edit.new_text.find("${1:") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE(Unqualified) {
|
|
code_complete(R"cpp(
|
|
namespace A {
|
|
void fooooo();
|
|
}
|
|
|
|
void bar() {
|
|
fo$(pos)
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST_CASE(Functor) {
|
|
code_complete(R"cpp(
|
|
struct X {
|
|
void operator() () {};
|
|
};
|
|
|
|
void bar() {
|
|
X foo;
|
|
fo$(pos);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
TEST_CASE(Lambda) {
|
|
code_complete(R"cpp(
|
|
void bar() {
|
|
auto foo = [](int x){ };
|
|
fo$(pos);
|
|
}
|
|
)cpp");
|
|
}
|
|
|
|
}; // TEST_SUITE(CodeCompletion)
|
|
|
|
} // namespace
|
|
|
|
} // namespace clice::testing
|