Class: Greenroom::CLI::Check::Analyzer
- Inherits:
-
Object
- Object
- Greenroom::CLI::Check::Analyzer
- Defined in:
- lib/greenroom/cli/check.rb
Defined Under Namespace
Classes: Experiment, Scope
Constant Summary collapse
- SourceError =
Class.new(StandardError)
Instance Method Summary collapse
- #check ⇒ Object
-
#experiments ⇒ Object
The judge uses the same structural relation that the merge gate verifies.
-
#initialize(before, after) ⇒ Analyzer
constructor
A new instance of Analyzer.
Constructor Details
#initialize(before, after) ⇒ Analyzer
Returns a new instance of Analyzer.
112 113 114 115 116 117 |
# File 'lib/greenroom/cli/check.rb', line 112 def initialize(before, after) # Syntax trees omit source locations. Therefore, comment and white # space positions do not affect the acceptance result. @before = parse(before) @after = parse(after) end |
Instance Method Details
#check ⇒ Object
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/greenroom/cli/check.rb', line 119 def check return reject("Unsupported Ruby method or class syntax is present.") if unsupported?(@before) || unsupported?(@after) before_scopes = scopes(@before) after_scopes = scopes(@after) return reject("The class and module structure changed.") unless before_scopes.keys == after_scopes.keys return reject("The class or module header changed.") unless before_scopes.all? { |name, scope| same_header?(scope, after_scopes.fetch(name)) } return reject("A top-level statement changed.") unless top_level(@before) == top_level(@after) changed = [] added = [] before_scopes.each do |name, before_scope| after_scope = after_scopes.fetch(name) removed_names = before_scope.methods.keys - after_scope.methods.keys return reject("An existing method was removed.") unless removed_names.empty? common_names = before_scope.methods.keys & after_scope.methods.keys common_names.each do |method_name| changed << [name, before_scope.methods.fetch(method_name), after_scope.methods.fetch(method_name)] unless same_ast?(before_scope.methods.fetch(method_name), after_scope.methods.fetch(method_name)) end (after_scope.methods.keys - before_scope.methods.keys).each do |method_name| added << [name, after_scope.methods.fetch(method_name)] end end return reject("Exactly one existing method must change.") unless changed.one? return reject("Exactly one candidate method must be added.") unless added.one? scope_name, before_method, after_method = changed.first added_scope_name, candidate = added.first return reject("The candidate method must be in the changed class.") unless scope_name == added_scope_name return reject("The candidate method must be private.") unless private_method?(after_scopes.fetch(scope_name), candidate) return reject("An existing method visibility changed.") unless same_visibilities?(before_scopes.fetch(scope_name), after_scopes.fetch(scope_name)) return reject("Another class body statement changed.") unless unchanged_statements?(before_scopes.fetch(scope_name), after_scopes.fetch(scope_name), candidate) return reject("The changed method signature must stay the same.") unless same_ast?(before_method.children[1], after_method.children[1]) other_scope_changed = before_scopes.any? do |name, before_scope| next false if name == scope_name after_scope = after_scopes.fetch(name) !unchanged_statements?(before_scope, after_scope) end return reject("Another class or module changed.") if other_scope_changed science = science_blocks(after_method) unless science shape_error = science_shape_error(after_method) return reject(shape_error || "The changed method body must contain one Scientist.run call.") end use_block, try_block, _compare_block, experiment, science_statement = science return reject("The use block must match the old method body.") unless matching_span?(before_method, after_method, science_statement, use_block) # Tests found behavior changes when statements move into a block. # A return in the moved part bypasses compare and publish. # A binding sees the block's local variables. Tests found the same # behavior for yield, rescue, ensure, __method__, local variables, # and constant references, so those forms remain valid. return reject("A return in a use or try block is unsafe.") if unsafe_return?(use_block) || unsafe_return?(try_block) return reject("A binding in a use or try block is unsafe.") if binding_call?(use_block) || binding_call?(try_block) candidate_name = candidate.children[0] Result.new(true, "Accepted experiment #{experiment} with candidate #{candidate_name}.", experiment, candidate_name.to_s) end |
#experiments ⇒ Object
The judge uses the same structural relation that the merge gate verifies. A second parser could accept a shape that the gate rejects.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/greenroom/cli/check.rb', line 184 def experiments scopes(@after).values.flat_map do |scope| scope.methods.values.filter_map do |method| science = science_blocks(method) next unless science use_block, try_block, _compare_block, name = science try_body = block_body(try_block) next unless try_body&.send_type? && try_body.receiver.nil? candidate = scope.methods[try_body.method_name] Experiment.new(name, method, use_block, candidate) if candidate end end end |