Esempio n. 1
0
 def test_parallel_walk_inconsistent_trees(self):
     node_1 = parser.parse(
         textwrap.dedent("""
   def f(a):
     return a + 1
 """))
     node_2 = parser.parse(
         textwrap.dedent("""
   def f(a):
     return a + (a * 2)
 """))
     node_3 = parser.parse(
         textwrap.dedent("""
   def f(a):
     return a + 2
 """))
     with self.assertRaises(ValueError):
         for _ in ast_util.parallel_walk(node_1, node_2):
             pass
     # There is not particular reason to reject trees that differ only in the
     # value of a constant.
     # TODO(mdan): This should probably be allowed.
     with self.assertRaises(ValueError):
         for _ in ast_util.parallel_walk(node_1, node_3):
             pass
Esempio n. 2
0
    def test_subscript_resolve(self):
        samples = """
      x[i]
      x[i.b]
      a.b[c]
      a.b[x.y]
      a[z[c]]
      a[b[c[d]]]
      a[b].c
      a.b.c[d].e.f
      a.b[c[d]].e.f
      a.b[c[d.e.f].g].h
    """
        nodes = parser.parse(textwrap.dedent(samples), single_node=False)
        nodes = tuple(resolve(node).value for node in nodes)

        self.assertQNStringIs(nodes[0], 'x[i]')
        self.assertQNStringIs(nodes[1], 'x[i.b]')
        self.assertQNStringIs(nodes[2], 'a.b[c]')
        self.assertQNStringIs(nodes[3], 'a.b[x.y]')
        self.assertQNStringIs(nodes[4], 'a[z[c]]')
        self.assertQNStringIs(nodes[5], 'a[b[c[d]]]')
        self.assertQNStringIs(nodes[6], 'a[b].c')
        self.assertQNStringIs(nodes[7], 'a.b.c[d].e.f')
        self.assertQNStringIs(nodes[8], 'a.b[c[d]].e.f')
        self.assertQNStringIs(nodes[9], 'a.b[c[d.e.f].g].h')
Esempio n. 3
0
 def test_find_matching_definitions_lambda(self):
     node = parser.parse(textwrap.dedent("""
   f = lambda x: 1
 """))
     f = lambda x: x
     nodes = ast_util.find_matching_definitions(node, f)
     self.assertLambdaNodes(nodes, ('(1)', ))
Esempio n. 4
0
    def test_resolve(self):

        source = """
      def test_fn(x):
        '''Docstring.'''
        return x  # comment
    """
        source = textwrap.dedent(source)
        node = parser.parse(source)
        origin_info.resolve(node, source, 'test_file', 10, 10)

        def_origin = anno.getanno(node, anno.Basic.ORIGIN)
        self.assertEqual(def_origin.loc.filename, 'test_file')
        self.assertEqual(def_origin.loc.lineno, 10)
        self.assertEqual(def_origin.loc.col_offset, 10)
        self.assertEqual(def_origin.source_code_line, 'def test_fn(x):')
        self.assertIsNone(def_origin.comment)

        docstring_origin = anno.getanno(node.body[0], anno.Basic.ORIGIN)
        self.assertEqual(def_origin.loc.filename, 'test_file')
        self.assertEqual(docstring_origin.loc.lineno, 11)
        self.assertEqual(docstring_origin.loc.col_offset, 12)
        self.assertEqual(docstring_origin.source_code_line,
                         "  '''Docstring.'''")
        self.assertIsNone(docstring_origin.comment)

        ret_origin = anno.getanno(node.body[1], anno.Basic.ORIGIN)
        self.assertEqual(def_origin.loc.filename, 'test_file')
        self.assertEqual(ret_origin.loc.lineno, 12)
        self.assertEqual(ret_origin.loc.col_offset, 12)
        self.assertEqual(ret_origin.source_code_line, '  return x  # comment')
        self.assertEqual(ret_origin.comment, 'comment')
Esempio n. 5
0
    def test_rename_symbols_function(self):
        node = parser.parse('def f():\n  pass')
        node = ast_util.rename_symbols(
            node, {qual_names.QN('f'): qual_names.QN('f1')})

        source = parser.unparse(node, include_encoding_marker=False)
        self.assertEqual(source.strip(), 'def f1():\n    pass')
Esempio n. 6
0
 def test_find_matching_definitions_lambda_multiple_matches(self):
     node = parser.parse(
         textwrap.dedent("""
   f = lambda x: 1, lambda x: 2
 """))
     f = lambda x: x
     nodes = ast_util.find_matching_definitions(node, f)
     self.assertLambdaNodes(nodes, ('(1)', '(2)'))
Esempio n. 7
0
 def test_parallel_walk_string_leaves(self):
     src = """
   def f(a):
     global g
 """
     node = parser.parse(textwrap.dedent(src))
     for child_a, child_b in ast_util.parallel_walk(node, node):
         self.assertEqual(child_a, child_b)
Esempio n. 8
0
 def test_parallel_walk(self):
     src = """
   def f(a):
     return a + 1
 """
     node = parser.parse(textwrap.dedent(src))
     for child_a, child_b in ast_util.parallel_walk(node, node):
         self.assertEqual(child_a, child_b)
Esempio n. 9
0
    def test_to_code_basic(self):
        def test_fn(x, s):
            while tf.reduce_sum(x) > s:
                x /= 2
            return x

        # Just check that the output is parseable Python code.
        self.assertIsNotNone(parser.parse(api.to_code(test_fn)))
Esempio n. 10
0
 def test_keywords_to_dict(self):
     keywords = parser.parse_expression('f(a=b, c=1, d=\'e\')').keywords
     d = ast_util.keywords_to_dict(keywords)
     # Make sure we generate a usable dict node by attaching it to a variable and
     # compiling everything.
     node = parser.parse('def f(b): pass')
     node.body.append(ast.Return(d))
     result, _, _ = loader.load_ast(node)
     self.assertDictEqual(result.f(3), {'a': 3, 'c': 1, 'd': 'e'})
Esempio n. 11
0
    def test_rename_symbols_global(self):
        node = parser.parse('global a, b, c')
        node = qual_names.resolve(node)

        node = ast_util.rename_symbols(
            node, {qual_names.from_str('b'): qual_names.QN('renamed_b')})

        source = parser.unparse(node, include_encoding_marker=False)
        self.assertEqual(source.strip(), 'global a, renamed_b, c')
Esempio n. 12
0
    def test_rename_symbols_attributes(self):
        node = parser.parse('b.c = b.c.d')
        node = qual_names.resolve(node)

        node = ast_util.rename_symbols(
            node, {qual_names.from_str('b.c'): qual_names.QN('renamed_b_c')})

        source = parser.unparse(node, include_encoding_marker=False)
        self.assertEqual(source.strip(), 'renamed_b_c = renamed_b_c.d')
Esempio n. 13
0
 def test_apply_to_single_assignments_static_unpack(self):
     node = parser.parse('a, b, c = d, e, f')
     ast_util.apply_to_single_assignments(node.targets, node.value,
                                          self._mock_apply_fn)
     self.assertDictEqual(self._invocation_counts, {
         ('a', 'd'): 1,
         ('b', 'e'): 1,
         ('c', 'f'): 1,
     })
Esempio n. 14
0
    def test_rename_symbols_annotations(self):
        node = parser.parse('a[i]')
        node = qual_names.resolve(node)
        anno.setanno(node, 'foo', 'bar')
        orig_anno = anno.getanno(node, 'foo')

        node = ast_util.rename_symbols(
            node, {qual_names.QN('a'): qual_names.QN('b')})

        self.assertIs(anno.getanno(node, 'foo'), orig_anno)
Esempio n. 15
0
    def test_rename_symbols_basic(self):
        node = parser.parse('a + b')
        node = qual_names.resolve(node)

        node = ast_util.rename_symbols(
            node, {qual_names.QN('a'): qual_names.QN('renamed_a')})

        self.assertIsInstance(node.value.left.id, str)
        source = parser.unparse(node, include_encoding_marker=False)
        self.assertEqual(source.strip(), 'renamed_a + b')
Esempio n. 16
0
 def test_copy_clean(self):
     node = parser.parse(
         textwrap.dedent("""
   def f(a):
     return a + 1
 """))
     setattr(node, '__foo', 'bar')
     new_node = ast_util.copy_clean(node)
     self.assertIsNot(new_node, node)
     self.assertFalse(hasattr(new_node, '__foo'))
Esempio n. 17
0
 def test_copy_clean_preserves_annotations(self):
     node = parser.parse(
         textwrap.dedent("""
   def f(a):
     return a + 1
 """))
     anno.setanno(node, 'foo', 'bar')
     anno.setanno(node, 'baz', 1)
     new_node = ast_util.copy_clean(node, preserve_annos={'foo'})
     self.assertEqual(anno.getanno(new_node, 'foo'), 'bar')
     self.assertFalse(anno.hasanno(new_node, 'baz'))
Esempio n. 18
0
    def test_rename_symbols_basic(self):
        node = parser.parse('a + b')
        node = qual_names.resolve(node)

        node = ast_util.rename_symbols(
            node, {qual_names.QN('a'): qual_names.QN('renamed_a')})
        source = parser.unparse(node, include_encoding_marker=False)
        expected_node_src = 'renamed_a + b'

        self.assertIsInstance(node.value.left.id, str)
        self.assertAstMatches(node, source)
        self.assertAstMatches(node, expected_node_src)
Esempio n. 19
0
    def test_find_matching_definitions_lambda_uses_arg_names(self):
        node = parser.parse(
            textwrap.dedent("""
      f = lambda x: 1, lambda y: 2
    """))
        f = lambda x: x
        nodes = ast_util.find_matching_definitions(node, f)
        self.assertLambdaNodes(nodes, ('(1)', ))

        f = lambda y: y
        nodes = ast_util.find_matching_definitions(node, f)
        self.assertLambdaNodes(nodes, ('(2)', ))
Esempio n. 20
0
    def test_resolve_with_trailing_garbage(self):
        # This comment will be missed because the tokenizer fails to reach it.
        source = '   lambda: foo([], bar=1)), baz=2)()'
        clean_source = 'lambda: foo([], bar=1)'
        node = parser.parse(clean_source).value
        origin_info.resolve(node, source, 'test_file', 10, 10)

        def_origin = anno.getanno(node, anno.Basic.ORIGIN)
        self.assertEqual(def_origin.loc.lineno, 10)
        self.assertEqual(def_origin.loc.col_offset, 10)
        self.assertEqual(def_origin.source_code_line, source)
        self.assertIsNone(def_origin.comment)
Esempio n. 21
0
 def test_function_calls(self):
     samples = """
   a.b
   a.b()
   a().b
   z[i]
   z[i]()
   z()[i]
 """
     nodes = parser.parse(textwrap.dedent(samples), single_node=False)
     nodes = tuple(resolve(node).value for node in nodes)
     self.assertQNStringIs(nodes[0], 'a.b')
     self.assertQNStringIs(nodes[1].func, 'a.b')
     self.assertQNStringIs(nodes[2].value.func, 'a')
     self.assertQNStringIs(nodes[3], 'z[i]')
     self.assertQNStringIs(nodes[4].func, 'z[i]')
     self.assertQNStringIs(nodes[5].value.func, 'z')
Esempio n. 22
0
def replace(template, **replacements):
  """Replaces placeholders in a Python template.

  AST Name and Tuple nodes always receive the context that inferred from
  the template. However, when replacing more complex nodes (that can potentially
  contain Name children), then the caller is responsible for setting the
  appropriate context.

  Args:
    template: A string representing Python code. Any symbol name can be used
        that appears in the template code can be used as placeholder.
    **replacements: A mapping from placeholder names to (lists of) AST nodes
        that these placeholders will be replaced by. String values are also
        supported as a shorthand for AST Name nodes with the respective ID.

  Returns:
    An AST node or list of AST nodes with the replacements made. If the
    template was a function, a list will be returned. If the template was a
    node, the same node will be returned. If the template was a string, an
    AST node will be returned (a `Module` node in the case of a multi-line
    string, an `Expr` node otherwise).

  Raises:
    ValueError: if the arguments are incorrect.
  """
  if not isinstance(template, str):
    raise ValueError('Expected string template, got %s' % type(template))
  for k in replacements:
    replacements[k] = _convert_to_ast(replacements[k])
  template_str = parser.STANDARD_PREAMBLE + textwrap.dedent(template)
  nodes = parser.parse(
      template_str,
      preamble_len=parser.STANDARD_PREAMBLE_LEN,
      single_node=False)
  results = []
  for node in nodes:
    node = ReplaceTransformer(replacements).visit(node)
    if isinstance(node, (list, tuple)):
      results.extend(node)
    else:
      results.append(node)
  results = [qual_names.resolve(r) for r in results]
  return results
Esempio n. 23
0
    def test_resolve(self):
        samples = """
      a
      a.b
      (c, d.e)
      [f, (g.h.i)]
      j(k, l)
    """
        nodes = parser.parse(textwrap.dedent(samples), single_node=False)
        nodes = tuple(resolve(node).value for node in nodes)

        self.assertQNStringIs(nodes[0], 'a')
        self.assertQNStringIs(nodes[1], 'a.b')
        self.assertQNStringIs(nodes[2].elts[0], 'c')
        self.assertQNStringIs(nodes[2].elts[1], 'd.e')
        self.assertQNStringIs(nodes[3].elts[0], 'f')
        self.assertQNStringIs(nodes[3].elts[1], 'g.h.i')
        self.assertQNStringIs(nodes[4].func, 'j')
        self.assertQNStringIs(nodes[4].args[0], 'k')
        self.assertQNStringIs(nodes[4].args[1], 'l')
Esempio n. 24
0
  def test_create_source_map(self):

    source = """
      def test_fn(x):
        return x + 1
    """
    source = textwrap.dedent(source)

    node = parser.parse(source)
    fake_origin = origin_info.OriginInfo(
        loc=origin_info.Location('fake_filename', 3, 7),
        function_name='fake_function_name',
        source_code_line='fake source line',
        comment=None)
    anno.setanno(node, anno.Basic.ORIGIN, fake_origin)

    source_map = origin_info.create_source_map(node, source, 'test_filename')

    loc = origin_info.LineLocation('test_filename', 2)
    self.assertIn(loc, source_map)
    self.assertIs(source_map[loc], fake_origin)
Esempio n. 25
0
 def transform_ast(self, ast, transformer_context):
   print_code = parser.parse('print("Hello", name)')
   ast.body = [print_code] + ast.body
   return ast
Esempio n. 26
0
def create_source_map(nodes, code, filepath):
    """Creates a source map between an annotated AST and the code it compiles to.

  Note: this function assumes nodes nodes, code and filepath correspond to the
  same code.

  Args:
    nodes: Iterable[ast.AST, ...], one or more AST modes.
    code: Text, the source code in which nodes are found.
    filepath: Text

  Returns:
    Dict[LineLocation, OriginInfo], mapping locations in code to locations
    indicated by origin annotations in node.
  """
    reparsed_nodes = parser.parse(code, preamble_len=0, single_node=False)
    for node in reparsed_nodes:
        resolve(node, code, filepath, node.lineno, node.col_offset)

    source_map = {}

    try:
        for before, after in ast_util.parallel_walk(nodes, reparsed_nodes):
            # Note: generated code might not be mapped back to its origin.
            # TODO(mdan): Generated code should always be mapped to something.
            origin_info = anno.getanno(before, anno.Basic.ORIGIN, default=None)
            final_info = anno.getanno(after, anno.Basic.ORIGIN, default=None)
            if origin_info is None or final_info is None:
                continue

            # Note: the keys are by line only, excluding the column offset.
            line_loc = LineLocation(final_info.loc.filename,
                                    final_info.loc.lineno)

            existing_origin = source_map.get(line_loc)
            if existing_origin is not None:
                # Overlaps may exist because of child nodes, but almost never to
                # different line locations. Exception make decorated functions, where
                # both lines are mapped to the same line in the AST.

                # Line overlaps: keep bottom node.
                if existing_origin.loc.line_loc == origin_info.loc.line_loc:
                    if existing_origin.loc.lineno >= origin_info.loc.lineno:
                        continue

                # In case of column overlaps, keep the leftmost node.
                if existing_origin.loc.col_offset <= origin_info.loc.col_offset:
                    continue

            source_map[line_loc] = origin_info

    except ValueError as err:
        new_msg = 'Inconsistent ASTs detected. This is a bug. Cause: \n'
        new_msg += str(err)
        new_msg += 'Diff:\n'

        for n, rn in zip(nodes, reparsed_nodes):
            nodes_str = pretty_printer.fmt(n, color=False, noanno=True)
            reparsed_nodes_str = pretty_printer.fmt(rn,
                                                    color=False,
                                                    noanno=True)
            diff = difflib.context_diff(nodes_str.split('\n'),
                                        reparsed_nodes_str.split('\n'),
                                        fromfile='Original nodes',
                                        tofile='Reparsed nodes',
                                        n=7)
            diff = '\n'.join(diff)
            new_msg += diff + '\n'
        raise ValueError(new_msg)

    return source_map