Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1972)

Unified Diff: tools/telemetry/third_party/rope/rope/refactor/restructure.py

Issue 1132103009: Example of refactoring using rope library. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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]

Powered by Google App Engine
This is Rietveld 408576698