immosquare-cleaner 1.2.3 → 1.3.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: 53d2dc2a160c411d3acbeb2dc03692343ef60b598468fe288bfca2ec85667e87
4
- data.tar.gz: 802af16e18bb055b0f6a6a77b2e9ec47d2a1dab3d7a624ee848cd4f813f27e48
3
+ metadata.gz: 7caffdb6ce093eae93ca5f22d1e25676d935bbd8f034805307d53bd20e69c201
4
+ data.tar.gz: 3fa0fc3dc8f247f866f89108a13752aee5761b9e0fcb1f62071bb2de3eb35add
5
5
  SHA512:
6
- metadata.gz: c75021c32ffb683d464ef3834eb0e40bec740645f7be9afc045942b99093b160ad6e9867798858d37b030e34227a2f1b31df72895e9e376cd704f82af4e522a4
7
- data.tar.gz: d4023816cebb368e5bff4bbc5ac7b824135532b2d738b74a0df0c530923a375bef4ee627a77f31d09ffd22d1e44bcc94f3e77d241963eb83ca142d8d039f98d8
6
+ metadata.gz: e94943fb8aedca8ff9d71bd0dcc4a9a4360392806b814b8e72dbed789d37b3bdfe0952c95bf7c19bc3fc26038920de831577d44894f97fa032a5f9e2f6c8bb7c
7
+ data.tar.gz: 1d27e92164cf050d08421c100af8d181a696ba8b3c0c25648d669ef269cff5b2b724fccf4fe8d40da5785949aa7fc99640dec8797e788640fcf37eda9cadf346
@@ -2,16 +2,19 @@ module ImmosquareCleaner
2
2
  class Configuration
3
3
 
4
4
  attr_accessor(:rubocop_options, :htmlbeautifier_options, :erblint_options, :exclude_files)
5
+ attr_reader :ruby_target_version
5
6
 
6
7
  def initialize
7
8
  @rubocop_options = nil
9
+ @ruby_target_version = nil
8
10
  @htmlbeautifier_options = nil
9
11
  @erblint_options = nil
10
12
  @exclude_files = []
11
13
  end
12
14
 
13
-
14
-
15
+ def ruby_target_version=(version)
16
+ @ruby_target_version = version.nil? ? nil : RubyTarget.normalize(version)
17
+ end
15
18
 
16
19
  end
17
20
  end
@@ -22,14 +22,14 @@ module ImmosquareCleaner
22
22
  ## rubocop-X.Y.Z.yml)
23
23
  ## --autocorrect : apply autocorrectable offenses in place
24
24
  ##============================================================##
25
- config_path = "#{ImmosquareCleaner.gem_root}/linters/erb-lint-#{RUBY_VERSION}.yml"
25
+ config_path = ImmosquareCleaner.linter_config_path("erb-lint", origin_path)
26
26
  htmlbeautifier_options = ImmosquareCleaner.configuration.htmlbeautifier_options || "--keep-blank-lines 4"
27
27
  erblint_options = ImmosquareCleaner.configuration.erblint_options || "--autocorrect"
28
28
  escaped_file = Shellwords.escape(file_path)
29
29
 
30
30
  cmds = [
31
31
  "bundle exec htmlbeautifier #{escaped_file} #{htmlbeautifier_options}",
32
- "bundle exec erb_lint --config #{config_path} #{escaped_file} #{erblint_options}"
32
+ "bundle exec erb_lint --config #{Shellwords.escape(config_path)} #{escaped_file} #{erblint_options}"
33
33
  ]
34
34
 
35
35
  launch_cmds(cmds)
@@ -70,9 +70,9 @@ module ImmosquareCleaner
70
70
  ## HTML-context linters off the host string literals.
71
71
  ##============================================================##
72
72
  config_basename = file_path.downcase.end_with?(*JS_ERB_EXTENSIONS) ? "js-erb-lint" : "erb-lint"
73
- erblint_config = "#{ImmosquareCleaner.gem_root}/linters/#{config_basename}-#{RUBY_VERSION}.yml"
73
+ erblint_config = ImmosquareCleaner.linter_config_path(config_basename, origin_path)
74
74
  erblint_options = ImmosquareCleaner.configuration.erblint_options || "--autocorrect"
75
- cmds << "bundle exec erb_lint --config #{erblint_config} #{escaped_temp} #{erblint_options}"
75
+ cmds << "bundle exec erb_lint --config #{Shellwords.escape(erblint_config)} #{escaped_temp} #{erblint_options}"
76
76
  else
77
77
  cmds << "bun #{ImmosquareCleaner.gem_root}/linters/normalize-comments.mjs #{escaped_temp}"
78
78
  end
@@ -47,9 +47,9 @@ module ImmosquareCleaner
47
47
  ## overhead and breaks custom cop loading
48
48
  ##============================================================##
49
49
  rubocop_options = ImmosquareCleaner.configuration.rubocop_options || "--autocorrect-all --no-parallel"
50
- config_path = "#{ImmosquareCleaner.gem_root}/linters/rubocop-#{RUBY_VERSION}.yml"
50
+ config_path = ImmosquareCleaner.linter_config_path("rubocop", origin_path)
51
51
 
52
- cmds = ["bundle exec rubocop -c #{config_path} #{Shellwords.escape(file_path)} #{rubocop_options}"]
52
+ cmds = ["bundle exec rubocop -c #{Shellwords.escape(config_path)} #{Shellwords.escape(file_path)} #{rubocop_options}"]
53
53
  launch_cmds(cmds)
54
54
  normalize_last_line
55
55
  end
@@ -0,0 +1,109 @@
1
+ module ImmosquareCleaner
2
+ class RubyTarget
3
+
4
+ MUTEX = Mutex.new
5
+ GEMSPEC_REQUIREMENT_REGEX = /required_ruby_version\s*=\s*(?:Gem::Requirement\.new\()?\s*(\[[^\]]*\]|"[^"]*"|'[^']*')/
6
+ RUBY_VERSION_FILE_REGEX = /\A(?:ruby-)?(\d+\.\d+)/
7
+ LOWER_BOUND_OPERATORS = [">=", ">", "~>", "="].freeze
8
+ PROJECT_MARKERS = ["Gemfile", ".git"].freeze
9
+
10
+ class << self
11
+
12
+ ##============================================================##
13
+ ## RuboCop targets Ruby releases rather than patch versions.
14
+ ## Explicit targets must be supported by the installed RuboCop.
15
+ ##============================================================##
16
+ def normalize(version)
17
+ value = version.to_s
18
+ raise(ArgumentError, "ruby_target_version must be a Ruby version such as 3.2 or 3.2.6") if !value.match?(/\A\d+\.\d+(?:\.\d+)?\z/)
19
+
20
+ target = release(value)
21
+ validate!(target)
22
+ target
23
+ end
24
+
25
+ def release(version)
26
+ Gem::Version.new(version).segments.first(2).join(".")
27
+ end
28
+
29
+ ##============================================================##
30
+ ## Target of the project that owns the file: the gemspec minimum
31
+ ## (a gem must keep working on the oldest Ruby it accepts), then
32
+ ## .ruby-version, then the running Ruby. The lookup stops at the
33
+ ## project root so a parent folder's .ruby-version never applies.
34
+ ## Autocorrect with a newer target can introduce syntax that the
35
+ ## project's Ruby rejects (`_1` rewritten to `it`, Ruby 3.4+).
36
+ ##============================================================##
37
+ def detect(path)
38
+ directory = File.dirname(File.expand_path(path))
39
+ MUTEX.synchronize do
40
+ @detected ||= {}
41
+ @detected[directory] ||= supported_target(declared_version(directory)) || release(RUBY_VERSION)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def declared_version(directory)
48
+ loop do
49
+ gemspecs = Dir.glob("*.gemspec", :base => directory).sort
50
+ version = gemspec_minimum(directory, gemspecs) || ruby_version_file(directory)
51
+ return release(version) if version
52
+ return nil if gemspecs.any? || PROJECT_MARKERS.any? {|marker| File.exist?(File.join(directory, marker)) }
53
+
54
+ parent = File.dirname(directory)
55
+ return nil if parent == directory
56
+
57
+ directory = parent
58
+ end
59
+ end
60
+
61
+ def gemspec_minimum(directory, gemspecs)
62
+ gemspecs.each do |name|
63
+ declaration = File.read(File.join(directory, name))[GEMSPEC_REQUIREMENT_REGEX, 1]
64
+ next if declaration.nil?
65
+
66
+ requirement = Gem::Requirement.new(*declaration.scan(/["']([^"']+)["']/).flatten)
67
+ bounds = requirement.requirements.filter_map {|operator, version| version if LOWER_BOUND_OPERATORS.include?(operator) }
68
+ return bounds.max.to_s if bounds.any?
69
+ rescue Gem::Requirement::BadRequirementError
70
+ next
71
+ end
72
+ nil
73
+ end
74
+
75
+ def ruby_version_file(directory)
76
+ path = File.join(directory, ".ruby-version")
77
+ File.file?(path) ? File.read(path).strip[RUBY_VERSION_FILE_REGEX, 1] : nil
78
+ end
79
+
80
+ ##============================================================##
81
+ ## A declared version the installed RuboCop cannot target falls
82
+ ## back to the running Ruby: cleaning must never stop on it.
83
+ ##============================================================##
84
+ def supported_target(target)
85
+ return nil if target.nil?
86
+ return target if target == release(RUBY_VERSION)
87
+
88
+ validate!(target)
89
+ target
90
+ rescue ArgumentError => e
91
+ warn("immosquare-cleaner: #{e.message}; using Ruby #{release(RUBY_VERSION)}")
92
+ nil
93
+ end
94
+
95
+ def validate!(target)
96
+ require "rubocop"
97
+
98
+ config = RuboCop::Config.new({"AllCops" => {"TargetRubyVersion" => target}}, __FILE__)
99
+ raise(ArgumentError, "ruby_target_version #{target.inspect} does not match RuboCop's effective target #{config.target_ruby_version}") if config.target_ruby_version.to_s != target
100
+
101
+ config.validate_after_resolution
102
+ rescue RuboCop::ValidationError => e
103
+ raise(ArgumentError, "Invalid ruby_target_version #{target.inspect}: #{e.message}")
104
+ end
105
+
106
+ end
107
+
108
+ end
109
+ end
@@ -1,3 +1,3 @@
1
1
  module ImmosquareCleaner
2
- VERSION = "1.2.3".freeze
2
+ VERSION = "1.3.0".freeze
3
3
  end
@@ -4,6 +4,8 @@ require "json"
4
4
  require "immosquare-yaml"
5
5
  require "immosquare-extensions"
6
6
  require "fileutils"
7
+ require "digest"
8
+ require_relative "immosquare-cleaner/ruby_target"
7
9
  require_relative "immosquare-cleaner/configuration"
8
10
  require_relative "immosquare-cleaner/markdown"
9
11
  require_relative "immosquare-cleaner/directory_cleaner"
@@ -39,6 +41,9 @@ require_relative "immosquare-cleaner/processors/prettier"
39
41
  ## See: https://github.com/Shopify/erb_lint/blob/main/lib/erb_lint/linter_registry.rb#L7
40
42
  ##============================================================##
41
43
  module ImmosquareCleaner
44
+ LINTER_CONFIG_MUTEX = Mutex.new
45
+ LINTER_CONFIG_NAMES = ["rubocop", "erb-lint", "js-erb-lint"].freeze
46
+
42
47
  class << self
43
48
 
44
49
  ##============================================================##
@@ -66,11 +71,6 @@ module ImmosquareCleaner
66
71
  end
67
72
 
68
73
  def clean(file_path, origin_path = nil)
69
- ##============================================================##
70
- ## Ensure linter configurations are ready
71
- ##============================================================##
72
- setup_linter_configs!
73
-
74
74
  ##============================================================##
75
75
  ## Return if the file is in the exclusion list or is a generator
76
76
  ## template. With `-p`, file_path is a /tmp copy: the template
@@ -101,6 +101,17 @@ module ImmosquareCleaner
101
101
  File.expand_path("..", __dir__)
102
102
  end
103
103
 
104
+ ##============================================================##
105
+ ## path : the file's location in its project (origin_path), which
106
+ ## decides the Ruby target unless ruby_target_version is set
107
+ ##============================================================##
108
+ def linter_config_path(name, path)
109
+ raise(ArgumentError, "Unknown Ruby linter configuration") if !LINTER_CONFIG_NAMES.include?(name)
110
+
111
+ target = configuration.ruby_target_version || RubyTarget.detect(path)
112
+ setup_linter_configs!(target).fetch(name)
113
+ end
114
+
104
115
  ##============================================================##
105
116
  ## Processor registry — order matters.
106
117
  ##
@@ -136,36 +147,43 @@ module ImmosquareCleaner
136
147
  end
137
148
 
138
149
  ##============================================================##
139
- ## Setup linter with the correct ruby version
140
- ## This is run once to avoid redundant file operations.
150
+ ## Configs are shared by target Ruby and template fingerprint.
151
+ ## Atomic writes and a mutex prevent partially generated YAML
152
+ ## during parallel cleaning; template edits invalidate the cache.
141
153
  ##============================================================##
142
- def setup_linter_configs!
143
- @setup_linter_configs ||= begin
144
- rubocop_config_with_version_path = "#{gem_root}/linters/rubocop-#{RUBY_VERSION}.yml"
145
-
146
- if !File.exist?(rubocop_config_with_version_path)
147
- rubocop_config = YAML.load_file("#{gem_root}/linters/rubocop.yml")
148
- rubocop_config["AllCops"]["TargetRubyVersion"] = RUBY_VERSION
149
- rubocop_config["AllCops"]["ParserEngine"] = RUBY_VERSION >= "3.3" ? "parser_prism" : "parser_whitequark"
150
- File.write(rubocop_config_with_version_path, rubocop_config.to_yaml)
154
+ def setup_linter_configs!(target)
155
+ templates = LINTER_CONFIG_NAMES.to_h {|name| [name, File.join(gem_root, "linters", "#{name}.yml")] }
156
+ signature = templates.values.map {|path| [File.mtime(path), File.size(path)] }
157
+ LINTER_CONFIG_MUTEX.synchronize do
158
+ @linter_configs ||= {}
159
+ key = [target, signature]
160
+ return @linter_configs[key] if @linter_configs.key?(key)
161
+
162
+ sources = templates.transform_values {|path| File.read(path) }
163
+ fingerprint = Digest::SHA256.hexdigest(sources.values.join("\n")).slice(0, 16)
164
+ paths = LINTER_CONFIG_NAMES.to_h {|name| [name, File.join(gem_root, "linters", "#{name}-#{target}-#{fingerprint}.yml")] }
165
+ rubocop_config = YAML.safe_load(sources.fetch("rubocop"))
166
+ rubocop_config["AllCops"]["TargetRubyVersion"] = target
167
+ rubocop_config["AllCops"]["ParserEngine"] = Gem::Version.new(target) >= Gem::Version.new("3.3") ? "parser_prism" : "parser_whitequark"
168
+ write_linter_config(paths.fetch("rubocop"), rubocop_config)
169
+
170
+ ["erb-lint", "js-erb-lint"].each do |name|
171
+ config = YAML.safe_load(sources.fetch(name))
172
+ config["linters"]["Rubocop"]["rubocop_config"]["inherit_from"] = ["linters/#{File.basename(paths.fetch("rubocop"))}"]
173
+ write_linter_config(paths.fetch(name), config)
151
174
  end
175
+ @linter_configs[key] = paths.freeze
176
+ end
177
+ end
152
178
 
153
- ##============================================================##
154
- ## erb_lint configs that inherit the versioned rubocop config.
155
- ## - erb-lint : default config for .html.erb
156
- ## - js-erb-lint : JS-specific config for .js.erb / .ts.erb / etc.
157
- ##============================================================##
158
- ["erb-lint", "js-erb-lint"].each do |basename|
159
- versioned_path = "#{gem_root}/linters/#{basename}-#{RUBY_VERSION}.yml"
160
- next if File.exist?(versioned_path)
161
-
162
- config = YAML.load_file("#{gem_root}/linters/#{basename}.yml")
163
- config["linters"]["Rubocop"]["rubocop_config"]["inherit_from"] = ["linters/rubocop-#{RUBY_VERSION}.yml"]
164
- File.write(versioned_path, config.to_yaml)
165
- end
179
+ def write_linter_config(path, config)
180
+ return if File.file?(path)
166
181
 
167
- true
168
- end
182
+ temporary = "#{path}.#{Process.pid}.#{Thread.current.object_id}.tmp"
183
+ File.write(temporary, config.to_yaml)
184
+ File.rename(temporary, path)
185
+ ensure
186
+ File.delete(temporary) if temporary && File.file?(temporary)
169
187
  end
170
188
 
171
189
  end
@@ -0,0 +1,308 @@
1
+ module RuboCop
2
+ module Cop
3
+ module CustomCops
4
+ module Style
5
+ ##============================================================##
6
+ ## This cop aligns the blocks of Rails routes in
7
+ ## `config/routes.rb` and `config/routes/*.rb`: inside a
8
+ ## block of consecutive route lines, the verb, the path,
9
+ ## the target and every option (`:as`, `:via`,
10
+ ## `:constraints`...) start in the same column. Options are
11
+ ## aligned by name: a route without `:via` leaves that
12
+ ## column empty and its `:as` lands under the `:as` of its
13
+ ## neighbours.
14
+ ##
15
+ ## Only whitespace changes: paths, targets, options and
16
+ ## comments come out identical, in the same order. The
17
+ ## corrections only rewrite the gaps between tokens, never
18
+ ## a token itself, so they cannot clash with the
19
+ ## corrections of other cops.
20
+ ##
21
+ ## The option columns follow an order compatible with the
22
+ ## order of every route in the block (`:via` before `:as`
23
+ ## when a route carries them in that order, even when a
24
+ ## neighbour without `:via` meets `:as` first). When two
25
+ ## routes impose opposite orders, the block is left alone:
26
+ ## no alignment can satisfy both without reordering.
27
+ ##
28
+ ## Recognized shapes: `get "a" => "c#a", :as => ...`, the
29
+ ## same call with parentheses (`match("a" => "c#a", ...)`),
30
+ ## and the shape without a positional target
31
+ ## (`match "404", :to => ...`).
32
+ ##
33
+ ## @example
34
+ ## bad
35
+ ## get "users" => "users#index", :as => :users
36
+ ## match "users/:id" => "users#update", :via => [:patch, :put], :as => :user
37
+ ##
38
+ ## good
39
+ ## get "users" => "users#index", :as => :users
40
+ ## match "users/:id" => "users#update", :via => [:patch, :put], :as => :user
41
+ ##============================================================##
42
+ class AlignRoutes < RuboCop::Cop::Base
43
+
44
+ extend AutoCorrector
45
+
46
+ MSG = "Align the columns of consecutive routes.".freeze
47
+
48
+ VERBS = [:get, :post, :put, :patch, :delete, :match].freeze
49
+
50
+ ##============================================================##
51
+ ## Only the files Rails loads its routes from. A `get`
52
+ ## call anywhere else is not a route.
53
+ ##============================================================##
54
+ ROUTES_FILE = %r{config/routes(?:/[^/]+\.rb|\.rb)\z}
55
+
56
+ def on_new_investigation
57
+ @routes = []
58
+ end
59
+
60
+ def on_send(node)
61
+ return unless routes_file?
62
+ return unless VERBS.include?(node.method_name)
63
+ return unless node.receiver.nil?
64
+
65
+ route = parse_route(node)
66
+ @routes << route if route
67
+ end
68
+
69
+ def on_investigation_end
70
+ groups.each {|group| check_group(group) }
71
+ end
72
+
73
+ private
74
+
75
+ def routes_file?
76
+ path = processed_source.file_path
77
+ path.nil? || ROUTES_FILE.match?(path.to_s)
78
+ end
79
+
80
+ ##============================================================##
81
+ ## A route line cut into segments: verb, path, target when
82
+ ## there is one, then its options under their name (`:as`,
83
+ ## `:via`...), in the order the line carries them. Every
84
+ ## segment keeps its source range, so the corrections can
85
+ ## address the gaps between segments. Nil when the call
86
+ ## is not a single-line route starting its line.
87
+ ##============================================================##
88
+ def parse_route(node)
89
+ range = node.source_range
90
+ return if range.first_line != range.last_line
91
+
92
+ line = processed_source.lines[range.first_line - 1]
93
+ return if line.nil? || !line.strip.start_with?(node.source.strip)
94
+
95
+ first = node.arguments.first
96
+ return if first.nil?
97
+
98
+ if first.hash_type?
99
+ return if first.braces? || first.pairs.empty?
100
+
101
+ pair = first.pairs.first
102
+ return unless pair.key.str_type?
103
+
104
+ target_pair = pair
105
+ path_range = pair.key.source_range
106
+ rest_pairs = first.pairs.drop(1)
107
+ elsif first.str_type?
108
+ target_pair = nil
109
+ path_range = first.source_range
110
+ rest_pairs = []
111
+ if node.arguments.size > 1
112
+ hash = node.arguments[1]
113
+ return unless hash.hash_type? && !hash.braces?
114
+
115
+ rest_pairs = hash.pairs
116
+ end
117
+ else
118
+ return
119
+ end
120
+
121
+ segments = [{:kind => :path, :range => path_range}]
122
+ segments << {:kind => :target, :range => target_pair.value.source_range, :pair => target_pair} if target_pair
123
+ rest_pairs.each_with_index do |pair, index|
124
+ key = pair.key.sym_type? ? ":#{pair.key.value}" : "__raw#{index}"
125
+ segments << {:kind => :option, :key => key, :range => pair.source_range}
126
+ end
127
+
128
+ {:node => node, :line => range.first_line, :segments => segments}
129
+ end
130
+
131
+ ##============================================================##
132
+ ## Blocks of routes on adjacent lines. Any other line
133
+ ## (comment, `scope`, `end`, a route this cop does not
134
+ ## recognize...) puts at least one line between two
135
+ ## routes and so closes the block. Two routes sharing a
136
+ ## line are dropped: aligning them would rewrite the
137
+ ## same line twice.
138
+ ##============================================================##
139
+ def groups
140
+ sorted = @routes.sort_by {|route| route[:node].source_range.begin_pos }
141
+ counts = sorted.map {|route| route[:line] }.tally
142
+ sorted = sorted.reject {|route| counts[route[:line]] > 1 }
143
+
144
+ sorted.chunk_while {|a, b| b[:line] == a[:line] + 1 }.to_a
145
+ end
146
+
147
+ ##============================================================##
148
+ ## Check one block: compute the column of every segment,
149
+ ## then flag each route whose gaps differ, correcting
150
+ ## gap by gap.
151
+ ##============================================================##
152
+ def check_group(group)
153
+ return if group.size < 2
154
+
155
+ keys = ordered_keys(group)
156
+ return if keys.nil?
157
+
158
+ place_segments(group, keys)
159
+
160
+ group.each do |route|
161
+ corrections = corrections_for(route)
162
+ next if corrections.empty?
163
+
164
+ add_offense(route[:node], :message => MSG) do |corrector|
165
+ corrections.each {|range, text| corrector.replace(range, text) }
166
+ end
167
+ end
168
+ end
169
+
170
+ ##============================================================##
171
+ ## The column of every segment of every route of the
172
+ ## block: first `=>` in one column, then one column per
173
+ ## option name, in the order computed for the block. The
174
+ ## comma stays glued to the value before it, the spaces
175
+ ## follow it.
176
+ ##============================================================##
177
+ def place_segments(group, keys)
178
+ verb_width = group.map {|route| route[:node].method_name.length }.max
179
+ path_width = group.map {|route| segment(route, :path)[:range].size }.max
180
+
181
+ group.each do |route|
182
+ node = route[:node]
183
+ path_start = node.source_range.column + verb_width + 1
184
+ segment(route, :path)[:start] = path_start
185
+
186
+ target = segment(route, :target)
187
+ head_end =
188
+ if target
189
+ target[:start] = path_start + path_width + 4
190
+ target[:start] + target[:range].size
191
+ else
192
+ path_start + path_width
193
+ end
194
+ route[:head_end] = head_end
195
+ end
196
+
197
+ starts = {}
198
+ column = group.map {|route| route[:head_end] }.max + 2
199
+ keys.each do |key|
200
+ starts[key] = column
201
+ followers = keys.drop(keys.index(key) + 1)
202
+ ends = group.filter_map do |route|
203
+ option = route[:segments].find {|seg| seg[:key] == key }
204
+ column + option[:range].size if option && (option_keys(route) & followers).any?
205
+ end
206
+ column = ends.max + 2 if ends.any?
207
+ end
208
+
209
+ group.each do |route|
210
+ route[:segments].each do |seg|
211
+ seg[:start] = starts[seg[:key]] if seg[:kind] == :option
212
+ end
213
+ end
214
+ end
215
+
216
+ ##============================================================##
217
+ ## The gaps of one route that do not match the computed
218
+ ## columns, as [range, expected text] pairs. Ranges only
219
+ ## ever span whitespace and the commas separating the
220
+ ## segments, never a segment itself.
221
+ ##============================================================##
222
+ def corrections_for(route)
223
+ node = route[:node]
224
+ selector = node.loc.selector
225
+ path = segment(route, :path)
226
+ corrections = []
227
+
228
+ if node.loc.begin
229
+ replace_gap(corrections, selector.end_pos, node.loc.begin.begin_pos, "")
230
+ replace_gap(corrections, node.loc.begin.end_pos, path[:range].begin_pos, "")
231
+ else
232
+ replace_gap(corrections, selector.end_pos, path[:range].begin_pos,
233
+ " " * (path[:start] - selector.column - selector.size))
234
+ end
235
+
236
+ previous = path
237
+ if (target = segment(route, :target))
238
+ operator = target[:pair].loc.operator
239
+ ##============================================================##
240
+ ## The `=>` sits 3 columns before the target (its 2
241
+ ## chars plus the space after it); the path ends, in
242
+ ## the aligned line, at its start plus its length.
243
+ ##============================================================##
244
+ replace_gap(corrections, path[:range].end_pos, operator.begin_pos,
245
+ " " * ((target[:start] - 3) - (path[:start] + path[:range].size)))
246
+ replace_gap(corrections, operator.end_pos, target[:range].begin_pos, " ")
247
+ previous = target
248
+ end
249
+
250
+ route[:segments].select {|seg| seg[:kind] == :option }.each do |option|
251
+ prev_end = previous[:start] + previous[:range].size
252
+ replace_gap(corrections, previous[:range].end_pos, option[:range].begin_pos,
253
+ ",#{" " * (option[:start] - prev_end - 1)}")
254
+ previous = option
255
+ end
256
+ corrections
257
+ end
258
+
259
+ ##============================================================##
260
+ ## Record a gap correction when the current text differs.
261
+ ##============================================================##
262
+ def replace_gap(corrections, begin_pos, end_pos, expected)
263
+ range = Parser::Source::Range.new(processed_source.buffer, begin_pos, end_pos)
264
+ return if range.source == expected
265
+
266
+ corrections << [range, expected]
267
+ end
268
+
269
+ def segment(route, kind)
270
+ route[:segments].find {|seg| seg[:kind] == kind }
271
+ end
272
+
273
+ def option_keys(route)
274
+ route[:segments].filter_map {|seg| seg[:key] }
275
+ end
276
+
277
+ ##============================================================##
278
+ ## The order of the option columns: the first one
279
+ ## compatible with the order every route gives its
280
+ ## options, keeping first-encounter order on ties. Nil
281
+ ## when two routes impose opposite orders.
282
+ ##============================================================##
283
+ def ordered_keys(group)
284
+ keys = group.flat_map {|route| option_keys(route) }.uniq
285
+ predecessors = Hash.new {|hash, key| hash[key] = [] }
286
+ group.each do |route|
287
+ option_keys(route).each_cons(2) do |before, after|
288
+ predecessors[after] << before unless predecessors[after].include?(before)
289
+ end
290
+ end
291
+
292
+ sorted = []
293
+ remaining = keys.dup
294
+ while remaining.any?
295
+ ready = remaining.select {|key| (predecessors[key] - sorted).empty? }
296
+ return if ready.empty?
297
+
298
+ sorted << ready.first
299
+ remaining.delete(ready.first)
300
+ end
301
+ sorted
302
+ end
303
+
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
data/linters/rubocop.yml CHANGED
@@ -5,6 +5,7 @@ require:
5
5
  - rubocop/cop/custom_cops/style/align_assignments
6
6
  - rubocop/cop/custom_cops/style/inline_multiline_calls
7
7
  - rubocop/cop/custom_cops/style/kwarg_priority_order
8
+ - rubocop/cop/custom_cops/style/align_routes
8
9
 
9
10
  # Enable all RuboCop methods and turn caching on for large files
10
11
  AllCops:
@@ -127,6 +128,10 @@ Style/RescueModifier:
127
128
  Enabled: false # Allow inline rescue modifier (some_method rescue SomeException)
128
129
  Style/ArrayIntersect:
129
130
  Enabled: false # The cop assumes the receiver of `include?` is a collection, so `MARKERS.any? { |m| some_string.include?(m) }` (searching several markers in one string) is rewritten as `MARKERS.intersect?(some_string)`. `Array#intersect?` only takes an Array, so the corrected code raises `TypeError: no implicit conversion of String into Array` — at runtime, not on save. The correction is unsafe, hence applied by our `--autocorrect-all`. Fixed upstream in rubocop/rubocop#15439 but unreleased as of 1.88.2; re-enable once a release ships it. To match several markers in a string, use `some_string.match?(Regexp.union(*MARKERS))`.
131
+ Style/HashSlice:
132
+ AutoCorrect: false # The cop assumes the receiver of `select {|key, _value| LIST.include?(key) }` is a Hash and rewrites it as `.slice(*LIST)`. On an Array of pairs (e.g. `Gem::Requirement#requirements`) the corrected code raises `TypeError: no implicit conversion of String into Integer` at runtime. The correction is unsafe, hence applied by our `--autocorrect-all`. Keep the warning, drop the rewrite.
133
+ Style/HashExcept:
134
+ AutoCorrect: false # Same receiver guess as Style/HashSlice: `reject {|key, _value| LIST.include?(key) }` on an Array of pairs becomes `.except(*LIST)`, which Array does not define (NoMethodError at runtime). Keep the warning, drop the rewrite.
130
135
  Style/NestedParenthesizedCalls:
131
136
  Enabled: false # Redundant with Style/MethodCallWithArgsParentheses (require_parentheses) — both flag `outer(inner arg)`. RuboCop dedupes the inserted `(` but not the matching `)`, and erb_lint's integration ships both corrections independently → output gets an extra `)` (e.g. `j(render :partial => "x")` becomes `j(render(:partial => "x")))`, breaking the ERB at render time).
132
137
 
@@ -257,8 +262,11 @@ CustomCops/Style/KwargPriorityOrder:
257
262
  - remote
258
263
  - method
259
264
 
265
+ CustomCops/Style/AlignRoutes:
266
+ Enabled: true # Align the columns of consecutive routes in config/routes.rb and config/routes/*.rb
267
+
260
268
  #################### GEM ###########################
261
269
  Gemspec/RequireMFA:
262
270
  Enabled: false # Don't require MFA to push a gem
263
271
  Gemspec/RequiredRubyVersion:
264
- Enabled: false # TargetRubyVersion is always set to the Ruby running the cleaner, so it never matches the minimum a gemspec declares (`>= 3.2.6` vs 4.0.x): the cop would flag every gemspec.
272
+ Enabled: false # Not autocorrectable, and TargetRubyVersion can come from ruby_target_version or .ruby-version rather than the gemspec: the cop would only report noise.
data/package.json CHANGED
@@ -9,10 +9,10 @@
9
9
  "dependencies": {
10
10
  "@babel/parser": "^8.0.7",
11
11
  "@eslint/js": "^10.0.1",
12
- "@typescript-eslint/eslint-plugin": "^8.71.2",
13
- "@typescript-eslint/parser": "^8.71.2",
12
+ "@typescript-eslint/eslint-plugin": "^8.72.0",
13
+ "@typescript-eslint/parser": "^8.72.0",
14
14
  "@typescript/native": "npm:typescript@^7.0.2",
15
- "eslint": "^10.12.0",
15
+ "eslint": "^10.13.0",
16
16
  "eslint-plugin-align-import": "^1.0.0",
17
17
  "eslint-plugin-erb": "^2.1.1",
18
18
  "eslint-plugin-prefer-arrow": "^1.2.3",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immosquare-cleaner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - immosquare
@@ -181,9 +181,8 @@ files:
181
181
  - lib/immosquare-cleaner/processors/shell.rb
182
182
  - lib/immosquare-cleaner/processors/toml.rb
183
183
  - lib/immosquare-cleaner/processors/yaml.rb
184
+ - lib/immosquare-cleaner/ruby_target.rb
184
185
  - lib/immosquare-cleaner/version.rb
185
- - linters/erb-lint-4.0.6.yml
186
- - linters/erb-lint-4.0.7.yml
187
186
  - linters/erb-lint.yml
188
187
  - linters/erb_lint/custom_align_consecutive_calls.rb
189
188
  - linters/erb_lint/custom_html_to_content_tag.rb
@@ -191,15 +190,12 @@ files:
191
190
  - linters/eslint-plugins/align-assignments.mjs
192
191
  - linters/eslint-plugins/eslint10-compat.mjs
193
192
  - linters/eslint.config.mjs
194
- - linters/js-erb-lint-4.0.6.yml
195
- - linters/js-erb-lint-4.0.7.yml
196
193
  - linters/js-erb-lint.yml
197
194
  - linters/normalize-comments.mjs
198
195
  - linters/prettier.yml
199
- - linters/rubocop-4.0.6.yml
200
- - linters/rubocop-4.0.7.yml
201
196
  - linters/rubocop.yml
202
197
  - linters/rubocop/cop/custom_cops/style/align_assignments.rb
198
+ - linters/rubocop/cop/custom_cops/style/align_routes.rb
203
199
  - linters/rubocop/cop/custom_cops/style/comment_normalization.rb
204
200
  - linters/rubocop/cop/custom_cops/style/font_awesome_normalization.rb
205
201
  - linters/rubocop/cop/custom_cops/style/inline_multiline_calls.rb
@@ -228,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
228
224
  - !ruby/object:Gem::Version
229
225
  version: '0'
230
226
  requirements: []
231
- rubygems_version: 4.0.22
227
+ rubygems_version: 4.0.23
232
228
  specification_version: 4
233
229
  summary: One command to format every file of a repository, built to run as an AI agent
234
230
  hook.
@@ -1,22 +0,0 @@
1
- ---
2
- EnableDefaultLinters: true
3
- linters:
4
- NoJavascriptTagHelper:
5
- enabled: false
6
- SpaceInHtmlTag:
7
- enabled: false
8
- CustomSingleLineIfModifier:
9
- enabled: true
10
- CustomHtmlToContentTag:
11
- enabled: true
12
- CustomAlignConsecutiveCalls:
13
- enabled: true
14
- methods:
15
- - link_to
16
- Rubocop:
17
- enabled: true
18
- rubocop_config:
19
- inherit_from:
20
- - linters/rubocop-4.0.6.yml
21
- Layout/TrailingWhitespace:
22
- Enabled: false
@@ -1,27 +0,0 @@
1
- ---
2
- EnableDefaultLinters: true
3
- linters:
4
- NoJavascriptTagHelper:
5
- enabled: false
6
- SpaceInHtmlTag:
7
- enabled: false
8
- AllowedScriptType:
9
- enabled: true
10
- allowed_types:
11
- - text/javascript
12
- - module
13
- CustomSingleLineIfModifier:
14
- enabled: true
15
- CustomHtmlToContentTag:
16
- enabled: true
17
- CustomAlignConsecutiveCalls:
18
- enabled: true
19
- methods:
20
- - link_to
21
- Rubocop:
22
- enabled: true
23
- rubocop_config:
24
- inherit_from:
25
- - linters/rubocop-4.0.7.yml
26
- Layout/TrailingWhitespace:
27
- Enabled: false
@@ -1,26 +0,0 @@
1
- ---
2
- EnableDefaultLinters: true
3
- linters:
4
- NoJavascriptTagHelper:
5
- enabled: false
6
- SpaceInHtmlTag:
7
- enabled: false
8
- SelfClosingTag:
9
- enabled: false
10
- RequireInputAutocomplete:
11
- enabled: false
12
- DeprecatedClassesInline:
13
- enabled: false
14
- HardCodedString:
15
- enabled: false
16
- CustomHtmlToContentTag:
17
- enabled: false
18
- CustomSingleLineIfModifier:
19
- enabled: true
20
- Rubocop:
21
- enabled: true
22
- rubocop_config:
23
- inherit_from:
24
- - linters/rubocop-4.0.6.yml
25
- Layout/TrailingWhitespace:
26
- Enabled: false
@@ -1,26 +0,0 @@
1
- ---
2
- EnableDefaultLinters: true
3
- linters:
4
- NoJavascriptTagHelper:
5
- enabled: false
6
- SpaceInHtmlTag:
7
- enabled: false
8
- SelfClosingTag:
9
- enabled: false
10
- RequireInputAutocomplete:
11
- enabled: false
12
- DeprecatedClassesInline:
13
- enabled: false
14
- HardCodedString:
15
- enabled: false
16
- CustomHtmlToContentTag:
17
- enabled: false
18
- CustomSingleLineIfModifier:
19
- enabled: true
20
- Rubocop:
21
- enabled: true
22
- rubocop_config:
23
- inherit_from:
24
- - linters/rubocop-4.0.7.yml
25
- Layout/TrailingWhitespace:
26
- Enabled: false
@@ -1,219 +0,0 @@
1
- ---
2
- require:
3
- - rubocop/cop/style/method_call_with_args_parentheses_override
4
- - rubocop/cop/custom_cops/style/comment_normalization
5
- - rubocop/cop/custom_cops/style/font_awesome_normalization
6
- - rubocop/cop/custom_cops/style/align_assignments
7
- - rubocop/cop/custom_cops/style/inline_multiline_calls
8
- - rubocop/cop/custom_cops/style/kwarg_priority_order
9
- AllCops:
10
- NewCops: enable
11
- EnabledByDefault: false
12
- UseCache: true
13
- SuggestExtensions: false
14
- ActiveSupportExtensionsEnabled: true
15
- TargetRubyVersion: 4.0.6
16
- ParserEngine: parser_prism
17
- Metrics:
18
- Enabled: false
19
- Lint/UselessAssignment:
20
- Enabled: false
21
- Lint/RescueException:
22
- Enabled: false
23
- Lint/UnusedMethodArgument:
24
- Enabled: false
25
- Lint/UselessOr:
26
- Enabled: false
27
- Lint/InterpolationCheck:
28
- AutoCorrect: false
29
- Naming/VariableNumber:
30
- Enabled: false
31
- Naming/FileName:
32
- Enabled: false
33
- Naming/MethodParameterName:
34
- MinNameLength: 1
35
- Layout/LeadingEmptyLines:
36
- Enabled: false
37
- Layout/InitialIndentation:
38
- Enabled: false
39
- Layout/TrailingEmptyLines:
40
- Enabled: false
41
- Layout/ExtraSpacing:
42
- Enabled: false
43
- Layout/LineLength:
44
- Enabled: false
45
- Layout/TrailingWhitespace:
46
- Enabled: true
47
- Layout/EmptyLines:
48
- Enabled: false
49
- Layout/LeadingCommentSpace:
50
- Enabled: false
51
- Layout/SpaceInsideBlockBraces:
52
- SpaceBeforeBlockParameters: false
53
- Layout/EmptyLinesAroundClassBody:
54
- EnforcedStyle: empty_lines
55
- Layout/FirstHashElementIndentation:
56
- EnforcedStyle: consistent
57
- Layout/ArgumentAlignment:
58
- EnforcedStyle: with_fixed_indentation
59
- Layout/HashAlignment:
60
- EnforcedHashRocketStyle: table
61
- Layout/MultilineAssignmentLayout:
62
- EnforcedStyle: same_line
63
- Layout/SpaceInsideHashLiteralBraces:
64
- EnforcedStyle: no_space
65
- Layout/MultilineMethodCallIndentation:
66
- EnforcedStyle: indented
67
- Style/CombinableLoops:
68
- Enabled: false
69
- Style/SingleArgumentDig:
70
- Enabled: false
71
- Style/RedundantBegin:
72
- Enabled: false
73
- Style/MultilineTernaryOperator:
74
- Enabled: false
75
- Style/NestedTernaryOperator:
76
- Enabled: false
77
- Style/MultilineIfModifier:
78
- Enabled: false
79
- Style/FrozenStringLiteralComment:
80
- Enabled: false
81
- Style/Next:
82
- Enabled: false
83
- Style/Documentation:
84
- Enabled: false
85
- Style/IfUnlessModifierOfIfUnless:
86
- Enabled: false
87
- Style/NegatedIf:
88
- Enabled: false
89
- Style/FormatStringToken:
90
- EnforcedStyle: template
91
- Style/NumericPredicate:
92
- EnforcedStyle: comparison
93
- Style/StringLiterals:
94
- EnforcedStyle: double_quotes
95
- Style/StringLiteralsInInterpolation:
96
- EnforcedStyle: double_quotes
97
- Style/HashSyntax:
98
- EnforcedStyle: hash_rockets
99
- Style/WordArray:
100
- EnforcedStyle: brackets
101
- Style/SymbolArray:
102
- EnforcedStyle: brackets
103
- Style/RescueModifier:
104
- Enabled: false
105
- Style/ArrayIntersect:
106
- Enabled: false
107
- Style/NestedParenthesizedCalls:
108
- Enabled: false
109
- Style/MethodCallWithArgsParentheses:
110
- Enabled: true
111
- EnforcedStyle: require_parentheses
112
- IgnoreMacros: false
113
- AllowedMethods:
114
- - require
115
- - require_relative
116
- - include
117
- - extend
118
- - prepend
119
- - raise
120
- - fail
121
- - yield
122
- - attr_accessor
123
- - attr_reader
124
- - attr_writer
125
- - run
126
- - exec
127
- - load
128
- - gem
129
- - group
130
- - source
131
- - ruby
132
- - install_plugin
133
- - desc
134
- - task
135
- - namespace
136
- - before_action
137
- - after_action
138
- - around_action
139
- - skip_before_action
140
- - prepend_before_action
141
- - append_before_action
142
- - helper_method
143
- - layout
144
- - rescue_from
145
- - belongs_to
146
- - has_many
147
- - has_one
148
- - has_and_belongs_to_many
149
- - validates
150
- - validate
151
- - validates_presence_of
152
- - validates_uniqueness_of
153
- - validates_length_of
154
- - validates_format_of
155
- - validates_numericality_of
156
- - validates_inclusion_of
157
- - validates_exclusion_of
158
- - validates_confirmation_of
159
- - validates_acceptance_of
160
- - validates_absence_of
161
- - validates_associated
162
- - validates_each
163
- - validates_with
164
- - after_initialize
165
- - after_find
166
- - before_validation
167
- - after_validation
168
- - before_save
169
- - after_save
170
- - around_save
171
- - before_create
172
- - after_create
173
- - around_create
174
- - before_update
175
- - after_update
176
- - around_update
177
- - before_destroy
178
- - after_destroy
179
- - around_destroy
180
- - after_touch
181
- - after_commit
182
- - after_rollback
183
- - delegate
184
- - enum
185
- - get
186
- - post
187
- - put
188
- - patch
189
- - delete
190
- - options
191
- - head
192
- - match
193
- - root
194
- - rake
195
- - every
196
- - command
197
- - set
198
- - append
199
- - server
200
- - after
201
- CustomCops/Style/CommentNormalization:
202
- Enabled: true
203
- CustomCops/Style/FontAwesomeNormalization:
204
- Enabled: true
205
- CustomCops/Style/AlignAssignments:
206
- Enabled: false
207
- CustomCops/Style/InlineMultilineCalls:
208
- Enabled: true
209
- Methods:
210
- - link_to
211
- CustomCops/Style/KwargPriorityOrder:
212
- Enabled: true
213
- Methods:
214
- - link_to
215
- PriorityKeys:
216
- - remote
217
- - method
218
- Gemspec/RequireMFA:
219
- Enabled: false
@@ -1,234 +0,0 @@
1
- ---
2
- require:
3
- - rubocop/cop/style/method_call_with_args_parentheses_override
4
- - rubocop/cop/custom_cops/style/comment_normalization
5
- - rubocop/cop/custom_cops/style/font_awesome_normalization
6
- - rubocop/cop/custom_cops/style/align_assignments
7
- - rubocop/cop/custom_cops/style/inline_multiline_calls
8
- - rubocop/cop/custom_cops/style/kwarg_priority_order
9
- AllCops:
10
- NewCops: enable
11
- EnabledByDefault: false
12
- UseCache: true
13
- SuggestExtensions: false
14
- ActiveSupportExtensionsEnabled: true
15
- TargetRubyVersion: 4.0.7
16
- ParserEngine: parser_prism
17
- Metrics:
18
- Enabled: false
19
- Lint/UselessAssignment:
20
- Enabled: false
21
- Lint/RescueException:
22
- Enabled: false
23
- Lint/UnusedMethodArgument:
24
- Enabled: false
25
- Lint/UselessOr:
26
- Enabled: false
27
- Lint/InterpolationCheck:
28
- AutoCorrect: false
29
- Naming/VariableNumber:
30
- Enabled: false
31
- Naming/FileName:
32
- Enabled: false
33
- Naming/MethodParameterName:
34
- MinNameLength: 1
35
- Naming/PredicateMethod:
36
- AllowedMethods:
37
- - call
38
- - move_to_cache
39
- - move_to_store
40
- - save
41
- - check_health
42
- - elastic_import_with_progressbar
43
- Layout/LeadingEmptyLines:
44
- Enabled: false
45
- Layout/InitialIndentation:
46
- Enabled: false
47
- Layout/TrailingEmptyLines:
48
- Enabled: false
49
- Layout/ExtraSpacing:
50
- Enabled: false
51
- Layout/LineLength:
52
- Enabled: false
53
- Layout/TrailingWhitespace:
54
- Enabled: true
55
- Layout/EmptyLines:
56
- Enabled: false
57
- Layout/LeadingCommentSpace:
58
- Enabled: false
59
- Layout/SpaceInsideBlockBraces:
60
- SpaceBeforeBlockParameters: false
61
- Layout/EmptyLinesAroundClassBody:
62
- EnforcedStyle: empty_lines
63
- Layout/FirstHashElementIndentation:
64
- EnforcedStyle: consistent
65
- Layout/ArgumentAlignment:
66
- EnforcedStyle: with_fixed_indentation
67
- Layout/HashAlignment:
68
- EnforcedHashRocketStyle: table
69
- Layout/MultilineAssignmentLayout:
70
- EnforcedStyle: same_line
71
- Layout/SpaceInsideHashLiteralBraces:
72
- EnforcedStyle: no_space
73
- Layout/MultilineMethodCallIndentation:
74
- EnforcedStyle: indented
75
- Style/CombinableLoops:
76
- Enabled: false
77
- Style/SingleArgumentDig:
78
- Enabled: false
79
- Style/RedundantBegin:
80
- Enabled: false
81
- Style/MultilineTernaryOperator:
82
- Enabled: false
83
- Style/NestedTernaryOperator:
84
- Enabled: false
85
- Style/MultilineIfModifier:
86
- Enabled: false
87
- Style/FrozenStringLiteralComment:
88
- Enabled: false
89
- Style/Next:
90
- Enabled: false
91
- Style/Documentation:
92
- Enabled: false
93
- Style/IfUnlessModifierOfIfUnless:
94
- Enabled: false
95
- Style/NegatedIf:
96
- Enabled: false
97
- Style/FormatStringToken:
98
- EnforcedStyle: template
99
- Mode: conservative
100
- Style/OptionalBooleanParameter:
101
- AllowedMethods:
102
- - respond_to_missing?
103
- - perform
104
- Style/NumericPredicate:
105
- EnforcedStyle: comparison
106
- Style/StringLiterals:
107
- EnforcedStyle: double_quotes
108
- Style/StringLiteralsInInterpolation:
109
- EnforcedStyle: double_quotes
110
- Style/HashSyntax:
111
- EnforcedStyle: hash_rockets
112
- Style/WordArray:
113
- EnforcedStyle: brackets
114
- Style/SymbolArray:
115
- EnforcedStyle: brackets
116
- Style/RescueModifier:
117
- Enabled: false
118
- Style/ArrayIntersect:
119
- Enabled: false
120
- Style/NestedParenthesizedCalls:
121
- Enabled: false
122
- Style/MethodCallWithArgsParentheses:
123
- Enabled: true
124
- EnforcedStyle: require_parentheses
125
- IgnoreMacros: false
126
- AllowedMethods:
127
- - require
128
- - require_relative
129
- - include
130
- - extend
131
- - prepend
132
- - raise
133
- - fail
134
- - yield
135
- - attr_accessor
136
- - attr_reader
137
- - attr_writer
138
- - run
139
- - exec
140
- - load
141
- - gem
142
- - group
143
- - source
144
- - ruby
145
- - install_plugin
146
- - desc
147
- - task
148
- - namespace
149
- - before_action
150
- - after_action
151
- - around_action
152
- - skip_before_action
153
- - prepend_before_action
154
- - append_before_action
155
- - helper_method
156
- - layout
157
- - rescue_from
158
- - belongs_to
159
- - has_many
160
- - has_one
161
- - has_and_belongs_to_many
162
- - validates
163
- - validate
164
- - validates_presence_of
165
- - validates_uniqueness_of
166
- - validates_length_of
167
- - validates_format_of
168
- - validates_numericality_of
169
- - validates_inclusion_of
170
- - validates_exclusion_of
171
- - validates_confirmation_of
172
- - validates_acceptance_of
173
- - validates_absence_of
174
- - validates_associated
175
- - validates_each
176
- - validates_with
177
- - after_initialize
178
- - after_find
179
- - before_validation
180
- - after_validation
181
- - before_save
182
- - after_save
183
- - around_save
184
- - before_create
185
- - after_create
186
- - around_create
187
- - before_update
188
- - after_update
189
- - around_update
190
- - before_destroy
191
- - after_destroy
192
- - around_destroy
193
- - after_touch
194
- - after_commit
195
- - after_rollback
196
- - delegate
197
- - enum
198
- - get
199
- - post
200
- - put
201
- - patch
202
- - delete
203
- - options
204
- - head
205
- - match
206
- - root
207
- - rake
208
- - every
209
- - command
210
- - set
211
- - append
212
- - server
213
- - after
214
- CustomCops/Style/CommentNormalization:
215
- Enabled: true
216
- CustomCops/Style/FontAwesomeNormalization:
217
- Enabled: true
218
- CustomCops/Style/AlignAssignments:
219
- Enabled: false
220
- CustomCops/Style/InlineMultilineCalls:
221
- Enabled: true
222
- Methods:
223
- - link_to
224
- CustomCops/Style/KwargPriorityOrder:
225
- Enabled: true
226
- Methods:
227
- - link_to
228
- PriorityKeys:
229
- - remote
230
- - method
231
- Gemspec/RequireMFA:
232
- Enabled: false
233
- Gemspec/RequiredRubyVersion:
234
- Enabled: false