From 65826db5f0a8c3f5817ef62dd66dcac6f66782ec Mon Sep 17 00:00:00 2001 From: aviatesk Date: Sat, 12 Oct 2019 00:44:55 +0900 Subject: [PATCH 01/19] local refactoring --- src/Atom.jl | 1 + src/refactor.jl | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/refactor.jl diff --git a/src/Atom.jl b/src/Atom.jl index 6cf4c10b..02d69595 100644 --- a/src/Atom.jl +++ b/src/Atom.jl @@ -51,6 +51,7 @@ include("outline.jl") include("completions.jl") include("goto.jl") include("datatip.jl") +include("refactor.jl") include("misc.jl") include("formatter.jl") include("frontend.jl") diff --git a/src/refactor.jl b/src/refactor.jl new file mode 100644 index 00000000..a9521b44 --- /dev/null +++ b/src/refactor.jl @@ -0,0 +1,84 @@ +handle("refactor") do data + @destruct [ + old, + new, + path, + # local context + column || 1, + row || 1, + startRow || 0, + context || "", + # module context + mod || "Main", + ] = data + refactor(old, new, path, column, row, startRow, context, mod) +end + +function refactor( + old, new, path, + column = 1, row = 1, startrow = 0, context = "", + mod = "Main", +) + expr = CSTParser.parse(context, true) + bind = CSTParser.bindingof(expr) + + # local refactor only if `old` is really a local binding + if bind === nothing || old != bind.name + try + refactored = localrefactor(old, new, path, column, row, startrow, context) + isempty(refactored) || return Dict(:text => refactored) + catch err + @error err + end + end + + try + refactored = localrefactor(old, new, path, column, row, startrow, context) + isempty(refactored) || return Dict(:text => refactored) + catch err + @error err + end + + # try + # globalrefactor(old, new, path, mod) && return nothing + # catch err + # @error err + # end + + return Dict(:error => true, :msg => "no refactor") +end + +function localrefactor(old, new, path, column, row, startrow, context) + old = first(split(old, '.')) # ignore dot accessors + position = row - startrow + + return if old ∈ map(l -> l[:name], locals(context, position, column)) + oldsym = Symbol(old) + quote + MacroTools.textwalk($context) do ex + @capture(ex, $oldsym) ? Symbol($new) : ex + end + end |> eval + else + "" + end +end + +# mod = getmodule(m) +# parentfile, modulefiles = modulefiles(mod) +# sourcewalk("../packages/Atom/src/goto.jl") do x +# isshort = MacroTools.isshortdef(x) +# ex = MacroTools.shortdef(x) +# if @capture(ex, locals(args__) = body_) +# return if isshort +# :(newlocals(args...) = body) +# else +# :(function newlocals(args...) +# body +# end) +# end +# end +# return x +# isstruct = MacroTools.isstructdef(x) +# if @capture(x, struct ) +# end From e2da219733274585487daf95d530d92e398b752e Mon Sep 17 00:00:00 2001 From: aviatesk Date: Tue, 22 Oct 2019 01:53:24 +0900 Subject: [PATCH 02/19] global refactoring --- src/refactor.jl | 99 ++++++++++++++++++++++++++++++------------------- src/utils.jl | 28 ++++++++++++++ 2 files changed, 88 insertions(+), 39 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index a9521b44..5fde8f54 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -19,10 +19,8 @@ function refactor( column = 1, row = 1, startrow = 0, context = "", mod = "Main", ) - expr = CSTParser.parse(context, true) - bind = CSTParser.bindingof(expr) - # local refactor only if `old` is really a local binding + bind = CSTParser.bindingof(CSTParser.parse(context)) if bind === nothing || old != bind.name try refactored = localrefactor(old, new, path, column, row, startrow, context) @@ -33,52 +31,75 @@ function refactor( end try - refactored = localrefactor(old, new, path, column, row, startrow, context) - isempty(refactored) || return Dict(:text => refactored) + mod = getmodule(mod) + val = getfield′(mod, old) + result = globalrefactor(old, new, mod, val) + return result isa String ? Dict(:error => result) : Dict(:error => false) catch err @error err end - # try - # globalrefactor(old, new, path, mod) && return nothing - # catch err - # @error err - # end - - return Dict(:error => true, :msg => "no refactor") + return Dict(:error => "Rename refactoring failed: `$old` -> `$new`") end -function localrefactor(old, new, path, column, row, startrow, context) - old = first(split(old, '.')) # ignore dot accessors - position = row - startrow +# local refactor +# -------------- - return if old ∈ map(l -> l[:name], locals(context, position, column)) +function localrefactor(old, new, path, column, row, startrow, context) + return if old ∈ map(l -> l[:name], locals(context, row - startrow, column)) oldsym = Symbol(old) - quote - MacroTools.textwalk($context) do ex - @capture(ex, $oldsym) ? Symbol($new) : ex - end - end |> eval + newsym = Symbol(new) + MacroTools.textwalk(context) do sym + sym === oldsym ? newsym : sym + end else "" end end -# mod = getmodule(m) -# parentfile, modulefiles = modulefiles(mod) -# sourcewalk("../packages/Atom/src/goto.jl") do x -# isshort = MacroTools.isshortdef(x) -# ex = MacroTools.shortdef(x) -# if @capture(ex, locals(args__) = body_) -# return if isshort -# :(newlocals(args...) = body) -# else -# :(function newlocals(args...) -# body -# end) -# end -# end -# return x -# isstruct = MacroTools.isstructdef(x) -# if @capture(x, struct ) -# end +# global refactor +# --------------- + +globalrefactor(old, new, mod, @nospecialize(val)) = _globalrefactor(old, new, mod) # general case +function globalrefactor(old, new, mod, val::Undefined) + Symbol(old) in keys(Docs.keywords) ? + "Keywords can't be renamed: `$old`" : + _globalrefactor(old, new, mod) +end + +function _globalrefactor(old, new, mod) + entrypath, line = moduledefinition(mod) + files = modulefiles(entrypath) + + with_logger(JunoProgressLogger()) do + refactorfiles(old, new, mod, files) + end +end + +function refactorfiles(old, new, obj, files) + id = "global_rename_refactor_progress" + @info "Start global rename refactoring" progress=0 _id=id + + oldsym = Symbol(old) + newsym = Symbol(new) + modulesyms = Set(Symbol.(Base.loaded_modules_array())) + total = length(files) + + for (i, file) ∈ enumerate(files) + @info "Refactoring: $file ($i / $total)" progress=i/total _id=id + MacroTools.sourcewalk(file) do ex + return if ex === oldsym + newsym + elseif @capture(ex, obj_.$oldsym) + if obj in modulesyms + @warn "Came across a global rename refactoring across different modules: `$obj.$old` -> `$obj.$new`" + end + Expr(:., obj, newsym) + else + ex + end + end + end + + @info "Finish global rename refactoring" progress=1 _id=id +end diff --git a/src/utils.jl b/src/utils.jl index f4e6613e..a94accc3 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -204,6 +204,34 @@ function modulefiles(mod::Module) return fixpath(parentfile), [fixpath(mf[2]) for mf in included_files] end +""" + included_files = modulefiles(entrypath::String)::Vector{String} + +Return all the file paths that can be reached via [`include`](@ref) calls. +Note this function currently only looks for static _toplevel_ calls. +""" +function modulefiles(entrypath::String, files = []) + push!(files, entrypath) + + text = read(entrypath, String) + parsed = CSTParser.parse(text, true) + items = toplevelitems(parsed, text) + + for item in items + if item isa ToplevelCall + expr = item.expr + if isinclude(expr) + nextfile = expr.args[3].val + nextentrypath = joinpath(dirname(entrypath), nextfile) + isfile(nextentrypath) || continue + modulefiles(nextentrypath, files) + end + end + end + + return files +end + function moduledefinition(mod::Module) # NOTE: added when adapted evalmethod = first(methods(getfield(mod, :eval))) parentfile = String(evalmethod.file) From abbb84c8f9cd1df80894d26e63336aea1b6fbbc3 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Tue, 22 Oct 2019 05:40:22 +0900 Subject: [PATCH 03/19] better messaging & suppress field renaming --- src/refactor.jl | 55 +++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index 5fde8f54..bcdf86bb 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -1,6 +1,7 @@ -handle("refactor") do data +handle("renamerefactor") do data @destruct [ old, + full, new, path, # local context @@ -11,35 +12,48 @@ handle("refactor") do data # module context mod || "Main", ] = data - refactor(old, new, path, column, row, startRow, context, mod) + renamerefactor(old, full, new, path, column, row, startRow, context, mod) end -function refactor( - old, new, path, +function renamerefactor( + old, full, new, path, column = 1, row = 1, startrow = 0, context = "", mod = "Main", ) + mod = getmodule(mod) + + # catch field renaming + if (obj = first(split(full, '.'))) != old && !isa(getfield′(mod, obj), Module) + return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") + end + # local refactor only if `old` is really a local binding bind = CSTParser.bindingof(CSTParser.parse(context)) if bind === nothing || old != bind.name try refactored = localrefactor(old, new, path, column, row, startrow, context) - isempty(refactored) || return Dict(:text => refactored) + isempty(refactored) || return Dict( + :text => refactored, + :success => "Local rename refactoring `$old` ⟹ `$new` succeeded" + ) catch err @error err end end try - mod = getmodule(mod) val = getfield′(mod, old) - result = globalrefactor(old, new, mod, val) - return result isa String ? Dict(:error => result) : Dict(:error => false) + kind, description = globalrefactor(old, new, mod, val) + return Dict( + kind => description, + :success => kind !== :info ? false : + "Global rename refactoring `$old` ⟹ `$new` succeeded" + ) catch err @error err end - return Dict(:error => "Rename refactoring failed: `$old` -> `$new`") + return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed") end # local refactor @@ -63,7 +77,7 @@ end globalrefactor(old, new, mod, @nospecialize(val)) = _globalrefactor(old, new, mod) # general case function globalrefactor(old, new, mod, val::Undefined) Symbol(old) in keys(Docs.keywords) ? - "Keywords can't be renamed: `$old`" : + (:warning, "Keywords can't be renamed: `$old`") : _globalrefactor(old, new, mod) end @@ -76,25 +90,24 @@ function _globalrefactor(old, new, mod) end end -function refactorfiles(old, new, obj, files) +function refactorfiles(old, new, mod, files) id = "global_rename_refactor_progress" @info "Start global rename refactoring" progress=0 _id=id - oldsym = Symbol(old) - newsym = Symbol(new) - modulesyms = Set(Symbol.(Base.loaded_modules_array())) - total = length(files) + oldsym = Symbol(old) + newsym = Symbol(new) + total = length(files) + + desc = "" for (i, file) ∈ enumerate(files) @info "Refactoring: $file ($i / $total)" progress=i/total _id=id MacroTools.sourcewalk(file) do ex return if ex === oldsym newsym - elseif @capture(ex, obj_.$oldsym) - if obj in modulesyms - @warn "Came across a global rename refactoring across different modules: `$obj.$old` -> `$obj.$new`" - end - Expr(:., obj, newsym) + elseif @capture(ex, m_.$oldsym) && getfield′(mod, m) isa Module + desc *= "`$m.$old` ⟹ `$m.$new` in $(fullpath(file))\n" + Expr(:., m, newsym) else ex end @@ -102,4 +115,6 @@ function refactorfiles(old, new, obj, files) end @info "Finish global rename refactoring" progress=1 _id=id + + (:info, isempty(desc) ? "" : "Refactorings across modules\n\n" * desc) end From 79bd61778263281a05e9b2bc02609b1ecf5564ff Mon Sep 17 00:00:00 2001 From: aviatesk Date: Tue, 22 Oct 2019 05:49:25 +0900 Subject: [PATCH 04/19] add more methods for get utilities --- src/utils.jl | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/utils.jl b/src/utils.jl index a94accc3..e035ceb1 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -1,3 +1,6 @@ +# path utilities +# -------------- + include("path_matching.jl") isuntitled(p) = occursin(r"^(\.\\|\./)?untitled-[\d\w]+(:\d+)?$", p) @@ -99,6 +102,9 @@ function md_hlines(md) return MD(v) end +# string utilties +# --------------- + function strlimit(str::AbstractString, limit::Int = 30, ellipsis::AbstractString = "…") will_append = length(str) > limit @@ -122,33 +128,39 @@ shortstr(val) = strlimit(string(val), 20) struct Undefined end # get utilities +# ------------- + using CodeTools """ - getfield′(mod::Module, name::String, default = Undefined()) + getfield′(mod::Module, name::AbstractString, default = Undefined()) getfield′(mod::Module, name::Symbol, default = Undefined()) + getfield′(mod::AbstractString, name::Symbol, default = Undefined()) getfield′(object, name::Symbol, default = Undefined()) + getfield′(object, name::AbstractString, default = Undefined()) Returns the specified field of a given `Module` or some arbitrary `object`, or `default` if no such a field is found. """ -getfield′(mod::Module, name::String, default = Undefined()) = CodeTools.getthing(mod, name, default) +getfield′(mod::Module, name::AbstractString, default = Undefined()) = CodeTools.getthing(mod, name, default) getfield′(mod::Module, name::Symbol, default = Undefined()) = getfield′(mod, string(name), default) +getfield′(mod::AbstractString, name::Symbol, default = Undefined()) = getfield′(getmodule(mod), string(name), default) getfield′(@nospecialize(object), name::Symbol, default = Undefined()) = isdefined(object, name) ? getfield(object, name) : default +getfield′(@nospecialize(object), name::AbstractString, default = Undefined()) = isdefined(object, name) ? getfield(object, Symbol(name)) : default """ - getmodule(mod::String) - getmodule(parent::Union{Nothing, Module}, mod::String) + getmodule(mod::AbstractString) + getmodule(parent::Union{Nothing, Module}, mod::AbstractString) getmodule(code::AbstractString, pos; filemod) Calls `CodeTools.getmodule(args...)`, but returns `Main` instead of `nothing` in a fallback case. """ getmodule(args...) = (m = CodeTools.getmodule(args...)) === nothing ? Main : m -getmethods(mod::Module, word::String) = methods(CodeTools.getthing(mod, word)) -getmethods(mod::String, word::String) = getmethods(getmodule(mod), word) +getmethods(mod::Module, word::AbstractString) = methods(CodeTools.getthing(mod, word)) +getmethods(mod::AbstractString, word::AbstractString) = getmethods(getmodule(mod), word) -getdocs(mod::Module, word::String, fallbackmod::Module = Main) = begin +getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main) = begin md = if Symbol(word) in keys(Docs.keywords) Core.eval(Main, :(@doc($(Symbol(word))))) else @@ -164,13 +176,14 @@ getdocs(mod::Module, word::String, fallbackmod::Module = Main) = begin end md_hlines(md) end -getdocs(mod::String, word::String, fallbackmod::Module = Main) = +getdocs(mod::AbstractString, word::AbstractString, fallbackmod::Module = Main) = getdocs(getmodule(mod), word, fallbackmod) cangetdocs(mod::Module, word::Symbol) = Base.isbindingresolved(mod, word) && !Base.isdeprecated(mod, word) -cangetdocs(mod::Module, word::String) = cangetdocs(mod, Symbol(word)) +cangetdocs(mod::Module, word::AbstractString) = cangetdocs(mod, Symbol(word)) +cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdocs(getmodule(mod), word) #= module file detections From e618a16dfe1cb6660da580506eabbaba70108f5e Mon Sep 17 00:00:00 2001 From: aviatesk Date: Tue, 22 Oct 2019 05:49:57 +0900 Subject: [PATCH 05/19] jumpable global rename refactor information --- src/refactor.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index bcdf86bb..6e4b9c87 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -97,7 +97,6 @@ function refactorfiles(old, new, mod, files) oldsym = Symbol(old) newsym = Symbol(new) total = length(files) - desc = "" for (i, file) ∈ enumerate(files) @@ -105,8 +104,11 @@ function refactorfiles(old, new, mod, files) MacroTools.sourcewalk(file) do ex return if ex === oldsym newsym - elseif @capture(ex, m_.$oldsym) && getfield′(mod, m) isa Module - desc *= "`$m.$old` ⟹ `$m.$new` in $(fullpath(file))\n" + elseif @capture(ex, m_.$oldsym) && getfield′(mod, Symbol(m)) isa Module + # TODO: enable line location information (the upstream needs to be enhanced) + file = fullpath(file) + link = "atom://julia-client/?open=true&file=$(file)&line=0" + desc *= "- `$m.$old` ⟹ `$m.$new` in [$file]($link)\n" Expr(:., m, newsym) else ex @@ -116,5 +118,5 @@ function refactorfiles(old, new, mod, files) @info "Finish global rename refactoring" progress=1 _id=id - (:info, isempty(desc) ? "" : "Refactorings across modules\n\n" * desc) + (:info, isempty(desc) ? "" : "Refactorings across modules\n" * desc) end From 4682c11ea015ee1e48074ba9e58b052bb7541430 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Tue, 22 Oct 2019 07:08:02 +0900 Subject: [PATCH 06/19] even better messaging --- src/refactor.jl | 57 ++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index 6e4b9c87..719aeab6 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -23,8 +23,14 @@ function renamerefactor( mod = getmodule(mod) # catch field renaming - if (obj = first(split(full, '.'))) != old && !isa(getfield′(mod, obj), Module) - return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") + modulenote = if (obj = first(split(full, '.'))) != old + if (parentmod = getfield′(mod, obj)) isa Module && parentmod != mod + "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well." + else + return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") + end + else + "" end # local refactor only if `old` is really a local binding @@ -34,7 +40,7 @@ function renamerefactor( refactored = localrefactor(old, new, path, column, row, startrow, context) isempty(refactored) || return Dict( :text => refactored, - :success => "Local rename refactoring `$old` ⟹ `$new` succeeded" + :success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded" ) catch err @error err @@ -42,12 +48,21 @@ function renamerefactor( end try - val = getfield′(mod, old) - kind, description = globalrefactor(old, new, mod, val) + val = getfield′(mod, full) + # catch keyword renaming + if val isa Undefined && Symbol(old) in keys(Docs.keywords) + return Dict(:warning => "Keywords can't be renamed: `$old`") + end + # update modulenote + if isempty(modulenote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod + modulenote = + "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well." + end + kind, desc = globalrefactor(old, new, mod) return Dict( - kind => description, - :success => kind !== :info ? false : - "Global rename refactoring `$old` ⟹ `$new` succeeded" + kind => kind === :success ? + join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modulenote, desc), "\n\n") : + desc ) catch err @error err @@ -74,14 +89,7 @@ end # global refactor # --------------- -globalrefactor(old, new, mod, @nospecialize(val)) = _globalrefactor(old, new, mod) # general case -function globalrefactor(old, new, mod, val::Undefined) - Symbol(old) in keys(Docs.keywords) ? - (:warning, "Keywords can't be renamed: `$old`") : - _globalrefactor(old, new, mod) -end - -function _globalrefactor(old, new, mod) +function globalrefactor(old, new, mod) entrypath, line = moduledefinition(mod) files = modulefiles(entrypath) @@ -97,18 +105,18 @@ function refactorfiles(old, new, mod, files) oldsym = Symbol(old) newsym = Symbol(new) total = length(files) - desc = "" + + # TODO: enable line location information (the upstream needs to be enhanced) + refactoredfiles = Set{String}() for (i, file) ∈ enumerate(files) @info "Refactoring: $file ($i / $total)" progress=i/total _id=id MacroTools.sourcewalk(file) do ex return if ex === oldsym + push!(refactoredfiles, fullpath(file)) newsym elseif @capture(ex, m_.$oldsym) && getfield′(mod, Symbol(m)) isa Module - # TODO: enable line location information (the upstream needs to be enhanced) - file = fullpath(file) - link = "atom://julia-client/?open=true&file=$(file)&line=0" - desc *= "- `$m.$old` ⟹ `$m.$new` in [$file]($link)\n" + push!(refactoredfiles, fullpath(file)) Expr(:., m, newsym) else ex @@ -118,5 +126,10 @@ function refactorfiles(old, new, mod, files) @info "Finish global rename refactoring" progress=1 _id=id - (:info, isempty(desc) ? "" : "Refactorings across modules\n" * desc) + return if !isempty(refactoredfiles) + filelist = ("- [$file](atom://julia-client/?open=true&file=$(file)&line=0)" for file in refactoredfiles) + (:success, string("Refactored files (all in `$mod` module):\n\n", join(filelist, '\n'))) + else + (:warning, "No rename refactoring occured on `$old`") + end end From 713312acae42b2b8d2cd448677926f6d24a12b26 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Tue, 22 Oct 2019 07:58:44 +0900 Subject: [PATCH 07/19] create uri utilities --- src/completions.jl | 15 +++++---------- src/refactor.jl | 20 +++++++++++--------- src/utils.jl | 7 +++++++ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/completions.jl b/src/completions.jl index 80dbb01d..6682ec21 100644 --- a/src/completions.jl +++ b/src/completions.jl @@ -145,19 +145,14 @@ completionurl(c::REPLCompletions.ModuleCompletion) = begin mod, name = c.parent, c.mod val = getfield′(mod, name) if val isa Module # module info - parentmodule(val) == val || val ∈ (Main, Base, Core) ? - "atom://julia-client/?moduleinfo=true&mod=$(name)" : - "atom://julia-client/?moduleinfo=true&mod=$(mod).$(name)" + urimoduleinfo(parentmodule(val) == val || val ∈ (Base, Core) ? name : "$mod.$name") else - "atom://julia-client/?docs=true&mod=$(mod)&word=$(name)" + uridocs(mod, name) end end -completionurl(c::REPLCompletions.MethodCompletion) = - "atom://julia-client/?docs=true&mod=$(c.method.module)&word=$(c.method.name)" -completionurl(c::REPLCompletions.PackageCompletion) = - "atom://julia-client/?moduleinfo=true&mod=$(c.package)" -completionurl(c::REPLCompletions.KeywordCompletion) = - "atom://julia-client/?docs=true&mod=Main&word=$(c.keyword)" +completionurl(c::REPLCompletions.MethodCompletion) = uridocs(c.method.module, c.method.name) +completionurl(c::REPLCompletions.PackageCompletion) = urimoduleinfo(c.package) +completionurl(c::REPLCompletions.KeywordCompletion) = uridocs("Main", c.keyword) completionmodule(mod, c) = shortstr(mod) completionmodule(mod, c::REPLCompletions.ModuleCompletion) = shortstr(c.parent) diff --git a/src/refactor.jl b/src/refactor.jl index 719aeab6..d3b4cb1d 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -23,9 +23,9 @@ function renamerefactor( mod = getmodule(mod) # catch field renaming - modulenote = if (obj = first(split(full, '.'))) != old + modnote = if (obj = first(split(full, '.'))) != old if (parentmod = getfield′(mod, obj)) isa Module && parentmod != mod - "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well." + modulenote(old, parentmod) else return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") end @@ -53,15 +53,14 @@ function renamerefactor( if val isa Undefined && Symbol(old) in keys(Docs.keywords) return Dict(:warning => "Keywords can't be renamed: `$old`") end - # update modulenote - if isempty(modulenote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod - modulenote = - "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well." + # update modnote + if isempty(modnote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod + modnote = modulenote(old, parentmod) end kind, desc = globalrefactor(old, new, mod) return Dict( kind => kind === :success ? - join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modulenote, desc), "\n\n") : + join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n") : desc ) catch err @@ -71,6 +70,9 @@ function renamerefactor( return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed") end +modulenote(old, parentmod) = + "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well." + # local refactor # -------------- @@ -127,9 +129,9 @@ function refactorfiles(old, new, mod, files) @info "Finish global rename refactoring" progress=1 _id=id return if !isempty(refactoredfiles) - filelist = ("- [$file](atom://julia-client/?open=true&file=$(file)&line=0)" for file in refactoredfiles) + filelist = ("- [$file]($(uriopen(file)))" for file in refactoredfiles) (:success, string("Refactored files (all in `$mod` module):\n\n", join(filelist, '\n'))) else - (:warning, "No rename refactoring occured on `$old`") + (:warning, "No rename refactoring occured on `$old` in `$mod` module.") end end diff --git a/src/utils.jl b/src/utils.jl index e035ceb1..ee97a660 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -185,6 +185,13 @@ cangetdocs(mod::Module, word::Symbol) = cangetdocs(mod::Module, word::AbstractString) = cangetdocs(mod, Symbol(word)) cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdocs(getmodule(mod), word) +# uri utilties +# ------------ + +uriopen(file, line = 0) = "atom://julia-client/?open=true&file=$(file)&line=$(line)" +uridocs(mod, word) = "atom://julia-client/?docs=true&mod=$(mod)&word=$(word)" +urimoduleinfo(mod) = "atom://julia-client/?moduleinfo=true&mod=$(mod)" + #= module file detections From bf977e5dd8c496adf6d57d099fc2d3e068bd4bdd Mon Sep 17 00:00:00 2001 From: aviatesk Date: Tue, 22 Oct 2019 18:31:31 +0900 Subject: [PATCH 08/19] moar docs --- src/Atom.jl | 1 + src/utils.jl | 24 ++++++++++++++++++++++++ test/goto.jl | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Atom.jl b/src/Atom.jl index 02d69595..cca3c671 100644 --- a/src/Atom.jl +++ b/src/Atom.jl @@ -1,5 +1,6 @@ __precompile__() +@doc read(joinpath(dirname(@__DIR__), "README.md"), String) module Atom using Juno, Lazy, JSON, MacroTools, Media, Base.StackTraces diff --git a/src/utils.jl b/src/utils.jl index ee97a660..cbabe045 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -157,9 +157,25 @@ Calls `CodeTools.getmodule(args...)`, but returns `Main` instead of `nothing` in """ getmodule(args...) = (m = CodeTools.getmodule(args...)) === nothing ? Main : m +""" + getmethods(mod::Module, word::AbstractString) + getmethods(mod::AbstractString, word::AbstractString) + +Returns the [`MethodList`](@ref) for `word`, which is bound within `mod` module. +""" getmethods(mod::Module, word::AbstractString) = methods(CodeTools.getthing(mod, word)) getmethods(mod::AbstractString, word::AbstractString) = getmethods(getmodule(mod), word) +""" + getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main) + getdocs(mod::AbstractString, word::AbstractString, fallbackmod::Module = Main) + +Retrieves docs for `mod.word` with [`@doc`](@ref) macro. If `@doc` is not available + within `mod` module, `@doc` will be evaluated in `fallbackmod` module if possible. + +!!! note + You may want to run [`cangetdocs`](@ref) in advance. +""" getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main) = begin md = if Symbol(word) in keys(Docs.keywords) Core.eval(Main, :(@doc($(Symbol(word))))) @@ -179,6 +195,14 @@ end getdocs(mod::AbstractString, word::AbstractString, fallbackmod::Module = Main) = getdocs(getmodule(mod), word, fallbackmod) +""" + cangetdocs(mod::Module, word::Symbol) + cangetdocs(mod::Module, word::AbstractString) + cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) + +Checks if the documentation bindings for `mod.word` is resolved and `mod.word` + is not deprecated. +""" cangetdocs(mod::Module, word::Symbol) = Base.isbindingresolved(mod, word) && !Base.isdeprecated(mod, word) diff --git a/test/goto.jl b/test/goto.jl index 246a21e0..3ebf158d 100644 --- a/test/goto.jl +++ b/test/goto.jl @@ -52,7 +52,7 @@ @testset "module goto" begin let item = modulegotoitems("Atom", Main)[1] @test item.file == realpath′(joinpath(@__DIR__, "..", "src", "Atom.jl")) - @test item.line == 2 + @test item.line == 3 end let item = modulegotoitems("Junk2", Main.Junk)[1] @test item.file == joinpath(@__DIR__, "fixtures", "Junk.jl") From e4d7ee49dc54e40b00f8216378f65220d69fe8ae Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 05:08:24 +0900 Subject: [PATCH 09/19] global refactor only from its definition --- src/refactor.jl | 87 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index d3b4cb1d..86b5fbfb 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -22,20 +22,27 @@ function renamerefactor( ) mod = getmodule(mod) - # catch field renaming + # check on dot accessor modnote = if (obj = first(split(full, '.'))) != old - if (parentmod = getfield′(mod, obj)) isa Module && parentmod != mod - modulenote(old, parentmod) + if (parent = getfield′(mod, obj)) isa Module + if parent != mod + modulenote(old, parent) + else + "" + end else + # catch field renaming return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") end else "" end - # local refactor only if `old` is really a local binding - bind = CSTParser.bindingof(CSTParser.parse(context)) - if bind === nothing || old != bind.name + expr = CSTParser.parse(context) + bind = CSTParser.bindingof(expr) + + # local rename refactor if `old` isn't a toplevel binding + if bind === nothing || old ≠ bind.name try refactored = localrefactor(old, new, path, column, row, startrow, context) isempty(refactored) || return Dict( @@ -47,22 +54,34 @@ function renamerefactor( end end + # global rename refactor if the local rename refactor didn't happen try val = getfield′(mod, full) + # catch keyword renaming if val isa Undefined && Symbol(old) in keys(Docs.keywords) return Dict(:warning => "Keywords can't be renamed: `$old`") end - # update modnote - if isempty(modnote) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) != mod - modnote = modulenote(old, parentmod) + + # catch global refactoring not on definition, e.g.: on a call site + if bind === nothing || old ≠ bind.name + # TODO: `goto` uri + return Dict(:info => contextnote(old, mod, context)) end - kind, desc = globalrefactor(old, new, mod) - return Dict( - kind => kind === :success ? - join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n") : - desc - ) + + kind, desc = globalrefactor(old, new, mod, expr) + + # make description + if kind === :success + # update modnote + if isempty(modnote) && applicable(parentmodule, val) && (parent = parentmodule(val)) ≠ mod + modnote = modulenote(old, parent) + end + + desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n") + end + + return Dict(kind => desc) catch err @error err end @@ -70,8 +89,17 @@ function renamerefactor( return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed") end -modulenote(old, parentmod) = - "**NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well." +modulenote(old, parentmod) = """ + **NOTE**: `$old` is defined in `$parentmod` + -- you may need the same rename refactorings in that module as well. + """ + +contextnote(old, mod, context) = """ + `$old` isn't found in local bindings in the current context: +
Context
$(strip(context))

+ + If you want a global rename refactoring on `$mod.$old`, you need to call from its definition. + """ # local refactor # -------------- @@ -91,35 +119,42 @@ end # global refactor # --------------- -function globalrefactor(old, new, mod) +function globalrefactor(old, new, mod, expr) entrypath, line = moduledefinition(mod) files = modulefiles(entrypath) with_logger(JunoProgressLogger()) do - refactorfiles(old, new, mod, files) + refactorfiles(old, new, mod, files, expr) end end -function refactorfiles(old, new, mod, files) - id = "global_rename_refactor_progress" - @info "Start global rename refactoring" progress=0 _id=id +function refactorfiles(old, new, mod, files, expr) + ismacro = CSTParser.defines_macro(expr) + oldsym = ismacro ? Symbol("@" * old) : Symbol(old) + newsym = ismacro ? Symbol("@" * new) : Symbol(new) - oldsym = Symbol(old) - newsym = Symbol(new) total = length(files) - # TODO: enable line location information (the upstream needs to be enhanced) refactoredfiles = Set{String}() + id = "global_rename_refactor_progress" + @info "Start global rename refactoring" progress=0 _id=id + for (i, file) ∈ enumerate(files) @info "Refactoring: $file ($i / $total)" progress=i/total _id=id + MacroTools.sourcewalk(file) do ex - return if ex === oldsym + if ex === oldsym push!(refactoredfiles, fullpath(file)) newsym + # handle dot (module) accessor elseif @capture(ex, m_.$oldsym) && getfield′(mod, Symbol(m)) isa Module push!(refactoredfiles, fullpath(file)) Expr(:., m, newsym) + # macro case + elseif ismacro && @capture(ex, macro $(Symbol(old))(args__) body_ end) + push!(refactoredfiles, fullpath(file)) + Expr(:macro, :($(Symbol(new))($(args...))), :($body)) else ex end From d746383748816e5f027ac4b12019dce5d630df04 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 05:39:36 +0900 Subject: [PATCH 10/19] organize description --- src/refactor.jl | 57 +++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index 86b5fbfb..2fb78e27 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -23,10 +23,10 @@ function renamerefactor( mod = getmodule(mod) # check on dot accessor - modnote = if (obj = first(split(full, '.'))) != old - if (parent = getfield′(mod, obj)) isa Module - if parent != mod - modulenote(old, parent) + moddesc = if (obj = first(split(full, '.'))) != old + if (parentmod = getfield′(mod, obj)) isa Module + if parentmod != mod + moduledescription(old, parentmod) else "" end @@ -66,19 +66,19 @@ function renamerefactor( # catch global refactoring not on definition, e.g.: on a call site if bind === nothing || old ≠ bind.name # TODO: `goto` uri - return Dict(:info => contextnote(old, mod, context)) + return Dict(:info => contextdescription(old, mod, context)) end kind, desc = globalrefactor(old, new, mod, expr) # make description if kind === :success - # update modnote - if isempty(modnote) && applicable(parentmodule, val) && (parent = parentmodule(val)) ≠ mod - modnote = modulenote(old, parent) + # update modesc + if isempty(moddesc) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) ≠ mod + moddesc = moduledescription(old, parentmod) end - desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", modnote, desc), "\n\n") + desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", moddesc, desc), "\n\n") end return Dict(kind => desc) @@ -89,18 +89,6 @@ function renamerefactor( return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed") end -modulenote(old, parentmod) = """ - **NOTE**: `$old` is defined in `$parentmod` - -- you may need the same rename refactorings in that module as well. - """ - -contextnote(old, mod, context) = """ - `$old` isn't found in local bindings in the current context: -
Context
$(strip(context))

- - If you want a global rename refactoring on `$mod.$old`, you need to call from its definition. - """ - # local refactor # -------------- @@ -164,9 +152,32 @@ function refactorfiles(old, new, mod, files, expr) @info "Finish global rename refactoring" progress=1 _id=id return if !isempty(refactoredfiles) - filelist = ("- [$file]($(uriopen(file)))" for file in refactoredfiles) - (:success, string("Refactored files (all in `$mod` module):\n\n", join(filelist, '\n'))) + (:success, filedescription(mod, refactoredfiles)) else (:warning, "No rename refactoring occured on `$old` in `$mod` module.") end end + +# descriptions +# ------------ + +moduledescription(old, parentmod) = """ + **NOTE**: `$old` is defined in `$parentmod` + -- you may need the same rename refactorings in that module as well. + """ + +contextdescription(old, mod, context) = """ + `$old` isn't found in local bindings in the current context: +
Context
$(strip(context))

+ + If you want a global rename refactoring on `$mod.$old`, you need to call from its definition. + """ + +function filedescription(mod, files) + filelist = join(("
  • [$file]($(uriopen(file)))
  • " for file in files), '\n') + """ +
    + Refactored files (all in `$mod` module): +
      $(filelist)
    + """ +end From 6f84942652a822e82843b04fd9a5cf5ba3e6333c Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 06:34:16 +0900 Subject: [PATCH 11/19] add goto uri :tada: --- src/refactor.jl | 20 +++++++++++++------- src/utils.jl | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index 2fb78e27..a33bceb8 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -65,7 +65,6 @@ function renamerefactor( # catch global refactoring not on definition, e.g.: on a call site if bind === nothing || old ≠ bind.name - # TODO: `goto` uri return Dict(:info => contextdescription(old, mod, context)) end @@ -78,7 +77,7 @@ function renamerefactor( moddesc = moduledescription(old, parentmod) end - desc = join(("_Global_ rename refactoring `$old` ⟹ `$new` succeeded.", moddesc, desc), "\n\n") + desc = join(("_Global_ rename refactoring `$mod.$old` ⟹ `$mod.$new` succeeded.", moddesc, desc), "\n\n") end return Dict(kind => desc) @@ -161,17 +160,24 @@ end # descriptions # ------------ -moduledescription(old, parentmod) = """ - **NOTE**: `$old` is defined in `$parentmod` - -- you may need the same rename refactorings in that module as well. +function moduledescription(old, parentmod) + gotouri = urigoto(parentmod, old) """ + **NOTE**: `$old` was defined in `$parentmod` -- you may need the same rename refactorings + in that module as well. + """ +end -contextdescription(old, mod, context) = """ +function contextdescription(old, mod, context) + gotouri = urigoto(mod, old) + """ `$old` isn't found in local bindings in the current context:
    Context
    $(strip(context))

    - If you want a global rename refactoring on `$mod.$old`, you need to call from its definition. + If you want a global rename refactoring on `$mod.$old`, you need to run this command + from its definition. """ +end function filedescription(mod, files) filelist = join(("
  • [$file]($(uriopen(file)))
  • " for file in files), '\n') diff --git a/src/utils.jl b/src/utils.jl index cbabe045..f2577838 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -214,6 +214,7 @@ cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdoc uriopen(file, line = 0) = "atom://julia-client/?open=true&file=$(file)&line=$(line)" uridocs(mod, word) = "atom://julia-client/?docs=true&mod=$(mod)&word=$(word)" +urigoto(mod, word) = "atom://julia-client/?goto=true&mod=$(mod)&word=$(word)" urimoduleinfo(mod) = "atom://julia-client/?moduleinfo=true&mod=$(mod)" #= From 39392af3c9c9d2454de335b97cb093bdd31da752 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 06:52:13 +0900 Subject: [PATCH 12/19] handle entrypath for Main module gracefully --- src/refactor.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/refactor.jl b/src/refactor.jl index a33bceb8..32b672ce 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -107,7 +107,11 @@ end # --------------- function globalrefactor(old, new, mod, expr) - entrypath, line = moduledefinition(mod) + entrypath, line = if mod == Main + MAIN_MODULE_LOCATION[] + else + moduledefinition(mod) + end files = modulefiles(entrypath) with_logger(JunoProgressLogger()) do From be7c0d3f32b39dde224bdb8467334a189b76382e Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 07:38:32 +0900 Subject: [PATCH 13/19] improve parent module detection logic --- src/refactor.jl | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index 32b672ce..24c6d854 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -21,21 +21,12 @@ function renamerefactor( mod = "Main", ) mod = getmodule(mod) + head = first(split(full, '.')) + headval = getfield′(mod, head) - # check on dot accessor - moddesc = if (obj = first(split(full, '.'))) != old - if (parentmod = getfield′(mod, obj)) isa Module - if parentmod != mod - moduledescription(old, parentmod) - else - "" - end - else - # catch field renaming - return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") - end - else - "" + # catch field renaming + if head ≠ old && !isa(headval, Module) + return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") end expr = CSTParser.parse(context) @@ -72,9 +63,11 @@ function renamerefactor( # make description if kind === :success - # update modesc - if isempty(moddesc) && applicable(parentmodule, val) && (parentmod = parentmodule(val)) ≠ mod - moddesc = moduledescription(old, parentmod) + moddesc = if (headval isa Module && headval ≠ mod) || + (applicable(parentmodule, val) && (headval = parentmodule(val)) ≠ mod) + moduledescription(old, headval) + else + "" end desc = join(("_Global_ rename refactoring `$mod.$old` ⟹ `$mod.$new` succeeded.", moddesc, desc), "\n\n") @@ -167,7 +160,7 @@ end function moduledescription(old, parentmod) gotouri = urigoto(parentmod, old) """ - **NOTE**: `$old` was defined in `$parentmod` -- you may need the same rename refactorings + **NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings in that module as well. """ end From df93c729ae0abfcac32c4314b7c02e12934bcab2 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 07:51:36 +0900 Subject: [PATCH 14/19] handle non-writable case --- src/refactor.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/refactor.jl b/src/refactor.jl index 24c6d854..a374f402 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -107,6 +107,11 @@ function globalrefactor(old, new, mod, expr) end files = modulefiles(entrypath) + nonwritablefiles = filter(f -> Int(Base.uperm(f)) ≠ 6, files) + if !isempty(nonwritablefiles) + return :warning, nonwritabledescription(mod, nonwritablefiles) + end + with_logger(JunoProgressLogger()) do refactorfiles(old, new, mod, files, expr) end @@ -176,6 +181,18 @@ function contextdescription(old, mod, context) """ end +function nonwritabledescription(mod, files) + filelist = join(("
  • [$file]($(uriopen(file)))
  • " for file in files), '\n') + """ + Global rename refactor failed, since there are non-writable files detected in + `$mod` module. + +
    + Non writable files (all in `$mod` module): +
      $(filelist)
    + """ +end + function filedescription(mod, files) filelist = join(("
  • [$file]($(uriopen(file)))
  • " for file in files), '\n') """ From 517d64581e7646cb19152c3a67714763419f1c04 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 08:28:42 +0900 Subject: [PATCH 15/19] improve toplevel binding detection logic --- src/refactor.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/refactor.jl b/src/refactor.jl index a374f402..ab7f94cc 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -30,7 +30,9 @@ function renamerefactor( end expr = CSTParser.parse(context) - bind = CSTParser.bindingof(expr) + items = toplevelitems(expr, context) + ind = findfirst(item -> item isa ToplevelBinding, items) + bind = ind === nothing ? nothing : items[ind].bind # local rename refactor if `old` isn't a toplevel binding if bind === nothing || old ≠ bind.name From 486b619317eae2d5a765a7c14d508e79cce5880f Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 17:31:11 +0900 Subject: [PATCH 16/19] local refactor only on the current scope --- src/refactor.jl | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index ab7f94cc..df288801 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -37,7 +37,7 @@ function renamerefactor( # local rename refactor if `old` isn't a toplevel binding if bind === nothing || old ≠ bind.name try - refactored = localrefactor(old, new, path, column, row, startrow, context) + refactored = localrefactor(old, new, path, column, row, startrow, context, expr) isempty(refactored) || return Dict( :text => refactored, :success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded" @@ -86,16 +86,38 @@ end # local refactor # -------------- -function localrefactor(old, new, path, column, row, startrow, context) - return if old ∈ map(l -> l[:name], locals(context, row - startrow, column)) - oldsym = Symbol(old) - newsym = Symbol(new) - MacroTools.textwalk(context) do sym - sym === oldsym ? newsym : sym +function localrefactor(old, new, path, column, row, startrow, context, expr) + bindings = local_bindings(expr, context) + line = row - startrow + scope = current_scope(old, bindings, byteoffset(context, line, column)) + scope === nothing && return "" + + current_context = scope.bindstr + oldsym = Symbol(old) + newsym = Symbol(new) + new_context = MacroTools.textwalk(current_context) do sym + sym === oldsym ? newsym : sym + end + + replace(context, current_context => new_context) +end + +function current_scope(name, bindings, byteoffset) + for binding in bindings + isa(binding, LocalScope) || continue + + scope = binding + if byteoffset in scope.span && + any(bind -> bind isa LocalBinding && name == bind.name, scope.children) + return scope + else + let scope = current_scope(name, scope.children, byteoffset) + scope !== nothing && return scope + end end - else - "" end + + return nothing end # global refactor From 75e2592b836b9daf75d10bf5fd4e1fd6bf6f7ffa Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 17:32:20 +0900 Subject: [PATCH 17/19] simpler returns --- src/docs.jl | 2 +- src/refactor.jl | 22 ++++++++++------------ src/utils.jl | 8 +++++++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/docs.jl b/src/docs.jl index d4909835..473a42f3 100644 --- a/src/docs.jl +++ b/src/docs.jl @@ -32,7 +32,7 @@ function renderitem(x) mod = getmodule(x.mod) name = Symbol(x.name) - r[:typ], r[:icon], r[:nativetype] = if (name !== :ans || mod === Base) && name ∈ keys(Docs.keywords) + r[:typ], r[:icon], r[:nativetype] = if (name !== :ans || mod === Base) && iskeyword(name) "keyword", "k", x.typ else val = getfield′(mod, name) diff --git a/src/refactor.jl b/src/refactor.jl index df288801..a4f9848e 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -20,14 +20,17 @@ function renamerefactor( column = 1, row = 1, startrow = 0, context = "", mod = "Main", ) + # catch keyword renaming + iskeyword(old) && return Dict(:warning => "Keywords can't be renamed: `$old`") + mod = getmodule(mod) head = first(split(full, '.')) headval = getfield′(mod, head) # catch field renaming - if head ≠ old && !isa(headval, Module) - return Dict(:warning => "Rename refactoring on a field isn't available: `$obj.$old`") - end + head ≠ old && !isa(headval, Module) && return Dict( + :warning => "Rename refactoring on a field isn't available: `$obj.$old`" + ) expr = CSTParser.parse(context) items = toplevelitems(expr, context) @@ -35,7 +38,7 @@ function renamerefactor( bind = ind === nothing ? nothing : items[ind].bind # local rename refactor if `old` isn't a toplevel binding - if bind === nothing || old ≠ bind.name + if islocalrefactor(bind, old) try refactored = localrefactor(old, new, path, column, row, startrow, context, expr) isempty(refactored) || return Dict( @@ -51,15 +54,8 @@ function renamerefactor( try val = getfield′(mod, full) - # catch keyword renaming - if val isa Undefined && Symbol(old) in keys(Docs.keywords) - return Dict(:warning => "Keywords can't be renamed: `$old`") - end - # catch global refactoring not on definition, e.g.: on a call site - if bind === nothing || old ≠ bind.name - return Dict(:info => contextdescription(old, mod, context)) - end + islocalrefactor(bind, old) && return Dict(:info => contextdescription(old, mod, context)) kind, desc = globalrefactor(old, new, mod, expr) @@ -83,6 +79,8 @@ function renamerefactor( return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed") end +islocalrefactor(bind, name) = bind === nothing || name ≠ bind.name + # local refactor # -------------- diff --git a/src/utils.jl b/src/utils.jl index f2577838..d4d5838c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -177,7 +177,7 @@ Retrieves docs for `mod.word` with [`@doc`](@ref) macro. If `@doc` is not availa You may want to run [`cangetdocs`](@ref) in advance. """ getdocs(mod::Module, word::AbstractString, fallbackmod::Module = Main) = begin - md = if Symbol(word) in keys(Docs.keywords) + md = if iskeyword(word) Core.eval(Main, :(@doc($(Symbol(word))))) else docsym = Symbol("@doc") @@ -209,6 +209,12 @@ cangetdocs(mod::Module, word::Symbol) = cangetdocs(mod::Module, word::AbstractString) = cangetdocs(mod, Symbol(word)) cangetdocs(mod::AbstractString, word::Union{Symbol, AbstractString}) = cangetdocs(getmodule(mod), word) +# is utilities +# ------------ + +iskeyword(word::Symbol) = word in keys(Docs.keywords) +iskeyword(word::AbstractString) = iskeyword(Symbol(word)) + # uri utilties # ------------ From df134489be20f08afc5d9c6648495f73da054e01 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 17:42:27 +0900 Subject: [PATCH 18/19] simplify the refactoring logic --- src/refactor.jl | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index a4f9848e..a54c469f 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -41,10 +41,13 @@ function renamerefactor( if islocalrefactor(bind, old) try refactored = localrefactor(old, new, path, column, row, startrow, context, expr) - isempty(refactored) || return Dict( - :text => refactored, - :success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded" - ) + return isempty(refactored) ? + # NOTE: global refactoring not on definition, e.g.: on a call site, will be caught here + Dict(:info => contextdescription(old, mod, context)) : + Dict( + :text => refactored, + :success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded" + ) catch err @error err end @@ -52,17 +55,13 @@ function renamerefactor( # global rename refactor if the local rename refactor didn't happen try - val = getfield′(mod, full) - - # catch global refactoring not on definition, e.g.: on a call site - islocalrefactor(bind, old) && return Dict(:info => contextdescription(old, mod, context)) - kind, desc = globalrefactor(old, new, mod, expr) # make description if kind === :success + val = getfield′(mod, full) moddesc = if (headval isa Module && headval ≠ mod) || - (applicable(parentmodule, val) && (headval = parentmodule(val)) ≠ mod) + (applicable(parentmodule, val) && (headval = parentmodule(val)) ≠ mod) moduledescription(old, headval) else "" @@ -175,23 +174,15 @@ function refactorfiles(old, new, mod, files, expr) @info "Finish global rename refactoring" progress=1 _id=id return if !isempty(refactoredfiles) - (:success, filedescription(mod, refactoredfiles)) + :success, filedescription(mod, refactoredfiles) else - (:warning, "No rename refactoring occured on `$old` in `$mod` module.") + :warning, "No rename refactoring occured on `$old` in `$mod` module." end end # descriptions # ------------ -function moduledescription(old, parentmod) - gotouri = urigoto(parentmod, old) - """ - **NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings - in that module as well. - """ -end - function contextdescription(old, mod, context) gotouri = urigoto(mod, old) """ @@ -203,6 +194,14 @@ function contextdescription(old, mod, context) """ end +function moduledescription(old, parentmod) + gotouri = urigoto(parentmod, old) + """ + **NOTE**: `$old` is defined in `$parentmod` -- you may need the same rename refactorings + in that module as well. + """ +end + function nonwritabledescription(mod, files) filelist = join(("
  • [$file]($(uriopen(file)))
  • " for file in files), '\n') """ From b8802f34ded21f937f45be7ee384b70a6abc36a8 Mon Sep 17 00:00:00 2001 From: aviatesk Date: Wed, 23 Oct 2019 18:09:49 +0900 Subject: [PATCH 19/19] return on error and report back the error message --- src/refactor.jl | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/refactor.jl b/src/refactor.jl index a54c469f..07dccdd0 100644 --- a/src/refactor.jl +++ b/src/refactor.jl @@ -49,7 +49,7 @@ function renamerefactor( :success => "_Local_ rename refactoring `$old` ⟹ `$new` succeeded" ) catch err - @error err + return Dict(:error => errdescription(old, new, err)) end end @@ -72,10 +72,8 @@ function renamerefactor( return Dict(kind => desc) catch err - @error err + return Dict(:error => errdescription(old, new, err)) end - - return Dict(:error => "Rename refactoring `$old` ⟹ `$new` failed") end islocalrefactor(bind, name) = bind === nothing || name ≠ bind.name @@ -187,7 +185,7 @@ function contextdescription(old, mod, context) gotouri = urigoto(mod, old) """ `$old` isn't found in local bindings in the current context: -
    Context
    $(strip(context))

    +
    Context:
    $(strip(context))

    If you want a global rename refactoring on `$mod.$old`, you need to run this command from its definition. @@ -222,3 +220,11 @@ function filedescription(mod, files)
      $(filelist)
    """ end + +function errdescription(old, new, err) + """ + Rename refactoring `$old` ⟹ `$new` failed. + +
    Error:
    $(errmsg(err))

    + """ +end