Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/LanguageServer.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module LanguageServer
import URIParser
using JSON, REPL, CSTParser, DocumentFormat, SymbolServer, StaticLint
using CSTParser: EXPR, Tokenize.Tokens, typof, kindof, parentof, valof
using CSTParser: EXPR, Tokenize.Tokens, Tokenize.Tokens.kind, headof, parentof, valof
using StaticLint: refof, scopeof, bindingof
using UUIDs
using Base.Docs, Markdown
Expand Down
4 changes: 2 additions & 2 deletions src/languageserverinstance.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function deletedocument!(server::LanguageServerInstance, uri::URI2)
for d in getdocuments_value(server)
if d.root === doc
d.root = d
scopepass(getroot(d), d)
semantic_pass(getroot(d), d)
end
end
end
Expand Down Expand Up @@ -319,7 +319,7 @@ function Base.run(server::LanguageServerInstance)
root = getroot(doc)
if !(root in roots)
push!(roots, root)
scopepass(root, doc)
semantic_pass(root, doc)
end

lint!(doc, server)
Expand Down
43 changes: 21 additions & 22 deletions src/requests/actions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ end

function find_using_statement(x::EXPR)
for ref in refof(x).refs
if parentof(ref) isa EXPR && typof(parentof(ref)) === CSTParser.Using
if StaticLint.is_in_fexpr(ref, x -> headof(x) === :using || headof(x) === :import)
return parentof(ref)
end
end
Expand All @@ -50,9 +50,8 @@ function explicitly_import_used_variables(x::EXPR, server, conn)
vars = Set{String}() # names that need to be imported
# Find uses of `x` and mark edits
for ref in refof(x).refs
if parentof(ref) isa EXPR && typof(parentof(ref)) == CSTParser.BinaryOpCall && length(parentof(ref).args) == 3 && kindof(parentof(ref).args[2]) === CSTParser.Tokens.DOT && parentof(ref).args[1] == ref
typof(parentof(ref).args[3]) !== CSTParser.Quotenode && continue # some malformed EXPR, skip
childname = parentof(ref).args[3].args[1]
if parentof(ref) isa EXPR && CSTParser.is_getfield_w_quotenode(parentof(ref)) && parentof(ref).args[1] == ref
childname = parentof(ref).args[2].args[1]
StaticLint.hasref(childname) && refof(childname) isa StaticLint.Binding && continue # check this isn't the name of something being explictly overwritten
!haskey(refof(x).val.vals, Symbol(valof(childname))) && continue # skip, perhaps mark as missing ref ?

Expand All @@ -67,7 +66,7 @@ function explicitly_import_used_variables(x::EXPR, server, conn)
isempty(tdes) && return

# Add `using x: vars...` statement
if parentof(using_stmt) isa EXPR && (typof(parentof(using_stmt)) === CSTParser.Block || typof(parentof(using_stmt)) === CSTParser.FileH)
if parentof(using_stmt) isa EXPR && (headof(parentof(using_stmt)) === :block || headof(parentof(using_stmt)) === :file)
# this should cover all cases
i1 = 0
for i = 1:length(parentof(using_stmt).args)
Expand All @@ -93,28 +92,28 @@ function explicitly_import_used_variables(x::EXPR, server, conn)
JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, collect(values(tdes)))))
end

is_single_line_func(x) = CSTParser.defines_function(x) && typof(x) !== CSTParser.FunctionDef
is_single_line_func(x) = CSTParser.defines_function(x) && headof(x) !== :function

function expand_inline_func(x, server, conn)
func = _get_parent_fexpr(x, is_single_line_func)
length(func) < 3 && return
sig = func[1]
op = func[2]
body = func[3]
if typof(body) == CSTParser.Block && length(body) == 1
sig = func.args[1]
op = func.head
body = func.args[2]
if headof(body) == :block && length(body) == 1
file, offset = get_file_loc(func)
tde = TextDocumentEdit(VersionedTextDocumentIdentifier(file._uri, file._version), TextEdit[
TextEdit(Range(file, offset .+ (0:func.fullspan)), string("function ", get_text(file)[offset .+ (1:sig.span)], "\n ", get_text(file)[offset + sig.fullspan + op.fullspan .+ (1:body.span)], "\nend\n"))
])
JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde])))
elseif (typof(body) === CSTParser.Begin || typof(body) === CSTParser.InvisBrackets) && length(body) == 3 &&
typof(body[2]) === CSTParser.Block && length(body[2]) > 0
elseif (headof(body) === :begin || CSTParser.isbracketed(body)) &&
headof(body.args[1]) === :block && length(body.args[1]) > 0
file, offset = get_file_loc(func)
newtext = string("function ", get_text(file)[offset .+ (1:sig.span)])
blockoffset = offset + sig.fullspan + op.fullspan + body[1].fullspan
for i = 1:length(body[2])
newtext = string(newtext, "\n ", get_text(file)[blockoffset .+ (1:body[2][i].span)])
blockoffset += body[2][i].fullspan
blockoffset = offset + sig.fullspan + op.fullspan + body.trivia[1].fullspan
for i = 1:length(body.args[1].args)
newtext = string(newtext, "\n ", get_text(file)[blockoffset .+ (1:body.args[1].args[i].span)])
blockoffset += body.args[1].args[i].fullspan
end
newtext = string(newtext, "\nend\n")
tde = TextDocumentEdit(VersionedTextDocumentIdentifier(file._uri, file._version), TextEdit[TextEdit(Range(file, offset .+ (0:func.fullspan)), newtext)])
Expand Down Expand Up @@ -153,8 +152,8 @@ function get_next_line_offset(x)
end

function reexport_package(x::EXPR, server, conn)
(refof(x).type === StaticLint.CoreTypes.Module || (refof(x).val isa StaticLint.Binding && refof(x).val.type === StaticLint.CoreTypes.Module)) || (refof(x).val isa SymbolServer.ModuleStore) || return
mod::SymbolServer.ModuleStore = refof(x).val
(refof(x) isa SymbolServer.ModuleStore || refof(x).type === StaticLint.CoreTypes.Module || (refof(x).val isa StaticLint.Binding && refof(x).val.type === StaticLint.CoreTypes.Module)) || (refof(x).val isa SymbolServer.ModuleStore) || return
mod::SymbolServer.ModuleStore = refof(x) isa SymbolServer.ModuleStore ? refof(x) : refof(x).val
using_stmt = parentof(x)
file, offset = get_file_loc(x)
insertpos = get_next_line_offset(using_stmt)
Expand All @@ -167,13 +166,13 @@ function reexport_package(x::EXPR, server, conn)
JSONRPC.send(conn, workspace_applyEdit_request_type, ApplyWorkspaceEditParams(missing, WorkspaceEdit(missing, TextDocumentEdit[tde])))
end

# TODO move to StaticLint
# TODO move to StaticL int
# to be called where typof(x) === CSTParser.ModuleH/BareModule
function find_exported_names(x::EXPR)
exported_vars = EXPR[]
for i in 1:length(x.args[3].args)
expr = x.args[3].args[i]
if typof(expr) == CSTParser.Export &&
if headof(expr) === :export
for j = 2:length(expr)
if CSTParser.isidentifier(expr.args[j]) && StaticLint.hasref(expr.args[j])
push!(exported_vars, expr.args[j])
Expand All @@ -187,7 +186,7 @@ end
function reexport_module(x::EXPR, server, conn)
using_stmt = parentof(x)
mod_expr = refof(x).val isa StaticLint.Binding ? refof(x).val.val : refof(x).val
(mod_expr.args isa Nothing || length(mod_expr.args) < 3 || typof(mod_expr.args[3]) != CSTParser.Block || mod_expr.args[3].args isa Nothing) && return # module expr without block
(mod_expr.args isa Nothing || length(mod_expr.args) < 3 || headof(mod_expr.args[3]) !== :block || mod_expr.args[3].args isa Nothing) && return # module expr without block
# find export EXPR
exported_names = find_exported_names(mod_expr)

Expand Down Expand Up @@ -266,6 +265,6 @@ const LSActions = Dict(
(x, params) -> is_fixable_missing_ref(x, params.context),
applymissingreffix),
"ReexportModule" => ServerAction(Command("Re-export package variables.", "ReexportModule", missing),
(x, params) -> parentof(x) isa EXPR && typof(parentof(x)) === CSTParser.Using && refof(x) isa StaticLint.Binding && (refof(x).type === StaticLint.CoreTypes.Module || (refof(x).val isa StaticLint.Binding && refof(x).val.type === StaticLint.CoreTypes.Module) || refof(x).val isa SymbolServer.ModuleStore),
(x, params) -> StaticLint.is_in_fexpr(x, x -> headof(x) === :using || headof(x) === :import) && (refof(x) isa StaticLint.Binding && (refof(x).type === StaticLint.CoreTypes.Module || (refof(x).val isa StaticLint.Binding && refof(x).val.type === StaticLint.CoreTypes.Module) || refof(x).val isa SymbolServer.ModuleStore) || refof(x) isa SymbolServer.ModuleStore),
reexport_package)
)
26 changes: 14 additions & 12 deletions src/requests/completions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function textDocument_completion_request(params::CompletionParams, server::Langu
!isempty(partial) && latex_completions(doc, offset, partial, CIs)
elseif t isa CSTParser.Tokens.Token && (t.kind == CSTParser.Tokenize.Tokens.STRING || t.kind == CSTParser.Tokenize.Tokens.TRIPLE_STRING)
string_completion(doc, offset, rng, t, CIs)
elseif x isa EXPR && parentof(x) !== nothing && (typof(parentof(x)) === CSTParser.Using || typof(parentof(x)) === CSTParser.Import)
elseif x isa EXPR && is_in_import_statement(x)
import_completions(doc, offset, rng, ppt, pt, t, is_at_end, x, CIs, server)
elseif t isa CSTParser.Tokens.Token && t.kind == CSTParser.Tokens.DOT && pt isa CSTParser.Tokens.Token && pt.kind == CSTParser.Tokens.IDENTIFIER
# getfield completion, no partial
Expand Down Expand Up @@ -143,7 +143,7 @@ function collect_completions(x::EXPR, spartial, rng, CIs, server, inclexported=f
end
end
end
if parentof(x) !== nothing && typof(x) !== CSTParser.ModuleH && typof(x) !== CSTParser.BareModule
if parentof(x) !== nothing && !CSTParser.defines_module(x)
return collect_completions(parentof(x), spartial, rng, CIs, server, inclexported, dotcomps)
else
return
Expand All @@ -168,9 +168,9 @@ end

function is_rebinding_of_module(x)
x isa EXPR && refof(x).type === StaticLint.CoreTypes.Module && # binding is a Module
refof(x).val isa EXPR && typof(refof(x).val) === CSTParser.BinaryOpCall && kindof(refof(x).val.args[2]) === CSTParser.Tokens.EQ && # binding expr is an assignment
StaticLint.hasref(refof(x).val.args[3]) && refof(refof(x).val.args[3]).type === StaticLint.CoreTypes.Module &&
refof(refof(x).val.args[3]).val isa EXPR && typof(refof(refof(x).val.args[3]).val) === CSTParser.ModuleH# double check the rhs points to a module
refof(x).val isa EXPR && CSTParser.isassignment(refof(x).val) && # binding expr is an assignment
StaticLint.hasref(refof(x).val.args[2]) && refof(refof(x).val.args[2]).type === StaticLint.CoreTypes.Module &&
refof(refof(x).val.args[2]).val isa EXPR && CSTParser.defines_module(refof(refof(x).val.args[2]).val)# double check the rhs points to a module
end

function _get_dot_completion(px, spartial, rng, CIs, server) end
Expand All @@ -179,10 +179,10 @@ function _get_dot_completion(px::EXPR, spartial, rng, CIs, server)
if refof(px) isa StaticLint.Binding
if refof(px).val isa StaticLint.SymbolServer.ModuleStore
collect_completions(refof(px).val, spartial, rng, CIs, server, true)
elseif refof(px).val isa EXPR && typof(refof(px).val) === CSTParser.ModuleH && scopeof(refof(px).val) isa StaticLint.Scope
elseif refof(px).val isa EXPR && CSTParser.defines_module(refof(px).val) && scopeof(refof(px).val) isa StaticLint.Scope
collect_completions(scopeof(refof(px).val), spartial, rng, CIs, server, true)
elseif is_rebinding_of_module(px)
collect_completions(scopeof(refof(refof(px).val.args[3]).val), spartial, rng, CIs, server, true)
collect_completions(scopeof(refof(refof(px).val.args[2]).val), spartial, rng, CIs, server, true)
elseif refof(px).type isa SymbolServer.DataTypeStore
for a in refof(px).type.fieldnames
a = String(a)
Expand Down Expand Up @@ -235,10 +235,8 @@ function _completion_kind(b, server)
end

function get_import_root(x::EXPR)
for i = 1:length(x.args)
if typof(x.args[i]) === CSTParser.OPERATOR && kindof(x.args[i]) === CSTParser.Tokens.COLON && i > 2
return x.args[i - 1]
end
if CSTParser.isoperator(headof(x.args[1])) && valof(headof(x.args[1])) == ":"
return last(x.args[1].args[1].args)
end
return nothing
end
Expand Down Expand Up @@ -321,9 +319,13 @@ function path_completion(doc, offset, rng, t, CIs)
end
end

is_in_import_statement(x::EXPR) = is_in_fexpr(x, x -> headof(x) in (:using, :import))

function import_completions(doc, offset, rng, ppt, pt, t, is_at_end, x, CIs, server)
import_statement = parentof(x)
import_statement = StaticLint.get_parent_fexpr(x, x -> headof(x) === :using || headof(x) === :import)

import_root = get_import_root(import_statement)

if (t.kind == CSTParser.Tokens.WHITESPACE && pt.kind ∈ (CSTParser.Tokens.USING, CSTParser.Tokens.IMPORT, CSTParser.Tokens.IMPORTALL, CSTParser.Tokens.COMMA, CSTParser.Tokens.COLON)) ||
(t.kind in (CSTParser.Tokens.COMMA, CSTParser.Tokens.COLON))
# no partial, no dot
Expand Down
Loading