unparser 0.6.7 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +10 -10
- data/lib/unparser/adamantium.rb +3 -1
- data/lib/unparser/anima.rb +12 -3
- data/lib/unparser/ast/local_variable_scope.rb +29 -26
- data/lib/unparser/ast.rb +32 -49
- data/lib/unparser/buffer.rb +44 -2
- data/lib/unparser/cli.rb +48 -11
- data/lib/unparser/color.rb +9 -1
- data/lib/unparser/comments.rb +2 -2
- data/lib/unparser/diff.rb +8 -1
- data/lib/unparser/either.rb +13 -11
- data/lib/unparser/emitter/args.rb +5 -1
- data/lib/unparser/emitter/array.rb +0 -4
- data/lib/unparser/emitter/array_pattern.rb +1 -9
- data/lib/unparser/emitter/assignment.rb +17 -8
- data/lib/unparser/emitter/begin.rb +0 -6
- data/lib/unparser/emitter/binary.rb +1 -1
- data/lib/unparser/emitter/block.rb +18 -7
- data/lib/unparser/emitter/def.rb +1 -1
- data/lib/unparser/emitter/dstr.rb +6 -5
- data/lib/unparser/emitter/dsym.rb +1 -1
- data/lib/unparser/emitter/ensure.rb +16 -0
- data/lib/unparser/emitter/flipflop.rb +7 -2
- data/lib/unparser/emitter/flow_modifier.rb +1 -7
- data/lib/unparser/emitter/for.rb +1 -1
- data/lib/unparser/emitter/hash.rb +0 -8
- data/lib/unparser/emitter/hash_pattern.rb +1 -1
- data/lib/unparser/emitter/in_pattern.rb +9 -1
- data/lib/unparser/emitter/index.rb +0 -4
- data/lib/unparser/emitter/kwbegin.rb +1 -1
- data/lib/unparser/emitter/match_pattern.rb +7 -11
- data/lib/unparser/emitter/match_pattern_p.rb +6 -1
- data/lib/unparser/emitter/mlhs.rb +7 -1
- data/lib/unparser/emitter/op_assign.rb +0 -5
- data/lib/unparser/emitter/pair.rb +31 -5
- data/lib/unparser/emitter/primitive.rb +4 -6
- data/lib/unparser/emitter/range.rb +23 -2
- data/lib/unparser/emitter/regexp.rb +5 -17
- data/lib/unparser/emitter/rescue.rb +7 -1
- data/lib/unparser/emitter/root.rb +2 -9
- data/lib/unparser/emitter/send.rb +1 -5
- data/lib/unparser/emitter/string.rb +31 -0
- data/lib/unparser/emitter/xstr.rb +8 -1
- data/lib/unparser/emitter.rb +11 -11
- data/lib/unparser/equalizer.rb +105 -71
- data/lib/unparser/generation.rb +33 -29
- data/lib/unparser/node_details/send.rb +1 -0
- data/lib/unparser/node_details.rb +8 -1
- data/lib/unparser/node_helpers.rb +19 -9
- data/lib/unparser/util.rb +24 -0
- data/lib/unparser/validation.rb +70 -28
- data/lib/unparser/writer/array.rb +51 -0
- data/lib/unparser/writer/binary.rb +8 -4
- data/lib/unparser/writer/dynamic_string.rb +130 -138
- data/lib/unparser/writer/regexp.rb +101 -0
- data/lib/unparser/writer/resbody.rb +37 -3
- data/lib/unparser/writer/rescue.rb +3 -7
- data/lib/unparser/writer/send/unary.rb +9 -4
- data/lib/unparser/writer/send.rb +8 -14
- data/lib/unparser/writer.rb +31 -1
- data/lib/unparser.rb +157 -41
- metadata +82 -33
- data/lib/unparser/concord.rb +0 -114
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unparser
|
|
4
|
+
module Writer
|
|
5
|
+
# Writer for regexp literals
|
|
6
|
+
class Regexp
|
|
7
|
+
include Writer, Adamantium
|
|
8
|
+
|
|
9
|
+
CANDIDATES = [
|
|
10
|
+
['/', '/'].freeze,
|
|
11
|
+
['%r{', '}'].freeze
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
define_group(:body, 0..-2)
|
|
15
|
+
|
|
16
|
+
def dispatch
|
|
17
|
+
effective_writer.write_to_buffer
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# mutant:disable
|
|
23
|
+
def effective_writer
|
|
24
|
+
CANDIDATES.each do |token_open, token_close|
|
|
25
|
+
source = render_with_delimiter(token_close:, token_open:)
|
|
26
|
+
|
|
27
|
+
next unless round_trips?(source:)
|
|
28
|
+
|
|
29
|
+
return writer_with(Effective, node:, token_close:, token_open:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
fail 'Could not find a round tripping solution for regexp'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Effective
|
|
36
|
+
include Writer, Adamantium
|
|
37
|
+
|
|
38
|
+
include anima.add(:token_close, :token_open)
|
|
39
|
+
|
|
40
|
+
define_group(:body, 0..-2)
|
|
41
|
+
|
|
42
|
+
def dispatch
|
|
43
|
+
buffer.root_indent do
|
|
44
|
+
write(token_open)
|
|
45
|
+
body.each(&method(:emit_body))
|
|
46
|
+
write(token_close)
|
|
47
|
+
emit_options
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def emit_body(node)
|
|
54
|
+
if n_begin?(node)
|
|
55
|
+
write('#{')
|
|
56
|
+
node.children.each(&method(:visit))
|
|
57
|
+
write('}')
|
|
58
|
+
elsif n_gvar?(node)
|
|
59
|
+
write('#')
|
|
60
|
+
write_regular(node.children.first.to_s)
|
|
61
|
+
else
|
|
62
|
+
write_regular(node.children.first)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def write_regular(string)
|
|
67
|
+
if string.length > 1 && string.start_with?("\n")
|
|
68
|
+
string.each_char do |char|
|
|
69
|
+
buffer.append_without_prefix(char.eql?("\n") ? '\c*' : char)
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
buffer.append_without_prefix(string)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def emit_options
|
|
77
|
+
write(children.last.children.join)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# mutant:disable
|
|
82
|
+
def render_with_delimiter(token_close:, token_open:)
|
|
83
|
+
buffer = Buffer.new
|
|
84
|
+
|
|
85
|
+
writer = Effective.new(
|
|
86
|
+
buffer:,
|
|
87
|
+
comments:,
|
|
88
|
+
explicit_encoding:,
|
|
89
|
+
local_variable_scope:,
|
|
90
|
+
node:,
|
|
91
|
+
token_close:,
|
|
92
|
+
token_open:
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
writer.dispatch
|
|
96
|
+
buffer.nl_flush_heredocs
|
|
97
|
+
buffer.content
|
|
98
|
+
end
|
|
99
|
+
end # Regexp
|
|
100
|
+
end # Emitter
|
|
101
|
+
end # Unparser
|
|
@@ -6,11 +6,21 @@ module Unparser
|
|
|
6
6
|
class Resbody
|
|
7
7
|
include Writer
|
|
8
8
|
|
|
9
|
+
OPERATORS = {
|
|
10
|
+
csend: '&.',
|
|
11
|
+
send: '.'
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
9
14
|
children :exception, :assignment, :body
|
|
10
15
|
|
|
11
16
|
def emit_postcontrol
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
if body
|
|
18
|
+
write(' rescue ')
|
|
19
|
+
visit(body)
|
|
20
|
+
else
|
|
21
|
+
nl
|
|
22
|
+
write('rescue')
|
|
23
|
+
end
|
|
14
24
|
end
|
|
15
25
|
|
|
16
26
|
def emit_regular
|
|
@@ -33,7 +43,31 @@ module Unparser
|
|
|
33
43
|
return unless assignment
|
|
34
44
|
|
|
35
45
|
write(' => ')
|
|
36
|
-
|
|
46
|
+
|
|
47
|
+
case assignment.type
|
|
48
|
+
when :send, :csend
|
|
49
|
+
write_send_assignment
|
|
50
|
+
when :indexasgn
|
|
51
|
+
write_index_assignment
|
|
52
|
+
else
|
|
53
|
+
visit(assignment)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def write_send_assignment
|
|
58
|
+
details = NodeDetails::Send.new(assignment)
|
|
59
|
+
|
|
60
|
+
visit(details.receiver)
|
|
61
|
+
write(OPERATORS.fetch(assignment.type))
|
|
62
|
+
write(details.non_assignment_selector)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def write_index_assignment
|
|
66
|
+
receiver, *indexes = assignment.children
|
|
67
|
+
visit(receiver)
|
|
68
|
+
write('[')
|
|
69
|
+
delimited(indexes)
|
|
70
|
+
write(']')
|
|
37
71
|
end
|
|
38
72
|
end # Resbody
|
|
39
73
|
end # Writer
|
|
@@ -20,13 +20,9 @@ module Unparser
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def emit_heredoc_reminders
|
|
24
|
-
emitter(body).emit_heredoc_reminders
|
|
25
|
-
end
|
|
26
|
-
|
|
27
23
|
def emit_postcontrol
|
|
28
|
-
visit(body)
|
|
29
|
-
writer_with(Resbody, rescue_body).emit_postcontrol
|
|
24
|
+
visit(body) if body
|
|
25
|
+
writer_with(Resbody, node: rescue_body).emit_postcontrol
|
|
30
26
|
end
|
|
31
27
|
|
|
32
28
|
private
|
|
@@ -36,7 +32,7 @@ module Unparser
|
|
|
36
32
|
end
|
|
37
33
|
|
|
38
34
|
def emit_rescue_body(node)
|
|
39
|
-
writer_with(Resbody, node).emit_regular
|
|
35
|
+
writer_with(Resbody, node:).emit_regular
|
|
40
36
|
end
|
|
41
37
|
end # Rescue
|
|
42
38
|
end # Writer
|
|
@@ -12,13 +12,18 @@ module Unparser
|
|
|
12
12
|
|
|
13
13
|
private_constant(*constants(false))
|
|
14
14
|
|
|
15
|
-
def dispatch
|
|
15
|
+
def dispatch # rubocop:disable Metrics/AbcSize
|
|
16
16
|
name = selector
|
|
17
|
+
first_child = children.fetch(0)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
if n_flipflop?(first_child) || n_and?(first_child) || n_or?(first_child)
|
|
20
|
+
write 'not '
|
|
21
|
+
else
|
|
22
|
+
write(MAP.fetch(name, name).to_s)
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
if n_int?(receiver) && selector.equal?(:+@)
|
|
25
|
+
write('+')
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
visit(receiver)
|
data/lib/unparser/writer/send.rb
CHANGED
|
@@ -30,15 +30,10 @@ module Unparser
|
|
|
30
30
|
write(details.string_selector)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def emit_heredoc_reminders
|
|
34
|
-
emitter(receiver).emit_heredoc_reminders if receiver
|
|
35
|
-
arguments.each(&method(:emit_heredoc_reminder))
|
|
36
|
-
end
|
|
37
|
-
|
|
38
33
|
private
|
|
39
34
|
|
|
40
35
|
def effective_writer
|
|
41
|
-
writer_with(effective_writer_class, node)
|
|
36
|
+
writer_with(effective_writer_class, node:)
|
|
42
37
|
end
|
|
43
38
|
memoize :effective_writer
|
|
44
39
|
|
|
@@ -78,10 +73,6 @@ module Unparser
|
|
|
78
73
|
parentheses { delimited(arguments) }
|
|
79
74
|
end
|
|
80
75
|
|
|
81
|
-
def emit_heredoc_reminder(argument)
|
|
82
|
-
emitter(argument).emit_heredoc_reminders
|
|
83
|
-
end
|
|
84
|
-
|
|
85
76
|
def avoid_clash?
|
|
86
77
|
local_variable_clash? || parses_as_constant?
|
|
87
78
|
end
|
|
@@ -91,9 +82,12 @@ module Unparser
|
|
|
91
82
|
end
|
|
92
83
|
|
|
93
84
|
def parses_as_constant?
|
|
94
|
-
test = Unparser
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
test = Unparser
|
|
86
|
+
.parse_ast_either(selector.to_s)
|
|
87
|
+
.fmap(&:node)
|
|
88
|
+
.from_right do
|
|
89
|
+
fail InvalidNodeError.new("Invalid selector for send node: #{selector.inspect}", node)
|
|
90
|
+
end
|
|
97
91
|
|
|
98
92
|
n_const?(test)
|
|
99
93
|
end
|
|
@@ -105,7 +99,7 @@ module Unparser
|
|
|
105
99
|
|
|
106
100
|
def emit_send_regular(node)
|
|
107
101
|
if n_send?(node)
|
|
108
|
-
writer_with(Regular, node).dispatch
|
|
102
|
+
writer_with(Regular, node:).dispatch
|
|
109
103
|
else
|
|
110
104
|
visit(node)
|
|
111
105
|
end
|
data/lib/unparser/writer.rb
CHANGED
|
@@ -4,12 +4,42 @@ module Unparser
|
|
|
4
4
|
module Writer
|
|
5
5
|
include Generation, NodeHelpers
|
|
6
6
|
|
|
7
|
+
# mutant:disable
|
|
7
8
|
def self.included(descendant)
|
|
8
9
|
descendant.class_eval do
|
|
9
|
-
include Anima.new(:buffer, :comments, :node, :local_variable_scope)
|
|
10
|
+
include Adamantium, Anima.new(:buffer, :comments, :explicit_encoding, :node, :local_variable_scope)
|
|
10
11
|
|
|
11
12
|
extend DSL
|
|
12
13
|
end
|
|
13
14
|
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# mutant:disable
|
|
19
|
+
def emitter(node)
|
|
20
|
+
Emitter.emitter(
|
|
21
|
+
buffer: buffer,
|
|
22
|
+
comments: comments,
|
|
23
|
+
explicit_encoding: explicit_encoding,
|
|
24
|
+
local_variable_scope: local_variable_scope,
|
|
25
|
+
node: node
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# mutant:disable
|
|
30
|
+
def round_trips?(source:)
|
|
31
|
+
parser = Unparser.parser
|
|
32
|
+
local_variable_scope.local_variables_for_node(node).each do |local_variable|
|
|
33
|
+
parser.declare_local_variable(local_variable)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
buffer = Buffer.new
|
|
37
|
+
buffer.write_encoding(explicit_encoding) if explicit_encoding
|
|
38
|
+
buffer.write(source)
|
|
39
|
+
|
|
40
|
+
node.eql?(parser.parse(Unparser.buffer(buffer.content)))
|
|
41
|
+
rescue Parser::SyntaxError
|
|
42
|
+
false
|
|
43
|
+
end
|
|
14
44
|
end # Writer
|
|
15
45
|
end # Unparser
|
data/lib/unparser.rb
CHANGED
|
@@ -3,37 +3,76 @@
|
|
|
3
3
|
require 'diff/lcs'
|
|
4
4
|
require 'diff/lcs/hunk'
|
|
5
5
|
require 'optparse'
|
|
6
|
-
require 'parser/current'
|
|
7
|
-
require 'set'
|
|
8
6
|
|
|
9
7
|
require 'unparser/equalizer'
|
|
10
8
|
require 'unparser/adamantium'
|
|
11
9
|
require 'unparser/adamantium/method_builder'
|
|
12
10
|
require 'unparser/abstract_type'
|
|
13
11
|
|
|
14
|
-
require 'unparser/concord'
|
|
15
12
|
require 'unparser/either'
|
|
16
13
|
require 'unparser/anima'
|
|
17
14
|
require 'unparser/anima/attribute'
|
|
18
15
|
require 'unparser/anima/error'
|
|
19
16
|
|
|
20
17
|
# Library namespace
|
|
21
|
-
module Unparser
|
|
18
|
+
module Unparser # rubocop:disable Metrics/ModuleLength
|
|
22
19
|
# Unparser specific AST builder defaulting to modern AST format
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
if Gem::Version.new(RUBY_VERSION) <= '3.4'
|
|
21
|
+
require 'parser/current'
|
|
22
|
+
class Builder < Parser::Builders::Default
|
|
23
|
+
modernize
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
# mutant:disable
|
|
26
|
+
def initialize
|
|
27
|
+
super
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
self.emit_file_line_as_literals = false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
require 'prism'
|
|
34
|
+
class Builder < Prism::Translation::Parser::Builder
|
|
35
|
+
modernize
|
|
36
|
+
|
|
37
|
+
# mutant:disable
|
|
38
|
+
def initialize
|
|
39
|
+
super
|
|
40
|
+
|
|
41
|
+
self.emit_file_line_as_literals = false
|
|
42
|
+
end
|
|
30
43
|
end
|
|
31
44
|
end
|
|
32
45
|
|
|
46
|
+
PARSER_CLASS =
|
|
47
|
+
if Gem::Version.new(RUBY_VERSION) <= '3.4'
|
|
48
|
+
Class.new(Parser::CurrentRuby) do
|
|
49
|
+
def declare_local_variable(local_variable)
|
|
50
|
+
static_env.declare(local_variable)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
prism_translation_parser =
|
|
55
|
+
if Gem::Version.new(RUBY_VERSION) >= '4.0'
|
|
56
|
+
Prism::Translation::Parser40
|
|
57
|
+
else
|
|
58
|
+
Prism::Translation::Parser34
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Class.new(prism_translation_parser) do
|
|
62
|
+
def declare_local_variable(local_variable)
|
|
63
|
+
(@local_variables ||= Set.new) << local_variable
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def prism_options
|
|
67
|
+
super.merge(scopes: [@local_variables.to_a])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
33
72
|
EMPTY_STRING = ''.freeze
|
|
34
73
|
EMPTY_ARRAY = [].freeze
|
|
35
74
|
|
|
36
|
-
private_constant(*constants(false) - %i[Adamantium AbstractType Anima
|
|
75
|
+
private_constant(*constants(false) - %i[Adamantium AbstractType Anima Either Equalizer Memoizable])
|
|
37
76
|
|
|
38
77
|
# Error raised when unparser encounters an invalid AST
|
|
39
78
|
class InvalidNodeError < RuntimeError
|
|
@@ -44,12 +83,18 @@ module Unparser
|
|
|
44
83
|
@node = node
|
|
45
84
|
freeze
|
|
46
85
|
end
|
|
47
|
-
end
|
|
86
|
+
end # InvalidNodeError
|
|
87
|
+
|
|
88
|
+
# Error raised when unparser encounders AST it cannot generate source for that would parse to the same AST.
|
|
89
|
+
class UnsupportedNodeError < RuntimeError
|
|
90
|
+
end # UnsupportedNodeError
|
|
48
91
|
|
|
49
92
|
# Unparse an AST (and, optionally, comments) into a string
|
|
50
93
|
#
|
|
51
94
|
# @param [Parser::AST::Node, nil] node
|
|
52
|
-
# @param [Array]
|
|
95
|
+
# @param [Array] comments
|
|
96
|
+
# @param [Encoding, nil] explicit_encoding
|
|
97
|
+
# @param [Set<Symbol>] static_local_variables
|
|
53
98
|
#
|
|
54
99
|
# @return [String]
|
|
55
100
|
#
|
|
@@ -57,44 +102,99 @@ module Unparser
|
|
|
57
102
|
# if the node passed is invalid
|
|
58
103
|
#
|
|
59
104
|
# @api public
|
|
60
|
-
|
|
61
|
-
|
|
105
|
+
#
|
|
106
|
+
# mutant:disable
|
|
107
|
+
# rubocop:disable Metrics/ParameterLists
|
|
108
|
+
def self.unparse(
|
|
109
|
+
node,
|
|
110
|
+
comments: EMPTY_ARRAY,
|
|
111
|
+
explicit_encoding: nil,
|
|
112
|
+
static_local_variables: Set.new
|
|
113
|
+
)
|
|
114
|
+
unparse_ast(
|
|
115
|
+
AST.new(
|
|
116
|
+
comments: comments,
|
|
117
|
+
explicit_encoding: explicit_encoding,
|
|
118
|
+
node: node,
|
|
119
|
+
static_local_variables: static_local_variables
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
# rubocop:enable Metrics/ParameterLists
|
|
124
|
+
|
|
125
|
+
# Unparse an AST
|
|
126
|
+
#
|
|
127
|
+
# @param [AST] ast
|
|
128
|
+
#
|
|
129
|
+
# @return [String]
|
|
130
|
+
#
|
|
131
|
+
# @raise InvalidNodeError
|
|
132
|
+
# if the node passed is invalid
|
|
133
|
+
#
|
|
134
|
+
# @raise UnsupportedNodeError
|
|
135
|
+
# if the node passed is valid but unparser cannot unparse it
|
|
136
|
+
#
|
|
137
|
+
# @api public
|
|
138
|
+
def self.unparse_ast(ast)
|
|
139
|
+
return EMPTY_STRING if ast.node.nil?
|
|
140
|
+
|
|
141
|
+
local_variable_scope = AST::LocalVariableScope.new(
|
|
142
|
+
node: ast.node,
|
|
143
|
+
static_local_variables: ast.static_local_variables
|
|
144
|
+
)
|
|
62
145
|
|
|
63
146
|
Buffer.new.tap do |buffer|
|
|
64
147
|
Emitter::Root.new(
|
|
65
|
-
buffer,
|
|
66
|
-
|
|
67
|
-
|
|
148
|
+
buffer: buffer,
|
|
149
|
+
comments: Comments.new(ast.comments),
|
|
150
|
+
explicit_encoding: ast.explicit_encoding,
|
|
151
|
+
local_variable_scope: local_variable_scope,
|
|
152
|
+
node: ast.node
|
|
68
153
|
).write_to_buffer
|
|
69
154
|
end.content
|
|
70
155
|
end
|
|
71
156
|
|
|
72
|
-
# Unparse
|
|
157
|
+
# Unparse AST either
|
|
73
158
|
#
|
|
74
|
-
# @param [
|
|
75
|
-
# @param [Array] comment_array
|
|
159
|
+
# @param [AST] ast
|
|
76
160
|
#
|
|
77
|
-
# @return [Either<
|
|
78
|
-
def self.
|
|
79
|
-
|
|
80
|
-
|
|
161
|
+
# @return [Either<Exception,String>]
|
|
162
|
+
def self.unparse_ast_either(ast)
|
|
163
|
+
Either.wrap_error(Exception) { unparse_ast(ast) }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Unparse AST either
|
|
167
|
+
#
|
|
168
|
+
# @param [AST] ast
|
|
169
|
+
#
|
|
170
|
+
# @return [Either<Exception,String>]
|
|
171
|
+
#
|
|
172
|
+
# mutant:disable
|
|
173
|
+
def self.unparse_validate_ast_either(ast:)
|
|
174
|
+
validation = Validation.from_ast(ast:)
|
|
81
175
|
|
|
82
176
|
if validation.success?
|
|
83
|
-
Either::Right.new(
|
|
177
|
+
Either::Right.new(validation.generated_source.from_right)
|
|
84
178
|
else
|
|
85
179
|
Either::Left.new(validation)
|
|
86
180
|
end
|
|
87
181
|
end
|
|
88
182
|
|
|
89
|
-
# Unparse
|
|
90
|
-
#
|
|
91
|
-
# This is mostly useful for writing testing tools against unparser.
|
|
183
|
+
# Unparse with validation
|
|
92
184
|
#
|
|
93
185
|
# @param [Parser::AST::Node, nil] node
|
|
186
|
+
# @param [Array] comments
|
|
94
187
|
#
|
|
95
|
-
# @return [Either<
|
|
96
|
-
def self.
|
|
97
|
-
|
|
188
|
+
# @return [Either<Validation,String>]
|
|
189
|
+
def self.unparse_validate(node, comments: EMPTY_ARRAY)
|
|
190
|
+
generated = unparse(node, comments:)
|
|
191
|
+
validation = Validation.from_string(generated)
|
|
192
|
+
|
|
193
|
+
if validation.success?
|
|
194
|
+
Either::Right.new(generated)
|
|
195
|
+
else
|
|
196
|
+
Either::Left.new(validation)
|
|
197
|
+
end
|
|
98
198
|
end
|
|
99
199
|
|
|
100
200
|
# Parse string into AST
|
|
@@ -103,27 +203,37 @@ module Unparser
|
|
|
103
203
|
#
|
|
104
204
|
# @return [Parser::AST::Node, nil]
|
|
105
205
|
def self.parse(source)
|
|
106
|
-
|
|
206
|
+
parse_ast(source).node
|
|
107
207
|
end
|
|
108
208
|
|
|
109
209
|
# Parse string into either syntax error or AST
|
|
110
210
|
#
|
|
111
211
|
# @param [String] source
|
|
112
212
|
#
|
|
113
|
-
# @return [Either<
|
|
114
|
-
def self.
|
|
115
|
-
Either.wrap_error(
|
|
116
|
-
|
|
213
|
+
# @return [Either<Exception, (Parser::ASTNode, nil)>]
|
|
214
|
+
def self.parse_ast_either(source)
|
|
215
|
+
Either.wrap_error(Exception) do
|
|
216
|
+
parse_ast(source)
|
|
117
217
|
end
|
|
118
218
|
end
|
|
119
219
|
|
|
120
|
-
# Parse
|
|
220
|
+
# Parse source with ast details
|
|
121
221
|
#
|
|
122
222
|
# @param [String] source
|
|
123
223
|
#
|
|
124
|
-
# @return [
|
|
125
|
-
|
|
126
|
-
|
|
224
|
+
# @return [AST]
|
|
225
|
+
#
|
|
226
|
+
# mutant:disable
|
|
227
|
+
def self.parse_ast(source, static_local_variables: Set.new)
|
|
228
|
+
explicit_encoding = Parser::Source::Buffer.recognize_encoding(source.dup.force_encoding(Encoding::BINARY))
|
|
229
|
+
node, comments = parser.parse_with_comments(buffer(source))
|
|
230
|
+
|
|
231
|
+
AST.new(
|
|
232
|
+
comments: comments,
|
|
233
|
+
explicit_encoding: explicit_encoding,
|
|
234
|
+
node: node,
|
|
235
|
+
static_local_variables: static_local_variables
|
|
236
|
+
)
|
|
127
237
|
end
|
|
128
238
|
|
|
129
239
|
# Parser instance that produces AST unparser understands
|
|
@@ -131,8 +241,9 @@ module Unparser
|
|
|
131
241
|
# @return [Parser::Base]
|
|
132
242
|
#
|
|
133
243
|
# @api private
|
|
244
|
+
# mutant:disable
|
|
134
245
|
def self.parser
|
|
135
|
-
|
|
246
|
+
PARSER_CLASS.new(Builder.new).tap do |parser|
|
|
136
247
|
parser.diagnostics.tap do |diagnostics|
|
|
137
248
|
diagnostics.all_errors_are_fatal = true
|
|
138
249
|
end
|
|
@@ -179,6 +290,7 @@ require 'unparser/emitter/def'
|
|
|
179
290
|
require 'unparser/emitter/defined'
|
|
180
291
|
require 'unparser/emitter/dstr'
|
|
181
292
|
require 'unparser/emitter/dsym'
|
|
293
|
+
require 'unparser/emitter/ensure'
|
|
182
294
|
require 'unparser/emitter/flipflop'
|
|
183
295
|
require 'unparser/emitter/float'
|
|
184
296
|
require 'unparser/emitter/flow_modifier'
|
|
@@ -210,6 +322,7 @@ require 'unparser/emitter/rescue'
|
|
|
210
322
|
require 'unparser/emitter/root'
|
|
211
323
|
require 'unparser/emitter/send'
|
|
212
324
|
require 'unparser/emitter/simple'
|
|
325
|
+
require 'unparser/emitter/string'
|
|
213
326
|
require 'unparser/emitter/splat'
|
|
214
327
|
require 'unparser/emitter/super'
|
|
215
328
|
require 'unparser/emitter/undef'
|
|
@@ -222,8 +335,10 @@ require 'unparser/emitter/find_pattern'
|
|
|
222
335
|
require 'unparser/emitter/match_pattern'
|
|
223
336
|
require 'unparser/emitter/match_pattern_p'
|
|
224
337
|
require 'unparser/writer'
|
|
338
|
+
require 'unparser/writer/array'
|
|
225
339
|
require 'unparser/writer/binary'
|
|
226
340
|
require 'unparser/writer/dynamic_string'
|
|
341
|
+
require 'unparser/writer/regexp'
|
|
227
342
|
require 'unparser/writer/resbody'
|
|
228
343
|
require 'unparser/writer/rescue'
|
|
229
344
|
require 'unparser/writer/send'
|
|
@@ -234,6 +349,7 @@ require 'unparser/writer/send/unary'
|
|
|
234
349
|
require 'unparser/node_details'
|
|
235
350
|
require 'unparser/node_details/send'
|
|
236
351
|
require 'unparser/cli'
|
|
352
|
+
require 'unparser/util'
|
|
237
353
|
|
|
238
354
|
require 'unparser/validation'
|
|
239
355
|
# make it easy for zombie
|