refactor: public feature types and snapshot testing infrastructure (#442)
## Summary
- **Public feature types**: Move `SemanticToken`, `FoldingRange`,
`DocumentSymbol`, `InlayHint`, and `HintCategory` from internal `.cpp`
files to `feature.h` as public API types. Each feature now exposes two
overloads: a raw overload returning offset-based types and a protocol
overload that converts to LSP wire-format with explicit
`PositionEncoding`.
- **Snapshot testing**: Add corpus-driven snapshot tests using
`ASSERT_SNAPSHOT_GLOB` for semantic tokens, folding ranges, inlay hints,
document symbols, and TU index. Tests compile real C++ corpus files,
format output as YAML flow mappings, and diff against `.snap.yml`
baselines.
- **Test infrastructure**: Add `compile_file()` to `Tester`,
`yaml_str()` utility, `--corpus-dir` / `--snapshot-dir` CLI options, and
`--verbose` flag for unit tests. Migrate to kotatsu's unified
`kota::zest::Options` API.
- **Toolchain robustness**: Filter unknown cc1 args via
`clang::driver::getDriverOptTable()` to handle system compilers newer
than embedded LLVM.
- **Dependency bump**: Update kotatsu to 7381404 (unified zest Options,
out-param `from_json` API).
## Details
### Feature type changes
All five feature modules (`semantic_tokens`, `folding_ranges`,
`document_symbols`, `inlay_hints`, `document_links`) now follow the same
two-overload pattern. The raw overload returns offset-based structs
suitable for indexing and testing; the protocol overload adds
`PositionEncoding` conversion for LSP responses. `stateful_worker.cpp`
explicitly passes `PositionEncoding::UTF16` at every call site.
### Snapshot tests
Corpus files live in `tests/corpus/` (organized by language construct).
Snapshot baselines live in `tests/snapshots/<feature>/`. Format lambdas
are inlined directly in test bodies — no separate format functions for
single-use formatters. YAML output uses flow mappings (`- { key: value
}`) for compact, diffable baselines.
### cc1 arg filtering
`src/command/toolchain.cpp` now parses the cc1 argument list through
LLVM's driver option table and drops any args classified as
`UnknownClass`. This prevents compilation failures when the system
compiler emits flags that the embedded LLVM version doesn't recognize.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,18 @@
|
||||
#include <algorithm>
|
||||
#include <format>
|
||||
#include <set>
|
||||
|
||||
#include "test/test.h"
|
||||
#include "test/tester.h"
|
||||
#include "feature/feature.h"
|
||||
#include "index/tu_index.h"
|
||||
|
||||
#include "kota/meta/enum.h"
|
||||
|
||||
namespace clice::testing {
|
||||
namespace {
|
||||
|
||||
TEST_SUITE(TUIndex, Tester) {
|
||||
TEST_SUITE(tu_index, Tester) {
|
||||
|
||||
index::TUIndex tu_index;
|
||||
|
||||
@@ -500,6 +505,64 @@ TEST_CASE(SymbolKinds) {
|
||||
check_kind("ns", SymbolKind::Namespace);
|
||||
}
|
||||
|
||||
}; // TEST_SUITE(TUIndex)
|
||||
TEST_CASE(snapshot) {
|
||||
ASSERT_SNAPSHOT_GLOB(corpus_dir, "**/*.cpp", [&](std::string_view path) -> std::string {
|
||||
if(!compile_file(path))
|
||||
return "COMPILE_ERROR";
|
||||
auto idx = index::TUIndex::build(*unit);
|
||||
auto content = unit->interested_content();
|
||||
feature::PositionMapper mapper(content, feature::PositionEncoding::UTF8);
|
||||
std::string result;
|
||||
|
||||
auto sorted = idx.main_file_index.occurrences;
|
||||
std::ranges::sort(sorted, [](auto& lhs, auto& rhs) {
|
||||
return std::tuple(lhs.range.begin, lhs.range.end, lhs.target) <
|
||||
std::tuple(rhs.range.begin, rhs.range.end, rhs.target);
|
||||
});
|
||||
|
||||
for(auto& occ: sorted) {
|
||||
auto text = content.substr(occ.range.begin, occ.range.end - occ.range.begin);
|
||||
auto pos = mapper.to_position(occ.range.begin);
|
||||
if(!pos)
|
||||
continue;
|
||||
|
||||
auto sym_it = idx.symbols.find(occ.target);
|
||||
std::string_view kind_name = "?";
|
||||
if(sym_it != idx.symbols.end()) {
|
||||
kind_name =
|
||||
kota::meta::enum_name(static_cast<SymbolKind::Kind>(sym_it->second.kind),
|
||||
"Unknown");
|
||||
}
|
||||
|
||||
result += std::format("- {{ loc: \"{}:{}\", kind: {}, text: {}",
|
||||
pos->line,
|
||||
pos->character,
|
||||
kind_name,
|
||||
yaml_str(text));
|
||||
|
||||
auto rel_it = idx.main_file_index.relations.find(occ.target);
|
||||
if(rel_it != idx.main_file_index.relations.end()) {
|
||||
std::string rels;
|
||||
for(auto& rel: rel_it->second) {
|
||||
if(rel.range != occ.range)
|
||||
continue;
|
||||
if(!rels.empty())
|
||||
rels += ", ";
|
||||
rels += kota::meta::enum_name(static_cast<RelationKind::Kind>(rel.kind), "?");
|
||||
}
|
||||
if(!rels.empty()) {
|
||||
result += std::format(", relations: [{}]", rels);
|
||||
}
|
||||
}
|
||||
|
||||
result += " }\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
}; // TEST_SUITE(tu_index)
|
||||
|
||||
} // namespace
|
||||
} // namespace clice::testing
|
||||
|
||||
Reference in New Issue
Block a user