Module: WhyClasses::Correctors::DataBucketCorrector

Defined in:
lib/why_classes/correctors/data_bucket_corrector.rb

Overview

Decides whether a data-bucket class can be mechanically rewritten to a Struct/Data one-liner, and generates the replacement source.

Conservative by design: anything it is unsure about is reported as advice-only (analysis returns nil) rather than rewritten incorrectly.

Defined Under Namespace

Classes: Analysis

Constant Summary collapse

H =
AST::NodeHelpers

Class Method Summary collapse

Class Method Details

.analyze(shape, prefer_data:) ⇒ Object

Returns an Analysis when the class is a safely-convertible data bucket, else nil (caller downgrades to advice-only).



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/why_classes/correctors/data_bucket_corrector.rb', line 31

def analyze(shape, prefer_data:)
  return nil unless viable?(shape)

  fields = fields_for(shape)
  return nil if fields.empty?

  init = shape.initialize_method
  # attr fields must be covered by the constructor's parameters.
  return nil unless shape.attr_fields.all? { |f| fields.include?(f) }

  keyword = init ? H.keyword_params?(init.children[1]) : false
  Analysis.new(fields: fields, kind: kind_for(shape, prefer_data), keyword: keyword)
end

.fields_for(shape) ⇒ Object



65
66
67
68
# File 'lib/why_classes/correctors/data_bucket_corrector.rb', line 65

def fields_for(shape)
  init = shape.initialize_method
  init ? H.param_names(init.children[1]) : shape.attr_fields
end

.kind_for(shape, prefer_data) ⇒ Object

attr_accessor/attr_writer imply mutation -> Struct; otherwise Data.



71
72
73
74
75
76
# File 'lib/why_classes/correctors/data_bucket_corrector.rb', line 71

def kind_for(shape, prefer_data)
  writable = !(shape.attr_macros[:attr_writer] + shape.attr_macros[:attr_accessor]).empty?
  return :struct if writable && !prefer_data

  :data
end

.superclass_ok?(shape) ⇒ Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/why_classes/correctors/data_bucket_corrector.rb', line 61

def superclass_ok?(shape)
  shape.superclass.nil? || shape.superclass == "Object"
end

.viable?(shape) ⇒ Boolean

The structural preconditions for treating a class as a pure data bucket.

Returns:

  • (Boolean)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/why_classes/correctors/data_bucket_corrector.rb', line 46

def viable?(shape)
  return false if shape.module?
  return false if shape.uses_metaprogramming?
  return false unless superclass_ok?(shape)
  return false unless shape.behavior_methods.empty?
  return false unless shape.singleton_methods.empty?
  return false unless shape.other_statements.empty?

  init = shape.initialize_method
  # No initialize: a bare attr_* container still qualifies.
  return !shape.attr_fields.empty? if init.nil?

  H.initialize_only_assigns_params?(shape.node)
end