immosquare-cleaner 0.1.78 → 0.1.80

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: dadcd82a9246f29c49b8f6133cb4ec9a8b059435828a6a0906b6d70d4a845457
4
- data.tar.gz: fb334b4d1e5c6c815269c4cc9a933dc31c0eb64c043c05c3905769a9cf038a9d
3
+ metadata.gz: 160d617b3d77622b3c4c3b4978166b13a832eebd125e7e3cec3d27f250befcff
4
+ data.tar.gz: 7f4ea0e67420419f1c97b4cc6780aa6f6de11ead46f3e74f9fc6c32ba973325b
5
5
  SHA512:
6
- metadata.gz: b3e042005cc8401df8c7cbeb967e4caee2e0f2e0d64698ba9b7586981427edd6f3f62bb13d4979eb8bd0248acdca9f59ce4400b04583994ab6445c9899076c80
7
- data.tar.gz: 67edec140e0b02668d4980bcc92e8e57c55a4576d2e3be204211b675246b2a5fd526e18e3af3fd4963ed6b4474dd2426266fc77933763d71b07f7695840201eb
6
+ metadata.gz: c6a38cffd458d0174ce2ac361249da090c7037d1d28ddc6dcc3653ad21f1c0091ee3c8056a2030abf8324f74bbe6f713038a064751b218b96b3d40c228d19869
7
+ data.tar.gz: 2f3b914ea9df14c7fe03d18cd45d9df078645aa5f37fc6fb27027d4c0a90c52c1e27527661fb12007ff5912f5b0430f1869e954dffa9b3098a640808d352651d
data/.erb_linters ADDED
@@ -0,0 +1 @@
1
+ linters/erb_lint
@@ -29,7 +29,7 @@ OptionParser.new do |parser|
29
29
  parser.banner = "Usage: immosquare-cleaner [options] file"
30
30
 
31
31
  parser.on("-h", "--help", "Prints this help") do
32
- puts parser
32
+ puts(parser)
33
33
  exit
34
34
  end
35
35
  end.parse!
@@ -1,7 +1,7 @@
1
1
  module ImmosquareCleaner
2
2
  class Configuration
3
3
 
4
- attr_accessor :rubocop_options, :htmlbeautifier_options, :erblint_options, :exclude_files
4
+ attr_accessor(:rubocop_options, :htmlbeautifier_options, :erblint_options, :exclude_files)
5
5
 
6
6
  def initialize
7
7
  @rubocop_options = nil
@@ -2,7 +2,7 @@ module ImmosquareCleaner
2
2
  class Railtie < Rails::Railtie
3
3
 
4
4
  rake_tasks do
5
- load "tasks/immosquare_cleaner.rake"
5
+ load("tasks/immosquare_cleaner.rake")
6
6
  end
7
7
 
8
8
  end
@@ -1,3 +1,3 @@
1
1
  module ImmosquareCleaner
2
- VERSION = "0.1.78".freeze
2
+ VERSION = "0.1.80".freeze
3
3
  end
@@ -9,10 +9,17 @@ require_relative "immosquare-cleaner/markdown"
9
9
  require_relative "immosquare-cleaner/railtie" if defined?(Rails)
10
10
 
11
11
  ##============================================================##
12
- ## Importing the 'English' library allows us to use more human-readable
12
+ ## note 1 : Importing the 'English' library allows us to use more human-readable
13
13
  ## global variables, such as $INPUT_RECORD_SEPARATOR instead of $/,
14
14
  ## which enhances code clarity and makes it easier to understand
15
15
  ## the purpose of these variables in our code.
16
+ ## ---------
17
+ ## note 2 :
18
+ ## Custom erb_lint linters are stored in this folder.
19
+ ## A symlink .erb_linters -> linters/erb_lint is required at the
20
+ ## gem root because erb_lint hardcodes the custom linters directory
21
+ ## to ".erb_linters" and this cannot be configured.
22
+ ## See: https://github.com/Shopify/erb_lint/blob/main/lib/erb_lint/linter_registry.rb#L7
16
23
  ##============================================================##
17
24
  module ImmosquareCleaner
18
25
  class << self
@@ -24,6 +31,7 @@ module ImmosquareCleaner
24
31
  RUBY_FILES = [
25
32
  ".rb",
26
33
  ".rake",
34
+ "Brewfile",
27
35
  "Gemfile",
28
36
  "Rakefile",
29
37
  "Capfile",
@@ -45,7 +53,7 @@ module ImmosquareCleaner
45
53
  ##============================================================##
46
54
  ## Gem configuration
47
55
  ##============================================================##
48
- attr_writer :configuration
56
+ attr_writer(:configuration)
49
57
 
50
58
  def configuration
51
59
  @configuration ||= Configuration.new
@@ -24,10 +24,10 @@ namespace :immosquare_cleaner do
24
24
  File.directory?(file_path) || file_path.gsub("#{Rails.root}/", "").start_with?(*paths_to_exclude) || file_path.end_with?(*extensions_to_exclude)
25
25
  end
26
26
 
27
- puts "Cleaning files..."
27
+ puts("Cleaning files...")
28
28
 
29
29
  file_paths.each.with_index do |file_path, index|
30
- puts "#{index + 1}/#{file_paths.size} - #{file_path}"
30
+ puts("#{index + 1}/#{file_paths.size} - #{file_path}")
31
31
  ImmosquareCleaner.clean(file_path)
32
32
  end
33
33
  end
@@ -5,6 +5,10 @@ linters:
5
5
  enabled: false
6
6
  SpaceInHtmlTag:
7
7
  enabled: false
8
+ CustomSingleLineIfModifier:
9
+ enabled: true
10
+ CustomHtmlToContentTag:
11
+ enabled: true
8
12
  Rubocop:
9
13
  enabled: true
10
14
  rubocop_config:
data/linters/erb-lint.yml CHANGED
@@ -4,6 +4,10 @@ linters:
4
4
  enabled: false
5
5
  SpaceInHtmlTag:
6
6
  enabled: false
7
+ CustomSingleLineIfModifier:
8
+ enabled: true
9
+ CustomHtmlToContentTag:
10
+ enabled: true
7
11
  Rubocop:
8
12
  enabled: true
9
13
  rubocop_config:
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ module Linters
5
+ ##============================================================##
6
+ ## This linter detects HTML tags containing only a single ERB
7
+ ## output statement and converts them to content_tag helpers.
8
+ ##
9
+ ## @example
10
+ ## # bad
11
+ ## <div class="card-title"><%= t("app.title") %></div>
12
+ ##
13
+ ## # good
14
+ ## <%= content_tag(:div, t("app.title"), :class => "card-title") %>
15
+ ##
16
+ ##============================================================##
17
+ class CustomHtmlToContentTag < Linter
18
+ include LinterRegistry
19
+
20
+ MSG = "Use content_tag helper instead of HTML tag with ERB output."
21
+
22
+ ##============================================================##
23
+ ## Void elements that cannot have content (self-closing tags)
24
+ ##============================================================##
25
+ VOID_ELEMENTS = %w[
26
+ area base br col command embed hr img input keygen
27
+ link menuitem meta param source track wbr
28
+ ].freeze
29
+
30
+ def run(processed_source)
31
+ document = processed_source.ast
32
+ children = document.children.to_a
33
+
34
+ children.each_with_index do |child, index|
35
+ ##============================================================##
36
+ ## Look for opening tag (not closing, not void element)
37
+ ##============================================================##
38
+ next unless child&.type == :tag
39
+ next if closing_tag?(child)
40
+
41
+ tag_name = extract_tag_name(child)
42
+ next if tag_name.nil?
43
+ next if VOID_ELEMENTS.include?(tag_name.downcase)
44
+
45
+ ##============================================================##
46
+ ## Check if next child is text containing only ERB output
47
+ ##============================================================##
48
+ next_child = children[index + 1]
49
+ next unless next_child&.type == :text
50
+
51
+ erb_node = extract_single_erb_output(next_child)
52
+ next unless erb_node
53
+
54
+ ##============================================================##
55
+ ## Check if next-next child is the closing tag
56
+ ##============================================================##
57
+ closing_child = children[index + 2]
58
+ next unless closing_child&.type == :tag
59
+ next unless closing_tag?(closing_child)
60
+ next unless extract_tag_name(closing_child) == tag_name
61
+
62
+ ##============================================================##
63
+ ## Build the content_tag replacement
64
+ ##============================================================##
65
+ attributes = extract_attributes(child)
66
+ erb_code = extract_erb_code(erb_node)
67
+
68
+ new_code = build_content_tag(tag_name, erb_code, attributes)
69
+
70
+ ##============================================================##
71
+ ## Calculate the full range from opening to closing tag
72
+ ##============================================================##
73
+ full_range = processed_source.to_source_range(
74
+ child.loc.begin_pos...closing_child.loc.end_pos
75
+ )
76
+
77
+ add_offense(full_range, MSG, {:new_code => new_code})
78
+ end
79
+ end
80
+
81
+ def autocorrect(_processed_source, offense)
82
+ lambda do |corrector|
83
+ corrector.replace(offense.source_range, offense.context[:new_code])
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ ##============================================================##
90
+ ## Check if tag is a closing tag (has solidus as first child)
91
+ ##============================================================##
92
+ def closing_tag?(tag_node)
93
+ tag_node.children.first&.type == :solidus
94
+ end
95
+
96
+ ##============================================================##
97
+ ## Extract tag name from tag node
98
+ ##============================================================##
99
+ def extract_tag_name(tag_node)
100
+ name_node = tag_node.children.find {|c| c&.type == :tag_name }
101
+ name_node&.children&.first
102
+ end
103
+
104
+ ##============================================================##
105
+ ## Extract single ERB output node from text node
106
+ ## Returns nil if text contains anything other than whitespace
107
+ ## and a single ERB output
108
+ ##============================================================##
109
+ def extract_single_erb_output(text_node)
110
+ erb_nodes = []
111
+ has_non_whitespace_text = false
112
+
113
+ text_node.children.each do |child|
114
+ if child.is_a?(String)
115
+ has_non_whitespace_text = true unless child.match?(/\A\s*\z/)
116
+ elsif child&.type == :erb
117
+ erb_nodes << child
118
+ end
119
+ end
120
+
121
+ return nil if has_non_whitespace_text
122
+ return nil if erb_nodes.size != 1
123
+
124
+ erb_node = erb_nodes.first
125
+ ##============================================================##
126
+ ## Must be output ERB (<%= ... %>) not statement (<% ... %>)
127
+ ##============================================================##
128
+ indicator = erb_node.children.first
129
+ return nil unless indicator&.type == :indicator
130
+ return nil unless indicator.children.first == "="
131
+
132
+ erb_node
133
+ end
134
+
135
+ ##============================================================##
136
+ ## Extract Ruby code from ERB node
137
+ ##============================================================##
138
+ def extract_erb_code(erb_node)
139
+ code_node = erb_node.children.find {|c| c&.type == :code }
140
+ code_node&.loc&.source&.strip
141
+ end
142
+
143
+ ##============================================================##
144
+ ## Extract attributes from tag node as hash
145
+ ##============================================================##
146
+ def extract_attributes(tag_node)
147
+ attrs = {}
148
+ attrs_node = tag_node.children.find {|c| c&.type == :tag_attributes }
149
+ return attrs unless attrs_node
150
+
151
+ attrs_node.children.each do |attr|
152
+ next unless attr&.type == :attribute
153
+
154
+ name_node = attr.children.find {|c| c&.type == :attribute_name }
155
+ value_node = attr.children.find {|c| c&.type == :attribute_value }
156
+
157
+ name = name_node&.children&.first
158
+ next unless name
159
+
160
+ ##============================================================##
161
+ ## Value can be a string or contain ERB
162
+ ##============================================================##
163
+ value = extract_attribute_value(value_node)
164
+ attrs[name] = value
165
+ end
166
+
167
+ attrs
168
+ end
169
+
170
+ ##============================================================##
171
+ ## Extract attribute value, handling both static and ERB values
172
+ ##============================================================##
173
+ def extract_attribute_value(value_node)
174
+ return nil unless value_node
175
+
176
+ parts = []
177
+ value_node.children.each do |child|
178
+ if child.is_a?(String)
179
+ parts << child
180
+ elsif child.respond_to?(:type)
181
+ next if child.type == :quote
182
+
183
+ if child.type == :erb
184
+ erb_code = extract_erb_code(child)
185
+ parts << "\#{#{erb_code}}"
186
+ end
187
+ end
188
+ end
189
+
190
+ parts.join
191
+ end
192
+
193
+ ##============================================================##
194
+ ## Build content_tag call
195
+ ##============================================================##
196
+ def build_content_tag(tag_name, content, attributes)
197
+ if attributes.empty?
198
+ "<%= content_tag(:#{tag_name}, #{content}) %>"
199
+ else
200
+ attrs_str = attributes.map do |name, value|
201
+ key = normalize_attribute_name(name)
202
+ ##============================================================##
203
+ ## Check if value contains interpolation
204
+ ##============================================================##
205
+ if value&.include?('#{')
206
+ "#{key} => \"#{value}\""
207
+ else
208
+ "#{key} => \"#{value}\""
209
+ end
210
+ end.join(", ")
211
+
212
+ "<%= content_tag(:#{tag_name}, #{content}, #{attrs_str}) %>"
213
+ end
214
+ end
215
+
216
+ ##============================================================##
217
+ ## Normalize attribute name to Ruby symbol format
218
+ ## class -> :class, data-value -> :\"data-value\" or data: { value: }
219
+ ##============================================================##
220
+ def normalize_attribute_name(name)
221
+ if name.match?(/\A[a-z_][a-z0-9_]*\z/i)
222
+ ":#{name}"
223
+ else
224
+ ":\"#{name}\""
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ERBLint
4
+ module Linters
5
+ ##============================================================##
6
+ ## This linter detects if/unless blocks with a single ERB output
7
+ ## statement and converts them to inline modifier syntax.
8
+ ##
9
+ ## @example
10
+ ## bad
11
+ ## <% if condition %>
12
+ ## <%= link_to("Home", root_path) %>
13
+ ## <% end %>
14
+ ##
15
+ ## good
16
+ ## <%= link_to("Home", root_path) if condition %>
17
+ ##
18
+ ##============================================================##
19
+ class CustomSingleLineIfModifier < Linter
20
+
21
+ include LinterRegistry
22
+
23
+ MSG = "Use modifier if/unless for single-line ERB output."
24
+
25
+ def run(processed_source)
26
+ erb_nodes = processed_source.ast.descendants(:erb).to_a
27
+ return if erb_nodes.size < 3
28
+
29
+ erb_nodes.each_with_index do |erb_node, index|
30
+ ##============================================================##
31
+ ## We need at least 3 consecutive ERB nodes:
32
+ ## 1. <% if/unless condition %>
33
+ ## 2. <%= output %>
34
+ ## 3. <% end %>
35
+ ##============================================================##
36
+ next if index + 2 >= erb_nodes.size
37
+
38
+ if_node = erb_node
39
+ output_node = erb_nodes[index + 1]
40
+ end_node = erb_nodes[index + 2]
41
+
42
+ ##============================================================##
43
+ ## Check if this is a valid pattern to transform
44
+ ##============================================================##
45
+ next unless valid_if_modifier_pattern?(if_node, output_node, end_node, processed_source)
46
+
47
+ ##============================================================##
48
+ ## Extract the condition and output code
49
+ ##============================================================##
50
+ if_code = extract_code(if_node)
51
+ output_code = extract_code(output_node)
52
+
53
+ condition_match = if_code.match(/\A\s*(if|unless)\s+(.+)\s*\z/m)
54
+ next unless condition_match
55
+
56
+ keyword = condition_match[1]
57
+ condition = condition_match[2].strip
58
+
59
+ ##============================================================##
60
+ ## Build the new inline syntax
61
+ ##============================================================##
62
+ new_code = "#{output_code.strip} #{keyword} #{condition}"
63
+
64
+ ##============================================================##
65
+ ## Calculate the full range from if to end (inclusive)
66
+ ##============================================================##
67
+ full_range = processed_source.to_source_range(
68
+ if_node.loc.begin_pos...end_node.loc.end_pos
69
+ )
70
+
71
+ ##============================================================##
72
+ ## Store context for autocorrect
73
+ ##============================================================##
74
+ context = {
75
+ :new_code => "<%= #{new_code} %>",
76
+ :output_node => output_node,
77
+ :is_output_node => is_output_erb?(output_node)
78
+ }
79
+
80
+ add_offense(full_range, MSG, context)
81
+ end
82
+ end
83
+
84
+ def autocorrect(_processed_source, offense)
85
+ lambda do |corrector|
86
+ corrector.replace(offense.source_range, offense.context[:new_code])
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ ##============================================================##
93
+ ## Check if this is a valid pattern:
94
+ ## - First node is <% if/unless condition %>
95
+ ## - Second node is <%= output %> (output ERB)
96
+ ## - Third node is <% end %>
97
+ ## - No other ERB nodes between them
98
+ ##============================================================##
99
+ def valid_if_modifier_pattern?(if_node, output_node, end_node, processed_source)
100
+ ##============================================================##
101
+ ## Check indicators: if_node should be <% (not <%=)
102
+ ## output_node should be <%= (output)
103
+ ## end_node should be <% (not <%=)
104
+ ##============================================================##
105
+ return false unless is_statement_erb?(if_node)
106
+ return false unless is_output_erb?(output_node)
107
+ return false unless is_statement_erb?(end_node)
108
+
109
+ ##============================================================##
110
+ ## Extract and validate the code
111
+ ##============================================================##
112
+ if_code = extract_code(if_node)
113
+ output_code = extract_code(output_node)
114
+ end_code = extract_code(end_node)
115
+
116
+ return false unless if_code&.match?(/\A\s*(if|unless)\s+.+\z/m)
117
+ return false unless end_code&.strip == "end"
118
+ return false if output_code.to_s.strip.empty?
119
+
120
+ ##============================================================##
121
+ ## Check that there's only whitespace/newlines between the nodes
122
+ ## (no other HTML content or ERB nodes)
123
+ ##============================================================##
124
+ between_if_and_output = source_between(processed_source, if_node, output_node)
125
+ between_output_and_end = source_between(processed_source, output_node, end_node)
126
+
127
+ return false unless between_if_and_output&.match?(/\A\s*\z/)
128
+ return false unless between_output_and_end&.match?(/\A\s*\z/)
129
+
130
+ true
131
+ end
132
+
133
+ ##============================================================##
134
+ ## Check if ERB node is a statement (<% ... %>)
135
+ ##============================================================##
136
+ def is_statement_erb?(erb_node)
137
+ indicator = erb_node.children.first
138
+ indicator.nil? || (indicator.respond_to?(:children) && indicator.children.first.nil?)
139
+ end
140
+
141
+ ##============================================================##
142
+ ## Check if ERB node is an output (<%= ... %>)
143
+ ##============================================================##
144
+ def is_output_erb?(erb_node)
145
+ indicator = erb_node.children.first
146
+ indicator.respond_to?(:children) && indicator.children.first == "="
147
+ end
148
+
149
+ ##============================================================##
150
+ ## Extract the Ruby code from an ERB node
151
+ ##============================================================##
152
+ def extract_code(erb_node)
153
+ code_node = erb_node.children[2]
154
+ return nil unless code_node
155
+
156
+ code_node.loc.source
157
+ end
158
+
159
+ ##============================================================##
160
+ ## Get the source text between two nodes
161
+ ##============================================================##
162
+ def source_between(processed_source, node1, node2)
163
+ start_pos = node1.loc.end_pos
164
+ end_pos = node2.loc.begin_pos
165
+ return "" if start_pos >= end_pos
166
+
167
+ processed_source.source_buffer.source[start_pos...end_pos]
168
+ end
169
+
170
+ end
171
+ end
172
+ end
@@ -96,6 +96,23 @@ Style/RescueModifier:
96
96
  Enabled: false
97
97
  Style/MethodCallWithArgsParentheses:
98
98
  Enabled: true
99
+ EnforcedStyle: require_parentheses
100
+ IgnoreMacros: false
101
+ AllowedMethods:
102
+ - require
103
+ - require_relative
104
+ - include
105
+ - extend
106
+ - prepend
107
+ - raise
108
+ - fail
109
+ - gem
110
+ - source
111
+ - ruby
112
+ - desc
113
+ - task
114
+ - namespace
115
+ - yield
99
116
  CustomCops/Style/CommentNormalization:
100
117
  Enabled: true
101
118
  CustomCops/Style/FontAwesomeNormalization:
data/linters/rubocop.yml CHANGED
@@ -108,6 +108,23 @@ Style/RescueModifier:
108
108
  #################### OVERWRITE ###########################
109
109
  Style/MethodCallWithArgsParentheses:
110
110
  Enabled: true # On veut forcer les parenthèses où elles sont utilises pour rendre le code plus propre
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
+ - gem
122
+ - source
123
+ - ruby
124
+ - desc
125
+ - task
126
+ - namespace
127
+ - yield
111
128
 
112
129
  #################### CUSTOMS ###########################
113
130
  CustomCops/Style/CommentNormalization:
data/package.json CHANGED
@@ -2,17 +2,16 @@
2
2
  "name": "immosquare-cleaner",
3
3
  "private": true,
4
4
  "dependencies": {
5
- "@eslint/js": "^9.35.0",
6
- "@typescript-eslint/eslint-plugin": "^8.6.0",
7
- "@typescript-eslint/parser": "^8.6.0",
8
- "eslint": "^9.35.0",
9
- "eslint-plugin-align-assignments": "^1.1.2",
10
- "eslint-plugin-align-import": "^1.0.0",
11
- "eslint-plugin-erb": "^2.1.1",
12
- "eslint-plugin-prefer-arrow": "^1.2.3",
13
- "eslint-plugin-sonarjs": "^3.0.5",
14
- "prettier": "^3.6.2",
15
- "typescript": "^5.5.4"
16
- },
17
- "scripts": {"morning": "bundle update && bundle clean --force && bun update --latest && bun install && bundle exec immosquare-cleaner 'package.json'"}
5
+ "@eslint/js": "^9.39.1",
6
+ "@typescript-eslint/eslint-plugin": "^8.48.1",
7
+ "@typescript-eslint/parser": "^8.48.1",
8
+ "eslint": "^9.39.1",
9
+ "eslint-plugin-align-assignments": "^1.1.2",
10
+ "eslint-plugin-align-import": "^1.0.0",
11
+ "eslint-plugin-erb": "^2.1.1",
12
+ "eslint-plugin-prefer-arrow": "^1.2.3",
13
+ "eslint-plugin-sonarjs": "^3.0.5",
14
+ "prettier": "^3.7.4",
15
+ "typescript": "^5.9.3"
16
+ }
18
17
  }
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: 0.1.78
4
+ version: 0.1.80
5
5
  platform: ruby
6
6
  authors:
7
7
  - immosquare
@@ -139,6 +139,7 @@ executables:
139
139
  extensions: []
140
140
  extra_rdoc_files: []
141
141
  files:
142
+ - ".erb_linters"
142
143
  - bin/immosquare-cleaner
143
144
  - lib/immosquare-cleaner.rb
144
145
  - lib/immosquare-cleaner/configuration.rb
@@ -148,6 +149,8 @@ files:
148
149
  - lib/tasks/immosquare_cleaner.rake
149
150
  - linters/erb-lint-3.4.1.yml
150
151
  - linters/erb-lint.yml
152
+ - linters/erb_lint/custom_html_to_content_tag.rb
153
+ - linters/erb_lint/custom_single_line_if_modifier.rb
151
154
  - linters/eslint.config.mjs
152
155
  - linters/prettier.yml
153
156
  - linters/rubocop-3.4.1.yml
@@ -176,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
179
  - !ruby/object:Gem::Version
177
180
  version: '0'
178
181
  requirements: []
179
- rubygems_version: 3.7.2
182
+ rubygems_version: 4.0.0
180
183
  specification_version: 4
181
184
  summary: A gem to lint and organize files in a Rails application.
182
185
  test_files: []