bullematic 0.1.0 → 0.2.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -1
  3. data/README.md +23 -11
  4. data/exe/bullematic +7 -0
  5. data/lib/bullematic/ast/finder.rb +190 -35
  6. data/lib/bullematic/ast/parser.rb +1 -17
  7. data/lib/bullematic/ast/rewriter.rb +90 -28
  8. data/lib/bullematic/cli.rb +185 -0
  9. data/lib/bullematic/configuration.rb +15 -4
  10. data/lib/bullematic/detection.rb +110 -13
  11. data/lib/bullematic/evidence_store.rb +78 -0
  12. data/lib/bullematic/fixer.rb +258 -31
  13. data/lib/bullematic/integrations/minitest.rb +20 -6
  14. data/lib/bullematic/integrations/rails.rb +23 -9
  15. data/lib/bullematic/integrations/rspec.rb +19 -7
  16. data/lib/bullematic/logger.rb +24 -12
  17. data/lib/bullematic/notifier.rb +131 -36
  18. data/lib/bullematic/version.rb +1 -1
  19. data/lib/bullematic.rb +12 -4
  20. data/sig/generated/bullematic/ast/finder.rbs +70 -11
  21. data/sig/generated/bullematic/ast/parser.rbs +0 -6
  22. data/sig/generated/bullematic/ast/rewriter.rbs +28 -11
  23. data/sig/generated/bullematic/cli.rbs +56 -0
  24. data/sig/generated/bullematic/configuration.rbs +16 -20
  25. data/sig/generated/bullematic/detection.rbs +77 -7
  26. data/sig/generated/bullematic/evidence_store.rbs +35 -0
  27. data/sig/generated/bullematic/fixer.rbs +59 -3
  28. data/sig/generated/bullematic/integrations/rails.rbs +7 -0
  29. data/sig/generated/bullematic/logger.rbs +5 -0
  30. data/sig/generated/bullematic/notifier.rbs +17 -13
  31. data/sig/generated/bullematic.rbs +5 -0
  32. data/sig/stubs/rails.rbs +60 -0
  33. metadata +161 -8
  34. data/Rakefile +0 -26
  35. data/Steepfile +0 -15
  36. data/assets/logo-header.svg +0 -28
  37. data/rbs_collection.lock.yaml +0 -24
  38. data/rbs_collection.yaml +0 -14
@@ -6,71 +6,166 @@ module Bullematic
6
6
  class << self
7
7
  #: () -> void
8
8
  def setup
9
- return unless defined?(Bullet)
9
+ return if compatible?
10
10
 
11
- setup_notification_hook
11
+ Bullematic.configuration&.logger&.warn(
12
+ "[Bullematic] Disabled: Bullet notification API is unavailable"
13
+ )
12
14
  end
13
15
 
14
- #: () -> void
15
- def process_notifications
16
- return unless defined?(Bullet) && Bullet.notification?
16
+ #: () -> bool
17
+ def compatible?
18
+ return false unless defined?(Bullet)
19
+
20
+ Bullet.respond_to?(:notification?, true) &&
21
+ Bullet.respond_to?(:notification_collector, true) &&
22
+ !defined?(Bullet::Notification::NPlusOneQuery).nil?
23
+ end
24
+
25
+ # @rbs context_id: untyped
26
+ # @rbs return: void
27
+ def process_notifications(context_id: nil)
28
+ return unless compatible? && Bullet.notification?
29
+
30
+ context_id ||= Object.new.object_id
31
+
32
+ config = Bullematic.configuration
33
+ if config&.logger && config.debug
34
+ config.logger.debug "[Bullematic] Processing notifications..."
35
+ end
36
+
37
+ collector = Bullet.send(:notification_collector)
38
+ return unless collector&.respond_to?(:collection)
17
39
 
18
- notifications = Bullet.send(:notification_collector)&.collection || {}
40
+ notifications = collector.collection
19
41
 
20
- notifications.each_value do |notification_set|
21
- notification_set.each do |notification|
22
- next unless notification.is_a?(Bullet::Notification::NPlusOneQuery)
42
+ if config&.logger && config.debug
43
+ config.logger.debug "[Bullematic] Notifications collection type: #{notifications.class}"
44
+ config.logger.debug "[Bullematic] Notifications count: #{notifications.respond_to?(:size) ? notifications.size : 'unknown'}"
45
+ end
46
+
47
+ n_plus_one_count = 0
48
+ unused_eager_loading_count = 0
23
49
 
24
- process_n_plus_one(notification)
50
+ case notifications
51
+ when Hash
52
+ notifications.each_value do |notification_set|
53
+ notification_set = [notification_set] unless notification_set.respond_to?(:each)
54
+ notification_set.each do |notification|
55
+ type = process_notification(notification, context_id)
56
+ n_plus_one_count += 1 if type == :n_plus_one
57
+ unused_eager_loading_count += 1 if type == :unused_eager_loading
58
+ end
59
+ end
60
+ when Enumerable
61
+ notifications.each do |notification|
62
+ type = process_notification(notification, context_id)
63
+ n_plus_one_count += 1 if type == :n_plus_one
64
+ unused_eager_loading_count += 1 if type == :unused_eager_loading
25
65
  end
26
66
  end
67
+
68
+ if config&.logger && config.debug
69
+ config.logger.debug "[Bullematic] Processed #{n_plus_one_count} N+1 notifications"
70
+ config.logger.debug "[Bullematic] Processed #{unused_eager_loading_count} UnusedEagerLoading notifications"
71
+ config.logger.debug "[Bullematic] Detection queue size: #{Fixer.detection_queue.size}"
72
+ end
73
+ rescue StandardError => error
74
+ begin
75
+ Bullematic.configuration&.logger&.warn(
76
+ "[Bullematic] Failed to process Bullet notifications: #{error.message}"
77
+ )
78
+ rescue StandardError
79
+ nil
80
+ end
27
81
  end
28
82
 
29
83
  private
30
84
 
31
- #: () -> void
32
- def setup_notification_hook
33
- Bullet::Notification::NPlusOneQuery.prepend(NotificationHook)
85
+ # @rbs notification: untyped
86
+ # @rbs context_id: untyped
87
+ # @rbs return: Symbol?
88
+ def process_notification(notification, context_id)
89
+ if notification.is_a?(Bullet::Notification::NPlusOneQuery)
90
+ process_n_plus_one(notification, context_id)
91
+ :n_plus_one
92
+ elsif defined?(Bullet::Notification::UnusedEagerLoading) &&
93
+ notification.is_a?(Bullet::Notification::UnusedEagerLoading)
94
+ process_unused_eager_loading(notification, context_id)
95
+ :unused_eager_loading
96
+ end
97
+ rescue StandardError => error
98
+ Bullematic.configuration&.logger&.warn(
99
+ "[Bullematic] Skipped invalid Bullet notification: #{error.message}"
100
+ )
101
+ nil
34
102
  end
35
103
 
36
104
  # @rbs notification: untyped
105
+ # @rbs context_id: untyped
37
106
  # @rbs return: void
38
- def process_n_plus_one(notification)
107
+ def process_n_plus_one(notification, context_id)
39
108
  detection = Detection.new(
40
109
  type: :n_plus_one,
41
110
  base_class: notification.base_class,
42
111
  associations: notification.associations,
43
- call_stack: extract_call_stack(notification)
112
+ call_stack: extract_call_stack(notification),
113
+ context_id: context_id
44
114
  )
45
115
 
46
- Fixer.queue(detection) if detection.fixable?
47
- end
116
+ config = Bullematic.configuration
117
+ if config&.logger && config.debug
118
+ config.logger.debug "[Bullematic] N+1 Detection created:"
119
+ config.logger.debug " Base class: #{notification.base_class}"
120
+ config.logger.debug " Associations: #{notification.associations.inspect}"
121
+ config.logger.debug " Source file: #{detection.source_file}"
122
+ config.logger.debug " Line number: #{detection.line_number}"
123
+ config.logger.debug " Method name: #{detection.method_name}"
124
+ config.logger.debug " Call stack: #{detection.call_stack.inspect}"
125
+ config.logger.debug " Fixable: #{detection.fixable?}"
126
+ config.logger.debug " Queue size before: #{Fixer.detection_queue.size}"
127
+ end
48
128
 
49
- # @rbs notification: untyped
50
- # @rbs return: Array[String]
51
- def extract_call_stack(notification)
52
- notification.instance_variable_get(:@callers) || caller
129
+ if detection.fixable?
130
+ Fixer.queue(detection)
131
+ if config&.logger && config.debug
132
+ config.logger.debug " Queue size after: #{Fixer.detection_queue.size}"
133
+ end
134
+ end
53
135
  end
54
- end
55
136
 
56
- module NotificationHook
57
- # @rbs base_class: untyped
58
- # @rbs associations: untyped
59
- # @rbs path: untyped
137
+ # @rbs notification: untyped
138
+ # @rbs context_id: untyped
60
139
  # @rbs return: void
61
- def initialize(base_class, associations, path = nil)
62
- super
63
- return unless Bullematic.enabled?
140
+ def process_unused_eager_loading(notification, context_id)
141
+ config = Bullematic.configuration
142
+ return unless config
64
143
 
65
- detection = Detection.new(
66
- type: :n_plus_one,
67
- base_class: base_class,
68
- associations: associations,
69
- call_stack: caller
70
- )
144
+ if EvidenceStore.recording?
145
+ detection = Detection.new(
146
+ type: :unused_eager_loading,
147
+ base_class: notification.base_class,
148
+ associations: notification.associations,
149
+ call_stack: extract_call_stack(notification),
150
+ context_id: context_id
151
+ )
152
+ Fixer.queue(detection) if detection.fixable?
153
+ end
154
+
155
+ if config.logger && config.debug
156
+ config.logger.debug "[Bullematic] UnusedEagerLoading detected:"
157
+ config.logger.debug " Base class: #{notification.base_class}"
158
+ config.logger.debug " Unused associations: #{notification.associations.inspect}"
159
+ end
160
+ end
71
161
 
72
- Fixer.queue(detection) if detection.fixable?
162
+ # @rbs notification: untyped
163
+ # @rbs return: Array[String]
164
+ def extract_call_stack(notification)
165
+ callers = notification.instance_variable_get(:@callers)
166
+ callers.is_a?(Array) ? callers.map(&:to_s) : []
73
167
  end
74
168
  end
169
+
75
170
  end
76
171
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Bullematic
5
- VERSION = "0.1.0" #: String
5
+ VERSION = "0.2.0" #: String
6
6
  end
data/lib/bullematic.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  # rbs_inline: enabled
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "bullet"
5
+
4
6
  require_relative "bullematic/version"
5
7
  require_relative "bullematic/configuration"
6
8
  require_relative "bullematic/logger"
7
9
  require_relative "bullematic/detection"
10
+ require_relative "bullematic/evidence_store"
8
11
  require_relative "bullematic/ast/parser"
9
12
  require_relative "bullematic/ast/finder"
10
13
  require_relative "bullematic/ast/rewriter"
@@ -19,9 +22,15 @@ module Bullematic
19
22
  # @rbs @configuration: Configuration?
20
23
 
21
24
  class << self
22
- # @rbs!
23
- # attr_accessor configuration: Configuration?
24
- attr_accessor :configuration
25
+ #: () -> Configuration?
26
+ def configuration
27
+ @configuration
28
+ end
29
+
30
+ #: (Configuration?) -> Configuration?
31
+ def configuration=(configuration)
32
+ @configuration = configuration
33
+ end
25
34
 
26
35
  #: () { (Configuration) -> void } -> void
27
36
  def configure
@@ -46,7 +55,6 @@ module Bullematic
46
55
  def reset!
47
56
  @configuration = nil
48
57
  Fixer.clear
49
- AST::Parser.clear_cache
50
58
  end
51
59
  end
52
60
  end
@@ -5,7 +5,7 @@ module Bullematic
5
5
  class Finder
6
6
  QUERY_METHODS: Array[Symbol]
7
7
 
8
- type query_location = { node: untyped, location: untyped, receiver: untyped, method_name: Symbol }
8
+ type query_location = { node: untyped, location: untyped, receiver: untyped, method_name: Symbol, target_name: Symbol? }
9
9
 
10
10
  class QueryLocation < Struct[untyped]
11
11
  attr_accessor node(): untyped
@@ -16,8 +16,10 @@ module Bullematic
16
16
 
17
17
  attr_accessor method_name(): untyped
18
18
 
19
- def self.new: (?node: untyped, ?location: untyped, ?receiver: untyped, ?method_name: untyped) -> instance
20
- | ({ ?node: untyped, ?location: untyped, ?receiver: untyped, ?method_name: untyped }) -> instance
19
+ attr_accessor target_name(): untyped
20
+
21
+ def self.new: (?node: untyped, ?location: untyped, ?receiver: untyped, ?method_name: untyped, ?target_name: untyped) -> instance
22
+ | ({ ?node: untyped, ?location: untyped, ?receiver: untyped, ?method_name: untyped, ?target_name: untyped }) -> instance
21
23
  end
22
24
 
23
25
  @parse_result: untyped
@@ -35,9 +37,17 @@ module Bullematic
35
37
  # @rbs return: Array[QueryLocation]
36
38
  def find_model_queries: (String model_class_name, ?line_number: Integer?) -> Array[QueryLocation]
37
39
 
40
+ # @rbs model_class_name: String
38
41
  # @rbs line_number: Integer
39
- # @rbs return: QueryLocation?
40
- def find_query_at_line: (Integer line_number) -> QueryLocation?
42
+ # @rbs associations: Array[Symbol]
43
+ # @rbs return: Array[QueryLocation]
44
+ def find_model_queries_for_variables_at_line: (String model_class_name, Integer line_number, Array[Symbol] associations) -> Array[QueryLocation]
45
+
46
+ # @rbs parent_association: Symbol
47
+ # @rbs child_associations: Array[Symbol]
48
+ # @rbs line_number: Integer?
49
+ # @rbs return: bool
50
+ def nested_association?: (Symbol parent_association, Array[Symbol] child_associations, Integer? line_number) -> bool
41
51
 
42
52
  private
43
53
 
@@ -54,12 +64,6 @@ module Bullematic
54
64
  # @rbs return: void
55
65
  def mark_descendant_calls: (untyped node, Set[Integer] skip_nodes) -> void
56
66
 
57
- # @rbs node: untyped
58
- # @rbs queries: Array[QueryLocation]
59
- # @rbs target_line: Integer?
60
- # @rbs return: void
61
- def visit_all_queries: (untyped node, Array[QueryLocation] queries, Integer? target_line) -> void
62
-
63
67
  # @rbs node: untyped
64
68
  # @rbs queries: Array[QueryLocation]
65
69
  # @rbs model_class_name: String
@@ -74,6 +78,37 @@ module Bullematic
74
78
  # @rbs return: bool
75
79
  def check_assignment_node: (untyped node, Array[QueryLocation] queries, String model_class_name, Integer? target_line) -> bool
76
80
 
81
+ # @rbs node: untyped
82
+ # @rbs line_number: Integer
83
+ # @rbs associations: Array[Symbol]
84
+ # @rbs return: Array[Symbol]
85
+ def association_receiver_names_at_line: (untyped node, Integer line_number, Array[Symbol] associations) -> Array[Symbol]
86
+
87
+ # @rbs node: untyped
88
+ # @rbs line_number: Integer
89
+ # @rbs names: Array[Symbol]
90
+ # @rbs return: void
91
+ def add_block_receiver_names: (untyped node, Integer line_number, Array[Symbol] names) -> void
92
+
93
+ # @rbs node: untyped
94
+ # @rbs name: Symbol
95
+ # @rbs line_number: Integer
96
+ # @rbs return: bool
97
+ def block_parameter_at_line?: (untyped node, Symbol name, Integer line_number) -> bool
98
+
99
+ # @rbs node: untyped
100
+ # @rbs line_number: Integer
101
+ # @rbs return: untyped
102
+ def method_scope_at_line: (untyped node, Integer line_number) -> untyped
103
+
104
+ # @rbs node: untyped
105
+ # @rbs name: Symbol
106
+ # @rbs start_line: Integer
107
+ # @rbs end_line: Integer
108
+ # @rbs scope: untyped
109
+ # @rbs return: bool
110
+ def variable_written_between?: (untyped node, Symbol name, Integer start_line, Integer end_line, untyped scope) -> bool
111
+
77
112
  # @rbs node: untyped
78
113
  # @rbs return: untyped
79
114
  def find_root_receiver: (untyped node) -> untyped
@@ -86,6 +121,30 @@ module Bullematic
86
121
  # @rbs method_name: Symbol
87
122
  # @rbs return: bool
88
123
  def query_method?: (Symbol method_name) -> bool
124
+
125
+ # @rbs node: untyped
126
+ # @rbs parent_association: Symbol
127
+ # @rbs child_associations: Array[Symbol]
128
+ # @rbs line_number: Integer
129
+ # @rbs return: bool
130
+ def nested_association_in_node?: (untyped node, Symbol parent_association, Array[Symbol] child_associations, Integer line_number) -> bool
131
+
132
+ # @rbs node: untyped
133
+ # @rbs method_name: Symbol
134
+ # @rbs names: Array[Symbol]
135
+ # @rbs line_number: Integer
136
+ # @rbs return: bool
137
+ def call_on_variable_at_line?: (untyped node, Symbol method_name, Array[Symbol] names, Integer line_number) -> bool
138
+
139
+ # @rbs node: untyped
140
+ # @rbs method_name: Symbol
141
+ # @rbs return: bool
142
+ def receiver_calls?: (untyped node, Symbol method_name) -> bool
143
+
144
+ # @rbs node: untyped
145
+ # @rbs names: Array[Symbol]
146
+ # @rbs return: bool
147
+ def variable_written?: (untyped node, Array[Symbol] names) -> bool
89
148
  end
90
149
  end
91
150
  end
@@ -7,12 +7,6 @@ module Bullematic
7
7
 
8
8
  @filepath: String?
9
9
 
10
- # : () -> Hash[String, untyped]
11
- def self.cache: () -> Hash[String, untyped]
12
-
13
- # : () -> void
14
- def self.clear_cache: () -> void
15
-
16
10
  # @rbs filepath: String
17
11
  # @rbs return: untyped
18
12
  def self.parse_file: (String filepath) -> untyped
@@ -20,10 +20,10 @@ module Bullematic
20
20
  | ({ ?type: untyped, ?offset: untyped, ?byte_length: untyped, ?new_text: untyped, ?associations: untyped }) -> instance
21
21
  end
22
22
 
23
- @source: String
24
-
25
23
  @modifications: Array[Modification]
26
24
 
25
+ @source: String
26
+
27
27
  # @rbs!
28
28
  # attr_reader source: String
29
29
  # attr_reader modifications: Array[Modification]
@@ -39,9 +39,9 @@ module Bullematic
39
39
  def initialize: (String source) -> void
40
40
 
41
41
  # @rbs query_location: Finder::QueryLocation
42
- # @rbs associations: Array[Symbol]
43
- # @rbs return: void
44
- def add_includes: (Finder::QueryLocation query_location, Array[Symbol] associations) -> void
42
+ # @rbs associations: Array[untyped]
43
+ # @rbs return: Symbol
44
+ def add_includes: (Finder::QueryLocation query_location, Array[untyped] associations) -> Symbol
45
45
 
46
46
  # : () -> String
47
47
  def rewrite: () -> String
@@ -57,21 +57,38 @@ module Bullematic
57
57
  def find_chain_insert_point: (untyped node) -> Integer
58
58
 
59
59
  # @rbs query_location: Finder::QueryLocation
60
- # @rbs associations: Array[Symbol]
60
+ # @rbs associations: Array[untyped]
61
61
  # @rbs return: bool
62
- def already_has_includes?: (Finder::QueryLocation query_location, Array[Symbol] associations) -> bool
62
+ def already_has_includes?: (Finder::QueryLocation query_location, Array[untyped] associations) -> bool
63
63
 
64
64
  # @rbs node: untyped
65
65
  # @rbs return: Array[Symbol]
66
66
  def find_existing_includes: (untyped node) -> Array[Symbol]
67
67
 
68
68
  # @rbs call_node: untyped
69
- # @rbs return: Array[Symbol]
70
- def extract_associations_from_call: (untyped call_node) -> Array[Symbol]
69
+ # @rbs return: Array[untyped]
70
+ def extract_associations_from_call: (untyped call_node) -> Array[untyped]
71
+
72
+ # @rbs node: untyped
73
+ # @rbs associations: Array[untyped]
74
+ # @rbs return: void
75
+ def collect_literal_associations: (untyped node, Array[untyped] associations) -> void
76
+
77
+ # @rbs node: untyped
78
+ # @rbs return: untyped
79
+ def literal_association: (untyped node) -> untyped
71
80
 
72
- # @rbs associations: Array[Symbol]
81
+ # @rbs associations: Array[untyped]
73
82
  # @rbs return: String
74
- def format_associations: (Array[Symbol] associations) -> String
83
+ def format_associations: (Array[untyped] associations) -> String
84
+
85
+ # @rbs association: untyped
86
+ # @rbs braces: bool
87
+ # @rbs return: String
88
+ def format_association: (untyped association, ?bool braces) -> String
89
+
90
+ # : () -> void
91
+ def validate_modifications!: () -> void
75
92
  end
76
93
  end
77
94
  end
@@ -0,0 +1,56 @@
1
+ # Generated from lib/bullematic/cli.rb with RBS::Inline
2
+
3
+ module Bullematic
4
+ class CLI
5
+ # @rbs argv: Array[String]
6
+ # @rbs return: Integer
7
+ def self.run: (Array[String] argv) -> Integer
8
+
9
+ # @rbs argv: Array[String]
10
+ # @rbs return: Array[String]
11
+ private def self.command_args: (Array[String] argv) -> Array[String]
12
+
13
+ # @rbs command: Array[String]
14
+ # @rbs return: Integer
15
+ private def self.record: (Array[String] command) -> Integer
16
+
17
+ # @rbs dry_run: bool
18
+ # @rbs return: Integer
19
+ private def self.apply: (dry_run: bool) -> Integer
20
+
21
+ # @rbs argv: Array[String]
22
+ # @rbs return: Integer
23
+ private def self.fix: (Array[String] argv) -> Integer
24
+
25
+ # @rbs command: Array[String]
26
+ # @rbs return: Integer
27
+ private def self.verify: (Array[String] command) -> Integer
28
+
29
+ # : () -> Integer
30
+ private def self.doctor: () -> Integer
31
+
32
+ # @rbs command: Array[String]
33
+ # @rbs evidence_path: String
34
+ # @rbs return: Integer
35
+ private def self.run_command: (Array[String] command, String evidence_path) -> Integer
36
+
37
+ # : () -> void
38
+ private def self.load_application: () -> void
39
+
40
+ # @rbs dry_run: bool
41
+ # @rbs return: void
42
+ private def self.configure: (bool dry_run) -> void
43
+
44
+ # @rbs evidence_path: String
45
+ # @rbs command: Array[String]
46
+ # @rbs return: void
47
+ private def self.write_command: (String evidence_path, Array[String] command) -> void
48
+
49
+ # @rbs evidence_path: String
50
+ # @rbs return: Array[String]
51
+ private def self.read_command: (String evidence_path) -> Array[String]
52
+
53
+ # : (String, Integer) { (File) -> untyped } -> untyped
54
+ private def self.open_command: (String, Integer) { (File) -> untyped } -> untyped
55
+ end
56
+ end
@@ -2,26 +2,28 @@
2
2
 
3
3
  module Bullematic
4
4
  class Configuration
5
+ VALID_FIX_STRATEGIES: Array[Symbol]
6
+
5
7
  # @rbs skip: to avoid empty array warning
6
8
  DEFAULTS: untyped
7
9
 
8
- @enabled: bool
10
+ @logger: Logger?
9
11
 
10
- @auto_fix: bool
12
+ @debug: bool
11
13
 
12
- @target_paths: Array[String]
14
+ @fix_strategy: Symbol
13
15
 
14
- @skip_paths: Array[String]
16
+ @backup: bool
15
17
 
16
18
  @dry_run: bool
17
19
 
18
- @backup: bool
20
+ @skip_paths: Array[String]
19
21
 
20
- @fix_strategy: Symbol
22
+ @target_paths: Array[String]
21
23
 
22
- @debug: bool
24
+ @auto_fix: bool
23
25
 
24
- @logger: Logger?
26
+ @enabled: bool
25
27
 
26
28
  # @rbs!
27
29
  # attr_accessor enabled: bool
@@ -95,18 +97,6 @@ module Bullematic
95
97
  # attr_writer logger: Logger?
96
98
  attr_accessor backup: untyped
97
99
 
98
- # @rbs!
99
- # attr_accessor enabled: bool
100
- # attr_accessor auto_fix: bool
101
- # attr_accessor target_paths: Array[String]
102
- # attr_accessor skip_paths: Array[String]
103
- # attr_accessor dry_run: bool
104
- # attr_accessor backup: bool
105
- # attr_accessor fix_strategy: Symbol
106
- # attr_accessor debug: bool
107
- # attr_writer logger: Logger?
108
- attr_accessor fix_strategy: untyped
109
-
110
100
  # @rbs!
111
101
  # attr_accessor enabled: bool
112
102
  # attr_accessor auto_fix: bool
@@ -119,6 +109,8 @@ module Bullematic
119
109
  # attr_writer logger: Logger?
120
110
  attr_accessor debug: untyped
121
111
 
112
+ attr_reader fix_strategy: untyped
113
+
122
114
  attr_writer logger: untyped
123
115
 
124
116
  # : () -> void
@@ -127,6 +119,10 @@ module Bullematic
127
119
  # : () -> Logger
128
120
  def logger: () -> Logger
129
121
 
122
+ # @rbs strategy: Symbol
123
+ # @rbs return: Symbol
124
+ def fix_strategy=: (Symbol strategy) -> Symbol
125
+
130
126
  private
131
127
 
132
128
  # : () -> Logger