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
21 changes: 19 additions & 2 deletions diffgraph/structural.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,19 @@ def _resolution_warning(item: ResolutionWarning) -> Dict[str, str]:
return _warning(code, item.path, "{}: {}".format(item.code, item.message))


def _parse_failure_warning(
side: str, path: str, oid: Optional[str], error: Exception,
) -> Dict[str, str]:
"""Preserve the exact immutable snapshot that Tree-sitter could not parse."""
return _warning(
"PARSE_FAILURE",
path,
"{}-change blob {}: {}: {}".format(
side, oid or "absent", type(error).__name__, error,
),
)


def _symbol_id(path: str, qualified_name: str) -> str:
return "sym::{}::{}".format(path, qualified_name)

Expand Down Expand Up @@ -974,15 +987,19 @@ def analyze_local_diff(
_parse_python(old) if old is not None else ([], [], [], {}, [])
)
except parser_errors as error:
warnings.append(_warning("PARSE_FAILURE", entry.old_path or path, "pre-change: {}: {}".format(type(error).__name__, error)))
warnings.append(_parse_failure_warning(
"pre", entry.old_path or path, entry.old_oid, error,
))
skipped += 1
continue
try:
new_symbols, new_imports, new_calls, new_bindings, new_rebindings = (
_parse_python(new) if new is not None else ([], [], [], {}, [])
)
except parser_errors as error:
warnings.append(_warning("PARSE_FAILURE", path, "post-change: {}: {}".format(type(error).__name__, error)))
warnings.append(_parse_failure_warning(
"post", path, entry.new_oid, error,
))
skipped += 1
continue
analyzed += 1
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 at line 1, column 12"
"detail": "post-change blob c440d6d7f7fca646fb897d0b5d279ec775aeddfd: ValueError: Tree-sitter reported a syntax error at line 1, column 12"
}
]
}
Expand Down
28 changes: 26 additions & 2 deletions tests/test_structural.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,30 @@ def test_parse_failure_is_scoped_and_matches_the_golden_fixture(tmp_path):
assert actual == expected


def test_pre_change_parse_failure_names_the_immutable_blob(tmp_path):
"""A broken pre-change snapshot remains reproducible after it is fixed."""
root = repo(tmp_path)
write(root, "recovered.py", "def broken(:\n")
commit(root)
old_oid = git(root, "rev-parse", "HEAD:recovered.py")
write(root, "recovered.py", "def recovered():\n return True\n")

artifact = analyze_local_diff(str(root))

assert_valid(artifact)
assert artifact["metadata"]["files_analyzed"] == 0
assert artifact["metadata"]["files_skipped"] == 1
assert artifact["symbols"] == []
assert artifact["metadata"]["warnings"] == [{
"code": "PARSE_FAILURE",
"file": "recovered.py",
"detail": (
"pre-change blob {}: ValueError: Tree-sitter reported a syntax error "
"at line 1, column 12"
).format(old_oid),
}]


def test_no_network_calls(monkeypatch, tmp_path):
root = repo(tmp_path)
write(root, "a.py", "def a():\n pass\n")
Expand Down Expand Up @@ -1254,9 +1278,9 @@ def unavailable():
"code": "PARSE_FAILURE",
"file": "app.py",
"detail": (
"post-change: StructuralDependencyError: "
"post-change blob {}: StructuralDependencyError: "
"parser dependency is unavailable"
),
).format(git(root, "hash-object", "app.py")),
}]


Expand Down
Loading