Atom.jl icon indicating copy to clipboard operation
Atom.jl copied to clipboard

WIP: rename refactor

Open aviatesk opened this issue 6 years ago • 8 comments

Purpose

Implement rename refactoring command:

  • rename local binding
  • rename global bindings (module-based)

Approach

  • use MacroTools.jl for easy tokenizing and code replacing
  • for module-based global refactoring, CSTParser-based static file detection is used:
    • since our Revise-like module file detection will easily fail with the refactorings, which breaks precompilation cache
  • global refactoring are designed to work only when it's called on the definition place
  • first tries local refactoring, and then move to global refactoring if needed
  • rename refactoring on fields is all suppressed, since not every code is statically typed and so I think we would end up with lots of false positive

Task / Achievements

  • [x] local refactor
  • [x] module-based global refactor
  • graceful messages
    • [x] notice the need of refactoring in the parent module if the refactored binding is imported/overloaded
    • [x] report back error message
    • [x] notice refactored files
    • [x] show context
  • handle edge cases
    • [x] suppress keyword refactoring
    • [x] suppress field refactoring
    • [x] handle global refactoring that are not called at a definition place
    • [x] handle global refactoring on an unsaved editor
    • [x] handle global refactoring on files without write permission detected
    • [x] global refactoring with dot accessor like Atom.isdebugging(), Base.countlines(args...)
    • [x] global refactoring on macro
  • [ ] test

aviatesk avatar Oct 23 '19 11:10 aviatesk

xref: https://github.com/JunoLab/atom-julia-client/pull/641 xref: https://github.com/JunoLab/atom-ink/pull/236

aviatesk avatar Oct 23 '19 11:10 aviatesk

Here are some edge cases I've noticed but still I'm not sure about how to handle.

local refactoring

If we have the code like:

function outer()
  val = 10
  function inner(val)
    2 * val
  end
  return inner(val)
end

and we refactor on outer's val, the inner's vals are also refactored, even though we may want to only refactor outers. This is obviously because the inner function is included in the outer function scope, but I still not found any good way to handle this case with MacroTools's source walk. As an alternative, this case would be easily solved if we can directly manipulate CSTParser's expressions and then reconstruct the source appropriately. But I'm not sure even if it's possible.

global refactoring

If a module contains code like:

function fun() end

function other()
  fun = 10
end

and we rename fun to fun2, then the binding fun in other will also be renamed to fun2. This is because I intentionally looks just for identifier symbols (like fun), not for something more complicated like call sites (the code here), since in Julia every binding is an object, and we can have a code like map(fun, itr). Current behavior can be annoying, but at least doesn't break code. If we just looks for call sites, then we may have false negatives, which would break the code. So imho the current behavior may suffice.

aviatesk avatar Oct 23 '19 11:10 aviatesk

I agree the logic inside renamerefactor would look messy, and please feel free to ask me if you find anything weird -- I probably miss some edge cases.

https://github.com/JunoLab/Atom.jl/blob/86b3b2c795cda7a3accd99b7b72476e3bf36040a/src/refactor.jl#L18-L77

aviatesk avatar Oct 23 '19 11:10 aviatesk

Codecov Report

Merging #203 into master will decrease coverage by 6.47%. The diff coverage is 0%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #203      +/-   ##
==========================================
- Coverage   55.18%   48.71%   -6.48%     
==========================================
  Files          42       42              
  Lines        2399     2184     -215     
==========================================
- Hits         1324     1064     -260     
- Misses       1075     1120      +45
Impacted Files Coverage Δ
src/debugger/stepper.jl 1.64% <ø> (+0.21%) :arrow_up:
src/Atom.jl 100% <ø> (ø) :arrow_up:
src/refactor.jl 0% <0%> (ø)
src/docs.jl 0% <0%> (-75%) :arrow_down:
src/display/markdown.jl 33.33% <0%> (-33.34%) :arrow_down:
src/debugger/debugger.jl 28.57% <0%> (-11.43%) :arrow_down:
src/display/view.jl 60% <0%> (-5%) :arrow_down:
src/outline.jl 65.51% <0%> (-3.45%) :arrow_down:
src/static/static.jl 89.87% <0%> (-1.16%) :arrow_down:
src/debugger/breakpoints.jl 0% <0%> (ø) :arrow_up:
... and 15 more

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 97483de...8c5f32d. Read the comment docs.

codecov[bot] avatar Oct 23 '19 11:10 codecov[bot]

#208, #209, #210 need to be merged in order to finish up this PR (this only thing remain is to add tests)

aviatesk avatar Oct 29 '19 09:10 aviatesk

Both of these edge cases should be avoidable by checking each new local scope for assignments that shadow a global, right? We already should have the logic for that.

pfitzseb avatar Nov 08 '19 11:11 pfitzseb

We'll also need to think about proper UIs for all of this, especially the global refactors. I'd love to reuse find-and-replaces pane, but that doesn't seem to be possible.

So we could/should build one ourselves and ideally make it reuseable for something like this.

pfitzseb avatar Nov 08 '19 11:11 pfitzseb

We'll also need to think about proper UIs for all of this, especially the global refactors. I'd love to reuse find-and-replaces pane, but that doesn't seem to be possible.

So we could/should build one ourselves and ideally make it reuseable for something like this.

Yeah, would be great to have, and I'm willing to implement that. To me it seems better to start with implementing the "Find reference" feature, and then enable the global rename-refactor within that as an additional feature.

aviatesk avatar Nov 09 '19 09:11 aviatesk