function _unwrap_parse_error(core_hook_result) @test Meta.isexpr(core_hook_result[1], :error, 1) err = core_hook_result[1].args[1] if JuliaSyntax._has_v1_10_hooks @test err isa Meta.ParseError return err.detail else @test err isa JuliaSyntax.ParseError return err end end @testset "Hooks for Core integration" begin @testset "whitespace parsing" begin @test JuliaSyntax.core_parser_hook("", "somefile", 1, 0, :statement) == Core.svec(nothing, 0) @test JuliaSyntax.core_parser_hook("", "somefile", 1, 0, :statement) == Core.svec(nothing, 0) @test JuliaSyntax.core_parser_hook(" ", "somefile", 1, 2, :statement) == Core.svec(nothing,2) @test JuliaSyntax.core_parser_hook(" #==# ", "somefile", 1, 6, :statement) == Core.svec(nothing,6) @test JuliaSyntax.core_parser_hook(" x \n", "somefile", 1, 0, :statement) == Core.svec(:x,4) @test JuliaSyntax.core_parser_hook(" x \n", "somefile", 1, 0, :atom) == Core.svec(:x,2) end @testset "filename and lineno" begin ex = JuliaSyntax.core_parser_hook("@a", "somefile", 1, 0, :statement)[1] @test Meta.isexpr(ex, :macrocall) @test ex.args[2] == LineNumberNode(1, "somefile") ex = JuliaSyntax.core_parser_hook("@a", "otherfile", 2, 0, :statement)[1] @test ex.args[2] == LineNumberNode(2, "otherfile") # Errors also propagate file & lineno err = _unwrap_parse_error( JuliaSyntax.core_parser_hook("[x)", "f1", 1, 0, :statement) ) @test err isa JuliaSyntax.ParseError @test err.source.filename == "f1" @test err.source.first_line == 1 err = _unwrap_parse_error( JuliaSyntax.core_parser_hook("[x)", "f2", 2, 0, :statement) ) @test err isa JuliaSyntax.ParseError @test err.source.filename == "f2" @test err.source.first_line == 2 # Errors including nontrivial offset indices err = _unwrap_parse_error( JuliaSyntax.core_parser_hook("a\nh{x)\nb", "test.jl", 1, 2, :statement) ) @test err isa JuliaSyntax.ParseError @test err.source.first_line == 1 @test err.diagnostics[1].first_byte == 6 @test err.diagnostics[1].last_byte == 5 @test err.diagnostics[1].message == "Expected `}`" end @testset "toplevel errors" begin ex = JuliaSyntax.core_parser_hook("a\nb\n[x,\ny)", "somefile", 1, 0, :all)[1] @test ex.head == :toplevel @test ex.args[1:5] == [ LineNumberNode(1, "somefile"), :a, LineNumberNode(2, "somefile"), :b, LineNumberNode(4, "somefile"), ] @test Meta.isexpr(ex.args[6], :error) ex = JuliaSyntax.core_parser_hook("x.", "somefile", 0, 0, :all)[1] @test ex.head == :toplevel @test ex.args[2].head == :incomplete end @testset "enable_in_core!" begin JuliaSyntax.enable_in_core!() @test Meta.parse("x + 1") == :(x + 1) @test Meta.parse("x + 1", 1) == (:(x + 1), 6) # Test that parsing statements incrementally works and stops after # whitespace / comment trivia @test Meta.parse("x + 1\n(y)\n", 1) == (:(x + 1), 7) @test Meta.parse("x + 1\n(y)\n", 7) == (:y, 11) @test Meta.parse(" x#==#", 1) == (:x, 7) @test Meta.parse(" #==# ", 1) == (nothing, 7) # Check the exception type that Meta.parse throws if JuliaSyntax._has_v1_10_hooks @test_throws Meta.ParseError Meta.parse("[x)") @test_throws Meta.ParseError eval(Meta.parse("[x)", raise=false)) @test_throws Meta.ParseError eval(Meta.parse("(x")) # Expr(:incomplete) else @test_throws JuliaSyntax.ParseError Meta.parse("[x)") end # Check custom string types defined in a world age later than # enable_in_core!() can be passed to Meta.parse() mystr = @eval begin struct MyString <: AbstractString x::String end Base.String(s::MyString) = s.x Base.ncodeunits(s::MyString) = ncodeunits(s.x) MyString("hi") end @test Meta.parse(mystr) == :hi JuliaSyntax.enable_in_core!(false) end @testset "Expr(:incomplete)" begin JuliaSyntax.enable_in_core!() err = Meta.parse("\"") @test Meta.isexpr(err, :incomplete) if JuliaSyntax._has_v1_10_hooks @test err.args[1] isa Meta.ParseError exc = err.args[1] @test exc.msg == "ParseError:\n# Error @ none:1:2\n\"\n#└ ── unterminated string literal" @test exc.detail isa JuliaSyntax.ParseError @test exc.detail.incomplete_tag === :string else @test err.args[1] isa String end for (str, tag) in [ "\"" => :string "\"\$foo" => :string "#=" => :comment "'" => :char "'a" => :char "`" => :cmd "(" => :other "[" => :other "begin" => :block "quote" => :block "let" => :block "let;" => :block "for" => :other "for x=xs" => :block "function" => :other "function f()" => :block "macro" => :other "macro f()" => :block "f() do" => :other "f() do x" => :block "module" => :other "module X" => :block "baremodule" => :other "baremodule X" => :block "mutable struct" => :other "mutable struct X" => :block "struct" => :other "struct X" => :block "if" => :other "if x" => :block "while" => :other "while x" => :block "try" => :block # could be `try x catch exc body end` or `try x catch ; body end` "try x catch" => :block "using" => :other "import" => :other "local" => :other "global" => :other "1 == 2 ?" => :other "1 == 2 ? 3 :" => :other "1," => :other "1, " => :other "1,\n" => :other "1, \n" => :other # Reference parser fails to detect incomplete exprs in this case "(x for y" => :other # Syntax which may be an error but is not incomplete "" => :none ")" => :none "1))" => :none "a b" => :none "()x" => :none "." => :none ] @testset "$(repr(str))" begin @test Base.incomplete_tag(Meta.parse(str, raise=false)) == tag end end JuliaSyntax.enable_in_core!(false) # Should not throw @test JuliaSyntax.core_parser_hook("+=", "somefile", 1, 0, :statement)[1] isa Expr end end