fasterer 0.1.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 (103) hide show
  1. checksums.yaml +7 -0
  2. data/.fasterer.yml +17 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +8 -0
  6. data/.travis.yml +6 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +93 -0
  10. data/Rakefile +7 -0
  11. data/bin/fasterer +5 -0
  12. data/fasterer.gemspec +28 -0
  13. data/lib/fasterer.rb +7 -0
  14. data/lib/fasterer/analyzer.rb +99 -0
  15. data/lib/fasterer/binary_call.rb +4 -0
  16. data/lib/fasterer/cli.rb +10 -0
  17. data/lib/fasterer/file_traverser.rb +81 -0
  18. data/lib/fasterer/method_call.rb +138 -0
  19. data/lib/fasterer/method_definition.rb +51 -0
  20. data/lib/fasterer/offense.rb +69 -0
  21. data/lib/fasterer/offense_collector.rb +17 -0
  22. data/lib/fasterer/parse_error.rb +10 -0
  23. data/lib/fasterer/parser.rb +11 -0
  24. data/lib/fasterer/rescue_call.rb +36 -0
  25. data/lib/fasterer/scanners/method_call_scanner.rb +134 -0
  26. data/lib/fasterer/scanners/method_definition_scanner.rb +52 -0
  27. data/lib/fasterer/scanners/offensive.rb +25 -0
  28. data/lib/fasterer/scanners/rescue_call_scanner.rb +28 -0
  29. data/lib/fasterer/token.rb +27 -0
  30. data/lib/fasterer/version.rb +3 -0
  31. data/spec/lib/fasterer/analyzer/01_parallel_assignment_spec.rb +12 -0
  32. data/spec/lib/fasterer/analyzer/02_rescue_vs_respond_to_spec.rb +12 -0
  33. data/spec/lib/fasterer/analyzer/03_module_eval_spec.rb +12 -0
  34. data/spec/lib/fasterer/analyzer/04_find_vs_bsearch_spec.rb +12 -0
  35. data/spec/lib/fasterer/analyzer/06_shuffle_first_vs_sample_spec.rb +12 -0
  36. data/spec/lib/fasterer/analyzer/08_for_loop_vs_each_spec.rb +12 -0
  37. data/spec/lib/fasterer/analyzer/09_each_with_index_vs_while_spec.rb +12 -0
  38. data/spec/lib/fasterer/analyzer/10_map_flatten_vs_flat_map_spec.rb +12 -0
  39. data/spec/lib/fasterer/analyzer/11_reverse_each_vs_reverse_each_spec.rb +12 -0
  40. data/spec/lib/fasterer/analyzer/12_select_first_vs_detect_spec.rb +12 -0
  41. data/spec/lib/fasterer/analyzer/13_sort_vs_sort_by_spec.rb +12 -0
  42. data/spec/lib/fasterer/analyzer/14_fetch_with_argument_vs_block_spec.rb +12 -0
  43. data/spec/lib/fasterer/analyzer/15_keys_each_vs_each_key_spec.rb +12 -0
  44. data/spec/lib/fasterer/analyzer/16_hash_merge_bang_vs_hash_brackets_spec.rb +12 -0
  45. data/spec/lib/fasterer/analyzer/18_block_vs_symbol_to_proc_spec.rb +12 -0
  46. data/spec/lib/fasterer/analyzer/19_proc_call_vs_yield_spec.rb +12 -0
  47. data/spec/lib/fasterer/analyzer/24_gsub_vs_tr_spec.rb +12 -0
  48. data/spec/lib/fasterer/analyzer/98_misc_spec.rb +11 -0
  49. data/spec/lib/fasterer/analyzer/99_exceptional_files_spec.rb +11 -0
  50. data/spec/lib/fasterer/method_call_spec.rb +483 -0
  51. data/spec/lib/fasterer/method_definition_spec.rb +93 -0
  52. data/spec/lib/fasterer/rescue_call_spec.rb +76 -0
  53. data/spec/spec_helper.rb +99 -0
  54. data/spec/support/analyzer/01_parallel_assignment.rb +10 -0
  55. data/spec/support/analyzer/02_rescue_vs_respond_to.rb +36 -0
  56. data/spec/support/analyzer/03_module_eval.rb +11 -0
  57. data/spec/support/analyzer/04_find_vs_bsearch.rb +5 -0
  58. data/spec/support/analyzer/06_shuffle_first_vs_sample.rb +5 -0
  59. data/spec/support/analyzer/08_for_loop_vs_each.rb +8 -0
  60. data/spec/support/analyzer/09_each_with_index_vs_while.rb +3 -0
  61. data/spec/support/analyzer/10_map_flatten_vs_flat_map.rb +15 -0
  62. data/spec/support/analyzer/11_reverse_each_vs_reverse_each.rb +9 -0
  63. data/spec/support/analyzer/12_select_first_vs_detect.rb +7 -0
  64. data/spec/support/analyzer/13_sort_vs_sort_by.rb +9 -0
  65. data/spec/support/analyzer/14_fetch_with_argument_vs_block.rb +11 -0
  66. data/spec/support/analyzer/15_keys_each_vs_each_key.rb +15 -0
  67. data/spec/support/analyzer/16_hash_merge_bang_vs_hash_brackets.rb +21 -0
  68. data/spec/support/analyzer/18_block_vs_symbol_to_proc.rb +30 -0
  69. data/spec/support/analyzer/19_proc_call_vs_yield.rb +24 -0
  70. data/spec/support/analyzer/24_gsub_vs_tr.rb +14 -0
  71. data/spec/support/analyzer/98_misc.rb +15 -0
  72. data/spec/support/analyzer/99_exceptional_files.rb +7 -0
  73. data/spec/support/binary_call/simple_comparison.rb +0 -0
  74. data/spec/support/method_call/method_call_on_constant.rb +1 -0
  75. data/spec/support/method_call/method_call_on_integer.rb +1 -0
  76. data/spec/support/method_call/method_call_on_method_call.rb +1 -0
  77. data/spec/support/method_call/method_call_on_string.rb +1 -0
  78. data/spec/support/method_call/method_call_on_variable.rb +2 -0
  79. data/spec/support/method_call/method_call_with_a_block.rb +5 -0
  80. data/spec/support/method_call/method_call_with_a_integer_argument.rb +1 -0
  81. data/spec/support/method_call/method_call_with_a_regex_argument.rb +1 -0
  82. data/spec/support/method_call/method_call_with_an_argument_and_a_block.rb +2 -0
  83. data/spec/support/method_call/method_call_with_an_implicit_receiver.rb +1 -0
  84. data/spec/support/method_call/method_call_with_an_implicit_receiver_and_no_brackets.rb +1 -0
  85. data/spec/support/method_call/method_call_with_an_implicit_receiver_and_no_brackets_and_do_end.rb +3 -0
  86. data/spec/support/method_call/method_call_with_equals.rb +1 -0
  87. data/spec/support/method_call/method_call_with_one_argument.rb +1 -0
  88. data/spec/support/method_call/method_call_with_two_arguments.rb +2 -0
  89. data/spec/support/method_call/method_call_without_brackets.rb +1 -0
  90. data/spec/support/method_definition/method_with_argument_and_block.rb +3 -0
  91. data/spec/support/method_definition/method_with_block.rb +3 -0
  92. data/spec/support/method_definition/method_with_default_argument.rb +3 -0
  93. data/spec/support/method_definition/method_with_splat_and_block.rb +3 -0
  94. data/spec/support/method_definition/simple_method.rb +2 -0
  95. data/spec/support/method_definition/simple_method_omitted_parenthesis.rb +2 -0
  96. data/spec/support/method_definition/simple_method_with_argument.rb +3 -0
  97. data/spec/support/rescue_call/plain_rescue.rb +7 -0
  98. data/spec/support/rescue_call/rescue_with_class.rb +5 -0
  99. data/spec/support/rescue_call/rescue_with_class_and_variable.rb +5 -0
  100. data/spec/support/rescue_call/rescue_with_multiple_classes.rb +4 -0
  101. data/spec/support/rescue_call/rescue_with_multiple_classes_and_variable.rb +5 -0
  102. data/spec/support/rescue_call/rescue_with_variable.rb +6 -0
  103. metadata +303 -0
@@ -0,0 +1,138 @@
1
+ module Fasterer
2
+ class MethodCall
3
+ attr_reader :element
4
+ attr_reader :receiver
5
+ attr_reader :method_name
6
+ attr_reader :arguments
7
+ attr_reader :block_body
8
+ attr_reader :block_argument_names
9
+
10
+ alias_method :name, :method_name
11
+
12
+ def initialize(element)
13
+ @element = element
14
+ set_call_element
15
+ set_receiver
16
+ set_method_name
17
+ set_arguments
18
+ set_block_presence
19
+ set_block_body
20
+ set_block_argument_names
21
+ end
22
+
23
+ def has_block?
24
+ @block_present || false
25
+ end
26
+
27
+ def receiver_element
28
+ call_element[1]
29
+ end
30
+
31
+ def arguments_element
32
+ call_element[3..-1] || []
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :call_element
38
+ # TODO: explanation
39
+ def set_call_element
40
+ @call_element = case element.sexp_type
41
+ when :call
42
+ @element
43
+ when :iter
44
+ @element[1]
45
+ else
46
+ fail '!!!!!!!'
47
+ end
48
+ end
49
+
50
+ def set_receiver
51
+ @receiver = ReceiverFactory.new(receiver_element)
52
+ end
53
+
54
+ def set_method_name
55
+ @method_name = call_element[2]
56
+ end
57
+
58
+ def set_arguments
59
+ @arguments = arguments_element.map { |argument| Argument.new(argument) }
60
+ end
61
+
62
+ def set_block_presence
63
+ if element.sexp_type == :iter
64
+ @block_present = true
65
+ end
66
+ end
67
+
68
+ def set_block_body
69
+ if has_block?
70
+ @block_body = element[3]
71
+ end
72
+ end
73
+
74
+ # TODO: write specs for lambdas and procs
75
+ def set_block_argument_names
76
+ @block_argument_names = if has_block? and element[2].is_a?(Sexp) # hack for lambdas
77
+ element[2].drop(1).map { |argument| argument }
78
+ end || []
79
+ end
80
+
81
+ def token
82
+ element[0]
83
+ end
84
+ end
85
+
86
+ # For now, used for determening if
87
+ # the receiver is a reference or not.
88
+ class ReceiverFactory
89
+ attr_reader :name
90
+ attr_reader :method
91
+
92
+ def self.new(receiver_info)
93
+ return if receiver_info.nil?
94
+ token = receiver_info.first
95
+
96
+ case token
97
+ when :lvar
98
+ return VariableReference.new(receiver_info)
99
+ when :method_add_arg, :method_add_block
100
+ case receiver_info[1][0]
101
+ when :call, :fcall
102
+ return MethodCall.new(receiver_info[1])
103
+ else
104
+ # binding.pry watch out for :method_add_arg
105
+ # raise 'nije ni metoda'
106
+ end
107
+ when :call, :iter
108
+ return MethodCall.new(receiver_info)
109
+ end
110
+ end
111
+ end
112
+
113
+ class VariableReference
114
+ attr_reader :name
115
+
116
+ def initialize(reference_info)
117
+ @reference_info = reference_info
118
+ @name = reference_info[1]
119
+ end
120
+ end
121
+
122
+ class Argument
123
+
124
+ attr_reader :element
125
+
126
+ def initialize(element)
127
+ @element = element
128
+ end
129
+
130
+ def type
131
+ @type ||= @element[0]
132
+ end
133
+
134
+ def value
135
+ @value ||= @element[1]
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,51 @@
1
+ module Fasterer
2
+ class MethodDefinition
3
+ attr_reader :element # for testing purposes
4
+ attr_reader :method_name
5
+ attr_reader :block_argument_name
6
+ attr_reader :body
7
+
8
+ def initialize(element)
9
+ @element = element # Ripper element
10
+ set_method_name
11
+ set_body
12
+
13
+ if arguments_element.any?
14
+ set_argument_names
15
+ set_block_argument_name
16
+ end
17
+ end
18
+
19
+ def has_block?
20
+ !!@block_argument_name
21
+ end
22
+
23
+ private
24
+
25
+ def arguments_element
26
+ element[2].drop(1)
27
+ end
28
+
29
+ def set_method_name
30
+ @method_name = @element[1]
31
+ end
32
+
33
+ def set_argument_names
34
+ # TODO
35
+ end
36
+
37
+ def set_body
38
+ @body = @element[3..-1]
39
+ end
40
+
41
+ def set_block_argument_name
42
+ if last_argument.to_s.start_with?('&')
43
+ @block_argument_name = last_argument.to_s[1..-1].to_sym
44
+ end
45
+ end
46
+
47
+ def last_argument
48
+ arguments_element.last
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,69 @@
1
+ module Fasterer
2
+ class Offense
3
+ attr_reader :offense_name, :line_number
4
+
5
+ alias_method :name, :offense_name
6
+ alias_method :line, :line_number
7
+
8
+ def initialize(offense_name, line_number)
9
+ @offense_name = offense_name
10
+ @line_number = line_number
11
+ explanation # Set explanation right away.
12
+ end
13
+
14
+ def explanation
15
+ @explanation ||= EXPLANATIONS.fetch(offense_name)
16
+ end
17
+
18
+ EXPLANATIONS = {
19
+ parallel_assignment:
20
+ 'Parallel assignment is slower than sequential assignment',
21
+
22
+ rescue_vs_respond_to:
23
+ 'Don\'t rescue NoMethodError, rather check with respond_to?',
24
+
25
+ module_eval:
26
+ 'Using module_eval is slower than define_method',
27
+
28
+ shuffle_first_vs_sample:
29
+ 'Array#shuffle.first is slower than Array#sample',
30
+
31
+ for_loop_vs_each:
32
+ 'For loop is slower than using each',
33
+
34
+ each_with_index_vs_while:
35
+ 'Using each_with_index is slower than while loop',
36
+
37
+ map_flatten_vs_flat_map:
38
+ 'Array#map.flatten(1) is slower than Array#flat_map',
39
+
40
+ reverse_each_vs_reverse_each:
41
+ 'Array#reverese.each is slower than Array#reverse_each',
42
+
43
+ select_first_vs_detect:
44
+ 'Array#select.first is slower than Array#detect',
45
+
46
+ sort_vs_sort_by:
47
+ 'Enumerable#sort is slower than Enumerable#sort_by',
48
+
49
+ fetch_with_argument_vs_block:
50
+ 'Hash#fetch with second argument is slower than Hash#fetch with block',
51
+
52
+ keys_each_vs_each_key:
53
+ 'Hash#keys.each is slower than Hash#each_key',
54
+
55
+ hash_merge_bang_vs_hash_brackets:
56
+ 'Hash#merge! with one argument is slower than Hash#[]',
57
+
58
+ block_vs_symbol_to_proc:
59
+ 'Calling argumentless methods within blocks is slower than using symbol to proc',
60
+
61
+ proc_call_vs_yield:
62
+ 'Calling blocks with call is slower than yielding',
63
+
64
+ gsub_vs_tr:
65
+ 'Use tr instead of gsub when grepping plain strings'
66
+ }
67
+
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ require 'forwardable'
2
+
3
+ module Fasterer
4
+ class OffenseCollector
5
+ extend Forwardable
6
+
7
+ def initialize
8
+ @offenses = []
9
+ end
10
+
11
+ def [](offense_name)
12
+ @offenses.select { |offense| offense.name == offense_name }
13
+ end
14
+
15
+ def_delegators :@offenses, :push, :any?, :each, :group_by
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ module Fasterer
2
+ class ParseError < StandardError
3
+ attr_reader :file_path
4
+
5
+ def initialize(file_path)
6
+ @file_path = file_path
7
+ super("Unable to read #{file_path}")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'ruby_parser'
2
+
3
+ module Fasterer
4
+ class Parser
5
+ PARSER_CLASS = RubyParser
6
+
7
+ def self.parse(ruby_code)
8
+ PARSER_CLASS.new.parse(ruby_code)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,36 @@
1
+ module Fasterer
2
+ class RescueCall
3
+ attr_reader :element
4
+ attr_reader :rescue_classes
5
+
6
+ def initialize(element)
7
+ @element = element
8
+ @rescue_classes = []
9
+ set_rescue_classes
10
+ end
11
+
12
+ private
13
+
14
+ def set_rescue_classes
15
+ return if element[1].sexp_type != :array
16
+
17
+ @rescue_classes = element[1].drop(1).map do |rescue_reference|
18
+ if rescue_reference.sexp_type == :const
19
+ rescue_reference[1]
20
+ end
21
+ end.compact
22
+ end
23
+
24
+ def set_multiple_rescue_classes
25
+ @rescue_classes = element[1].drop(1).map do |rescue_reference|
26
+ rescue_reference.flatten[2]
27
+ end
28
+ end
29
+
30
+ def set_single_rescue_class
31
+ if element[1][0][0] == :var_ref
32
+ @rescue_classes = Array(element[1][0][1][1])
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,134 @@
1
+ require 'fasterer/method_call'
2
+ require 'fasterer/offense'
3
+ require 'fasterer/scanners/offensive'
4
+
5
+ module Fasterer
6
+ class MethodCallScanner
7
+ include Fasterer::Offensive
8
+
9
+ attr_reader :element
10
+
11
+ def initialize(element)
12
+ @element = element
13
+ check_offense
14
+ end
15
+
16
+ def method_call
17
+ @method_call ||= MethodCall.new(element)
18
+ end
19
+
20
+ private
21
+
22
+ def check_offense
23
+ case method_call.method_name
24
+ when :module_eval
25
+ check_module_eval_offense
26
+ when :gsub
27
+ check_gsub_offense
28
+ when :sort
29
+ check_sort_offense
30
+ when :each_with_index
31
+ check_each_with_index_offense
32
+ when :first
33
+ check_first_offense
34
+ when :each
35
+ check_each_offense
36
+ when :flatten
37
+ check_flatten_offense
38
+ when :fetch
39
+ check_fetch_offense
40
+ when :merge!
41
+ check_merge_bang_offense
42
+ end
43
+ end
44
+
45
+ def check_module_eval_offense
46
+ add_offense(:module_eval)
47
+ end
48
+
49
+ def check_gsub_offense
50
+ unless method_call.arguments.first.value.is_a? Regexp
51
+ add_offense(:gsub_vs_tr)
52
+ end
53
+ end
54
+
55
+ def check_sort_offense
56
+ if method_call.arguments.count > 0 || method_call.has_block?
57
+ add_offense(:sort_vs_sort_by)
58
+ end
59
+ end
60
+
61
+ def check_each_with_index_offense
62
+ add_offense(:each_with_index_vs_while)
63
+ end
64
+
65
+ def check_first_offense
66
+ return method_call unless method_call.receiver.is_a?(MethodCall)
67
+
68
+ case method_call.receiver.name
69
+ when :shuffle
70
+ add_offense(:shuffle_first_vs_sample)
71
+ when :select
72
+ add_offense(:select_first_vs_detect)
73
+ end
74
+ end
75
+
76
+ def check_each_offense
77
+ return method_call unless method_call.receiver.is_a?(MethodCall)
78
+
79
+ case method_call.receiver.name
80
+ when :reverse
81
+ add_offense(:reverse_each_vs_reverse_each)
82
+ when :keys
83
+ add_offense(:keys_each_vs_each_key)
84
+ end
85
+
86
+ check_symbol_to_proc
87
+ end
88
+
89
+ def check_flatten_offense
90
+ return method_call unless method_call.receiver.is_a?(MethodCall)
91
+
92
+ if method_call.receiver.name == :map &&
93
+ method_call.arguments.count == 1 &&
94
+ method_call.arguments.first.value == 1
95
+
96
+ add_offense(:map_flatten_vs_flat_map)
97
+ end
98
+ end
99
+
100
+ def check_fetch_offense
101
+ if method_call.arguments.count == 2 && !method_call.has_block?
102
+ add_offense(:fetch_with_argument_vs_block)
103
+ end
104
+ end
105
+
106
+ # Need to refactor, fukken complicated conditions.
107
+ def check_symbol_to_proc
108
+ return unless method_call.block_argument_names.count == 1
109
+ return if method_call.block_body.nil?
110
+ return unless method_call.block_body.sexp_type == :call
111
+ return if method_call.arguments.count > 0
112
+
113
+ body_method_call = MethodCall.new(method_call.block_body)
114
+
115
+ return unless body_method_call.arguments.count.zero?
116
+ return if body_method_call.has_block?
117
+ return unless body_method_call.receiver.name == method_call.block_argument_names.first
118
+
119
+ add_offense(:block_vs_symbol_to_proc)
120
+ end
121
+
122
+ def check_merge_bang_offense
123
+ return unless method_call.arguments.count == 1
124
+
125
+ first_argument = method_call.arguments.first
126
+ return unless first_argument.type == :hash
127
+
128
+ if first_argument.element.drop(1).count == 2 # each key and value is an item by itself.
129
+ add_offense(:hash_merge_bang_vs_hash_brackets)
130
+ end
131
+ end
132
+
133
+ end
134
+ end