move.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. """A module containing classes for move refactoring
  2. `create_move()` is a factory for creating move refactoring objects
  3. based on inputs.
  4. """
  5. from rope.base import pyobjects, codeanalyze, exceptions, pynames, taskhandle, evaluate, worder
  6. from rope.base.change import ChangeSet, ChangeContents, MoveResource
  7. from rope.refactor import importutils, rename, occurrences, sourceutils, functionutils
  8. def create_move(project, resource, offset=None):
  9. """A factory for creating Move objects
  10. Based on `resource` and `offset`, return one of `MoveModule`,
  11. `MoveGlobal` or `MoveMethod` for performing move refactoring.
  12. """
  13. if offset is None:
  14. return MoveModule(project, resource)
  15. this_pymodule = project.pycore.resource_to_pyobject(resource)
  16. pyname = evaluate.eval_location(this_pymodule, offset)
  17. if pyname is None:
  18. raise exceptions.RefactoringError(
  19. 'Move only works on classes, functions, modules and methods.')
  20. pyobject = pyname.get_object()
  21. if isinstance(pyobject, pyobjects.PyModule) or \
  22. isinstance(pyobject, pyobjects.PyPackage):
  23. return MoveModule(project, pyobject.get_resource())
  24. if isinstance(pyobject, pyobjects.PyFunction) and \
  25. isinstance(pyobject.parent, pyobjects.PyClass):
  26. return MoveMethod(project, resource, offset)
  27. if isinstance(pyobject, pyobjects.PyDefinedObject) and \
  28. isinstance(pyobject.parent, pyobjects.PyModule):
  29. return MoveGlobal(project, resource, offset)
  30. raise exceptions.RefactoringError(
  31. 'Move only works on global classes/functions, modules and methods.')
  32. class MoveMethod(object):
  33. """For moving methods
  34. It makes a new method in the destination class and changes
  35. the body of the old method to call the new method. You can
  36. inline the old method to change all of its occurrences.
  37. """
  38. def __init__(self, project, resource, offset):
  39. self.project = project
  40. self.pycore = project.pycore
  41. this_pymodule = self.pycore.resource_to_pyobject(resource)
  42. pyname = evaluate.eval_location(this_pymodule, offset)
  43. self.method_name = worder.get_name_at(resource, offset)
  44. self.pyfunction = pyname.get_object()
  45. if self.pyfunction.get_kind() != 'method':
  46. raise exceptions.RefactoringError('Only normal methods'
  47. ' can be moved.')
  48. def get_changes(self, dest_attr, new_name=None, resources=None,
  49. task_handle=taskhandle.NullTaskHandle()):
  50. """Return the changes needed for this refactoring
  51. Parameters:
  52. - `dest_attr`: the name of the destination attribute
  53. - `new_name`: the name of the new method; if `None` uses
  54. the old name
  55. - `resources` can be a list of `rope.base.resources.File`\s to
  56. apply this refactoring on. If `None`, the restructuring
  57. will be applied to all python files.
  58. """
  59. changes = ChangeSet('Moving method <%s>' % self.method_name)
  60. if resources is None:
  61. resources = self.pycore.get_python_files()
  62. if new_name is None:
  63. new_name = self.get_method_name()
  64. resource1, start1, end1, new_content1 = \
  65. self._get_changes_made_by_old_class(dest_attr, new_name)
  66. collector1 = codeanalyze.ChangeCollector(resource1.read())
  67. collector1.add_change(start1, end1, new_content1)
  68. resource2, start2, end2, new_content2 = \
  69. self._get_changes_made_by_new_class(dest_attr, new_name)
  70. if resource1 == resource2:
  71. collector1.add_change(start2, end2, new_content2)
  72. else:
  73. collector2 = codeanalyze.ChangeCollector(resource2.read())
  74. collector2.add_change(start2, end2, new_content2)
  75. result = collector2.get_changed()
  76. import_tools = importutils.ImportTools(self.pycore)
  77. new_imports = self._get_used_imports(import_tools)
  78. if new_imports:
  79. goal_pymodule = self.pycore.get_string_module(result,
  80. resource2)
  81. result = _add_imports_to_module(
  82. import_tools, goal_pymodule, new_imports)
  83. if resource2 in resources:
  84. changes.add_change(ChangeContents(resource2, result))
  85. if resource1 in resources:
  86. changes.add_change(ChangeContents(resource1,
  87. collector1.get_changed()))
  88. return changes
  89. def get_method_name(self):
  90. return self.method_name
  91. def _get_used_imports(self, import_tools):
  92. return importutils.get_imports(self.pycore, self.pyfunction)
  93. def _get_changes_made_by_old_class(self, dest_attr, new_name):
  94. pymodule = self.pyfunction.get_module()
  95. indents = self._get_scope_indents(self.pyfunction)
  96. body = 'return self.%s.%s(%s)\n' % (dest_attr, new_name,
  97. self._get_passed_arguments_string())
  98. region = sourceutils.get_body_region(self.pyfunction)
  99. return (pymodule.get_resource(), region[0], region[1],
  100. sourceutils.fix_indentation(body, indents))
  101. def _get_scope_indents(self, pyobject):
  102. pymodule = pyobject.get_module()
  103. return sourceutils.get_indents(
  104. pymodule.lines, pyobject.get_scope().get_start()) + \
  105. sourceutils.get_indent(self.pycore)
  106. def _get_changes_made_by_new_class(self, dest_attr, new_name):
  107. old_pyclass = self.pyfunction.parent
  108. if dest_attr not in old_pyclass:
  109. raise exceptions.RefactoringError(
  110. 'Destination attribute <%s> not found' % dest_attr)
  111. pyclass = old_pyclass[dest_attr].get_object().get_type()
  112. if not isinstance(pyclass, pyobjects.PyClass):
  113. raise exceptions.RefactoringError(
  114. 'Unknown class type for attribute <%s>' % dest_attr)
  115. pymodule = pyclass.get_module()
  116. resource = pyclass.get_module().get_resource()
  117. start, end = sourceutils.get_body_region(pyclass)
  118. pre_blanks = '\n'
  119. if pymodule.source_code[start:end].strip() != 'pass':
  120. pre_blanks = '\n\n'
  121. start = end
  122. indents = self._get_scope_indents(pyclass)
  123. body = pre_blanks + sourceutils.fix_indentation(
  124. self.get_new_method(new_name), indents)
  125. return resource, start, end, body
  126. def get_new_method(self, name):
  127. return '%s\n%s' % (
  128. self._get_new_header(name),
  129. sourceutils.fix_indentation(self._get_body(),
  130. sourceutils.get_indent(self.pycore)))
  131. def _get_unchanged_body(self):
  132. return sourceutils.get_body(self.pyfunction)
  133. def _get_body(self, host='host'):
  134. self_name = self._get_self_name()
  135. body = self_name + ' = None\n' + self._get_unchanged_body()
  136. pymodule = self.pycore.get_string_module(body)
  137. finder = occurrences.create_finder(
  138. self.pycore, self_name, pymodule[self_name])
  139. result = rename.rename_in_module(finder, host, pymodule=pymodule)
  140. if result is None:
  141. result = body
  142. return result[result.index('\n') + 1:]
  143. def _get_self_name(self):
  144. return self.pyfunction.get_param_names()[0]
  145. def _get_new_header(self, name):
  146. header = 'def %s(self' % name
  147. if self._is_host_used():
  148. header += ', host'
  149. definition_info = functionutils.DefinitionInfo.read(self.pyfunction)
  150. others = definition_info.arguments_to_string(1)
  151. if others:
  152. header += ', ' + others
  153. return header + '):'
  154. def _get_passed_arguments_string(self):
  155. result = ''
  156. if self._is_host_used():
  157. result = 'self'
  158. definition_info = functionutils.DefinitionInfo.read(self.pyfunction)
  159. others = definition_info.arguments_to_string(1)
  160. if others:
  161. if result:
  162. result += ', '
  163. result += others
  164. return result
  165. def _is_host_used(self):
  166. return self._get_body('__old_self') != self._get_unchanged_body()
  167. class MoveGlobal(object):
  168. """For moving global function and classes"""
  169. def __init__(self, project, resource, offset):
  170. self.pycore = project.pycore
  171. this_pymodule = self.pycore.resource_to_pyobject(resource)
  172. self.old_pyname = evaluate.eval_location(this_pymodule, offset)
  173. self.old_name = self.old_pyname.get_object().get_name()
  174. pymodule = self.old_pyname.get_object().get_module()
  175. self.source = pymodule.get_resource()
  176. self.tools = _MoveTools(self.pycore, self.source,
  177. self.old_pyname, self.old_name)
  178. self.import_tools = self.tools.import_tools
  179. self._check_exceptional_conditions()
  180. def _check_exceptional_conditions(self):
  181. if self.old_pyname is None or \
  182. not isinstance(self.old_pyname.get_object(), pyobjects.PyDefinedObject):
  183. raise exceptions.RefactoringError(
  184. 'Move refactoring should be performed on a class/function.')
  185. moving_pyobject = self.old_pyname.get_object()
  186. if not self._is_global(moving_pyobject):
  187. raise exceptions.RefactoringError(
  188. 'Move refactoring should be performed on a global class/function.')
  189. def _is_global(self, pyobject):
  190. return pyobject.get_scope().parent == pyobject.get_module().get_scope()
  191. def get_changes(self, dest, resources=None,
  192. task_handle=taskhandle.NullTaskHandle()):
  193. if resources is None:
  194. resources = self.pycore.get_python_files()
  195. if dest is None or not dest.exists():
  196. raise exceptions.RefactoringError(
  197. 'Move destination does not exist.')
  198. if dest.is_folder() and dest.has_child('__init__.py'):
  199. dest = dest.get_child('__init__.py')
  200. if dest.is_folder():
  201. raise exceptions.RefactoringError(
  202. 'Move destination for non-modules should not be folders.')
  203. if self.source == dest:
  204. raise exceptions.RefactoringError(
  205. 'Moving global elements to the same module.')
  206. return self._calculate_changes(dest, resources, task_handle)
  207. def _calculate_changes(self, dest, resources, task_handle):
  208. changes = ChangeSet('Moving global <%s>' % self.old_name)
  209. job_set = task_handle.create_jobset('Collecting Changes',
  210. len(resources))
  211. for file_ in resources:
  212. job_set.started_job(file_.path)
  213. if file_ == self.source:
  214. changes.add_change(self._source_module_changes(dest))
  215. elif file_ == dest:
  216. changes.add_change(self._dest_module_changes(dest))
  217. elif self.tools.occurs_in_module(resource=file_):
  218. pymodule = self.pycore.resource_to_pyobject(file_)
  219. # Changing occurrences
  220. placeholder = '__rope_renaming_%s_' % self.old_name
  221. source = self.tools.rename_in_module(placeholder,
  222. resource=file_)
  223. should_import = source is not None
  224. # Removing out of date imports
  225. pymodule = self.tools.new_pymodule(pymodule, source)
  226. source = self.tools.remove_old_imports(pymodule)
  227. # Adding new import
  228. if should_import:
  229. pymodule = self.tools.new_pymodule(pymodule, source)
  230. source, imported = importutils.add_import(
  231. self.pycore, pymodule, self._new_modname(dest), self.old_name)
  232. source = source.replace(placeholder, imported)
  233. source = self.tools.new_source(pymodule, source)
  234. if source != file_.read():
  235. changes.add_change(ChangeContents(file_, source))
  236. job_set.finished_job()
  237. return changes
  238. def _source_module_changes(self, dest):
  239. placeholder = '__rope_moving_%s_' % self.old_name
  240. handle = _ChangeMoveOccurrencesHandle(placeholder)
  241. occurrence_finder = occurrences.create_finder(
  242. self.pycore, self.old_name, self.old_pyname)
  243. start, end = self._get_moving_region()
  244. renamer = ModuleSkipRenamer(occurrence_finder, self.source,
  245. handle, start, end)
  246. source = renamer.get_changed_module()
  247. if handle.occurred:
  248. pymodule = self.pycore.get_string_module(source, self.source)
  249. # Adding new import
  250. source, imported = importutils.add_import(
  251. self.pycore, pymodule, self._new_modname(dest), self.old_name)
  252. source = source.replace(placeholder, imported)
  253. return ChangeContents(self.source, source)
  254. def _new_modname(self, dest):
  255. return self.pycore.modname(dest)
  256. def _dest_module_changes(self, dest):
  257. # Changing occurrences
  258. pymodule = self.pycore.resource_to_pyobject(dest)
  259. source = self.tools.rename_in_module(self.old_name, pymodule)
  260. pymodule = self.tools.new_pymodule(pymodule, source)
  261. moving, imports = self._get_moving_element_with_imports()
  262. source = self.tools.remove_old_imports(pymodule)
  263. pymodule = self.tools.new_pymodule(pymodule, source)
  264. pymodule, has_changed = self._add_imports2(pymodule, imports)
  265. module_with_imports = self.import_tools.module_imports(pymodule)
  266. source = pymodule.source_code
  267. lineno = 0
  268. if module_with_imports.imports:
  269. lineno = module_with_imports.imports[-1].end_line - 1
  270. else:
  271. while lineno < pymodule.lines.length() and \
  272. pymodule.lines.get_line(lineno + 1).lstrip().startswith('#'):
  273. lineno += 1
  274. if lineno > 0:
  275. cut = pymodule.lines.get_line_end(lineno) + 1
  276. result = source[:cut] + '\n\n' + moving + source[cut:]
  277. else:
  278. result = moving + source
  279. # Organizing imports
  280. source = result
  281. pymodule = self.pycore.get_string_module(source, dest)
  282. source = self.import_tools.organize_imports(pymodule, sort=False,
  283. unused=False)
  284. return ChangeContents(dest, source)
  285. def _get_moving_element_with_imports(self):
  286. return moving_code_with_imports(
  287. self.pycore, self.source, self._get_moving_element())
  288. def _get_module_with_imports(self, source_code, resource):
  289. pymodule = self.pycore.get_string_module(source_code, resource)
  290. return self.import_tools.module_imports(pymodule)
  291. def _get_moving_element(self):
  292. start, end = self._get_moving_region()
  293. moving = self.source.read()[start:end]
  294. return moving.rstrip() + '\n'
  295. def _get_moving_region(self):
  296. pymodule = self.pycore.resource_to_pyobject(self.source)
  297. lines = pymodule.lines
  298. scope = self.old_pyname.get_object().get_scope()
  299. start = lines.get_line_start(scope.get_start())
  300. end_line = scope.get_end()
  301. while end_line < lines.length() and \
  302. lines.get_line(end_line + 1).strip() == '':
  303. end_line += 1
  304. end = min(lines.get_line_end(end_line) + 1, len(pymodule.source_code))
  305. return start, end
  306. def _add_imports2(self, pymodule, new_imports):
  307. source = self.tools.add_imports(pymodule, new_imports)
  308. if source is None:
  309. return pymodule, False
  310. else:
  311. resource = pymodule.get_resource()
  312. pymodule = self.pycore.get_string_module(source, resource)
  313. return pymodule, True
  314. class MoveModule(object):
  315. """For moving modules and packages"""
  316. def __init__(self, project, resource):
  317. self.project = project
  318. self.pycore = project.pycore
  319. if not resource.is_folder() and resource.name == '__init__.py':
  320. resource = resource.parent
  321. if resource.is_folder() and not resource.has_child('__init__.py'):
  322. raise exceptions.RefactoringError(
  323. 'Cannot move non-package folder.')
  324. dummy_pymodule = self.pycore.get_string_module('')
  325. self.old_pyname = pynames.ImportedModule(dummy_pymodule,
  326. resource=resource)
  327. self.source = self.old_pyname.get_object().get_resource()
  328. if self.source.is_folder():
  329. self.old_name = self.source.name
  330. else:
  331. self.old_name = self.source.name[:-3]
  332. self.tools = _MoveTools(self.pycore, self.source,
  333. self.old_pyname, self.old_name)
  334. self.import_tools = self.tools.import_tools
  335. def get_changes(self, dest, resources=None,
  336. task_handle=taskhandle.NullTaskHandle()):
  337. moving_pyobject = self.old_pyname.get_object()
  338. if resources is None:
  339. resources = self.pycore.get_python_files()
  340. if dest is None or not dest.is_folder():
  341. raise exceptions.RefactoringError(
  342. 'Move destination for modules should be packages.')
  343. return self._calculate_changes(dest, resources, task_handle)
  344. def _calculate_changes(self, dest, resources, task_handle):
  345. changes = ChangeSet('Moving module <%s>' % self.old_name)
  346. job_set = task_handle.create_jobset('Collecting changes',
  347. len(resources))
  348. for module in resources:
  349. job_set.started_job(module.path)
  350. if module == self.source:
  351. self._change_moving_module(changes, dest)
  352. else:
  353. source = self._change_occurrences_in_module(dest,
  354. resource=module)
  355. if source is not None:
  356. changes.add_change(ChangeContents(module, source))
  357. job_set.finished_job()
  358. if self.project == self.source.project:
  359. changes.add_change(MoveResource(self.source, dest.path))
  360. return changes
  361. def _new_modname(self, dest):
  362. destname = self.pycore.modname(dest)
  363. if destname:
  364. return destname + '.' + self.old_name
  365. return self.old_name
  366. def _new_import(self, dest):
  367. return importutils.NormalImport([(self._new_modname(dest), None)])
  368. def _change_moving_module(self, changes, dest):
  369. if not self.source.is_folder():
  370. pymodule = self.pycore.resource_to_pyobject(self.source)
  371. source = self.import_tools.relatives_to_absolutes(pymodule)
  372. pymodule = self.tools.new_pymodule(pymodule, source)
  373. source = self._change_occurrences_in_module(dest, pymodule)
  374. source = self.tools.new_source(pymodule, source)
  375. if source != self.source.read():
  376. changes.add_change(ChangeContents(self.source, source))
  377. def _change_occurrences_in_module(self, dest, pymodule=None,
  378. resource=None):
  379. if not self.tools.occurs_in_module(pymodule=pymodule,
  380. resource=resource):
  381. return
  382. if pymodule is None:
  383. pymodule = self.pycore.resource_to_pyobject(resource)
  384. new_name = self._new_modname(dest)
  385. new_import = self._new_import(dest)
  386. source = self.tools.rename_in_module(
  387. new_name, imports=True, pymodule=pymodule, resource=resource)
  388. should_import = self.tools.occurs_in_module(
  389. pymodule=pymodule, resource=resource, imports=False)
  390. pymodule = self.tools.new_pymodule(pymodule, source)
  391. source = self.tools.remove_old_imports(pymodule)
  392. if should_import:
  393. pymodule = self.tools.new_pymodule(pymodule, source)
  394. source = self.tools.add_imports(pymodule, [new_import])
  395. source = self.tools.new_source(pymodule, source)
  396. if source != pymodule.resource.read():
  397. return source
  398. class _ChangeMoveOccurrencesHandle(object):
  399. def __init__(self, new_name):
  400. self.new_name = new_name
  401. self.occurred = False
  402. def occurred_inside_skip(self, change_collector, occurrence):
  403. pass
  404. def occurred_outside_skip(self, change_collector, occurrence):
  405. start, end = occurrence.get_primary_range()
  406. change_collector.add_change(start, end, self.new_name)
  407. self.occurred = True
  408. class _MoveTools(object):
  409. def __init__(self, pycore, source, pyname, old_name):
  410. self.pycore = pycore
  411. self.source = source
  412. self.old_pyname = pyname
  413. self.old_name = old_name
  414. self.import_tools = importutils.ImportTools(self.pycore)
  415. def remove_old_imports(self, pymodule):
  416. old_source = pymodule.source_code
  417. module_with_imports = self.import_tools.module_imports(pymodule)
  418. class CanSelect(object):
  419. changed = False
  420. old_name = self.old_name
  421. old_pyname = self.old_pyname
  422. def __call__(self, name):
  423. try:
  424. if name == self.old_name and \
  425. pymodule[name].get_object() == \
  426. self.old_pyname.get_object():
  427. self.changed = True
  428. return False
  429. except exceptions.AttributeNotFoundError:
  430. pass
  431. return True
  432. can_select = CanSelect()
  433. module_with_imports.filter_names(can_select)
  434. new_source = module_with_imports.get_changed_source()
  435. if old_source != new_source:
  436. return new_source
  437. def rename_in_module(self, new_name, pymodule=None,
  438. imports=False, resource=None):
  439. occurrence_finder = self._create_finder(imports)
  440. source = rename.rename_in_module(
  441. occurrence_finder, new_name, replace_primary=True,
  442. pymodule=pymodule, resource=resource)
  443. return source
  444. def occurs_in_module(self, pymodule=None, resource=None, imports=True):
  445. finder = self._create_finder(imports)
  446. for occurrence in finder.find_occurrences(pymodule=pymodule,
  447. resource=resource):
  448. return True
  449. return False
  450. def _create_finder(self, imports):
  451. return occurrences.create_finder(self.pycore, self.old_name,
  452. self.old_pyname, imports=imports)
  453. def new_pymodule(self, pymodule, source):
  454. if source is not None:
  455. return self.pycore.get_string_module(
  456. source, pymodule.get_resource())
  457. return pymodule
  458. def new_source(self, pymodule, source):
  459. if source is None:
  460. return pymodule.source_code
  461. return source
  462. def add_imports(self, pymodule, new_imports):
  463. return _add_imports_to_module(self.import_tools, pymodule, new_imports)
  464. def _add_imports_to_module(import_tools, pymodule, new_imports):
  465. module_with_imports = import_tools.module_imports(pymodule)
  466. for new_import in new_imports:
  467. module_with_imports.add_import(new_import)
  468. return module_with_imports.get_changed_source()
  469. def moving_code_with_imports(pycore, resource, source):
  470. import_tools = importutils.ImportTools(pycore)
  471. pymodule = pycore.get_string_module(source, resource)
  472. origin = pycore.resource_to_pyobject(resource)
  473. imports = []
  474. for stmt in import_tools.module_imports(origin).imports:
  475. imports.append(stmt.import_info)
  476. back_names = []
  477. for name in origin:
  478. if name not in pymodule:
  479. back_names.append(name)
  480. imports.append(import_tools.get_from_import(resource, back_names))
  481. source = _add_imports_to_module(import_tools, pymodule, imports)
  482. pymodule = pycore.get_string_module(source, resource)
  483. source = import_tools.relatives_to_absolutes(pymodule)
  484. pymodule = pycore.get_string_module(source, resource)
  485. source = import_tools.organize_imports(pymodule, selfs=False)
  486. pymodule = pycore.get_string_module(source, resource)
  487. # extracting imports after changes
  488. module_imports = import_tools.module_imports(pymodule)
  489. imports = [import_stmt.import_info
  490. for import_stmt in module_imports.imports]
  491. start = 1
  492. if module_imports.imports:
  493. start = module_imports.imports[-1].end_line
  494. lines = codeanalyze.SourceLinesAdapter(source)
  495. while start < lines.length() and not lines.get_line(start).strip():
  496. start += 1
  497. moving = source[lines.get_line_start(start):]
  498. return moving, imports
  499. class ModuleSkipRenamerHandle(object):
  500. def occurred_outside_skip(self, change_collector, occurrence):
  501. pass
  502. def occurred_inside_skip(self, change_collector, occurrence):
  503. pass
  504. class ModuleSkipRenamer(object):
  505. """Rename occurrences in a module
  506. This class can be used when you want to treat a region in a file
  507. separately from other parts when renaming.
  508. """
  509. def __init__(self, occurrence_finder, resource, handle=None,
  510. skip_start=0, skip_end=0, replacement=''):
  511. """Constructor
  512. if replacement is `None` the region is not changed. Otherwise
  513. it is replaced with `replacement`.
  514. """
  515. self.occurrence_finder = occurrence_finder
  516. self.resource = resource
  517. self.skip_start = skip_start
  518. self.skip_end = skip_end
  519. self.replacement = replacement
  520. self.handle = handle
  521. if self.handle is None:
  522. self.handle = ModuleSkipHandle()
  523. def get_changed_module(self):
  524. source = self.resource.read()
  525. change_collector = codeanalyze.ChangeCollector(source)
  526. if self.replacement is not None:
  527. change_collector.add_change(self.skip_start, self.skip_end,
  528. self.replacement)
  529. for occurrence in self.occurrence_finder.find_occurrences(self.resource):
  530. start, end = occurrence.get_primary_range()
  531. if self.skip_start <= start < self.skip_end:
  532. self.handle.occurred_inside_skip(change_collector, occurrence)
  533. else:
  534. self.handle.occurred_outside_skip(change_collector, occurrence)
  535. result = change_collector.get_changed()
  536. if result is not None and result != source:
  537. return result