Step 525: add legal operation graph validation
This commit is contained in:
@@ -3604,4 +3604,13 @@ target_link_libraries(step524_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step525_test tests/step525_test.cpp)
|
||||
target_include_directories(step525_test PRIVATE src)
|
||||
target_link_libraries(step525_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)
|
||||
|
||||
79
editor/src/LegalOperationGraph.h
Normal file
79
editor/src/LegalOperationGraph.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
// Step 525: Legal Operation Graph
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
struct LegalOpNode {
|
||||
std::string language;
|
||||
std::string nodeKind;
|
||||
std::set<std::string> operations;
|
||||
};
|
||||
|
||||
struct LegalOpQueryResult {
|
||||
bool supported = false;
|
||||
std::vector<std::string> operations;
|
||||
};
|
||||
|
||||
class LegalOperationGraph {
|
||||
public:
|
||||
LegalOperationGraph() { initDefaults(); }
|
||||
|
||||
void registerRule(const std::string& language,
|
||||
const std::string& nodeKind,
|
||||
const std::vector<std::string>& ops) {
|
||||
std::set<std::string> dedup(ops.begin(), ops.end());
|
||||
rules_[key(language, nodeKind)] = LegalOpNode{language, nodeKind, dedup};
|
||||
}
|
||||
|
||||
LegalOpQueryResult allowedOps(const std::string& language,
|
||||
const std::string& nodeKind) const {
|
||||
auto it = rules_.find(key(language, nodeKind));
|
||||
if (it == rules_.end()) return {};
|
||||
LegalOpQueryResult r;
|
||||
r.supported = true;
|
||||
r.operations.assign(it->second.operations.begin(), it->second.operations.end());
|
||||
return r;
|
||||
}
|
||||
|
||||
bool opAllowed(const std::string& language,
|
||||
const std::string& nodeKind,
|
||||
const std::string& op) const {
|
||||
auto q = allowedOps(language, nodeKind);
|
||||
if (!q.supported) return false;
|
||||
for (const auto& candidate : q.operations) {
|
||||
if (candidate == op) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, LegalOpNode> rules_;
|
||||
|
||||
static std::string key(const std::string& language,
|
||||
const std::string& nodeKind) {
|
||||
return language + "::" + nodeKind;
|
||||
}
|
||||
|
||||
void initDefaults() {
|
||||
// C/C++
|
||||
registerRule("cpp", "Function", {"rename", "update", "extract", "inline"});
|
||||
registerRule("cpp", "Variable", {"rename", "update", "delete"});
|
||||
registerRule("cpp", "Include", {"insert", "delete", "reorder"});
|
||||
registerRule("c", "Function", {"rename", "update", "extract"});
|
||||
|
||||
// Python/TypeScript
|
||||
registerRule("python", "Function", {"rename", "update", "extract"});
|
||||
registerRule("python", "Class", {"rename", "update", "extract"});
|
||||
registerRule("typescript", "Function", {"rename", "update", "extract", "inline"});
|
||||
registerRule("typescript", "Import", {"insert", "delete", "reorder"});
|
||||
|
||||
// Rust/Go
|
||||
registerRule("rust", "Function", {"rename", "update", "extract"});
|
||||
registerRule("rust", "Struct", {"rename", "update"});
|
||||
registerRule("go", "Function", {"rename", "update", "extract"});
|
||||
registerRule("go", "Import", {"insert", "delete", "reorder"});
|
||||
}
|
||||
};
|
||||
131
editor/tests/step525_test.cpp
Normal file
131
editor/tests/step525_test.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
// Step 525: Legal Operation Graph (12 tests)
|
||||
|
||||
#include "LegalOperationGraph.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
void test_cpp_function_has_expected_ops() {
|
||||
TEST(cpp_function_has_expected_ops);
|
||||
LegalOperationGraph g;
|
||||
auto q = g.allowedOps("cpp", "Function");
|
||||
CHECK(q.supported, "cpp function should be supported");
|
||||
CHECK(g.opAllowed("cpp", "Function", "rename"), "rename should be allowed");
|
||||
CHECK(g.opAllowed("cpp", "Function", "inline"), "inline should be allowed");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_cpp_variable_disallows_extract() {
|
||||
TEST(cpp_variable_disallows_extract);
|
||||
LegalOperationGraph g;
|
||||
CHECK(!g.opAllowed("cpp", "Variable", "extract"), "extract should be disallowed");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_python_class_supports_rename_update_extract() {
|
||||
TEST(python_class_supports_rename_update_extract);
|
||||
LegalOperationGraph g;
|
||||
CHECK(g.opAllowed("python", "Class", "rename"), "rename missing");
|
||||
CHECK(g.opAllowed("python", "Class", "update"), "update missing");
|
||||
CHECK(g.opAllowed("python", "Class", "extract"), "extract missing");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_typescript_import_supports_reorder() {
|
||||
TEST(typescript_import_supports_reorder);
|
||||
LegalOperationGraph g;
|
||||
CHECK(g.opAllowed("typescript", "Import", "reorder"), "reorder should be allowed");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_rust_struct_disallows_delete() {
|
||||
TEST(rust_struct_disallows_delete);
|
||||
LegalOperationGraph g;
|
||||
CHECK(!g.opAllowed("rust", "Struct", "delete"), "delete should be disallowed");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_unknown_language_is_unsupported() {
|
||||
TEST(unknown_language_is_unsupported);
|
||||
LegalOperationGraph g;
|
||||
auto q = g.allowedOps("haskell", "Function");
|
||||
CHECK(!q.supported, "unknown language should be unsupported");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_unknown_node_kind_is_unsupported() {
|
||||
TEST(unknown_node_kind_is_unsupported);
|
||||
LegalOperationGraph g;
|
||||
auto q = g.allowedOps("cpp", "LambdaMagic");
|
||||
CHECK(!q.supported, "unknown node kind should be unsupported");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_register_rule_adds_new_language_mapping() {
|
||||
TEST(register_rule_adds_new_language_mapping);
|
||||
LegalOperationGraph g;
|
||||
g.registerRule("java", "Method", {"rename", "update"});
|
||||
CHECK(g.opAllowed("java", "Method", "rename"), "registered op missing");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_register_rule_overrides_existing_mapping() {
|
||||
TEST(register_rule_overrides_existing_mapping);
|
||||
LegalOperationGraph g;
|
||||
CHECK(g.opAllowed("cpp", "Function", "inline"), "baseline inline missing");
|
||||
g.registerRule("cpp", "Function", {"rename"});
|
||||
CHECK(g.opAllowed("cpp", "Function", "rename"), "rename should remain");
|
||||
CHECK(!g.opAllowed("cpp", "Function", "inline"), "inline should be removed by override");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_rule_deduplicates_ops() {
|
||||
TEST(rule_deduplicates_ops);
|
||||
LegalOperationGraph g;
|
||||
g.registerRule("go", "Function", {"rename", "rename", "update"});
|
||||
auto q = g.allowedOps("go", "Function");
|
||||
CHECK(q.supported, "go function should be supported");
|
||||
CHECK((int)q.operations.size() == 2, "ops should be deduplicated");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_c_function_supports_extract() {
|
||||
TEST(c_function_supports_extract);
|
||||
LegalOperationGraph g;
|
||||
CHECK(g.opAllowed("c", "Function", "extract"), "extract should be allowed for c function");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_go_import_supports_insert_delete_reorder() {
|
||||
TEST(go_import_supports_insert_delete_reorder);
|
||||
LegalOperationGraph g;
|
||||
CHECK(g.opAllowed("go", "Import", "insert"), "insert missing");
|
||||
CHECK(g.opAllowed("go", "Import", "delete"), "delete missing");
|
||||
CHECK(g.opAllowed("go", "Import", "reorder"), "reorder missing");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 525: Legal Operation Graph\n";
|
||||
|
||||
test_cpp_function_has_expected_ops(); // 1
|
||||
test_cpp_variable_disallows_extract(); // 2
|
||||
test_python_class_supports_rename_update_extract(); // 3
|
||||
test_typescript_import_supports_reorder(); // 4
|
||||
test_rust_struct_disallows_delete(); // 5
|
||||
test_unknown_language_is_unsupported(); // 6
|
||||
test_unknown_node_kind_is_unsupported(); // 7
|
||||
test_register_rule_adds_new_language_mapping(); // 8
|
||||
test_register_rule_overrides_existing_mapping(); // 9
|
||||
test_rule_deduplicates_ops(); // 10
|
||||
test_c_function_supports_extract(); // 11
|
||||
test_go_import_supports_insert_delete_reorder(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
33
progress.md
33
progress.md
@@ -8591,3 +8591,36 @@ expected diagnostics deltas, rejecting under-specified taskitems.
|
||||
- `editor/src/TypedTaskitemContractSchema.h` within header-size limit (`114` <= `600`)
|
||||
- `editor/tests/step524_test.cpp` within test-file size guidance (`148` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 525: Legal Operation Graph
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements a legal-operation graph for language/node-kind pairs so constrained
|
||||
editor tasks can validate whether requested refactors are structurally allowed
|
||||
before execution.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/LegalOperationGraph.h` - operation-eligibility graph module:
|
||||
- default per-language/per-node operation rules
|
||||
- rule registration and override support
|
||||
- deduplication of operation sets
|
||||
- query helpers for supported nodes and allowed operations
|
||||
- `editor/tests/step525_test.cpp` - 12 tests covering:
|
||||
- default rule coverage across cpp/c/python/typescript/rust/go
|
||||
- unsupported language/node behavior
|
||||
- rule registration and override semantics
|
||||
- operation deduplication behavior
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step525_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step525_test step524_test` - PASS
|
||||
- `./editor/build-native/step525_test` - PASS (12/12)
|
||||
- `./editor/build-native/step524_test` - PASS (12/12) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/LegalOperationGraph.h` within header-size limit (`79` <= `600`)
|
||||
- `editor/tests/step525_test.cpp` within test-file size guidance (`131` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user