fixsyntax.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import rope.base.codeanalyze
  2. import rope.base.evaluate
  3. from rope.base import worder, exceptions, utils
  4. from rope.base.codeanalyze import ArrayLinesAdapter, LogicalLineFinder
  5. class FixSyntax(object):
  6. def __init__(self, pycore, code, resource, maxfixes=1):
  7. self.pycore = pycore
  8. self.code = code
  9. self.resource = resource
  10. self.maxfixes = maxfixes
  11. @utils.saveit
  12. def get_pymodule(self):
  13. """Get a `PyModule`"""
  14. errors = []
  15. code = self.code
  16. tries = 0
  17. while True:
  18. try:
  19. if tries == 0 and self.resource is not None and \
  20. self.resource.read() == code:
  21. return self.pycore.resource_to_pyobject(self.resource,
  22. force_errors=True)
  23. return self.pycore.get_string_module(
  24. code, resource=self.resource, force_errors=True)
  25. except exceptions.ModuleSyntaxError, e:
  26. if tries < self.maxfixes:
  27. tries += 1
  28. self.commenter.comment(e.lineno)
  29. code = '\n'.join(self.commenter.lines)
  30. errors.append(' * line %s: %s ... fixed' % (e.lineno,
  31. e.message_))
  32. else:
  33. errors.append(' * line %s: %s ... raised!' % (e.lineno,
  34. e.message_))
  35. new_message = ('\nSyntax errors in file %s:\n' % e.filename) \
  36. + '\n'.join(errors)
  37. raise exceptions.ModuleSyntaxError(e.filename, e.lineno,
  38. new_message)
  39. @property
  40. @utils.saveit
  41. def commenter(self):
  42. return _Commenter(self.code)
  43. def pyname_at(self, offset):
  44. pymodule = self.get_pymodule()
  45. def old_pyname():
  46. word_finder = worder.Worder(self.code, True)
  47. expression = word_finder.get_primary_at(offset)
  48. expression = expression.replace('\\\n', ' ').replace('\n', ' ')
  49. lineno = self.code.count('\n', 0, offset)
  50. scope = pymodule.get_scope().get_inner_scope_for_line(lineno)
  51. return rope.base.evaluate.eval_str(scope, expression)
  52. new_code = pymodule.source_code
  53. def new_pyname():
  54. newoffset = self.commenter.transfered_offset(offset)
  55. return rope.base.evaluate.eval_location(pymodule, newoffset)
  56. if new_code.startswith(self.code[:offset + 1]):
  57. return new_pyname()
  58. result = old_pyname()
  59. if result is None:
  60. return new_pyname()
  61. return result
  62. class _Commenter(object):
  63. def __init__(self, code):
  64. self.code = code
  65. self.lines = self.code.split('\n')
  66. self.lines.append('\n')
  67. self.origs = range(len(self.lines) + 1)
  68. self.diffs = [0] * (len(self.lines) + 1)
  69. def comment(self, lineno):
  70. start = _logical_start(self.lines, lineno, check_prev=True) - 1
  71. # using self._get_stmt_end() instead of self._get_block_end()
  72. # to lower commented lines
  73. end = self._get_stmt_end(start)
  74. indents = _get_line_indents(self.lines[start])
  75. if 0 < start:
  76. last_lineno = self._last_non_blank(start - 1)
  77. last_line = self.lines[last_lineno]
  78. if last_line.rstrip().endswith(':'):
  79. indents = _get_line_indents(last_line) + 4
  80. self._set(start, ' ' * indents + 'pass')
  81. for line in range(start + 1, end + 1):
  82. self._set(line, self.lines[start])
  83. self._fix_incomplete_try_blocks(lineno, indents)
  84. def transfered_offset(self, offset):
  85. lineno = self.code.count('\n', 0, offset)
  86. diff = sum(self.diffs[:lineno])
  87. return offset + diff
  88. def _last_non_blank(self, start):
  89. while start > 0 and self.lines[start].strip() == '':
  90. start -= 1
  91. return start
  92. def _get_block_end(self, lineno):
  93. end_line = lineno
  94. base_indents = _get_line_indents(self.lines[lineno])
  95. for i in range(lineno + 1, len(self.lines)):
  96. if _get_line_indents(self.lines[i]) >= base_indents:
  97. end_line = i
  98. else:
  99. break
  100. return end_line
  101. def _get_stmt_end(self, lineno):
  102. end_line = lineno
  103. base_indents = _get_line_indents(self.lines[lineno])
  104. for i in range(lineno + 1, len(self.lines)):
  105. if _get_line_indents(self.lines[i]) <= base_indents:
  106. return i - 1
  107. return lineno
  108. def _fix_incomplete_try_blocks(self, lineno, indents):
  109. block_start = lineno
  110. last_indents = current_indents = indents
  111. while block_start > 0:
  112. block_start = rope.base.codeanalyze.get_block_start(
  113. ArrayLinesAdapter(self.lines), block_start) - 1
  114. if self.lines[block_start].strip().startswith('try:'):
  115. indents = _get_line_indents(self.lines[block_start])
  116. if indents > last_indents:
  117. continue
  118. last_indents = indents
  119. block_end = self._find_matching_deindent(block_start)
  120. line = self.lines[block_end].strip()
  121. if not (line.startswith('finally:') or
  122. line.startswith('except ') or
  123. line.startswith('except:')):
  124. self._insert(block_end, ' ' * indents + 'finally:')
  125. self._insert(block_end + 1, ' ' * indents + ' pass')
  126. def _find_matching_deindent(self, line_number):
  127. indents = _get_line_indents(self.lines[line_number])
  128. current_line = line_number + 1
  129. while current_line < len(self.lines):
  130. line = self.lines[current_line]
  131. if not line.strip().startswith('#') and not line.strip() == '':
  132. # HACK: We should have used logical lines here
  133. if _get_line_indents(self.lines[current_line]) <= indents:
  134. return current_line
  135. current_line += 1
  136. return len(self.lines) - 1
  137. def _set(self, lineno, line):
  138. self.diffs[self.origs[lineno]] += len(line) - len(self.lines[lineno])
  139. self.lines[lineno] = line
  140. def _insert(self, lineno, line):
  141. self.diffs[self.origs[lineno]] += len(line) + 1
  142. self.origs.insert(lineno, self.origs[lineno])
  143. self.lines.insert(lineno, line)
  144. def _logical_start(lines, lineno, check_prev=False):
  145. logical_finder = LogicalLineFinder(ArrayLinesAdapter(lines))
  146. if check_prev:
  147. prev = lineno - 1
  148. while prev > 0:
  149. start, end = logical_finder.logical_line_in(prev)
  150. if end is None or start <= lineno < end:
  151. return start
  152. if start <= prev:
  153. break
  154. prev -= 1
  155. return logical_finder.logical_line_in(lineno)[0]
  156. def _get_line_indents(line):
  157. return rope.base.codeanalyze.count_line_indents(line)