immosquare-cleaner 0.1.93 → 0.1.95

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: 386d987876b864e4d445826193b67f8ea4bfdf30a292fc08f4271b2ab5eb1c06
4
- data.tar.gz: 410548afe07047e7f7cef616f8d766a299f151ea95ee1f072b6d32ac147d3048
3
+ metadata.gz: ef4743e368013a24d51795c0401fb25551318cf708de34ede70da2463732fb14
4
+ data.tar.gz: d071271dcda68ef78e8c042217d39ba13f484b8081e120384c7c01b3c8e0deaa
5
5
  SHA512:
6
- metadata.gz: 219571e479c00ae7575c079b2a504877ea4e3f85176bf0e7d44526684ef43dd68ae12e2b3b78c049a267ffa12dcf03c7925ce1786eba549c835f31cd484d1daf
7
- data.tar.gz: 6aed55c5b70fa5116a34c4d49cac06f7aa7639b18d01806026ea0bcc2582db0b9f203fb733cd1ec431b58bc40705d3f51a14b13faa44468e7563997cd5279a0e
6
+ metadata.gz: 11cda1db31886b571cf81a294798e080eef10e30870adeb23071d37242415d047e0b7916c3c6af0bca855f116c49808a2bd38aeba1744f7e29bd1fbd768a5385
7
+ data.tar.gz: 2451ad2ef109bf3847a554d7d557bd182e24fae97c7be882e85e8ff1392cffc0a14258f724b2197e386ca6905a51e46c302d2c54d483eea8fc3b5ecd748b5520
@@ -1,3 +1,3 @@
1
1
  module ImmosquareCleaner
2
- VERSION = "0.1.93".freeze
2
+ VERSION = "0.1.95".freeze
3
3
  end
@@ -168,7 +168,7 @@ module ImmosquareCleaner
168
168
  File.write(temp_file_path, File.read(file_path))
169
169
  cmds = []
170
170
  cmds << "bundle exec erb_lint --config #{erblint_config_with_version_path} #{file_path} #{ImmosquareCleaner.configuration.erblint_options || "--autocorrect"}" if file_path.end_with?(".erb")
171
- cmds << "bun #{gem_root}/linters/normalize-comments.mjs #{temp_file_path}"
171
+ cmds << "bun #{gem_root}/linters/normalize-comments.mjs #{temp_file_path}" if !file_path.end_with?(".erb")
172
172
  cmds << "bun eslint --config #{gem_root}/linters/eslint.config.mjs #{temp_file_path} --fix"
173
173
 
174
174
  launch_cmds(cmds)
@@ -19,8 +19,19 @@ namespace :immosquare_cleaner do
19
19
  ]
20
20
  extensions_to_exclude = [
21
21
  ".lock",
22
- ".lockb"
22
+ ".lockb",
23
+ ".otf",
24
+ ".ttf",
25
+ ".png",
26
+ ".jpg",
27
+ ".jpeg",
28
+ ".gif",
29
+ ".svg",
30
+ ".ico",
31
+ ".webp",
32
+ ".csv"
23
33
  ]
34
+
24
35
  file_paths = Dir.glob("#{Rails.root}/**/*").reject do |file_path|
25
36
  File.directory?(file_path) || file_path.gsub("#{Rails.root}/", "").start_with?(*paths_to_exclude) || file_path.end_with?(*extensions_to_exclude)
26
37
  end
@@ -213,21 +213,31 @@ module ERBLint
213
213
  ##============================================================##
214
214
  ## Extract single ERB output node from text node
215
215
  ## Returns nil if text contains anything other than whitespace
216
- ## and a single ERB output
216
+ ## and a single ERB output. Also rejects if there are nested tags.
217
217
  ##============================================================##
218
218
  def extract_single_erb_output(text_node)
219
219
  erb_nodes = []
220
220
  has_non_whitespace_text = false
221
+ has_nested_tags = false
221
222
 
222
223
  text_node.children.each do |child|
223
224
  if child.is_a?(String)
224
225
  has_non_whitespace_text = true unless child.match?(/\A\s*\z/)
225
- elsif child&.type == :erb
226
- erb_nodes << child
226
+ elsif child.respond_to?(:type)
227
+ case child.type
228
+ when :erb
229
+ erb_nodes << child
230
+ when :tag
231
+ ##============================================================##
232
+ ## If there's any nested tag, don't convert the parent
233
+ ##============================================================##
234
+ has_nested_tags = true
235
+ end
227
236
  end
228
237
  end
229
238
 
230
239
  return nil if has_non_whitespace_text
240
+ return nil if has_nested_tags
231
241
  return nil if erb_nodes.size != 1
232
242
 
233
243
  erb_node = erb_nodes.first
@@ -256,41 +266,37 @@ module ERBLint
256
266
  def excluded_method?(erb_code)
257
267
  return false unless erb_code
258
268
 
259
- call_node = extract_call_node(erb_code)
260
- return false unless call_node
261
-
262
- method_name = call_node.name.to_s
263
- return true if EXCLUDED_METHODS.include?(method_name)
269
+ result = Prism.parse(erb_code)
270
+ node = result.value.statements.body.first
264
271
 
265
272
  ##============================================================##
266
- ## If method is a form builder method called on a receiver,
267
- ## skip conversion (e.g., f.input, form.text_field, etc.)
273
+ ## yield is a YieldNode, not a CallNode - always exclude it
274
+ ## Note: Prism reports yield outside block as syntax error,
275
+ ## but still creates the node, so check before result.success?
268
276
  ##============================================================##
269
- return true if call_node.receiver && FORM_BUILDER_METHODS.include?(method_name)
270
-
271
- false
272
- end
273
-
274
- ##============================================================##
275
- ## Extract the CallNode from Ruby code using Prism parser
276
- ## Handles both direct calls and calls with if/unless modifiers
277
- ##============================================================##
278
- def extract_call_node(erb_code)
279
- result = Prism.parse(erb_code)
280
- return nil unless result.success?
277
+ return true if node.is_a?(Prism::YieldNode)
281
278
 
282
- node = result.value.statements.body.first
279
+ return false unless result.success?
283
280
 
284
281
  ##============================================================##
285
282
  ## Handle if/unless modifiers: `render(...) if condition`
286
283
  ##============================================================##
287
284
  node = node.statements&.body&.first if node.is_a?(Prism::IfNode) || node.is_a?(Prism::UnlessNode)
288
285
 
289
- return nil unless node.is_a?(Prism::CallNode)
286
+ return false unless node.is_a?(Prism::CallNode)
287
+
288
+ method_name = node.name.to_s
289
+ return true if EXCLUDED_METHODS.include?(method_name)
290
290
 
291
- node
291
+ ##============================================================##
292
+ ## If method is a form builder method called on a receiver,
293
+ ## skip conversion (e.g., f.input, form.text_field, etc.)
294
+ ##============================================================##
295
+ return true if node.receiver && FORM_BUILDER_METHODS.include?(method_name)
296
+
297
+ false
292
298
  rescue StandardError
293
- nil
299
+ false
294
300
  end
295
301
 
296
302
  ##============================================================##
@@ -322,25 +328,54 @@ module ERBLint
322
328
 
323
329
  ##============================================================##
324
330
  ## Extract attribute value, handling both static and ERB values
331
+ ## Returns a hash with :type (:static, :dynamic, :erb_only)
332
+ ## For :dynamic, also includes :parts array for proper escaping
325
333
  ##============================================================##
326
334
  def extract_attribute_value(value_node)
327
335
  return nil unless value_node
328
336
 
329
337
  parts = []
338
+ erb_parts = []
339
+ static_parts = []
340
+
330
341
  value_node.children.each do |child|
331
342
  if child.is_a?(String)
332
- parts << child
343
+ parts << {:type => :static, :value => child}
344
+ static_parts << child
333
345
  elsif child.respond_to?(:type)
334
346
  next if child.type == :quote
335
347
 
336
348
  if child.type == :erb
337
349
  erb_code = extract_erb_code(child)
338
- parts << "\#{#{erb_code}}"
350
+ parts << {:type => :erb, :value => erb_code}
351
+ erb_parts << erb_code
339
352
  end
340
353
  end
341
354
  end
342
355
 
343
- parts.join
356
+ ##============================================================##
357
+ ## Determine the attribute value type:
358
+ ## - :erb_only: Only ERB, no static text (use Ruby directly)
359
+ ## - :dynamic: Mix of static and ERB (use interpolation)
360
+ ## - :static: Only static text (use quoted string)
361
+ ##============================================================##
362
+ if erb_parts.any? && static_parts.all? {|s| s.match?(/\A\s*\z/) }
363
+ ##============================================================##
364
+ ## Pure ERB value: class="<%= expr %>" -> :class => (expr)
365
+ ##============================================================##
366
+ {:type => :erb_only, :value => erb_parts.first}
367
+ elsif erb_parts.any?
368
+ ##============================================================##
369
+ ## Mixed value: keep parts separate for proper escaping
370
+ ## class="foo <%= expr %>" -> :class => "foo #{expr}"
371
+ ##============================================================##
372
+ {:type => :dynamic, :parts => parts}
373
+ else
374
+ ##============================================================##
375
+ ## Static value: class="foo" -> :class => "foo"
376
+ ##============================================================##
377
+ {:type => :static, :value => static_parts.join}
378
+ end
344
379
  end
345
380
 
346
381
  ##============================================================##
@@ -367,10 +402,10 @@ module ERBLint
367
402
  if attributes.empty?
368
403
  base = "content_tag(#{formatted_tag}, #{wrapped_content})"
369
404
  else
370
- attrs_str = attributes.map do |name, value|
405
+ attrs_str = attributes.map do |name, attr_data|
371
406
  key = normalize_attribute_name(name)
372
- quoted_value = quote_attribute_value(value)
373
- "#{key} => #{quoted_value}"
407
+ formatted_value = format_attribute_value(attr_data)
408
+ "#{key} => #{formatted_value}"
374
409
  end.join(", ")
375
410
 
376
411
  base = "content_tag(#{formatted_tag}, #{wrapped_content}, #{attrs_str})"
@@ -451,27 +486,76 @@ module ERBLint
451
486
  end
452
487
 
453
488
  ##============================================================##
454
- ## Quote attribute value for Ruby output
455
- ## - Uses double quotes by default
456
- ## - Switches to single quotes if value contains double quotes
457
- ## - Handles interpolation by keeping double quotes and escaping
489
+ ## Format attribute value based on its type
490
+ ## - :erb_only: Pure ERB -> (expr) with parentheses if needed
491
+ ## - :dynamic: Mixed -> "text #{expr}" with interpolation
492
+ ## - :static: Pure text -> "value" with proper quoting
458
493
  ##============================================================##
459
- def quote_attribute_value(value)
460
- return '""' if value.nil? || value.empty?
494
+ def format_attribute_value(attr_data)
495
+ return '""' if attr_data.nil?
461
496
 
462
- has_interpolation = value.include?('#{')
463
- has_double_quotes = value.include?('"')
497
+ type = attr_data[:type]
464
498
 
465
- if has_interpolation
499
+ case type
500
+ when :erb_only
466
501
  ##============================================================##
467
- ## Must use double quotes for interpolation, escape inner quotes
502
+ ## Pure ERB: wrap in parentheses for safety
503
+ ## class="<%= x if y %>" -> :class => (x if y)
468
504
  ##============================================================##
469
- escaped = value.gsub('"', '\\"')
470
- "\"#{escaped}\""
471
- elsif has_double_quotes
505
+ value = attr_data[:value]
506
+ return '""' if value.nil? || value.empty?
507
+
508
+ "(#{value})"
509
+ when :dynamic
472
510
  ##============================================================##
473
- ## Use single quotes to avoid escaping
511
+ ## Mixed static and ERB: build string from parts
512
+ ## Escape quotes only in static parts, not in ERB parts
513
+ ## class="foo <%= bar %>" -> :class => "foo #{bar}"
514
+ ##============================================================##
515
+ parts = attr_data[:parts]
516
+ return '""' if parts.nil? || parts.empty?
517
+
518
+ build_interpolated_string(parts)
519
+ else
474
520
  ##============================================================##
521
+ ## Static value: simple quoting
522
+ ##============================================================##
523
+ value = attr_data[:value]
524
+ return '""' if value.nil? || value.empty?
525
+
526
+ quote_static_string(value)
527
+ end
528
+ end
529
+
530
+ ##============================================================##
531
+ ## Build an interpolated string from parts
532
+ ## Static parts have quotes escaped, ERB parts are wrapped in #{}
533
+ ##============================================================##
534
+ def build_interpolated_string(parts)
535
+ result = parts.map do |part|
536
+ if part[:type] == :erb
537
+ ##============================================================##
538
+ ## ERB part: wrap in interpolation, no escaping needed
539
+ ##============================================================##
540
+ "\#{#{part[:value]}}"
541
+ else
542
+ ##============================================================##
543
+ ## Static part: escape double quotes
544
+ ##============================================================##
545
+ part[:value].gsub('"', '\\"')
546
+ end
547
+ end.join
548
+
549
+ "\"#{result}\""
550
+ end
551
+
552
+ ##============================================================##
553
+ ## Quote a static string value for Ruby output
554
+ ## - Uses double quotes by default
555
+ ## - Switches to single quotes if value contains double quotes
556
+ ##============================================================##
557
+ def quote_static_string(value)
558
+ if value.include?('"')
475
559
  "'#{value}'"
476
560
  else
477
561
  "\"#{value}\""
@@ -497,8 +581,10 @@ module ERBLint
497
581
  if node.is_a?(Prism::IfNode) || node.is_a?(Prism::UnlessNode)
498
582
  ##============================================================##
499
583
  ## Only handle modifier form (no else, single statement body)
584
+ ## Use subsequent for IfNode, else_clause for UnlessNode
500
585
  ##============================================================##
501
- if node.consequent.nil? && node.statements&.body&.size == 1
586
+ else_branch = node.is_a?(Prism::IfNode) ? node.subsequent : node.else_clause
587
+ if else_branch.nil? && node.statements&.body&.size == 1
502
588
  keyword = node.is_a?(Prism::IfNode) ? "if" : "unless"
503
589
  condition = node.predicate.slice
504
590
  content = node.statements.body.first.slice
@@ -87,6 +87,18 @@ const groupConsecutiveComments = (comments, originalLines) => {
87
87
  return
88
88
  }
89
89
 
90
+ //============================================================//
91
+ // Skip Sprockets directives (//= link, //= require, etc.)
92
+ //============================================================//
93
+ if (comment.value.startsWith("=")) {
94
+ if (currentBlock.length > 0) {
95
+ blocks.push(currentBlock)
96
+ currentBlock = []
97
+ }
98
+ lastLine = -2
99
+ return
100
+ }
101
+
90
102
  //============================================================//
91
103
  // Check if this is a standalone comment (line starts with //)
92
104
  // Skip end-of-line comments (e.g., const x = 1 // comment)
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.93
4
+ version: 0.1.95
5
5
  platform: ruby
6
6
  authors:
7
7
  - immosquare