ffast 0.0.7 → 0.0.8

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: 36aaca199777feb62d91a6e052c038f4a971ab20fd37f1adcb7b0c8d3a90553b
4
- data.tar.gz: 0ab405982443936ecaad957b43100496dce63d213e2a61c840b3002028c245e8
3
+ metadata.gz: 5e62b5be6bf84eabaa2ed601696164c248a6969cc504343f636b1d487bb4fbf2
4
+ data.tar.gz: a86d3f27396de8cb9aa097c02d1baf19c39ad7090f03b5894e1eddb087150f6b
5
5
  SHA512:
6
- metadata.gz: 32a5615982972c2e13582a8279cf94043a3a571044a6b1a04fee253f04c7ff93733eab4fca22f2878dc3e16d519cb3d78f06adafbe4b6bb998a72bea97929bc6
7
- data.tar.gz: 277f3082ab12c310be88c1bda1d8fa57be86bae02ddc39c8c5adb6e49704aec036c801abe6d6f18527f23996d560bd95d199cb6c0209c72474acc1c031600bc0
6
+ metadata.gz: 4a2a7db73282e36fd8ece6d9d78b55eb52e98227e5be77d77058a82ae97af634e94de7dd5f98640a844739956f5e8c5bf9f2fd91e88971d21da6a762cf69fdbb
7
+ data.tar.gz: 635bd2bb2a9e26975731fa4096f7980281ef929d2ae13d54f8f4897e8695d6e824deb15ed4cea6e6df9114191e99e210510a5878dc703910b711e1a17c446a68
@@ -4,6 +4,7 @@
4
4
  $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
5
 
6
6
  require 'fast'
7
+ require 'fast/experiment'
7
8
  require 'coderay'
8
9
 
9
10
  arguments = ARGV
@@ -69,7 +69,7 @@ module Fast
69
69
  Matcher.new(ast, search, *args).match?
70
70
  end
71
71
 
72
- def replace(ast, search, replacement)
72
+ def replace(ast, search, &replacement)
73
73
  buffer = Parser::Source::Buffer.new('replacement')
74
74
  buffer.source = ast.loc.expression.source
75
75
  to_replace = search(ast, search)
@@ -82,9 +82,9 @@ module Fast
82
82
  rewriter.rewrite(buffer, ast)
83
83
  end
84
84
 
85
- def replace_file(file, search, replacement)
85
+ def replace_file(file, search, &replacement)
86
86
  ast = ast_from_file(file)
87
- replace(ast, search, replacement)
87
+ replace(ast, search, &replacement)
88
88
  end
89
89
 
90
90
  def search_file(pattern, file)
@@ -152,13 +152,6 @@ module Fast
152
152
  def expression(string)
153
153
  ExpressionParser.new(string).parse
154
154
  end
155
-
156
- def experiment(name, &block)
157
- @experiments ||= {}
158
- @experiments[name] = Experiment.new(name, &block)
159
- end
160
-
161
- attr_reader :experiments
162
155
  attr_accessor :debugging
163
156
 
164
157
  def debug
@@ -540,7 +533,6 @@ module Fast
540
533
  end
541
534
  end
542
535
 
543
- # rubocop:disable Metrics/AbcSize
544
536
  def match?(ast = @ast, fast = @fast)
545
537
  head, *tail = fast
546
538
  return false unless head.match?(ast)
@@ -555,8 +547,6 @@ module Fast
555
547
  end && find_captures
556
548
  end
557
549
 
558
- # rubocop:enable Metrics/AbcSize
559
-
560
550
  def prepare_token(token)
561
551
  case token
562
552
  when Fast::FindWithCapture
@@ -582,224 +572,4 @@ module Fast
582
572
  end
583
573
  end
584
574
  end
585
-
586
- # You can define experiments and build experimental files to improve some code in
587
- # an automated way. Let's create a hook to check if a `before` or `after` block
588
- # is useless in a specific spec:
589
- #
590
- # ```ruby
591
- # Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
592
- # lookup 'some_spec.rb'
593
- # search "(block (send nil {before after}))"
594
- # edit {|node| remove(node.loc.expression) }
595
- # policy {|new_file| system("bin/spring rspec --fail-fast #{new_file}") }
596
- # end
597
- # ```
598
- class Experiment
599
- attr_writer :files
600
- attr_reader :name, :replacement, :expression, :files_or_folders, :ok_if
601
-
602
- def initialize(name, &block)
603
- @name = name
604
- puts "\nStarting experiment: #{name}"
605
- instance_exec(&block)
606
- end
607
-
608
- def run_with(file)
609
- ExperimentFile.new(file, self).run
610
- end
611
-
612
- def search(expression)
613
- @expression = expression
614
- end
615
-
616
- def edit(&block)
617
- @replacement = block
618
- end
619
-
620
- def lookup(files_or_folders)
621
- @files_or_folders = files_or_folders
622
- end
623
-
624
- def policy(&block)
625
- @ok_if = block
626
- end
627
-
628
- def files
629
- @files ||= Fast.ruby_files_from(@files_or_folders)
630
- end
631
-
632
- def run
633
- files.map(&method(:run_with))
634
- end
635
- end
636
-
637
- # Suggest possible combinations of occurrences to replace.
638
- class ExperimentCombinations
639
- attr_reader :combinations
640
-
641
- def initialize(round:, occurrences_count:, ok_experiments:, fail_experiments:)
642
- @round = round
643
- @ok_experiments = ok_experiments
644
- @fail_experiments = fail_experiments
645
- @occurrences_count = occurrences_count
646
- end
647
-
648
- def generate_combinations
649
- case @round
650
- when 1
651
- individual_replacements
652
- when 2
653
- all_ok_replacements_combined
654
- else
655
- ok_replacements_pair_combinations
656
- end
657
- end
658
-
659
- # Replace a single occurrence at each iteration and identify which
660
- # individual replacements work.
661
- def individual_replacements
662
- (1..@occurrences_count).to_a
663
- end
664
-
665
- # After identifying all individual replacements that work, try combining all
666
- # of them.
667
- def all_ok_replacements_combined
668
- [@ok_experiments.uniq.sort]
669
- end
670
-
671
- # Combining all successful individual replacements has failed. Lets divide
672
- # and conquer.
673
- def ok_replacements_pair_combinations
674
- @ok_experiments
675
- .combination(2)
676
- .map { |e| e.flatten.uniq.sort }
677
- .uniq - @fail_experiments - @ok_experiments
678
- end
679
- end
680
-
681
- # Encapsulate the join of an Experiment with an specific file.
682
- # This is important to coordinate and regulate multiple experiments in the same file.
683
- # It can track successfull experiments and failures and suggest new combinations to keep replacing the file.
684
- class ExperimentFile
685
- attr_reader :ok_experiments, :fail_experiments, :experiment
686
-
687
- def initialize(file, experiment)
688
- @file = file
689
- @ast = Fast.ast_from_file(file) if file
690
- @experiment = experiment
691
- @ok_experiments = []
692
- @fail_experiments = []
693
- @round = 0
694
- end
695
-
696
- def search
697
- experiment.expression
698
- end
699
-
700
- def experimental_filename(combination)
701
- parts = @file.split('/')
702
- dir = parts[0..-2]
703
- filename = "experiment_#{[*combination].join('_')}_#{parts[-1]}"
704
- File.join(*dir, filename)
705
- end
706
-
707
- def ok_with(combination)
708
- @ok_experiments << combination
709
- return unless combination.is_a?(Array)
710
-
711
- combination.each do |element|
712
- @ok_experiments.delete(element)
713
- end
714
- end
715
-
716
- def failed_with(combination)
717
- @fail_experiments << combination
718
- end
719
-
720
- def search_cases
721
- Fast.search(@ast, experiment.expression) || []
722
- end
723
-
724
- # rubocop:disable Metrics/AbcSize
725
- # rubocop:disable Metrics/MethodLength
726
- def partial_replace(*indices)
727
- replacement = experiment.replacement
728
- new_content = Fast.replace_file @file, experiment.expression, ->(node, *captures) do # rubocop:disable Style/Lambda
729
- if indices.nil? || indices.empty? || indices.include?(match_index)
730
- if replacement.parameters.length == 1
731
- instance_exec node, &replacement
732
- else
733
- instance_exec node, *captures, &replacement
734
- end
735
- end
736
- end
737
- return unless new_content
738
-
739
- write_experiment_file(indices, new_content)
740
- new_content
741
- end
742
- # rubocop:enable Metrics/AbcSize
743
- # rubocop:enable Metrics/MethodLength
744
-
745
- def write_experiment_file(index, new_content)
746
- filename = experimental_filename(index)
747
- File.open(filename, 'w+') { |f| f.puts new_content }
748
- filename
749
- end
750
-
751
- def done!
752
- count_executed_combinations = @fail_experiments.size + @ok_experiments.size
753
- puts "Done with #{@file} after #{count_executed_combinations} combinations"
754
- return unless perfect_combination = @ok_experiments.last # rubocop:disable Lint/AssignmentInCondition
755
-
756
- puts 'The following changes were applied to the file:'
757
- `diff #{experimental_filename(perfect_combination)} #{@file}`
758
- puts "mv #{experimental_filename(perfect_combination)} #{@file}"
759
- `mv #{experimental_filename(perfect_combination)} #{@file}`
760
- end
761
-
762
- def build_combinations
763
- @round += 1
764
- ExperimentCombinations.new(
765
- round: @round,
766
- occurrences_count: search_cases.size,
767
- ok_experiments: @ok_experiments,
768
- fail_experiments: @fail_experiments
769
- ).generate_combinations
770
- end
771
-
772
- def run
773
- while (combinations = build_combinations).any?
774
- if combinations.size > 1000
775
- puts "Ignoring #{@file} because it has #{combinations.size} possible combinations"
776
- break
777
- end
778
- puts "#{@file} - Round #{@round} - Possible combinations: #{combinations.inspect}"
779
- while combination = combinations.shift # rubocop:disable Lint/AssignmentInCondition
780
- run_partial_replacement_with(combination)
781
- end
782
- end
783
- done!
784
- end
785
-
786
- def run_partial_replacement_with(combination) # rubocop:disable Metrics/AbcSize
787
- content = partial_replace(*combination)
788
- experimental_file = experimental_filename(combination)
789
-
790
- File.open(experimental_file, 'w+') { |f| f.puts content }
791
-
792
- raise 'No changes were made to the file.' if FileUtils.compare_file(@file, experimental_file)
793
-
794
- result = experiment.ok_if.call(experimental_file)
795
-
796
- if result.success
797
- ok_with(combination)
798
- puts "✅ #{experimental_file} - Combination: #{combination} - Time: #{result.execution_time}s"
799
- else
800
- failed_with(combination)
801
- puts "🔴 #{experimental_file} - Combination: #{combination}"
802
- end
803
- end
804
- end
805
575
  end
@@ -117,9 +117,7 @@ module Fast
117
117
  end
118
118
 
119
119
  def debug(*info)
120
- return unless @debug
121
-
122
- puts info
120
+ puts(info) if debug_mode?
123
121
  end
124
122
 
125
123
  def report(result, file)
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fast'
4
+
5
+ module Fast
6
+ class << self
7
+ # Fast.experiment is a shortcut to define new experiments and allow them to
8
+ # work together in experiment combinantions.
9
+ #
10
+ # The following experiment look into `spec` folder and try to remove
11
+ # `before` and `after` blocks on testing code. Sometimes they're not
12
+ # effective and we can avoid the hard work of do it manually.
13
+ #
14
+ # If the spec does not fail, it keeps the change.
15
+ #
16
+ # Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
17
+ # lookup 'spec'
18
+ # search "(block (send nil {before after}))"
19
+ # edit { |node| remove(node.loc.expression) }
20
+ # policy { |new_file| system("rspec --fail-fast #{new_file}") }
21
+ # end
22
+ def experiment(name, &block)
23
+ @experiments ||= {}
24
+ @experiments[name] = Experiment.new(name, &block)
25
+ end
26
+
27
+ attr_reader :experiments
28
+ end
29
+
30
+ # You can define experiments and build experimental files to improve some code in
31
+ # an automated way. Let's create a hook to check if a `before` or `after` block
32
+ # is useless in a specific spec:
33
+ #
34
+ # ```ruby
35
+ # Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
36
+ # lookup 'some_spec.rb'
37
+ # search "(block (send nil {before after}))"
38
+ # edit {|node| remove(node.loc.expression) }
39
+ # policy {|new_file| system("bin/spring rspec --fail-fast #{new_file}") }
40
+ # end
41
+ # ```
42
+ class Experiment
43
+ attr_writer :files
44
+ attr_reader :name, :replacement, :expression, :files_or_folders, :ok_if
45
+
46
+ def initialize(name, &block)
47
+ @name = name
48
+ puts "\nStarting experiment: #{name}"
49
+ instance_exec(&block)
50
+ end
51
+
52
+ def run_with(file)
53
+ ExperimentFile.new(file, self).run
54
+ end
55
+
56
+ def search(expression)
57
+ @expression = expression
58
+ end
59
+
60
+ def edit(&block)
61
+ @replacement = block
62
+ end
63
+
64
+ def lookup(files_or_folders)
65
+ @files_or_folders = files_or_folders
66
+ end
67
+
68
+ def policy(&block)
69
+ @ok_if = block
70
+ end
71
+
72
+ def files
73
+ @files ||= Fast.ruby_files_from(@files_or_folders)
74
+ end
75
+
76
+ def run
77
+ files.map(&method(:run_with))
78
+ end
79
+ end
80
+
81
+ # Suggest possible combinations of occurrences to replace.
82
+ class ExperimentCombinations
83
+ attr_reader :combinations
84
+
85
+ def initialize(round:, occurrences_count:, ok_experiments:, fail_experiments:)
86
+ @round = round
87
+ @ok_experiments = ok_experiments
88
+ @fail_experiments = fail_experiments
89
+ @occurrences_count = occurrences_count
90
+ end
91
+
92
+ def generate_combinations
93
+ case @round
94
+ when 1
95
+ individual_replacements
96
+ when 2
97
+ all_ok_replacements_combined
98
+ else
99
+ ok_replacements_pair_combinations
100
+ end
101
+ end
102
+
103
+ # Replace a single occurrence at each iteration and identify which
104
+ # individual replacements work.
105
+ def individual_replacements
106
+ (1..@occurrences_count).to_a
107
+ end
108
+
109
+ # After identifying all individual replacements that work, try combining all
110
+ # of them.
111
+ def all_ok_replacements_combined
112
+ [@ok_experiments.uniq.sort]
113
+ end
114
+
115
+ # Combining all successful individual replacements has failed. Lets divide
116
+ # and conquer.
117
+ def ok_replacements_pair_combinations
118
+ @ok_experiments
119
+ .combination(2)
120
+ .map { |e| e.flatten.uniq.sort }
121
+ .uniq - @fail_experiments - @ok_experiments
122
+ end
123
+ end
124
+
125
+ # Encapsulate the join of an Experiment with an specific file.
126
+ # This is important to coordinate and regulate multiple experiments in the same file.
127
+ # It can track successfull experiments and failures and suggest new combinations to keep replacing the file.
128
+ class ExperimentFile
129
+ attr_reader :ok_experiments, :fail_experiments, :experiment
130
+
131
+ def initialize(file, experiment)
132
+ @file = file
133
+ @ast = Fast.ast_from_file(file) if file
134
+ @experiment = experiment
135
+ @ok_experiments = []
136
+ @fail_experiments = []
137
+ @round = 0
138
+ end
139
+
140
+ def search
141
+ experiment.expression
142
+ end
143
+
144
+ def experimental_filename(combination)
145
+ parts = @file.split('/')
146
+ dir = parts[0..-2]
147
+ filename = "experiment_#{[*combination].join('_')}_#{parts[-1]}"
148
+ File.join(*dir, filename)
149
+ end
150
+
151
+ def ok_with(combination)
152
+ @ok_experiments << combination
153
+ return unless combination.is_a?(Array)
154
+
155
+ combination.each do |element|
156
+ @ok_experiments.delete(element)
157
+ end
158
+ end
159
+
160
+ def failed_with(combination)
161
+ @fail_experiments << combination
162
+ end
163
+
164
+ def search_cases
165
+ Fast.search(@ast, experiment.expression) || []
166
+ end
167
+
168
+ # rubocop:disable Metrics/AbcSize
169
+ # rubocop:disable Metrics/MethodLength
170
+ def partial_replace(*indices)
171
+ replacement = experiment.replacement
172
+ new_content = Fast.replace_file @file, experiment.expression do |node, *captures|
173
+ if indices.nil? || indices.empty? || indices.include?(match_index)
174
+ if replacement.parameters.length == 1
175
+ instance_exec node, &replacement
176
+ else
177
+ instance_exec node, *captures, &replacement
178
+ end
179
+ end
180
+ end
181
+ return unless new_content
182
+
183
+ write_experiment_file(indices, new_content)
184
+ new_content
185
+ end
186
+ # rubocop:enable Metrics/AbcSize
187
+ # rubocop:enable Metrics/MethodLength
188
+
189
+ def write_experiment_file(index, new_content)
190
+ filename = experimental_filename(index)
191
+ File.open(filename, 'w+') { |f| f.puts new_content }
192
+ filename
193
+ end
194
+
195
+ def done!
196
+ count_executed_combinations = @fail_experiments.size + @ok_experiments.size
197
+ puts "Done with #{@file} after #{count_executed_combinations} combinations"
198
+ return unless perfect_combination = @ok_experiments.last # rubocop:disable Lint/AssignmentInCondition
199
+
200
+ puts 'The following changes were applied to the file:'
201
+ `diff #{experimental_filename(perfect_combination)} #{@file}`
202
+ puts "mv #{experimental_filename(perfect_combination)} #{@file}"
203
+ `mv #{experimental_filename(perfect_combination)} #{@file}`
204
+ end
205
+
206
+ def build_combinations
207
+ @round += 1
208
+ ExperimentCombinations.new(
209
+ round: @round,
210
+ occurrences_count: search_cases.size,
211
+ ok_experiments: @ok_experiments,
212
+ fail_experiments: @fail_experiments
213
+ ).generate_combinations
214
+ end
215
+
216
+ def run
217
+ while (combinations = build_combinations).any?
218
+ if combinations.size > 1000
219
+ puts "Ignoring #{@file} because it has #{combinations.size} possible combinations"
220
+ break
221
+ end
222
+ puts "#{@file} - Round #{@round} - Possible combinations: #{combinations.inspect}"
223
+ while combination = combinations.shift # rubocop:disable Lint/AssignmentInCondition
224
+ run_partial_replacement_with(combination)
225
+ end
226
+ end
227
+ done!
228
+ end
229
+
230
+ def run_partial_replacement_with(combination) # rubocop:disable Metrics/AbcSize
231
+ content = partial_replace(*combination)
232
+ experimental_file = experimental_filename(combination)
233
+
234
+ File.open(experimental_file, 'w+') { |f| f.puts content }
235
+
236
+ raise 'No changes were made to the file.' if FileUtils.compare_file(@file, experimental_file)
237
+
238
+ result = experiment.ok_if.call(experimental_file)
239
+
240
+ if result.success
241
+ ok_with(combination)
242
+ puts "✅ #{experimental_file} - Combination: #{combination} - Time: #{result.execution_time}s"
243
+ else
244
+ failed_with(combination)
245
+ puts "🔴 #{experimental_file} - Combination: #{combination}"
246
+ end
247
+ end
248
+ end
249
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fast
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.8'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jônatas Davi Paganini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-24 00:00:00.000000000 Z
11
+ date: 2019-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: astrolabe
@@ -235,6 +235,7 @@ files:
235
235
  - fast.gemspec
236
236
  - lib/fast.rb
237
237
  - lib/fast/cli.rb
238
+ - lib/fast/experiment.rb
238
239
  - lib/fast/version.rb
239
240
  - mkdocs.yml
240
241
  homepage: https://jonatas.github.io/fast/