3 Commits

Author SHA1 Message Date
ykiko
f397e95b34 feat(hover): format definition code blocks with clang-format
Use the project's .clang-format style to format the code snippets shown
in hover popups, so they match the user's formatting preferences instead
of Clang's raw pretty-printer output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-04 20:28:04 +08:00
ykiko
2df42abd80 fix(hover): use double newline between markdown blocks for proper VSCode rendering
Single newline caused setext heading interpretation (text\n--- → H2) and
paragraph merging in CommonMark/markdown-it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-04 19:17:12 +08:00
ykiko
d55fb3d6d2 refactor(hover): fix doxygen rendering and rewrite Markup (formerly StructedText)
- Parse documentation through strip_doxygen_info() to render \param, \return,
  \brief and other doxygen commands as structured markdown instead of raw text
- Fix markdown block separation: add newline between blocks so paragraphs,
  bullet lists, and code blocks no longer merge on the same line
- Fix CodeBlock closing fence not on its own line when code lacks trailing newline
- Fix Heading::clone() slicing to Paragraph (lost heading level on copy)
- Fix BulletList multi-line items missing continuation indentation
- Fix double backtick in hover heading (name wrapped manually + InlineCode)
- Rename StructedText → Markup, fix Strikethough → Strikethrough typo
- Add DoxygenInfo::get_param_command_comments() for param iteration
- Rewrite Markup tests from 3 assertion-less smoke tests to 27 proper tests
- Add 6 doxygen-specific hover tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-04 19:17:12 +08:00
50 changed files with 2335 additions and 1228 deletions

3
.gitignore vendored
View File

@@ -68,7 +68,8 @@ tests/unit/Local/
.pixi/*
!.pixi/config.toml
.codex
.codex/
.claude/*
!.claude/CLAUDE.md
!.claude/commands/
openspec/

View File

@@ -124,31 +124,8 @@ if(CLICE_CI_ENVIRONMENT)
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
endif()
set(CLICE_CLANG_TIDY_MODULE_LIBRARIES)
set(CLICE_MISSING_CLANG_TIDY_MODULES)
foreach(module IN LISTS CLICE_CLANG_TIDY_MODULE_COMPONENTS)
find_library(CLICE_${module}_LIBRARY
NAMES "${module}"
PATHS "${LLVM_INSTALL_PATH}/lib"
NO_DEFAULT_PATH
)
if(CLICE_${module}_LIBRARY)
list(APPEND CLICE_CLANG_TIDY_MODULE_LIBRARIES "${CLICE_${module}_LIBRARY}")
else()
list(APPEND CLICE_MISSING_CLANG_TIDY_MODULES "${module}")
endif()
endforeach()
if(CLICE_MISSING_CLANG_TIDY_MODULES)
message(STATUS "Clang-tidy module libraries not available: ${CLICE_MISSING_CLANG_TIDY_MODULES}")
else()
target_compile_definitions(clice_options INTERFACE CLICE_HAS_CLANG_TIDY_MODULES=1)
endif()
set(FBS_SCHEMA_FILE "${PROJECT_SOURCE_DIR}/src/index/schema.fbs")
set(GENERATED_HEADER "${PROJECT_BINARY_DIR}/generated/schema_generated.h")
set(CLANG_TIDY_CONFIG_SOURCE_FILE "${PROJECT_SOURCE_DIR}/config/clang-tidy-config.h")
set(CLANG_TIDY_CONFIG_GENERATED_FILE "${PROJECT_BINARY_DIR}/generated/clang-tidy-config.h")
if(CMAKE_CROSSCOMPILING)
find_program(FLATC_EXECUTABLE flatc REQUIRED)
@@ -166,21 +143,10 @@ add_custom_command(
add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}")
add_custom_command(
OUTPUT "${CLANG_TIDY_CONFIG_GENERATED_FILE}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${CLANG_TIDY_CONFIG_SOURCE_FILE}"
"${CLANG_TIDY_CONFIG_GENERATED_FILE}"
DEPENDS "${CLANG_TIDY_CONFIG_SOURCE_FILE}"
COMMENT "Generating C++ header from ${CLANG_TIDY_CONFIG_SOURCE_FILE}"
)
add_custom_target(generate_clang_tidy_config DEPENDS "${CLANG_TIDY_CONFIG_GENERATED_FILE}")
file(GLOB_RECURSE CLICE_CORE_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/*.cpp")
add_library(clice-core STATIC ${CLICE_CORE_SOURCES})
add_library(clice::core ALIAS clice-core)
add_dependencies(clice-core generate_flatbuffers_schema generate_clang_tidy_config)
add_dependencies(clice-core generate_flatbuffers_schema)
target_include_directories(clice-core PUBLIC
"${PROJECT_SOURCE_DIR}/src"
@@ -196,9 +162,6 @@ target_link_libraries(clice-core PUBLIC
kota::codec::toml
simdjson::simdjson
)
if(CLICE_CLANG_TIDY_MODULE_LIBRARIES)
target_link_libraries(clice-core PUBLIC ${CLICE_CLANG_TIDY_MODULE_LIBRARIES})
endif()
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
target_link_libraries(clice PRIVATE clice::core kota::deco)

View File

@@ -1,34 +1,5 @@
include_guard()
set(CLICE_CLANG_TIDY_MODULE_COMPONENTS
# Keep this in sync with scripts/llvm-components.json and the old
# ALL_CLANG_TIDY_CHECKS list. MPIModule is intentionally excluded because
# clice disables static analyzer checks in ClangTidyForceLinker.h.
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
)
function(setup_llvm LLVM_VERSION)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
@@ -116,6 +87,29 @@ function(setup_llvm LLVM_VERSION)
clangSerialization
clangTidy
clangTidyUtils
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
clangTooling
clangToolingCore
clangToolingInclusions

View File

@@ -41,7 +41,7 @@ set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
kotatsu
GIT_REPOSITORY https://github.com/clice-io/kotatsu
GIT_TAG 73814044ce8142f4438a3028f44668675fc09fff
GIT_TAG e024f3b427a554502c4aa015952800a03ca4384b
)
set(KOTA_ENABLE_ZEST ON)

View File

@@ -161,7 +161,7 @@ depends-on = [{ task = "lint-cpp", args = ["{{ type }}"] }]
[feature.test.tasks.unit-test]
args = [{ arg = "type", default = "RelWithDebInfo" }]
cmd = './build/{{ type }}/bin/unit_tests --test-dir="./tests/data" --snapshot-dir="./tests/snapshots" --corpus-dir="./tests/corpus" --verbose'
cmd = './build/{{ type }}/bin/unit_tests --test-dir="./tests/data"'
[feature.test.tasks.integration-test]
args = [{ arg = "type", default = "RelWithDebInfo" }]
@@ -257,7 +257,7 @@ format-markdown = "fd -H -e md -x prettier --write"
format-json = "fd -H -e json -E package-lock.json -x prettier --write"
format-toml = "fd -H -e toml -x tombi format"
format-yaml = """
fd -H -e yaml -e yml -E pnpm-lock.yaml -E '*.snap.yml' -x prettier --write && \
fd -H -e yaml -e yml -E pnpm-lock.yaml -x prettier --write && \
fd -H "^\\.clang-(format|tidy)$" -x prettier --write --parser yaml
"""
format = { depends-on = [

View File

@@ -16,10 +16,7 @@ import subprocess
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Iterable, List, Optional, Set
LLVM_COMPONENTS_FILE = Path(__file__).with_name("llvm-components.json")
from typing import Iterable, List, Optional
def parse_args() -> argparse.Namespace:
@@ -105,33 +102,12 @@ def run_build(build_dir: Path) -> bool:
return False
def protected_library_names() -> Set[str]:
data = json.loads(LLVM_COMPONENTS_FILE.read_text())
components = data.get("components", [])
if not isinstance(components, list):
raise ValueError(f"{LLVM_COMPONENTS_FILE} missing 'components' list")
names: Set[str] = set()
for component in components:
if not isinstance(component, str):
continue
if not (component.startswith("clangTidy") and component.endswith("Module")):
continue
names.add(f"lib{component}.a")
names.add(f"{component}.lib")
return names
def candidate_files(install_dir: Path) -> Iterable[Path]:
if not install_dir.is_dir():
raise FileNotFoundError(f"lib dir not found: {install_dir}")
protected = protected_library_names()
for path in sorted(install_dir.iterdir()):
if not path.is_file():
continue
if path.name in protected:
print(f"Keeping protected clang-tidy module library: {path.name}")
continue
if path.suffix.lower() in {".a", ".lib"}:
yield path
else:
@@ -180,11 +156,7 @@ def apply_manifest(manifest: Path, install_dir: Path) -> None:
removed = data.get("removed", [])
if not isinstance(removed, list):
raise ValueError("Manifest missing 'removed' list")
protected = protected_library_names()
for name in removed:
if name in protected:
print(f"Keeping protected clang-tidy module library from manifest: {name}")
continue
target = install_dir / name
if target.exists():
print(f"Deleting {target}")

View File

@@ -53,7 +53,7 @@ struct Options {
help =
"Agentic method (compileCommand, symbolSearch, definition, references, "
"documentSymbols, readSymbol, callGraph, typeHierarchy, projectFiles, "
"lint, fileDeps, impactAnalysis, status, shutdown)",
"fileDeps, impactAnalysis, status, shutdown)",
required = false)
<std::string> method;

View File

@@ -9,7 +9,6 @@
#include "support/logging.h"
#include "kota/meta/enum.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
@@ -18,7 +17,6 @@
#include "llvm/TargetParser/Host.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/Options.h"
#include "clang/Driver/Tool.h"
#ifndef _WIN32
@@ -472,32 +470,11 @@ std::vector<const char*> query_clang_toolchain(const QueryParams& params) {
continue;
}
// FIXME: the system compiler may be newer than our embedded LLVM,
// producing cc1 flags we don't recognize. Filter them out here.
// Long-term we should unify the command pipeline so the driver
// version always matches the embedded LLVM.
auto& table = clang::driver::getDriverOptTable();
auto cc1_args = llvm::ArrayRef(args).drop_front(2);
unsigned missing_index = 0, missing_count = 0;
auto parsed = table.ParseArgs(cc1_args, missing_index, missing_count);
llvm::DenseSet<unsigned> unknown_indices;
for(auto* a: parsed) {
if(a->getOption().getKind() == llvm::opt::Option::UnknownClass) {
unknown_indices.insert(a->getIndex());
}
}
result.emplace_back(params.callback(args[0]));
result.emplace_back(params.callback(args[1]));
for(unsigned i = 0; i < cc1_args.size(); ++i) {
if(unknown_indices.contains(i)) {
for(auto arg: args) {
if(arg == "-###"sv) {
continue;
}
if(cc1_args[i] == "-###"sv) {
continue;
}
result.emplace_back(params.callback(cc1_args[i]));
result.emplace_back(params.callback(arg));
}
}
}

View File

@@ -418,8 +418,6 @@ CompilationUnit compile(CompilationParams& params, PCMInfo& out) {
}
CompilationUnit complete(CompilationParams& params, clang::CodeCompleteConsumer* consumer) {
params.kind = CompilationKind::Completion;
auto& [file, offset] = params.completion;
/// The location of clang is 1-1 based.

View File

@@ -65,7 +65,7 @@ struct PCMInfo : ModuleInfo {
struct CompilationParams {
/// The kind of this compilation.
CompilationKind kind = CompilationKind::Content;
CompilationKind kind;
/// Whether to run clang-tidy.
bool clang_tidy = false;

View File

@@ -12,10 +12,6 @@
#include "clang-tidy/ClangTidyDiagnosticConsumer.h"
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang-tidy/ClangTidyOptions.h"
#ifdef CLICE_HAS_CLANG_TIDY_MODULES
#define CLANG_TIDY_DISABLE_STATIC_ANALYZER_CHECKS
#include "clang-tidy/ClangTidyForceLinker.h"
#endif
namespace clice::tidy {

View File

@@ -93,9 +93,18 @@ auto symbol_detail(clang::ASTContext& context, const clang::NamedDecl& decl) ->
return detail;
}
struct InternalSymbol {
std::string name;
std::string detail;
SymbolKind kind = SymbolKind::Invalid;
LocalSourceRange range;
LocalSourceRange selection_range;
std::vector<InternalSymbol> children;
};
struct SymbolFrame {
std::vector<DocumentSymbol> symbols;
std::vector<DocumentSymbol>* cursor = &symbols;
std::vector<InternalSymbol> symbols;
std::vector<InternalSymbol>* cursor = &symbols;
};
class DocumentSymbolCollector : public FilteredASTVisitor<DocumentSymbolCollector> {
@@ -134,7 +143,7 @@ public:
return ok;
}
auto collect() -> std::vector<DocumentSymbol> {
auto collect() -> std::vector<InternalSymbol> {
TraverseDecl(unit.tu());
return std::move(result.symbols);
}
@@ -165,8 +174,8 @@ private:
SymbolFrame result;
};
void sort_symbols(std::vector<DocumentSymbol>& symbols) {
std::ranges::sort(symbols, [](const DocumentSymbol& lhs, const DocumentSymbol& rhs) {
void sort_symbols(std::vector<InternalSymbol>& symbols) {
std::ranges::sort(symbols, [](const InternalSymbol& lhs, const InternalSymbol& rhs) {
if(lhs.range.begin != rhs.range.begin) {
return lhs.range.begin < rhs.range.begin;
}
@@ -178,7 +187,7 @@ void sort_symbols(std::vector<DocumentSymbol>& symbols) {
}
}
auto to_protocol_symbol(const DocumentSymbol& symbol, const PositionMapper& converter)
auto to_protocol_symbol(const InternalSymbol& symbol, const PositionMapper& converter)
-> protocol::DocumentSymbol {
protocol::DocumentSymbol result{
.name = symbol.name,
@@ -206,15 +215,10 @@ auto to_protocol_symbol(const DocumentSymbol& symbol, const PositionMapper& conv
} // namespace
auto document_symbols(CompilationUnitRef unit) -> std::vector<DocumentSymbol> {
auto result = DocumentSymbolCollector(unit).collect();
sort_symbols(result);
return result;
}
auto document_symbols(CompilationUnitRef unit, PositionEncoding encoding)
-> std::vector<protocol::DocumentSymbol> {
auto internal = document_symbols(unit);
auto internal = DocumentSymbolCollector(unit).collect();
sort_symbols(internal);
PositionMapper converter(unit.interested_content(), encoding);
std::vector<protocol::DocumentSymbol> symbols;

View File

@@ -7,7 +7,6 @@
#include "compile/compilation.h"
#include "compile/compilation_unit.h"
#include "semantic/symbol_kind.h"
#include "kota/ipc/lsp/position.h"
#include "kota/ipc/lsp/protocol.h"
@@ -60,66 +59,18 @@ struct InlayHintsOptions {
struct SignatureHelpOptions {};
struct SemanticToken {
LocalSourceRange range;
SymbolKind kind = SymbolKind::Invalid;
std::uint32_t modifiers = 0;
};
struct FoldingRange {
LocalSourceRange range;
std::optional<protocol::FoldingRangeKind> kind;
std::string collapsed_text;
};
struct DocumentSymbol {
std::string name;
std::string detail;
SymbolKind kind = SymbolKind::Invalid;
LocalSourceRange range;
LocalSourceRange selection_range;
std::vector<DocumentSymbol> children;
};
enum class HintCategory : std::uint8_t {
Parameter,
DefaultArgument,
Type,
Designator,
BlockEnd,
};
struct InlayHint {
std::uint32_t offset = 0;
HintCategory kind = HintCategory::Type;
std::string label;
bool padding_left = false;
bool padding_right = false;
};
auto semantic_tokens(CompilationUnitRef unit) -> std::vector<SemanticToken>;
auto semantic_tokens(CompilationUnitRef unit, PositionEncoding encoding)
auto semantic_tokens(CompilationUnitRef unit, PositionEncoding encoding = PositionEncoding::UTF16)
-> protocol::SemanticTokens;
auto folding_ranges(CompilationUnitRef unit) -> std::vector<FoldingRange>;
auto folding_ranges(CompilationUnitRef unit, PositionEncoding encoding)
-> std::vector<protocol::FoldingRange>;
auto document_symbols(CompilationUnitRef unit) -> std::vector<DocumentSymbol>;
auto document_symbols(CompilationUnitRef unit, PositionEncoding encoding)
-> std::vector<protocol::DocumentSymbol>;
auto inlay_hints(CompilationUnitRef unit,
LocalSourceRange target,
const InlayHintsOptions& options = {}) -> std::vector<InlayHint>;
auto inlay_hints(CompilationUnitRef unit,
LocalSourceRange target,
const InlayHintsOptions& options,
PositionEncoding encoding) -> std::vector<protocol::InlayHint>;
auto document_links(CompilationUnitRef unit, PositionEncoding encoding = PositionEncoding::UTF16)
-> std::vector<protocol::DocumentLink>;
auto document_symbols(CompilationUnitRef unit, PositionEncoding encoding = PositionEncoding::UTF16)
-> std::vector<protocol::DocumentSymbol>;
auto folding_ranges(CompilationUnitRef unit, PositionEncoding encoding = PositionEncoding::UTF16)
-> std::vector<protocol::FoldingRange>;
auto diagnostics(CompilationUnitRef unit, PositionEncoding encoding = PositionEncoding::UTF16)
-> std::vector<protocol::Diagnostic>;
@@ -138,6 +89,12 @@ auto hover(CompilationUnitRef unit,
const HoverOptions& options = {},
PositionEncoding encoding = PositionEncoding::UTF16) -> std::optional<protocol::Hover>;
auto inlay_hints(CompilationUnitRef unit,
LocalSourceRange target,
const InlayHintsOptions& options = {},
PositionEncoding encoding = PositionEncoding::UTF16)
-> std::vector<protocol::InlayHint>;
auto signature_help(CompilationParams& params, const SignatureHelpOptions& options = {})
-> protocol::SignatureHelp;
@@ -147,4 +104,6 @@ auto document_format(llvm::StringRef file,
PositionEncoding encoding = PositionEncoding::UTF16)
-> std::vector<protocol::TextEdit>;
auto format_code(llvm::StringRef file, llvm::StringRef code) -> std::string;
} // namespace clice::feature

View File

@@ -53,6 +53,12 @@ auto to_kind(FoldingKind kind) -> protocol::FoldingRangeKind {
return protocol::FoldingRangeKind(protocol::FoldingRangeKind::region);
}
struct RawFoldingRange {
LocalSourceRange range;
std::optional<protocol::FoldingRangeKind> kind;
std::string collapsed_text;
};
class FoldingRangeCollector : public FilteredASTVisitor<FoldingRangeCollector> {
public:
explicit FoldingRangeCollector(CompilationUnitRef unit) : FilteredASTVisitor(unit, true) {}
@@ -179,7 +185,7 @@ public:
return true;
}
auto collect() -> std::vector<FoldingRange> {
auto collect() -> std::vector<RawFoldingRange> {
TraverseDecl(unit.tu());
auto directives_it = unit.directives().find(unit.interested_file());
@@ -187,7 +193,7 @@ public:
collect_directives(directives_it->second);
}
std::ranges::sort(ranges, [](const FoldingRange& lhs, const FoldingRange& rhs) {
std::ranges::sort(ranges, [](const RawFoldingRange& lhs, const RawFoldingRange& rhs) {
if(lhs.range.begin != rhs.range.begin) {
return lhs.range.begin < rhs.range.begin;
}
@@ -337,18 +343,14 @@ private:
}
private:
std::vector<FoldingRange> ranges;
std::vector<RawFoldingRange> ranges;
};
} // namespace
auto folding_ranges(CompilationUnitRef unit) -> std::vector<FoldingRange> {
return FoldingRangeCollector(unit).collect();
}
auto folding_ranges(CompilationUnitRef unit, PositionEncoding encoding)
-> std::vector<protocol::FoldingRange> {
auto collected = folding_ranges(unit);
auto collected = FoldingRangeCollector(unit).collect();
PositionMapper converter(unit.interested_content(), encoding);
std::vector<protocol::FoldingRange> result;

View File

@@ -66,4 +66,20 @@ auto document_format(llvm::StringRef file,
return edits;
}
auto format_code(llvm::StringRef file, llvm::StringRef code) -> std::string {
auto style = clang::format::getStyle(clang::format::DefaultFormatStyle,
file,
clang::format::DefaultFallbackStyle,
code);
if(!style)
return code.str();
auto replacements = clang::format::reformat(*style, code, {tooling::Range(0, code.size())});
auto result = tooling::applyAllReplacements(code, replacements);
if(!result)
return code.str();
return llvm::StringRef(*result).trim().str();
}
} // namespace clice::feature

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,22 @@ using llvm::dyn_cast_or_null;
// For now, inlay hints are always anchored at the left or right of their range.
enum class HintSide { Left, Right };
enum class HintCategory : std::uint8_t {
Parameter,
DefaultArgument,
Type,
Designator,
BlockEnd,
};
struct RawInlayHint {
std::uint32_t offset = 0;
HintCategory kind = HintCategory::Type;
std::string label;
bool padding_left = false;
bool padding_right = false;
};
bool is_expanded_from_param_pack(const clang::ParmVarDecl* param) {
return ast::underlying_pack_type(param) != nullptr;
}
@@ -107,7 +123,7 @@ struct Callee {
class Builder {
public:
Builder(std::vector<InlayHint>& result,
Builder(std::vector<RawInlayHint>& result,
CompilationUnitRef unit,
LocalSourceRange restrict_range,
const InlayHintsOptions& options) :
@@ -483,7 +499,7 @@ public:
bool pad_left = prefix.consume_front(" ");
bool pad_right = suffix.consume_back(" ");
InlayHint hint{
RawInlayHint hint{
.offset = offset,
.kind = kind,
.label = (prefix + label + suffix).str(),
@@ -538,7 +554,7 @@ public:
}
private:
std::vector<InlayHint>& result;
std::vector<RawInlayHint>& result;
CompilationUnitRef unit;
LocalSourceRange restrict_range;
const InlayHintsOptions& options;
@@ -897,43 +913,36 @@ private:
} // namespace
auto inlay_hints(CompilationUnitRef unit, LocalSourceRange target, const InlayHintsOptions& options)
-> std::vector<InlayHint> {
auto inlay_hints(CompilationUnitRef unit,
LocalSourceRange target,
const InlayHintsOptions& options,
PositionEncoding encoding) -> std::vector<protocol::InlayHint> {
if(!options.enabled) {
return {};
}
std::vector<InlayHint> raw_hints;
std::vector<RawInlayHint> raw_hints;
Builder builder(raw_hints, unit, target, options);
Visitor visitor(builder, unit, target, options);
visitor.TraverseDecl(unit.tu());
std::ranges::sort(raw_hints, [](const InlayHint& lhs, const InlayHint& rhs) {
std::ranges::sort(raw_hints, [](const RawInlayHint& lhs, const RawInlayHint& rhs) {
return std::tie(lhs.offset, lhs.label, lhs.kind, lhs.padding_left, lhs.padding_right) <
std::tie(rhs.offset, rhs.label, rhs.kind, rhs.padding_left, rhs.padding_right);
});
auto unique_begin =
std::ranges::unique(raw_hints, [](const InlayHint& lhs, const InlayHint& rhs) {
std::ranges::unique(raw_hints, [](const RawInlayHint& lhs, const RawInlayHint& rhs) {
return lhs.offset == rhs.offset && lhs.kind == rhs.kind && lhs.label == rhs.label &&
lhs.padding_left == rhs.padding_left && lhs.padding_right == rhs.padding_right;
});
raw_hints.erase(unique_begin.begin(), unique_begin.end());
return raw_hints;
}
auto inlay_hints(CompilationUnitRef unit,
LocalSourceRange target,
const InlayHintsOptions& options,
PositionEncoding encoding) -> std::vector<protocol::InlayHint> {
auto collected = inlay_hints(unit, target, options);
PositionMapper converter(unit.interested_content(), encoding);
std::vector<protocol::InlayHint> hints;
hints.reserve(collected.size());
hints.reserve(raw_hints.size());
for(const auto& hint: collected) {
for(const auto& hint: raw_hints) {
protocol::InlayHint out{
.position = *converter.to_position(hint.offset),
.label = hint.label,

View File

@@ -18,6 +18,12 @@ namespace clice::feature {
namespace {
struct RawToken {
LocalSourceRange range;
SymbolKind kind = SymbolKind::Invalid;
std::uint32_t modifiers = 0;
};
void add_modifier(std::uint32_t& modifiers, SymbolModifiers::Kind kind) {
modifiers |= SymbolModifiers::to_mask(kind);
}
@@ -34,34 +40,6 @@ bool is_dependent(const clang::Decl* D) {
return isa<clang::UnresolvedUsingValueDecl>(D);
}
/// Whether a declaration name is backed by source text that should be highlighted.
bool can_highlight_name(clang::DeclarationName name) {
switch(name.getNameKind()) {
case clang::DeclarationName::Identifier: {
auto* info = name.getAsIdentifierInfo();
return info && !info->getName().empty();
}
case clang::DeclarationName::CXXConstructorName:
case clang::DeclarationName::CXXDestructorName: {
return true;
}
case clang::DeclarationName::CXXConversionFunctionName:
case clang::DeclarationName::CXXOperatorName:
case clang::DeclarationName::CXXDeductionGuideName:
case clang::DeclarationName::CXXLiteralOperatorName:
case clang::DeclarationName::CXXUsingDirective:
case clang::DeclarationName::ObjCZeroArgSelector:
case clang::DeclarationName::ObjCOneArgSelector:
case clang::DeclarationName::ObjCMultiArgSelector: {
return false;
}
}
std::unreachable();
}
/// Returns true if `decl` is considered to be from a default/system library.
/// This currently checks the systemness of the file by include type, although
/// different heuristics may be used in the future (e.g. sysroot paths).
@@ -188,7 +166,7 @@ class SemanticTokensCollector : public SemanticVisitor<SemanticTokensCollector>
public:
explicit SemanticTokensCollector(CompilationUnitRef unit) : SemanticVisitor(unit, true) {}
auto collect() -> std::vector<SemanticToken> {
auto collect() -> std::vector<RawToken> {
highlight_lexical(unit.interested_file());
run();
highlight_modules();
@@ -199,10 +177,6 @@ public:
void handleDeclOccurrence(const clang::NamedDecl* decl,
RelationKind relation,
clang::SourceLocation location) {
if(relation.isReference() && !can_highlight_name(decl->getDeclName())) {
return;
}
std::uint32_t modifiers = 0;
if(relation.is_one_of(RelationKind::Definition)) {
// todo: clangd add both Declaration and Definition modifiers for definitions.
@@ -424,7 +398,7 @@ private:
}
}
static void resolve_conflict(SemanticToken& last, const SemanticToken& current) {
static void resolve_conflict(RawToken& last, const RawToken& current) {
if(last.kind == SymbolKind::Conflict) {
return;
}
@@ -440,14 +414,14 @@ private:
}
void merge_tokens() {
std::ranges::sort(tokens, [](const SemanticToken& lhs, const SemanticToken& rhs) {
std::ranges::sort(tokens, [](const RawToken& lhs, const RawToken& rhs) {
if(lhs.range.begin != rhs.range.begin) {
return lhs.range.begin < rhs.range.begin;
}
return lhs.range.end < rhs.range.end;
});
std::vector<SemanticToken> merged;
std::vector<RawToken> merged;
merged.reserve(tokens.size());
for(const auto& token: tokens) {
@@ -474,7 +448,7 @@ private:
}
public:
std::vector<SemanticToken> tokens;
std::vector<RawToken> tokens;
};
class SemanticTokenEncoder {
@@ -484,7 +458,7 @@ public:
protocol::SemanticTokens& output) :
content(content), converter(content, encoding), output(output) {}
void append(const SemanticToken& token) {
void append(const RawToken& token) {
if(!token.range.valid() || token.range.end <= token.range.begin ||
token.range.end > content.size()) {
return;
@@ -568,14 +542,10 @@ private:
} // namespace
auto semantic_tokens(CompilationUnitRef unit) -> std::vector<SemanticToken> {
SemanticTokensCollector collector(unit);
return collector.collect();
}
auto semantic_tokens(CompilationUnitRef unit, PositionEncoding encoding)
-> protocol::SemanticTokens {
auto tokens = semantic_tokens(unit);
SemanticTokensCollector collector(unit);
auto tokens = collector.collect();
protocol::SemanticTokens result;
result.data.reserve(tokens.size() * 5);

View File

@@ -669,7 +669,6 @@ kota::task<> Compiler::run_compile(std::uint32_t pid, std::shared_ptr<Session::P
params.path = file_path;
params.version = sess->version;
params.text = sess->text;
params.clang_tidy = workspace.config.project.clang_tidy.value;
if(!fill_compile_args(file_path, params.directory, params.arguments, sess)) {
finish_compile();
co_return;

View File

@@ -5,7 +5,6 @@
#include <string>
#include <vector>
#include "kota/ipc/lsp/protocol.h"
#include "kota/ipc/protocol.h"
namespace clice::agentic {
@@ -203,13 +202,6 @@ struct TypeHierarchyResult {
std::vector<TypeHierarchyEntry> subtypes;
};
struct LintParams {
std::string path;
std::optional<int> line;
};
using LintResult = std::vector<kota::ipc::protocol::Diagnostic>;
struct StatusParams {};
struct StatusResult {
@@ -291,12 +283,6 @@ struct RequestTraits<clice::agentic::TypeHierarchyParams> {
constexpr inline static std::string_view method = "agentic/typeHierarchy";
};
template <>
struct RequestTraits<clice::agentic::LintParams> {
using Result = clice::agentic::LintResult;
constexpr inline static std::string_view method = "agentic/lint";
};
template <>
struct RequestTraits<clice::agentic::StatusParams> {
using Result = clice::agentic::StatusResult;

View File

@@ -43,7 +43,6 @@ struct CompileParams {
std::string text;
std::string directory;
std::vector<std::string> arguments;
bool clang_tidy = false;
std::pair<std::string, uint32_t> pch;
std::unordered_map<std::string, std::string> pcms;
};

View File

@@ -6,14 +6,11 @@
#include <string>
#include <vector>
#include "compile/compilation.h"
#include "feature/feature.h"
#include "server/protocol/agentic.h"
#include "server/service/master_server.h"
#include "support/filesystem.h"
#include "support/logging.h"
#include "kota/async/async.h"
#include "kota/ipc/lsp/uri.h"
#include "kota/meta/enum.h"
#include "llvm/ADT/DenseSet.h"
@@ -772,36 +769,6 @@ AgentClient::AgentClient(MasterServer& server, kota::ipc::JsonPeer& peer) :
co_return result;
});
peer.on_request([&srv](RequestContext&, const LintParams& params) -> RequestResult<LintParams> {
std::string directory;
std::vector<std::string> arguments;
if(!srv.compiler.fill_compile_args(params.path, directory, arguments)) {
co_return kota::outcome_error(
kota::ipc::Error{std::format("no compile command found for {}", params.path)});
}
auto result = co_await kota::queue([path = params.path,
directory = std::move(directory),
arguments = std::move(arguments)]() mutable {
CompilationParams cp;
cp.kind = CompilationKind::Content;
cp.clang_tidy = true;
cp.directory = std::move(directory);
for(auto& arg: arguments) {
cp.arguments.push_back(arg.c_str());
}
auto unit = compile(cp);
if(!unit.completed() && !unit.fatal_error()) {
LOG_WARN("Lint compilation failed: {}", path);
return LintResult{};
}
return feature::diagnostics(unit);
});
co_return result.value();
});
peer.on_request([&srv](RequestContext&, const StatusParams&) -> RequestResult<StatusParams> {
StatusResult result;
result.idle = srv.indexer.is_idle();

View File

@@ -85,9 +85,6 @@ static kota::task<> agentic_request(kota::ipc::JsonPeer& peer,
.line = line,
.direction = dir,
});
} else if(opts.method == "lint") {
auto line = opts.line > 0 ? std::optional(opts.line) : std::nullopt;
ok = co_await send_and_print(peer, agentic::LintParams{.path = opts.path, .line = line});
} else if(opts.method == "fileDeps") {
auto dir = opts.direction.empty() ? std::nullopt : std::optional(opts.direction);
ok = co_await send_and_print(peer,

View File

@@ -152,7 +152,6 @@ void StatefulWorker::register_handlers() {
CompilationParams cp;
cp.kind = CompilationKind::Content;
cp.clang_tidy = params.clang_tidy;
fill_args(cp, doc->directory, doc->arguments);
if(!doc->pch.first.empty()) {
cp.pch = doc->pch;
@@ -246,33 +245,26 @@ void StatefulWorker::register_handlers() {
co_return kota::codec::RawValue{"[]"};
case K::SemanticTokens:
co_return co_await with_ast(params.path, [&](DocumentEntry& doc) {
return to_raw(
feature::semantic_tokens(doc.unit, feature::PositionEncoding::UTF16));
return to_raw(feature::semantic_tokens(doc.unit));
});
case K::InlayHints:
co_return co_await with_ast(params.path, [&](DocumentEntry& doc) {
auto range = params.range;
if(range.begin == static_cast<uint32_t>(-1))
range = LocalSourceRange{0, static_cast<uint32_t>(doc.text.size())};
return to_raw(feature::inlay_hints(doc.unit,
range,
{},
feature::PositionEncoding::UTF16));
return to_raw(feature::inlay_hints(doc.unit, range));
});
case K::FoldingRange:
co_return co_await with_ast(params.path, [&](DocumentEntry& doc) {
return to_raw(
feature::folding_ranges(doc.unit, feature::PositionEncoding::UTF16));
return to_raw(feature::folding_ranges(doc.unit));
});
case K::DocumentSymbol:
co_return co_await with_ast(params.path, [&](DocumentEntry& doc) {
return to_raw(
feature::document_symbols(doc.unit, feature::PositionEncoding::UTF16));
return to_raw(feature::document_symbols(doc.unit));
});
case K::DocumentLink:
co_return co_await with_ast(params.path, [&](DocumentEntry& doc) {
return to_raw(
feature::document_links(doc.unit, feature::PositionEncoding::UTF16));
return to_raw(feature::document_links(doc.unit));
});
case K::CodeAction:
// TODO: Implement code actions

View File

@@ -156,13 +156,13 @@ std::optional<Config> Config::load(llvm::StringRef path, llvm::StringRef workspa
}
std::optional<Config> Config::load_from_json(llvm::StringRef json, llvm::StringRef workspace_root) {
Config config{};
auto result = kota::codec::json::from_json(json, config);
auto result = kota::codec::json::from_json<Config>(json);
if(!result) {
LOG_WARN("Failed to parse initializationOptions JSON: {}", result.error().message);
LOG_WARN("Failed to parse initializationOptions JSON: {}", result.error().message());
return std::nullopt;
}
auto config = std::move(*result);
config.apply_defaults(workspace_root);
LOG_INFO("Loaded config from initializationOptions");
return config;

View File

@@ -51,6 +51,15 @@ std::vector<std::pair<llvm::StringRef, llvm::ArrayRef<DoxygenInfo::BlockCommandC
return res;
}
std::vector<std::pair<llvm::StringRef, const DoxygenInfo::ParamCommandCommentContent*>>
DoxygenInfo::get_param_command_comments() const {
std::vector<std::pair<llvm::StringRef, const ParamCommandCommentContent*>> res;
for(const auto& [name, info]: param_command_comments) {
res.emplace_back(name, &info);
}
return res;
}
/// Process inline commands, we only interested in `\b` (bold), `\e` (italic) and `\c` (inline code)
///
/// \param line The line

View File

@@ -49,6 +49,9 @@ public:
return doc_for_return;
}
std::vector<std::pair<llvm::StringRef, const ParamCommandCommentContent*>>
get_param_command_comments() const;
private:
llvm::SmallDenseMap<llvm::StringRef, std::vector<BlockCommandCommentContent>>
block_command_comments;

View File

@@ -1,4 +1,4 @@
#include "support/structed_text.h"
#include "support/markup.h"
#include <algorithm>
#include <cctype>
@@ -25,22 +25,23 @@ std::unique_ptr<Block> BulletList::clone() const {
void BulletList::render_markdown(llvm::raw_ostream& os) const {
for(auto& item: items) {
os << "- " << item.as_markdown() << '\n';
auto content = item.as_markdown();
os << "- ";
for(size_t i = 0; i < content.size(); ++i) {
os << content[i];
if(content[i] == '\n' && i + 1 < content.size())
os << " ";
}
os << '\n';
}
}
StructedText& BulletList::add_item() {
Markup& BulletList::add_item() {
return items.emplace_back();
}
// Clangd inserts escape char '\' before '*', '-' and other markdown markers
// That causes markdown comments are escaped and cannot be rendered properly
// on editors
// We do nothing on it. All the left comments are regarded as markdown rather
// than plain text
void Paragraph::render_markdown(llvm::raw_ostream& os) const {
bool need_space = false;
bool has_chunks = false;
for(auto& chunk: chunks) {
if(chunk.space_ahead || need_space) {
os << ' ';
@@ -58,17 +59,15 @@ void Paragraph::render_markdown(llvm::raw_ostream& os) const {
os << '`' << chunk.content << '`';
break;
}
case Kind::Strikethough: {
case Kind::Strikethrough: {
os << "~~" << chunk.content << "~~";
break;
}
default: {
// Kind::PlainText
os << chunk.content;
break;
}
}
has_chunks = true;
need_space = chunk.space_after;
}
}
@@ -76,7 +75,6 @@ void Paragraph::render_markdown(llvm::raw_ostream& os) const {
Paragraph& Paragraph::append_text(std::string text, Kind kind) {
if(kind == Kind::PlainText) {
llvm::StringRef s{text};
// s = s.trim(" \t\v\f\r");
if(s.empty()) {
return *this;
}
@@ -112,6 +110,10 @@ public:
Paragraph::render_markdown(os);
}
std::unique_ptr<Block> clone() const override {
return std::make_unique<Heading>(*this);
}
private:
unsigned level;
};
@@ -119,7 +121,7 @@ private:
class Ruler : public Block {
public:
void render_markdown(llvm::raw_ostream& os) const override {
os << "\n---\n";
os << "---\n";
}
bool is_ruler() const override {
@@ -134,7 +136,10 @@ public:
class CodeBlock : public Block {
public:
void render_markdown(llvm::raw_ostream& os) const override {
os << "```" << lang << '\n' << code << "```\n";
os << "```" << lang << '\n' << code;
if(!code.empty() && code.back() != '\n')
os << '\n';
os << "```\n";
}
std::unique_ptr<Block> clone() const override {
@@ -160,60 +165,55 @@ static std::string render_blocks(llvm::ArrayRef<std::unique_ptr<Block>> blocks)
blocks = blocks.drop_back(blocks.end() - last.base());
bool last_block_was_ruler = true;
// render
for(const auto& b: blocks) {
if(b->is_ruler() && last_block_was_ruler) {
continue;
}
last_block_was_ruler = b->is_ruler();
b->render_markdown(os);
os << "\n\n";
}
// Get rid of redundant empty lines introduced in plaintext while imitating
// padding in markdown.
std::string adjusted_result;
llvm::StringRef trimmed_text(os.str());
trimmed_text = trimmed_text.trim(" \t\v\f\r");
// Collapse runs of 3+ newlines down to 2 (one blank line max).
std::string result;
llvm::StringRef text(os.str());
text = text.trim();
llvm::copy_if(trimmed_text,
std::back_inserter(adjusted_result),
[&trimmed_text](const char& C) {
return !llvm::StringRef(trimmed_text.data(), &C - trimmed_text.data() + 1)
// We allow at most two newlines.
.ends_with("\n\n\n");
});
llvm::copy_if(text, std::back_inserter(result), [&text](const char& C) {
return !llvm::StringRef(text.data(), &C - text.data() + 1).ends_with("\n\n\n");
});
return adjusted_result;
return result;
}
void StructedText::append(StructedText& other) {
void Markup::append(Markup& other) {
std::move(other.blocks.begin(), other.blocks.end(), std::back_inserter(blocks));
}
Paragraph& StructedText::add_paragraph() {
Paragraph& Markup::add_paragraph() {
blocks.emplace_back(std::make_unique<Paragraph>());
return *static_cast<Paragraph*>(blocks.back().get());
}
void StructedText::add_ruler() {
void Markup::add_ruler() {
blocks.push_back(std::make_unique<Ruler>());
}
void StructedText::add_code_block(std::string code, std::string lang) {
void Markup::add_code_block(std::string code, std::string lang) {
blocks.emplace_back(std::make_unique<CodeBlock>(std::move(code), std::move(lang)));
}
Paragraph& StructedText::add_heading(unsigned level) {
Paragraph& Markup::add_heading(unsigned level) {
blocks.emplace_back(std::make_unique<Heading>(level));
return *static_cast<Paragraph*>(blocks.back().get());
}
BulletList& StructedText::add_bullet_list() {
BulletList& Markup::add_bullet_list() {
blocks.push_back(std::make_unique<BulletList>());
return *static_cast<BulletList*>(blocks.back().get());
}
std::string StructedText::as_markdown() const {
std::string Markup::as_markdown() const {
return render_blocks(blocks);
}

View File

@@ -5,11 +5,11 @@
#include <string>
#include <vector>
#include "llvm/Support/raw_os_ostream.h"
#include "llvm/Support/raw_ostream.h"
namespace clice {
/// Base class of structed text
/// Base class of markup blocks
class Block {
public:
virtual void render_markdown(llvm::raw_ostream& os) const = 0;
@@ -31,7 +31,7 @@ public:
Italic,
PlainText,
InlineCode,
Strikethough,
Strikethrough,
};
void render_markdown(llvm::raw_ostream& os) const override;
@@ -54,7 +54,7 @@ private:
std::vector<Chunk> chunks;
};
class StructedText;
class Markup;
/// Allow nested structure
class BulletList : public Block {
@@ -65,23 +65,23 @@ public:
std::unique_ptr<Block> clone() const override;
StructedText& add_item();
Markup& add_item();
private:
std::vector<StructedText> items;
std::vector<Markup> items;
};
class StructedText {
class Markup {
public:
StructedText() = default;
Markup() = default;
StructedText(const StructedText& other) {
Markup(const Markup& other) {
*this = other;
}
StructedText(StructedText&&) = default;
Markup(Markup&&) = default;
StructedText& operator=(const StructedText& other) {
Markup& operator=(const Markup& other) {
blocks.clear();
for(auto& b: other.blocks) {
blocks.push_back(b->clone());
@@ -89,9 +89,9 @@ public:
return *this;
}
StructedText& operator=(StructedText&&) = default;
Markup& operator=(Markup&&) = default;
void append(StructedText& doc);
void append(Markup& doc);
Paragraph& add_paragraph();

View File

@@ -1,36 +0,0 @@
// basic if and if-else
namespace basic_if {
int abs_val(int x) {
if(x < 0)
return -x;
return x;
}
const char* sign(int x) {
if(x > 0) {
return "positive";
} else if(x < 0) {
return "negative";
} else {
return "zero";
}
}
// dangling else: else binds to nearest if
int nested_if(int a, int b) {
if(a > 0)
if(b > 0)
return 1;
else
return 2;
return 0;
}
void test() {
[[maybe_unused]] int r1 = abs_val(-3);
[[maybe_unused]] auto r2 = sign(5);
[[maybe_unused]] int r3 = nested_if(1, -1);
}
} // namespace basic_if

View File

@@ -1,14 +0,0 @@
---
source: document_symbol_tests.cpp
created_at: 2026-05-20
input_file: statements/if/basic_if.cpp
---
- { name: "basic_if", kind: Namespace, range: "1:0-35:1", selection_range: "1:10-1:18" }
- { name: "abs_val", kind: Function, range: "3:0-7:1", selection_range: "3:4-3:11", detail: "int (int)" }
- { name: "sign", kind: Function, range: "9:0-17:1", selection_range: "9:12-9:16", detail: "const char *(int)" }
- { name: "nested_if", kind: Function, range: "20:0-27:1", selection_range: "20:4-20:13", detail: "int (int, int)" }
- { name: "test", kind: Function, range: "29:0-33:1", selection_range: "29:5-29:9", detail: "void ()" }
- { name: "r1", kind: Variable, range: "30:21-30:41", selection_range: "30:25-30:27", detail: "int" }
- { name: "r2", kind: Variable, range: "31:21-31:38", selection_range: "31:26-31:28", detail: "const char *" }
- { name: "r3", kind: Variable, range: "32:21-32:46", selection_range: "32:25-32:27", detail: "int" }

View File

@@ -1,11 +0,0 @@
---
source: folding_range_tests.cpp
created_at: 2026-05-20
input_file: statements/if/basic_if.cpp
---
- { range: "1:19-35:1", kind: namespace, collapsed_text: "{...}" }
- { range: "3:19-7:1", kind: functionBody, collapsed_text: "{...}" }
- { range: "9:24-17:1", kind: functionBody, collapsed_text: "{...}" }
- { range: "20:28-27:1", kind: functionBody, collapsed_text: "{...}" }
- { range: "29:12-33:1", kind: functionBody, collapsed_text: "{...}" }

View File

@@ -1,11 +0,0 @@
---
source: inlay_hint_tests.cpp
created_at: 2026-05-20
input_file: statements/if/basic_if.cpp
---
- { pos: "30:38", kind: Parameter, label: "x:", padding_right: true }
- { pos: "31:28", kind: Type, label: ": const char *" }
- { pos: "31:36", kind: Parameter, label: "x:", padding_right: true }
- { pos: "32:40", kind: Parameter, label: "a:", padding_right: true }
- { pos: "32:43", kind: Parameter, label: "b:", padding_right: true }

View File

@@ -1,75 +0,0 @@
---
source: semantic_tokens_tests.cpp
created_at: 2026-05-20
input_file: statements/if/basic_if.cpp
---
- { loc: "0:0", text: "// basic if and if-else", kind: Comment }
- { loc: "1:0", text: "namespace", kind: Keyword }
- { loc: "1:10", text: "basic_if", kind: Namespace, modifiers: [Definition] }
- { loc: "3:0", text: "int", kind: Keyword }
- { loc: "3:4", text: "abs_val", kind: Function, modifiers: [Definition] }
- { loc: "3:12", text: "int", kind: Keyword }
- { loc: "3:16", text: "x", kind: Parameter, modifiers: [Definition] }
- { loc: "4:4", text: "if", kind: Keyword }
- { loc: "4:7", text: "x", kind: Parameter }
- { loc: "4:11", text: "0", kind: Number }
- { loc: "5:8", text: "return", kind: Keyword }
- { loc: "5:16", text: "x", kind: Parameter }
- { loc: "6:4", text: "return", kind: Keyword }
- { loc: "6:11", text: "x", kind: Parameter }
- { loc: "9:0", text: "const", kind: Keyword }
- { loc: "9:6", text: "char", kind: Keyword }
- { loc: "9:12", text: "sign", kind: Function, modifiers: [Definition, Readonly] }
- { loc: "9:17", text: "int", kind: Keyword }
- { loc: "9:21", text: "x", kind: Parameter, modifiers: [Definition] }
- { loc: "10:4", text: "if", kind: Keyword }
- { loc: "10:7", text: "x", kind: Parameter }
- { loc: "10:11", text: "0", kind: Number }
- { loc: "11:8", text: "return", kind: Keyword }
- { loc: "11:15", text: "\"positive\"", kind: String }
- { loc: "12:6", text: "else", kind: Keyword }
- { loc: "12:11", text: "if", kind: Keyword }
- { loc: "12:14", text: "x", kind: Parameter }
- { loc: "12:18", text: "0", kind: Number }
- { loc: "13:8", text: "return", kind: Keyword }
- { loc: "13:15", text: "\"negative\"", kind: String }
- { loc: "14:6", text: "else", kind: Keyword }
- { loc: "15:8", text: "return", kind: Keyword }
- { loc: "15:15", text: "\"zero\"", kind: String }
- { loc: "19:0", text: "// dangling else: else binds to nearest if", kind: Comment }
- { loc: "20:0", text: "int", kind: Keyword }
- { loc: "20:4", text: "nested_if", kind: Function, modifiers: [Definition] }
- { loc: "20:14", text: "int", kind: Keyword }
- { loc: "20:18", text: "a", kind: Parameter, modifiers: [Definition] }
- { loc: "20:21", text: "int", kind: Keyword }
- { loc: "20:25", text: "b", kind: Parameter, modifiers: [Definition] }
- { loc: "21:4", text: "if", kind: Keyword }
- { loc: "21:7", text: "a", kind: Parameter }
- { loc: "21:11", text: "0", kind: Number }
- { loc: "22:8", text: "if", kind: Keyword }
- { loc: "22:11", text: "b", kind: Parameter }
- { loc: "22:15", text: "0", kind: Number }
- { loc: "23:12", text: "return", kind: Keyword }
- { loc: "23:19", text: "1", kind: Number }
- { loc: "24:8", text: "else", kind: Keyword }
- { loc: "25:12", text: "return", kind: Keyword }
- { loc: "25:19", text: "2", kind: Number }
- { loc: "26:4", text: "return", kind: Keyword }
- { loc: "26:11", text: "0", kind: Number }
- { loc: "29:0", text: "void", kind: Keyword }
- { loc: "29:5", text: "test", kind: Function, modifiers: [Definition] }
- { loc: "30:21", text: "int", kind: Keyword }
- { loc: "30:25", text: "r1", kind: Variable, modifiers: [Definition] }
- { loc: "30:30", text: "abs_val", kind: Function }
- { loc: "30:39", text: "3", kind: Number }
- { loc: "31:21", text: "auto", kind: Keyword }
- { loc: "31:26", text: "r2", kind: Variable, modifiers: [Definition, Readonly] }
- { loc: "31:31", text: "sign", kind: Function, modifiers: [Readonly] }
- { loc: "31:36", text: "5", kind: Number }
- { loc: "32:21", text: "int", kind: Keyword }
- { loc: "32:25", text: "r3", kind: Variable, modifiers: [Definition] }
- { loc: "32:30", text: "nested_if", kind: Function }
- { loc: "32:40", text: "1", kind: Number }
- { loc: "32:44", text: "1", kind: Number }
- { loc: "35:3", text: "// namespace basic_if", kind: Comment }

View File

@@ -1,28 +0,0 @@
---
source: tu_index_tests.cpp
created_at: 2026-05-20
input_file: statements/if/basic_if.cpp
---
- { loc: "1:10", kind: Namespace, text: "basic_if", relations: [Definition] }
- { loc: "3:4", kind: Function, text: "abs_val", relations: [Definition] }
- { loc: "3:16", kind: Parameter, text: "x", relations: [Definition] }
- { loc: "4:7", kind: Parameter, text: "x", relations: [Reference] }
- { loc: "5:16", kind: Parameter, text: "x", relations: [Reference] }
- { loc: "6:11", kind: Parameter, text: "x", relations: [Reference] }
- { loc: "9:12", kind: Function, text: "sign", relations: [Definition] }
- { loc: "9:21", kind: Parameter, text: "x", relations: [Definition] }
- { loc: "10:7", kind: Parameter, text: "x", relations: [Reference] }
- { loc: "12:14", kind: Parameter, text: "x", relations: [Reference] }
- { loc: "20:4", kind: Function, text: "nested_if", relations: [Definition] }
- { loc: "20:18", kind: Parameter, text: "a", relations: [Definition] }
- { loc: "20:25", kind: Parameter, text: "b", relations: [Definition] }
- { loc: "21:7", kind: Parameter, text: "a", relations: [Reference] }
- { loc: "22:11", kind: Parameter, text: "b", relations: [Reference] }
- { loc: "29:5", kind: Function, text: "test", relations: [Definition] }
- { loc: "30:25", kind: Variable, text: "r1", relations: [Definition] }
- { loc: "30:30", kind: Function, text: "abs_val", relations: [Reference] }
- { loc: "31:26", kind: Variable, text: "r2", relations: [Definition] }
- { loc: "31:31", kind: Function, text: "sign", relations: [Reference] }
- { loc: "32:25", kind: Variable, text: "r3", relations: [Definition] }
- { loc: "32:30", kind: Function, text: "nested_if", relations: [Reference] }

View File

@@ -1,6 +1,5 @@
#include "test/test.h"
#include "compile/compilation.h"
#include "compile/implement.h"
namespace clice::testing {
namespace {
@@ -8,10 +7,6 @@ namespace {
TEST_SUITE(ClangTidy) {
TEST_CASE(FastCheck) {
#ifdef CLICE_HAS_CLANG_TIDY_MODULES
ASSERT_TRUE(tidy::is_registered_tidy_check("bugprone-integer-division"));
#endif
// ASSERT_TRUE(tidy::is_fast_tidy_check("readability-misleading-indentation"));
// ASSERT_TRUE(tidy::is_fast_tidy_check("bugprone-unused-return-value"));
//
@@ -27,7 +22,6 @@ TEST_CASE(Tidy) {
std::string main_path = TestVFS::path("main.cpp");
CompilationParams params;
params.kind = CompilationKind::Content;
params.clang_tidy = true;
params.vfs = vfs;
params.arguments = {"clang++", "-ffreestanding", "-Xclang", "-undef", main_path.c_str()};
@@ -36,37 +30,6 @@ TEST_CASE(Tidy) {
ASSERT_FALSE(unit.diagnostics().empty());
}
#ifdef CLICE_HAS_CLANG_TIDY_MODULES
TEST_CASE(BugproneIntegerDivision) {
auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();
vfs->add("main.cpp",
"int main() {"
" double d;"
" int i = 42;"
" d = 32 * 8 / (2 + i);"
" return static_cast<int>(d);"
"}");
std::string main_path = TestVFS::path("main.cpp");
CompilationParams params;
params.kind = CompilationKind::Content;
params.clang_tidy = true;
params.vfs = vfs;
params.arguments = {"clang++", "-ffreestanding", "-Xclang", "-undef", main_path.c_str()};
auto unit = compile(params);
ASSERT_TRUE(unit.completed());
bool found = false;
for(auto& diagnostic: unit.diagnostics()) {
if(llvm::StringRef(diagnostic.message).contains("integer division")) {
found = true;
break;
}
}
ASSERT_TRUE(found);
}
#endif
}; // TEST_SUITE(ClangTidy)
} // namespace
} // namespace clice::testing

View File

@@ -11,7 +11,7 @@ namespace {
namespace protocol = kota::ipc::protocol;
TEST_SUITE(document_link, Tester) {
TEST_SUITE(DocumentLink, Tester) {
std::vector<protocol::DocumentLink> links;
@@ -136,7 +136,7 @@ ABCDE
EXPECT_LINK(0, "0", TestVFS::path("data.bin"));
}
}; // TEST_SUITE(document_link)
}; // TEST_SUITE(DocumentLink)
} // namespace

View File

@@ -1,5 +1,4 @@
#include <cstddef>
#include <format>
#include <functional>
#include <memory>
#include <vector>
@@ -8,15 +7,13 @@
#include "test/tester.h"
#include "feature/feature.h"
#include "kota/meta/enum.h"
namespace clice::testing {
namespace {
namespace protocol = kota::ipc::protocol;
TEST_SUITE(document_symbol, Tester) {
TEST_SUITE(DocumentSymbol, Tester) {
std::vector<protocol::DocumentSymbol> symbols;
@@ -183,57 +180,7 @@ VAR(test)
ASSERT_EQ(total_size(symbols), 3U);
}
void format_document_symbols(std::string& out,
const feature::PositionMapper& mapper,
llvm::ArrayRef<feature::DocumentSymbol> nodes,
int depth) {
auto pad = std::string(depth * 2, ' ');
for(auto& node: nodes) {
auto kind = kota::meta::enum_name(static_cast<SymbolKind::Kind>(node.kind), "Unknown");
auto start = mapper.to_position(node.range.begin);
auto end = mapper.to_position(node.range.end);
if(!start || !end)
continue;
auto sel_start = mapper.to_position(node.selection_range.begin);
auto sel_end = mapper.to_position(node.selection_range.end);
out += std::format("- {}{{ name: {}, kind: {}, range: \"{}:{}-{}:{}\"",
pad,
yaml_str(node.name),
kind,
start->line,
start->character,
end->line,
end->character);
if(sel_start && sel_end) {
out += std::format(", selection_range: \"{}:{}-{}:{}\"",
sel_start->line,
sel_start->character,
sel_end->line,
sel_end->character);
}
if(!node.detail.empty()) {
out += std::format(", detail: {}", yaml_str(node.detail));
}
out += " }\n";
if(!node.children.empty()) {
format_document_symbols(out, mapper, node.children, depth + 1);
}
}
}
TEST_CASE(snapshot) {
ASSERT_SNAPSHOT_GLOB(corpus_dir, "**/*.cpp", [&](std::string_view path) -> std::string {
if(!compile_file(path))
return "COMPILE_ERROR";
auto content = unit->interested_content();
feature::PositionMapper mapper(content, feature::PositionEncoding::UTF8);
std::string result;
format_document_symbols(result, mapper, feature::document_symbols(*unit), 0);
return result;
});
}
}; // TEST_SUITE(document_symbol)
}; // TEST_SUITE(DocumentSymbol)
} // namespace

View File

@@ -11,7 +11,7 @@ namespace {
namespace protocol = kota::ipc::protocol;
TEST_SUITE(folding_range, Tester) {
TEST_SUITE(FoldingRange, Tester) {
std::vector<protocol::FoldingRange> ranges;
@@ -429,36 +429,7 @@ $(1)#pragma region level1
)cpp");
}
TEST_CASE(snapshot) {
ASSERT_SNAPSHOT_GLOB(corpus_dir, "**/*.cpp", [&](std::string_view path) -> std::string {
if(!compile_file(path))
return "COMPILE_ERROR";
auto ranges = feature::folding_ranges(*unit);
feature::PositionMapper mapper(unit->interested_content(), feature::PositionEncoding::UTF8);
std::string result;
for(auto& r: ranges) {
auto start = mapper.to_position(r.range.begin);
auto end = mapper.to_position(r.range.end);
if(!start || !end)
continue;
result += std::format("- {{ range: \"{}:{}-{}:{}\"",
start->line,
start->character,
end->line,
end->character);
if(r.kind.has_value()) {
result += std::format(", kind: {}", static_cast<const std::string&>(*r.kind));
}
if(!r.collapsed_text.empty()) {
result += std::format(", collapsed_text: {}", yaml_str(r.collapsed_text));
}
result += " }\n";
}
return result;
});
}
}; // TEST_SUITE(folding_range)
}; // TEST_SUITE(FoldingRange)
} // namespace

View File

@@ -35,6 +35,18 @@ TEST_CASE(IncludeSort) {
ASSERT_NE(edits.size(), 0U);
}
TEST_CASE(FormatCode) {
auto result = feature::format_code("main.cpp", "int add( int a,int b ){return a+b;}");
EXPECT_NE(result.find("int add("), std::string::npos);
EXPECT_EQ(result.find(" int a,int"), std::string::npos);
}
TEST_CASE(FormatCodeIdempotent) {
auto first = feature::format_code("main.cpp", "int add( int a,int b ){return a+b;}");
auto second = feature::format_code("main.cpp", first);
EXPECT_EQ(first, second);
}
}; // TEST_SUITE(Formatting)
} // namespace

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,16 @@
#include <format>
#include <string>
#include "test/test.h"
#include "test/tester.h"
#include "feature/feature.h"
#include "kota/meta/enum.h"
namespace clice::testing {
namespace {
namespace protocol = kota::ipc::protocol;
TEST_SUITE(inlay_hint, Tester) {
TEST_SUITE(InlayHint, Tester) {
std::vector<protocol::InlayHint> hints;
llvm::DenseMap<std::uint32_t, protocol::InlayHint> hints_map;
@@ -1532,38 +1529,6 @@ TEST_CASE(Dependent, skip = true) {
EXPECT_HINT("2", "par3:");
}
TEST_CASE(snapshot) {
ASSERT_SNAPSHOT_GLOB(corpus_dir, "**/*.cpp", [&](std::string_view path) -> std::string {
if(!compile_file(path))
return "COMPILE_ERROR";
auto content = unit->interested_content();
LocalSourceRange range(0, content.size());
auto hints = feature::inlay_hints(*unit, range);
feature::PositionMapper mapper(content, feature::PositionEncoding::UTF8);
std::string result;
for(auto& hint: hints) {
auto pos = mapper.to_position(hint.offset);
if(!pos)
continue;
auto kind = kota::meta::enum_name(hint.kind, "Unknown");
result += std::format("- {{ pos: \"{}:{}\", kind: {}, label: {}",
pos->line,
pos->character,
kind,
yaml_str(hint.label));
if(hint.padding_left) {
result += ", padding_left: true";
}
if(hint.padding_right) {
result += ", padding_right: true";
}
result += " }\n";
}
return result;
});
}
}; // TEST_SUITE(inlay_hint)
}; // TEST_SUITE(InlayHint)
} // namespace
} // namespace clice::testing

View File

@@ -9,8 +9,6 @@
#include "feature/feature.h"
#include "semantic/symbol_kind.h"
#include "kota/meta/enum.h"
namespace clice::testing {
namespace {
@@ -101,7 +99,7 @@ auto decode_relative_tokens(const protocol::SemanticTokens& tokens) -> std::vect
return result;
}
TEST_SUITE(semantic_tokens, Tester) {
TEST_SUITE(SemanticTokens, Tester) {
protocol::SemanticTokens tokens;
std::vector<DecodedToken> decoded;
@@ -140,10 +138,6 @@ void EXPECT_TOKEN(llvm::StringRef name,
ASSERT_EQ(token->modifiers, expected_modifiers);
}
void EXPECT_NO_TOKEN(llvm::StringRef name) {
ASSERT_TRUE(find_by_range(name) == nullptr);
}
TEST_CASE(BasicLexicalKinds) {
run_utf8(R"cpp(
@d1[#define] @m0[FOO]
@@ -270,44 +264,6 @@ int main() {
EXPECT_TOKEN("x3", SymbolKind::Variable, 0);
}
TEST_CASE(IneligibleOperatorReferenceIsSuppressed) {
run_utf8(R"cpp(
struct S {};
S operator+(S lhs, S rhs);
void use(S lhs, S rhs) {
(void)(lhs @plus[+] rhs);
}
)cpp");
EXPECT_NO_TOKEN("plus");
}
TEST_CASE(ConstructorAndDestructorNamesRemainHighlighted) {
run_utf8(R"cpp(
struct S {
@ctor_decl[S]();
@dtor_decl[~]S();
};
S::@ctor_def[S]() {}
void use(S* value) {
value->@dtor_ref[~]S();
}
)cpp");
auto declaration = modifier_mask({SymbolModifiers::Declaration});
auto definition = modifier_mask({SymbolModifiers::Definition});
auto special_member = modifier_mask({SymbolModifiers::ConstructorOrDestructor});
EXPECT_TOKEN("ctor_decl", SymbolKind::Method, declaration | special_member);
EXPECT_TOKEN("dtor_decl", SymbolKind::Method, declaration | special_member);
EXPECT_TOKEN("ctor_def", SymbolKind::Method, definition | special_member);
EXPECT_TOKEN("dtor_ref", SymbolKind::Method, special_member);
}
TEST_CASE(LegacyVarDeclTemplates) {
run_utf8(R"cpp(
extern int @x1[x];
@@ -583,53 +539,7 @@ void f() {
EXPECT_TOKEN("v2", SymbolKind::Variable, definition);
}
TEST_CASE(snapshot) {
ASSERT_SNAPSHOT_GLOB(corpus_dir, "**/*.cpp", [&](std::string_view path) -> std::string {
if(!compile_file(path))
return "COMPILE_ERROR";
auto content = unit->interested_content();
auto tokens = feature::semantic_tokens(*unit);
feature::PositionMapper mapper(content, feature::PositionEncoding::UTF8);
std::string result;
for(auto& token: tokens) {
if(!token.range.valid() || token.range.end <= token.range.begin ||
token.range.end > content.size())
continue;
auto pos = mapper.to_position(token.range.begin);
if(!pos)
continue;
auto text = content.substr(token.range.begin, token.range.length());
auto kind = kota::meta::enum_name(static_cast<SymbolKind::Kind>(token.kind), "Unknown");
result += std::format("- {{ loc: \"{}:{}\", text: {}, kind: {}",
pos->line,
pos->character,
yaml_str(text),
kind);
std::string mods;
for(std::uint32_t i = 0; i < 32; ++i) {
if(token.modifiers & (1u << i)) {
auto name = kota::meta::enum_name(static_cast<SymbolModifiers::Kind>(i));
if(!name.empty()) {
if(!mods.empty())
mods += ", ";
mods += name;
}
}
}
if(!mods.empty()) {
result += std::format(", modifiers: [{}]", mods);
}
result += " }\n";
}
return result;
});
}
}; // TEST_SUITE(semantic_tokens)
}; // TEST_SUITE(SemanticTokens)
} // namespace

View File

@@ -1,18 +1,13 @@
#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(tu_index, Tester) {
TEST_SUITE(TUIndex, Tester) {
index::TUIndex tu_index;
@@ -505,64 +500,6 @@ TEST_CASE(SymbolKinds) {
check_kind("ns", SymbolKind::Namespace);
}
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)
}; // TEST_SUITE(TUIndex)
} // namespace
} // namespace clice::testing

View File

@@ -0,0 +1,248 @@
#include "test/test.h"
#include "support/markup.h"
namespace clice::testing {
namespace {
TEST_SUITE(Markup) {
TEST_CASE(EmptyDocument) {
Markup st;
EXPECT_EQ(st.as_markdown(), "");
}
TEST_CASE(SingleParagraph) {
Markup st;
st.add_paragraph().append_text("hello world");
EXPECT_EQ(st.as_markdown(), "hello world");
}
TEST_CASE(PlainTextSpacing) {
Markup st;
auto& p = st.add_paragraph();
p.append_text("hello");
p.append_text("world");
EXPECT_EQ(st.as_markdown(), "hello world");
}
TEST_CASE(InlineCode) {
Markup st;
auto& p = st.add_paragraph();
p.append_text("Type:");
p.append_text("int", Paragraph::Kind::InlineCode);
EXPECT_EQ(st.as_markdown(), "Type: `int`");
}
TEST_CASE(Bold) {
Markup st;
st.add_paragraph().append_text("important", Paragraph::Kind::Bold);
EXPECT_EQ(st.as_markdown(), "**important**");
}
TEST_CASE(Italic) {
Markup st;
st.add_paragraph().append_text("emphasis", Paragraph::Kind::Italic);
EXPECT_EQ(st.as_markdown(), "*emphasis*");
}
TEST_CASE(Strikethrough) {
Markup st;
st.add_paragraph().append_text("removed", Paragraph::Kind::Strikethrough);
EXPECT_EQ(st.as_markdown(), "~~removed~~");
}
TEST_CASE(MixedInline) {
Markup st;
auto& p = st.add_paragraph();
p.append_text("Returns:", Paragraph::Kind::Bold);
p.append_text("the result");
EXPECT_EQ(st.as_markdown(), "**Returns:** the result");
}
TEST_CASE(ConsecutiveInlineCode) {
Markup st;
auto& p = st.add_paragraph();
p.append_text("int", Paragraph::Kind::InlineCode);
p.append_text("x", Paragraph::Kind::InlineCode);
EXPECT_EQ(st.as_markdown(), "`int` `x`");
}
TEST_CASE(Heading) {
Markup st;
st.add_heading(3).append_text("Title");
EXPECT_EQ(st.as_markdown(), "### Title");
}
TEST_CASE(HeadingWithInlineCode) {
Markup st;
auto& h = st.add_heading(2);
h.append_text("function");
h.append_text("foo", Paragraph::Kind::InlineCode);
EXPECT_EQ(st.as_markdown(), "## function `foo`");
}
TEST_CASE(Ruler) {
Markup st;
st.add_paragraph().append_text("above");
st.add_ruler();
st.add_paragraph().append_text("below");
auto md = st.as_markdown();
EXPECT_NE(md.find("above"), std::string::npos);
EXPECT_NE(md.find("---"), std::string::npos);
EXPECT_NE(md.find("below"), std::string::npos);
}
TEST_CASE(ConsecutiveRulers) {
Markup st;
st.add_paragraph().append_text("text");
st.add_ruler();
st.add_ruler();
st.add_paragraph().append_text("more");
auto md = st.as_markdown();
auto first = md.find("---");
auto second = md.find("---", first + 3);
EXPECT_EQ(second, std::string::npos);
}
TEST_CASE(LeadingTrailingRulers) {
Markup st;
st.add_ruler();
st.add_paragraph().append_text("content");
st.add_ruler();
EXPECT_EQ(st.as_markdown(), "content");
}
TEST_CASE(CodeBlock) {
Markup st;
st.add_code_block("int x = 0;", "cpp");
EXPECT_EQ(st.as_markdown(), "```cpp\nint x = 0;\n```");
}
TEST_CASE(CodeBlockTrailingNewline) {
Markup st;
st.add_code_block("int x = 0;\n", "cpp");
EXPECT_EQ(st.as_markdown(), "```cpp\nint x = 0;\n```");
}
TEST_CASE(CodeBlockNoLang) {
Markup st;
st.add_code_block("hello");
EXPECT_EQ(st.as_markdown(), "```\nhello\n```");
}
TEST_CASE(BulletListSimple) {
Markup st;
auto& list = st.add_bullet_list();
list.add_item().add_paragraph().append_text("one");
list.add_item().add_paragraph().append_text("two");
list.add_item().add_paragraph().append_text("three");
EXPECT_EQ(st.as_markdown(), "- one\n- two\n- three");
}
TEST_CASE(BulletListFormatted) {
Markup st;
auto& list = st.add_bullet_list();
list.add_item().add_paragraph().append_text("bold", Paragraph::Kind::Bold);
list.add_item().add_paragraph().append_text("code", Paragraph::Kind::InlineCode);
EXPECT_EQ(st.as_markdown(), "- **bold**\n- `code`");
}
TEST_CASE(BulletListMultiline) {
Markup st;
auto& list = st.add_bullet_list();
list.add_item().add_paragraph().append_text("line1\nline2");
auto md = st.as_markdown();
EXPECT_NE(md.find("- line1\n line2"), std::string::npos);
}
TEST_CASE(BlockSeparation) {
Markup st;
st.add_paragraph().append_text("first");
st.add_paragraph().append_text("second");
auto md = st.as_markdown();
EXPECT_NE(md.find("first\n"), std::string::npos);
EXPECT_NE(md.find("second"), std::string::npos);
EXPECT_EQ(md.find("firstsecond"), std::string::npos);
}
TEST_CASE(ParagraphThenList) {
Markup st;
st.add_paragraph().append_text("Parameters:");
auto& list = st.add_bullet_list();
list.add_item().add_paragraph().append_text("int x", Paragraph::Kind::InlineCode);
auto md = st.as_markdown();
EXPECT_EQ(md.find("Parameters:- "), std::string::npos);
EXPECT_EQ(md.find("Parameters:-"), std::string::npos);
EXPECT_NE(md.find("Parameters:"), std::string::npos);
EXPECT_NE(md.find("- `int x`"), std::string::npos);
}
TEST_CASE(HeadingThenRuler) {
Markup st;
st.add_heading(3).append_text("title");
st.add_ruler();
st.add_paragraph().append_text("body");
auto md = st.as_markdown();
EXPECT_NE(md.find("### title\n"), std::string::npos);
EXPECT_NE(md.find("---"), std::string::npos);
EXPECT_NE(md.find("body"), std::string::npos);
}
TEST_CASE(TripleNewlineCollapse) {
Markup st;
st.add_paragraph().append_text("a\n\n\n\nb");
auto md = st.as_markdown();
EXPECT_EQ(md.find("\n\n\n"), std::string::npos);
}
TEST_CASE(ClonePreservesHeading) {
Markup st;
st.add_heading(2).append_text("Title");
Markup copy = st;
auto md = copy.as_markdown();
EXPECT_NE(md.find("## Title"), std::string::npos);
}
TEST_CASE(NewlineChar) {
Markup st;
auto& p = st.add_paragraph();
p.append_text("line1");
p.append_newline_char();
p.append_text("line2");
auto md = st.as_markdown();
EXPECT_NE(md.find("line1"), std::string::npos);
EXPECT_NE(md.find("line2"), std::string::npos);
}
TEST_CASE(FullHoverLike) {
Markup st;
st.add_heading(3).append_text("function").append_text("add", Paragraph::Kind::InlineCode);
st.add_ruler();
st.add_paragraph().append_text("\xe2\x86\x92").append_text("int", Paragraph::Kind::InlineCode);
st.add_paragraph().append_text("Parameters:");
auto& params = st.add_bullet_list();
params.add_item().add_paragraph().append_text("int a", Paragraph::Kind::InlineCode);
params.add_item().add_paragraph().append_text("int b", Paragraph::Kind::InlineCode);
st.add_ruler();
st.add_code_block("int add(int a, int b);\n", "cpp");
auto md = st.as_markdown();
EXPECT_NE(md.find("### function `add`"), std::string::npos);
EXPECT_NE(md.find("---"), std::string::npos);
EXPECT_NE(md.find("\xe2\x86\x92 `int`"), std::string::npos);
EXPECT_NE(md.find("Parameters:"), std::string::npos);
EXPECT_NE(md.find("- `int a`"), std::string::npos);
EXPECT_NE(md.find("- `int b`"), std::string::npos);
EXPECT_NE(md.find("```cpp"), std::string::npos);
EXPECT_EQ(md.find("`int`Parameters"), std::string::npos);
EXPECT_EQ(md.find("Parameters:- "), std::string::npos);
}
}; // TEST_SUITE(Markup)
} // namespace
} // namespace clice::testing

View File

@@ -1,121 +0,0 @@
#include "test/test.h"
#include "support/format.h"
#include "support/structed_text.h"
namespace clice::testing {
namespace {
TEST_SUITE(StructedText) {
TEST_CASE(Paragraph) {
constexpr const char* cb =
R"c(// Without processing, recommended
char *longestPalindrome_solv2(const char *s) {
int len = strlen(s);
int max_start = 0;
int max_len = 0;
for (int i = 0; i < len; ++i) {
// j = 0, max_len is odd
// j = 1, max_len is even
for (int j = 0; j <= 1; ++j) {
int l = i;
int r = i + j;
// expand the range from center
while (l >= 0 && r < len && s[l] == s[r]) {
--l;
++r;
}
++l;
--r;
if (max_len < r - l + 1) {
max_len = r - l + 1;
max_start = i;
}
}
}
char *res = (char *)malloc((max_len + 1) * sizeof(char));
memcpy(res, s + max_start, max_len);
res[max_len] = '\0';
return res;
}
)c";
StructedText st;
st.add_paragraph().append_text("CodeBlock Example:").append_newline_char();
st.add_code_block(cb, "c");
auto& para = st.add_paragraph();
para.append_text("para1").append_newline_char();
/// std::println("{}", st.as_markdown());
}
TEST_CASE(BulletList) {
StructedText st;
st.add_bullet_list().add_item().add_paragraph().append_text("Item1");
st.add_bullet_list().add_item().add_paragraph().append_text("Item2",
Paragraph::Kind::InlineCode);
st.add_bullet_list().add_item().add_paragraph().append_text("Item3", Paragraph::Kind::Bold);
st.add_bullet_list().add_item().add_paragraph().append_text("Item4", Paragraph::Kind::Italic);
st.add_bullet_list().add_item().add_paragraph().append_text("Item5",
Paragraph::Kind::Strikethough);
/// std::println("{}", st.as_markdown());
}
TEST_CASE(FullText) {
StructedText st;
st.add_heading(3)
.append_text("function")
.append_text("test_bar", Paragraph::Kind::InlineCode)
.append_newline_char()
.append_text("Provided by:")
.append_text("`foo/bar/baz.h`");
st.add_ruler();
st.add_paragraph()
.append_text("")
.append_text("int", Paragraph::Kind::InlineCode)
.append_newline_char();
st.add_paragraph().append_text("Paramaters:", Paragraph::Kind::Bold).append_newline_char();
auto& params = st.add_bullet_list();
params.add_item()
.add_paragraph()
.append_text("int foo", Paragraph::Kind::InlineCode)
.append_text("doc for foo\ndoc for foo line2");
params.add_item()
.add_paragraph()
.append_text("char** bar", Paragraph::Kind::InlineCode)
.append_text("doc for bar");
params.add_item()
.add_paragraph()
.append_text("char** baz", Paragraph::Kind::InlineCode)
.append_text("doc for baz");
st.add_paragraph().append_text(R"md(
brief block
brief line2
a b c d e f
~~~~^
This is *Italic* **Bold** ~~Striketough~~, `InlineCode`
)md");
st.add_ruler();
st.add_paragraph().append_text("Details:", Paragraph::Kind::Bold).append_newline_char();
auto& details = st.add_bullet_list();
details.add_item().add_paragraph().append_text("Detail1: blah blah...");
details.add_item().add_paragraph().append_text("Detail2: blah blah...\n Line2: ......");
details.add_item().add_paragraph().append_text("Detail3: blah blah...");
st.add_ruler();
st.add_paragraph().append_text("Details:", Paragraph::Kind::Bold).append_newline_char();
auto& warnings = st.add_bullet_list();
warnings.add_item().add_paragraph().append_text("warnings1: blah blah...");
warnings.add_item().add_paragraph().append_text("warnings2: blah blah...\n Line2: ......");
warnings.add_item().add_paragraph().append_text("warnings3: blah blah...");
st.add_ruler();
st.add_code_block("int test_bar(int foo, char **bar, char **baz);\n", "cpp");
/// std::println("{}", st.as_markdown());
}
}; // TEST_SUITE(StructedText)
} // namespace
} // namespace clice::testing

View File

@@ -1,5 +1,3 @@
#include <string>
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
@@ -7,12 +5,6 @@
namespace clice::testing {
/// Set by --test-dir from the command line; empty if not specified.
inline std::string test_dir;
/// Set by --corpus-dir from the command line; empty if not specified.
inline std::string corpus_dir;
#ifdef _WIN32
constexpr inline bool Windows = true;
#else

View File

@@ -230,17 +230,6 @@ bool Tester::compile_with_modules(llvm::StringRef standard) {
return try_compile();
}
bool Tester::compile_file(llvm::StringRef path, llvm::StringRef standard) {
auto buffer = llvm::MemoryBuffer::getFile(path);
if(!buffer) {
LOG_ERROR("Failed to read file: {}", path);
return false;
}
auto filename = llvm::sys::path::filename(path);
add_main(filename, (*buffer)->getBuffer());
return compile(standard);
}
std::uint32_t Tester::point(llvm::StringRef name, llvm::StringRef file) {
if(file.empty()) {
file = src_path;

View File

@@ -1,6 +1,5 @@
#pragma once
#include <format>
#include <optional>
#include <string>
#include <vector>
@@ -64,9 +63,6 @@ struct Tester {
bool compile_with_modules(llvm::StringRef standard = "-std=c++20");
/// Read a file from disk and compile it directly (no VFS content needed).
bool compile_file(llvm::StringRef path, llvm::StringRef standard = "-std=c++20");
/// Driver path: uses CompilationDatabase + toolchain cache, has system headers.
void prepare_driver(llvm::StringRef standard = "-std=c++20");
@@ -89,28 +85,4 @@ struct Tester {
void clear();
};
inline std::string yaml_str(llvm::StringRef s) {
std::string result;
result.reserve(s.size() + 2);
result += '"';
for(char c: s) {
switch(c) {
case '"': result += "\\\""; break;
case '\\': result += "\\\\"; break;
case '\n': result += "\\n"; break;
case '\r': result += "\\r"; break;
case '\t': result += "\\t"; break;
default:
if(static_cast<unsigned char>(c) < 0x20) {
result += std::format("\\x{:02x}", static_cast<unsigned char>(c));
} else {
result += c;
}
break;
}
}
result += '"';
return result;
}
} // namespace clice::testing

View File

@@ -1,7 +1,6 @@
#include <string>
#include <string_view>
#include "test/platform.h"
#include "support/logging.h"
#include "kota/deco/deco.h"
@@ -12,20 +11,23 @@ namespace {
using kota::deco::decl::KVStyle;
struct TestOptions {
kota::zest::Options zest;
DecoKV(style = KVStyle::JoinedOrSeparate,
names = {"--test-filter", "--test-filter="},
help = "Filter tests by name",
required = false)
<std::string> test_filter;
DecoKVStyled(KVStyle::JoinedOrSeparate, help = "log level: trace/debug/info/warn/err";
required = false)
DecoKV(style = KVStyle::JoinedOrSeparate,
names = {"--log-level", "--log-level="},
help = "Log level: trace/debug/info/warn/err",
required = false)
<std::string> log_level;
DecoKVStyled(KVStyle::JoinedOrSeparate, meta_var = "<DIR>"; help = "test data directory";
required = false)
DecoKV(style = KVStyle::JoinedOrSeparate,
names = {"--test-dir", "--test-dir="},
help = "Test data directory",
required = false)
<std::string> test_dir;
DecoKVStyled(KVStyle::JoinedOrSeparate, meta_var = "<DIR>";
help = "corpus directory for snapshot glob tests";
required = false)
<std::string> corpus_dir;
};
} // namespace
@@ -34,20 +36,13 @@ int main(int argc, const char** argv) {
auto args = kota::deco::util::argvify(argc, argv);
auto parsed = kota::deco::cli::parse<TestOptions>(args);
if(!parsed.has_value()) {
return 1;
std::string_view filter = {};
if(parsed.has_value() && parsed->options.test_filter.has_value()) {
filter = *parsed->options.test_filter;
}
auto& opts = parsed->options;
if(opts.test_dir.has_value())
clice::testing::test_dir = *opts.test_dir;
if(opts.corpus_dir.has_value())
clice::testing::corpus_dir = *opts.corpus_dir;
if(opts.log_level.has_value()) {
auto level = *opts.log_level;
if(parsed.has_value() && parsed->options.log_level.has_value()) {
auto level = *parsed->options.log_level;
if(level == "trace") {
clice::logging::options.level = clice::logging::Level::trace;
} else if(level == "debug") {
@@ -63,5 +58,5 @@ int main(int argc, const char** argv) {
clice::logging::stderr_logger("test", clice::logging::options);
return kota::zest::run_tests(std::move(opts.zest));
return kota::zest::Runner::instance().run_tests(filter);
}