| Index: tools/telemetry/third_party/rope/rope/refactor/restructure.py
|
| diff --git a/tools/telemetry/third_party/rope/rope/refactor/restructure.py b/tools/telemetry/third_party/rope/rope/refactor/restructure.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..98a11e3d7741b0311a42a1068003747a1eb01a69
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/rope/rope/refactor/restructure.py
|
| @@ -0,0 +1,307 @@
|
| +import warnings
|
| +
|
| +from rope.base import change, taskhandle, builtins, ast, codeanalyze
|
| +from rope.base import libutils
|
| +from rope.refactor import patchedast, similarfinder, sourceutils
|
| +from rope.refactor.importutils import module_imports
|
| +
|
| +
|
| +class Restructure(object):
|
| + """A class to perform python restructurings
|
| +
|
| + A restructuring transforms pieces of code matching `pattern` to
|
| + `goal`. In the `pattern` wildcards can appear. Wildcards match
|
| + some piece of code based on their kind and arguments that are
|
| + passed to them through `args`.
|
| +
|
| + `args` is a dictionary of wildcard names to wildcard arguments.
|
| + If the argument is a tuple, the first item of the tuple is
|
| + considered to be the name of the wildcard to use; otherwise the
|
| + "default" wildcard is used. For getting the list arguments a
|
| + wildcard supports, see the pydoc of the wildcard. (see
|
| + `rope.refactor.wildcard.DefaultWildcard` for the default
|
| + wildcard.)
|
| +
|
| + `wildcards` is the list of wildcard types that can appear in
|
| + `pattern`. See `rope.refactor.wildcards`. If a wildcard does not
|
| + specify its kind (by using a tuple in args), the wildcard named
|
| + "default" is used. So there should be a wildcard with "default"
|
| + name in `wildcards`.
|
| +
|
| + `imports` is the list of imports that changed modules should
|
| + import. Note that rope handles duplicate imports and does not add
|
| + the import if it already appears.
|
| +
|
| + Example #1::
|
| +
|
| + pattern ${pyobject}.get_attribute(${name})
|
| + goal ${pyobject}[${name}]
|
| + args pyobject: instance=rope.base.pyobjects.PyObject
|
| +
|
| + Example #2::
|
| +
|
| + pattern ${name} in ${pyobject}.get_attributes()
|
| + goal ${name} in {pyobject}
|
| + args pyobject: instance=rope.base.pyobjects.PyObject
|
| +
|
| + Example #3::
|
| +
|
| + pattern ${pycore}.create_module(${project}.root, ${name})
|
| + goal generate.create_module(${project}, ${name})
|
| +
|
| + imports
|
| + from rope.contrib import generate
|
| +
|
| + args
|
| + project: type=rope.base.project.Project
|
| +
|
| + Example #4::
|
| +
|
| + pattern ${pow}(${param1}, ${param2})
|
| + goal ${param1} ** ${param2}
|
| + args pow: name=mod.pow, exact
|
| +
|
| + Example #5::
|
| +
|
| + pattern ${inst}.longtask(${p1}, ${p2})
|
| + goal
|
| + ${inst}.subtask1(${p1})
|
| + ${inst}.subtask2(${p2})
|
| + args
|
| + inst: type=mod.A,unsure
|
| +
|
| + """
|
| +
|
| + def __init__(self, project, pattern, goal, args=None,
|
| + imports=None, wildcards=None):
|
| + """Construct a restructuring
|
| +
|
| + See class pydoc for more info about the arguments.
|
| +
|
| + """
|
| + self.project = project
|
| + self.pattern = pattern
|
| + self.goal = goal
|
| + self.args = args
|
| + if self.args is None:
|
| + self.args = {}
|
| + self.imports = imports
|
| + if self.imports is None:
|
| + self.imports = []
|
| + self.wildcards = wildcards
|
| + self.template = similarfinder.CodeTemplate(self.goal)
|
| +
|
| + def get_changes(self, checks=None, imports=None, resources=None,
|
| + task_handle=taskhandle.NullTaskHandle()):
|
| + """Get the changes needed by this restructuring
|
| +
|
| + `resources` can be a list of `rope.base.resources.File`\s to
|
| + apply the restructuring on. If `None`, the restructuring will
|
| + be applied to all python files.
|
| +
|
| + `checks` argument has been deprecated. Use the `args` argument
|
| + of the constructor. The usage of::
|
| +
|
| + strchecks = {'obj1.type': 'mod.A', 'obj2': 'mod.B',
|
| + 'obj3.object': 'mod.C'}
|
| + checks = restructuring.make_checks(strchecks)
|
| +
|
| + can be replaced with::
|
| +
|
| + args = {'obj1': 'type=mod.A', 'obj2': 'name=mod.B',
|
| + 'obj3': 'object=mod.C'}
|
| +
|
| + where obj1, obj2 and obj3 are wildcard names that appear
|
| + in restructuring pattern.
|
| +
|
| + """
|
| + if checks is not None:
|
| + warnings.warn(
|
| + 'The use of checks parameter is deprecated; '
|
| + 'use the args parameter of the constructor instead.',
|
| + DeprecationWarning, stacklevel=2)
|
| + for name, value in checks.items():
|
| + self.args[name] = similarfinder._pydefined_to_str(value)
|
| + if imports is not None:
|
| + warnings.warn(
|
| + 'The use of imports parameter is deprecated; '
|
| + 'use imports parameter of the constructor, instead.',
|
| + DeprecationWarning, stacklevel=2)
|
| + self.imports = imports
|
| + changes = change.ChangeSet('Restructuring <%s> to <%s>' %
|
| + (self.pattern, self.goal))
|
| + if resources is not None:
|
| + files = [resource for resource in resources
|
| + if libutils.is_python_file(self.project, resource)]
|
| + else:
|
| + files = self.project.get_python_files()
|
| + job_set = task_handle.create_jobset('Collecting Changes', len(files))
|
| + for resource in files:
|
| + job_set.started_job(resource.path)
|
| + pymodule = self.project.get_pymodule(resource)
|
| + finder = similarfinder.SimilarFinder(pymodule,
|
| + wildcards=self.wildcards)
|
| + matches = list(finder.get_matches(self.pattern, self.args))
|
| + computer = self._compute_changes(matches, pymodule)
|
| + result = computer.get_changed()
|
| + if result is not None:
|
| + imported_source = self._add_imports(resource, result,
|
| + self.imports)
|
| + changes.add_change(change.ChangeContents(resource,
|
| + imported_source))
|
| + job_set.finished_job()
|
| + return changes
|
| +
|
| + def _compute_changes(self, matches, pymodule):
|
| + return _ChangeComputer(
|
| + pymodule.source_code, pymodule.get_ast(),
|
| + pymodule.lines, self.template, matches)
|
| +
|
| + def _add_imports(self, resource, source, imports):
|
| + if not imports:
|
| + return source
|
| + import_infos = self._get_import_infos(resource, imports)
|
| + pymodule = libutils.get_string_module(self.project, source, resource)
|
| + imports = module_imports.ModuleImports(self.project, pymodule)
|
| + for import_info in import_infos:
|
| + imports.add_import(import_info)
|
| + return imports.get_changed_source()
|
| +
|
| + def _get_import_infos(self, resource, imports):
|
| + pymodule = libutils.get_string_module(
|
| + self.project, '\n'.join(imports), resource)
|
| + imports = module_imports.ModuleImports(self.project, pymodule)
|
| + return [imports.import_info
|
| + for imports in imports.imports]
|
| +
|
| + def make_checks(self, string_checks):
|
| + """Convert str to str dicts to str to PyObject dicts
|
| +
|
| + This function is here to ease writing a UI.
|
| +
|
| + """
|
| + checks = {}
|
| + for key, value in string_checks.items():
|
| + is_pyname = not key.endswith('.object') and \
|
| + not key.endswith('.type')
|
| + evaluated = self._evaluate(value, is_pyname=is_pyname)
|
| + if evaluated is not None:
|
| + checks[key] = evaluated
|
| + return checks
|
| +
|
| + def _evaluate(self, code, is_pyname=True):
|
| + attributes = code.split('.')
|
| + pyname = None
|
| + if attributes[0] in ('__builtin__', '__builtins__'):
|
| + class _BuiltinsStub(object):
|
| + def get_attribute(self, name):
|
| + return builtins.builtins[name]
|
| + pyobject = _BuiltinsStub()
|
| + else:
|
| + pyobject = self.project.get_module(attributes[0])
|
| + for attribute in attributes[1:]:
|
| + pyname = pyobject[attribute]
|
| + if pyname is None:
|
| + return None
|
| + pyobject = pyname.get_object()
|
| + return pyname if is_pyname else pyobject
|
| +
|
| +
|
| +def replace(code, pattern, goal):
|
| + """used by other refactorings"""
|
| + finder = similarfinder.RawSimilarFinder(code)
|
| + matches = list(finder.get_matches(pattern))
|
| + ast = patchedast.get_patched_ast(code)
|
| + lines = codeanalyze.SourceLinesAdapter(code)
|
| + template = similarfinder.CodeTemplate(goal)
|
| + computer = _ChangeComputer(code, ast, lines, template, matches)
|
| + result = computer.get_changed()
|
| + if result is None:
|
| + return code
|
| + return result
|
| +
|
| +
|
| +class _ChangeComputer(object):
|
| +
|
| + def __init__(self, code, ast, lines, goal, matches):
|
| + self.source = code
|
| + self.goal = goal
|
| + self.matches = matches
|
| + self.ast = ast
|
| + self.lines = lines
|
| + self.matched_asts = {}
|
| + self._nearest_roots = {}
|
| + if self._is_expression():
|
| + for match in self.matches:
|
| + self.matched_asts[match.ast] = match
|
| +
|
| + def get_changed(self):
|
| + if self._is_expression():
|
| + result = self._get_node_text(self.ast)
|
| + if result == self.source:
|
| + return None
|
| + return result
|
| + else:
|
| + collector = codeanalyze.ChangeCollector(self.source)
|
| + last_end = -1
|
| + for match in self.matches:
|
| + start, end = match.get_region()
|
| + if start < last_end:
|
| + if not self._is_expression():
|
| + continue
|
| + last_end = end
|
| + replacement = self._get_matched_text(match)
|
| + collector.add_change(start, end, replacement)
|
| + return collector.get_changed()
|
| +
|
| + def _is_expression(self):
|
| + return self.matches and isinstance(self.matches[0],
|
| + similarfinder.ExpressionMatch)
|
| +
|
| + def _get_matched_text(self, match):
|
| + mapping = {}
|
| + for name in self.goal.get_names():
|
| + node = match.get_ast(name)
|
| + if node is None:
|
| + raise similarfinder.BadNameInCheckError(
|
| + 'Unknown name <%s>' % name)
|
| + force = self._is_expression() and match.ast == node
|
| + mapping[name] = self._get_node_text(node, force)
|
| + unindented = self.goal.substitute(mapping)
|
| + return self._auto_indent(match.get_region()[0], unindented)
|
| +
|
| + def _get_node_text(self, node, force=False):
|
| + if not force and node in self.matched_asts:
|
| + return self._get_matched_text(self.matched_asts[node])
|
| + start, end = patchedast.node_region(node)
|
| + main_text = self.source[start:end]
|
| + collector = codeanalyze.ChangeCollector(main_text)
|
| + for node in self._get_nearest_roots(node):
|
| + sub_start, sub_end = patchedast.node_region(node)
|
| + collector.add_change(sub_start - start, sub_end - start,
|
| + self._get_node_text(node))
|
| + result = collector.get_changed()
|
| + if result is None:
|
| + return main_text
|
| + return result
|
| +
|
| + def _auto_indent(self, offset, text):
|
| + lineno = self.lines.get_line_number(offset)
|
| + indents = sourceutils.get_indents(self.lines, lineno)
|
| + result = []
|
| + for index, line in enumerate(text.splitlines(True)):
|
| + if index != 0 and line.strip():
|
| + result.append(' ' * indents)
|
| + result.append(line)
|
| + return ''.join(result)
|
| +
|
| + def _get_nearest_roots(self, node):
|
| + if node not in self._nearest_roots:
|
| + result = []
|
| + for child in ast.get_child_nodes(node):
|
| + if child in self.matched_asts:
|
| + result.append(child)
|
| + else:
|
| + result.extend(self._get_nearest_roots(child))
|
| + self._nearest_roots[node] = result
|
| + return self._nearest_roots[node]
|
|
|