factory_sloth 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: []