Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/libcst/_exceptions.py: 64%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) Meta Platforms, Inc. and affiliates.
2#
3# This source code is licensed under the MIT license found in the
4# LICENSE file in the root directory of this source tree.
6from enum import auto, Enum
7from typing import Any, Callable, final, Optional, Sequence, Tuple
9from libcst._tabs import expand_tabs
12_NEWLINE_CHARS: str = "\r\n"
15class EOFSentinel(Enum):
16 EOF = auto()
19class CSTLogicError(Exception):
20 """General purpose internal error within LibCST itself."""
22 pass
25# pyre-fixme[2]: 'Any' type isn't pyre-strict.
26def _parser_syntax_error_unpickle(kwargs: Any) -> "ParserSyntaxError":
27 return ParserSyntaxError(**kwargs)
30@final
31class PartialParserSyntaxError(Exception):
32 """
33 An internal exception that represents a partially-constructed
34 :class:`ParserSyntaxError`. It's raised by our internal parser conversion functions,
35 which don't always know the current line and column information.
37 This partial object only contains a message, with the expectation that the line and
38 column information will be filled in by :class:`libcst._base_parser.BaseParser`.
40 This should never be visible to the end-user.
41 """
43 message: str
45 def __init__(self, message: str) -> None:
46 self.message = message
49@final
50class ParserSyntaxError(Exception):
51 """
52 Contains an error encountered while trying to parse a piece of source code. This
53 exception shouldn't be constructed directly by the user, but instead may be raised
54 by calls to :func:`parse_module`, :func:`parse_expression`, or
55 :func:`parse_statement`.
57 This does not inherit from :class:`SyntaxError` because Python's may raise a
58 :class:`SyntaxError` for any number of reasons, potentially leading to unintended
59 behavior.
60 """
62 #: A human-readable explanation of the syntax error without information about where
63 #: the error occurred.
64 #:
65 #: For a human-readable explanation of the error alongside information about where
66 #: it occurred, use :meth:`__str__` (via ``str(ex)``) instead.
67 message: str
69 # An internal value used to compute `editor_column` and to pretty-print where the
70 # syntax error occurred in the code.
71 _lines: Sequence[str]
73 #: The one-indexed line where the error occured.
74 raw_line: int
76 #: The zero-indexed column as a number of characters from the start of the line
77 #: where the error occured.
78 raw_column: int
80 def __init__(
81 self, message: str, *, lines: Sequence[str], raw_line: int, raw_column: int
82 ) -> None:
83 super(ParserSyntaxError, self).__init__(message)
84 self.message = message
85 self._lines = lines
86 self.raw_line = raw_line
87 self.raw_column = raw_column
89 def __reduce__(
90 self,
91 ) -> Tuple[Callable[..., "ParserSyntaxError"], Tuple[object, ...]]:
92 return (
93 _parser_syntax_error_unpickle,
94 (
95 {
96 "message": self.message,
97 "lines": self._lines,
98 "raw_line": self.raw_line,
99 "raw_column": self.raw_column,
100 },
101 ),
102 )
104 def __str__(self) -> str:
105 """
106 A multi-line human-readable error message of where the syntax error is in their
107 code. For example::
109 Syntax Error @ 2:1.
110 Incomplete input. Encountered end of file (EOF), but expected 'except', or 'finally'.
112 try: pass
113 ^
114 """
115 context = self.context
116 return (
117 f"Syntax Error @ {self.editor_line}:{self.editor_column}.\n"
118 + f"{self.message}"
119 + (f"\n\n{context}" if context is not None else "")
120 )
122 def __repr__(self) -> str:
123 return (
124 "ParserSyntaxError("
125 + f"{self.message!r}, lines=[...], raw_line={self.raw_line!r}, "
126 + f"raw_column={self.raw_column!r})"
127 )
129 @property
130 def context(self) -> Optional[str]:
131 """
132 A formatted string containing the line of code with the syntax error (or a
133 non-empty line above it) along with a caret indicating the exact column where
134 the error occurred.
136 Return ``None`` if there's no relevant non-empty line to show. (e.g. the file
137 consists of only blank lines)
138 """
139 displayed_line = self.editor_line
140 displayed_column = self.editor_column
141 # we want to avoid displaying a blank line for context. If we're on a blank line
142 # find the nearest line above us that isn't blank.
143 while displayed_line >= 1 and not len(self._lines[displayed_line - 1].strip()):
144 displayed_line -= 1
145 displayed_column = len(self._lines[displayed_line - 1])
147 # only show context if we managed to find a non-empty line
148 if len(self._lines[displayed_line - 1].strip()):
149 formatted_source_line = expand_tabs(self._lines[displayed_line - 1]).rstrip(
150 _NEWLINE_CHARS
151 )
152 # fmt: off
153 return (
154 f"{formatted_source_line}\n"
155 + f"{' ' * (displayed_column - 1)}^"
156 )
157 # fmt: on
158 else:
159 return None
161 @property
162 def editor_line(self) -> int:
163 """
164 The expected one-indexed line in the user's editor. This is the same as
165 :attr:`raw_line`.
166 """
167 return self.raw_line # raw_line is already one-indexed.
169 @property
170 def editor_column(self) -> int:
171 """
172 The expected one-indexed column that's likely to match the behavior of the
173 user's editor, assuming tabs expand to 1-8 spaces. This is the column number
174 shown when the syntax error is printed out with `str`.
176 This assumes single-width characters. However, because python doesn't ship with
177 a wcwidth function, it's hard to handle this properly without a third-party
178 dependency.
180 For a raw zero-indexed character offset without tab expansion, see
181 :attr:`raw_column`.
182 """
183 prefix_str = self._lines[self.raw_line - 1][: self.raw_column]
184 tab_adjusted_column = len(expand_tabs(prefix_str))
185 # Text editors use a one-indexed column, so we need to add one to our
186 # zero-indexed column to get a human-readable result.
187 return tab_adjusted_column + 1
190class MetadataException(Exception):
191 pass