From 02e76576f38e12bb9ad43d05e29b2cc19e17c566 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 14:57:08 -0700 Subject: [PATCH] Step 401: parse AnnotationConflictExtended self-hosting coverage --- editor/CMakeLists.txt | 9 ++ editor/tests/step401_test.cpp | 231 ++++++++++++++++++++++++++++++++++ progress.md | 59 +++++++++ 3 files changed, 299 insertions(+) create mode 100644 editor/tests/step401_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index b5abd9c..f0bf4b4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2488,4 +2488,13 @@ target_link_libraries(step400_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step401_test tests/step401_test.cpp) +target_include_directories(step401_test PRIVATE src) +target_link_libraries(step401_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/tests/step401_test.cpp b/editor/tests/step401_test.cpp new file mode 100644 index 0000000..b758c23 --- /dev/null +++ b/editor/tests/step401_test.cpp @@ -0,0 +1,231 @@ +// Step 401: Parse AnnotationConflictExtended.h (12 tests) +// Self-hosting: parse Whetstone's own header and validate core structure. + +#include +#include +#include +#include +#include + +#include "SelfHostHarness.h" +#include "ast/ClassDeclaration.h" +#include "ast/Function.h" +#include "ast/Variable.h" + +namespace { + +std::string resolveHeaderPath() { + const std::vector candidates = { + "src/AnnotationConflictExtended.h", + "editor/src/AnnotationConflictExtended.h", + "../src/AnnotationConflictExtended.h", + "../../editor/src/AnnotationConflictExtended.h" + }; + for (const auto& candidate : candidates) { + if (std::filesystem::exists(candidate)) return candidate; + } + return "src/AnnotationConflictExtended.h"; +} + +std::string kHeaderPath() { + static const std::string path = resolveHeaderPath(); + return path; +} + +ExpectedStructure expectedAnnotationConflictStructure() { + ExpectedStructure expected; + expected.filePath = kHeaderPath(); + expected.constructs = { + {"struct", "CrossTypeConflict"}, + {"function", "collectCrossTypeConflicts"} + }; + return expected; +} + +const ClassDeclaration* findClass(const Module* module, const std::string& name) { + if (!module) return nullptr; + for (auto* clsNode : module->getChildren("classes")) { + auto* cls = static_cast(clsNode); + if (cls->name == name) return cls; + } + return nullptr; +} + +const Function* findFunction(const Module* module, const std::string& name) { + if (!module) return nullptr; + for (auto* fnNode : module->getChildren("functions")) { + auto* fn = static_cast(fnNode); + if (fn->name == name) return fn; + } + return nullptr; +} + +bool hasFieldName(const ClassDeclaration* cls, const std::string& fieldName) { + if (!cls) return false; + for (auto* fieldNode : cls->getChildren("fields")) { + if (fieldNode->conceptType == "Variable") { + auto* var = static_cast(fieldNode); + if (var->name == fieldName) return true; + } + if (fieldNode->id.find(fieldName) != std::string::npos) return true; + } + return false; +} + +} // namespace + +int main() { + int passed = 0; + + // Test 1: Parse real header file from disk. + { + assert(std::filesystem::exists(kHeaderPath())); + auto result = SelfHostHarness::parseFile(kHeaderPath()); + assert(result.parseSuccess); + assert(result.ast != nullptr); + std::cout << "PASS: test 1 — parse real AnnotationConflictExtended.h file\n"; + passed++; + } + + // Test 2: Struct definition is captured. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + assert(SelfHostHarness::hasParsedConstruct(result.ast.get(), "struct", "CrossTypeConflict")); + std::cout << "PASS: test 2 — CrossTypeConflict struct captured\n"; + passed++; + } + + // Test 3: Free function signature is captured. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + assert(SelfHostHarness::hasParsedConstruct( + result.ast.get(), "function", "collectCrossTypeConflicts")); + std::cout << "PASS: test 3 — collectCrossTypeConflicts signature captured\n"; + passed++; + } + + // Test 4: Expected self-hosting structure has full coverage. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + auto expected = expectedAnnotationConflictStructure(); + SelfHostHarness::checkExpected(result, expected); + assert(result.totalExpected == 2); + assert(result.totalParsed == 2); + assert(result.totalOpaque == 0); + assert(result.overallCoverage() == 1.0); + std::cout << "PASS: test 4 — expected structure coverage 100%\n"; + passed++; + } + + // Test 5: Struct fields are present for the self-hosting type. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + const auto* ctc = findClass(result.ast.get(), "CrossTypeConflict"); + assert(ctc != nullptr); + assert(ctc->getChildren("fields").size() >= 4); + std::cout << "PASS: test 5 — CrossTypeConflict fields parsed\n"; + passed++; + } + + // Test 6: Struct field names are accessible. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + const auto* ctc = findClass(result.ast.get(), "CrossTypeConflict"); + assert(ctc != nullptr); + assert(hasFieldName(ctc, "type1")); + assert(hasFieldName(ctc, "type2")); + assert(hasFieldName(ctc, "message")); + assert(hasFieldName(ctc, "nodeId")); + std::cout << "PASS: test 6 — CrossTypeConflict field names preserved\n"; + passed++; + } + + // Test 7: Free function has expected parameters. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + const auto* fn = findFunction(result.ast.get(), "collectCrossTypeConflicts"); + assert(fn != nullptr); + assert(fn->getChildren("parameters").size() >= 2); + std::cout << "PASS: test 7 — collectCrossTypeConflicts parameters parsed\n"; + passed++; + } + + // Test 8: Free function has a body node (parsed or opaque block). + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + const auto* fn = findFunction(result.ast.get(), "collectCrossTypeConflicts"); + assert(fn != nullptr); + assert(fn->getChild("body") != nullptr); + std::cout << "PASS: test 8 — collectCrossTypeConflicts body attached\n"; + passed++; + } + + // Test 9: Coverage report includes file path and successful parse status. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + auto expected = expectedAnnotationConflictStructure(); + SelfHostHarness::checkExpected(result, expected); + const std::string report = SelfHostHarness::coverageReport(result); + assert(report.find(kHeaderPath()) != std::string::npos); + assert(report.find("Parse success: yes") != std::string::npos); + assert(report.find("100%") != std::string::npos); + std::cout << "PASS: test 9 — coverage report reflects self-host parse\n"; + passed++; + } + + // Test 10: Parser tolerates lambda-heavy sections from this header style. + { + const std::string source = R"( +inline void lambda_case() { + auto checkPair = [&](const std::string& a, const std::string& b) { + return a == b; + }; + (void)checkPair("x", "y"); +} +)"; + auto result = SelfHostHarness::parseSource(source, "lambda_case"); + assert(result.parseSuccess); + assert(SelfHostHarness::countFunctions(result.ast.get()) == 1); + std::cout << "PASS: test 10 — lambda-heavy function parses without crash\n"; + passed++; + } + + // Test 11: Parser tolerates std::map/std::function patterns in header bodies. + { + const std::string source = R"( +inline void complex_types_case() { + std::map m; + std::function fn = []() { return true; }; + std::vector> pairs; +} +)"; + auto result = SelfHostHarness::parseSource(source, "complex_types_case"); + assert(result.parseSuccess); + assert(SelfHostHarness::countFunctions(result.ast.get()) == 1); + std::cout << "PASS: test 11 — STL-heavy function parses without crash\n"; + passed++; + } + + // Test 12: Missing expected constructs become opaque instead of hard failure. + { + auto result = SelfHostHarness::parseFile(kHeaderPath()); + ExpectedStructure expected; + expected.filePath = kHeaderPath(); + expected.constructs = { + {"struct", "CrossTypeConflict"}, + {"function", "collectCrossTypeConflicts"}, + {"class", "DefinitelyMissingClass"}, + {"function", "definitelyMissingFunction"} + }; + SelfHostHarness::checkExpected(result, expected); + assert(result.totalExpected == 4); + assert(result.totalParsed == 2); + assert(result.totalOpaque == 2); + assert(result.overallCoverage() == 0.5); + std::cout << "PASS: test 12 — graceful degradation via opaque tracking\n"; + passed++; + } + + std::cout << "\nStep 401 result: " << passed << "/12 tests passed\n"; + return (passed == 12) ? 0 : 1; +} diff --git a/progress.md b/progress.md index ede52d0..6a9c387 100644 --- a/progress.md +++ b/progress.md @@ -3437,6 +3437,65 @@ End-to-end Phase 16a validation combining all new C++ constructs. - Legacy oversized header persists: - `editor/src/MCPServer.h` (`1679` > `600`) +## Phase 16b: Self-Hosting Phase 1 + +### Step 400: Self-Hosting Test Harness +**Status:** PASS (12/12 tests) + +Added a reusable self-hosting harness for parsing real project headers, +checking expected structure, and reporting construct coverage with opaque +tracking for graceful degradation. + +**Files created:** +- `editor/src/SelfHostHarness.h` — parseFile/parseSource helpers, expected + structure checks, coverage tracking, coverage report output +- `editor/tests/step400_test.cpp` — 12 tests for parsing, coverage accounting, + graceful degradation, and report formatting + +**Files modified:** +- `editor/CMakeLists.txt` — `step400_test` target + +**Architecture gate check:** +- `editor/src/SelfHostHarness.h` within header-size limit (`308` <= `600`) +- `editor/tests/step400_test.cpp` within test-file size guidance (`256` lines) + +### Step 401: Parse AnnotationConflictExtended.h +**Status:** PASS (12/12 tests) + +Validated self-hosting against a real project header (`AnnotationConflictExtended.h`): +struct and free-function capture, parameter/body extraction, field-name fidelity, +coverage reporting, and graceful degradation on missing expected constructs. + +**Files created:** +- `editor/tests/step401_test.cpp` — 12 tests covering: + 1. parse real header file from disk + 2. `CrossTypeConflict` struct capture + 3. `collectCrossTypeConflicts` signature capture + 4. expected-structure 100% coverage + 5. struct field count + 6. struct field-name preservation + 7. function parameter extraction + 8. function body presence + 9. coverage report content checks + 10. lambda-heavy header pattern parse stability + 11. STL-heavy type pattern parse stability + 12. graceful degradation via opaque coverage accounting + +**Files modified:** +- `editor/CMakeLists.txt` — `step401_test` target + +**Verification run:** +- `step401_test` — PASS (12/12) new step coverage +- `step400_test` — PASS (12/12) regression coverage +- `step399_test` — PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/tests/step401_test.cpp` within test-file size guidance (`231` lines) +- `editor/src/SelfHostHarness.h` remains within header-size limit (`308` <= `600`) +- Legacy oversized headers persist: + - `editor/src/MCPServer.h` (`1679` > `600`) + - `editor/src/HeadlessAgentRPCHandler.h` (`2623` > `600`) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)