3RNN/Lib/site-packages/pasta/augment/rename.py
2024-05-26 19:49:15 +02:00

155 lines
5.0 KiB
Python

# coding=utf-8
"""Rename names in a python module."""
# Copyright 2017 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import ast
import six
from pasta.augment import import_utils
from pasta.base import ast_utils
from pasta.base import scope
def rename_external(t, old_name, new_name):
"""Rename an imported name in a module.
This will rewrite all import statements in `tree` that reference the old
module as well as any names in `tree` which reference the imported name. This
may introduce new import statements, but only if necessary.
For example, to move and rename the module `foo.bar.utils` to `foo.bar_utils`:
> rename_external(tree, 'foo.bar.utils', 'foo.bar_utils')
- import foo.bar.utils
+ import foo.bar_utils
- from foo.bar import utils
+ from foo import bar_utils
- from foo.bar import logic, utils
+ from foo.bar import logic
+ from foo import bar_utils
Arguments:
t: (ast.Module) Module syntax tree to perform the rename in. This will be
updated as a result of this function call with all affected nodes changed
and potentially new Import/ImportFrom nodes added.
old_name: (string) Fully-qualified path of the name to replace.
new_name: (string) Fully-qualified path of the name to update to.
Returns:
True if any changes were made, False otherwise.
"""
sc = scope.analyze(t)
if old_name not in sc.external_references:
return False
has_changed = False
renames = {}
already_changed = []
for ref in sc.external_references[old_name]:
if isinstance(ref.node, ast.alias):
parent = sc.parent(ref.node)
# An alias may be the most specific reference to an imported name, but it
# could if it is a child of an ImportFrom, the ImportFrom node's module
# may also need to be updated.
if isinstance(parent, ast.ImportFrom) and parent not in already_changed:
assert _rename_name_in_importfrom(sc, parent, old_name, new_name)
renames[old_name.rsplit('.', 1)[-1]] = new_name.rsplit('.', 1)[-1]
already_changed.append(parent)
else:
ref.node.name = new_name + ref.node.name[len(old_name):]
if not ref.node.asname:
renames[old_name] = new_name
has_changed = True
elif isinstance(ref.node, ast.ImportFrom):
if ref.node not in already_changed:
assert _rename_name_in_importfrom(sc, ref.node, old_name, new_name)
renames[old_name.rsplit('.', 1)[-1]] = new_name.rsplit('.', 1)[-1]
already_changed.append(ref.node)
has_changed = True
for rename_old, rename_new in six.iteritems(renames):
_rename_reads(sc, t, rename_old, rename_new)
return has_changed
def _rename_name_in_importfrom(sc, node, old_name, new_name):
if old_name == new_name:
return False
module_parts = node.module.split('.')
old_parts = old_name.split('.')
new_parts = new_name.split('.')
# If just the module is changing, rename it
if module_parts[:len(old_parts)] == old_parts:
node.module = '.'.join(new_parts + module_parts[len(old_parts):])
return True
# Find the alias node to be changed
for alias_to_change in node.names:
if alias_to_change.name == old_parts[-1]:
break
else:
return False
alias_to_change.name = new_parts[-1]
# Split the import if the package has changed
if module_parts != new_parts[:-1]:
if len(node.names) > 1:
new_import = import_utils.split_import(sc, node, alias_to_change)
new_import.module = '.'.join(new_parts[:-1])
else:
node.module = '.'.join(new_parts[:-1])
return True
def _rename_reads(sc, t, old_name, new_name):
"""Updates all locations in the module where the given name is read.
Arguments:
sc: (scope.Scope) Scope to work in. This should be the scope of `t`.
t: (ast.AST) The AST to perform updates in.
old_name: (string) Dotted name to update.
new_name: (string) Dotted name to replace it with.
Returns:
True if any changes were made, False otherwise.
"""
name_parts = old_name.split('.')
try:
name = sc.names[name_parts[0]]
for part in name_parts[1:]:
name = name.attrs[part]
except KeyError:
return False
has_changed = False
for ref_node in name.reads:
if isinstance(ref_node, (ast.Name, ast.Attribute)):
ast_utils.replace_child(sc.parent(ref_node), ref_node,
ast.parse(new_name).body[0].value)
has_changed = True
return has_changed