Add multi-language logic exploration
This commit is contained in:
parent
9b4258c852
commit
9161889492
18 changed files with 1639 additions and 76 deletions
154
tests/test_treesitter_logic.py
Normal file
154
tests/test_treesitter_logic.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from docforge.errors import DocForgeError
|
||||
from docforge.treesitter_logic import (
|
||||
TreeSitterLogicOwner,
|
||||
analyze_cpp_source,
|
||||
analyze_javascript_source,
|
||||
)
|
||||
|
||||
|
||||
class JavaScriptLogicTests(unittest.TestCase):
|
||||
def test_branches_short_circuit_and_converge(self) -> None:
|
||||
source = """
|
||||
function choose(enabled, ready) {
|
||||
if (enabled && ready()) {
|
||||
accept();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
return enabled;
|
||||
}
|
||||
""".strip()
|
||||
projection = analyze_javascript_source(
|
||||
source,
|
||||
source_id="source.javascript",
|
||||
owners=(TreeSitterLogicOwner("js.symbol.choose", "choose", 1),),
|
||||
)[0]
|
||||
|
||||
kinds = {node.kind for node in projection.nodes}
|
||||
labels = {node.label for node in projection.nodes}
|
||||
relations = {edge.relation for edge in projection.edges}
|
||||
self.assertIn("condition", kinds)
|
||||
self.assertIn("convergence", kinds)
|
||||
self.assertIn("Decision convergence", labels)
|
||||
self.assertIn("when_true", relations)
|
||||
self.assertIn("when_false", relations)
|
||||
self.assertIn("return", relations)
|
||||
|
||||
def test_arrow_function_expression_is_a_returning_projection(self) -> None:
|
||||
source = "const choose = (enabled) => enabled ? accept() : reject();"
|
||||
projection = analyze_javascript_source(
|
||||
source,
|
||||
source_id="source.javascript",
|
||||
owners=(TreeSitterLogicOwner("js.symbol.choose", "choose", 1),),
|
||||
)[0]
|
||||
|
||||
labels = {node.label for node in projection.nodes}
|
||||
self.assertIn("enabled", labels)
|
||||
self.assertIn("return accept()", labels)
|
||||
self.assertIn("return reject()", labels)
|
||||
|
||||
def test_loops_switch_and_exception_paths_are_preserved(self) -> None:
|
||||
source = """
|
||||
function process(items, mode) {
|
||||
for (const item of items) {
|
||||
if (!item.ready) continue;
|
||||
use(item);
|
||||
}
|
||||
switch (mode) {
|
||||
case 1:
|
||||
one();
|
||||
break;
|
||||
default:
|
||||
fallback();
|
||||
}
|
||||
try {
|
||||
risk();
|
||||
} catch (error) {
|
||||
recover(error);
|
||||
} finally {
|
||||
clean();
|
||||
}
|
||||
}
|
||||
""".strip()
|
||||
projection = analyze_javascript_source(
|
||||
source,
|
||||
source_id="source.javascript",
|
||||
owners=(TreeSitterLogicOwner("js.symbol.process", "process", 1),),
|
||||
)[0]
|
||||
|
||||
kinds = {node.kind for node in projection.nodes}
|
||||
labels = {node.label for node in projection.nodes}
|
||||
relations = {edge.relation for edge in projection.edges}
|
||||
self.assertTrue({"loop", "continue", "case", "try", "except", "finally"} <= kinds)
|
||||
self.assertIn("Case convergence", labels)
|
||||
self.assertIn("Exception convergence", labels)
|
||||
self.assertTrue({"loop", "continue", "case", "exception"} <= relations)
|
||||
|
||||
|
||||
class CppLogicTests(unittest.TestCase):
|
||||
def test_cpp_function_branches_and_throws(self) -> None:
|
||||
source = """
|
||||
int choose(bool enabled) {
|
||||
if (enabled) {
|
||||
return 1;
|
||||
}
|
||||
throw Error();
|
||||
}
|
||||
""".strip()
|
||||
projection = analyze_cpp_source(
|
||||
source,
|
||||
source_id="source.cpp",
|
||||
owners=(TreeSitterLogicOwner("cpp.symbol.choose", "choose", 1),),
|
||||
)[0]
|
||||
|
||||
kinds = {node.kind for node in projection.nodes}
|
||||
relations = {edge.relation for edge in projection.edges}
|
||||
self.assertTrue({"entry", "condition", "return", "raise", "exit"} <= kinds)
|
||||
self.assertTrue({"when_true", "when_false", "return", "raise"} <= relations)
|
||||
|
||||
def test_cpp_qualified_method_owner_is_resolved(self) -> None:
|
||||
source = """
|
||||
class Worker {
|
||||
public:
|
||||
int run(bool ready) {
|
||||
while (ready) {
|
||||
ready = tick();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
""".strip()
|
||||
projection = analyze_cpp_source(
|
||||
source,
|
||||
source_id="source.cpp",
|
||||
owners=(TreeSitterLogicOwner("cpp.symbol.worker.run", "Worker.run", 3),),
|
||||
)[0]
|
||||
|
||||
labels = {node.label for node in projection.nodes}
|
||||
self.assertIn("Loop exit", labels)
|
||||
self.assertIn("return 0", labels)
|
||||
|
||||
def test_invalid_source_and_missing_owner_fail_closed(self) -> None:
|
||||
with self.assertRaises(DocForgeError) as invalid:
|
||||
analyze_cpp_source(
|
||||
"int broken( {",
|
||||
source_id="source.cpp",
|
||||
owners=(),
|
||||
)
|
||||
self.assertEqual(invalid.exception.code, "invalid_logic_source")
|
||||
|
||||
with self.assertRaises(DocForgeError) as missing:
|
||||
analyze_javascript_source(
|
||||
"function exists() {}",
|
||||
source_id="source.javascript",
|
||||
owners=(TreeSitterLogicOwner("js.symbol.missing", "missing", 1),),
|
||||
)
|
||||
self.assertEqual(missing.exception.code, "missing_logic_owner")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue