Class: Ryac::Pipeline::Compactor
- Inherits:
-
Object
- Object
- Ryac::Pipeline::Compactor
- Defined in:
- lib/ryac/pipeline/compactor.rb,
sig/ryac/pipeline/compactor.rbs
Overview
Level 0: Compaction — full AST rebuild with no syntax optimizations. Removes comments and whitespace, joins with ";". Preserves original paren form. No renaming, no TypeProf needed. Self-contained — no shared mixins.
Constant Summary collapse
- BINARY_OPERATORS =
%i[== != < > <= >= + - * / % & | ^ << >> === =~ !~ <=>].freeze
- NON_ASSOCIATIVE_OPS =
%i[== === != =~ !~ <=>].to_set.freeze
- OPERATOR_PRECEDENCE =
{ :"|" => 1, :'^' => 1, :'&' => 2, :'<=>' => 3, :== => 3, :=== => 3, :!= => 3, :=~ => 3, :'!~' => 3, :> => 4, :>= => 4, :< => 4, :<= => 4, :<< => 5, :>> => 5, :+ => 6, :- => 6, :* => 7, :/ => 7, :% => 7, :** => 8 }.freeze
- COMPOUND_WRITE_NODES =
[ Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode, Prism::ClassVariableOperatorWriteNode, Prism::ClassVariableOrWriteNode, Prism::ClassVariableAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantPathOperatorWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathAndWriteNode, Prism::CallOperatorWriteNode, Prism::CallOrWriteNode, Prism::CallAndWriteNode, Prism::IndexOperatorWriteNode, Prism::IndexOrWriteNode, Prism::IndexAndWriteNode, ].freeze
- GROUP_MIN =
def self.xruns of GROUP_MIN or more become oneclass<<selfblock. Only CONSECUTIVE defs form a run — grouping across other statements would reorder their side effects. 4- MODIFIER_KEYWORD_NODES =
--- Return / Break / Next ---
[Prism::IfNode, Prism::UnlessNode, Prism::WhileNode, Prism::UntilNode].freeze
- PERCENT_SAFE_WORD =
A word must survive %w/%i verbatim: no whitespace, brackets, or backslash (those are the notation's escapes).
/\A[^\s\[\]\\]+\z/- ASSIGNMENT_NODES =
[ Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::ClassVariableWriteNode, Prism::GlobalVariableWriteNode, Prism::ConstantWriteNode, Prism::ConstantPathWriteNode, Prism::MultiWriteNode, *COMPOUND_WRITE_NODES, ].freeze
- PARENS_KEPT_NODES =
Match nodes keep parens like assignments do: an endless def body of
(value in pattern)stripped bare rebinds theinto the def statement itself and no longer parses. [*ASSIGNMENT_NODES, *AstUtils::MATCH_REBIND_NODES].freeze
- PARENS_KEPT_CLASSES =
PARENS_KEPT_NODES.to_set.freeze
- JUMP_NODES =
[Prism::ReturnNode, Prism::BreakNode, Prism::NextNode].freeze
- LOGIC_LOOSE_OPERANDS =
Operand classes that bind at or below the keyword logic forms — bare under && or || they would re-associate or fail to parse. Derived from the paren-keeping and modifier-keyword families, so a class added there joins this rule with it.
[ *PARENS_KEPT_NODES, *MODIFIER_KEYWORD_NODES, Prism::CaseNode, Prism::CaseMatchNode, Prism::ForNode, Prism::BeginNode, Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::FlipFlopNode, Prism::LambdaNode ].freeze
- LOGIC_LOOSE_OPERAND_CLASSES =
LOGIC_LOOSE_OPERANDS.to_set.freeze
Class Method Summary collapse
-
.jump_with_value?(node) ⇒ Boolean
A jump is loose only when it carries a value:
x&&returnparses,x&&return 5does not. -
.loose_logic_operand?(node, tight:) ⇒ Boolean
The one owner of "does this operand need parens under && / ||".
Instance Method Summary collapse
- #bare_standard_error?(exceptions) ⇒ Boolean
- #binary_op_separator(recv_node, recv_wrapped, op) ⇒ String
- #binary_operator_call?(node) ⇒ Boolean
- #block_uses_it?(body) ⇒ Boolean
-
#build_call_args(node, block_pass = nil) ⇒ String
--- Helpers ---.
- #build_def_params(params) ⇒ Array[String]
- #build_if_chain(node) ⇒ String
- #build_lambda_params(block_params) ⇒ String
- #call(input_string) ⇒ String
- #call_target(node) ⇒ String
- #compound(target, op, node) ⇒ String
- #escape_for_dquote(str) ⇒ String
- #escape_regexp_slash(content) ⇒ String
- #extract_block_params(block) ⇒ Array[String]
-
#flush_singleton_run(run) ⇒ Array[String]
Renders and clears the pending run of
def self.statements: one class<<self group when the run is long enough to pay for the wrapper, the plain renders otherwise. - #fmt_body(body) ⇒ String
- #idx_target(node) ⇒ String
- #interp_part(part, escape_quotes: false) ⇒ String
- #logic_operand(operand, tight:) ⇒ String
- #node_returns_bot?(node) ⇒ Boolean
-
#percent_array_form(node) ⇒ String?
The %-spelling of an all-static word array, whatever the source wrote — bracket, %w/%i, or a %W/%I that never interpolates.
- #prism_traverse(node) {|node| ... } ⇒ void
- #r(node) ⇒ String
-
#r_alias_name(node) ⇒ String
A plain symbol drops its colon (
alias a b); anything else — an interpolated symbol — must keep its own syntax. -
#r_and(node) ⇒ String
Always the operator spelling.
- #r_array(node) ⇒ String
-
#r_array_pattern(node) ⇒ String
--- Pattern matching ---.
- #r_assoc(element) ⇒ String
-
#r_begin(node) ⇒ String
--- Error handling ---.
- #r_binary_op(node) ⇒ String
- #r_block_call(node) ⇒ String?
- #r_break(node) ⇒ String
-
#r_call(node) ⇒ String
--- Method calls ---.
- #r_case(node) ⇒ String
- #r_case_match(node) ⇒ String
-
#r_class(node) ⇒ String
--- Classes / Modules ---.
-
#r_compound_write(node) ⇒ String
--- Compound assignments ---.
-
#r_def(node, drop_self_receiver: false) ⇒ String
drop_self_receiver: the def sits directly inside
class<<self, so its writtenself.disappears; anything nested in its body renders through the plain path and keeps its own receiver. - #r_defined(node) ⇒ String
- #r_delimited(node) ⇒ String
- #r_find_pattern(node) ⇒ String
-
#r_flipflop(node) ⇒ String
A flip-flop keeps its operands parenthesized — their own rendering, not a slice of the source, so interior spacing compacts like everything else.
-
#r_float(node) ⇒ String
The shortest spelling that denotes the same decimal value: such a respelling parses to the identical double, so only the bytes change.
- #r_for(node) ⇒ String
- #r_hash(node) ⇒ String
- #r_hash_pattern(node) ⇒ String
- #r_if(node) ⇒ String
-
#r_in_pattern(pattern) ⇒ String
in PATTERN if GUARDparses with the guard as an If/Unless node wrapping the pattern — rendering that as a statement-if corrupts the clause intoin if guard;pattern;end. - #r_index_access(node) ⇒ String
- #r_index_assign(node) ⇒ String
- #r_interp_regexp(node) ⇒ String
-
#r_interp_string(node) ⇒ String
--- Interpolation ---.
- #r_interp_symbol(node) ⇒ String
- #r_jump(keyword, arguments) ⇒ String
- #r_keyword_hash(node) ⇒ String
- #r_lambda(node) ⇒ String
- #r_module(node) ⇒ String
- #r_multi_target(target) ⇒ String
- #r_multi_target_param(target) ⇒ String
-
#r_multi_write(node) ⇒ String
--- Multi-write ---.
- #r_next(node) ⇒ String
- #r_or(node) ⇒ String
- #r_parens(node) ⇒ String
-
#r_pattern(node) ⇒ String
Patterns are their own little language:
nilthere is the nil pattern and()is a syntax error, so pattern positions dispatch through here. -
#r_range(node, bare: false) ⇒ String
Parenthesized by default:
..binds below almost everything, so in an unknown position the parens are what keep the parse. - #r_regexp(node) ⇒ String
- #r_return(node) ⇒ String
- #r_singleton_class(node) ⇒ String
- #r_stmt(nodes) ⇒ String
-
#r_string(node) ⇒ String
--- Strings ---.
-
#r_super(node) ⇒ String
--- Other ---.
-
#r_symbol(node) ⇒ String
--- Literals ---.
- #r_unary(node, op) ⇒ String
- #r_unless(node) ⇒ String
- #r_until(node) ⇒ String
- #r_while(node) ⇒ String
-
#r_yield(node) ⇒ String
--- Control flow ---.
-
#rebuild ⇒ Array[String]
--- Dispatch ---.
- #recv_with_op(recv_node, call_op) ⇒ String
- #render_statement_list(stmts) ⇒ Array[String]
- #rescue_var_used?(rescue_node) ⇒ Boolean
- #setter_call?(node) ⇒ Boolean
- #singleton_self_def?(node) ⇒ Boolean
-
#standard_error_redefined? ⇒ Boolean
Lazy: only a literal
rescue StandardErrorclause ever needs the answer, so most programs never pay for the walk. - #wrap_operand(node, parent_op, side) ⇒ [String, bool]
Class Method Details
.jump_with_value?(node) ⇒ Boolean
A jump is loose only when it carries a value: x&&return parses,
x&&return 5 does not.
1125 1126 1127 |
# File 'lib/ryac/pipeline/compactor.rb', line 1125 def self.jump_with_value?(node) JUMP_NODES.include?(node.class) && !node.arguments.nil? # steep:ignore NoMethod end |
.loose_logic_operand?(node, tight:) ⇒ Boolean
The one owner of "does this operand need parens under && / ||". ControlFlowSimplify asks the same question when it folds nested conditionals into the compacted dialect. An OrNode operand needs parens under && but not under ||. Prism node classes are concrete, so a class-keyed Set replaces an is_a? scan.
1134 1135 1136 1137 1138 |
# File 'lib/ryac/pipeline/compactor.rb', line 1134 def self.loose_logic_operand?(node, tight:) LOGIC_LOOSE_OPERAND_CLASSES.include?(node.class) || (tight && node.is_a?(Prism::OrNode)) || jump_with_value?(node) end |
Instance Method Details
#bare_standard_error?(exceptions) ⇒ Boolean
776 777 778 779 780 |
# File 'lib/ryac/pipeline/compactor.rb', line 776 def (exceptions) exceptions.size == 1 && exceptions[0].is_a?(Prism::ConstantReadNode) && exceptions[0].name == :StandardError end |
#binary_op_separator(recv_node, recv_wrapped, op) ⇒ String
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 |
# File 'lib/ryac/pipeline/compactor.rb', line 1057 def binary_op_separator(recv_node, recv_wrapped, op) return '' if recv_wrapped inner = AstUtils.unwrap_statements(recv_node) if op == :'!~' AstUtils.ends_with_name_char?(inner) ? ' ' : '' elsif op.to_s.start_with?('=') && inner.is_a?(Prism::CallNode) && inner.name.to_s.end_with?('?', '!') ' ' else '' end end |
#binary_operator_call?(node) ⇒ Boolean
1157 1158 1159 1160 |
# File 'lib/ryac/pipeline/compactor.rb', line 1157 def binary_operator_call?(node) node.is_a?(Prism::CallNode) && node.receiver && node.arguments&.arguments&.size == 1 && BINARY_OPERATORS.include?(node.name) end |
#block_uses_it?(body) ⇒ Boolean
1189 1190 1191 1192 1193 |
# File 'lib/ryac/pipeline/compactor.rb', line 1189 def block_uses_it?(body) return false unless body prism_traverse(body) { |n| return true if n.is_a?(Prism::ItLocalVariableReadNode) } false end |
#build_call_args(node, block_pass = nil) ⇒ String
--- Helpers ---
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 |
# File 'lib/ryac/pipeline/compactor.rb', line 925 def build_call_args(node, block_pass = nil) raw_args = node.arguments&.arguments || [] args = [] #: Array[String] raw_args.each do |arg| case arg when Prism::SplatNode args << (arg.expression ? "*#{r_delimited(arg.expression)}" : '*') when Prism::KeywordHashNode arg.elements.each do |el| args << (el.is_a?(Prism::AssocSplatNode) ? "**#{r(el.value)}" : r_assoc(el)) end when Prism::ForwardingArgumentsNode args << '...' else args << r_delimited(arg) end end args << block_pass if block_pass return '' if args.empty? "(#{args.join(',')})" end |
#build_def_params(params) ⇒ Array[String]
947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 |
# File 'lib/ryac/pipeline/compactor.rb', line 947 def build_def_params(params) return [] unless params req = (params.requireds || []).map { |p| case p when Prism::RequiredParameterNode then p.name.to_s when Prism::MultiTargetNode then r_multi_target_param(p) else p.slice end } opt = (params.optionals || []).map { |p| "#{p.name}=#{r(p.value)}" } rest = if params.rest.is_a?(Prism::RestParameterNode) [params.rest.name ? "*#{params.rest.name}" : '*'] else [] #: Array[String] end post = (params.posts || []).map { |p| p.is_a?(Prism::RequiredParameterNode) ? p.name.to_s : p.slice } req_kw = [] #: Array[String] opt_kw = [] #: Array[String] (params.keywords || []).each do |p| case p when Prism::RequiredKeywordParameterNode then req_kw << "#{p.name}:" when Prism::OptionalKeywordParameterNode val = r(p.value) sep = val.start_with?(':') ? ' ' : '' opt_kw << "#{p.name}:#{sep}#{val}" end end rest_kw = if params.keyword_rest.is_a?(Prism::KeywordRestParameterNode) [params.keyword_rest.name ? "**#{params.keyword_rest.name}" : '**'] elsif params.keyword_rest.is_a?(Prism::NoKeywordsParameterNode) ['**nil'] elsif params.keyword_rest.is_a?(Prism::ForwardingParameterNode) ['...'] else [] #: Array[String] end block_param = if params.block.is_a?(Prism::BlockParameterNode) [params.block.name ? "&#{params.block.name}" : '&'] else [] #: Array[String] end req + opt + rest + post + req_kw + opt_kw + rest_kw + block_param end |
#build_if_chain(node) ⇒ String
1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 |
# File 'lib/ryac/pipeline/compactor.rb', line 1069 def build_if_chain(node) parts = [] #: Array[String] current = node keyword = "if" loop do inner = current.statements ? AstUtils.unwrap_statements(current.statements) : nil then_body = (inner.nil? || inner.is_a?(Prism::NilNode)) ? nil : r_stmt(current.statements) parts << "#{keyword} #{r(current.predicate)}#{fmt_body(then_body)}" subsequent = current.subsequent if subsequent.is_a?(Prism::IfNode) current = subsequent keyword = "elsif" else if subsequent.is_a?(Prism::ElseNode) inner = subsequent.statements ? AstUtils.unwrap_statements(subsequent.statements) : nil unless inner.nil? || inner.is_a?(Prism::NilNode) eb = r_stmt(subsequent.statements) parts << "else#{fmt_body(eb)}" if eb && !eb.empty? end end break end end parts.join(";") + ";end" end |
#build_lambda_params(block_params) ⇒ String
1009 1010 1011 1012 1013 1014 1015 |
# File 'lib/ryac/pipeline/compactor.rb', line 1009 def build_lambda_params(block_params) return "" unless block_params.is_a?(Prism::BlockParametersNode) inner = block_params.parameters return "()" unless inner all = build_def_params(inner) all.empty? ? "" : "(#{all.join(',')})" end |
#call(input_string) ⇒ String
33 34 35 36 37 |
# File 'lib/ryac/pipeline/compactor.rb', line 33 def call(input_string) @prism_ast = Prism.parse(input_string).value @standard_error_redefined = nil rebuild.join(";") end |
#call_target(node) ⇒ String
473 474 475 476 477 478 |
# File 'lib/ryac/pipeline/compactor.rb', line 473 def call_target(node) recv_str = r(node.receiver) base = node.read_name.to_s.delete_suffix('=') call_op = node. ? '&.' : '.' "#{recv_str}#{call_op}#{base}" end |
#compound(target, op, node) ⇒ String
471 |
# File 'lib/ryac/pipeline/compactor.rb', line 471 def compound(target, op, node) = "#{target}#{op}=#{r(node.value)}" |
#escape_for_dquote(str) ⇒ String
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 |
# File 'lib/ryac/pipeline/compactor.rb', line 684 def escape_for_dquote(str) result = +"" i = 0 while i < str.size c = str[i] case c when '\\' then result << '\\\\' when '"' then result << '\\"' when "\n" then result << '\\n' when "\t" then result << '\\t' when "\r" then result << '\\r' when "\0" then result << '\\0' when '#' if i + 1 < str.size && '{@$'.include?(str[i + 1]) # steep:ignore ArgumentTypeMismatch result << '\\#' else result << '#' end else result << c # steep:ignore ArgumentTypeMismatch end i += 1 end result end |
#escape_regexp_slash(content) ⇒ String
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 |
# File 'lib/ryac/pipeline/compactor.rb', line 509 def escape_regexp_slash(content) result = +"" i = 0 while i < content.size if content[i] == '\\' result << content[i] # steep:ignore ArgumentTypeMismatch i += 1 result << content[i] if i < content.size # steep:ignore ArgumentTypeMismatch i += 1 elsif content[i] == '/' result << '\\/' i += 1 else result << content[i] # steep:ignore ArgumentTypeMismatch i += 1 end end result end |
#extract_block_params(block) ⇒ Array[String]
1017 1018 1019 1020 |
# File 'lib/ryac/pipeline/compactor.rb', line 1017 def extract_block_params(block) return [] unless block.parameters.is_a?(Prism::BlockParametersNode) && block.parameters.parameters build_def_params(block.parameters.parameters) end |
#flush_singleton_run(run) ⇒ Array[String]
Renders and clears the pending run of def self. statements: one
class<<self group when the run is long enough to pay for the
wrapper, the plain renders otherwise.
180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/ryac/pipeline/compactor.rb', line 180 def flush_singleton_run(run) rendered = if run.size >= GROUP_MIN inner = run.map { |n| r_def(n, drop_self_receiver: true) } inner.reject! { |s| s.nil? || s.empty? } ["class<<self;#{inner.join(';')};end"] else run.map { |n| r(n) }.reject { |s| s.nil? || s.empty? } end run.clear rendered end |
#fmt_body(body) ⇒ String
1155 |
# File 'lib/ryac/pipeline/compactor.rb', line 1155 def fmt_body(body) = body.nil? || body.empty? ? "" : ";#{body}" |
#idx_target(node) ⇒ String
480 481 482 483 |
# File 'lib/ryac/pipeline/compactor.rb', line 480 def idx_target(node) keys = (node.arguments&.arguments || []).map { |a| r_delimited(a) }.join(',') "#{r(node.receiver)}[#{keys}]" end |
#interp_part(part, escape_quotes: false) ⇒ String
670 671 672 673 674 675 676 677 678 679 680 681 682 |
# File 'lib/ryac/pipeline/compactor.rb', line 670 def interp_part(part, escape_quotes: false) case part when Prism::StringNode if escape_quotes escape_for_dquote(part.unescaped) else part.content end when Prism::EmbeddedStatementsNode then "\#{#{r_stmt(part.statements)}}" when Prism::EmbeddedVariableNode then "\#{#{r(part.variable)}}" else "\#{#{r(part)}}" end end |
#logic_operand(operand, tight:) ⇒ String
742 743 744 745 746 747 |
# File 'lib/ryac/pipeline/compactor.rb', line 742 def logic_operand(operand, tight:) # Classified through source parens: `x&&(y||z)` arrives as a # ParenthesesNode operand whose parens must survive. inner = AstUtils.unwrap_statements(operand) Compactor.loose_logic_operand?(inner, tight: tight) ? "(#{r(inner)})" : r(operand) end |
#node_returns_bot?(node) ⇒ Boolean
1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 |
# File 'lib/ryac/pipeline/compactor.rb', line 1168 def node_returns_bot?(node) case node when Prism::ReturnNode, Prism::BreakNode, Prism::NextNode then true when Prism::CallNode # Only the Kernel spellings end the flow; `logger.raise(1)` is an # ordinary method call. node.receiver.nil? && (node.name == :raise || node.name == :fail) else false end end |
#percent_array_form(node) ⇒ String?
The %-spelling of an all-static word array, whatever the source wrote — bracket, %w/%i, or a %W/%I that never interpolates. nil when the elements aren't uniformly plain words; the caller keeps whichever spelling is shorter (bracket on ties, so single-char strings stay available to CharShorten).
546 547 548 549 550 551 552 553 554 555 556 557 |
# File 'lib/ryac/pipeline/compactor.rb', line 546 def percent_array_form(node) elements = node.elements words, kind = if elements.all? { |e| e.is_a?(Prism::SymbolNode) } [elements.map { |e| e.value.to_s }, 'i'] # steep:ignore NoMethod elsif elements.all? { |e| e.is_a?(Prism::StringNode) } [elements.map(&:unescaped), 'w'] # steep:ignore BlockTypeMismatch end return nil unless words&.any? return nil unless words.all? { |w| w.match?(PERCENT_SAFE_WORD) } "%#{kind}[#{words.join(' ')}]" end |
#prism_traverse(node) {|node| ... } ⇒ void
This method returns an undefined value.
1195 1196 1197 1198 1199 |
# File 'lib/ryac/pipeline/compactor.rb', line 1195 def prism_traverse(node, &block) return unless node yield node node.compact_child_nodes.each { |child| prism_traverse(child, &block) } end |
#r(node) ⇒ String
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/ryac/pipeline/compactor.rb', line 47 def r(node) case node when Prism::CallNode then r_call(node) when Prism::DefNode then r_def(node) when Prism::YieldNode then r_yield(node) when Prism::IfNode then r_if(node) when Prism::UnlessNode then r_unless(node) when Prism::WhileNode then r_while(node) when Prism::CaseNode then r_case(node) when Prism::ReturnNode then r_return(node) when Prism::ClassNode then r_class(node) when Prism::ModuleNode then r_module(node) when Prism::AndNode then r_and(node) when Prism::FlipFlopNode then r_flipflop(node) when Prism::OrNode then r_or(node) when Prism::LocalVariableWriteNode then "#{node.name}=#{r(node.value)}" when Prism::InstanceVariableWriteNode, Prism::GlobalVariableWriteNode, Prism::ClassVariableWriteNode then "#{node.name}=#{r(node.value)}" when Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode, Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode, Prism::GlobalVariableAndWriteNode, Prism::ClassVariableOperatorWriteNode, Prism::ClassVariableOrWriteNode, Prism::ClassVariableAndWriteNode, Prism::ConstantOperatorWriteNode, Prism::ConstantOrWriteNode, Prism::ConstantAndWriteNode, Prism::ConstantPathOperatorWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathAndWriteNode, Prism::CallOperatorWriteNode, Prism::CallOrWriteNode, Prism::CallAndWriteNode, Prism::IndexOperatorWriteNode, Prism::IndexOrWriteNode, Prism::IndexAndWriteNode r_compound_write(node) when Prism::ConstantPathNode node.parent ? "#{r(node.parent)}::#{node.name}" : "::#{node.name}" when Prism::ConstantWriteNode then "#{node.name}=#{r(node.value)}" when Prism::ConstantPathWriteNode then "#{r(node.target)}=#{r(node.value)}" when Prism::StringNode then r_string(node) when Prism::IntegerNode then node.value.to_s when Prism::FloatNode then r_float(node) when Prism::ArrayNode then r_array(node) when Prism::RangeNode then r_range(node) when Prism::SymbolNode then r_symbol(node) when Prism::HashNode then r_hash(node) when Prism::InterpolatedStringNode then r_interp_string(node) when Prism::InterpolatedSymbolNode then r_interp_symbol(node) when Prism::InterpolatedRegularExpressionNode then r_interp_regexp(node) when Prism::RegularExpressionNode then r_regexp(node) when Prism::StatementsNode then r_stmt(node) when Prism::ParenthesesNode then r_parens(node) when Prism::SuperNode then r_super(node) when Prism::BeginNode then r_begin(node) when Prism::RescueModifierNode then "(#{r(node.expression)} rescue #{r(node.rescue_expression)})" when Prism::BreakNode then r_break(node) when Prism::NextNode then r_next(node) when Prism::SplatNode then node.expression ? "*#{r_delimited(node.expression)}" : '*' when Prism::UntilNode then r_until(node) when Prism::AliasMethodNode then "alias #{r_alias_name(node.new_name)} #{r_alias_name(node.old_name)}" when Prism::UndefNode then "undef #{node.names.map { |n| r_alias_name(n) }.join(',')}" when Prism::LambdaNode then r_lambda(node) when Prism::PostExecutionNode then "END{#{r_stmt(node.statements)}}" when Prism::MultiWriteNode then r_multi_write(node) when Prism::MatchWriteNode then r(node.call) when Prism::DefinedNode then r_defined(node) when Prism::ForNode then r_for(node) when Prism::MatchRequiredNode then "#{r(node.value)}=>#{r_pattern(node.pattern)}" when Prism::MatchPredicateNode then "#{r(node.value)} in #{r_pattern(node.pattern)}" when Prism::SingletonClassNode then r_singleton_class(node) when Prism::CaseMatchNode then r_case_match(node) when Prism::ArrayPatternNode then r_array_pattern(node) when Prism::HashPatternNode then r_hash_pattern(node) when Prism::FindPatternNode then r_find_pattern(node) when Prism::AlternationPatternNode, Prism::CapturePatternNode then r_pattern(node) when Prism::PinnedVariableNode then "^#{r(node.variable)}" when Prism::PinnedExpressionNode then "^(#{r_delimited(node.expression)})" when Prism::ImplicitNode then r(node.value) when Prism::ShareableConstantNode then r(node.write) when Prism::CallTargetNode then "#{r(node.receiver)}.#{node.name.to_s.chomp('=')}" when Prism::KeywordHashNode then r_keyword_hash(node) # nil spelled as the empty grouped expression: same value, one byte # shorter. Pattern positions reject it and go through r_pattern. when Prism::NilNode then '()' when Prism::TrueNode, Prism::FalseNode, Prism::SelfNode, Prism::RedoNode, Prism::RetryNode, Prism::ForwardingSuperNode, Prism::ItLocalVariableReadNode, Prism::NumberedReferenceReadNode, Prism::SourceEncodingNode, Prism::SourceFileNode, Prism::SourceLineNode, Prism::LocalVariableReadNode, Prism::LocalVariableTargetNode, Prism::InstanceVariableReadNode, Prism::GlobalVariableReadNode, Prism::ClassVariableReadNode, Prism::ConstantReadNode, Prism::RationalNode, Prism::ImaginaryNode, Prism::MatchLastLineNode, Prism::InterpolatedMatchLastLineNode, Prism::BackReferenceReadNode, Prism::AliasGlobalVariableNode node.slice else raise Ryac::MinifyError, "Unknown node: #{node.class}" end end |
#r_alias_name(node) ⇒ String
A plain symbol drops its colon (alias a b); anything else — an
interpolated symbol — must keep its own syntax.
441 |
# File 'lib/ryac/pipeline/compactor.rb', line 441 def r_alias_name(node) = node.is_a?(Prism::SymbolNode) ? node.value.to_s : r(node) |
#r_and(node) ⇒ String
Always the operator spelling. The keyword forms bind below
assignment, so and/or accept operands &&/|| cannot take
bare — assignments, jumps with a value, control expressions — and
those operands are parenthesized here. Rendering the keyword would
need the surrounding position to be known (z=(a and b) unwrapped
is a different program), and the renderer is position-free.
734 735 736 |
# File 'lib/ryac/pipeline/compactor.rb', line 734 def r_and(node) "#{logic_operand(node.left, tight: true)}&&#{logic_operand(node.right, tight: true)}" end |
#r_array(node) ⇒ String
535 536 537 538 539 |
# File 'lib/ryac/pipeline/compactor.rb', line 535 def r_array(node) bracket = "[#{node.elements.map { r_delimited(_1) }.join(',')}]" percent = percent_array_form(node) percent && percent.bytesize < bracket.bytesize ? percent : bracket end |
#r_array_pattern(node) ⇒ String
--- Pattern matching ---
876 877 878 879 880 881 882 883 884 885 886 887 |
# File 'lib/ryac/pipeline/compactor.rb', line 876 def r_array_pattern(node) parts = node.requireds.map { |n| r_pattern(n) } if node.rest parts << if node.rest.is_a?(Prism::SplatNode) && node.rest.expression "*#{r(node.rest.expression)}" else '*' end end parts.concat(node.posts.map { |p| r_pattern(p) }) "[#{parts.join(',')}]" end |
#r_assoc(element) ⇒ String
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 |
# File 'lib/ryac/pipeline/compactor.rb', line 617 def r_assoc(element) key = element.key val = element.value val_str = r_delimited(val) if key.is_a?(Prism::SymbolNode) && key.value.to_s.match?(/\A[a-zA-Z_][a-zA-Z0-9_]*\z/) key_name = key.value.to_s return "#{key_name}:" if val.is_a?(Prism::ImplicitNode) sep = val_str.start_with?(':') ? ': ' : ':' "#{key_name}#{sep}#{val_str}" else key_str = r(key) separator = if key.is_a?(Prism::SymbolNode) && !key.value.to_s.match?(/[a-zA-Z0-9_]\z/) ' =>' else '=>' end "#{key_str}#{separator}#{val_str}" end end |
#r_begin(node) ⇒ String
--- Error handling ---
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 |
# File 'lib/ryac/pipeline/compactor.rb', line 751 def r_begin(node) parts = ["begin#{fmt_body(r_stmt(node.statements))}"] rescue_node = node.rescue_clause while rescue_node rp = ';rescue' exceptions = rescue_node.exceptions || [] # `rescue StandardError` is what a bare rescue already means — # unless the program redefines StandardError, when the bare form # would resolve differently. unless exceptions.empty? || ((exceptions) && !standard_error_redefined?) rp += " #{exceptions.map { |e| r(e) }.join(',')}" end if rescue_node.reference && rescue_var_used?(rescue_node) # the reference is a name-bearing target node in valid rescue clauses rp += "=>#{rescue_node.reference.name}" # steep:ignore NoMethod end parts << "#{rp}#{fmt_body(r_stmt(rescue_node.statements))}" rescue_node = rescue_node.subsequent end parts << ";else#{fmt_body(r_stmt(node.else_clause.statements))}" if node.else_clause parts << ";ensure#{fmt_body(r_stmt(node.ensure_clause.statements))}" if node.ensure_clause parts << ';end' parts.join end |
#r_binary_op(node) ⇒ String
221 222 223 224 225 226 227 228 |
# File 'lib/ryac/pipeline/compactor.rb', line 221 def r_binary_op(node) recv_str, recv_wrapped = wrap_operand(node.receiver, node.name, :left) # binary operator calls always carry exactly one argument arg_str, = wrap_operand(node.arguments.arguments.first, node.name, :right) # steep:ignore NoMethod op = node.name sep = binary_op_separator(node.receiver, recv_wrapped, op) "#{recv_str}#{sep}#{op}#{arg_str}" end |
#r_block_call(node) ⇒ String?
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/ryac/pipeline/compactor.rb', line 230 def r_block_call(node) method_name = node.name call_op = node. ? '&.' : '.' block = node.block if block.is_a?(Prism::BlockNode) block_params = extract_block_params(block) uses_numbered = block_params.any? { |p| p.match?(/^_\d+$/) } block_params_str = if block_params.empty? || uses_numbered || block_uses_it?(block.body) "" else "|#{block_params.join(',')}|" end "#{recv_with_op(node.receiver, call_op)}#{method_name}#{build_call_args(node)}{#{block_params_str}#{r_stmt(block.body)}}" elsif block.is_a?(Prism::BlockArgumentNode) bp = block.expression ? "&#{r(block.expression)}" : "&" "#{recv_with_op(node.receiver, call_op)}#{method_name}#{build_call_args(node, bp)}" end end |
#r_break(node) ⇒ String
437 |
# File 'lib/ryac/pipeline/compactor.rb', line 437 def r_break(node) = r_jump('break', node.arguments) |
#r_call(node) ⇒ String
--- Method calls ---
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/ryac/pipeline/compactor.rb', line 194 def r_call(node) return r_binary_op(node) if AstUtils.middle_method?(node.name) # has_block? guarantees a BlockNode/BlockArgumentNode, so r_block_call # cannot actually return nil here. return r_block_call(node) if AstUtils.has_block?(node) # steep:ignore ReturnTypeMismatch return r_index_access(node) if node.name == :'[]' return r_index_assign(node) if node.name == :'[]=' return r_unary(node, '!') if node.name == :'!' return r_unary(node, node.name == :'-@' ? '-' : '+') if node.name == :'-@' || node.name == :'+@' call_op = node. ? '&.' : '.' # `x.call(args)` has proc-call sugar: `x.(args)` dispatches to the # exact same method on any receiver. Erasing the message this early # is sound only because :call sits in EXCLUDED_METHODS — no renamer # ever needs to find these sites by name. if node.name == :call && node.receiver raw_args = node.arguments&.arguments || [] return "#{recv_with_op(node.receiver, call_op)}(#{raw_args.map { |a| r_delimited(a) }.join(',')})" end method_name = node.name if setter_call?(node) # setter_call? implies exactly one argument is present "#{recv_with_op(node.receiver, call_op)}#{method_name}#{r(node.arguments.arguments.first)}" # steep:ignore NoMethod else "#{recv_with_op(node.receiver, call_op)}#{method_name}#{build_call_args(node)}" end end |
#r_case(node) ⇒ String
369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/ryac/pipeline/compactor.rb', line 369 def r_case(node) predicate = node.predicate ? " #{r(node.predicate)}" : '' body = "case#{predicate};" + node.conditions.map { |wn| conditions = wn.conditions.map { |c| r_delimited(c) }.join(',') "when #{conditions}#{fmt_body(r_stmt(wn.statements))}" }.join(";") if node.else_clause eb = node.else_clause.statements ? r_stmt(node.else_clause.statements) : nil body += ";else#{fmt_body(eb)}" end body + ";end" end |
#r_case_match(node) ⇒ String
382 383 384 385 386 387 388 389 390 391 |
# File 'lib/ryac/pipeline/compactor.rb', line 382 def r_case_match(node) body = "case #{r(node.predicate)};" body += node.conditions.map { |in_node| "in #{r_in_pattern(in_node.pattern)}#{fmt_body(r_stmt(in_node.statements))}" }.join(";") if node.else_clause body += ";else#{fmt_body(r_stmt(node.else_clause.statements))}" end body + ";end" end |
#r_class(node) ⇒ String
--- Classes / Modules ---
712 713 714 715 716 |
# File 'lib/ryac/pipeline/compactor.rb', line 712 def r_class(node) class_name = node.constant_path.slice superclass_str = node.superclass ? "<#{r(node.superclass)}" : '' "class #{class_name}#{superclass_str}#{fmt_body(r_stmt(node.body))};end" end |
#r_compound_write(node) ⇒ String
--- Compound assignments ---
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 |
# File 'lib/ryac/pipeline/compactor.rb', line 455 def r_compound_write(node) target = case node when Prism::ConstantPathOperatorWriteNode, Prism::ConstantPathOrWriteNode, Prism::ConstantPathAndWriteNode r(node.target) when Prism::CallOperatorWriteNode, Prism::CallOrWriteNode, Prism::CallAndWriteNode call_target(node) when Prism::IndexOperatorWriteNode, Prism::IndexOrWriteNode, Prism::IndexAndWriteNode idx_target(node) else node.name end # binary_operator exists exactly on the *Operator* variants selected here op = node.class.name.include?('Operator') ? node.binary_operator : (node.class.name.include?('Or') ? '||' : '&&') # steep:ignore NoMethod compound(target, op, node) end |
#r_def(node, drop_self_receiver: false) ⇒ String
drop_self_receiver: the def sits directly inside class<<self, so
its written self. disappears; anything nested in its body renders
through the plain path and keeps its own receiver.
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/ryac/pipeline/compactor.rb', line 281 def r_def(node, drop_self_receiver: false) method_name = node.name receiver_prefix = if node.receiver if drop_self_receiver && node.receiver.is_a?(Prism::SelfNode) "" else "#{r(node.receiver)}." end else "" end all_params = build_def_params(node.parameters) body_node = node.body if body_node.is_a?(Prism::StatementsNode) && body_node.body.size == 1 && body_node.body.first.is_a?(Prism::ParenthesesNode) body_node = body_node.body.first.body # steep:ignore NoMethod end # A sole-nil body and an empty body are the same method, and the # empty spelling is what `def f =()` parses back into — emitting it # directly keeps re-minification at a fixed point. if body_node.is_a?(Prism::StatementsNode) && body_node.body.size == 1 && body_node.body.first.is_a?(Prism::NilNode) body_node = nil end body = r_stmt(body_node) params_str = all_params.empty? ? "" : "(#{all_params.join(',')})" "def #{receiver_prefix}#{method_name}#{params_str}#{fmt_body(body)};end" end |
#r_defined(node) ⇒ String
816 817 818 |
# File 'lib/ryac/pipeline/compactor.rb', line 816 def r_defined(node) "defined?(#{r(node.value)})" end |
#r_delimited(node) ⇒ String
590 591 592 593 |
# File 'lib/ryac/pipeline/compactor.rb', line 590 def r_delimited(node) inner = AstUtils.unwrap_statements(node) inner.is_a?(Prism::RangeNode) ? r_range(inner, bare: true) : r(node) end |
#r_find_pattern(node) ⇒ String
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 |
# File 'lib/ryac/pipeline/compactor.rb', line 903 def r_find_pattern(node) parts = [] #: Array[String] if node.left parts << if node.left.is_a?(Prism::SplatNode) && node.left.expression "*#{r(node.left.expression)}" else '*' end end parts.concat(node.requireds.map { |n| r_pattern(n) }) if node.right parts << if node.right.is_a?(Prism::SplatNode) && node.right.expression "*#{r(node.right.expression)}" else '*' end end "[#{parts.join(',')}]" end |
#r_flipflop(node) ⇒ String
A flip-flop keeps its operands parenthesized — their own rendering, not a slice of the source, so interior spacing compacts like everything else.
598 599 600 601 602 603 |
# File 'lib/ryac/pipeline/compactor.rb', line 598 def r_flipflop(node) op = node.exclude_end? ? '...' : '..' left = node.left ? "(#{r(node.left)})" : '' right = node.right ? "(#{r(node.right)})" : '' "#{left}#{op}#{right}" end |
#r_float(node) ⇒ String
The shortest spelling that denotes the same decimal value: such a respelling parses to the identical double, so only the bytes change. One shape covers plain and exponent forms — pull the digits together, fold leading/trailing zeros into the exponent — and the final to_f comparison is a belt-and-suspenders check.
564 565 566 567 568 569 570 571 572 573 574 575 576 577 |
# File 'lib/ryac/pipeline/compactor.rb', line 564 def r_float(node) s = node.value.to_s m = s.match(/\A(-?)(\d+)\.(\d+)(?:e([+-]?\d+))?\z/) return s unless m frac = m[3].to_s exp = m[4].to_i - frac.size digits = "#{m[2]}#{frac}".sub(/\A0+/, '') digits = digits.sub(/0+\z/) { |z| exp += z.size; '' } return s if digits.empty? || exp == 0 cand = "#{m[1]}#{digits}e#{exp}" cand.size < s.size && cand.to_f == node.value ? cand : s end |
#r_for(node) ⇒ String
426 427 428 429 430 |
# File 'lib/ryac/pipeline/compactor.rb', line 426 def r_for(node) idx = node.index target = idx.is_a?(Prism::LocalVariableTargetNode) ? idx.name.to_s : idx.slice "for #{target} in #{r(node.collection)}#{fmt_body(r_stmt(node.statements))};end" end |
#r_hash(node) ⇒ String
605 606 607 608 609 |
# File 'lib/ryac/pipeline/compactor.rb', line 605 def r_hash(node) "{" + node.elements.map { |e| e.is_a?(Prism::AssocSplatNode) ? "**#{r(e.value)}" : r_assoc(e) }.join(',') + "}" end |
#r_hash_pattern(node) ⇒ String
889 890 891 892 893 894 895 896 897 898 899 900 901 |
# File 'lib/ryac/pipeline/compactor.rb', line 889 def r_hash_pattern(node) # hash-pattern keys are always plain SymbolNodes parts = node.elements.map { |a| "#{a.key.value}: #{r_pattern(a.value)}" } # steep:ignore NoMethod if node.rest case node.rest when Prism::AssocSplatNode parts << (node.rest.value ? "**#{r(node.rest.value)}" : '**') when Prism::NoKeywordsParameterNode parts << '**nil' end end "{#{parts.join(',')}}" end |
#r_if(node) ⇒ String
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/ryac/pipeline/compactor.rb', line 324 def r_if(node) then_body = r_stmt(node.statements) else_node = node.subsequent else_body = case else_node when nil then nil when Prism::IfNode then r(else_node) when Prism::ElseNode inner = else_node.statements ? AstUtils.unwrap_statements(else_node.statements) : nil (inner.nil? || inner.is_a?(Prism::NilNode)) ? nil : r_stmt(else_node.statements) end if else_body.nil? "if #{r(node.predicate)};#{then_body || ''};end" elsif else_node.is_a?(Prism::IfNode) build_if_chain(node) else "if #{r(node.predicate)};#{then_body || ''};else;#{else_body};end" end end |
#r_in_pattern(pattern) ⇒ String
in PATTERN if GUARD parses with the guard as an If/Unless node
wrapping the pattern — rendering that as a statement-if corrupts the
clause into in if guard;pattern;end. Guards cannot nest, so the
wrapped node is always a plain pattern.
397 398 399 400 401 402 403 404 405 406 |
# File 'lib/ryac/pipeline/compactor.rb', line 397 def r_in_pattern(pattern) case pattern when Prism::IfNode, Prism::UnlessNode keyword = pattern.is_a?(Prism::IfNode) ? 'if' : 'unless' # a guard's IfNode always wraps a non-empty statements body "#{r_pattern(pattern.statements.body[0])} #{keyword} #{r(pattern.predicate)}" # steep:ignore NoMethod else r_pattern(pattern) end end |
#r_index_access(node) ⇒ String
249 250 251 252 253 254 255 256 257 |
# File 'lib/ryac/pipeline/compactor.rb', line 249 def r_index_access(node) raw_args = node.arguments&.arguments || [] args_str = raw_args.map { |a| r_delimited(a) }.join(',') if node. "#{r(node.receiver)}&.[](#{args_str})" else "#{r(node.receiver)}[#{args_str}]" end end |
#r_index_assign(node) ⇒ String
259 260 261 262 263 264 265 266 267 |
# File 'lib/ryac/pipeline/compactor.rb', line 259 def r_index_assign(node) raw_args = node.arguments&.arguments || [] if node. "#{r(node.receiver)}&.[]=(#{raw_args.map { |a| r_delimited(a) }.join(',')})" else keys = raw_args[0..-2].map { |a| r_delimited(a) }.join(',') # steep:ignore NoMethod "#{r(node.receiver)}[#{keys}]=#{r(raw_args.last)}" end end |
#r_interp_regexp(node) ⇒ String
658 659 660 661 662 663 664 665 666 667 668 |
# File 'lib/ryac/pipeline/compactor.rb', line 658 def r_interp_regexp(node) needs_slash_escape = node.opening != '/' parts = node.parts.map { |p| if needs_slash_escape && p.is_a?(Prism::StringNode) escape_regexp_slash(p.content) else interp_part(p) end } "/" + parts.join + "/#{node.closing[1..]}" end |
#r_interp_string(node) ⇒ String
--- Interpolation ---
649 650 651 652 |
# File 'lib/ryac/pipeline/compactor.rb', line 649 def r_interp_string(node) needs_escape = node.opening != '"' "\"" + node.parts.map { |p| interp_part(p, escape_quotes: needs_escape) }.join + "\"" end |
#r_interp_symbol(node) ⇒ String
654 655 656 |
# File 'lib/ryac/pipeline/compactor.rb', line 654 def r_interp_symbol(node) ":\"" + node.parts.map { |p| interp_part(p) }.join + "\"" end |
#r_jump(keyword, arguments) ⇒ String
444 445 446 447 448 449 450 451 |
# File 'lib/ryac/pipeline/compactor.rb', line 444 def r_jump(keyword, arguments) args = arguments&.arguments return keyword if args.nil? || args.empty? result = args.map { |a| r_delimited(a) }.join(',') first = args.first unwrapped = first.is_a?(Prism::ParenthesesNode) ? AstUtils.unwrap_statements(first) : first MODIFIER_KEYWORD_NODES.any? { |t| unwrapped.is_a?(t) } ? "#{keyword}(#{result})" : "#{keyword} #{result}" end |
#r_keyword_hash(node) ⇒ String
611 612 613 614 615 |
# File 'lib/ryac/pipeline/compactor.rb', line 611 def r_keyword_hash(node) node.elements.map { |e| e.is_a?(Prism::AssocSplatNode) ? "**#{r(e.value)}" : r_assoc(e) }.join(',') end |
#r_lambda(node) ⇒ String
310 311 312 313 314 |
# File 'lib/ryac/pipeline/compactor.rb', line 310 def r_lambda(node) params_str = build_lambda_params(node.parameters) body = r_stmt(node.body) "->#{params_str}{#{body}}" end |
#r_module(node) ⇒ String
718 719 720 |
# File 'lib/ryac/pipeline/compactor.rb', line 718 def r_module(node) "module #{node.constant_path.slice}#{fmt_body(r_stmt(node.body))};end" end |
#r_multi_target(target) ⇒ String
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 |
# File 'lib/ryac/pipeline/compactor.rb', line 843 def r_multi_target(target) case target when Prism::LocalVariableTargetNode, Prism::InstanceVariableTargetNode, Prism::ClassVariableTargetNode, Prism::GlobalVariableTargetNode target.name.to_s when Prism::ConstantTargetNode target.name.to_s when Prism::ConstantPathTargetNode target.parent ? "#{r(target.parent)}::#{target.name}" : "::#{target.name}" when Prism::MultiTargetNode parts = target.lefts.map { |t| r_multi_target(t) } if target.rest case target.rest when Prism::SplatNode parts << (target.rest.expression ? "*#{r_multi_target(target.rest.expression)}" : '*') when Prism::ImplicitRestNode parts << '*' end end parts.concat(target.rights.map { |t| r_multi_target(t) }) parts.join(',') when Prism::IndexTargetNode args = target.arguments&.arguments || [] "#{r(target.receiver)}[#{args.map { |a| r(a) }.join(',')}]" when Prism::SplatNode target.expression ? "*#{r_multi_target(target.expression)}" : '*' else r(target) end end |
#r_multi_target_param(target) ⇒ String
994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 |
# File 'lib/ryac/pipeline/compactor.rb', line 994 def r_multi_target_param(target) parts = target.lefts.map { |t| case t when Prism::RequiredParameterNode then t.name.to_s when Prism::MultiTargetNode then r_multi_target_param(t) else t.slice end } if target.rest parts << (target.rest.is_a?(Prism::SplatNode) && target.rest.expression ? "*#{target.rest.expression.slice}" : '*') end parts.concat(target.rights.map { |t| t.respond_to?(:name) ? t.name.to_s : t.slice }) # steep:ignore NoMethod "(#{parts.join(',')})" end |
#r_multi_write(node) ⇒ String
--- Multi-write ---
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 |
# File 'lib/ryac/pipeline/compactor.rb', line 822 def r_multi_write(node) targets = node.lefts.map { |t| r_multi_target(t) } if node.rest case node.rest when Prism::SplatNode targets << (node.rest.expression ? "*#{r_multi_target(node.rest.expression)}" : '*') when Prism::ImplicitRestNode targets << '*' end end targets.concat(node.rights.map { |t| r_multi_target(t) }) value = node.value sep = targets.last&.end_with?('*') ? ' ' : '' if value.is_a?(Prism::ArrayNode) && (value.opening_loc.nil? || value.elements.size >= 2) elements = value.elements.map { |e| r(e) } "#{targets.join(',')}#{sep}=#{elements.join(',')}" else "#{targets.join(',')}#{sep}=#{r(value)}" end end |
#r_next(node) ⇒ String
442 |
# File 'lib/ryac/pipeline/compactor.rb', line 442 def r_next(node) = r_jump('next', node.arguments) |
#r_or(node) ⇒ String
738 739 740 |
# File 'lib/ryac/pipeline/compactor.rb', line 738 def r_or(node) "#{logic_operand(node.left, tight: false)}||#{logic_operand(node.right, tight: false)}" end |
#r_parens(node) ⇒ String
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 |
# File 'lib/ryac/pipeline/compactor.rb', line 1140 def r_parens(node) body = r_stmt(node.body) # Empty parens ARE the nil literal this renderer emits — keeping # them (instead of collapsing to '') is what makes a second pass # over already-minified source round-trip. return '()' if body.empty? inner = AstUtils.unwrap_statements(node) keep = (node.body.is_a?(Prism::StatementsNode) && node.body.body.size > 1) || PARENS_KEPT_CLASSES.include?(inner.class) || # `(return 5)` bare after `&&` (or another operator) fails # to parse; a valueless jump is fine bare. Compactor.jump_with_value?(inner) keep ? "(#{body})" : body end |
#r_pattern(node) ⇒ String
Patterns are their own little language: nil there is the nil
pattern and () is a syntax error, so pattern positions dispatch
through here. Anything that reads the same in both languages —
other literals, constants, pins, capture targets — falls through
to r.
413 414 415 416 417 418 419 420 421 422 423 424 |
# File 'lib/ryac/pipeline/compactor.rb', line 413 def r_pattern(node) case node when Prism::NilNode then 'nil' when Prism::ParenthesesNode # pattern parens always wrap exactly one pattern inner = AstUtils.unwrap_statements(node) #: Prism::Node "(#{r_pattern(inner)})" when Prism::AlternationPatternNode then "#{r_pattern(node.left)} | #{r_pattern(node.right)}" when Prism::CapturePatternNode then "#{r_pattern(node.value)}=>#{r_multi_target(node.target)}" else r(node) end end |
#r_range(node, bare: false) ⇒ String
Parenthesized by default: .. binds below almost everything, so in
an unknown position the parens are what keep the parse. A position
with its own delimiters (argument lists, subscripts, array and hash
literals, when conditions, splats, pins) takes the bare form.
583 584 585 586 587 588 |
# File 'lib/ryac/pipeline/compactor.rb', line 583 def r_range(node, bare: false) op = node.exclude_end? ? '...' : '..' left = node.left ? r(node.left) : '' right = node.right ? r(node.right) : '' ? "#{left}#{op}#{right}" : "(#{left}#{op}#{right})" end |
#r_regexp(node) ⇒ String
502 503 504 505 506 507 |
# File 'lib/ryac/pipeline/compactor.rb', line 502 def r_regexp(node) content = node.content content = escape_regexp_slash(content) unless node.opening == '/' flags = node.closing[1..] "/#{content}/#{flags}" end |
#r_return(node) ⇒ String
436 |
# File 'lib/ryac/pipeline/compactor.rb', line 436 def r_return(node) = r_jump('return', node.arguments) |
#r_singleton_class(node) ⇒ String
722 723 724 |
# File 'lib/ryac/pipeline/compactor.rb', line 722 def r_singleton_class(node) "class<<#{r(node.expression)}#{fmt_body(r_stmt(node.body))};end" end |
#r_stmt(nodes) ⇒ String
139 140 141 142 143 144 145 146 147 148 |
# File 'lib/ryac/pipeline/compactor.rb', line 139 def r_stmt(nodes) return '' if nodes.nil? if nodes.is_a?(Prism::StatementsNode) render_statement_list(nodes.body).join(';') else result = r(nodes) result.nil? || result.empty? ? '' : result end end |
#r_string(node) ⇒ String
--- Strings ---
639 640 641 642 643 644 645 |
# File 'lib/ryac/pipeline/compactor.rb', line 639 def r_string(node) if node.opening == '"' || node.opening.nil? "\"#{node.content}\"" else node.unescaped.inspect end end |
#r_super(node) ⇒ String
--- Other ---
810 811 812 813 814 |
# File 'lib/ryac/pipeline/compactor.rb', line 810 def r_super(node) args = node.arguments&.arguments&.map { |a| r_delimited(a) } || [] args_str = args.join(',') args_str.empty? ? 'super()' : "super(#{args_str})" end |
#r_symbol(node) ⇒ String
--- Literals ---
487 488 489 490 491 492 493 494 495 496 497 498 499 500 |
# File 'lib/ryac/pipeline/compactor.rb', line 487 def r_symbol(node) sym = node.value.to_sym # steep:ignore NoMethod name = sym.to_s if name.match?(/\A[a-zA-Z_]\w*[?!=]?\z/) || BINARY_OPERATORS.include?(sym) || %i[[] []=].include?(sym) ":#{name}" elsif name.start_with?('$') && !name.include?('\\') # A backslash never survives the bare form — `:$\` does not parse — # so those fall through to the quoted form, where the raw content # (Prism's value keeps escapes as written) reproduces itself. ":#{name}" else ":\"#{name.gsub('"', '\\"')}\"" end end |
#r_unary(node, op) ⇒ String
269 270 271 272 273 274 |
# File 'lib/ryac/pipeline/compactor.rb', line 269 def r_unary(node, op) recv_str = r(node.receiver) inner = AstUtils.unwrap_statements(node.receiver) needs_wrap = binary_operator_call?(inner) || AstUtils.logical_op?(inner) needs_wrap ? "#{op}(#{recv_str})" : "#{op}#{recv_str}" end |
#r_unless(node) ⇒ String
344 345 346 347 |
# File 'lib/ryac/pipeline/compactor.rb', line 344 def r_unless(node) then_body = r_stmt(node.statements) "unless #{r(node.predicate)};#{then_body || ''};end" end |
#r_until(node) ⇒ String
359 360 361 362 363 364 365 366 367 |
# File 'lib/ryac/pipeline/compactor.rb', line 359 def r_until(node) cond = r(node.predicate) body = r_stmt(node.statements) if node.begin_modifier? "#{body} until #{cond}" else "until #{cond}#{fmt_body(body)};end" end end |
#r_while(node) ⇒ String
349 350 351 352 353 354 355 356 357 |
# File 'lib/ryac/pipeline/compactor.rb', line 349 def r_while(node) cond = r(node.predicate) body = r_stmt(node.statements) if node.begin_modifier? "#{body} while #{cond}" else "while #{cond}#{fmt_body(body)};end" end end |
#r_yield(node) ⇒ String
--- Control flow ---
318 319 320 321 322 |
# File 'lib/ryac/pipeline/compactor.rb', line 318 def r_yield(node) args = node.arguments&.arguments || [] return 'yield' if args.empty? "yield(#{args.map { |a| r_delimited(a) }.join(',')})" end |
#rebuild ⇒ Array[String]
--- Dispatch ---
43 44 45 |
# File 'lib/ryac/pipeline/compactor.rb', line 43 def rebuild render_statement_list(@prism_ast.statements.body) end |
#recv_with_op(recv_node, call_op) ⇒ String
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 |
# File 'lib/ryac/pipeline/compactor.rb', line 1022 def recv_with_op(recv_node, call_op) return '' if recv_node.nil? recv_str = r(recv_node) inner = AstUtils.unwrap_statements(recv_node) # Unary calls must keep their parens under a chain: `(-vec).to_a` # stripped to `-vec.to_a` rebinds as `-(vec.to_a)` — String#-@ even # makes that run silently instead of failing. needs_wrap = binary_operator_call?(inner) || AstUtils.logical_op?(inner) || inner.is_a?(Prism::TrueNode) || inner.is_a?(Prism::FalseNode) || (inner.is_a?(Prism::CallNode) && %i[! -@ +@].include?(inner.name)) needs_wrap ? "(#{recv_str})#{call_op}" : "#{recv_str}#{call_op}" end |
#render_statement_list(stmts) ⇒ Array[String]
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/ryac/pipeline/compactor.rb', line 159 def render_statement_list(stmts) results = [] #: Array[String] run = [] #: Array[Prism::DefNode] stmts.each do |subnode| if singleton_self_def?(subnode) # @type var subnode: Prism::DefNode run << subnode next end results.concat(flush_singleton_run(run)) result = r(subnode) results << result unless result.nil? || result.empty? break if node_returns_bot?(subnode) end results.concat(flush_singleton_run(run)) results end |
#rescue_var_used?(rescue_node) ⇒ Boolean
1179 1180 1181 1182 1183 1184 1185 1186 1187 |
# File 'lib/ryac/pipeline/compactor.rb', line 1179 def rescue_var_used?(rescue_node) # only called when the (name-bearing) reference is present var = rescue_node.reference.name # steep:ignore NoMethod return false unless rescue_node.statements prism_traverse(rescue_node.statements) { |n| return true if n.is_a?(Prism::LocalVariableReadNode) && n.name == var } false end |
#setter_call?(node) ⇒ Boolean
1162 1163 1164 1165 1166 |
# File 'lib/ryac/pipeline/compactor.rb', line 1162 def setter_call?(node) name = node.name.to_s node.receiver && name.end_with?('=') && name.size > 1 && !%w[== != <= >= ===].include?(name) && name != '[]=' end |
#singleton_self_def?(node) ⇒ Boolean
155 156 157 |
# File 'lib/ryac/pipeline/compactor.rb', line 155 def singleton_self_def?(node) node.is_a?(Prism::DefNode) && node.receiver.is_a?(Prism::SelfNode) end |
#standard_error_redefined? ⇒ Boolean
Lazy: only a literal rescue StandardError clause ever needs the
answer, so most programs never pay for the walk. Any definition-ish
node whose written name is StandardError counts — over-matching a
nested Foo::StandardError only forgoes a few bytes.
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 |
# File 'lib/ryac/pipeline/compactor.rb', line 786 def standard_error_redefined? # a non-nil memo is always one of the two bools return @standard_error_redefined unless @standard_error_redefined.nil? # steep:ignore ReturnTypeMismatch found = false AstUtils.each_node(@prism_ast) do |node| case node when Prism::ClassNode path = node.constant_path name = path.is_a?(Prism::ConstantReadNode) || path.is_a?(Prism::ConstantPathNode) ? path.name : nil found = true if name == :StandardError when Prism::ConstantWriteNode, Prism::ConstantTargetNode found = true if node.name == :StandardError when Prism::ConstantPathWriteNode found = true if node.target.name == :StandardError when Prism::ConstantPathTargetNode found = true if node.name == :StandardError end end @standard_error_redefined = found end |
#wrap_operand(node, parent_op, side) ⇒ [String, bool]
1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 |
# File 'lib/ryac/pipeline/compactor.rb', line 1035 def wrap_operand(node, parent_op, side) return ['', true] if node.nil? str = r(node) inner = AstUtils.unwrap_statements(node) return ["(#{str})", true] if AstUtils.logical_op?(inner) # ** binds tighter than unary minus: `(-a)**b` must not become -a**b. if parent_op == :** && inner.is_a?(Prism::CallNode) && %i[-@ +@].include?(inner.name) return ["(#{str})", true] end if binary_operator_call?(inner) pp = OPERATOR_PRECEDENCE[parent_op] # binary_operator_call? has established inner is a CallNode cp = OPERATOR_PRECEDENCE[inner.name] # steep:ignore NoMethod if pp && cp return ["(#{str})", true] if cp < pp return ["(#{str})", true] if cp == pp && side == :right return ["(#{str})", true] if cp == pp && NON_ASSOCIATIVE_OPS.include?(parent_op) end end [str, false] end |