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