Beispiel #1
0
 def test_restrictions(self):
     parsed = matcher.parse_ast('1\n2', '<string>')
     expr_match = parsed.tree.body[0].value
     expr_nomatch = parsed.tree.body[1].value
     m = syntax_matchers.ExprPattern(
         '$name', {'name': syntax_matchers.ExprPattern('1')})
     self.assertIsNotNone(m.match(matcher.MatchContext(parsed), expr_match))
     self.assertIsNone(m.match(matcher.MatchContext(parsed), expr_nomatch))
Beispiel #2
0
 def test_nonvariable_name_fails(self):
     """Names are only treated as variables, or anything weird, on request."""
     parsed = matcher.parse_ast('3', '<string>')
     expr = parsed.tree.body[0].value
     self.assertIsNone(
         syntax_matchers.ExprPattern('name').match(
             matcher.MatchContext(parsed), expr))
Beispiel #3
0
 def test_syntax_matchers(self):
     for expr in [
             "syntax_matchers.ExprPattern('$bar')", "ExprPattern('$bar')"
     ]:
         with self.subTest(expr=expr):
             self.assertEqual(evaluate.compile_matcher(expr),
                              syntax_matchers.ExprPattern('$bar'))
Beispiel #4
0
 def test_restrictions(self):
     parsed = matcher.parse_ast('a = 1\na = 2', '<string>')
     stmt_match, stmt_nomatch = parsed.tree.body
     m = syntax_matchers.StmtPattern(
         'a = $name', {'name': syntax_matchers.ExprPattern('1')})
     self.assertIsNotNone(m.match(matcher.MatchContext(parsed), stmt_match))
     self.assertIsNone(m.match(matcher.MatchContext(parsed), stmt_nomatch))
Beispiel #5
0
def idiom_fixer(
    old_expr,
    new_expr,
    category,
    url='https://refex.readthedocs.io/en/latest/guide/fixers/idiom.html',
):
    """Fixer for making expressions "clearer" / less convoluted.

  This also helps normalize them for other fixers to apply.

  Args:
    old_expr: An ExprPattern string for the expr to match.
    new_expr: A string.Template string for the replacement.
    category: A category for the fix.
    url: An URL describing the fix.

  Returns:
    A fixer that replaces old_expr with new_expr.
  """
    dotdotdot = fixer.ImmutableDefaultDict(lambda _: '...')
    return fixer.SimplePythonFixer(
        message=('This could be more Pythonic: %s -> %s.' %
                 ((string.Template(old_expr).substitute(dotdotdot),
                   string.Template(new_expr).substitute(dotdotdot)))),
        matcher=syntax_matchers.ExprPattern(old_expr),
        replacement=syntactic_template.PythonExprTemplate(new_expr),
        url=url,
        significant=False,
        category=category,
    )
Beispiel #6
0
def assert_alias_fixer(
    old_expr,
    new_expr,
    url='https://docs.python.org/3/library/unittest.html#deprecated-aliases'):
  """Fixer for deprecated unittest aliases.

  Args:
    old_expr: A string for an ExprPattern matching the target expr.
    new_expr: A string for a PythonExprTemplate to replace it with.
    url: The URL documenting the deprecation.

  Returns:
    A fixer that replaces old_expr with new_expr.
  """
  dotdotdot = fixer.ImmutableDefaultDict(lambda _: '...')
  return fixer.SimplePythonFixer(
      message=('{old} is a deprecated alias for {new} in the unittest module.'
               .format(
                   old=string.Template(old_expr).substitute(dotdotdot),
                   new=string.Template(new_expr).substitute(dotdotdot))),
      matcher=syntax_matchers.ExprPattern(old_expr),
      replacement=syntactic_template.PythonExprTemplate(new_expr),
      url=url,
      significant=False,
      category='pylint.g-deprecated-assert',
  )
Beispiel #7
0
def assert_message_fixer(old_expr, new_expr, method, is_absl=False):
  """Fixer for assertTrue()/assertFalse()/etc.

  related error fixes.

  assertTrue(...) often produces less readable error information than
  alternative methods like assertEqual etc.

  Args:
    old_expr: a ExprPattern string for the expr to match
    new_expr: a template string for the replacement
    method: the method to link to in the docs.
    is_absl: Whether this is an absl method with absl docs.

  Returns:
    A fixer that replaces old_expr with new_expr.
  """
  if is_absl:
    # absl doesn't have docs per se.
    url = f'https://github.com/abseil/abseil-py/search?q=%22def+{method}%22'
  else:
    url = f'https://docs.python.org/3/library/unittest.html#unittest.TestCase.{method}'
  dotdotdot = fixer.ImmutableDefaultDict(lambda _: '...')
  return fixer.SimplePythonFixer(
      message=(
          '%s is a more specific assertion, and may give more detailed error information than %s.'
          % (string.Template(new_expr).substitute(dotdotdot),
             string.Template(old_expr).substitute(dotdotdot))),
      matcher=syntax_matchers.ExprPattern(old_expr),
      replacement=syntactic_template.PythonExprTemplate(new_expr),
      url=url,
      category='pylint.g-generic-assert',
  )
Beispiel #8
0
def main():
    cli.run(
        runner=cli.RefexRunner(
            searcher=search.PyExprRewritingSearcher.from_matcher(
                # --mode=py.expr is equivalent to PyExprRewritingSearcher paired
                # with an ExprPattern. However, you can pass any matcher, not just
                # an ExprPattern.
                syntax_matchers.ExprPattern('hello'),
                {
                    # Using ROOT_LABEL as a key is equivalent to --sub=world.
                    # To get the equivalent of --named-sub=x=world,
                    # it would 'x' as a key instead.
                    #
                    # The value type corresponds to the --sub-mode. While
                    # refex on the command line defaults to picking the paired
                    # --sub-mode that matches the --mode, here there are no
                    # defaults and you must be explicit.
                    # e.g. for unsafe textual substitutions, as with
                    # --sub-mode=sh, you would use formatting.ShTemplate.
                    search.ROOT_LABEL:
                    syntactic_template.PythonExprTemplate('world')
                },
            ),
            dry_run=False,
        ),
        files=sys.argv[1:],
        bug_report_url='<project bug report URL goes here>',
    )
Beispiel #9
0
 def test_labeled_replacements_example_fragment(self):
     fx = fixer.SimplePythonFixer(
         message='',
         matcher=syntax_matchers.ExprPattern('$y'),
         replacement={'y': syntactic_template.PythonExprTemplate('$y')},
     )
     with self.assertRaises(TypeError):
         fx.example_replacement()
Beispiel #10
0
def _dict_iter_fixer(method_name):
    return fixer.SimplePythonFixer(
        message=('dict.{method} is deprecated and does not exist in Python 3. '
                 'Instead, import six and use six.{method}').format(
                     method=method_name),
        matcher=with_six(
            syntax_matchers.ExprPattern('$x.{}()'.format(method_name), {
                'x':
                base_matchers.Unless(syntax_matchers.ExprPattern('six'))
            }), ),
        replacement=syntactic_template.PythonExprTemplate(
            'six.{}($x)'.format(method_name)),
        url='https://www.python.org/doc/sunset-python-2/',
        category='pylint.dict-iter-method',
        # Must define manually due to the extra restrictions on the pattern.
        example_fragment='import six; x.{}()'.format(method_name),
        example_replacement='import six; six.{}(x)'.format(method_name),
    )
Beispiel #11
0
 def test_variable_name(self):
     parsed = matcher.parse_ast('3', '<string>')
     expr = parsed.tree.body[0].value
     expr_match = matcher.LexicalASTMatch(expr, parsed.text,
                                          expr.first_token, expr.last_token)
     self.assertEqual(
         syntax_matchers.ExprPattern('$name').match(
             matcher.MatchContext(parsed), expr),
         matcher.MatchInfo(expr_match,
                           {'name': matcher.BoundValue(expr_match)}))
Beispiel #12
0
 def test_complex_variable(self):
     parsed = matcher.parse_ast('foo + bar', '<string>')
     expr = parsed.tree.body[0].value
     self.assertEqual(
         syntax_matchers.ExprPattern('foo + $name').match(
             matcher.MatchContext(parsed), expr),
         matcher.MatchInfo(matcher.LexicalASTMatch(expr, parsed.text,
                                                   expr.first_token,
                                                   expr.last_token),
                           bindings=mock.ANY))
Beispiel #13
0
    def test_replace(self):
        fix = fixer.SimplePythonFixer(
            matcher=syntax_matchers.ExprPattern('$obj.attr'),
            replacement=syntactic_template.PythonTemplate(u'$obj.other'),
        )
        searcher = fixer.CombiningPythonFixer([fix])

        source = 'my_obj.attr + other_obj.attr'
        self.assertEqual('my_obj.other + other_obj.other',
                         search.rewrite_string(searcher, source, 'example.py'))
Beispiel #14
0
def _search_replace_fixer(search_expr,
                          replace,
                          message=None,
                          url='',
                          **kwargs):
    return fixer.SimplePythonFixer(
        message=message if message is not None else search_expr,
        matcher=syntax_matchers.ExprPattern(search_expr),
        replacement=syntactic_template.PythonExprTemplate(replace),
        url=url,
        category='TESTONLY',
        **kwargs)
Beispiel #15
0
    def test_discards_unparseable_expr(self):
        """Searching discards unparseable substitutions for expressions.

    (Note: this only happens during fixedpoint computation.)
    """
        fx = fixer.CombiningPythonFixer([
            fixer.SimplePythonFixer(message='',
                                    matcher=syntax_matchers.ExprPattern('a'),
                                    replacement=formatting.ShTemplate('x x x'),
                                    url='')
        ])
        self.assertEqual(
            list(search.find_iter(fx, 'a', 'foo.py', max_iterations=10)), [])
Beispiel #16
0
    def test_variable_conflict(self):
        """Variables use the default conflict resolution outside of the pattern.

    Inside of the pattern, they use MERGE_EQUIVALENT_AST, but this is opaque to
    callers.
    """
        # The AllOf shouldn't make a difference, because the $x variable is just
        # a regular Bind() variable outside of the pattern, and merges via KEEP_LAST
        # per normal.
        self.assertEqual(
            self.get_all_match_strings(
                base_matchers.AllOf(syntax_matchers.ExprPattern('$x'),
                                    base_matchers.Bind('x')), '1'), ['1'])
Beispiel #17
0
    def test_identical_patterns(self):
        """Tests that patterns match themselves when not parameterized.

    Many cases (e.g. None) are interesting for 2/3 compatibility, because the
    AST changes in Python 3. syntax_matchers gives an easy way to get
    cross-version compatibility.
    """
        for code in ['None', '{}', '[]', '{1:2, 3:4}', 'lambda a: a', '""']:
            parsed = matcher.parse_ast(code, '<string>')
            expr = parsed.tree.body[0].value
            for extra_comment in ['', "# comment doesn't matter"]:
                with self.subTest(code=code, extra_comment=extra_comment):
                    self.assertEqual(
                        syntax_matchers.ExprPattern(
                            code + extra_comment).match(
                                matcher.MatchContext(parsed), expr),
                        matcher.MatchInfo(
                            matcher.LexicalASTMatch(expr, parsed.text,
                                                    expr.first_token,
                                                    expr.last_token)))
Beispiel #18
0
from refex.fix import fixer
from refex.python import syntactic_template
from refex.python.matchers import ast_matchers
from refex.python.matchers import base_matchers
from refex.python.matchers import syntax_matchers

SIMPLE_PYTHON_FIXERS = []  # Disabled except when running in Python 2.


def with_six(m):
    return syntax_matchers.WithTopLevelImport(m, 'six')


_HAS_KEY_FIXER = fixer.SimplePythonFixer(
    message='dict.has_key() was removed in Python 3.',
    matcher=syntax_matchers.ExprPattern('$a.has_key($b)'),
    replacement=syntactic_template.PythonExprTemplate('$b in $a'),
    url='https://docs.python.org/3.1/whatsnew/3.0.html#builtins',
    significant=True,
    category='refex.modernize.dict_has_key',
)


def _dict_iter_fixer(method_name):
    return fixer.SimplePythonFixer(
        message=('dict.{method} is deprecated and does not exist in Python 3. '
                 'Instead, import six and use six.{method}').format(
                     method=method_name),
        matcher=with_six(
            syntax_matchers.ExprPattern('$x.{}()'.format(method_name), {
                'x':
Beispiel #19
0
 def from_pattern(
     cls, pattern: str, templates: Optional[Dict[str, formatting.Template]]
 ) -> "PyExprRewritingSearcher":
     """Creates a searcher from a ``--mode=py.expr`` template."""
     return cls.from_matcher(syntax_matchers.ExprPattern(pattern),
                             templates=templates)
Beispiel #20
0
 def test_no_such_variable(self):
     with self.assertRaises(KeyError):
         syntax_matchers.ExprPattern('a', {'x': base_matchers.Anything()})
Beispiel #21
0
 def test_syntaxerror(self):
     with self.assertRaises(ValueError):
         syntax_matchers.ExprPattern('{')
Beispiel #22
0
 def test_no_statement(self):
     with self.assertRaises(ValueError):
         syntax_matchers.ExprPattern('')
Beispiel #23
0
 def test_no_expr(self):
     with self.assertRaises(ValueError):
         syntax_matchers.ExprPattern('x = 1')
Beispiel #24
0
        category=_MUTABLE_CONSTANT_CATEGORY,
        matcher=matcher,
        replacement=replacement,
        **kwargs)


def _function_containing(matcher):
    """Returns a ast_matchers matcher for a function where any statement in the body matches `matcher`."""
    return syntax_matchers.NamedFunctionDefinition(body=base_matchers.Contains(
        syntax_matchers.IsOrHasDescendant(matcher)))


# Matches any function returning Optional[T] for some T.
_IN_FUNCTION_RETURNING_OPTIONAL = syntax_matchers.InNamedFunction(
    syntax_matchers.NamedFunctionDefinition(returns=base_matchers.AnyOf(
        syntax_matchers.ExprPattern('Optional[$_]'),
        syntax_matchers.ExprPattern('typing.Optional[$_]'),
        # TODO: May want to also include Union[None, ...].
        # TODO: match type comments as well.
    )))

# Matches any returning that's not "return" or "return None" (which are two
# different ast node values: ast.Return(value=None) and
# ast.Return(value=ast.Name(id='None')) respectively)
_NON_NONE_RETURN = matcher_.DebugLabeledMatcher(
    'Non-none return',
    ast_matchers.Return(value=base_matchers.Unless(
        base_matchers.AnyOf(base_matchers.Equals(None),
                            syntax_matchers.ExprPattern('None')))))

_NONE_RETURNS_FIXERS = [
Beispiel #25
0
 def test_nonname(self):
     with self.assertRaises(ValueError) as cm:
         syntax_matchers.ExprPattern('a.$x')
     self.assertIn('metavariable', str(cm.exception))
Beispiel #26
0
 def test_multiple_statements(self):
     with self.assertRaises(ValueError):
         syntax_matchers.ExprPattern('{}; {}')
Beispiel #27
0
 def test_dict_wrong_order(self):
     parsed = matcher.parse_ast('{1:2, 3:4}', '<string>')
     expr = parsed.tree.body[0].value
     self.assertIsNone(
         syntax_matchers.ExprPattern('{3:4, 1:2}').match(
             matcher.MatchContext(parsed), expr))
Beispiel #28
0
 def test_repeated_variable(self):
     self.assertEqual(
         self.get_all_match_strings(syntax_matchers.ExprPattern('$x + $x'),
                                    '1 + 1\n1 + 2\na + a\na + b'),
         ['1 + 1', 'a + a'])
Beispiel #29
0
 def _pattern_factory(pattern):
     return ast_matchers.Module(body=base_matchers.ItemsAre(
         [ast_matchers.Expr(value=syntax_matchers.ExprPattern(pattern))]))