module; export module lsp.test.semantic.main; import std; import tree_sitter; import lsp.language.ast; import lsp.language.semantic; import lsp.language.symbol; import lsp.utils.string; import lsp.protocol; extern "C" const TSLanguage* tree_sitter_tsf(void); namespace { using namespace lsp; struct Options { std::filesystem::path workspace; std::filesystem::path target_file; bool show_calls = true; bool show_inheritance = true; bool show_references = true; bool quiet = false; bool help = false; }; void PrintUsage(const char* program_name) { std::cout << "\n╭─────────────────────────────────────╮\n"; std::cout << "│ Semantic Graph Inspector │\n"; std::cout << "╰─────────────────────────────────────╯\n\n"; std::cout << "Usage:\n"; std::cout << " " << program_name << " [options]\n\n"; std::cout << "Options:\n"; std::cout << " -h, --help Show this help message\n"; std::cout << " --no-calls Skip printing call graph\n"; std::cout << " --no-inheritance Skip printing inheritance relationships\n"; std::cout << " --no-references Skip printing reference summary\n"; std::cout << " -q, --quiet Reduce informational output\n\n"; std::cout << "Example:\n"; std::cout << " " << program_name << " ./tsl_workspace ./tsl_workspace/sample.tsf\n\n"; } bool ParseArguments(int argc, char** argv, Options& options) { for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg == "-h" || arg == "--help") { options.help = true; return true; } else if (arg == "--no-calls") { options.show_calls = false; } else if (arg == "--no-inheritance") { options.show_inheritance = false; } else if (arg == "--no-references") { options.show_references = false; } else if (arg == "-q" || arg == "--quiet") { options.quiet = true; } else if (arg.starts_with("-")) { std::cerr << "Unknown option: " << arg << std::endl; return false; } else if (options.workspace.empty()) { options.workspace = arg; } else if (options.target_file.empty()) { options.target_file = arg; } else { std::cerr << "Unexpected argument: " << arg << std::endl; return false; } } return !options.workspace.empty() && !options.target_file.empty(); } void Expect(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } std::string ReadFile(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary); Expect(file.is_open(), "Failed to open fixture: " + path.string()); std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); return content; } bool IsTsfFile(const std::filesystem::path& path) { if (!path.has_extension()) return false; auto ext = utils::ToLower(path.extension().string()); return ext == ".tsf"; } void BuildSymbolsForFile(const std::filesystem::path& path, language::symbol::SymbolTable& table) { const std::string source = ReadFile(path); std::unique_ptr parser(ts_parser_new(), &ts_parser_delete); Expect(parser != nullptr, "Failed to create tree-sitter parser"); Expect(ts_parser_set_language(parser.get(), tree_sitter_tsf()), "Failed to set TSL language"); std::unique_ptr tree( ts_parser_parse_string(parser.get(), nullptr, source.c_str(), source.size()), &ts_tree_delete); Expect(tree != nullptr, "Failed to parse " + path.string()); language::ast::Deserializer deserializer; auto ast_result = deserializer.Parse(ts_tree_root_node(tree.get()), source); Expect(ast_result.IsSuccess(), "AST parse failed for " + path.string()); Expect(ast_result.root != nullptr, "AST root missing for " + path.string()); language::symbol::Builder builder(table); builder.Build(*ast_result.root); } struct WorkspaceSymbols { std::unordered_map, utils::IHasher, utils::IEqualTo> by_name; }; void CollectSymbolsFromFile(const std::filesystem::path& path, WorkspaceSymbols& registry) { language::symbol::SymbolTable table; BuildSymbolsForFile(path, table); for (const auto& symbol_ref : table.all_definitions()) { const auto& symbol = symbol_ref.get(); // 调试:打印某些类的 kind if (symbol.name() == "Document" || symbol.name() == "Properties" || symbol.name() == "Endnotes" || symbol.name() == "Theme") { std::cerr << "CollectSymbols: " << symbol.name() << " from " << path.filename() << " kind=" << static_cast(symbol.kind()) << std::endl; } registry.by_name[symbol.name()].push_back(symbol); } } void BuildWorkspaceSymbols(const std::filesystem::path& workspace, const std::filesystem::path& target_file, WorkspaceSymbols& registry) { auto options = std::filesystem::directory_options::follow_directory_symlink | std::filesystem::directory_options::skip_permission_denied; size_t total_files = 0; size_t loaded_files = 0; size_t failed_files = 0; for (const auto& entry : std::filesystem::recursive_directory_iterator(workspace, options)) { if (!entry.is_regular_file()) continue; if (!IsTsfFile(entry.path())) continue; total_files++; std::error_code ec; if (std::filesystem::equivalent(entry.path(), target_file, ec) && !ec) { continue; } try { CollectSymbolsFromFile(entry.path(), registry); loaded_files++; } catch (const std::exception& e) { failed_files++; std::cerr << "⚠ Failed to load " << entry.path().filename() << ": " << e.what() << std::endl; } } std::cout << "\n📊 Summary: " << loaded_files << "/" << total_files << " files loaded successfully"; if (failed_files > 0) { std::cout << " (" << failed_files << " failed)"; } std::cout << "\n"; } struct FileAnalysis { std::filesystem::path path; std::unique_ptr symbol_table; std::unique_ptr semantic_model; }; FileAnalysis AnalyzeFile(const std::filesystem::path& path, std::unique_ptr table, WorkspaceSymbols& registry) { Expect(table != nullptr, "Symbol table is null"); const std::string source = ReadFile(path); std::unique_ptr parser(ts_parser_new(), &ts_parser_delete); Expect(parser != nullptr, "Failed to create tree-sitter parser"); Expect(ts_parser_set_language(parser.get(), tree_sitter_tsf()), "Failed to set TSL language"); std::unique_ptr tree( ts_parser_parse_string(parser.get(), nullptr, source.c_str(), source.size()), &ts_tree_delete); Expect(tree != nullptr, "Failed to parse fixture into tree"); language::ast::Deserializer deserializer; auto ast_result = deserializer.Parse(ts_tree_root_node(tree.get()), source); Expect(ast_result.IsSuccess(), "AST parse failed for " + path.string()); Expect(ast_result.root != nullptr, "AST root missing for " + path.string()); language::symbol::Builder builder(*table); builder.Build(*ast_result.root); auto semantic_model = std::make_unique(*table); language::semantic::Analyzer analyzer(*table, *semantic_model); analyzer.SetExternalSymbolProvider( [®istry](const std::string& name) -> std::optional { auto it = registry.by_name.find(name); if (it == registry.by_name.end()) { return std::nullopt; } // 如果有多个同名符号,优先选择 Class if (it->second.size() > 1) { [[maybe_unused]] bool found_class = false; for (const auto& symbol : it->second) { if (symbol.kind() == protocol::SymbolKind::Class) { if (name == "Document" || name == "Endnotes") { std::cerr << "ExternalProvider: Selecting Class for " << name << std::endl; } found_class = true; return symbol; } } if (name == "Document" || name == "Endnotes") { std::cerr << "ExternalProvider: No Class found for " << name << ", size=" << it->second.size() << std::endl; for (const auto& s : it->second) { std::cerr << " - kind=" << static_cast(s.kind()) << std::endl; } } // 如果没有 Class,返回第一个 } return it->second.front(); }); analyzer.Analyze(*ast_result.root); FileAnalysis analysis; analysis.path = path; analysis.symbol_table = std::move(table); analysis.semantic_model = std::move(semantic_model); return analysis; } std::string SymbolName(const language::symbol::SymbolTable& table, language::symbol::SymbolId id) { const auto* symbol = table.definition(id); if (!symbol) return ""; return symbol->name(); } void PrintCallGraph(const FileAnalysis& analysis) { std::cout << "\n╭─────────────────────────────────────╮\n"; std::cout << "│ 📞 Call Graph │\n"; std::cout << "╰─────────────────────────────────────╯\n"; bool any = false; for (const auto& symbol_ref : analysis.symbol_table->all_definitions()) { const auto& symbol = symbol_ref.get(); const auto kind = symbol.kind(); if (kind != protocol::SymbolKind::Function && kind != protocol::SymbolKind::Method) continue; const auto& callees = analysis.semantic_model->calls().callees(symbol.id()); if (callees.empty()) continue; any = true; std::cout << " ▸ " << symbol.name() << " ⟶ "; for (size_t i = 0; i < callees.size(); ++i) { auto callee_symbol = analysis.symbol_table->definition(callees[i].callee); if (callee_symbol) { std::cout << callee_symbol->name(); // 如果被调用的是类(对象创建),添加标记 if (callee_symbol->kind() == protocol::SymbolKind::Class) { std::cout << " 🏗"; } else { // 调试:显示符号类型 std::cout << " [kind=" << static_cast(callee_symbol->kind()) << "]"; } } else { std::cout << ""; } if (i + 1 < callees.size()) std::cout << " • "; } std::cout << '\n'; } if (!any) std::cout << " ⚬ No call relationships detected\n"; } void PrintInheritanceGraph(const FileAnalysis& analysis) { std::cout << "\n╭─────────────────────────────────────╮\n"; std::cout << "│ 🔗 Inheritance Graph │\n"; std::cout << "╰─────────────────────────────────────╯\n"; bool any = false; for (const auto& symbol_ref : analysis.symbol_table->all_definitions()) { const auto& symbol = symbol_ref.get(); if (symbol.kind() != protocol::SymbolKind::Class) continue; const auto& bases = analysis.semantic_model->inheritance().base_classes(symbol.id()); if (bases.empty()) continue; any = true; std::cout << " ▸ " << symbol.name() << " ⇐ "; for (size_t i = 0; i < bases.size(); ++i) { std::cout << SymbolName(*analysis.symbol_table, bases[i]); if (i + 1 < bases.size()) std::cout << " • "; } std::cout << '\n'; } if (!any) std::cout << " ⚬ No inheritance relationships detected\n"; } void PrintReferenceSummary(const FileAnalysis& analysis) { std::cout << "\n╭─────────────────────────────────────────────────────────────────╮\n"; std::cout << "│ 📊 Reference Summary │\n"; std::cout << "╰─────────────────────────────────────────────────────────────────╯\n"; bool any = false; for (const auto& symbol_ref : analysis.symbol_table->all_definitions()) { const auto& symbol = symbol_ref.get(); const auto& refs = analysis.semantic_model->references().references(symbol.id()); if (refs.empty()) continue; any = true; size_t writes = 0; size_t reads = 0; for (const auto& ref : refs) { if (ref.is_write) ++writes; else ++reads; } // 打印符号名和统计信息 std::cout << "\n ┌─ " << symbol.name() << " (" << refs.size() << " reference" << (refs.size() > 1 ? "s" : "") << ")\n"; std::cout << " │ ├─ 📖 Reads: " << reads << '\n'; std::cout << " │ └─ ✏️ Writes: " << writes << '\n'; // 打印每个引用的位置 std::cout << " │\n"; for (const auto& ref : refs) { const char* icon = ref.is_write ? "✏️ " : "👁 "; const char* type = ref.is_write ? "write" : "read "; std::cout << " │ " << icon << " Line " << ref.location.start_line << ":" << ref.location.start_column << " - " << ref.location.end_line << ":" << ref.location.end_column << " [" << type << "]"; if (ref.is_definition) { std::cout << " 🔖"; } std::cout << '\n'; } } if (!any) std::cout << " ⚬ No tracked references\n"; else std::cout << '\n'; } } int main(int argc, char** argv) { try { Options options; if (!ParseArguments(argc, argv, options)) { PrintUsage(argv[0]); return options.help ? 0 : 1; } if (options.help) { PrintUsage(argv[0]); return 0; } const auto workspace_dir = std::filesystem::absolute(options.workspace); const auto target_file = std::filesystem::absolute(options.target_file); Expect(std::filesystem::exists(workspace_dir) && std::filesystem::is_directory(workspace_dir), "Workspace path is invalid: " + workspace_dir.string()); Expect(std::filesystem::exists(target_file) && std::filesystem::is_regular_file(target_file), "Target file is invalid: " + target_file.string()); if (!options.quiet) { std::cout << "\n╭─────────────────────────────────────╮\n"; std::cout << "│ 🔍 Analysis Configuration │\n"; std::cout << "╰─────────────────────────────────────╯\n"; std::cout << " 📁 Workspace: " << workspace_dir << '\n'; std::cout << " 📄 Target : " << target_file << '\n'; } WorkspaceSymbols workspace_symbols; BuildWorkspaceSymbols(workspace_dir, target_file, workspace_symbols); auto symbol_table = std::make_unique(); const auto analysis = AnalyzeFile(target_file, std::move(symbol_table), workspace_symbols); if (options.show_calls) PrintCallGraph(analysis); if (options.show_inheritance) PrintInheritanceGraph(analysis); if (options.show_references) PrintReferenceSummary(analysis); if (!options.quiet) { std::cout << "\n╭─────────────────────────────────────╮\n"; std::cout << "│ ✓ Analysis Completed │\n"; std::cout << "╰─────────────────────────────────────╯\n"; } return 0; } catch (const std::exception& e) { std::cerr << "\n╭─────────────────────────────────────╮\n"; std::cerr << "│ ✗ Analysis Failed │\n"; std::cerr << "╰─────────────────────────────────────╯\n"; std::cerr << " Error: " << e.what() << std::endl; return 1; } }