factory_sloth 1.3.0 → 1.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9032b54abec61f25adcfd938a503f312017667f64b4aa1f303ede50a346a62be
4
- data.tar.gz: 901d358d3572a708ec8190c2d2e59dbdab3bce42f894301284dc285895d48f19
3
+ metadata.gz: c9c56ba87b89aec371bdf53b61c50c66a0f0c51b8ba75c4edc12245967face48
4
+ data.tar.gz: f1ead7cb06982fd34eedde94bc017dfb5612c744266d9e76fa014864fdaccc7c
5
5
  SHA512:
6
- metadata.gz: b1aa29c8c7dd007c5e82ee4cfbc164df1550ace4c2d7880e0471b4a892a2dad33a8e03469c56ec5620f99e7c2dc8c6c47f0d7256102910385acea9a3f97f4b74
7
- data.tar.gz: bace623884d7e447200af29fb216f8814f86a7bae4deda093105bb9df716c8151332136a473c31cd6c9e51bc07ded9f2306e9eda29b536b7b4aa130384f0059a
6
+ metadata.gz: 1016ee245df8b06fb396f12806004c6ea63f88944b3286723623906dca43be42a5b39dae87db377f9616f35f6b5b3bea2d080eaa0818a14f429f110c388c9a4f
7
+ data.tar.gz: 96c402e4c4a287e69cb641ccf281365da5ea4f64f6d1092534483b9971d93fd0196bcb22c870516b3df5b88eaabe9097ae2a12758acc28f7616b2615e56935f6
data/CHANGELOG.md CHANGED
@@ -1,11 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Added
4
+
5
+ ## [1.4.0] - 2024-10-04
6
+
7
+ - ignore underscore-prefixed assignments e.g. `_user = create(:user)`
8
+
9
+ ## [1.3.1] - 2023-05-24
10
+
11
+ ### Fixed
12
+
13
+ - Stop trying to patch lines with multiple create calls, which was unreliable
14
+
3
15
  ## [1.3.0] - 2023-05-22
4
16
 
5
17
  ### Added
6
18
 
7
- - nicer output
8
- - verbose mode
19
+ - Nicer output
20
+ - Verbose mode
9
21
 
10
22
  ## [1.2.2] - 2023-05-18
11
23
 
data/README.md CHANGED
@@ -27,6 +27,7 @@ Examples:
27
27
  Options:
28
28
  -f, --force Ignore ./.factory_sloth_done
29
29
  -l, --lint Dont fix, just list bad create calls
30
+ -u, --underscore Check underscore-prefixed variables
30
31
  -V, --verbose Verbose output, useful for debugging
31
32
  -v, --version Show gem version
32
33
  -h, --help Show this help
@@ -80,6 +81,16 @@ expect { User.delete_all }.to change { User.count }.from(1).to(0)
80
81
 
81
82
  If you have a good idea about how to detect such cases automatically, let me know :)
82
83
 
84
+ ### Underscore-prefixed variable names
85
+
86
+ Prefixing a variable name with an underscore is a common way of saying "I am creating this for side effects":
87
+
88
+ ```ruby
89
+ _user = create(:user)
90
+ ```
91
+
92
+ Therefore `factory_sloth` ignores such cases by default. Use the `-u` flag to check such cases as well.
93
+
83
94
  ## Development
84
95
 
85
96
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -36,6 +36,10 @@ module FactorySloth
36
36
  FactorySloth.lint = true
37
37
  end
38
38
 
39
+ opts.on('-u', '--underscore', 'Check underscore-prefixed variables') do
40
+ FactorySloth.check_underscore_vars = true
41
+ end
42
+
39
43
  opts.on('-V', '--verbose', 'Verbose output, useful for debugging') do
40
44
  FactorySloth.verbose = true
41
45
  end
@@ -22,17 +22,7 @@ module FactorySloth
22
22
  def call
23
23
  self.create_calls = CreateCallFinder.call(code: original_code)
24
24
 
25
- # Performance note: it might be faster to write ALL possible patches for a
26
- # given spec file to tempfiles first, and then run them all in a single
27
- # rspec call. However, this would make it impossible to use `--fail-fast`,
28
- # and might make examples fail that are not as idempotent as they should be.
29
- self.changed_create_calls =
30
- create_calls.sort_by { |call| [-call.line, -call.column] }.select do |call|
31
- build_result = try_patch(call, 'build')
32
- next if build_result == ABORT
33
-
34
- build_result == SUCCESS || try_patch(call, 'build_stubbed') == SUCCESS
35
- end
25
+ self.changed_create_calls = find_changeable_create_calls
36
26
 
37
27
  # validate whole spec after changes, e.g. to detect side-effects
38
28
  self.ok = changed_create_calls.none? || begin
@@ -65,6 +55,27 @@ module FactorySloth
65
55
 
66
56
  attr_writer :create_calls, :changed_create_calls, :ok, :path, :original_code, :patched_code
67
57
 
58
+ # Performance note: it might be faster to write ALL possible patches for a
59
+ # given spec file to tempfiles first, and then run them all in a single
60
+ # rspec call. However, this would make it impossible to use `--fail-fast`,
61
+ # and might make examples fail that are not as idempotent as they should be.
62
+ def find_changeable_create_calls
63
+ lines = create_calls.map(&:line)
64
+
65
+ self.changed_create_calls =
66
+ create_calls.sort_by { |call| [-call.line, -call.column] }.select do |call|
67
+ if lines.count(call.line) > 1
68
+ print_call_info(call, 'multiple create calls per line are unsupported, skipping')
69
+ next
70
+ end
71
+
72
+ build_result = try_patch(call, 'build')
73
+ next if build_result == ABORT
74
+
75
+ build_result == SUCCESS || try_patch(call, 'build_stubbed') == SUCCESS
76
+ end
77
+ end
78
+
68
79
  def try_patch(call, base_variant)
69
80
  variant = call.name.sub('create', base_variant)
70
81
  FactorySloth.verbose && puts("#{link_to_call(call)}: trying #{variant} ...")
@@ -79,14 +90,14 @@ module FactorySloth
79
90
 
80
91
  if result.success?
81
92
  info = FactorySloth.dry_run ? 'can be replaced' : 'replaced'
82
- puts call_message(call, "#{info} with #{variant}"), ''
93
+ print_call_info(call, "#{info} with #{variant}")
83
94
  self.patched_code = new_patched_code
84
95
  SUCCESS
85
96
  elsif result.exitstatus == ExecutionCheck::FACTORY_UNUSED_CODE
86
- puts call_message(call, "is never executed, skipping"), ''
97
+ print_call_info(call, "is never executed, skipping")
87
98
  ABORT
88
99
  elsif result.exitstatus == ExecutionCheck::FACTORY_PERSISTED_LATER_CODE
89
- FactorySloth.verbose && puts("Record is persisted later, skipping")
100
+ FactorySloth.verbose && print_call_info("record is persisted later, skipping")
90
101
  ABORT
91
102
  end
92
103
  end
@@ -100,13 +111,17 @@ module FactorySloth
100
111
  ABORT = :ABORT # returned if there is no need to try other variants
101
112
  SUCCESS = :SUCCESS
102
113
 
103
- def call_message(call, message)
114
+ def print_call_info(call, message)
104
115
  line_content = original_code[/\A(?:.*\R){#{call.line - 1}}\K.*/]
105
- indent = line_content[/^\s*/]
106
-
107
- "#{link_to_call(call)}: #{call.name} #{message}\n"\
108
- " #{line_content.delete_prefix(indent)}\n"\
109
- " #{' ' * (call.column - indent.size)}#{Color.yellow('^' * call.name.size)}"
116
+ indentation = line_content[/^\s*/]
117
+ underline = Color.yellow('^' * call.name.size)
118
+
119
+ puts(
120
+ "#{link_to_call(call)}: #{call.name} #{message}",
121
+ " #{line_content.delete_prefix(indentation)}",
122
+ " #{' ' * (call.column - indentation.size)}#{underline}",
123
+ "",
124
+ )
110
125
  end
111
126
 
112
127
  def link_to_call(call)
@@ -14,8 +14,12 @@ module FactorySloth::Color
14
14
  private
15
15
 
16
16
  def colorize(str, color_code)
17
- return str unless $stdout.is_a?(IO) && $stdout.tty?
17
+ return str unless tty?
18
18
 
19
19
  "\e[#{color_code}m#{str}\e[0m"
20
20
  end
21
+
22
+ def tty?
23
+ $stdout.is_a?(IO) && $stdout.tty?
24
+ end
21
25
  end
@@ -17,19 +17,32 @@ class FactorySloth::CreateCallFinder < Ripper
17
17
 
18
18
  def store_call(obj)
19
19
  @calls << obj if obj.is_a?(FactorySloth::CreateCall) && !@disabled
20
+ obj
20
21
  end
21
22
 
22
23
  def on_ident(name, *)
23
- %w[create create_list create_pair].include?(name) &&
24
- FactorySloth::CreateCall.new(name: name, line: lineno, column: column)
24
+ return name unless %w[create create_list create_pair].include?(name)
25
+
26
+ FactorySloth::CreateCall.new(name: name, line: lineno, column: column)
27
+ end
28
+
29
+ def on_assign(left, right)
30
+ if left.to_s.match?(/\A_/) &&
31
+ !FactorySloth.check_underscore_vars &&
32
+ [right, Array(right).last].any? { |v| v.is_a?(FactorySloth::CreateCall) }
33
+ @calls.pop
34
+ end
35
+ [left, right]
25
36
  end
26
37
 
27
- def on_call(mod, _, obj, *)
28
- store_call(obj) if mod == 'FactoryBot'
38
+ def on_call(mod, op, method, *args)
39
+ store_call(method) if mod == 'FactoryBot'
40
+ [mod, op, method, *args]
29
41
  end
30
42
 
31
- def on_command_call(mod, _, obj, *)
32
- store_call(obj) if mod == 'FactoryBot'
43
+ def on_command_call(mod, op, method, *args)
44
+ store_call(method) if mod == 'FactoryBot'
45
+ [mod, op, method, *args]
33
46
  end
34
47
 
35
48
  def on_comment(text, *)
@@ -4,6 +4,10 @@
4
4
  # The rationale behind a) is that things like skipped examples should not
5
5
  # be broken. The rationale behind b) is that not much DB work would be saved,
6
6
  # but diff noise would be increased and ease of editing the example reduced.
7
+ #
8
+ # Note: the caller column is only available in a roundabout way in Ruby >= 3.1,
9
+ # https://bugs.ruby-lang.org/issues/17930, 19452, so multiple replacements
10
+ # in one line would not be validated correctly iff they had mixed validity.
7
11
 
8
12
  module FactorySloth::ExecutionCheck
9
13
  FACTORY_UNUSED_CODE = 77
@@ -15,10 +19,10 @@ module FactorySloth::ExecutionCheck
15
19
  records_by_line = {} # track records initialized through factories per line
16
20
 
17
21
  FactoryBot::Syntax::Methods.class_eval do
18
- alias ___original_#{variant} #{variant} # e.g. ___original_build build
22
+ original_variant = instance_method("#{variant}") # e.g. build
19
23
 
20
- define_method("#{variant}") do |*args, **kwargs, &blk| # e.g. build
21
- result = ___original_#{variant}(*args, **kwargs, &blk)
24
+ define_method("#{variant}") do |*args, **kwargs, &blk|
25
+ result = original_variant.bind_call(self, *args, **kwargs, &blk)
22
26
  list = records_by_line[caller_locations(1, 1)&.first&.lineno] ||= []
23
27
  list.concat([result].flatten) # to work with single, list, and pair
24
28
  result
@@ -1,3 +1,3 @@
1
1
  module FactorySloth
2
- VERSION = '1.3.0'
2
+ VERSION = '1.4.0'
3
3
  end
data/lib/factory_sloth.rb CHANGED
@@ -1,5 +1,11 @@
1
1
  module FactorySloth
2
- singleton_class.attr_accessor :dry_run, :force, :lint, :verbose
2
+ singleton_class.attr_accessor(*%i[
3
+ check_underscore_vars
4
+ dry_run
5
+ force
6
+ lint
7
+ verbose
8
+ ])
3
9
  end
4
10
 
5
11
  require_relative 'factory_sloth/cli'
metadata CHANGED
@@ -1,16 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factory_sloth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janosch Müller
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-05-22 00:00:00.000000000 Z
10
+ date: 2024-10-04 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email:
15
13
  - janosch84@gmail.com
16
14
  executables:
@@ -44,7 +42,6 @@ metadata:
44
42
  homepage_uri: https://github.com/jaynetics/factory_sloth
45
43
  source_code_uri: https://github.com/jaynetics/factory_sloth
46
44
  changelog_uri: https://github.com/jaynetics/factory_sloth/blob/main/CHANGELOG.md
47
- post_install_message:
48
45
  rdoc_options: []
49
46
  require_paths:
50
47
  - lib
@@ -59,8 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
56
  - !ruby/object:Gem::Version
60
57
  version: '0'
61
58
  requirements: []
62
- rubygems_version: 3.4.13
63
- signing_key:
59
+ rubygems_version: 3.6.0.dev
64
60
  specification_version: 4
65
61
  summary: Find and replace unnecessary factory_bot create calls.
66
62
  test_files: []