Group source comments in logic views
This commit is contained in:
parent
6d659ba381
commit
a30f021a52
4 changed files with 67 additions and 13 deletions
|
|
@ -209,6 +209,9 @@ const contributionStyles = Object.freeze({
|
||||||
"logic-action": {
|
"logic-action": {
|
||||||
label: "Action", section: "Actions & calls", color: "#34d399", fill: "#15372e",
|
label: "Action", section: "Actions & calls", color: "#34d399", fill: "#15372e",
|
||||||
},
|
},
|
||||||
|
"logic-comment": {
|
||||||
|
label: "Comment", section: "Source commentary", color: "#7dd3fc", fill: "#173447",
|
||||||
|
},
|
||||||
"logic-control": {
|
"logic-control": {
|
||||||
label: "Control", section: "Loops & exception handling", color: "#c084fc", fill: "#302044",
|
label: "Control", section: "Loops & exception handling", color: "#c084fc", fill: "#302044",
|
||||||
},
|
},
|
||||||
|
|
@ -223,7 +226,7 @@ const contributionOrder = Object.freeze([
|
||||||
"focus", "composition", "behavior", "dependency", "execution",
|
"focus", "composition", "behavior", "dependency", "execution",
|
||||||
"data", "evidence", "context", "related",
|
"data", "evidence", "context", "related",
|
||||||
"logic-entry", "logic-condition", "logic-action", "logic-control",
|
"logic-entry", "logic-condition", "logic-action", "logic-control",
|
||||||
"logic-convergence", "logic-terminal",
|
"logic-comment", "logic-convergence", "logic-terminal",
|
||||||
]);
|
]);
|
||||||
const compositionRelations = new Set(["contains", "defines", "defined_in"]);
|
const compositionRelations = new Set(["contains", "defines", "defined_in"]);
|
||||||
const behaviorRelations = new Set(["inherits", "implemented_by"]);
|
const behaviorRelations = new Set(["inherits", "implemented_by"]);
|
||||||
|
|
@ -939,6 +942,7 @@ function nodeContributionCategory(nodeId, data, topology) {
|
||||||
if (kind === "entry") return "logic-entry";
|
if (kind === "entry") return "logic-entry";
|
||||||
if (["condition", "case"].includes(kind)) return "logic-condition";
|
if (["condition", "case"].includes(kind)) return "logic-condition";
|
||||||
if (["action", "call"].includes(kind)) return "logic-action";
|
if (["action", "call"].includes(kind)) return "logic-action";
|
||||||
|
if (kind === "comment") return "logic-comment";
|
||||||
if (["loop", "try", "except", "finally", "break", "continue"].includes(kind)) {
|
if (["loop", "try", "except", "finally", "break", "continue"].includes(kind)) {
|
||||||
return "logic-control";
|
return "logic-control";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ from tree_sitter import Language, Node, Parser
|
||||||
from .errors import DocForgeError
|
from .errors import DocForgeError
|
||||||
from .models import LogicEdge, LogicNode, LogicProjection
|
from .models import LogicEdge, LogicNode, LogicProjection
|
||||||
|
|
||||||
_TRIVIA_NODE_TYPES = frozenset({"comment"})
|
_COMMENT_NODE_TYPES = frozenset({"comment"})
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|
@ -421,10 +421,29 @@ class _TreeSitterFunctionBuilder:
|
||||||
control: _Control | None,
|
control: _Control | None,
|
||||||
) -> tuple[_Tail, ...]:
|
) -> tuple[_Tail, ...]:
|
||||||
tails = incoming
|
tails = incoming
|
||||||
for statement in statements:
|
items = tuple(statements)
|
||||||
|
index = 0
|
||||||
|
while index < len(items):
|
||||||
if not tails:
|
if not tails:
|
||||||
break
|
break
|
||||||
|
statement = items[index]
|
||||||
|
if statement.type in _COMMENT_NODE_TYPES:
|
||||||
|
comments = [statement]
|
||||||
|
index += 1
|
||||||
|
while index < len(items):
|
||||||
|
candidate = items[index]
|
||||||
|
previous = comments[-1]
|
||||||
|
if (
|
||||||
|
candidate.type not in _COMMENT_NODE_TYPES
|
||||||
|
or candidate.start_point.row > previous.end_point.row + 1
|
||||||
|
):
|
||||||
|
break
|
||||||
|
comments.append(candidate)
|
||||||
|
index += 1
|
||||||
|
tails = self._comment_block(tuple(comments), tails)
|
||||||
|
continue
|
||||||
tails = self._statement(statement, tails, control=control)
|
tails = self._statement(statement, tails, control=control)
|
||||||
|
index += 1
|
||||||
return tails
|
return tails
|
||||||
|
|
||||||
def _statement(
|
def _statement(
|
||||||
|
|
@ -434,8 +453,8 @@ class _TreeSitterFunctionBuilder:
|
||||||
*,
|
*,
|
||||||
control: _Control | None,
|
control: _Control | None,
|
||||||
) -> tuple[_Tail, ...]:
|
) -> tuple[_Tail, ...]:
|
||||||
if statement.type in _TRIVIA_NODE_TYPES:
|
if statement.type in _COMMENT_NODE_TYPES:
|
||||||
return incoming
|
return self._comment_block((statement,), incoming)
|
||||||
if statement.type in self.profile.block_types:
|
if statement.type in self.profile.block_types:
|
||||||
return self._statements(statement.named_children, incoming, control=control)
|
return self._statements(statement.named_children, incoming, control=control)
|
||||||
if statement.type == "if_statement":
|
if statement.type == "if_statement":
|
||||||
|
|
@ -493,6 +512,19 @@ class _TreeSitterFunctionBuilder:
|
||||||
self._connect(incoming, node_id)
|
self._connect(incoming, node_id)
|
||||||
return (_Tail(node_id),)
|
return (_Tail(node_id),)
|
||||||
|
|
||||||
|
def _comment_block(
|
||||||
|
self,
|
||||||
|
comments: tuple[Node, ...],
|
||||||
|
incoming: tuple[_Tail, ...],
|
||||||
|
) -> tuple[_Tail, ...]:
|
||||||
|
node_id = self._node(
|
||||||
|
"comment",
|
||||||
|
_compact(" ".join(_comment_text(comment, self.raw) for comment in comments), 480),
|
||||||
|
comments[0],
|
||||||
|
)
|
||||||
|
self._connect(incoming, node_id)
|
||||||
|
return (_Tail(node_id),)
|
||||||
|
|
||||||
def _if(
|
def _if(
|
||||||
self,
|
self,
|
||||||
statement: Node,
|
statement: Node,
|
||||||
|
|
@ -830,6 +862,15 @@ def _text(node: Node, raw: bytes) -> str:
|
||||||
return raw[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
|
return raw[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
|
||||||
|
def _comment_text(node: Node, raw: bytes) -> str:
|
||||||
|
value = _text(node, raw).strip()
|
||||||
|
if value.startswith("//"):
|
||||||
|
return value[2:].strip()
|
||||||
|
if value.startswith("/*") and value.endswith("*/"):
|
||||||
|
value = value[2:-2]
|
||||||
|
return " ".join(line.strip().removeprefix("*").strip() for line in value.splitlines()).strip()
|
||||||
|
|
||||||
|
|
||||||
def _compact(value: str, limit: int = 240) -> str:
|
def _compact(value: str, limit: int = 240) -> str:
|
||||||
compact = " ".join(value.strip().split())
|
compact = " ".join(value.strip().split())
|
||||||
return compact if len(compact) <= limit else f"{compact[: limit - 1]}…"
|
return compact if len(compact) <= limit else f"{compact[: limit - 1]}…"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from docforge.treesitter_logic import (
|
||||||
|
|
||||||
|
|
||||||
class JavaScriptLogicTests(unittest.TestCase):
|
class JavaScriptLogicTests(unittest.TestCase):
|
||||||
def test_comments_do_not_become_logic_actions(self) -> None:
|
def test_consecutive_comments_become_one_logic_comment_block(self) -> None:
|
||||||
source = """
|
source = """
|
||||||
function choose(enabled) {
|
function choose(enabled) {
|
||||||
// Explain the condition.
|
// Explain the condition.
|
||||||
|
|
@ -28,9 +28,13 @@ function choose(enabled) {
|
||||||
owners=(TreeSitterLogicOwner("js.symbol.choose", "choose", 1),),
|
owners=(TreeSitterLogicOwner("js.symbol.choose", "choose", 1),),
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
labels = {node.label for node in projection.nodes}
|
comments = [node for node in projection.nodes if node.kind == "comment"]
|
||||||
self.assertFalse(any(label.startswith(("//", "/*")) for label in labels))
|
self.assertEqual(len(comments), 1)
|
||||||
self.assertIn("accept();", labels)
|
self.assertEqual(
|
||||||
|
comments[0].label,
|
||||||
|
"Explain the condition. Continue the explanation. A block comment is trivia too.",
|
||||||
|
)
|
||||||
|
self.assertIn("accept();", {node.label for node in projection.nodes})
|
||||||
|
|
||||||
def test_branches_short_circuit_and_converge(self) -> None:
|
def test_branches_short_circuit_and_converge(self) -> None:
|
||||||
source = """
|
source = """
|
||||||
|
|
@ -111,7 +115,7 @@ function process(items, mode) {
|
||||||
|
|
||||||
|
|
||||||
class CppLogicTests(unittest.TestCase):
|
class CppLogicTests(unittest.TestCase):
|
||||||
def test_comments_do_not_become_logic_actions(self) -> None:
|
def test_consecutive_comments_become_one_logic_comment_block(self) -> None:
|
||||||
source = """
|
source = """
|
||||||
int choose(bool enabled) {
|
int choose(bool enabled) {
|
||||||
// Explain the condition.
|
// Explain the condition.
|
||||||
|
|
@ -128,9 +132,13 @@ int choose(bool enabled) {
|
||||||
owners=(TreeSitterLogicOwner("cpp.symbol.choose", "choose", 1),),
|
owners=(TreeSitterLogicOwner("cpp.symbol.choose", "choose", 1),),
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
labels = {node.label for node in projection.nodes}
|
comments = [node for node in projection.nodes if node.kind == "comment"]
|
||||||
self.assertFalse(any(label.startswith(("//", "/*")) for label in labels))
|
self.assertEqual(len(comments), 1)
|
||||||
self.assertIn("return 1", labels)
|
self.assertEqual(
|
||||||
|
comments[0].label,
|
||||||
|
"Explain the condition. A block comment is trivia too.",
|
||||||
|
)
|
||||||
|
self.assertIn("return 1", {node.label for node in projection.nodes})
|
||||||
|
|
||||||
def test_cpp_function_branches_and_throws(self) -> None:
|
def test_cpp_function_branches_and_throws(self) -> None:
|
||||||
source = """
|
source = """
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,7 @@ if (pruned.prunedCount !== 2) fail("pruned node count");
|
||||||
self.assertIn("Structure & containment", javascript)
|
self.assertIn("Structure & containment", javascript)
|
||||||
self.assertIn("Inherited & implemented behavior", javascript)
|
self.assertIn("Inherited & implemented behavior", javascript)
|
||||||
self.assertIn("Required dependencies", javascript)
|
self.assertIn("Required dependencies", javascript)
|
||||||
|
self.assertIn('"logic-comment"', javascript)
|
||||||
self.assertNotIn(">Children<", html)
|
self.assertNotIn(">Children<", html)
|
||||||
self.assertIn("distanceShade", javascript)
|
self.assertIn("distanceShade", javascript)
|
||||||
self.assertIn("nodeDisplayName", javascript)
|
self.assertIn("nodeDisplayName", javascript)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue