55from typing import TYPE_CHECKING
66from typing import Optional
77
8+ from .token import TOKEN_EOF
9+
810if TYPE_CHECKING :
911 from .token import Token
1012
@@ -22,13 +24,69 @@ def __init__(self, *args: object, token: Optional[Token] = None) -> None:
2224 self .token : Optional [Token ] = token
2325
2426 def __str__ (self ) -> str :
25- msg = super (). __str__ ()
27+ return self . detailed_message ()
2628
29+ def detailed_message (self ) -> str :
30+ """Return an error message formatted with extra context info."""
2731 if not self .token :
28- return msg
32+ return super (). __str__ ()
2933
30- line , column = self .token .position ()
31- return f"{ msg } , line { line } , column { column } "
34+ lineno , col , _prev , current , _next = self ._error_context (
35+ self .token .path , self .token .index
36+ )
37+
38+ if self .token .kind == TOKEN_EOF :
39+ col = len (current )
40+
41+ pad = " " * len (str (lineno ))
42+ length = len (self .token .value )
43+ pointer = (" " * col ) + ("^" * max (length , 1 ))
44+
45+ return (
46+ f"{ self .message } \n "
47+ f"{ pad } -> { self .token .path !r} { lineno } :{ col } \n "
48+ f"{ pad } |\n "
49+ f"{ lineno } | { current } \n "
50+ f"{ pad } | { pointer } { self .message } \n "
51+ )
52+
53+ @property
54+ def message (self ) -> object :
55+ """The exception's error message if one was given."""
56+ if self .args :
57+ return self .args [0 ]
58+ return None
59+
60+ def _error_context (self , text : str , index : int ) -> tuple [int , int , str , str , str ]:
61+ lines = text .splitlines (keepends = True )
62+ cumulative_length = 0
63+ target_line_index = - 1
64+
65+ for i , line in enumerate (lines ):
66+ cumulative_length += len (line )
67+ if index < cumulative_length :
68+ target_line_index = i
69+ break
70+
71+ if target_line_index == - 1 :
72+ raise ValueError ("index is out of bounds for the given string" )
73+
74+ # Line number (1-based)
75+ line_number = target_line_index + 1
76+ # Column number within the line
77+ column_number = index - (cumulative_length - len (lines [target_line_index ]))
78+
79+ previous_line = (
80+ lines [target_line_index - 1 ].rstrip () if target_line_index > 0 else ""
81+ )
82+ current_line = lines [target_line_index ].rstrip ()
83+ next_line = (
84+ lines [target_line_index + 1 ].rstrip ()
85+ if target_line_index < len (lines ) - 1
86+ else ""
87+ )
88+
89+ return line_number , column_number , previous_line , current_line , next_line
3290
3391
3492class JSONPathSyntaxError (JSONPathError ):
0 commit comments