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

Unified Diff: tools/telemetry/third_party/rope/rope/base/codeanalyze.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/base/codeanalyze.py
diff --git a/tools/telemetry/third_party/rope/rope/base/codeanalyze.py b/tools/telemetry/third_party/rope/rope/base/codeanalyze.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c1f055d5a57967f368128185ebd75709fde4c52
--- /dev/null
+++ b/tools/telemetry/third_party/rope/rope/base/codeanalyze.py
@@ -0,0 +1,362 @@
+import bisect
+import re
+import token
+import tokenize
+
+
+class ChangeCollector(object):
+
+ def __init__(self, text):
+ self.text = text
+ self.changes = []
+
+ def add_change(self, start, end, new_text=None):
+ if new_text is None:
+ new_text = self.text[start:end]
+ self.changes.append((start, end, new_text))
+
+ def get_changed(self):
+ if not self.changes:
+ return None
+
+ self.changes.sort(key=lambda x: x[:2])
+ pieces = []
+ last_changed = 0
+ for change in self.changes:
+ start, end, text = change
+ pieces.append(self.text[last_changed:start] + text)
+ last_changed = end
+ if last_changed < len(self.text):
+ pieces.append(self.text[last_changed:])
+ result = ''.join(pieces)
+ if result != self.text:
+ return result
+
+
+class SourceLinesAdapter(object):
+ """Adapts source to Lines interface
+
+ Note: The creation of this class is expensive.
+ """
+
+ def __init__(self, source_code):
+ self.code = source_code
+ self.starts = None
+ self._initialize_line_starts()
+
+ def _initialize_line_starts(self):
+ self.starts = []
+ self.starts.append(0)
+ try:
+ i = 0
+ while True:
+ i = self.code.index('\n', i) + 1
+ self.starts.append(i)
+ except ValueError:
+ pass
+ self.starts.append(len(self.code) + 1)
+
+ def get_line(self, lineno):
+ return self.code[self.starts[lineno - 1]:
+ self.starts[lineno] - 1]
+
+ def length(self):
+ return len(self.starts) - 1
+
+ def get_line_number(self, offset):
+ return bisect.bisect(self.starts, offset)
+
+ def get_line_start(self, lineno):
+ return self.starts[lineno - 1]
+
+ def get_line_end(self, lineno):
+ return self.starts[lineno] - 1
+
+
+class ArrayLinesAdapter(object):
+
+ def __init__(self, lines):
+ self.lines = lines
+
+ def get_line(self, line_number):
+ return self.lines[line_number - 1]
+
+ def length(self):
+ return len(self.lines)
+
+
+class LinesToReadline(object):
+
+ def __init__(self, lines, start):
+ self.lines = lines
+ self.current = start
+
+ def readline(self):
+ if self.current <= self.lines.length():
+ self.current += 1
+ return self.lines.get_line(self.current - 1) + '\n'
+ return ''
+
+ def __call__(self):
+ return self.readline()
+
+
+class _CustomGenerator(object):
+
+ def __init__(self, lines):
+ self.lines = lines
+ self.in_string = ''
+ self.open_count = 0
+ self.continuation = False
+
+ def __call__(self):
+ size = self.lines.length()
+ result = []
+ i = 1
+ while i <= size:
+ while i <= size and not self.lines.get_line(i).strip():
+ i += 1
+ if i <= size:
+ start = i
+ while True:
+ line = self.lines.get_line(i)
+ self._analyze_line(line)
+ if not (self.continuation or self.open_count or
+ self.in_string) or i == size:
+ break
+ i += 1
+ result.append((start, i))
+ i += 1
+ return result
+
+ _main_chars = re.compile(r'[\'|"|#|\\|\[|\]|\{|\}|\(|\)]')
+
+ def _analyze_line(self, line):
+ char = None
+ for match in self._main_chars.finditer(line):
+ char = match.group()
+ i = match.start()
+ if char in '\'"':
+ if not self.in_string:
+ self.in_string = char
+ if char * 3 == line[i:i + 3]:
+ self.in_string = char * 3
+ elif self.in_string == line[i:i + len(self.in_string)] and \
+ not (i > 0 and line[i - 1] == '\\' and
+ not (i > 1 and line[i - 2] == '\\')):
+ self.in_string = ''
+ if self.in_string:
+ continue
+ if char == '#':
+ break
+ if char in '([{':
+ self.open_count += 1
+ elif char in ')]}':
+ self.open_count -= 1
+ if line and char != '#' and line.endswith('\\'):
+ self.continuation = True
+ else:
+ self.continuation = False
+
+
+def custom_generator(lines):
+ return _CustomGenerator(lines)()
+
+
+class LogicalLineFinder(object):
+
+ def __init__(self, lines):
+ self.lines = lines
+
+ def logical_line_in(self, line_number):
+ indents = count_line_indents(self.lines.get_line(line_number))
+ tries = 0
+ while True:
+ block_start = get_block_start(self.lines, line_number, indents)
+ try:
+ return self._block_logical_line(block_start, line_number)
+ except IndentationError as e:
+ tries += 1
+ if tries == 5:
+ raise e
+ lineno = e.lineno + block_start - 1
+ indents = count_line_indents(self.lines.get_line(lineno))
+
+ def generate_starts(self, start_line=1, end_line=None):
+ for start, end in self.generate_regions(start_line, end_line):
+ yield start
+
+ def generate_regions(self, start_line=1, end_line=None):
+ # XXX: `block_start` should be at a better position!
+ block_start = 1
+ readline = LinesToReadline(self.lines, block_start)
+ try:
+ for start, end in self._logical_lines(readline):
+ real_start = start + block_start - 1
+ real_start = self._first_non_blank(real_start)
+ if end_line is not None and real_start >= end_line:
+ break
+ real_end = end + block_start - 1
+ if real_start >= start_line:
+ yield (real_start, real_end)
+ except tokenize.TokenError:
+ pass
+
+ def _block_logical_line(self, block_start, line_number):
+ readline = LinesToReadline(self.lines, block_start)
+ shifted = line_number - block_start + 1
+ region = self._calculate_logical(readline, shifted)
+ start = self._first_non_blank(region[0] + block_start - 1)
+ if region[1] is None:
+ end = self.lines.length()
+ else:
+ end = region[1] + block_start - 1
+ return start, end
+
+ def _calculate_logical(self, readline, line_number):
+ last_end = 1
+ try:
+ for start, end in self._logical_lines(readline):
+ if line_number <= end:
+ return (start, end)
+ last_end = end + 1
+ except tokenize.TokenError as e:
+ current = e.args[1][0]
+ return (last_end, max(last_end, current - 1))
+ return (last_end, None)
+
+ def _logical_lines(self, readline):
+ last_end = 1
+ for current_token in tokenize.generate_tokens(readline):
+ current = current_token[2][0]
+ if current_token[0] == token.NEWLINE:
+ yield (last_end, current)
+ last_end = current + 1
+
+ def _first_non_blank(self, line_number):
+ current = line_number
+ while current < self.lines.length():
+ line = self.lines.get_line(current).strip()
+ if line and not line.startswith('#'):
+ return current
+ current += 1
+ return current
+
+
+def tokenizer_generator(lines):
+ return LogicalLineFinder(lines).generate_regions()
+
+
+class CachingLogicalLineFinder(object):
+
+ def __init__(self, lines, generate=custom_generator):
+ self.lines = lines
+ self._generate = generate
+
+ _starts = None
+
+ @property
+ def starts(self):
+ if self._starts is None:
+ self._init_logicals()
+ return self._starts
+
+ _ends = None
+
+ @property
+ def ends(self):
+ if self._ends is None:
+ self._init_logicals()
+ return self._ends
+
+ def _init_logicals(self):
+ """Should initialize _starts and _ends attributes"""
+ size = self.lines.length() + 1
+ self._starts = [None] * size
+ self._ends = [None] * size
+ for start, end in self._generate(self.lines):
+ self._starts[start] = True
+ self._ends[end] = True
+
+ def logical_line_in(self, line_number):
+ start = line_number
+ while start > 0 and not self.starts[start]:
+ start -= 1
+ if start == 0:
+ try:
+ start = self.starts.index(True, line_number)
+ except ValueError:
+ return (line_number, line_number)
+ return (start, self.ends.index(True, start))
+
+ def generate_starts(self, start_line=1, end_line=None):
+ if end_line is None:
+ end_line = self.lines.length()
+ for index in range(start_line, end_line):
+ if self.starts[index]:
+ yield index
+
+
+def get_block_start(lines, lineno, maximum_indents=80):
+ """Approximate block start"""
+ pattern = get_block_start_patterns()
+ for i in range(lineno, 0, -1):
+ match = pattern.search(lines.get_line(i))
+ if match is not None and \
+ count_line_indents(lines.get_line(i)) <= maximum_indents:
+ striped = match.string.lstrip()
+ # Maybe we're in a list comprehension or generator expression
+ if i > 1 and striped.startswith('if') or striped.startswith('for'):
+ bracs = 0
+ for j in range(i, min(i + 5, lines.length() + 1)):
+ for c in lines.get_line(j):
+ if c == '#':
+ break
+ if c in '[(':
+ bracs += 1
+ if c in ')]':
+ bracs -= 1
+ if bracs < 0:
+ break
+ if bracs < 0:
+ break
+ if bracs < 0:
+ continue
+ return i
+ return 1
+
+
+_block_start_pattern = None
+
+
+def get_block_start_patterns():
+ global _block_start_pattern
+ if not _block_start_pattern:
+ pattern = '^\\s*(((def|class|if|elif|except|for|while|with)\\s)|'\
+ '((try|else|finally|except)\\s*:))'
+ _block_start_pattern = re.compile(pattern, re.M)
+ return _block_start_pattern
+
+
+def count_line_indents(line):
+ indents = 0
+ for char in line:
+ if char == ' ':
+ indents += 1
+ elif char == '\t':
+ indents += 8
+ else:
+ return indents
+ return 0
+
+
+def get_string_pattern():
+ start = r'(\b[uU]?[rR]?)?'
+ longstr = r'%s"""(\\.|"(?!"")|\\\n|[^"\\])*"""' % start
+ shortstr = r'%s"(\\.|\\\n|[^"\\])*"' % start
+ return '|'.join([longstr, longstr.replace('"', "'"),
+ shortstr, shortstr.replace('"', "'")])
+
+
+def get_comment_pattern():
+ return r'#[^\n]*'
« no previous file with comments | « tools/telemetry/third_party/rope/rope/base/change.py ('k') | tools/telemetry/third_party/rope/rope/base/default_config.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698