Add compat for JuliaSyntax v1#978
Conversation
|
@domluna all tests pass locally |
|
@penelopeysm can you review this? |
|
I could, but honestly, it would take me a bit of time! I haven't really gotten into the codebase as much as I'd like, and have been quite busy IRL too. If you don't mind waiting, I could try to get to it over the weekend? |
|
Sure no worries as long as we get this project moved forward again 🚂 |
pfitzseb
left a comment
There was a problem hiding this comment.
Ok, finally got a chance to take a proper look at this. It would generally be nice to get some more unit tests for the new utility functions if possible.
| function is_short_function_def(cst::JuliaSyntax.GreenNode) | ||
| kind(cst) === K"function" && haschildren(cst) || return false | ||
| idx = findfirst(n -> !JuliaSyntax.is_whitespace(n), children(cst)) | ||
| isnothing(idx) && return false | ||
| return kind(cst[idx]) !== K"function" | ||
| end |
There was a problem hiding this comment.
| function is_short_function_def(cst::JuliaSyntax.GreenNode) | |
| kind(cst) === K"function" && haschildren(cst) || return false | |
| idx = findfirst(n -> !JuliaSyntax.is_whitespace(n), children(cst)) | |
| isnothing(idx) && return false | |
| return kind(cst[idx]) !== K"function" | |
| end | |
| is_short_function_def(cst::JuliaSyntax.GreenNode) = JuliaSyntax.has_flags(cst, JuliaSyntax.SHORT_FORM_FUNCTION_FLAG) |
| elseif has_do_block_call(node) | ||
| p_do_call(style, node, s, ctx, lineage) |
There was a problem hiding this comment.
With this, does the
elseif k === K"do"
p_do(style, node, s, ctx, lineage)
ever get called? Seems like only having one of them should be enough (and specifically, why can't you re-use the existing case here)?
There was a problem hiding this comment.
That branch is still hit for macros like @m(x) do y; false; end. To be honest I didn't even know this was a valid syntactic construct until now
| return nothing | ||
| end | ||
|
|
||
| function is_source_operator(s::State, cst::JuliaSyntax.GreenNode, offset::Integer) |
There was a problem hiding this comment.
In theory this should be covered by the JuliaSyntax.is_operator_* calls no? Would be nice if we didn't have to look into the file for this.
| function source_kind(s::State, cst::JuliaSyntax.GreenNode, offset::Integer) | ||
| span(cst) == 0 && return nothing | ||
| offset = Int(offset) | ||
| n = Int(span(cst)) | ||
| val = getsrcval(s.doc, offset:(offset+n-1)) | ||
| try | ||
| return JuliaSyntax.Kind(val) | ||
| catch | ||
| return nothing | ||
| end | ||
| end | ||
|
|
||
| function source_operator_kind(s::State, cst::JuliaSyntax.GreenNode, offset::Integer) | ||
| if JuliaSyntax.is_operator(cst) && !haschildren(cst) | ||
| return kind(cst) | ||
| elseif kind(cst) === K"Identifier" && !haschildren(cst) | ||
| k = source_kind(s, cst, offset) | ||
| if !isnothing(k) && JuliaSyntax.is_operator(k) | ||
| return k | ||
| end | ||
| end | ||
| return nothing | ||
| end |
There was a problem hiding this comment.
We need this because of JuliaLang/JuliaSyntax.jl#591, right? Wonder whether this will get fixed upstream at some point...
There was a problem hiding this comment.
I guess this is an intentional choice in JuliaSyntax
JuliaLang/JuliaSyntax.jl#474
JuliaLang/JuliaSyntax.jl#523
So I think we have to work around it somehow. As mentioned in that PR, JuliaSyntax.tokenize() has a flag to retain the information, but we aren't using that, and doing so would probably be a large refactor. So I'm happy to use this approach of re-parsing.
(I think the JuliaFormatter codebase needs more comments in general to explain things like this, but that's a separate issue.)
| function source_op_kind(s::State, cst::JuliaSyntax.GreenNode, op_indices::Vector{Int}) | ||
| opkind = op_kind(cst) | ||
| opkind !== K"None" && opkind !== K"Identifier" && return opkind | ||
|
|
||
| offset = Int(s.offset) | ||
| childs = children(cst) | ||
| for (i, c) in enumerate(childs) | ||
| if i in op_indices | ||
| k = source_operator_kind(s, c, offset) | ||
| if !isnothing(k) && !(kind(cst) === K"dotcall" && k === K".") | ||
| return k | ||
| end | ||
| end | ||
| offset += Int(span(c)) | ||
| end | ||
| return opkind | ||
| end | ||
|
|
||
| function update_operator_indices(childs::Vector{JuliaSyntax.GreenNode{T}}) where {T} | ||
| args = findall(n -> !JuliaSyntax.is_whitespace(n), childs) | ||
| length(args) < 4 && return Int[] | ||
| return args[2:(end-1)] | ||
| end |
There was a problem hiding this comment.
Seems like the operator should be easy enough to find by checking in which position it occurs (JS.is_[prefix|infix|suffix]_op_call) and then directly accessing that argument.
penelopeysm
left a comment
There was a problem hiding this comment.
Thanks @svchb and sorry it took a while! I'm familiar enough with parsing but I had to figure out all the exact data structures that JuliaFormatter and JuliaSyntax use.
I'm conscious that bumping compat for JuliaSyntax has been a longstanding issue on JuliaFormatter. So I think that we should just try to merge this in as long as it works. Although I think that there are quite a few things that ought to be cleaned up (especially with the helper functions that do similar things to JuliaSyntax), we can defer these.
However I still do have some questions / comments which hopefully won't hold things up too much.
| return is_source_operator(s, childs[op_arg], offset) ? op_arg : nothing | ||
| end | ||
|
|
||
| function is_source_prefix_op_call(cst::JuliaSyntax.GreenNode, s::State) |
There was a problem hiding this comment.
I think this entire function can just be replaced with JuliaSyntax.is_prefix_op_call(cst). I ran the test suite with this change and it all passes
| function source_kind(s::State, cst::JuliaSyntax.GreenNode, offset::Integer) | ||
| span(cst) == 0 && return nothing | ||
| offset = Int(offset) | ||
| n = Int(span(cst)) | ||
| val = getsrcval(s.doc, offset:(offset+n-1)) | ||
| try | ||
| return JuliaSyntax.Kind(val) | ||
| catch | ||
| return nothing | ||
| end | ||
| end | ||
|
|
||
| function source_operator_kind(s::State, cst::JuliaSyntax.GreenNode, offset::Integer) | ||
| if JuliaSyntax.is_operator(cst) && !haschildren(cst) | ||
| return kind(cst) | ||
| elseif kind(cst) === K"Identifier" && !haschildren(cst) | ||
| k = source_kind(s, cst, offset) | ||
| if !isnothing(k) && JuliaSyntax.is_operator(k) | ||
| return k | ||
| end | ||
| end | ||
| return nothing | ||
| end |
There was a problem hiding this comment.
I guess this is an intentional choice in JuliaSyntax
JuliaLang/JuliaSyntax.jl#474
JuliaLang/JuliaSyntax.jl#523
So I think we have to work around it somehow. As mentioned in that PR, JuliaSyntax.tokenize() has a flag to retain the information, but we aren't using that, and doing so would probably be a large refactor. So I'm happy to use this approach of re-parsing.
(I think the JuliaFormatter codebase needs more comments in general to explain things like this, but that's a separate issue.)
| elseif is_source_prefix_op_call(node, s) | ||
| p_unaryopcall(style, node, s, ctx, lineage) |
There was a problem hiding this comment.
| elseif is_source_prefix_op_call(node, s) | |
| p_unaryopcall(style, node, s, ctx, lineage) | |
| elseif JuliaSyntax.is_prefix_op_call(node) | |
| p_unaryopcall(style, node, s, ctx, lineage) |
| elseif !isnothing(source_prefix_operator_index(node, s)) | ||
| p_call(style, node, s, ctx, lineage) |
There was a problem hiding this comment.
I'm pretty sure that this dispatch is not necessary -- they all fall through to the other branches correctly. For example, I think the intent of this branch is to catch something like ">=(x)"? (Please correct me if I'm wrong.) However, this doesn't actually get picked up by is_unary (because, since JuliaSyntax v1, the kind of the first token is Identifier), so it falls through all the way to is_func_call, which is exactly what we want. Again, I checked with this change and the test suite passes.
| elseif !isnothing(source_prefix_operator_index(node, s)) | |
| p_call(style, node, s, ctx, lineage) |
| t | ||
| end | ||
|
|
||
| function p_cartesian_iterator( |
There was a problem hiding this comment.
I think we should rename this function to p_iteration to avoid confusion (since there's no longer a cartesian iterator kind in JuliaSyntax v1, and this function handles all types of iterators regardless of whether there are multiple of them).
| function is_prefix_op_call(x::JuliaSyntax.GreenNode) | ||
| is_opcall(x) || return false | ||
| haschildren(x) || return false | ||
|
|
||
| idx = findfirst(!JuliaSyntax.is_whitespace, children(x)) | ||
| isnothing(idx) && return false | ||
|
|
||
| return JuliaSyntax.is_operator(x[idx]) | ||
| end |
There was a problem hiding this comment.
This function is now dead code (and gives the wrong behaviour anyway with JuliaSyntax v1 because prefix ops are parsed as K"Identifier") so can be removed.
| end | ||
|
|
||
| from_colon = ctx.from_colon | ||
| from_typedef = ctx.from_typedef || any(x -> x[1] === K"where", lineage) |
There was a problem hiding this comment.
Tests seem to pass without this any(...) check. What's the rationale for adding it?
| ctx::PrettyContext, | ||
| lineage::Vector{Tuple{JuliaSyntax.Kind,Bool,Bool}}, | ||
| lineage::Vector{Tuple{JuliaSyntax.Kind,Bool,Bool}}; | ||
| child_limit::Union{Int,Nothing} = nothing, |
There was a problem hiding this comment.
I think this keyword argument is pretending to be more general than it really needs to be. Looking at where it gets used, it really just means 'one before the do block, if a do block exists'. I'd suggest we could just pass in the do block index, which would be clearer, and would also avoid recalculating it inside this function.
| opkind = try | ||
| JuliaSyntax.Kind(op.val) | ||
| catch | ||
| metadata.op_kind | ||
| end |
There was a problem hiding this comment.
Kind(op.val) can't fail because op.val can only be one of in, = or ∈ so I think we can elide the try/catch.
| metadata.op_dotted, | ||
| metadata.is_standalone_shortcircuit, | ||
| metadata.is_short_form_function, | ||
| op.val == "=", |
There was a problem hiding this comment.
| op.val == "=", | |
| opkind === K"=", |
I kept the changes as minimal as possible.