aboutsummaryrefslogtreecommitdiff
path: root/tests/errors/formatting.test.py
diff options
context:
space:
mode:
authornatsuoto <[email protected]>2026-07-10 16:18:09 +0530
committernatsuoto <[email protected]>2026-07-10 16:18:09 +0530
commit9d80c99432c0c7c5f184cbe7bb25513aace01a7e (patch)
treeec79c1768bf35c10ce9f708d0ee77a18eb746d80 /tests/errors/formatting.test.py
parentc757ea1fea447df3ed69e74b407ca9710a9f85e0 (diff)
downloadedify-9d80c99432c0c7c5f184cbe7bb25513aace01a7e.tar.xz
edify-9d80c99432c0c7c5f184cbe7bb25513aace01a7e.zip
feat(errors): annotated error messages with caller-context pointers
Diffstat (limited to 'tests/errors/formatting.test.py')
-rw-r--r--tests/errors/formatting.test.py129
1 files changed, 129 insertions, 0 deletions
diff --git a/tests/errors/formatting.test.py b/tests/errors/formatting.test.py
new file mode 100644
index 0000000..8cdf0ab
--- /dev/null
+++ b/tests/errors/formatting.test.py
@@ -0,0 +1,129 @@
+"""Tests for the message formatter helpers in :mod:`edify.errors.formatting`."""
+
+from unittest import mock
+
+from edify.errors.context import CallerContext
+from edify.errors.formatting import (
+ FixInsertion,
+ Problem,
+ compose_annotated_message,
+ format_error,
+ format_fix_block,
+ format_help_header,
+ format_note_line,
+ format_pointer_block,
+ format_problem,
+ format_problem_header,
+)
+
+
+def _context(source_line: str = " x = do_the_thing(1, 2, 3)", colno: int = 9) -> CallerContext:
+ return CallerContext(
+ filename="/tmp/user_code.py",
+ lineno=42,
+ colno=colno,
+ end_colno=colno + 15,
+ source_line=source_line,
+ )
+
+
+def test_format_error_with_only_summary_returns_bare_header():
+ assert format_error("boom") == "error: boom"
+
+
+def test_format_error_with_summary_and_blocks_joins_them_with_blank_line():
+ output = format_error("boom", "first block", "second block")
+ assert output == "error: boom\n\nfirst block\n\nsecond block"
+
+
+def test_format_error_skips_empty_blocks():
+ output = format_error("boom", "", "only real block", "")
+ assert output == "error: boom\n\nonly real block"
+
+
+def test_format_pointer_block_draws_caret_at_caller_column():
+ context = _context()
+ block = format_pointer_block(context, "here")
+ lines = block.splitlines()
+ assert lines[0] == " --> /tmp/user_code.py:42:9"
+ assert "^^^^^^^^^^^^^^^ here" in block
+
+
+def test_format_pointer_block_has_at_least_one_caret_when_span_is_empty():
+ context = CallerContext(
+ filename="/tmp/x.py",
+ lineno=1,
+ colno=5,
+ end_colno=5,
+ source_line="abcde",
+ )
+ block = format_pointer_block(context, "point")
+ assert "^ point" in block
+
+
+def test_format_note_line_prepends_the_note_prefix():
+ assert format_note_line("something is off") == " = note: something is off"
+
+
+def test_format_problem_header_prepends_problem_prefix():
+ assert format_problem_header("stuff") == "problem: stuff"
+
+
+def test_format_help_header_prepends_help_prefix():
+ assert format_help_header("do X") == "help: do X"
+
+
+def test_format_fix_block_inserts_text_at_the_target_column():
+ context = _context(source_line="value = compute()", colno=1)
+ insertion = FixInsertion(column=9, text="new_")
+ block = format_fix_block(context, insertion)
+ assert "value = new_compute()" in block
+ assert "++++" in block
+
+
+def test_format_problem_composes_header_pointer_help_and_fix():
+ problem_context = _context(source_line="pattern = build()", colno=11)
+ fix_context = _context(source_line="pattern = build()", colno=11)
+ fix_insertion = FixInsertion(column=11, text=".digit()")
+ problem = Problem(
+ description="missing operand",
+ problem_context=problem_context,
+ problem_hint="no operand added",
+ help_summary="add .digit() before .build()",
+ fix_context=fix_context,
+ fix_insertion=fix_insertion,
+ )
+ output = format_problem(problem)
+ assert "problem: missing operand" in output
+ assert "no operand added" in output
+ assert "help: add .digit() before .build()" in output
+ assert "++++++++" in output
+
+
+def test_compose_annotated_message_when_caller_context_is_available():
+ output = compose_annotated_message(
+ summary="bad thing happened",
+ trigger_hint="right here",
+ note="because reasons",
+ help_line="help: do X",
+ )
+ assert output.startswith("error: bad thing happened")
+ assert "-->" in output
+ assert "= note: because reasons" in output
+ assert output.rstrip().endswith("help: do X")
+
+
+def test_compose_annotated_message_falls_back_when_caller_context_is_none():
+ with mock.patch(
+ "edify.errors.formatting.capture_caller_context", return_value=None
+ ):
+ output = compose_annotated_message(
+ summary="bad thing happened",
+ trigger_hint="here",
+ note="because reasons",
+ help_line="help: do X",
+ )
+ assert output.startswith("error: bad thing happened")
+ assert "-->" not in output
+ assert "= note: because reasons" in output
+ assert output.rstrip().endswith("help: do X")