From 092ace3ebb9347c4b46cada5f48d6f057b03384b Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Thu, 4 Jul 2024 02:22:45 +0200 Subject: [PATCH 1/9] Avoid raising bare Exception --- docs/source/metadata_tutorial.ipynb | 16 +++--- docs/source/scope_tutorial.ipynb | 24 ++++---- libcst/__init__.py | 2 + libcst/_excep.py | 2 + libcst/_exceptions.py | 2 + libcst/_nodes/base.py | 9 +-- libcst/_nodes/expression.py | 13 +++-- libcst/_nodes/statement.py | 13 ++--- libcst/_nodes/tests/test_funcdef.py | 2 +- libcst/_nodes/tests/test_namedexpr.py | 2 +- libcst/_nodes/tests/test_removal_behavior.py | 2 +- libcst/_parser/base_parser.py | 2 +- libcst/_parser/conversions/expression.py | 56 +++++++++++++++---- libcst/_parser/conversions/params.py | 13 +++-- libcst/_parser/conversions/statement.py | 32 +++++++---- libcst/_parser/grammar.py | 8 +-- libcst/_parser/parso/pgen2/generator.py | 2 +- libcst/_parser/parso/python/tokenize.py | 15 ++--- libcst/_parser/production_decorator.py | 2 +- libcst/_parser/py_whitespace_parser.py | 17 ++++-- libcst/codegen/gen_matcher_classes.py | 14 ++--- libcst/codemod/_cli.py | 4 +- libcst/codemod/_codemod.py | 4 +- libcst/codemod/_visitor.py | 8 +-- .../commands/convert_format_to_fstring.py | 23 +++++--- .../convert_percent_format_to_fstring.py | 4 +- .../codemod/commands/fix_pyre_directives.py | 5 +- libcst/codemod/commands/rename.py | 6 +- libcst/codemod/visitors/_add_imports.py | 10 ++-- libcst/codemod/visitors/_remove_imports.py | 9 +-- libcst/display/text.py | 4 +- libcst/helpers/_template.py | 12 ++-- libcst/helpers/common.py | 4 +- libcst/helpers/expression.py | 2 +- libcst/helpers/module.py | 4 +- libcst/helpers/tests/test_expression.py | 4 +- libcst/matchers/_matcher_base.py | 44 +++++++-------- libcst/metadata/base_provider.py | 2 +- libcst/metadata/full_repo_manager.py | 2 +- .../tests/test_type_inference_provider.py | 10 +--- libcst/metadata/type_inference_provider.py | 19 ++----- libcst/tool.py | 10 ++-- 42 files changed, 251 insertions(+), 187 deletions(-) create mode 100644 libcst/_excep.py diff --git a/docs/source/metadata_tutorial.ipynb b/docs/source/metadata_tutorial.ipynb index 499aaf736..66ce4963e 100644 --- a/docs/source/metadata_tutorial.ipynb +++ b/docs/source/metadata_tutorial.ipynb @@ -24,17 +24,16 @@ "metadata": { "nbsphinx": "hidden" }, - "outputs": [], "source": [ "import sys\n", "sys.path.append(\"../../\")" - ] + ], + "outputs": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import libcst as cst\n", "\n", @@ -55,7 +54,8 @@ " # Mark all other Name nodes as not parameters\n", " if not self.get_metadata(type(self), node, False):\n", " self.set_metadata(node, False)" - ] + ], + "outputs": [] }, { "cell_type": "raw", @@ -80,7 +80,6 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "module = cst.parse_module(\"x\")\n", "wrapper = cst.MetadataWrapper(module)\n", @@ -89,7 +88,8 @@ "x_name_node = wrapper.module.body[0].body[0].value\n", "\n", "print(isparam[x_name_node]) # should print False" - ] + ], + "outputs": [] }, { "cell_type": "raw", @@ -106,7 +106,6 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "from libcst.metadata import PositionProvider\n", "\n", @@ -123,7 +122,8 @@ "module = cst.parse_module(\"def foo(x):\\n y = 1\\n return x + y\")\n", "wrapper = cst.MetadataWrapper(module)\n", "result = wrapper.visit(ParamPrinter()) # NB: wrapper.visit not module.visit" - ] + ], + "outputs": [] } ], "metadata": { diff --git a/docs/source/scope_tutorial.ipynb b/docs/source/scope_tutorial.ipynb index 179e2ed78..6bfb407d7 100644 --- a/docs/source/scope_tutorial.ipynb +++ b/docs/source/scope_tutorial.ipynb @@ -26,17 +26,16 @@ "metadata": { "nbsphinx": "hidden" }, - "outputs": [], "source": [ "import sys\n", "sys.path.append(\"../../\")" - ] + ], + "outputs": [] }, { "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "source = \"\"\"\\\n", "import a, b, c as d, e as f # expect to keep: a, c as d\n", @@ -55,7 +54,8 @@ " var = k.method()\n", " func_undefined(var_undefined)\n", "\"\"\"" - ] + ], + "outputs": [] }, { "cell_type": "raw", @@ -71,7 +71,6 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import libcst as cst\n", "\n", @@ -80,7 +79,8 @@ "scopes = set(wrapper.resolve(cst.metadata.ScopeProvider).values())\n", "for scope in scopes:\n", " print(scope)" - ] + ], + "outputs": [] }, { "cell_type": "raw", @@ -97,7 +97,6 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "from collections import defaultdict\n", "from typing import Dict, Union, Set\n", @@ -125,7 +124,8 @@ " print(\n", " f\"Warning on line {location.line:2d}, column {location.column:2d}: Name reference `{node.value}` is not defined.\"\n", " )\n" - ] + ], + "outputs": [] }, { "cell_type": "raw", @@ -149,7 +149,6 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "class RemoveUnusedImportTransformer(cst.CSTTransformer):\n", " def __init__(\n", @@ -187,7 +186,8 @@ " self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom\n", " ) -> cst.ImportFrom:\n", " return self.leave_import_alike(original_node, updated_node)\n" - ] + ], + "outputs": [] }, { "cell_type": "raw", @@ -202,7 +202,6 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import difflib\n", "fixed_module = wrapper.module.visit(RemoveUnusedImportTransformer(unused_imports))\n", @@ -213,7 +212,8 @@ " difflib.unified_diff(source.splitlines(1), fixed_module.code.splitlines(1))\n", " )\n", ")" - ] + ], + "outputs": [] } ], "metadata": { diff --git a/libcst/__init__.py b/libcst/__init__.py index 2a8e47b31..2011613e0 100644 --- a/libcst/__init__.py +++ b/libcst/__init__.py @@ -5,6 +5,7 @@ from libcst._batched_visitor import BatchableCSTVisitor, visit_batched from libcst._exceptions import MetadataException, ParserSyntaxError +from libcst._excep import CSTLogicError from libcst._flatten_sentinel import FlattenSentinel from libcst._maybe_sentinel import MaybeSentinel from libcst._metadata_dependent import MetadataDependent @@ -238,6 +239,7 @@ "CSTNodeT", "CSTTransformer", "CSTValidationError", + "CSTLogicError", "CSTVisitor", "CSTVisitorT", "FlattenSentinel", diff --git a/libcst/_excep.py b/libcst/_excep.py new file mode 100644 index 000000000..5ae47e166 --- /dev/null +++ b/libcst/_excep.py @@ -0,0 +1,2 @@ +class CSTLogicError(Exception): + pass diff --git a/libcst/_exceptions.py b/libcst/_exceptions.py index 5359ca3c0..d9bc376a4 100644 --- a/libcst/_exceptions.py +++ b/libcst/_exceptions.py @@ -226,3 +226,5 @@ def editor_column(self) -> int: class MetadataException(Exception): pass + + diff --git a/libcst/_nodes/base.py b/libcst/_nodes/base.py index d9689f8f8..203f07266 100644 --- a/libcst/_nodes/base.py +++ b/libcst/_nodes/base.py @@ -8,6 +8,7 @@ from dataclasses import dataclass, field, fields, replace from typing import Any, cast, ClassVar, Dict, List, Mapping, Sequence, TypeVar, Union +from libcst._excep import CSTLogicError from libcst._flatten_sentinel import FlattenSentinel from libcst._nodes.internal import CodegenState from libcst._removal_sentinel import RemovalSentinel @@ -237,7 +238,7 @@ def visit( # validate return type of the user-defined `visitor.on_leave` method if not isinstance(leave_result, (CSTNode, RemovalSentinel, FlattenSentinel)): - raise Exception( + raise CSTValidationError( "Expected a node of type CSTNode or a RemovalSentinel, " + f"but got a return value of {type(leave_result).__name__}" ) @@ -383,7 +384,7 @@ def deep_replace( new_tree = self.visit(_ChildReplacementTransformer(old_node, new_node)) if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)): # The above transform never returns *Sentinel, so this isn't possible - raise Exception("Logic error, cannot get a *Sentinel here!") + raise CSTLogicError("Cannot get a *Sentinel here!") return new_tree def deep_remove( @@ -400,7 +401,7 @@ def deep_remove( if isinstance(new_tree, FlattenSentinel): # The above transform never returns FlattenSentinel, so this isn't possible - raise Exception("Logic error, cannot get a FlattenSentinel here!") + raise CSTLogicError("Cannot get a FlattenSentinel here!") return new_tree @@ -422,7 +423,7 @@ def with_deep_changes( new_tree = self.visit(_ChildWithChangesTransformer(old_node, changes)) if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)): # This is impossible with the above transform. - raise Exception("Logic error, cannot get a *Sentinel here!") + raise CSTLogicError("Cannot get a *Sentinel here!") return new_tree def __eq__(self: _CSTNodeSelfT, other: object) -> bool: diff --git a/libcst/_nodes/expression.py b/libcst/_nodes/expression.py index 75f7da133..c3563eabd 100644 --- a/libcst/_nodes/expression.py +++ b/libcst/_nodes/expression.py @@ -19,7 +19,12 @@ from libcst._add_slots import add_slots from libcst._maybe_sentinel import MaybeSentinel -from libcst._nodes.base import CSTCodegenError, CSTNode, CSTValidationError +from libcst._nodes.base import ( + CSTCodegenError, + CSTNode, + CSTValidationError, +) +from libcst._excep import CSTLogicError from libcst._nodes.internal import ( CodegenState, visit_optional, @@ -666,7 +671,7 @@ def quote(self) -> StringQuoteLiteral: if len(quote) not in {1, 3}: # We shouldn't get here due to construction validation logic, # but handle the case anyway. - raise Exception(f"Invalid string {self.value}") + raise CSTLogicError(f"Invalid string {self.value}") # pyre-ignore We know via the above validation that we will only # ever return one of the four string literals. @@ -1010,7 +1015,7 @@ def _validate(self) -> None: elif isinstance(right, FormattedString): rightbytes = "b" in right.prefix else: - raise Exception("Logic error!") + raise CSTLogicError() if leftbytes != rightbytes: raise CSTValidationError("Cannot concatenate string and bytes.") @@ -1688,7 +1693,7 @@ def _codegen_impl( if default_indicator == "->": state.add_token(" ") else: - raise Exception("Logic error!") + raise CSTLogicError() # Now, output the indicator and the rest of the annotation state.add_token(default_indicator) diff --git a/libcst/_nodes/statement.py b/libcst/_nodes/statement.py index 1cb9221fe..b4897c855 100644 --- a/libcst/_nodes/statement.py +++ b/libcst/_nodes/statement.py @@ -12,6 +12,7 @@ from libcst._add_slots import add_slots from libcst._maybe_sentinel import MaybeSentinel from libcst._nodes.base import CSTNode, CSTValidationError +from libcst._excep import CSTLogicError from libcst._nodes.expression import ( _BaseParenthesizedNode, Annotation, @@ -1161,12 +1162,10 @@ def _validate(self) -> None: ) try: self.evaluated_name - except Exception as e: - if str(e) == "Logic error!": - raise CSTValidationError( - "The imported name must be a valid qualified name." - ) - raise e + except CSTLogicError as e: + raise CSTValidationError( + "The imported name must be a valid qualified name." + ) from e def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "ImportAlias": return ImportAlias( @@ -1195,7 +1194,7 @@ def _name(self, node: CSTNode) -> str: elif isinstance(node, Attribute): return f"{self._name(node.value)}.{node.attr.value}" else: - raise Exception("Logic error!") + raise CSTLogicError() @property def evaluated_name(self) -> str: diff --git a/libcst/_nodes/tests/test_funcdef.py b/libcst/_nodes/tests/test_funcdef.py index 087dde19c..4ee03d4fa 100644 --- a/libcst/_nodes/tests/test_funcdef.py +++ b/libcst/_nodes/tests/test_funcdef.py @@ -1052,7 +1052,7 @@ def _parse_statement_force_38(code: str) -> cst.BaseCompoundStatement: code, config=cst.PartialParserConfig(python_version="3.8") ) if not isinstance(statement, cst.BaseCompoundStatement): - raise Exception("This function is expecting to parse compound statements only!") + raise ValueError("This function is expecting to parse compound statements only!") return statement diff --git a/libcst/_nodes/tests/test_namedexpr.py b/libcst/_nodes/tests/test_namedexpr.py index bddd4f3d2..f560ccf41 100644 --- a/libcst/_nodes/tests/test_namedexpr.py +++ b/libcst/_nodes/tests/test_namedexpr.py @@ -22,7 +22,7 @@ def _parse_statement_force_38(code: str) -> cst.BaseCompoundStatement: code, config=cst.PartialParserConfig(python_version="3.8") ) if not isinstance(statement, cst.BaseCompoundStatement): - raise Exception("This function is expecting to parse compound statements only!") + raise ValueError("This function is expecting to parse compound statements only!") return statement diff --git a/libcst/_nodes/tests/test_removal_behavior.py b/libcst/_nodes/tests/test_removal_behavior.py index 9b1bf6193..709b26f5b 100644 --- a/libcst/_nodes/tests/test_removal_behavior.py +++ b/libcst/_nodes/tests/test_removal_behavior.py @@ -95,7 +95,7 @@ def test_removal_pass_behavior( self, before: str, after: str, visitor: Type[CSTTransformer] ) -> None: if before.endswith("\n") or after.endswith("\n"): - raise Exception("Test cases should not be newline-terminated!") + raise ValueError("Test cases should not be newline-terminated!") # Test doesn't have newline termination case before_module = parse_module(before) diff --git a/libcst/_parser/base_parser.py b/libcst/_parser/base_parser.py index ef9e15192..8da4844d5 100644 --- a/libcst/_parser/base_parser.py +++ b/libcst/_parser/base_parser.py @@ -103,7 +103,7 @@ def __init__( def parse(self) -> _NodeT: # Ensure that we don't re-use parsers. if self.__was_parse_called: - raise Exception("Each parser object may only be used to parse once.") + raise ValueError("Each parser object may only be used to parse once.") self.__was_parse_called = True for token in self.tokens: diff --git a/libcst/_parser/conversions/expression.py b/libcst/_parser/conversions/expression.py index 1a46de2af..ebd56b472 100644 --- a/libcst/_parser/conversions/expression.py +++ b/libcst/_parser/conversions/expression.py @@ -12,7 +12,8 @@ Intnumber as INTNUMBER_RE, ) -from libcst._exceptions import PartialParserSyntaxError +from libcst._excep import CSTLogicError +from libcst._exceptions import PartialParserSyntaxError, ParserSyntaxError from libcst._maybe_sentinel import MaybeSentinel from libcst._nodes.expression import ( Arg, @@ -327,7 +328,12 @@ def convert_boolop( # Convert all of the operations that have no precedence in a loop for op, rightexpr in grouper(rightexprs, 2): if op.string not in BOOLOP_TOKEN_LUT: - raise Exception(f"Unexpected token '{op.string}'!") + raise ParserSyntaxError( + f"Unexpected token '{op.string}'!", + lines=config.lines, + raw_line=0, + raw_column=0 + ) leftexpr = BooleanOperation( left=leftexpr, # pyre-ignore Pyre thinks that the type of the LUT is CSTNode. @@ -420,7 +426,12 @@ def convert_comp_op( ) else: # this should be unreachable - raise Exception(f"Unexpected token '{op.string}'!") + raise ParserSyntaxError( + f"Unexpected token '{op.string}'!", + lines=config.lines, + raw_line=0, + raw_column=0 + ) else: # A two-token comparison leftcomp, rightcomp = children @@ -451,7 +462,12 @@ def convert_comp_op( ) else: # this should be unreachable - raise Exception(f"Unexpected token '{leftcomp.string} {rightcomp.string}'!") + raise ParserSyntaxError( + f"Unexpected token '{leftcomp.string} {rightcomp.string}'!", + lines=config.lines, + raw_line=0, + raw_column=0 + ) @with_production("star_expr", "'*' expr") @@ -493,7 +509,12 @@ def convert_binop( # Convert all of the operations that have no precedence in a loop for op, rightexpr in grouper(rightexprs, 2): if op.string not in BINOP_TOKEN_LUT: - raise Exception(f"Unexpected token '{op.string}'!") + raise ParserSyntaxError( + f"Unexpected token '{op.string}'!", + lines=config.lines, + raw_line=0, + raw_column=0 + ) leftexpr = BinaryOperation( left=leftexpr, # pyre-ignore Pyre thinks that the type of the LUT is CSTNode. @@ -540,7 +561,12 @@ def convert_factor( ) ) else: - raise Exception(f"Unexpected token '{op.string}'!") + raise ParserSyntaxError( + f"Unexpected token '{op.string}'!", + lines=config.lines, + raw_line=0, + raw_column=0 + ) return WithLeadingWhitespace( UnaryOperation(operator=opnode, expression=factor.value), op.whitespace_before @@ -651,7 +677,7 @@ def convert_atom_expr_trailer( ) else: # This is an invalid trailer, so lets give up - raise Exception("Logic error!") + raise CSTLogicError() return WithLeadingWhitespace(atom, whitespace_before) @@ -870,9 +896,19 @@ def convert_atom_basic( Imaginary(child.string), child.whitespace_before ) else: - raise Exception(f"Unparseable number {child.string}") + raise ParserSyntaxError( + f"Unparseable number {child.string}", + lines=config.lines, + raw_line=0, + raw_column=0 + ) else: - raise Exception(f"Logic error, unexpected token {child.type.name}") + raise ParserSyntaxError( + f"unexpected token {child.type.name}", + lines=config.lines, + raw_line=0, + raw_column=0 + ) @with_production("atom_squarebrackets", "'[' [testlist_comp_list] ']'") @@ -1447,7 +1483,7 @@ def convert_arg_assign_comp_for( if equal.string == ":=": val = convert_namedexpr_test(config, children) if not isinstance(val, WithLeadingWhitespace): - raise Exception( + raise TypeError( f"convert_namedexpr_test returned {val!r}, not WithLeadingWhitespace" ) return Arg(value=val.value) diff --git a/libcst/_parser/conversions/params.py b/libcst/_parser/conversions/params.py index 9ac7f1d16..ba1bcd47e 100644 --- a/libcst/_parser/conversions/params.py +++ b/libcst/_parser/conversions/params.py @@ -6,6 +6,7 @@ from typing import Any, List, Optional, Sequence, Union +from libcst._excep import CSTLogicError from libcst._exceptions import PartialParserSyntaxError from libcst._maybe_sentinel import MaybeSentinel from libcst._nodes.expression import ( @@ -121,7 +122,7 @@ def add_param( # Example code: # def fn(*abc, *): ... # This should be unreachable, the grammar already disallows it. - raise Exception( + raise ValueError( "Cannot have multiple star ('*') markers in a single argument " + "list." ) @@ -136,7 +137,7 @@ def add_param( # Example code: # def fn(foo, /, *, /, bar): ... # This should be unreachable, the grammar already disallows it. - raise Exception( + raise ValueError( "Cannot have multiple slash ('/') markers in a single argument " + "list." ) @@ -168,7 +169,7 @@ def add_param( # Example code: # def fn(**kwargs, trailing=None) # This should be unreachable, the grammar already disallows it. - raise Exception("Cannot have any arguments after a kwargs expansion.") + raise ValueError("Cannot have any arguments after a kwargs expansion.") elif ( isinstance(param.star, str) and param.star == "*" and param.default is None ): @@ -181,7 +182,7 @@ def add_param( # Example code: # def fn(*first, *second): ... # This should be unreachable, the grammar already disallows it. - raise Exception( + raise ValueError( "Expected a keyword argument but found a starred positional " + "argument expansion." ) @@ -197,13 +198,13 @@ def add_param( # Example code: # def fn(**first, **second) # This should be unreachable, the grammar already disallows it. - raise Exception( + raise ValueError( "Multiple starred keyword argument expansions are not allowed in a " + "single argument list" ) else: # The state machine should never end up here. - raise Exception("Logic error!") + raise CSTLogicError() return current_param diff --git a/libcst/_parser/conversions/statement.py b/libcst/_parser/conversions/statement.py index 608f002f0..c81f4c115 100644 --- a/libcst/_parser/conversions/statement.py +++ b/libcst/_parser/conversions/statement.py @@ -6,7 +6,8 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type -from libcst._exceptions import PartialParserSyntaxError +from libcst._excep import CSTLogicError +from libcst._exceptions import PartialParserSyntaxError, ParserSyntaxError from libcst._maybe_sentinel import MaybeSentinel from libcst._nodes.expression import ( Annotation, @@ -283,7 +284,12 @@ def convert_annassign(config: ParserConfig, children: Sequence[Any]) -> Any: whitespace_after=parse_simple_whitespace(config, equal.whitespace_after), ) else: - raise Exception("Invalid parser state!") + raise ParserSyntaxError( + "Invalid parser state!", + lines=config.lines, + raw_line=0, + raw_column=0 + ) return AnnAssignPartial( annotation=Annotation( @@ -319,7 +325,13 @@ def convert_annassign(config: ParserConfig, children: Sequence[Any]) -> Any: def convert_augassign(config: ParserConfig, children: Sequence[Any]) -> Any: op, expr = children if op.string not in AUGOP_TOKEN_LUT: - raise Exception(f"Unexpected token '{op.string}'!") + raise ParserSyntaxError( + f"Unexpected token '{op.string}'!", + lines=config.lines, + raw_line=0, + raw_column=0 + ) + return AugAssignPartial( # pyre-ignore Pyre seems to think that the value of this LUT is CSTNode operator=AUGOP_TOKEN_LUT[op.string]( @@ -447,7 +459,7 @@ def convert_import_relative(config: ParserConfig, children: Sequence[Any]) -> An # This should be the dotted name, and we can't get more than # one, but lets be sure anyway if dotted_name is not None: - raise Exception("Logic error!") + raise CSTLogicError() dotted_name = child return ImportRelativePartial(relative=tuple(dots), module=dotted_name) @@ -644,7 +656,7 @@ def convert_raise_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: item=source.value, ) else: - raise Exception("Logic error!") + raise CSTLogicError() return WithLeadingWhitespace( Raise(whitespace_after_raise=whitespace_after_raise, exc=exc, cause=cause), @@ -893,7 +905,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: if isinstance(clause, Token): if clause.string == "else": if orelse is not None: - raise Exception("Logic error!") + raise CSTLogicError() orelse = Else( leading_lines=parse_empty_lines(config, clause.whitespace_before), whitespace_before_colon=parse_simple_whitespace( @@ -903,7 +915,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: ) elif clause.string == "finally": if finalbody is not None: - raise Exception("Logic error!") + raise CSTLogicError() finalbody = Finally( leading_lines=parse_empty_lines(config, clause.whitespace_before), whitespace_before_colon=parse_simple_whitespace( @@ -912,7 +924,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: body=suite, ) else: - raise Exception("Logic error!") + raise CSTLogicError() elif isinstance(clause, ExceptClausePartial): handlers.append( ExceptHandler( @@ -927,7 +939,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: ) ) else: - raise Exception("Logic error!") + raise CSTLogicError() return Try( leading_lines=parse_empty_lines(config, trytoken.whitespace_before), @@ -1333,7 +1345,7 @@ def convert_asyncable_stmt(config: ParserConfig, children: Sequence[Any]) -> Any asynchronous=asyncnode, leading_lines=leading_lines ) else: - raise Exception("Logic error!") + raise CSTLogicError() @with_production("suite", "simple_stmt_suite | indented_suite") diff --git a/libcst/_parser/grammar.py b/libcst/_parser/grammar.py index 8e6ade59a..ee65ef72f 100644 --- a/libcst/_parser/grammar.py +++ b/libcst/_parser/grammar.py @@ -319,7 +319,7 @@ def validate_grammar() -> None: production_name = fn_productions[0].name expected_name = f"convert_{production_name}" if fn.__name__ != expected_name: - raise Exception( + raise ValueError( f"The conversion function for '{production_name}' " + f"must be called '{expected_name}', not '{fn.__name__}'." ) @@ -330,7 +330,7 @@ def _get_version_comparison(version: str) -> Tuple[str, PythonVersionInfo]: return (version[:2], parse_version_string(version[2:].strip())) if version[:1] in (">", "<"): return (version[:1], parse_version_string(version[1:].strip())) - raise Exception(f"Invalid version comparison specifier '{version}'") + raise ValueError(f"Invalid version comparison specifier '{version}'") def _compare_versions( @@ -350,7 +350,7 @@ def _compare_versions( return actual_version > requested_version if comparison == "<": return actual_version < requested_version - raise Exception(f"Invalid version comparison specifier '{comparison}'") + raise ValueError(f"Invalid version comparison specifier '{comparison}'") def _should_include( @@ -405,7 +405,7 @@ def get_nonterminal_conversions( if not _should_include_future(fn_production.future, future_imports): continue if fn_production.name in conversions: - raise Exception( + raise ValueError( f"Found duplicate '{fn_production.name}' production in grammar" ) conversions[fn_production.name] = fn diff --git a/libcst/_parser/parso/pgen2/generator.py b/libcst/_parser/parso/pgen2/generator.py index 4e20e89d7..69d6e9415 100644 --- a/libcst/_parser/parso/pgen2/generator.py +++ b/libcst/_parser/parso/pgen2/generator.py @@ -259,7 +259,7 @@ def generate_grammar(bnf_grammar: str, token_namespace: Any) -> Grammar[Any]: _calculate_tree_traversal(rule_to_dfas) if start_nonterminal is None: - raise Exception("could not find starting nonterminal!") + raise ValueError("could not find starting nonterminal!") return Grammar(start_nonterminal, rule_to_dfas, reserved_strings) diff --git a/libcst/_parser/parso/python/tokenize.py b/libcst/_parser/parso/python/tokenize.py index bfd159dda..5773e348a 100644 --- a/libcst/_parser/parso/python/tokenize.py +++ b/libcst/_parser/parso/python/tokenize.py @@ -36,6 +36,7 @@ from dataclasses import dataclass from typing import Dict, Generator, Iterable, Optional, Pattern, Set, Tuple +from libcst._excep import CSTLogicError from libcst._parser.parso.python.token import PythonTokenTypes from libcst._parser.parso.utils import PythonVersionInfo, split_lines @@ -522,14 +523,14 @@ def dedent_if_necessary(start): if contstr: # continued string if endprog is None: - raise Exception("Logic error!") + raise CSTLogicError() endmatch = endprog.match(line) if endmatch: pos = endmatch.end(0) if contstr_start is None: - raise Exception("Logic error!") + raise CSTLogicError() if stashed is not None: - raise Exception("Logic error!") + raise CSTLogicError() yield PythonToken(STRING, contstr + line[:pos], contstr_start, prefix) contstr = "" contline = None @@ -547,7 +548,7 @@ def dedent_if_necessary(start): ) if string: if stashed is not None: - raise Exception("Logic error!") + raise CSTLogicError() yield PythonToken( FSTRING_STRING, string, @@ -572,7 +573,7 @@ def dedent_if_necessary(start): pos += quote_length if fstring_end_token is not None: if stashed is not None: - raise Exception("Logic error!") + raise CSTLogicError() yield fstring_end_token continue @@ -885,12 +886,12 @@ def dedent_if_necessary(start): if contstr: # continued string if endprog is None: - raise Exception("Logic error!") + raise CSTLogicError() endmatch = endprog.match(line) if endmatch: pos = endmatch.end(0) if contstr_start is None: - raise Exception("Logic error!") + raise CSTLogicError() yield PythonToken(STRING, contstr + line[:pos], contstr_start, prefix) contstr = "" contline = None diff --git a/libcst/_parser/production_decorator.py b/libcst/_parser/production_decorator.py index 41a817f8f..d5ba52deb 100644 --- a/libcst/_parser/production_decorator.py +++ b/libcst/_parser/production_decorator.py @@ -39,7 +39,7 @@ def inner(fn: _NonterminalConversionT) -> _NonterminalConversionT: # pyre-ignore: Pyre doesn't think that fn has a __name__ attribute fn_name = fn.__name__ if not fn_name.startswith("convert_"): - raise Exception( + raise ValueError( "A function with a production must be named 'convert_X', not " + f"'{fn_name}'." ) diff --git a/libcst/_parser/py_whitespace_parser.py b/libcst/_parser/py_whitespace_parser.py index b1fd9b5ed..078bdc3ff 100644 --- a/libcst/_parser/py_whitespace_parser.py +++ b/libcst/_parser/py_whitespace_parser.py @@ -5,6 +5,7 @@ from typing import List, Optional, Sequence, Tuple, Union +from libcst import ParserSyntaxError, CSTLogicError from libcst._nodes.whitespace import ( Comment, COMMENT_RE, @@ -103,10 +104,13 @@ def parse_trailing_whitespace( ) -> TrailingWhitespace: trailing_whitespace = _parse_trailing_whitespace(config, state) if trailing_whitespace is None: - raise Exception( + raise ParserSyntaxError( "Internal Error: Failed to parse TrailingWhitespace. This should never " + "happen because a TrailingWhitespace is never optional in the grammar, " - + "so this error should've been caught by parso first." + + "so this error should've been caught by parso first.", + lines=config.lines, + raw_line=state.line, + raw_column=state.column ) return trailing_whitespace @@ -177,7 +181,7 @@ def _parse_indent( if state.column == len(line_str) and state.line == len(config.lines): # We're at EOF, treat this as a failed speculative parse return False - raise Exception("Internal Error: Column should be 0 when parsing an indent.") + raise CSTLogicError("Internal Error: Column should be 0 when parsing an indent.") if line_str.startswith(absolute_indent, state.column): state.column += len(absolute_indent) return True @@ -206,7 +210,12 @@ def _parse_newline( newline_str = newline_match.group(0) state.column += len(newline_str) if state.column != len(line_str): - raise Exception("Internal Error: Found a newline, but it wasn't the EOL.") + raise ParserSyntaxError( + "Internal Error: Found a newline, but it wasn't the EOL.", + lines=config.lines, + raw_line=state.line, + raw_column=state.column + ) if state.line < len(config.lines): # this newline was the end of a line, and there's another line, # therefore we should move to the next line diff --git a/libcst/codegen/gen_matcher_classes.py b/libcst/codegen/gen_matcher_classes.py index b7940f973..638405edc 100644 --- a/libcst/codegen/gen_matcher_classes.py +++ b/libcst/codegen/gen_matcher_classes.py @@ -8,7 +8,7 @@ from typing import Generator, List, Optional, Sequence, Set, Tuple, Type, Union import libcst as cst -from libcst import ensure_type, parse_expression +from libcst import ensure_type, parse_expression, CSTLogicError from libcst.codegen.gather import all_libcst_nodes, typeclasses CST_DIR: Set[str] = set(dir(cst)) @@ -180,9 +180,9 @@ def visit_Subscript(self, node: cst.Subscript) -> None: # type blocks, even for sequence types. return if len(node.slice) != 1: - raise Exception( + raise ValueError( "Unexpected number of sequence elements inside Sequence type " - + "annotation!" + "annotation!" ) nodeslice = node.slice[0].slice if isinstance(nodeslice, cst.Index): @@ -346,10 +346,10 @@ def _get_clean_type_from_subscript( if typecst.value.deep_equals(cst.Name("Sequence")): # Lets attempt to widen the sequence type and alias it. if len(typecst.slice) != 1: - raise Exception("Logic error, Sequence shouldn't have more than one param!") + raise CSTLogicError("Sequence shouldn't have more than one param!") inner_type = typecst.slice[0].slice if not isinstance(inner_type, cst.Index): - raise Exception("Logic error, expecting Index for only Sequence element!") + raise CSTLogicError("Expecting Index for only Sequence element!") inner_type = inner_type.value if isinstance(inner_type, cst.Subscript): @@ -357,7 +357,7 @@ def _get_clean_type_from_subscript( elif isinstance(inner_type, (cst.Name, cst.SimpleString)): clean_inner_type = _get_clean_type_from_expression(aliases, inner_type) else: - raise Exception("Logic error, unexpected type in Sequence!") + raise CSTLogicError("Unexpected type in Sequence!") return _get_wrapped_union_type( typecst.deep_replace(inner_type, clean_inner_type), @@ -397,7 +397,7 @@ def _get_clean_type_and_aliases( elif isinstance(typecst, (cst.Name, cst.SimpleString)): clean_type = _get_clean_type_from_expression(aliases, typecst) else: - raise Exception("Logic error, unexpected top level type!") + raise CSTLogicError("Unexpected top level type!") # Now, insert OneOf/AllOf and MatchIfTrue into unions so we can typecheck their usage. # This allows us to put OneOf[SomeType] or MatchIfTrue[cst.SomeType] into any diff --git a/libcst/codemod/_cli.py b/libcst/codemod/_cli.py index 8bfc11f89..72a4abc64 100644 --- a/libcst/codemod/_cli.py +++ b/libcst/codemod/_cli.py @@ -47,7 +47,7 @@ def invoke_formatter(formatter_args: Sequence[str], code: AnyStr) -> AnyStr: # Make sure there is something to run if len(formatter_args) == 0: - raise Exception("No formatter configured but code formatting requested.") + raise ValueError("No formatter configured but code formatting requested.") # Invoke the formatter, giving it the code as stdin and assuming the formatted # code comes from stdout. @@ -574,7 +574,7 @@ def parallel_exec_transform_with_prettyprint( # noqa: C901 ) if jobs < 1: - raise Exception("Must have at least one job to process!") + raise ValueError("Must have at least one job to process!") if total == 0: return ParallelTransformResult(successes=0, failures=0, skips=0, warnings=0) diff --git a/libcst/codemod/_codemod.py b/libcst/codemod/_codemod.py index c0c3b2c76..e267f1545 100644 --- a/libcst/codemod/_codemod.py +++ b/libcst/codemod/_codemod.py @@ -56,9 +56,9 @@ def module(self) -> Module: """ module = self.context.module if module is None: - raise Exception( + raise ValueError( f"Attempted access of {self.__class__.__name__}.module outside of " - + "transform_module()." + "transform_module()." ) return module diff --git a/libcst/codemod/_visitor.py b/libcst/codemod/_visitor.py index ab915c493..892488387 100644 --- a/libcst/codemod/_visitor.py +++ b/libcst/codemod/_visitor.py @@ -6,7 +6,7 @@ from typing import Mapping import libcst as cst -from libcst import MetadataDependent +from libcst import MetadataDependent, MetadataException from libcst.codemod._codemod import Codemod from libcst.codemod._context import CodemodContext from libcst.matchers import MatcherDecoratableTransformer, MatcherDecoratableVisitor @@ -69,14 +69,14 @@ def __init__(self, context: CodemodContext) -> None: if dependencies: wrapper = self.context.wrapper if wrapper is None: - raise Exception( + raise MetadataException( f"Attempting to instantiate {self.__class__.__name__} outside of " + "an active transform. This means that metadata hasn't been " + "calculated and we cannot successfully create this visitor." ) for dep in dependencies: if dep not in wrapper._metadata: - raise Exception( + raise MetadataException( f"Attempting to access metadata {dep.__name__} that was not a " + "declared dependency of parent transform! This means it is " + "not possible to compute this value. Please ensure that all " @@ -101,7 +101,7 @@ def module(self) -> cst.Module: """ module = self.context.module if module is None: - raise Exception( + raise ValueError( f"Attempted access of {self.__class__.__name__}.module outside of " + "transform_module()." ) diff --git a/libcst/codemod/commands/convert_format_to_fstring.py b/libcst/codemod/commands/convert_format_to_fstring.py index ab98c0ea0..0b03da97d 100644 --- a/libcst/codemod/commands/convert_format_to_fstring.py +++ b/libcst/codemod/commands/convert_format_to_fstring.py @@ -9,6 +9,8 @@ import libcst as cst import libcst.matchers as m +from libcst._excep import CSTLogicError +from libcst._exceptions import ParserSyntaxError from libcst.codemod import ( CodemodContext, ContextAwareTransformer, @@ -23,7 +25,7 @@ def _get_lhs(field: cst.BaseExpression) -> cst.BaseExpression: elif isinstance(field, (cst.Attribute, cst.Subscript)): return _get_lhs(field.value) else: - raise Exception("Unsupported node type!") + raise TypeError("Unsupported node type!") def _find_expr_from_field_name( @@ -48,7 +50,7 @@ def _find_expr_from_field_name( if isinstance(lhs, cst.Integer): index = int(lhs.value) if index < 0 or index >= len(args): - raise Exception(f"Logic error, arg sequence {index} out of bounds!") + raise CSTLogicError(f"Arg sequence {index} out of bounds!") elif isinstance(lhs, cst.Name): for i, arg in enumerate(args): kw = arg.keyword @@ -58,10 +60,10 @@ def _find_expr_from_field_name( index = i break if index is None: - raise Exception(f"Logic error, arg name {lhs.value} out of bounds!") + raise CSTLogicError(f"Arg name {lhs.value} out of bounds!") if index is None: - raise Exception(f"Logic error, unsupported fieldname expression {fieldname}!") + raise CSTLogicError(f"Unsupported fieldname expression {fieldname}!") # Format it! return field_expr.deep_replace(lhs, args[index].value) @@ -158,9 +160,14 @@ def _get_tokens( # noqa: C901 format_accum += char if in_brackets > 0: - raise Exception("Stray { in format string!") + raise ParserSyntaxError( + "Stray { in format string!", + lines=[string], + raw_line=0, + raw_column=0 + ) if format_accum: - raise Exception("Logic error!") + raise CSTLogicError() # Yield the last bit of information yield (prefix, None, None, None) @@ -188,7 +195,7 @@ class SwitchStringQuotesTransformer(ContextAwareTransformer): def __init__(self, context: CodemodContext, avoid_quote: str) -> None: super().__init__(context) if avoid_quote not in {'"', "'"}: - raise Exception("Must specify either ' or \" single quote to avoid.") + raise ValueError("Must specify either ' or \" single quote to avoid.") self.avoid_quote: str = avoid_quote self.replace_quote: str = '"' if avoid_quote == "'" else "'" @@ -296,7 +303,7 @@ def leave_Call( # noqa: C901 ) in format_spec_tokens: if spec_format_spec is not None: # This shouldn't be possible, we don't allow it in the spec! - raise Exception("Logic error!") + raise CSTLogicError() if spec_literal_text: format_spec_parts.append( cst.FormattedStringText(spec_literal_text) diff --git a/libcst/codemod/commands/convert_percent_format_to_fstring.py b/libcst/codemod/commands/convert_percent_format_to_fstring.py index 9908a5b65..aa0c65191 100644 --- a/libcst/codemod/commands/convert_percent_format_to_fstring.py +++ b/libcst/codemod/commands/convert_percent_format_to_fstring.py @@ -53,12 +53,12 @@ def leave_SimpleString( original_node.prefix + quo + original_node.raw_value + quo ) if escaped_string.evaluated_value != original_node.evaluated_value: - raise Exception( + raise ValueError( f"Failed to escape string:\n original:{original_node.value}\n escaped:{escaped_string.value}" ) else: return escaped_string - raise Exception( + raise ValueError( f"Cannot find a good quote for escaping the SimpleString: {original_node.value}" ) return original_node diff --git a/libcst/codemod/commands/fix_pyre_directives.py b/libcst/codemod/commands/fix_pyre_directives.py index c3ab41b7d..360e40079 100644 --- a/libcst/codemod/commands/fix_pyre_directives.py +++ b/libcst/codemod/commands/fix_pyre_directives.py @@ -7,6 +7,7 @@ import libcst import libcst.matchers as m +from libcst._excep import CSTLogicError from libcst.codemod import CodemodContext, VisitorBasedCodemodCommand from libcst.helpers import insert_header_comments @@ -29,12 +30,12 @@ def __init__(self, context: CodemodContext) -> None: def visit_Module_header(self, node: libcst.Module) -> None: if self.in_module_header: - raise Exception("Logic error!") + raise CSTLogicError() self.in_module_header = True def leave_Module_header(self, node: libcst.Module) -> None: if not self.in_module_header: - raise Exception("Logic error!") + raise CSTLogicError() self.in_module_header = False def leave_EmptyLine( diff --git a/libcst/codemod/commands/rename.py b/libcst/codemod/commands/rename.py index 4bd0ee3dc..861f6c776 100644 --- a/libcst/codemod/commands/rename.py +++ b/libcst/codemod/commands/rename.py @@ -121,7 +121,7 @@ def leave_Import( import_alias_name = import_alias.name import_alias_full_name = get_full_name_for_node(import_alias_name) if import_alias_full_name is None: - raise Exception("Could not parse full name for ImportAlias.name node.") + raise ValueError("Could not parse full name for ImportAlias.name node.") if isinstance(import_alias_name, cst.Name) and self.old_name.startswith( import_alias_full_name + "." @@ -249,7 +249,7 @@ def leave_Attribute( ) -> Union[cst.Name, cst.Attribute]: full_name_for_node = get_full_name_for_node(original_node) if full_name_for_node is None: - raise Exception("Could not parse full name for Attribute node.") + raise ValueError("Could not parse full name for Attribute node.") full_replacement_name = self.gen_replacement(full_name_for_node) # If a node has no associated QualifiedName, we are still inside an import statement. @@ -320,7 +320,7 @@ def gen_name_or_attr_node( ) -> Union[cst.Attribute, cst.Name]: name_or_attr_node: cst.BaseExpression = cst.parse_expression(dotted_expression) if not isinstance(name_or_attr_node, (cst.Name, cst.Attribute)): - raise Exception( + raise ValueError( "`parse_expression()` on dotted path returned non-Attribute-or-Name." ) return name_or_attr_node diff --git a/libcst/codemod/visitors/_add_imports.py b/libcst/codemod/visitors/_add_imports.py index f734af5cd..55c4b2a2f 100644 --- a/libcst/codemod/visitors/_add_imports.py +++ b/libcst/codemod/visitors/_add_imports.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Sequence, Set, Tuple, Union import libcst -from libcst import matchers as m, parse_statement +from libcst import matchers as m, parse_statement, CSTLogicError from libcst._nodes.statement import Import, ImportFrom, SimpleStatementLine from libcst.codemod._context import CodemodContext from libcst.codemod._visitor import ContextAwareTransformer @@ -107,7 +107,7 @@ def _get_imports_from_context( ) -> List[ImportItem]: imports = context.scratch.get(AddImportsVisitor.CONTEXT_KEY, []) if not isinstance(imports, list): - raise Exception("Logic error!") + raise CSTLogicError() return imports @staticmethod @@ -136,7 +136,7 @@ def add_needed_import( """ if module == "__future__" and obj is None: - raise Exception("Cannot import __future__ directly!") + raise ImportError("Cannot import __future__ directly!") imports = AddImportsVisitor._get_imports_from_context(context) imports.append(ImportItem(module, obj, asname, relative)) context.scratch[AddImportsVisitor.CONTEXT_KEY] = imports @@ -157,9 +157,9 @@ def __init__( # Verify that the imports are valid for imp in imps: if imp.module == "__future__" and imp.obj_name is None: - raise Exception("Cannot import __future__ directly!") + raise ImportError("Cannot import __future__ directly!") if imp.module == "__future__" and imp.alias is not None: - raise Exception("Cannot import __future__ objects with aliases!") + raise ImportError("Cannot import __future__ objects with aliases!") # Resolve relative imports if we have a module name imps = [imp.resolve_relative(self.context.full_package_name) for imp in imps] diff --git a/libcst/codemod/visitors/_remove_imports.py b/libcst/codemod/visitors/_remove_imports.py index 559401275..c55bd8cc2 100644 --- a/libcst/codemod/visitors/_remove_imports.py +++ b/libcst/codemod/visitors/_remove_imports.py @@ -6,6 +6,7 @@ from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union import libcst as cst +from libcst._excep import CSTLogicError from libcst.codemod._context import CodemodContext from libcst.codemod._visitor import ContextAwareTransformer, ContextAwareVisitor from libcst.codemod.visitors._gather_unused_imports import GatherUnusedImportsVisitor @@ -45,7 +46,7 @@ def _remove_imports_from_importfrom_stmt( self.context.full_package_name, import_node ) if module_name is None: - raise Exception("Cannot look up absolute module from relative import!") + raise ImportError("Cannot look up absolute module from relative import!") # We know any local names will refer to this as an alias if # there is one, and as the original name if there is not one @@ -72,7 +73,7 @@ def _visit_name_attr_alike(self, node: Union[cst.Name, cst.Attribute]) -> None: # Look up the scope for this node, remove the import that caused it to exist. metadata_wrapper = self.context.wrapper if metadata_wrapper is None: - raise Exception("Cannot look up import, metadata is not computed for node!") + raise ImportError("Cannot look up import, metadata is not computed for node!") scope_provider = metadata_wrapper.resolve(ScopeProvider) try: scope = scope_provider[node] @@ -185,7 +186,7 @@ def _get_imports_from_context( ) -> List[Tuple[str, Optional[str], Optional[str]]]: unused_imports = context.scratch.get(RemoveImportsVisitor.CONTEXT_KEY, []) if not isinstance(unused_imports, list): - raise Exception("Logic error!") + raise CSTLogicError() return unused_imports @staticmethod @@ -255,7 +256,7 @@ def remove_unused_import_by_node( context.full_package_name, node ) if module_name is None: - raise Exception("Cannot look up absolute module from relative import!") + raise ImportError("Cannot look up absolute module from relative import!") for import_alias in names: RemoveImportsVisitor.remove_unused_import( context, diff --git a/libcst/display/text.py b/libcst/display/text.py index 3c6dc2883..65eb189f1 100644 --- a/libcst/display/text.py +++ b/libcst/display/text.py @@ -8,7 +8,7 @@ import dataclasses from typing import List, Sequence -from libcst import CSTNode +from libcst import CSTNode, CSTLogicError from libcst.helpers import filter_node_fields _DEFAULT_INDENT: str = " " @@ -84,7 +84,7 @@ def _node_repr_recursive( # noqa: C901 else: child_tokens.append("[]") else: - raise Exception("Logic error!") + raise CSTLogicError() # Handle indentation and trailing comma. split_by_line = "".join(child_tokens).split("\n") diff --git a/libcst/helpers/_template.py b/libcst/helpers/_template.py index e3f915a55..e205e0af5 100644 --- a/libcst/helpers/_template.py +++ b/libcst/helpers/_template.py @@ -45,12 +45,12 @@ def unmangled_name(var: str) -> Optional[str]: def mangle_template(template: str, template_vars: Set[str]) -> str: if TEMPLATE_PREFIX in template or TEMPLATE_SUFFIX in template: - raise Exception("Cannot parse a template containing reserved strings") + raise ValueError("Cannot parse a template containing reserved strings") for var in template_vars: original = f"{{{var}}}" if original not in template: - raise Exception( + raise ValueError( f'Template string is missing a reference to "{var}" referred to in kwargs' ) template = template.replace(original, mangled_name(var)) @@ -142,7 +142,7 @@ def __init__( name for name in template_replacements if name not in supported_vars } if unsupported_vars: - raise Exception( + raise ValueError( f'Template replacement for "{next(iter(unsupported_vars))}" is unsupported' ) @@ -350,7 +350,7 @@ def __init__(self, template_vars: Set[str]) -> None: def visit_Name(self, node: cst.Name) -> None: for var in self.template_vars: if node.value == mangled_name(var): - raise Exception(f'Template variable "{var}" was not replaced properly') + raise ValueError(f'Template variable "{var}" was not replaced properly') def unmangle_nodes( @@ -424,8 +424,8 @@ def parse_template_statement( if not isinstance( new_statement, (cst.SimpleStatementLine, cst.BaseCompoundStatement) ): - raise Exception( - f"Expected a statement but got a {new_statement.__class__.__name__}!" + raise TypeError( + f"Expected a statement but got a {new_statement.__class__.__qualname__}!" ) new_statement.visit(TemplateChecker({name for name in template_replacements})) return new_statement diff --git a/libcst/helpers/common.py b/libcst/helpers/common.py index 16c77669a..dee73aa4c 100644 --- a/libcst/helpers/common.py +++ b/libcst/helpers/common.py @@ -19,7 +19,7 @@ def ensure_type(node: object, nodetype: Type[T]) -> T: """ if not isinstance(node, nodetype): - raise Exception( - f"Expected a {nodetype.__name__} but got a {node.__class__.__name__}!" + raise ValueError( + f"Expected a {nodetype.__name__} but got a {node.__class__.__qualname__}!" ) return node diff --git a/libcst/helpers/expression.py b/libcst/helpers/expression.py index beb5f3248..5ae016cf7 100644 --- a/libcst/helpers/expression.py +++ b/libcst/helpers/expression.py @@ -38,5 +38,5 @@ def get_full_name_for_node_or_raise(node: Union[str, cst.CSTNode]) -> str: """ full_name = get_full_name_for_node(node) if full_name is None: - raise Exception(f"Not able to parse full name for: {node}") + raise ValueError(f"Not able to parse full name for: {node}") return full_name diff --git a/libcst/helpers/module.py b/libcst/helpers/module.py index 37e6af088..2b2973bf7 100644 --- a/libcst/helpers/module.py +++ b/libcst/helpers/module.py @@ -80,7 +80,7 @@ def get_absolute_module_for_import_or_raise( ) -> str: module = get_absolute_module_for_import(current_module, import_node) if module is None: - raise Exception(f"Unable to compute absolute module for {import_node}") + raise ValueError(f"Unable to compute absolute module for {import_node}") return module @@ -121,7 +121,7 @@ def get_absolute_module_from_package_for_import_or_raise( ) -> str: module = get_absolute_module_from_package_for_import(current_package, import_node) if module is None: - raise Exception(f"Unable to compute absolute module for {import_node}") + raise ValueError(f"Unable to compute absolute module for {import_node}") return module diff --git a/libcst/helpers/tests/test_expression.py b/libcst/helpers/tests/test_expression.py index 2b44e14b0..2f1659073 100644 --- a/libcst/helpers/tests/test_expression.py +++ b/libcst/helpers/tests/test_expression.py @@ -41,8 +41,8 @@ def test_get_full_name_for_expression( ) -> None: self.assertEqual(get_full_name_for_node(input), output) if output is None: - with self.assertRaises(Exception): - get_full_name_for_node_or_raise(input) + # with self.assertRaises(Exception): + get_full_name_for_node_or_raise(input) else: self.assertEqual(get_full_name_for_node_or_raise(input), output) diff --git a/libcst/matchers/_matcher_base.py b/libcst/matchers/_matcher_base.py index 039694a5f..ae3524693 100644 --- a/libcst/matchers/_matcher_base.py +++ b/libcst/matchers/_matcher_base.py @@ -29,7 +29,7 @@ import libcst import libcst.metadata as meta -from libcst import FlattenSentinel, MaybeSentinel, RemovalSentinel +from libcst import FlattenSentinel, MaybeSentinel, RemovalSentinel, CSTLogicError from libcst._metadata_dependent import LazyValue @@ -143,7 +143,7 @@ def __init__(self, *options: Union[_MatcherTypeT, "TypeOf[_MatcherTypeT]"]) -> N for option in options: if isinstance(option, TypeOf): if option.initalized: - raise Exception( + raise ValueError( "Cannot chain an uninitalized TypeOf with an initalized one" ) actual_options.extend(option._raw_options) @@ -213,7 +213,7 @@ def __init__(self, *options: Union[_MatcherT, "OneOf[_MatcherT]"]) -> None: actual_options: List[_MatcherT] = [] for option in options: if isinstance(option, AllOf): - raise Exception("Cannot use AllOf and OneOf in combination!") + raise ValueError("Cannot use AllOf and OneOf in combination!") elif isinstance(option, (OneOf, TypeOf)): actual_options.extend(option.options) else: @@ -234,7 +234,7 @@ def __or__(self, other: _OtherNodeT) -> "OneOf[Union[_MatcherT, _OtherNodeT]]": return OneOf(self, other) def __and__(self, other: _OtherNodeT) -> NoReturn: - raise Exception("Cannot use AllOf and OneOf in combination!") + raise ValueError("Cannot use AllOf and OneOf in combination!") def __invert__(self) -> "AllOf[_MatcherT]": # Invert using De Morgan's Law so we don't have to complicate types. @@ -286,9 +286,9 @@ def __init__(self, *options: Union[_MatcherT, "AllOf[_MatcherT]"]) -> None: actual_options: List[_MatcherT] = [] for option in options: if isinstance(option, OneOf): - raise Exception("Cannot use AllOf and OneOf in combination!") + raise ValueError("Cannot use AllOf and OneOf in combination!") elif isinstance(option, TypeOf): - raise Exception("Cannot use AllOf and TypeOf in combination!") + raise ValueError("Cannot use AllOf and TypeOf in combination!") elif isinstance(option, AllOf): actual_options.extend(option.options) else: @@ -306,7 +306,7 @@ def options(self) -> Sequence[_MatcherT]: # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently. def __or__(self, other: _OtherNodeT) -> NoReturn: - raise Exception("Cannot use AllOf and OneOf in combination!") + raise ValueError("Cannot use AllOf and OneOf in combination!") def __and__(self, other: _OtherNodeT) -> "AllOf[Union[_MatcherT, _OtherNodeT]]": return AllOf(self, other) @@ -431,7 +431,7 @@ def __and__(self, other: _OtherNodeT) -> "AllOf[Union[_MatcherT, _OtherNodeT]]": # that are captured with an and, either all of them will be assigned the # same node, or none of them. It makes more sense to move the SaveMatchedNode # up to wrap the AllOf. - raise Exception( + raise ValueError( ( "Cannot use AllOf with SavedMatchedNode children! Instead, you should " + "use SaveMatchedNode(AllOf(options...))." @@ -447,10 +447,10 @@ def __getattr__(self, key: str) -> object: def __invert__(self) -> "_MatcherT": # This doesn't make sense. We don't want to capture a node only if it # doesn't match, since this will never capture anything. - raise Exception( + raise ValueError( ( "Cannot invert a SaveMatchedNode. Instead you should wrap SaveMatchedNode " - + "around your inversion itself" + "around your inversion itself" ) ) @@ -761,7 +761,7 @@ def __init__( n: int, ) -> None: if n < 0: - raise Exception(f"{self.__class__.__name__} n attribute must be positive") + raise ValueError(f"{self.__class__.__qualname__} n attribute must be positive") self._n: int = n self._matcher: Union[_MatcherT, DoNotCareSentinel] = matcher @@ -784,13 +784,13 @@ def matcher(self) -> Union[_MatcherT, DoNotCareSentinel]: # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently. def __or__(self, other: object) -> NoReturn: - raise Exception("AtLeastN cannot be used in a OneOf matcher") + raise ValueError("AtLeastN cannot be used in a OneOf matcher") def __and__(self, other: object) -> NoReturn: - raise Exception("AtLeastN cannot be used in an AllOf matcher") + raise ValueError("AtLeastN cannot be used in an AllOf matcher") def __invert__(self) -> NoReturn: - raise Exception("Cannot invert an AtLeastN matcher!") + raise ValueError("Cannot invert an AtLeastN matcher!") def __repr__(self) -> str: if self._n == 0: @@ -863,7 +863,7 @@ def __init__( n: int, ) -> None: if n < 0: - raise Exception(f"{self.__class__.__name__} n attribute must be positive") + raise ValueError(f"{self.__class__.__qualname__} n attribute must be positive") self._n: int = n self._matcher: Union[_MatcherT, DoNotCareSentinel] = matcher @@ -887,13 +887,13 @@ def matcher(self) -> Union[_MatcherT, DoNotCareSentinel]: # pyre-fixme[15]: `__or__` overrides method defined in `type` inconsistently. def __or__(self, other: object) -> NoReturn: - raise Exception("AtMostN cannot be used in a OneOf matcher") + raise ValueError("AtMostN cannot be used in a OneOf matcher") def __and__(self, other: object) -> NoReturn: - raise Exception("AtMostN cannot be used in an AllOf matcher") + raise ValueError("AtMostN cannot be used in an AllOf matcher") def __invert__(self) -> NoReturn: - raise Exception("Cannot invert an AtMostN matcher!") + raise ValueError("Cannot invert an AtMostN matcher!") def __repr__(self) -> str: if self._n == 1: @@ -1158,7 +1158,7 @@ def _sequence_matches( # noqa: C901 else: # There are no other types of wildcard consumers, but we're making # pyre happy with that fact. - raise Exception(f"Logic error unrecognized wildcard {type(matcher)}!") + raise CSTLogicError(f"Unrecognized wildcard {type(matcher)}!") elif isinstance(matcher, _ExtractMatchingNode): # See if the raw matcher matches. If it does, capture the sequence we matched and store it. result = _sequence_matches( @@ -1354,7 +1354,7 @@ def _metadata_matches( # noqa: C901 return None return {} if actual_value == metadata.value else None else: - raise Exception("Logic error!") + raise CSTLogicError() def _node_matches( # noqa: C901 @@ -1918,7 +1918,7 @@ def replace( elif isinstance(tree, meta.MetadataWrapper): return tree.module.deep_clone() else: - raise Exception("Logic error!") + raise CSTLogicError() if isinstance(tree, meta.MetadataWrapper) and metadata_resolver is None: # Provide a convenience for calling replace directly on a MetadataWrapper. @@ -1935,5 +1935,5 @@ def replace( new_tree = tree.visit(replacer) if isinstance(new_tree, FlattenSentinel): # The above transform never returns FlattenSentinel, so this isn't possible - raise Exception("Logic error, cannot get a FlattenSentinel here!") + raise CSTLogicError("Cannot get a FlattenSentinel here!") return new_tree diff --git a/libcst/metadata/base_provider.py b/libcst/metadata/base_provider.py index 2e03416f0..0de6a80c9 100644 --- a/libcst/metadata/base_provider.py +++ b/libcst/metadata/base_provider.py @@ -78,7 +78,7 @@ def __init__(self, cache: object = None) -> None: self._computed: MutableMapping["CSTNode", MaybeLazyMetadataT] = {} if self.gen_cache and cache is None: # The metadata provider implementation is responsible to store and use cache. - raise Exception( + raise ValueError( f"Cache is required for initializing {self.__class__.__name__}." ) self.cache = cache diff --git a/libcst/metadata/full_repo_manager.py b/libcst/metadata/full_repo_manager.py index 770ba1f63..ab6430d83 100644 --- a/libcst/metadata/full_repo_manager.py +++ b/libcst/metadata/full_repo_manager.py @@ -85,7 +85,7 @@ def get_cache_for_path(self, path: str) -> Mapping["ProviderT", object]: MetadataWrapper(module, cache=manager.get_cache_for_path("a.py")) """ if path not in self._paths: - raise Exception( + raise ValueError( "The path needs to be in paths parameter when constructing FullRepoManager for efficient batch processing." ) # Make sure that the cache is available to us. If the user called diff --git a/libcst/metadata/tests/test_type_inference_provider.py b/libcst/metadata/tests/test_type_inference_provider.py index 50ca34584..a0a70a8c8 100644 --- a/libcst/metadata/tests/test_type_inference_provider.py +++ b/libcst/metadata/tests/test_type_inference_provider.py @@ -63,17 +63,11 @@ class TypeInferenceProviderTest(UnitTest): @classmethod def setUpClass(cls) -> None: os.chdir(TEST_SUITE_PATH) - try: - subprocess.run(["pyre", "-n", "start", "--no-watchman"]) - except subprocess.TimeoutExpired as exc: - raise exc + subprocess.run(["pyre", "-n", "start", "--no-watchman"]) @classmethod def tearDownClass(cls) -> None: - try: - subprocess.run(["pyre", "-n", "stop"], cwd=TEST_SUITE_PATH) - except subprocess.TimeoutExpired as exc: - raise exc + subprocess.run(["pyre", "-n", "stop"], cwd=TEST_SUITE_PATH) @data_provider( ((TEST_SUITE_PATH / "simple_class.py", TEST_SUITE_PATH / "simple_class.json"),) diff --git a/libcst/metadata/type_inference_provider.py b/libcst/metadata/type_inference_provider.py index f00c97b6a..32e740128 100644 --- a/libcst/metadata/type_inference_provider.py +++ b/libcst/metadata/type_inference_provider.py @@ -60,17 +60,15 @@ def gen_cache( ) -> Mapping[str, object]: params = ",".join(f"path='{root_path / path}'" for path in paths) cmd_args = ["pyre", "--noninteractive", "query", f"types({params})"] - try: - stdout, stderr, return_code = run_command(cmd_args, timeout=timeout) - except subprocess.TimeoutExpired as exc: - raise exc - if return_code != 0: - raise Exception(f"stderr:\n {stderr}\nstdout:\n {stdout}") + result = subprocess.run(cmd_args, capture_output=True, timeout=timeout) + result.check_returncode() + stdout, stderr = result.stdout.decode(), result.stderr.decode() + try: resp = json.loads(stdout)["response"] except Exception as e: - raise Exception(f"{e}\n\nstderr:\n {stderr}\nstdout:\n {stdout}") + raise Exception(f"{e}\n\nstderr:\n {stderr}\nstdout:\n {stdout}") from e return {path: _process_pyre_data(data) for path, data in zip(paths, resp)} def __init__(self, cache: PyreData) -> None: @@ -104,13 +102,6 @@ def visit_Call(self, node: cst.Call) -> Optional[bool]: self._parse_metadata(node) -def run_command( - cmd_args: List[str], timeout: Optional[int] = None -) -> Tuple[str, str, int]: - process = subprocess.run(cmd_args, capture_output=True, timeout=timeout) - return process.stdout.decode(), process.stderr.decode(), process.returncode - - class RawPyreData(TypedDict): path: str types: Sequence[InferredType] diff --git a/libcst/tool.py b/libcst/tool.py index 3c00ba8d6..f61977b17 100644 --- a/libcst/tool.py +++ b/libcst/tool.py @@ -21,7 +21,7 @@ import yaml -from libcst import LIBCST_VERSION, parse_module, PartialParserConfig +from libcst import LIBCST_VERSION, parse_module, PartialParserConfig, CSTLogicError from libcst._parser.parso.utils import parse_version_string from libcst.codemod import ( CodemodCommand, @@ -191,7 +191,7 @@ def _find_and_load_config(proc_name: str) -> Dict[str, Any]: requires_config = bool(os.environ.get("LIBCST_TOOL_REQUIRE_CONFIG", "")) if requires_config and not found_config: - raise Exception( + raise FileNotFoundError( f"Did not find a {CONFIG_FILE_NAME} in current directory or any " + "parent directory! Perhaps you meant to run this command from a " + "configured subdirectory, or you need to initialize a new project " @@ -390,7 +390,7 @@ def _codemod_impl(proc_name: str, command_args: List[str]) -> int: # noqa: C901 # full-repo metadata since there is no path. if any(p == "-" for p in args.path): if len(args.path) > 1: - raise Exception("Cannot specify multiple paths when reading from stdin!") + raise ValueError("Cannot specify multiple paths when reading from stdin!") print("Codemodding from stdin", file=sys.stderr) oldcode = sys.stdin.read() @@ -477,7 +477,7 @@ def __init__(self, comment: str, *, newlines: bool = False) -> None: def _serialize_impl(self, key: str, value: object) -> str: if not isinstance(value, list): - raise Exception("Can only serialize lists!") + raise ValueError("Can only serialize lists!") if self.newlines: values = [f"- {v!r}" for v in value] return f"{key}:{os.linesep}{os.linesep.join(values)}" @@ -538,7 +538,7 @@ def _initialize_impl(proc_name: str, command_args: List[str]) -> int: # For safety, verify that it parses to the identical file. actual_config = yaml.safe_load(config_str) if actual_config != default_config: - raise Exception("Logic error, serialization is invalid!") + raise CSTLogicError("Logic error, serialization is invalid!") config_file = os.path.abspath(os.path.join(args.path, CONFIG_FILE_NAME)) with open(config_file, "w") as fp: From 8c5e4752c259fab897faf385bf59bbe6dc76e159 Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Thu, 4 Jul 2024 02:39:20 +0200 Subject: [PATCH 2/9] Revert doc changes (mistake) --- docs/source/metadata_tutorial.ipynb | 16 ++++++++-------- docs/source/scope_tutorial.ipynb | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/source/metadata_tutorial.ipynb b/docs/source/metadata_tutorial.ipynb index 66ce4963e..499aaf736 100644 --- a/docs/source/metadata_tutorial.ipynb +++ b/docs/source/metadata_tutorial.ipynb @@ -24,16 +24,17 @@ "metadata": { "nbsphinx": "hidden" }, + "outputs": [], "source": [ "import sys\n", "sys.path.append(\"../../\")" - ], - "outputs": [] + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import libcst as cst\n", "\n", @@ -54,8 +55,7 @@ " # Mark all other Name nodes as not parameters\n", " if not self.get_metadata(type(self), node, False):\n", " self.set_metadata(node, False)" - ], - "outputs": [] + ] }, { "cell_type": "raw", @@ -80,6 +80,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "module = cst.parse_module(\"x\")\n", "wrapper = cst.MetadataWrapper(module)\n", @@ -88,8 +89,7 @@ "x_name_node = wrapper.module.body[0].body[0].value\n", "\n", "print(isparam[x_name_node]) # should print False" - ], - "outputs": [] + ] }, { "cell_type": "raw", @@ -106,6 +106,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "from libcst.metadata import PositionProvider\n", "\n", @@ -122,8 +123,7 @@ "module = cst.parse_module(\"def foo(x):\\n y = 1\\n return x + y\")\n", "wrapper = cst.MetadataWrapper(module)\n", "result = wrapper.visit(ParamPrinter()) # NB: wrapper.visit not module.visit" - ], - "outputs": [] + ] } ], "metadata": { diff --git a/docs/source/scope_tutorial.ipynb b/docs/source/scope_tutorial.ipynb index 6bfb407d7..179e2ed78 100644 --- a/docs/source/scope_tutorial.ipynb +++ b/docs/source/scope_tutorial.ipynb @@ -26,16 +26,17 @@ "metadata": { "nbsphinx": "hidden" }, + "outputs": [], "source": [ "import sys\n", "sys.path.append(\"../../\")" - ], - "outputs": [] + ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "source = \"\"\"\\\n", "import a, b, c as d, e as f # expect to keep: a, c as d\n", @@ -54,8 +55,7 @@ " var = k.method()\n", " func_undefined(var_undefined)\n", "\"\"\"" - ], - "outputs": [] + ] }, { "cell_type": "raw", @@ -71,6 +71,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import libcst as cst\n", "\n", @@ -79,8 +80,7 @@ "scopes = set(wrapper.resolve(cst.metadata.ScopeProvider).values())\n", "for scope in scopes:\n", " print(scope)" - ], - "outputs": [] + ] }, { "cell_type": "raw", @@ -97,6 +97,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "from collections import defaultdict\n", "from typing import Dict, Union, Set\n", @@ -124,8 +125,7 @@ " print(\n", " f\"Warning on line {location.line:2d}, column {location.column:2d}: Name reference `{node.value}` is not defined.\"\n", " )\n" - ], - "outputs": [] + ] }, { "cell_type": "raw", @@ -149,6 +149,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "class RemoveUnusedImportTransformer(cst.CSTTransformer):\n", " def __init__(\n", @@ -186,8 +187,7 @@ " self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom\n", " ) -> cst.ImportFrom:\n", " return self.leave_import_alike(original_node, updated_node)\n" - ], - "outputs": [] + ] }, { "cell_type": "raw", @@ -202,6 +202,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import difflib\n", "fixed_module = wrapper.module.visit(RemoveUnusedImportTransformer(unused_imports))\n", @@ -212,8 +213,7 @@ " difflib.unified_diff(source.splitlines(1), fixed_module.code.splitlines(1))\n", " )\n", ")" - ], - "outputs": [] + ] } ], "metadata": { From 412a290920b9c570b23161691af35bf8c404bec5 Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Thu, 4 Jul 2024 02:40:53 +0200 Subject: [PATCH 3/9] Revert _exceptions.py changes (mistake) --- libcst/_exceptions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/libcst/_exceptions.py b/libcst/_exceptions.py index d9bc376a4..5359ca3c0 100644 --- a/libcst/_exceptions.py +++ b/libcst/_exceptions.py @@ -226,5 +226,3 @@ def editor_column(self) -> int: class MetadataException(Exception): pass - - From dfcfd3b355f919cab56657cf7de52a1356c38ff7 Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Thu, 4 Jul 2024 02:54:51 +0200 Subject: [PATCH 4/9] Linter ufmt pass --- libcst/__init__.py | 2 +- libcst/_nodes/expression.py | 8 ++------ libcst/_nodes/statement.py | 2 +- libcst/_nodes/tests/test_funcdef.py | 4 +++- libcst/_nodes/tests/test_namedexpr.py | 4 +++- libcst/_parser/conversions/expression.py | 16 ++++++++-------- libcst/_parser/conversions/statement.py | 9 +++------ libcst/_parser/py_whitespace_parser.py | 10 ++++++---- libcst/codegen/gen_matcher_classes.py | 2 +- .../commands/convert_format_to_fstring.py | 5 +---- libcst/codemod/visitors/_add_imports.py | 2 +- libcst/codemod/visitors/_remove_imports.py | 8 ++++++-- libcst/display/text.py | 2 +- libcst/matchers/_matcher_base.py | 10 +++++++--- libcst/tool.py | 2 +- 15 files changed, 45 insertions(+), 41 deletions(-) diff --git a/libcst/__init__.py b/libcst/__init__.py index 2011613e0..ef7224f07 100644 --- a/libcst/__init__.py +++ b/libcst/__init__.py @@ -4,8 +4,8 @@ # LICENSE file in the root directory of this source tree. from libcst._batched_visitor import BatchableCSTVisitor, visit_batched -from libcst._exceptions import MetadataException, ParserSyntaxError from libcst._excep import CSTLogicError +from libcst._exceptions import MetadataException, ParserSyntaxError from libcst._flatten_sentinel import FlattenSentinel from libcst._maybe_sentinel import MaybeSentinel from libcst._metadata_dependent import MetadataDependent diff --git a/libcst/_nodes/expression.py b/libcst/_nodes/expression.py index c3563eabd..6db859346 100644 --- a/libcst/_nodes/expression.py +++ b/libcst/_nodes/expression.py @@ -18,13 +18,9 @@ from typing import Callable, Generator, Literal, Optional, Sequence, Union from libcst._add_slots import add_slots -from libcst._maybe_sentinel import MaybeSentinel -from libcst._nodes.base import ( - CSTCodegenError, - CSTNode, - CSTValidationError, -) from libcst._excep import CSTLogicError +from libcst._maybe_sentinel import MaybeSentinel +from libcst._nodes.base import CSTCodegenError, CSTNode, CSTValidationError from libcst._nodes.internal import ( CodegenState, visit_optional, diff --git a/libcst/_nodes/statement.py b/libcst/_nodes/statement.py index b4897c855..2eab41b0b 100644 --- a/libcst/_nodes/statement.py +++ b/libcst/_nodes/statement.py @@ -10,9 +10,9 @@ from typing import Literal, Optional, Pattern, Sequence, Union from libcst._add_slots import add_slots +from libcst._excep import CSTLogicError from libcst._maybe_sentinel import MaybeSentinel from libcst._nodes.base import CSTNode, CSTValidationError -from libcst._excep import CSTLogicError from libcst._nodes.expression import ( _BaseParenthesizedNode, Annotation, diff --git a/libcst/_nodes/tests/test_funcdef.py b/libcst/_nodes/tests/test_funcdef.py index 4ee03d4fa..65a0ff07a 100644 --- a/libcst/_nodes/tests/test_funcdef.py +++ b/libcst/_nodes/tests/test_funcdef.py @@ -1052,7 +1052,9 @@ def _parse_statement_force_38(code: str) -> cst.BaseCompoundStatement: code, config=cst.PartialParserConfig(python_version="3.8") ) if not isinstance(statement, cst.BaseCompoundStatement): - raise ValueError("This function is expecting to parse compound statements only!") + raise ValueError( + "This function is expecting to parse compound statements only!" + ) return statement diff --git a/libcst/_nodes/tests/test_namedexpr.py b/libcst/_nodes/tests/test_namedexpr.py index f560ccf41..6ebcf978d 100644 --- a/libcst/_nodes/tests/test_namedexpr.py +++ b/libcst/_nodes/tests/test_namedexpr.py @@ -22,7 +22,9 @@ def _parse_statement_force_38(code: str) -> cst.BaseCompoundStatement: code, config=cst.PartialParserConfig(python_version="3.8") ) if not isinstance(statement, cst.BaseCompoundStatement): - raise ValueError("This function is expecting to parse compound statements only!") + raise ValueError( + "This function is expecting to parse compound statements only!" + ) return statement diff --git a/libcst/_parser/conversions/expression.py b/libcst/_parser/conversions/expression.py index ebd56b472..764d4e82a 100644 --- a/libcst/_parser/conversions/expression.py +++ b/libcst/_parser/conversions/expression.py @@ -13,7 +13,7 @@ ) from libcst._excep import CSTLogicError -from libcst._exceptions import PartialParserSyntaxError, ParserSyntaxError +from libcst._exceptions import ParserSyntaxError, PartialParserSyntaxError from libcst._maybe_sentinel import MaybeSentinel from libcst._nodes.expression import ( Arg, @@ -332,7 +332,7 @@ def convert_boolop( f"Unexpected token '{op.string}'!", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) leftexpr = BooleanOperation( left=leftexpr, @@ -430,7 +430,7 @@ def convert_comp_op( f"Unexpected token '{op.string}'!", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) else: # A two-token comparison @@ -466,7 +466,7 @@ def convert_comp_op( f"Unexpected token '{leftcomp.string} {rightcomp.string}'!", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) @@ -513,7 +513,7 @@ def convert_binop( f"Unexpected token '{op.string}'!", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) leftexpr = BinaryOperation( left=leftexpr, @@ -565,7 +565,7 @@ def convert_factor( f"Unexpected token '{op.string}'!", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) return WithLeadingWhitespace( @@ -900,14 +900,14 @@ def convert_atom_basic( f"Unparseable number {child.string}", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) else: raise ParserSyntaxError( f"unexpected token {child.type.name}", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) diff --git a/libcst/_parser/conversions/statement.py b/libcst/_parser/conversions/statement.py index c81f4c115..371bac911 100644 --- a/libcst/_parser/conversions/statement.py +++ b/libcst/_parser/conversions/statement.py @@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Sequence, Tuple, Type from libcst._excep import CSTLogicError -from libcst._exceptions import PartialParserSyntaxError, ParserSyntaxError +from libcst._exceptions import ParserSyntaxError, PartialParserSyntaxError from libcst._maybe_sentinel import MaybeSentinel from libcst._nodes.expression import ( Annotation, @@ -285,10 +285,7 @@ def convert_annassign(config: ParserConfig, children: Sequence[Any]) -> Any: ) else: raise ParserSyntaxError( - "Invalid parser state!", - lines=config.lines, - raw_line=0, - raw_column=0 + "Invalid parser state!", lines=config.lines, raw_line=0, raw_column=0 ) return AnnAssignPartial( @@ -329,7 +326,7 @@ def convert_augassign(config: ParserConfig, children: Sequence[Any]) -> Any: f"Unexpected token '{op.string}'!", lines=config.lines, raw_line=0, - raw_column=0 + raw_column=0, ) return AugAssignPartial( diff --git a/libcst/_parser/py_whitespace_parser.py b/libcst/_parser/py_whitespace_parser.py index 078bdc3ff..6b6573a65 100644 --- a/libcst/_parser/py_whitespace_parser.py +++ b/libcst/_parser/py_whitespace_parser.py @@ -5,7 +5,7 @@ from typing import List, Optional, Sequence, Tuple, Union -from libcst import ParserSyntaxError, CSTLogicError +from libcst import CSTLogicError, ParserSyntaxError from libcst._nodes.whitespace import ( Comment, COMMENT_RE, @@ -110,7 +110,7 @@ def parse_trailing_whitespace( + "so this error should've been caught by parso first.", lines=config.lines, raw_line=state.line, - raw_column=state.column + raw_column=state.column, ) return trailing_whitespace @@ -181,7 +181,9 @@ def _parse_indent( if state.column == len(line_str) and state.line == len(config.lines): # We're at EOF, treat this as a failed speculative parse return False - raise CSTLogicError("Internal Error: Column should be 0 when parsing an indent.") + raise CSTLogicError( + "Internal Error: Column should be 0 when parsing an indent." + ) if line_str.startswith(absolute_indent, state.column): state.column += len(absolute_indent) return True @@ -214,7 +216,7 @@ def _parse_newline( "Internal Error: Found a newline, but it wasn't the EOL.", lines=config.lines, raw_line=state.line, - raw_column=state.column + raw_column=state.column, ) if state.line < len(config.lines): # this newline was the end of a line, and there's another line, diff --git a/libcst/codegen/gen_matcher_classes.py b/libcst/codegen/gen_matcher_classes.py index 638405edc..16c1b0722 100644 --- a/libcst/codegen/gen_matcher_classes.py +++ b/libcst/codegen/gen_matcher_classes.py @@ -8,7 +8,7 @@ from typing import Generator, List, Optional, Sequence, Set, Tuple, Type, Union import libcst as cst -from libcst import ensure_type, parse_expression, CSTLogicError +from libcst import CSTLogicError, ensure_type, parse_expression from libcst.codegen.gather import all_libcst_nodes, typeclasses CST_DIR: Set[str] = set(dir(cst)) diff --git a/libcst/codemod/commands/convert_format_to_fstring.py b/libcst/codemod/commands/convert_format_to_fstring.py index 0b03da97d..c574ef97f 100644 --- a/libcst/codemod/commands/convert_format_to_fstring.py +++ b/libcst/codemod/commands/convert_format_to_fstring.py @@ -161,10 +161,7 @@ def _get_tokens( # noqa: C901 if in_brackets > 0: raise ParserSyntaxError( - "Stray { in format string!", - lines=[string], - raw_line=0, - raw_column=0 + "Stray { in format string!", lines=[string], raw_line=0, raw_column=0 ) if format_accum: raise CSTLogicError() diff --git a/libcst/codemod/visitors/_add_imports.py b/libcst/codemod/visitors/_add_imports.py index 55c4b2a2f..e263758b8 100644 --- a/libcst/codemod/visitors/_add_imports.py +++ b/libcst/codemod/visitors/_add_imports.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional, Sequence, Set, Tuple, Union import libcst -from libcst import matchers as m, parse_statement, CSTLogicError +from libcst import CSTLogicError, matchers as m, parse_statement from libcst._nodes.statement import Import, ImportFrom, SimpleStatementLine from libcst.codemod._context import CodemodContext from libcst.codemod._visitor import ContextAwareTransformer diff --git a/libcst/codemod/visitors/_remove_imports.py b/libcst/codemod/visitors/_remove_imports.py index c55bd8cc2..88042acda 100644 --- a/libcst/codemod/visitors/_remove_imports.py +++ b/libcst/codemod/visitors/_remove_imports.py @@ -73,7 +73,9 @@ def _visit_name_attr_alike(self, node: Union[cst.Name, cst.Attribute]) -> None: # Look up the scope for this node, remove the import that caused it to exist. metadata_wrapper = self.context.wrapper if metadata_wrapper is None: - raise ImportError("Cannot look up import, metadata is not computed for node!") + raise ImportError( + "Cannot look up import, metadata is not computed for node!" + ) scope_provider = metadata_wrapper.resolve(ScopeProvider) try: scope = scope_provider[node] @@ -256,7 +258,9 @@ def remove_unused_import_by_node( context.full_package_name, node ) if module_name is None: - raise ImportError("Cannot look up absolute module from relative import!") + raise ImportError( + "Cannot look up absolute module from relative import!" + ) for import_alias in names: RemoveImportsVisitor.remove_unused_import( context, diff --git a/libcst/display/text.py b/libcst/display/text.py index 65eb189f1..cda6fc661 100644 --- a/libcst/display/text.py +++ b/libcst/display/text.py @@ -8,7 +8,7 @@ import dataclasses from typing import List, Sequence -from libcst import CSTNode, CSTLogicError +from libcst import CSTLogicError, CSTNode from libcst.helpers import filter_node_fields _DEFAULT_INDENT: str = " " diff --git a/libcst/matchers/_matcher_base.py b/libcst/matchers/_matcher_base.py index ae3524693..cd21f4f1a 100644 --- a/libcst/matchers/_matcher_base.py +++ b/libcst/matchers/_matcher_base.py @@ -29,7 +29,7 @@ import libcst import libcst.metadata as meta -from libcst import FlattenSentinel, MaybeSentinel, RemovalSentinel, CSTLogicError +from libcst import CSTLogicError, FlattenSentinel, MaybeSentinel, RemovalSentinel from libcst._metadata_dependent import LazyValue @@ -761,7 +761,9 @@ def __init__( n: int, ) -> None: if n < 0: - raise ValueError(f"{self.__class__.__qualname__} n attribute must be positive") + raise ValueError( + f"{self.__class__.__qualname__} n attribute must be positive" + ) self._n: int = n self._matcher: Union[_MatcherT, DoNotCareSentinel] = matcher @@ -863,7 +865,9 @@ def __init__( n: int, ) -> None: if n < 0: - raise ValueError(f"{self.__class__.__qualname__} n attribute must be positive") + raise ValueError( + f"{self.__class__.__qualname__} n attribute must be positive" + ) self._n: int = n self._matcher: Union[_MatcherT, DoNotCareSentinel] = matcher diff --git a/libcst/tool.py b/libcst/tool.py index f61977b17..abf68cbf4 100644 --- a/libcst/tool.py +++ b/libcst/tool.py @@ -21,7 +21,7 @@ import yaml -from libcst import LIBCST_VERSION, parse_module, PartialParserConfig, CSTLogicError +from libcst import CSTLogicError, LIBCST_VERSION, parse_module, PartialParserConfig from libcst._parser.parso.utils import parse_version_string from libcst.codemod import ( CodemodCommand, From 59e433278e34a614545ccc01c5866b0616aa9f49 Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Thu, 4 Jul 2024 02:55:32 +0200 Subject: [PATCH 5/9] Linter check_copyright pass --- libcst/_excep.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libcst/_excep.py b/libcst/_excep.py index 5ae47e166..ce685b0af 100644 --- a/libcst/_excep.py +++ b/libcst/_excep.py @@ -1,2 +1,7 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + class CSTLogicError(Exception): pass From 3875bcbcd29a604bfdaa34f5ef2d07ef51e9f3b8 Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Thu, 4 Jul 2024 03:00:11 +0200 Subject: [PATCH 6/9] Revert test file (mistake) --- libcst/helpers/tests/test_expression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcst/helpers/tests/test_expression.py b/libcst/helpers/tests/test_expression.py index 2f1659073..2b44e14b0 100644 --- a/libcst/helpers/tests/test_expression.py +++ b/libcst/helpers/tests/test_expression.py @@ -41,8 +41,8 @@ def test_get_full_name_for_expression( ) -> None: self.assertEqual(get_full_name_for_node(input), output) if output is None: - # with self.assertRaises(Exception): - get_full_name_for_node_or_raise(input) + with self.assertRaises(Exception): + get_full_name_for_node_or_raise(input) else: self.assertEqual(get_full_name_for_node_or_raise(input), output) From f4e40144e7e614a18b151701cd88700b247af3e5 Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Thu, 4 Jul 2024 03:06:30 +0200 Subject: [PATCH 7/9] Linter ufmt pass after check_copyright --- libcst/_excep.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libcst/_excep.py b/libcst/_excep.py index ce685b0af..87759a33c 100644 --- a/libcst/_excep.py +++ b/libcst/_excep.py @@ -3,5 +3,6 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. + class CSTLogicError(Exception): pass From cad0347a2a78165cc965127a74d85d977d4f93eb Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Mon, 12 Aug 2024 16:15:11 +0200 Subject: [PATCH 8/9] Keep old exception messages (avoid breaking-changes for users relying on exception messages) --- libcst/_nodes/base.py | 6 +++--- libcst/_nodes/expression.py | 4 ++-- libcst/_nodes/statement.py | 2 +- libcst/_parser/conversions/expression.py | 2 +- libcst/_parser/conversions/params.py | 2 +- libcst/_parser/conversions/statement.py | 10 +++++----- libcst/_parser/parso/python/tokenize.py | 14 +++++++------- libcst/codegen/gen_matcher_classes.py | 12 ++++++++---- .../codemod/commands/convert_format_to_fstring.py | 12 +++++++----- libcst/codemod/commands/fix_pyre_directives.py | 4 ++-- libcst/codemod/visitors/_add_imports.py | 2 +- libcst/codemod/visitors/_remove_imports.py | 2 +- libcst/display/text.py | 2 +- libcst/matchers/_matcher_base.py | 8 ++++---- 14 files changed, 44 insertions(+), 38 deletions(-) diff --git a/libcst/_nodes/base.py b/libcst/_nodes/base.py index 203f07266..2c5baa8f2 100644 --- a/libcst/_nodes/base.py +++ b/libcst/_nodes/base.py @@ -384,7 +384,7 @@ def deep_replace( new_tree = self.visit(_ChildReplacementTransformer(old_node, new_node)) if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)): # The above transform never returns *Sentinel, so this isn't possible - raise CSTLogicError("Cannot get a *Sentinel here!") + raise CSTLogicError("Logic error, cannot get a *Sentinel here!") return new_tree def deep_remove( @@ -401,7 +401,7 @@ def deep_remove( if isinstance(new_tree, FlattenSentinel): # The above transform never returns FlattenSentinel, so this isn't possible - raise CSTLogicError("Cannot get a FlattenSentinel here!") + raise CSTLogicError("Logic error, cannot get a FlattenSentinel here!") return new_tree @@ -423,7 +423,7 @@ def with_deep_changes( new_tree = self.visit(_ChildWithChangesTransformer(old_node, changes)) if isinstance(new_tree, (FlattenSentinel, RemovalSentinel)): # This is impossible with the above transform. - raise CSTLogicError("Cannot get a *Sentinel here!") + raise CSTLogicError("Logic error, cannot get a *Sentinel here!") return new_tree def __eq__(self: _CSTNodeSelfT, other: object) -> bool: diff --git a/libcst/_nodes/expression.py b/libcst/_nodes/expression.py index 6db859346..4a6e90e9c 100644 --- a/libcst/_nodes/expression.py +++ b/libcst/_nodes/expression.py @@ -1011,7 +1011,7 @@ def _validate(self) -> None: elif isinstance(right, FormattedString): rightbytes = "b" in right.prefix else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") if leftbytes != rightbytes: raise CSTValidationError("Cannot concatenate string and bytes.") @@ -1689,7 +1689,7 @@ def _codegen_impl( if default_indicator == "->": state.add_token(" ") else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") # Now, output the indicator and the rest of the annotation state.add_token(default_indicator) diff --git a/libcst/_nodes/statement.py b/libcst/_nodes/statement.py index 2eab41b0b..cc1b76342 100644 --- a/libcst/_nodes/statement.py +++ b/libcst/_nodes/statement.py @@ -1194,7 +1194,7 @@ def _name(self, node: CSTNode) -> str: elif isinstance(node, Attribute): return f"{self._name(node.value)}.{node.attr.value}" else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") @property def evaluated_name(self) -> str: diff --git a/libcst/_parser/conversions/expression.py b/libcst/_parser/conversions/expression.py index 764d4e82a..0ce0b92d5 100644 --- a/libcst/_parser/conversions/expression.py +++ b/libcst/_parser/conversions/expression.py @@ -904,7 +904,7 @@ def convert_atom_basic( ) else: raise ParserSyntaxError( - f"unexpected token {child.type.name}", + f"Logic error, unexpected token {child.type.name}", lines=config.lines, raw_line=0, raw_column=0, diff --git a/libcst/_parser/conversions/params.py b/libcst/_parser/conversions/params.py index ba1bcd47e..ba9bb5610 100644 --- a/libcst/_parser/conversions/params.py +++ b/libcst/_parser/conversions/params.py @@ -204,7 +204,7 @@ def add_param( ) else: # The state machine should never end up here. - raise CSTLogicError() + raise CSTLogicError("Logic error!") return current_param diff --git a/libcst/_parser/conversions/statement.py b/libcst/_parser/conversions/statement.py index 371bac911..a98038805 100644 --- a/libcst/_parser/conversions/statement.py +++ b/libcst/_parser/conversions/statement.py @@ -902,7 +902,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: if isinstance(clause, Token): if clause.string == "else": if orelse is not None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") orelse = Else( leading_lines=parse_empty_lines(config, clause.whitespace_before), whitespace_before_colon=parse_simple_whitespace( @@ -912,7 +912,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: ) elif clause.string == "finally": if finalbody is not None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") finalbody = Finally( leading_lines=parse_empty_lines(config, clause.whitespace_before), whitespace_before_colon=parse_simple_whitespace( @@ -921,7 +921,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: body=suite, ) else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") elif isinstance(clause, ExceptClausePartial): handlers.append( ExceptHandler( @@ -936,7 +936,7 @@ def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any: ) ) else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") return Try( leading_lines=parse_empty_lines(config, trytoken.whitespace_before), @@ -1342,7 +1342,7 @@ def convert_asyncable_stmt(config: ParserConfig, children: Sequence[Any]) -> Any asynchronous=asyncnode, leading_lines=leading_lines ) else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") @with_production("suite", "simple_stmt_suite | indented_suite") diff --git a/libcst/_parser/parso/python/tokenize.py b/libcst/_parser/parso/python/tokenize.py index 5773e348a..1f57fbff2 100644 --- a/libcst/_parser/parso/python/tokenize.py +++ b/libcst/_parser/parso/python/tokenize.py @@ -523,14 +523,14 @@ def dedent_if_necessary(start): if contstr: # continued string if endprog is None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") endmatch = endprog.match(line) if endmatch: pos = endmatch.end(0) if contstr_start is None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") if stashed is not None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") yield PythonToken(STRING, contstr + line[:pos], contstr_start, prefix) contstr = "" contline = None @@ -548,7 +548,7 @@ def dedent_if_necessary(start): ) if string: if stashed is not None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") yield PythonToken( FSTRING_STRING, string, @@ -573,7 +573,7 @@ def dedent_if_necessary(start): pos += quote_length if fstring_end_token is not None: if stashed is not None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") yield fstring_end_token continue @@ -886,12 +886,12 @@ def dedent_if_necessary(start): if contstr: # continued string if endprog is None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") endmatch = endprog.match(line) if endmatch: pos = endmatch.end(0) if contstr_start is None: - raise CSTLogicError() + raise CSTLogicError("Logic error!") yield PythonToken(STRING, contstr + line[:pos], contstr_start, prefix) contstr = "" contline = None diff --git a/libcst/codegen/gen_matcher_classes.py b/libcst/codegen/gen_matcher_classes.py index 16c1b0722..3e91f92b7 100644 --- a/libcst/codegen/gen_matcher_classes.py +++ b/libcst/codegen/gen_matcher_classes.py @@ -346,10 +346,14 @@ def _get_clean_type_from_subscript( if typecst.value.deep_equals(cst.Name("Sequence")): # Lets attempt to widen the sequence type and alias it. if len(typecst.slice) != 1: - raise CSTLogicError("Sequence shouldn't have more than one param!") + raise CSTLogicError( + "Logic error, Sequence shouldn't have more than one param!" + ) inner_type = typecst.slice[0].slice if not isinstance(inner_type, cst.Index): - raise CSTLogicError("Expecting Index for only Sequence element!") + raise CSTLogicError( + "Logic error, expecting Index for only Sequence element!" + ) inner_type = inner_type.value if isinstance(inner_type, cst.Subscript): @@ -357,7 +361,7 @@ def _get_clean_type_from_subscript( elif isinstance(inner_type, (cst.Name, cst.SimpleString)): clean_inner_type = _get_clean_type_from_expression(aliases, inner_type) else: - raise CSTLogicError("Unexpected type in Sequence!") + raise CSTLogicError("Logic error, unexpected type in Sequence!") return _get_wrapped_union_type( typecst.deep_replace(inner_type, clean_inner_type), @@ -397,7 +401,7 @@ def _get_clean_type_and_aliases( elif isinstance(typecst, (cst.Name, cst.SimpleString)): clean_type = _get_clean_type_from_expression(aliases, typecst) else: - raise CSTLogicError("Unexpected top level type!") + raise CSTLogicError("Logic error, unexpected top level type!") # Now, insert OneOf/AllOf and MatchIfTrue into unions so we can typecheck their usage. # This allows us to put OneOf[SomeType] or MatchIfTrue[cst.SomeType] into any diff --git a/libcst/codemod/commands/convert_format_to_fstring.py b/libcst/codemod/commands/convert_format_to_fstring.py index c574ef97f..f98d42e51 100644 --- a/libcst/codemod/commands/convert_format_to_fstring.py +++ b/libcst/codemod/commands/convert_format_to_fstring.py @@ -50,7 +50,7 @@ def _find_expr_from_field_name( if isinstance(lhs, cst.Integer): index = int(lhs.value) if index < 0 or index >= len(args): - raise CSTLogicError(f"Arg sequence {index} out of bounds!") + raise CSTLogicError(f"Logic error, arg sequence {index} out of bounds!") elif isinstance(lhs, cst.Name): for i, arg in enumerate(args): kw = arg.keyword @@ -60,10 +60,12 @@ def _find_expr_from_field_name( index = i break if index is None: - raise CSTLogicError(f"Arg name {lhs.value} out of bounds!") + raise CSTLogicError(f"Logic error, arg name {lhs.value} out of bounds!") if index is None: - raise CSTLogicError(f"Unsupported fieldname expression {fieldname}!") + raise CSTLogicError( + f"Logic error, unsupported fieldname expression {fieldname}!" + ) # Format it! return field_expr.deep_replace(lhs, args[index].value) @@ -164,7 +166,7 @@ def _get_tokens( # noqa: C901 "Stray { in format string!", lines=[string], raw_line=0, raw_column=0 ) if format_accum: - raise CSTLogicError() + raise CSTLogicError("Logic error!") # Yield the last bit of information yield (prefix, None, None, None) @@ -300,7 +302,7 @@ def leave_Call( # noqa: C901 ) in format_spec_tokens: if spec_format_spec is not None: # This shouldn't be possible, we don't allow it in the spec! - raise CSTLogicError() + raise CSTLogicError("Logic error!") if spec_literal_text: format_spec_parts.append( cst.FormattedStringText(spec_literal_text) diff --git a/libcst/codemod/commands/fix_pyre_directives.py b/libcst/codemod/commands/fix_pyre_directives.py index 360e40079..f90e7f375 100644 --- a/libcst/codemod/commands/fix_pyre_directives.py +++ b/libcst/codemod/commands/fix_pyre_directives.py @@ -30,12 +30,12 @@ def __init__(self, context: CodemodContext) -> None: def visit_Module_header(self, node: libcst.Module) -> None: if self.in_module_header: - raise CSTLogicError() + raise CSTLogicError("Logic error!") self.in_module_header = True def leave_Module_header(self, node: libcst.Module) -> None: if not self.in_module_header: - raise CSTLogicError() + raise CSTLogicError("Logic error!") self.in_module_header = False def leave_EmptyLine( diff --git a/libcst/codemod/visitors/_add_imports.py b/libcst/codemod/visitors/_add_imports.py index e263758b8..6ca8c94a0 100644 --- a/libcst/codemod/visitors/_add_imports.py +++ b/libcst/codemod/visitors/_add_imports.py @@ -107,7 +107,7 @@ def _get_imports_from_context( ) -> List[ImportItem]: imports = context.scratch.get(AddImportsVisitor.CONTEXT_KEY, []) if not isinstance(imports, list): - raise CSTLogicError() + raise CSTLogicError("Logic error!") return imports @staticmethod diff --git a/libcst/codemod/visitors/_remove_imports.py b/libcst/codemod/visitors/_remove_imports.py index 88042acda..924e7973a 100644 --- a/libcst/codemod/visitors/_remove_imports.py +++ b/libcst/codemod/visitors/_remove_imports.py @@ -188,7 +188,7 @@ def _get_imports_from_context( ) -> List[Tuple[str, Optional[str], Optional[str]]]: unused_imports = context.scratch.get(RemoveImportsVisitor.CONTEXT_KEY, []) if not isinstance(unused_imports, list): - raise CSTLogicError() + raise CSTLogicError("Logic error!") return unused_imports @staticmethod diff --git a/libcst/display/text.py b/libcst/display/text.py index cda6fc661..0e2700096 100644 --- a/libcst/display/text.py +++ b/libcst/display/text.py @@ -84,7 +84,7 @@ def _node_repr_recursive( # noqa: C901 else: child_tokens.append("[]") else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") # Handle indentation and trailing comma. split_by_line = "".join(child_tokens).split("\n") diff --git a/libcst/matchers/_matcher_base.py b/libcst/matchers/_matcher_base.py index cd21f4f1a..260f8f138 100644 --- a/libcst/matchers/_matcher_base.py +++ b/libcst/matchers/_matcher_base.py @@ -1162,7 +1162,7 @@ def _sequence_matches( # noqa: C901 else: # There are no other types of wildcard consumers, but we're making # pyre happy with that fact. - raise CSTLogicError(f"Unrecognized wildcard {type(matcher)}!") + raise CSTLogicError(f"Logic error unrecognized wildcard {type(matcher)}!") elif isinstance(matcher, _ExtractMatchingNode): # See if the raw matcher matches. If it does, capture the sequence we matched and store it. result = _sequence_matches( @@ -1358,7 +1358,7 @@ def _metadata_matches( # noqa: C901 return None return {} if actual_value == metadata.value else None else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") def _node_matches( # noqa: C901 @@ -1922,7 +1922,7 @@ def replace( elif isinstance(tree, meta.MetadataWrapper): return tree.module.deep_clone() else: - raise CSTLogicError() + raise CSTLogicError("Logic error!") if isinstance(tree, meta.MetadataWrapper) and metadata_resolver is None: # Provide a convenience for calling replace directly on a MetadataWrapper. @@ -1939,5 +1939,5 @@ def replace( new_tree = tree.visit(replacer) if isinstance(new_tree, FlattenSentinel): # The above transform never returns FlattenSentinel, so this isn't possible - raise CSTLogicError("Cannot get a FlattenSentinel here!") + raise CSTLogicError("Logic error, cannot get a FlattenSentinel here!") return new_tree From eb3f2a6057262dfa8678663a9560c99f030ae845 Mon Sep 17 00:00:00 2001 From: zaicruvoir1rominet Date: Mon, 12 Aug 2024 16:36:19 +0200 Subject: [PATCH 9/9] Applying PR comments --- libcst/codemod/visitors/_add_imports.py | 6 +++--- libcst/codemod/visitors/_remove_imports.py | 8 +++----- libcst/metadata/type_inference_provider.py | 19 ++++++++++++++----- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/libcst/codemod/visitors/_add_imports.py b/libcst/codemod/visitors/_add_imports.py index 6ca8c94a0..eeab43ae3 100644 --- a/libcst/codemod/visitors/_add_imports.py +++ b/libcst/codemod/visitors/_add_imports.py @@ -136,7 +136,7 @@ def add_needed_import( """ if module == "__future__" and obj is None: - raise ImportError("Cannot import __future__ directly!") + raise ValueError("Cannot import __future__ directly!") imports = AddImportsVisitor._get_imports_from_context(context) imports.append(ImportItem(module, obj, asname, relative)) context.scratch[AddImportsVisitor.CONTEXT_KEY] = imports @@ -157,9 +157,9 @@ def __init__( # Verify that the imports are valid for imp in imps: if imp.module == "__future__" and imp.obj_name is None: - raise ImportError("Cannot import __future__ directly!") + raise ValueError("Cannot import __future__ directly!") if imp.module == "__future__" and imp.alias is not None: - raise ImportError("Cannot import __future__ objects with aliases!") + raise ValueError("Cannot import __future__ objects with aliases!") # Resolve relative imports if we have a module name imps = [imp.resolve_relative(self.context.full_package_name) for imp in imps] diff --git a/libcst/codemod/visitors/_remove_imports.py b/libcst/codemod/visitors/_remove_imports.py index 924e7973a..56d900dfc 100644 --- a/libcst/codemod/visitors/_remove_imports.py +++ b/libcst/codemod/visitors/_remove_imports.py @@ -46,7 +46,7 @@ def _remove_imports_from_importfrom_stmt( self.context.full_package_name, import_node ) if module_name is None: - raise ImportError("Cannot look up absolute module from relative import!") + raise ValueError("Cannot look up absolute module from relative import!") # We know any local names will refer to this as an alias if # there is one, and as the original name if there is not one @@ -73,7 +73,7 @@ def _visit_name_attr_alike(self, node: Union[cst.Name, cst.Attribute]) -> None: # Look up the scope for this node, remove the import that caused it to exist. metadata_wrapper = self.context.wrapper if metadata_wrapper is None: - raise ImportError( + raise ValueError( "Cannot look up import, metadata is not computed for node!" ) scope_provider = metadata_wrapper.resolve(ScopeProvider) @@ -258,9 +258,7 @@ def remove_unused_import_by_node( context.full_package_name, node ) if module_name is None: - raise ImportError( - "Cannot look up absolute module from relative import!" - ) + raise ValueError("Cannot look up absolute module from relative import!") for import_alias in names: RemoveImportsVisitor.remove_unused_import( context, diff --git a/libcst/metadata/type_inference_provider.py b/libcst/metadata/type_inference_provider.py index 32e740128..8a90c26b9 100644 --- a/libcst/metadata/type_inference_provider.py +++ b/libcst/metadata/type_inference_provider.py @@ -14,6 +14,11 @@ from libcst.metadata.position_provider import PositionProvider +class TypeInferenceError(Exception): + """An attempt to access inferred type annotation + (through Pyre Query API) failed.""" + + class Position(TypedDict): line: int column: int @@ -61,14 +66,18 @@ def gen_cache( params = ",".join(f"path='{root_path / path}'" for path in paths) cmd_args = ["pyre", "--noninteractive", "query", f"types({params})"] - result = subprocess.run(cmd_args, capture_output=True, timeout=timeout) - result.check_returncode() - stdout, stderr = result.stdout.decode(), result.stderr.decode() + result = subprocess.run( + cmd_args, capture_output=True, timeout=timeout, text=True + ) try: - resp = json.loads(stdout)["response"] + result.check_returncode() + resp = json.loads(result.stdout)["response"] except Exception as e: - raise Exception(f"{e}\n\nstderr:\n {stderr}\nstdout:\n {stdout}") from e + raise TypeInferenceError( + f"{e}\n\nstderr:\n {result.stderr}\nstdout:\n {result.stdout}" + ) from e + return {path: _process_pyre_data(data) for path, data in zip(paths, resp)} def __init__(self, cache: PyreData) -> None: