Skip to content

Commit

Permalink
Merge pull request #194 from TeroFrondelius/JSON
Browse files Browse the repository at this point in the history
Json format return message for lintserver()
  • Loading branch information
Michael-Klassen committed Mar 7, 2017
2 parents 1fb7cec + 38ccf65 commit 1dfcfd4
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 39 deletions.
3 changes: 2 additions & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
julia 0.4
julia 0.5
JSON 0.6.0
Compat 0.15.0
84 changes: 83 additions & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,92 @@ Make Julia start listening on a given port and return lint messages to requests
This feature is useful when you want to lint julia code in a non julia environment.

Existing plugins:

* Sublime Text: [SublimeLinter-contrib-julialintserver](https://github.com/invenia/SublimeLinter-contrib-julialintserver)
* linter-julia for Atom: [linter-julia](https://github.com/TeroFrondelius/linter-julia)

The protocol for the server is:
The new protocol for the server is JSON in both input and output:
```json
{
"file":"path_to_the_file",
"code_str":"full_text_of_the_file",
"ignore_codes":["E381","W361","I171"],
"ignore_info":false,
"ignore_warnings":false,
"show_code":true
}
```
Only the two first `"file"` and `"code_str"` are mandatory fields. For the output
there are four different protocols from which the `"lint-message"` is the direct
match of `LintMessage` and this way will be always up to date, but can also break.
Other three types are for convenience, they give you the opportunity to
directly pass the messages forward for example Atom linter. Here is one full example,
[to see more examples, see the tests.](https://github.com/tonyhffong/Lint.jl/blob/master/test/server.jl)
```julia
julia> using Lint

julia> using JSON

julia> if is_windows()
pipe_lm = "\\\\.\\pipe\\testsocket"
else # linux, osx
pipe_lm = tempname()
end
"/tmp/julial73DPo"

julia> server_lm = @async lintserver(pipe_lm,"lint-message")
Server running on port/pipe /tmp/julial73DPo ...
Task (queued) @0x00007f1b20a38280

julia> input = Dict("file" => "none", "code_str" => "something")
Dict{String,String} with 2 entries:
"file" => "none"
"code_str" => "something"

julia> conn = connect(pipe_lm)
Base.PipeEndpoint(open, 0 bytes waiting)

julia> JSON.print(conn, input)

julia> out = JSON.parse(conn)
1-element Array{Any,1}:
Dict{String,Any}(Pair{String,Any}("line",1),Pair{String,Any}("scope",""),Pair{String,Any}("message","use of undeclared symbol"),Pair{String,Any}("file","none"),Pair{String,Any}("code","E321"),Pair{String,Any}("variable","something"))

julia> out[1]
Dict{String,Any} with 6 entries:
"line" => 1
"scope" => ""
"message" => "use of undeclared symbol"
"file" => "none"
"code" => "E321"
"variable" => "something"

julia>
```
`lintserver(port,style)` style will accept four values:

1. "lint-message", which is the preferred and shown in above example
2. ["standard-linter-v1"](https://github.com/steelbrain/linter/blob/v1/docs/examples/standard-linter-v1.md)
3. ["vscode"](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#diagnostic)
4. ["standard-linter-v2"](https://github.com/steelbrain/linter/blob/master/docs/examples/standard-linter-v2.md)

If any of the above four JSON formats is not answering your needs, please make a
[new pull request](https://github.com/tonyhffong/Lint.jl/pulls). The file you want
to edit is [Lint.jl](https://github.com/tonyhffong/Lint.jl/blob/master/src/Lint.jl)
and the function is called `convertmsgtojson`. It is enough to add one `elseif`
block, here is one of them as an example:
```julia
elseif style == "standard-linter-v2"
push!(output, Dict("severity" => etype,
"location" => Dict("file" => file,
"position" => errorrange),
"excerpt" => code,
"description" => "$evar: $txt"))

end
```

The old protocol for the server is:

1. The file path followed by a new line
2. The number of bytes of code being sent followed by a new line
Expand Down
126 changes: 106 additions & 20 deletions src/Lint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Lint
using Base.Meta
using Compat
using Compat.TypeUtils
using JSON

if isdefined(Base, :unwrap_unionall)
using Base: unwrap_unionall
Expand Down Expand Up @@ -343,7 +344,7 @@ function lintinclude(ctx::LintContext, file::AbstractString)
end

"""
Lint all *.jl files at a given directory.
Lint all .jl ending files at a given directory.
Will ignore LintContext file and already included files.
"""
function lintdir{T<:AbstractString}(dir::T, ctx::LintContext=LintContext())
Expand All @@ -358,34 +359,118 @@ function lintdir{T<:AbstractString}(dir::T, ctx::LintContext=LintContext())
ctx.messages
end

function readandwritethestream(conn)
# println("Connection accepted")
# Get file, code length and code
file = strip(readline(conn))
# println("file: ", file)
code_len = parse(Int, strip(readline(conn)))
# println("Code bytes: ", code_len)
code = Compat.UTF8String(read(conn, code_len))
# println("Code received")
# Do the linting
msgs = lintfile(file, code)
# Write response to socket
for i in msgs
write(conn, string(i))
function convertmsgtojson(msgs, style, dict_data)
if style == "lint-message"
return msgs
end
output = Any[]
for msg in msgs
evar = msg.variable
txt = msg.message
file = msg.file
linenumber = msg.line
# Atom index starts from zero thus minus one
errorrange = Array[[linenumber-1, 0], [linenumber-1, 80]]
code = string(msg.code)
if code[1] == 'I'
etype = "info"
etypenumber = 3
elseif code[1] == 'W'
etype = "warning"
etypenumber = 2
else
etype = "error"
etypenumber = 1
end

if style == "standard-linter-v1"
if haskey(dict_data,"show_code")
if dict_data["show_code"]
msgtext = "$code $evar: $txt"
else
msgtext = "$evar: $txt"
end
else
msgtext = "$code $evar: $txt"
end
push!(output, Dict("type" => etype,
"text" => msgtext,
"range" => errorrange,
"filePath" => file))
elseif style == "vscode"
push!(output, Dict("severity" => etypenumber,
"message" => "$evar: $txt",
"range" => errorrange,
"filePath" => file,
"code" => code,
"source" => "Lint.jl"))
elseif style == "standard-linter-v2"
push!(output, Dict("severity" => etype,
"location" => Dict("file" => file,
"position" => errorrange),
"excerpt" => code,
"description" => "$evar: $txt"))

end
end
return output
end


function filtermsgs(msgs,dict_data)
if haskey(dict_data,"ignore_warnings")
if dict_data["ignore_warnings"]
msgs = filter(i -> !iswarning(i), msgs)
end
end
if haskey(dict_data,"ignore_info")
if dict_data["ignore_info"]
msgs = filter(i -> !isinfo(i), msgs)
end
end
if haskey(dict_data,"ignore_codes")
msgs = filter(i -> !(string(i.code) in dict_data["ignore_codes"]), msgs)
end
return msgs
end


function readandwritethestream(conn,style)
if style == "original_behaviour"
# println("Connection accepted")
# Get file, code length and code
file = strip(readline(conn))
# println("file: ", file)
code_len = parse(Int, strip(readline(conn)))
# println("Code bytes: ", code_len)
code = Compat.UTF8String(read(conn, code_len))
# println("Code received")
# Do the linting
msgs = lintfile(file, code)
# Write response to socket
for i in msgs
write(conn, string(i))
write(conn, "\n")
end
# Blank line to indicate end of messages
write(conn, "\n")
else
dict_data = JSON.parse(conn)
msgs = lintfile(dict_data["file"], dict_data["code_str"])
msgs = filtermsgs(msgs, dict_data)
out = convertmsgtojson(msgs, style, dict_data)
JSON.print(conn, out)
end
# Blank line to indicate end of messages
write(conn, "\n")
end

function lintserver(port)
function lintserver(port,style="original_behaviour")
server = listen(port)
try
println("Server running on port $port ...")
println("Server running on port/pipe $port ...")
while true
conn = accept(server)
@async try
readandwritethestream(conn)
readandwritethestream(conn,style)
catch err
println(STDERR, "connection ended with error $err")
finally
Expand All @@ -399,6 +484,7 @@ function lintserver(port)
end
end


# precompile hints
include("precompile.jl")

Expand Down

0 comments on commit 1dfcfd4

Please sign in to comment.