Class: Ryac::Pipeline::ParenOptimizer

Inherits:
Stage
  • Object
show all
Defined in:
lib/ryac/pipeline/paren_optimizer.rb,
sig/ryac/pipeline/paren_optimizer.rbs

Overview

Removes unnecessary parentheses from method calls at statement level. Operates as a source patcher: Prism.parse → walk AST → collect patches → apply. Only removes parens; never adds them.

Instance Method Summary collapse

Methods inherited from Stage

#analysis, #analysis_options, #call, #finish, #fixpoint?, #needs_analysis?, new

Methods included from SourcePatcher

#apply_patches, #mk, #src, #verify_parses

Instance Method Details

#add_paren_removal_patches(open_loc, close_loc, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • open_loc (Prism::Location)
  • close_loc (Prism::Location)
  • patches (Array[patch_entry])


154
155
156
157
# File 'lib/ryac/pipeline/paren_optimizer.rb', line 154

def add_paren_removal_patches(open_loc, close_loc, patches)
  patches << { start: open_loc.start_offset, end: open_loc.end_offset, replacement: ' ' }
  patches << { start: close_loc.start_offset, end: close_loc.end_offset, replacement: '' }
end

#collect(ctx, patches) ⇒ void

This method returns an undefined value.

Parameters:



9
10
11
# File 'lib/ryac/pipeline/paren_optimizer.rb', line 9

def collect(ctx, patches)
  walk(ctx.ast, ctx.source, patches, statement_level: true)
end

#try_remove_call_parens(node, source, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::CallNode)
  • source (String)
  • patches (Array[patch_entry])


129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/ryac/pipeline/paren_optimizer.rb', line 129

def try_remove_call_parens(node, source, patches)
  return unless node.opening_loc
  return unless AstUtils.can_omit_parens?(node)

  # Don't remove parens from calls with keyword args in modifier context.
  # After hash shorthand (`a:` for `a:a`), `foo a: if cond` is ambiguous.
  raw_args = node.arguments&.arguments || []
  if raw_args.any? { |a| a.is_a?(Prism::KeywordHashNode) }
    # opening_loc presence (checked above) implies the paired closing_loc
    after = source.byteslice(node.closing_loc.end_offset, 10) # steep:ignore NoMethod
    return if after&.match?(/\A (?:if|unless|while|until) /)
  end

  add_paren_removal_patches(node.opening_loc, node.closing_loc, patches) # steep:ignore ArgumentTypeMismatch
end

#try_remove_yield_parens(node, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::YieldNode)
  • patches (Array[patch_entry])


145
146
147
148
149
150
151
152
# File 'lib/ryac/pipeline/paren_optimizer.rb', line 145

def try_remove_yield_parens(node, patches)
  return unless node.lparen_loc
  args = node.arguments&.arguments || []
  return if args.empty?

  # lparen_loc presence (checked above) implies the paired rparen_loc
  add_paren_removal_patches(node.lparen_loc, node.rparen_loc, patches) # steep:ignore ArgumentTypeMismatch
end

#walk(node, source, patches, statement_level:) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::Node)
  • source (String)
  • patches (Array[patch_entry])
  • statement_level: (Boolean)


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
# File 'lib/ryac/pipeline/paren_optimizer.rb', line 15

def walk(node, source, patches, statement_level:)
  case node
  when Prism::ProgramNode
    walk(node.statements, source, patches, statement_level: true)

  when Prism::StatementsNode
    node.body.each { |child| walk(child, source, patches, statement_level: statement_level) }

  when Prism::CallNode
    try_remove_call_parens(node, source, patches) if statement_level
    walk_call_children(node, source, patches)

  when Prism::YieldNode
    try_remove_yield_parens(node, patches) if statement_level
    node.arguments&.arguments&.each { |arg| walk(arg, source, patches, statement_level: false) }

  when Prism::IfNode
    walk_if(node, source, patches)

  when Prism::UnlessNode
    walk(node.predicate, source, patches, statement_level: false)
    walk(node.statements, source, patches, statement_level: true) if node.statements
    walk(node.else_clause, source, patches, statement_level: true) if node.else_clause

  when Prism::ElseNode
    walk(node.statements, source, patches, statement_level: statement_level) if node.statements

  when Prism::WhileNode, Prism::UntilNode
    walk(node.predicate, source, patches, statement_level: false)
    walk(node.statements, source, patches, statement_level: true) if node.statements

  when Prism::ForNode
    walk(node.collection, source, patches, statement_level: false)
    walk(node.statements, source, patches, statement_level: true) if node.statements

  when Prism::DefNode
    walk(node.body, source, patches, statement_level: true) if node.body

  when Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode
    walk(node.body, source, patches, statement_level: true) if node.body

  when Prism::BeginNode
    walk(node.statements, source, patches, statement_level: true) if node.statements
    walk(node.rescue_clause, source, patches, statement_level: true) if node.rescue_clause
    walk(node.else_clause, source, patches, statement_level: true) if node.else_clause
    walk(node.ensure_clause, source, patches, statement_level: true) if node.ensure_clause

  when Prism::RescueNode
    walk(node.statements, source, patches, statement_level: true) if node.statements
    walk(node.subsequent, source, patches, statement_level: true) if node.subsequent

  when Prism::EnsureNode
    walk(node.statements, source, patches, statement_level: true) if node.statements

  when Prism::CaseNode
    walk(node.predicate, source, patches, statement_level: false) if node.predicate
    node.conditions.each { |cond| walk(cond, source, patches, statement_level: true) }
    walk(node.else_clause, source, patches, statement_level: true) if node.else_clause

  when Prism::WhenNode
    node.conditions.each { |c| walk(c, source, patches, statement_level: false) }
    walk(node.statements, source, patches, statement_level: true) if node.statements

  when Prism::CaseMatchNode
    walk(node.predicate, source, patches, statement_level: false) if node.predicate
    node.conditions.each { |cond| walk(cond, source, patches, statement_level: true) }
    walk(node.else_clause, source, patches, statement_level: true) if node.else_clause

  when Prism::InNode
    walk(node.statements, source, patches, statement_level: true) if node.statements

  when Prism::BlockNode, Prism::LambdaNode
    walk(node.body, source, patches, statement_level: true) if node.body

  when Prism::ParenthesesNode
    walk(node.body, source, patches, statement_level: statement_level) if node.body

  when Prism::LocalVariableWriteNode
    walk(node.value, source, patches, statement_level: statement_level)

  when Prism::PostExecutionNode
    walk(node.statements, source, patches, statement_level: true) if node.statements

  else
    node.compact_child_nodes.each { |child| walk(child, source, patches, statement_level: false) }
  end
end

#walk_call_children(node, source, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::CallNode)
  • source (String)
  • patches (Array[patch_entry])


117
118
119
120
121
122
123
124
125
126
127
# File 'lib/ryac/pipeline/paren_optimizer.rb', line 117

def walk_call_children(node, source, patches)
  walk(node.receiver, source, patches, statement_level: false) if node.receiver
  node.arguments&.arguments&.each { |arg| walk(arg, source, patches, statement_level: false) }
  if node.block
    if node.block.is_a?(Prism::BlockNode)
      walk(node.block, source, patches, statement_level: true)
    else
      walk(node.block, source, patches, statement_level: false)
    end
  end
end

#walk_if(node, source, patches) ⇒ void

This method returns an undefined value.

Parameters:

  • node (Prism::IfNode)
  • source (String)
  • patches (Array[patch_entry])


103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/ryac/pipeline/paren_optimizer.rb', line 103

def walk_if(node, source, patches)
  walk(node.predicate, source, patches, statement_level: false)

  if node.if_keyword_loc
    # Block-if or modifier-if: body is at statement level
    walk(node.statements, source, patches, statement_level: true) if node.statements
    walk(node.subsequent, source, patches, statement_level: true) if node.subsequent
  else
    # Ternary (no if_keyword_loc): arms are NOT at statement level
    walk(node.statements, source, patches, statement_level: false) if node.statements
    walk(node.subsequent, source, patches, statement_level: false) if node.subsequent
  end
end