fastererer 0.12.0 → 1.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -2
  3. data/README.md +97 -8
  4. data/config/locales/en.yml +60 -0
  5. data/lib/fastererer/analyzer.rb +28 -53
  6. data/lib/fastererer/cli.rb +21 -4
  7. data/lib/fastererer/explanation.rb +44 -0
  8. data/lib/fastererer/file_traverser.rb +43 -112
  9. data/lib/fastererer/finding.rb +18 -0
  10. data/lib/fastererer/formatters/base.rb +32 -0
  11. data/lib/fastererer/formatters/json_formatter.rb +48 -0
  12. data/lib/fastererer/formatters/rdjsonl_formatter.rb +34 -0
  13. data/lib/fastererer/formatters/text_formatter.rb +63 -0
  14. data/lib/fastererer/formatters/text_statistics.rb +45 -0
  15. data/lib/fastererer/formatters.rb +24 -0
  16. data/lib/fastererer/method_call.rb +139 -75
  17. data/lib/fastererer/method_definition.rb +61 -48
  18. data/lib/fastererer/offense.rb +5 -63
  19. data/lib/fastererer/offense_collector.rb +1 -1
  20. data/lib/fastererer/painter.rb +2 -1
  21. data/lib/fastererer/parser.rb +8 -4
  22. data/lib/fastererer/receiver.rb +14 -0
  23. data/lib/fastererer/report.rb +10 -0
  24. data/lib/fastererer/rescue_call.rb +10 -12
  25. data/lib/fastererer/rule_catalog.rb +83 -0
  26. data/lib/fastererer/rule_name.rb +17 -0
  27. data/lib/fastererer/scanners/for_loop_scanner.rb +23 -0
  28. data/lib/fastererer/scanners/method_call_scanner.rb +13 -17
  29. data/lib/fastererer/scanners/method_definition_scanner.rb +57 -29
  30. data/lib/fastererer/scanners/offensive.rb +2 -6
  31. data/lib/fastererer/scanners/rescue_call_scanner.rb +1 -1
  32. data/lib/fastererer/scanners/symbol_to_proc_check.rb +21 -8
  33. data/lib/fastererer/version.rb +1 -1
  34. metadata +19 -11
  35. data/.fastererer.yml +0 -27
  36. data/.ruby-version +0 -1
  37. data/CODE_OF_CONDUCT.md +0 -18
  38. data/CONTRIBUTING.md +0 -63
  39. data/Rakefile +0 -12
  40. data/SECURITY.md +0 -20
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Fastererer
6
+ class UnknownRuleError < StandardError; end
7
+
8
+ # Loads, validates, memoizes and looks up the rule catalog from the i18n locale.
9
+ module RuleCatalog
10
+ LOCALE_PATH = File.expand_path('../../config/locales/en.yml', __dir__)
11
+ KEY_SHAPE = /\A[a-z0-9_]+\z/
12
+ private_constant :KEY_SHAPE
13
+
14
+ class << self
15
+ def all
16
+ @all ||= load
17
+ end
18
+
19
+ def fetch(offense_name)
20
+ all.fetch(offense_name.to_s) do
21
+ raise UnknownRuleError, "Unknown rule: #{offense_name.inspect}"
22
+ end
23
+ end
24
+
25
+ # Guard-only: raises for an unknown rule, returns nil otherwise.
26
+ def validate!(offense_name)
27
+ fetch(offense_name)
28
+ nil
29
+ end
30
+
31
+ private
32
+
33
+ def load
34
+ rows = read_rows
35
+ rows.each { |key, row| validate_row!(key, row) }
36
+ rows.transform_values { |row| row.transform_values(&:freeze).freeze }.freeze
37
+ end
38
+
39
+ def read_rows
40
+ loaded = YAML.safe_load_file(LOCALE_PATH)
41
+ rows = loaded.is_a?(Hash) ? loaded.dig('en', 'fastererer', 'rules') : nil
42
+ return rows if rows.is_a?(Hash)
43
+
44
+ raise "Fastererer locale at #{LOCALE_PATH} is missing the 'en.fastererer.rules' section"
45
+ rescue Errno::ENOENT
46
+ raise "Fastererer locale file not found at #{LOCALE_PATH}"
47
+ rescue Psych::SyntaxError => e
48
+ raise "Fastererer locale at #{LOCALE_PATH} is not valid YAML: #{e.message}"
49
+ end
50
+
51
+ def validate_row!(key, row)
52
+ raise "Fastererer rule #{key} is malformed: #{row.inspect}" unless row.is_a?(Hash)
53
+ unless key.is_a?(String) && key.match?(KEY_SHAPE)
54
+ raise "Fastererer rule #{key.inspect} is not a snake_case key"
55
+ end
56
+
57
+ validate_url!(key, row['url'])
58
+ validate_description!(key, row['description'])
59
+ end
60
+
61
+ def validate_url!(key, url)
62
+ unless url.is_a?(String) && url.start_with?('https://')
63
+ raise "Fastererer rule #{key} has a non-https url: #{url.inspect}"
64
+ end
65
+
66
+ return if url.match?(/\A[[:print:]]+\z/)
67
+
68
+ raise "Fastererer rule #{key} has a non-printable url: #{url.inspect}"
69
+ end
70
+
71
+ def validate_description!(key, text)
72
+ return if text.is_a?(String) && text.match?(/\A[[:print:][:space:]]+\z/)
73
+
74
+ raise "Fastererer rule #{key} has a non-printable description: #{text.inspect}"
75
+ end
76
+
77
+ # Test-only hook: clears the memoized catalog so the YAML can be re-read.
78
+ def reset!
79
+ @all = nil
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fastererer
4
+ # Derives a rubocop-style "Performance/PascalCase" display name from a snake_case rule key.
5
+ module RuleName
6
+ DEPARTMENT = 'Performance'
7
+
8
+ def self.from(offense_name)
9
+ "#{DEPARTMENT}/#{pascal_case(offense_name)}"
10
+ end
11
+
12
+ def self.pascal_case(name)
13
+ name.to_s.split('_').filter_map { |part| part.capitalize unless part.empty? }.join
14
+ end
15
+ private_class_method :pascal_case
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fastererer/offense'
4
+ require 'fastererer/scanners/offensive'
5
+
6
+ module Fastererer
7
+ class ForLoopScanner
8
+ include Fastererer::Offensive
9
+
10
+ attr_reader :element
11
+
12
+ def initialize(element)
13
+ @element = element
14
+ check_offense
15
+ end
16
+
17
+ private
18
+
19
+ def check_offense
20
+ add_offense(:for_loop_vs_each) # for is always slower than each, so no guard
21
+ end
22
+ end
23
+ end
@@ -20,6 +20,7 @@ module Fastererer
20
20
  flatten: :check_flatten_offense,
21
21
  fetch: :check_fetch_offense,
22
22
  merge!: :check_merge_bang_offense,
23
+ update: :check_update_offense,
23
24
  last: :check_last_offense,
24
25
  include?: :check_range_include_offense
25
26
  }.freeze
@@ -33,7 +34,7 @@ module Fastererer
33
34
  end
34
35
 
35
36
  def method_call
36
- @method_call ||= MethodCall.new(element)
37
+ @method_call ||= MethodCall.build(element)
37
38
  end
38
39
 
39
40
  private
@@ -74,9 +75,7 @@ module Fastererer
74
75
  end
75
76
 
76
77
  def check_first_offense
77
- return unless method_call.receiver.is_a?(MethodCall)
78
-
79
- case method_call.receiver.name
78
+ case method_call.receiver&.call_name
80
79
  when :shuffle
81
80
  add_offense(:shuffle_first_vs_sample)
82
81
  when :select
@@ -88,9 +87,7 @@ module Fastererer
88
87
  end
89
88
 
90
89
  def check_each_offense
91
- return unless method_call.receiver.is_a?(MethodCall)
92
-
93
- case method_call.receiver.name
90
+ case method_call.receiver&.call_name
94
91
  when :reverse
95
92
  add_offense(:reverse_each_vs_reverse_each)
96
93
  when :keys
@@ -99,8 +96,7 @@ module Fastererer
99
96
  end
100
97
 
101
98
  def check_flatten_offense
102
- return unless method_call.receiver.is_a?(MethodCall)
103
- return unless method_call.receiver.name == :map && method_call.arguments.one?
99
+ return unless method_call.receiver&.call_name == :map && method_call.arguments.one?
104
100
 
105
101
  add_offense(:map_flatten_vs_flat_map) if method_call.arguments.first.value == 1
106
102
  end
@@ -115,25 +111,25 @@ module Fastererer
115
111
  return unless method_call.arguments.one?
116
112
 
117
113
  first_argument = method_call.arguments.first
118
- return unless first_argument.type == :hash
119
- # each key and value is an item by itself.
120
- return unless first_argument.element.drop(1).count == 2
114
+ return unless first_argument.type == :hash && first_argument.element.elements.one?
121
115
 
122
116
  add_offense(:hash_merge_bang_vs_hash_brackets)
123
117
  end
124
118
 
119
+ # Hash#update is a merge! alias but heavily overloaded, so require a provably-Hash receiver
120
+ def check_update_offense
121
+ check_merge_bang_offense if method_call.receiver&.hash?
122
+ end
123
+
125
124
  def check_last_offense
126
- return unless method_call.receiver.is_a?(MethodCall)
127
- return unless method_call.receiver.name == :select
125
+ return unless method_call.receiver&.call_name == :select
128
126
  return if method_call.arguments.any?
129
127
 
130
128
  add_offense(:select_last_vs_reverse_detect)
131
129
  end
132
130
 
133
131
  def check_range_include_offense
134
- return unless method_call.receiver.is_a?(Primitive) && method_call.receiver.range?
135
-
136
- add_offense(:include_vs_cover_on_range)
132
+ add_offense(:include_vs_cover_on_range) if method_call.receiver&.range?
137
133
  end
138
134
  end
139
135
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'prism'
3
4
  require 'fastererer/method_definition'
4
5
  require 'fastererer/method_call'
5
6
  require 'fastererer/offense'
@@ -27,33 +28,15 @@ module Fastererer
27
28
  end
28
29
 
29
30
  def scan_block_call_offense
30
- traverse_tree(method_definition.body) do |element|
31
- next unless element.sexp_type == :call
32
-
33
- method_call = MethodCall.new(element)
34
-
35
- if method_call.receiver.is_a?(Fastererer::VariableReference) &&
36
- method_call.receiver.name == method_definition.block_argument_name &&
37
- method_call.method_name == :call
38
-
39
- add_offense(:proc_call_vs_yield) && return
40
- end
41
- end
31
+ visitor = ProcCallVisitor.new(method_definition.block_argument_name)
32
+ method_definition.body.each { |node| node.accept(visitor) }
33
+ add_offense(:proc_call_vs_yield) if visitor.proc_call_found?
42
34
  end
43
35
 
44
36
  def method_definition
45
37
  @method_definition ||= MethodDefinition.new(element)
46
38
  end
47
39
 
48
- def traverse_tree(sexp_tree, &block)
49
- sexp_tree.each do |element|
50
- next unless element.is_a?(Array)
51
-
52
- yield element
53
- traverse_tree(element, &block)
54
- end
55
- end
56
-
57
40
  def scan_getter_and_setter_offense
58
41
  method_definition.setter? ? scan_setter_offense : scan_getter_offense
59
42
  end
@@ -69,12 +52,12 @@ module Fastererer
69
52
  end
70
53
 
71
54
  def trivial_setter?(first_argument)
72
- body_first = method_definition.body.first
73
- expected_ivar = "@#{method_definition.name.to_s.delete_suffix('=')}"
55
+ body_node = method_definition.body.first
56
+ return false unless body_node.is_a?(Prism::InstanceVariableWriteNode)
57
+ return false unless body_node.value.is_a?(Prism::LocalVariableReadNode)
74
58
 
75
- body_first.sexp_type == :iasgn &&
76
- body_first[1].to_s == expected_ivar &&
77
- body_first[2][1] == first_argument.name
59
+ expected_ivar = :"@#{method_definition.name.to_s.delete_suffix('=')}"
60
+ body_node.name == expected_ivar && body_node.value.name == first_argument.name
78
61
  end
79
62
 
80
63
  def scan_getter_offense
@@ -85,10 +68,55 @@ module Fastererer
85
68
  end
86
69
 
87
70
  def trivial_getter?
88
- body_first = method_definition.body.first
71
+ body_node = method_definition.body.first
72
+ expected_ivar = :"@#{method_definition.name}"
89
73
 
90
- body_first.sexp_type == :ivar &&
91
- body_first[1].to_s == "@#{method_definition.name}"
74
+ body_node.is_a?(Prism::InstanceVariableReadNode) && body_node.name == expected_ivar
92
75
  end
93
76
  end
77
+
78
+ # Finds `block_name.call` in a method body, without descending into nested def/class/module scopes
79
+ # where the block parameter is no longer in scope
80
+ class ProcCallVisitor < Prism::Visitor
81
+ def initialize(block_name)
82
+ super()
83
+ @block_name = block_name
84
+ @proc_call_found = false
85
+ end
86
+
87
+ def proc_call_found?
88
+ @proc_call_found
89
+ end
90
+
91
+ def visit_call_node(node)
92
+ @proc_call_found = true if proc_call?(node)
93
+ super
94
+ end
95
+
96
+ # No super on purpose: these open a new scope where the block param is unbound, so we stop
97
+ # descending rather than attribute their inner `name.call`s to the enclosing method.
98
+ def visit_def_node(_node)
99
+ end
100
+
101
+ def visit_class_node(_node)
102
+ end
103
+
104
+ def visit_module_node(_node)
105
+ end
106
+
107
+ def visit_singleton_class_node(_node)
108
+ end
109
+
110
+ private
111
+
112
+ def proc_call?(node)
113
+ return false if @block_name.nil? # an anonymous block param (&) is never invoked by name
114
+
115
+ node.receiver.is_a?(Prism::LocalVariableReadNode) &&
116
+ node.receiver.name == @block_name &&
117
+ node.name == :call
118
+ end
119
+ end
120
+
121
+ private_constant :ProcCallVisitor
94
122
  end
@@ -14,12 +14,8 @@ module Fastererer
14
14
 
15
15
  private
16
16
 
17
- def add_offense(offense_name, element_line_number = element.line)
18
- self.offense = Fastererer::Offense.new(offense_name, element_line_number)
19
- end
20
-
21
- def check_offense
22
- raise NotImplementedError
17
+ def add_offense(offense_name)
18
+ self.offense = Fastererer::Offense.new(offense_name, element.location.start_line)
23
19
  end
24
20
  end
25
21
  end
@@ -18,7 +18,7 @@ module Fastererer
18
18
  private
19
19
 
20
20
  def check_offense
21
- return unless rescue_call.rescue_classes.include? :NoMethodError
21
+ return unless rescue_call.rescue_classes.include?(:NoMethodError)
22
22
 
23
23
  add_offense(:rescue_vs_respond_to)
24
24
  end
@@ -1,35 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'prism'
3
4
  require 'fastererer/method_call'
4
5
 
5
6
  module Fastererer
6
- # Includer must respond to #method_call (returning a MethodCall) and #add_offense(symbol).
7
+ # Includer must respond to #method_call (returning a MethodCall) and #add_offense(symbol)
8
+ # (the latter via the Offensive mixin).
7
9
  module SymbolToProcCheck
8
10
  private
9
11
 
10
12
  def check_symbol_to_proc
11
13
  return unless symbol_to_proc_candidate?
12
14
 
13
- body_call = MethodCall.new(method_call.block_body)
15
+ body_call = MethodCall.build(method_call.block_body.first)
14
16
  return unless symbol_to_proc_body?(body_call)
15
17
 
16
18
  add_offense(:block_vs_symbol_to_proc)
17
19
  end
18
20
 
19
21
  def symbol_to_proc_candidate?
20
- method_call.block_argument_names.one? &&
21
- !method_call.block_body.nil? &&
22
- method_call.block_body.sexp_type == :call &&
22
+ single_block_argument_name &&
23
+ single_call_body? &&
23
24
  method_call.arguments.none? &&
24
25
  !method_call.lambda_literal?
25
26
  end
26
27
 
28
+ # A destructured param has no name, so |(a, b), c| must not read as a single-name block
29
+ def single_block_argument_name
30
+ names = method_call.block_argument_names
31
+ names.first if names.size == 1
32
+ end
33
+
34
+ # A safe-nav body (foo&.bar) is excluded: arr.map(&:bar) raises on a nil element, so the rewrite
35
+ # would not preserve behavior
36
+ def single_call_body?
37
+ body = method_call.block_body
38
+ body&.size == 1 && body.first.is_a?(Prism::CallNode) && !body.first.safe_navigation?
39
+ end
40
+
41
+ # A primitive receiver's name is nil, which never matches the named param the gate requires
27
42
  def symbol_to_proc_body?(body_call)
28
43
  body_call.arguments.none? &&
29
44
  !body_call.block? &&
30
- !body_call.receiver.nil? &&
31
- !body_call.receiver.is_a?(Fastererer::Primitive) &&
32
- body_call.receiver.name == method_call.block_argument_names.first
45
+ body_call.receiver&.name == single_block_argument_name
33
46
  end
34
47
  end
35
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fastererer
4
- VERSION = '0.12.0'
4
+ VERSION = '1.1.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastererer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Menefee
@@ -11,19 +11,19 @@ cert_chain: []
11
11
  date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ruby_parser
14
+ name: prism
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.22.0
19
+ version: 1.3.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 3.22.0
26
+ version: 1.3.0
27
27
  description: Use Fastererer to check various places in your code that could be faster.
28
28
  A fork of fasterer (https://github.com/DamirSvrtan/fasterer) with Ruby 4.0 support
29
29
  and native Prism parsing.
@@ -35,28 +35,36 @@ executables:
35
35
  extensions: []
36
36
  extra_rdoc_files: []
37
37
  files:
38
- - ".fastererer.yml"
39
- - ".ruby-version"
40
38
  - CHANGELOG.md
41
- - CODE_OF_CONDUCT.md
42
- - CONTRIBUTING.md
43
39
  - LICENSE.txt
44
40
  - README.md
45
- - Rakefile
46
- - SECURITY.md
41
+ - config/locales/en.yml
47
42
  - exe/fastererer
48
43
  - lib/fastererer.rb
49
44
  - lib/fastererer/analyzer.rb
50
45
  - lib/fastererer/cli.rb
51
46
  - lib/fastererer/config.rb
47
+ - lib/fastererer/explanation.rb
52
48
  - lib/fastererer/file_traverser.rb
49
+ - lib/fastererer/finding.rb
50
+ - lib/fastererer/formatters.rb
51
+ - lib/fastererer/formatters/base.rb
52
+ - lib/fastererer/formatters/json_formatter.rb
53
+ - lib/fastererer/formatters/rdjsonl_formatter.rb
54
+ - lib/fastererer/formatters/text_formatter.rb
55
+ - lib/fastererer/formatters/text_statistics.rb
53
56
  - lib/fastererer/method_call.rb
54
57
  - lib/fastererer/method_definition.rb
55
58
  - lib/fastererer/offense.rb
56
59
  - lib/fastererer/offense_collector.rb
57
60
  - lib/fastererer/painter.rb
58
61
  - lib/fastererer/parser.rb
62
+ - lib/fastererer/receiver.rb
63
+ - lib/fastererer/report.rb
59
64
  - lib/fastererer/rescue_call.rb
65
+ - lib/fastererer/rule_catalog.rb
66
+ - lib/fastererer/rule_name.rb
67
+ - lib/fastererer/scanners/for_loop_scanner.rb
60
68
  - lib/fastererer/scanners/method_call_scanner.rb
61
69
  - lib/fastererer/scanners/method_definition_scanner.rb
62
70
  - lib/fastererer/scanners/offensive.rb
@@ -86,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
94
  - !ruby/object:Gem::Version
87
95
  version: '0'
88
96
  requirements: []
89
- rubygems_version: 4.0.10
97
+ rubygems_version: 4.0.16
90
98
  specification_version: 4
91
99
  summary: Run Ruby more than fast. Fastererer
92
100
  test_files: []
data/.fastererer.yml DELETED
@@ -1,27 +0,0 @@
1
- speedups:
2
- rescue_vs_respond_to: true
3
- module_eval: true
4
- shuffle_first_vs_sample: true
5
- for_loop_vs_each: true
6
- each_with_index_vs_while: false
7
- map_flatten_vs_flat_map: true
8
- reverse_each_vs_reverse_each: true
9
- select_first_vs_detect: true
10
- select_last_vs_reverse_detect: true
11
- sort_vs_sort_by: true
12
- fetch_with_argument_vs_block: true
13
- keys_each_vs_each_key: true
14
- hash_merge_bang_vs_hash_brackets: true
15
- block_vs_symbol_to_proc: true
16
- proc_call_vs_yield: true
17
- gsub_vs_tr: true
18
- getter_vs_attr_reader: true
19
- setter_vs_attr_writer: true
20
-
21
- exclude_paths:
22
- - 'spec/support/analyzer/*.rb'
23
- - 'spec/support/binary_call/*.rb'
24
- - 'spec/support/method_call/*.rb'
25
- - 'spec/support/method_definition/*.rb'
26
- - 'spec/support/rescue_call/*.rb'
27
- - 'vendor/**/*.rb'
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 4.0.4
data/CODE_OF_CONDUCT.md DELETED
@@ -1,18 +0,0 @@
1
- # Code of Conduct
2
-
3
- Fastererer follows [The Ruby Community Conduct Guideline][ruby-conduct] in all "collaborative
4
- space", which is defined as community communications channels (such as mailing lists, submitted
5
- patches, commit comments, etc.):
6
-
7
- * Participants will be tolerant of opposing views.
8
- * Participants must ensure that their language and actions are free of personal attacks and
9
- disparaging personal remarks.
10
- * When interpreting the words and actions of others, participants should always assume good
11
- intentions.
12
- * Behaviour which can be reasonably considered harassment will not be tolerated.
13
-
14
- If you have any concerns about behaviour within this project, please contact us at
15
- [matt.menefee@extractablemedia.com][email].
16
-
17
- [email]: mailto:matt.menefee@extractablemedia.com
18
- [ruby-conduct]: https://www.ruby-lang.org/en/conduct
data/CONTRIBUTING.md DELETED
@@ -1,63 +0,0 @@
1
- # Contributing to fastererer
2
-
3
- Thanks for your interest in improving `fastererer`. This document explains how to report issues
4
- and submit code.
5
-
6
- ## Reporting Issues
7
-
8
- Before opening a new issue:
9
-
10
- 1. Check the [existing issues][issues] to avoid duplicates.
11
- 2. Confirm the bug reproduces on the latest release.
12
-
13
- When opening an issue, include:
14
-
15
- - The output of `fastererer --version`
16
- - Your Ruby version (`ruby --version`)
17
- - A minimal reproduction — the smallest Ruby snippet that triggers the behavior
18
- - The expected behavior and the actual behavior
19
-
20
- ## Pull Requests
21
-
22
- 1. Fork the repository and create a topic branch off `main`.
23
- 2. Run `bin/setup` to install dependencies (or `bundle install` directly).
24
- 3. Add tests that cover the change. Bug fixes should include a regression spec.
25
- 4. Run the test suite with `bin/rspec` and make sure it passes.
26
- 5. Update [CHANGELOG.md][changelog] under an unreleased heading describing the change.
27
- 6. Submit the PR with a clear description of what changed and why.
28
-
29
- For non-trivial changes, consider opening an issue first to discuss the approach.
30
-
31
- ## Local Development
32
-
33
- After cloning:
34
-
35
- ```bash
36
- bin/setup # installs dependencies
37
- bin/console # opens an IRB session with the gem loaded
38
- bin/rake # runs the test suite and rubocop (the default task)
39
- bin/rspec # runs just the test suite
40
- bin/rubocop # runs just the linter
41
- ```
42
-
43
- To try the executable against a Ruby file:
44
-
45
- ```bash
46
- bundle exec exe/fastererer path/to/file.rb
47
- ```
48
-
49
- ## Code Style
50
-
51
- - Follow the [Ruby Style Guide][ruby-style-guide].
52
- - Run `bin/rubocop` before submitting — CI runs the default rake task on every PR.
53
- - New Ruby files should start with `# frozen_string_literal: true`.
54
- - Tests use RSpec — see existing specs in `spec/` for examples and conventions.
55
-
56
- ## Code of Conduct
57
-
58
- By participating in this project, you agree to abide by its [Code of Conduct][code-of-conduct].
59
-
60
- [changelog]: CHANGELOG.md
61
- [code-of-conduct]: CODE_OF_CONDUCT.md
62
- [issues]: https://github.com/ExtractableMedia/fastererer/issues
63
- [ruby-style-guide]: https://rubystyle.guide
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
- require 'rubocop/rake_task'
6
-
7
- desc 'Run all RSpec tests'
8
- RSpec::Core::RakeTask.new(:spec)
9
-
10
- RuboCop::RakeTask.new
11
-
12
- task default: %i[spec rubocop]
data/SECURITY.md DELETED
@@ -1,20 +0,0 @@
1
- # Security Policy
2
-
3
- ## Supported Versions
4
-
5
- Only the latest released version of `fastererer` receives security updates. Older versions are not
6
- supported.
7
-
8
- ## Reporting a Vulnerability
9
-
10
- Please report security vulnerabilities **privately** rather than opening a public GitHub issue.
11
-
12
- The preferred channel is **GitHub Security Advisories**: [Report a vulnerability][advisory].
13
-
14
- If GitHub is unavailable, email [matt.menefee@extractablemedia.com][email].
15
-
16
- We will acknowledge receipt within a few business days and work with you on a coordinated disclosure
17
- timeline. Credit will be given in the release notes unless you prefer to remain anonymous.
18
-
19
- [advisory]: https://github.com/ExtractableMedia/fastererer/security/advisories/new
20
- [email]: mailto:matt.menefee@extractablemedia.com