From e8c4b034ed904c3e5fd1b166205ee7d679cde795 Mon Sep 17 00:00:00 2001 From: qingfengzl Date: Fri, 18 Jul 2025 20:17:09 +0800 Subject: [PATCH] Fix selection tree, add some unit tests (#154) Co-authored-by: ykiko --- .github/workflows/check-format.yml | 35 ++++++ .pre-commit-config.yaml | 2 +- include/AST/SourceCode.h | 1 - include/Async/Task.h | 6 +- include/Async/libuv.h | 3 +- include/Compiler/Command.h | 3 +- include/Test/CTest.h | 1 + src/AST/Selection.cpp | 5 + src/Async/Async.cpp | 1 - src/Async/libuv.cpp | 5 +- src/Driver/unit_tests.cc | 3 +- src/Feature/DocumentSymbol.cpp | 3 +- src/Feature/SemanticToken.cpp | 2 +- unittests/AST/Selection.cpp | 190 ++++++++++++++++++++++------- 14 files changed, 204 insertions(+), 56 deletions(-) create mode 100644 .github/workflows/check-format.yml diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml new file mode 100644 index 00000000..2460af8a --- /dev/null +++ b/.github/workflows/check-format.yml @@ -0,0 +1,35 @@ +name: format + +on: + pull_request: + push: + branches: [main] + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install clang-format + run: | + wget -qO - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/llvm.asc + echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" | sudo tee /etc/apt/sources.list.d/llvm20.list + echo "deb-src http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" | sudo tee -a /etc/apt/sources.list.d/llvm20.list + sudo apt-get update && sudo apt-get install -y clang-format-20 + clang-format-20 --version + + - name: Run clang-format check + run: | + FILES=$(find ./ -type f | grep -P '^\.\/(src|include)(\/[^\/]+)*\/[^\/]+\.(h|cpp|cc)$') + + UNFORMATTED=$(clang-format-20 --dry-run --Werror $FILES) + + if [ $? -ne 0 ]; then + echo "× Some files are not properly formatted. Run clang-format to fix them." + exit 1 + else + echo "✓ All files are properly formatted." + fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6a23ec45..27448bed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -files: '^(src|include)(/[^/]+)*/[^/]+\.(h|cpp)$' +files: '^(src|include)(/[^/]+)*/[^/]+\.(h|cpp|cc)$' repos: - repo: https://github.com/pre-commit/mirrors-clang-format rev: "v20.1.0" diff --git a/include/AST/SourceCode.h b/include/AST/SourceCode.h index 45250f8f..81915db4 100644 --- a/include/AST/SourceCode.h +++ b/include/AST/SourceCode.h @@ -28,7 +28,6 @@ struct LocalSourceRange { } }; - /// Get the content of the file with the given file ID. llvm::StringRef getFileContent(const clang::SourceManager& SM, clang::FileID fid); diff --git a/include/Async/Task.h b/include/Async/Task.h index 43880a2d..9056a703 100644 --- a/include/Async/Task.h +++ b/include/Async/Task.h @@ -290,9 +290,9 @@ public: promise_base* handle = core; while(handle) { clice::println("{}:{}:{}", - handle->location.file_name(), - handle->location.line(), - handle->location.function_name()); + handle->location.file_name(), + handle->location.line(), + handle->location.function_name()); handle = handle->continuation; } } diff --git a/include/Async/libuv.h b/include/Async/libuv.h index 30ba02ef..68f2eb38 100644 --- a/include/Async/libuv.h +++ b/include/Async/libuv.h @@ -59,7 +59,8 @@ T* uv_cast(U& u) { return reinterpret_cast(&u); } -void uv_check_result(const int result, const std::source_location location = std::source_location::current()); +void uv_check_result(const int result, + const std::source_location location = std::source_location::current()); template class Task; diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h index c2db740d..535d8266 100644 --- a/include/Compiler/Command.h +++ b/include/Compiler/Command.h @@ -79,7 +79,8 @@ public: auto load_commands(this Self& self, llvm::StringRef json_content) -> std::expected, std::string>; - auto get_command(this Self& self, llvm::StringRef file, bool resource_dir = false) -> LookupInfo; + auto get_command(this Self& self, llvm::StringRef file, bool resource_dir = false) + -> LookupInfo; private: /// The memory pool to hold all cstring and command list. diff --git a/include/Test/CTest.h b/include/Test/CTest.h index b3311fa0..dd62d40e 100644 --- a/include/Test/CTest.h +++ b/include/Test/CTest.h @@ -80,6 +80,7 @@ public: Tester& compile(llvm::StringRef standard = "-std=c++20") { auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path); + database.update_command("fake", src_path, command); params.arguments = database.get_command(src_path).arguments; diff --git a/src/AST/Selection.cpp b/src/AST/Selection.cpp index b81601b2..206c7d0f 100644 --- a/src/AST/Selection.cpp +++ b/src/AST/Selection.cpp @@ -79,6 +79,11 @@ struct SelectionBuilder { template bool hook(const Node* node, const Callback& callback) { + + if(!node) { + return true; + } + if constexpr(requires { node->isImplicit(); }) if(node->isImplicit()) return true; diff --git a/src/Async/Async.cpp b/src/Async/Async.cpp index f5725eb2..147c3205 100644 --- a/src/Async/Async.cpp +++ b/src/Async/Async.cpp @@ -4,7 +4,6 @@ namespace clice::async { - /// The default event loop. uv_loop_t* loop = nullptr; diff --git a/src/Async/libuv.cpp b/src/Async/libuv.cpp index 7e601484..59cea42b 100644 --- a/src/Async/libuv.cpp +++ b/src/Async/libuv.cpp @@ -30,10 +30,7 @@ const std::error_category& category() { void uv_check_result(const int result, const std::source_location location) { if(result < 0) { log::warn("libuv error: {}", uv_strerror(result)); - log::warn("At {}:{}:{}", - location.file_name(), - location.line(), - location.function_name()); + log::warn("At {}:{}:{}", location.file_name(), location.line(), location.function_name()); } } diff --git a/src/Driver/unit_tests.cc b/src/Driver/unit_tests.cc index 15010f33..2f7b2695 100644 --- a/src/Driver/unit_tests.cc +++ b/src/Driver/unit_tests.cc @@ -38,7 +38,8 @@ int main(int argc, char** argv) { fs::resource_dir = cl::resource_dir.getValue(); } else { if(auto result = fs::init_resource_dir(argv[0]); !result) { - llvm::outs() << std::format("Failed to get resource directory, because {}\n", result.error()); + llvm::outs() << std::format("Failed to get resource directory, because {}\n", + result.error()); return 1; } } diff --git a/src/Feature/DocumentSymbol.cpp b/src/Feature/DocumentSymbol.cpp index b7e1b1c5..5c0c022a 100644 --- a/src/Feature/DocumentSymbol.cpp +++ b/src/Feature/DocumentSymbol.cpp @@ -50,7 +50,8 @@ public: } auto ND = llvm::cast(decl); - auto [fid, selectionRange] = unit.decompose_range(unit.expansion_location(ND->getLocation())); + auto [fid, selectionRange] = + unit.decompose_range(unit.expansion_location(ND->getLocation())); auto& frame = interestedOnly ? result : sharedResult[fid]; auto cursor = frame.cursor; diff --git a/src/Feature/SemanticToken.cpp b/src/Feature/SemanticToken.cpp index b42e10cd..facb2b01 100644 --- a/src/Feature/SemanticToken.cpp +++ b/src/Feature/SemanticToken.cpp @@ -108,7 +108,7 @@ public: } auto [fid, range] = unit.decompose_range(location); - if(fid != unit.interested_file()){ + if(fid != unit.interested_file()) { /// FIXME: Use a better way to handle this. return; } diff --git a/unittests/AST/Selection.cpp b/unittests/AST/Selection.cpp index d4a04d6e..375b4945 100644 --- a/unittests/AST/Selection.cpp +++ b/unittests/AST/Selection.cpp @@ -75,64 +75,174 @@ std::array makeNodeSequence() { } TEST(Selection, VarDeclSelectionBoundary) { - const char* code = R"cpp( -$(b1)int xxx$(b2)yyy$(e1) = 1$(e2);$(e3) -)cpp"; + { + const char* code = R"cpp( + $(b1)int xxx$(b2)yyy$(e1) = 1$(e2);$(e3) + )cpp"; - SelectionTester tx("main.cpp", code); - tx.compile(); + SelectionTester tx("main.cpp", code); + tx.compile(); - std::vector selects; - for(int begin = 1; begin <= 2; begin++) { - for(int end = 1; end <= 3; end++) { - uint32_t bp = tx.offset(std::format("b{}", begin)); - uint32_t ep = tx.offset(std::format("e{}", end)); - selects.push_back({bp, ep}); + std::vector selects; + for(int begin = 1; begin <= 2; begin++) { + for(int end = 1; end <= 3; end++) { + uint32_t bp = tx.offset(std::format("b{}", begin)); + uint32_t ep = tx.offset(std::format("e{}", end)); + selects.push_back({bp, ep}); + } + } + + auto& unit = *tx.unit; + auto tokens = unit.spelled_tokens(unit.interested_file()); + for(auto& [begin, end]: selects) { + auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + + SelectionBuilder builder(left, right, unit.context(), unit); + auto tree = builder.build(); + // debug(tree); + + auto kinds = makeNodeSequence(); + tx.expectPreorderSequence(tree, kinds); } } - auto& unit = *tx.unit; - auto tokens = unit.spelled_tokens(unit.interested_file()); - for(auto& [begin, end]: selects) { - auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + { + const char* code = R"cpp( + int $(b1)x$(e1) = 114, $(b2)y$(e2) = 514, $(b3)z$(e3); + )cpp"; - SelectionBuilder builder(left, right, unit.context(), unit); + SelectionTester tx("main.cpp", code); + tx.compile(); + + std::vector selects; + for(int i = 1; i <= 3; ++i) { + uint32_t bp = tx.offset(std::format("b{}", i)); + uint32_t ep = tx.offset(std::format("e{}", i)); + selects.push_back({bp, ep}); + } + + auto& unit = *tx.unit; + auto tokens = unit.spelled_tokens(unit.interested_file()); + for(auto& [begin, end]: selects) { + auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + + SelectionBuilder builder(left, right, unit.context(), unit); + auto tree = builder.build(); + + auto kinds = makeNodeSequence(); + tx.expectPreorderSequence(tree, kinds); + } + } + + { + const char* code = R"cpp( + $(b1)const$(b2) static$(b3) char $(b4)x$(e1) = 'c';$(e2) + )cpp"; + + SelectionTester tx("main.cpp", code); + tx.compile(); + + std::vector selects; + for(int i = 1; i <= 4; ++i) { + for(int j = 1; j <= 2; ++j) { + uint32_t bp = tx.offset(std::format("b{}", i)); + uint32_t ep = tx.offset(std::format("e{}", j)); + selects.push_back({bp, ep}); + } + } + + auto& unit = *tx.unit; + auto tokens = unit.spelled_tokens(unit.interested_file()); + for(auto& [begin, end]: selects) { + auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + + SelectionBuilder builder(left, right, unit.context(), unit); + auto tree = builder.build(); + + auto kinds = makeNodeSequence(); + tx.expectPreorderSequence(tree, kinds); + } + } + + { + const char* code = R"cpp( + struct A { + int a; + int b; + }; + + int main(int argc, char **argv) { + $(b)auto$(e) a = A{114, 514}; + return 0; + } + )cpp"; + + SelectionTester tx("main.cpp", code); + tx.compile(); + + uint32_t bp = tx.offset("b"); + uint32_t ep = tx.offset("e"); + + auto& unit = *tx.unit; + auto tokens = unit.spelled_tokens(unit.interested_file()); + auto [left, right] = SelectionBuilder::selectionBound(tokens, {bp, ep}, unit); + SelectionBuilder builder{left, right, unit.context(), unit}; auto tree = builder.build(); - // debug(tree); - auto kinds = makeNodeSequence(); tx.expectPreorderSequence(tree, kinds); } } TEST(Selection, ParmVarDeclBoundary) { - const char* code = R"cpp( -void f($(b1)int xxx$(b2)yyy$(e1) = 1$(e2)) {} -)cpp"; + { + const char* code = R"cpp( + void f($(b1)int xxx$(b2)yyy$(e1) = 1$(e2)) {} + )cpp"; - SelectionTester tx("main.cpp", code); - tx.compile(); + SelectionTester tx("main.cpp", code); + tx.compile(); - std::vector selects; - for(int begin = 1; begin <= 2; begin++) { - for(int end = 1; end <= 2; end++) { - uint32_t bp = tx.offset(std::format("b{}", begin)); - uint32_t ep = tx.offset(std::format("e{}", end)); - selects.push_back({bp, ep}); + std::vector selects; + for(int begin = 1; begin <= 2; begin++) { + for(int end = 1; end <= 2; end++) { + uint32_t bp = tx.offset(std::format("b{}", begin)); + uint32_t ep = tx.offset(std::format("e{}", end)); + selects.push_back({bp, ep}); + } + } + + auto& unit = *tx.unit; + auto tokens = unit.spelled_tokens(unit.interested_file()); + for(auto& [begin, end]: selects) { + auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + + SelectionBuilder builder(left, right, unit.context(), unit); + auto tree = builder.build(); + // debug(tree); + + auto kinds = makeNodeSequence(); + tx.expectPreorderSequence(tree, kinds); } } - auto& unit = *tx.unit; - auto tokens = unit.spelled_tokens(unit.interested_file()); - for(auto& [begin, end]: selects) { - auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + { + const char* code = R"cpp( + int foo() { return 42; }$(b1);$(e1) + )cpp"; - SelectionBuilder builder(left, right, unit.context(), unit); + SelectionTester tx("main.cpp", code); + tx.compile(); + + auto bp = tx.offset("b1"); + auto ep = tx.offset("e1"); + + auto& unit = *tx.unit; + auto tokens = unit.spelled_tokens(unit.interested_file()); + auto [left, right] = SelectionBuilder::selectionBound(tokens, {bp, ep}, unit); + SelectionBuilder builder{left, right, unit.context(), unit}; auto tree = builder.build(); - // debug(tree); - auto kinds = makeNodeSequence(); - tx.expectPreorderSequence(tree, kinds); + tx.expectPreorderSequence(tree, {}); } } @@ -350,8 +460,7 @@ class Test { auto& unit = *tx.unit; auto tokens = unit.spelled_tokens(unit.interested_file()); for(auto& [begin, end]: b12_e123) { - auto [left, right] = - SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); SelectionBuilder builder(left, right, unit.context(), unit); auto tree = builder.build(); @@ -379,8 +488,7 @@ class Test { auto& unit = *tx.unit; auto tokens = unit.spelled_tokens(unit.interested_file()); for(auto& [begin, end]: b3_e123) { - auto [left, right] = - SelectionBuilder::selectionBound(tokens, {begin, end}, unit); + auto [left, right] = SelectionBuilder::selectionBound(tokens, {begin, end}, unit); SelectionBuilder builder(left, right, unit.context(), unit); auto tree = builder.build();