Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion diffgraph/structural.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,27 @@ def _is_type_alias_annotation(source: bytes, node) -> bool:
return text in ("TypeAlias", "typing.TypeAlias")


def _syntax_error_detail(root) -> str:
"""Return the first parser-reported error location in source order.

Tree-sitter's root error flag tells us a snapshot is unsafe to turn into
topology, but users still need a deterministic, file-scoped clue for the
repair. Traverse children in source order and report the first explicit
ERROR or missing node without attempting error recovery or inventing a
structural claim.
"""
pending = [root]
while pending:
node = pending.pop()
if node.is_error or node.is_missing:
line, column = node.start_point
return "Tree-sitter reported a syntax error at line {}, column {}".format(
line + 1, column + 1
)
pending.extend(reversed(node.children))
return "Tree-sitter reported a syntax error"


def _parse_python(
content: bytes,
) -> Tuple[
Expand All @@ -229,7 +250,7 @@ def _parse_python(
content.decode("utf-8")
tree = _parser().parse(content)
if tree.root_node.has_error:
raise ValueError("Tree-sitter reported a syntax error")
raise ValueError(_syntax_error_detail(tree.root_node))

symbols: List[_Symbol] = []
imports: List[_Import] = []
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/python_parser_failure.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
{
"code": "PARSE_FAILURE",
"file": "broken.py",
"detail": "post-change: ValueError: Tree-sitter reported a syntax error"
"detail": "post-change: ValueError: Tree-sitter reported a syntax error at line 1, column 12"
}
]
}
Expand Down
Loading