immosquare-cleaner 0.1.90 → 0.1.92

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: '039c6af561debfbe979defac9f7e31b82033b57eb9c7f994d56dd76ec80bd9e5'
4
- data.tar.gz: ce34414e29d4e42626f552efc41a74696e8821a03f693e1d801cf239a83d5742
3
+ metadata.gz: 2af51f9a79564d3c4e797a60d559c8101baffafcb7653e53c527ea8cdb55544c
4
+ data.tar.gz: 5cb8a8cc680641237fb96511a543d9c4a191b16c611e5058a3082ebb82961425
5
5
  SHA512:
6
- metadata.gz: ed73bf257cc143d1017a67d0d775f454e5a9811bf3b75d850b5814e384ef099541d20bdda963bd9aa7ec1aed4fef9ed2bbf3ee6701ecbb00520dc513013410ca
7
- data.tar.gz: 00b7da8708f36b7e85d617140f1145c98707c7911a8f049b28bc03b5cbeef48c9e70b66d9d3c0d686808a9175a18956216bcceb664a74d8779a4b5c51396b2be
6
+ metadata.gz: 9fc266e0b300ccf012802b850895a5690e5555df1eddc41f17f333c5a80ef90fb33256d22e72fb3184e99feb931416fd3fa18d18a21b687508ac519b40415ae2
7
+ data.tar.gz: 384648e167d646ecb0b70cc228d6b47feba045e9a8bc9ec4b209fcc3101fba86716bacaf89eb684ceb82491a1e172b885123bc3fd5c40e2148101e9d2a421c2e
@@ -1,3 +1,3 @@
1
1
  module ImmosquareCleaner
2
- VERSION = "0.1.90".freeze
2
+ VERSION = "0.1.92".freeze
3
3
  end
@@ -59,6 +59,8 @@ module ERBLint
59
59
  ## - content_tag(:div, x) → allow (user-written)
60
60
  ##============================================================##
61
61
  EXCLUDED_METHODS = [
62
+ "yield",
63
+ "render_to_string",
62
64
  "render",
63
65
  "content_tag",
64
66
  "image_tag",
@@ -343,23 +345,170 @@ module ERBLint
343
345
 
344
346
  ##============================================================##
345
347
  ## Build content_tag call
348
+ ## Handles if/unless modifiers by moving them outside content_tag
346
349
  ##============================================================##
347
350
  def build_content_tag(tag_name, content, attributes)
351
+ ##============================================================##
352
+ ## Extract modifier (if/unless) from content if present
353
+ ##============================================================##
354
+ content_part, modifier_part = extract_modifier(content)
355
+
356
+ ##============================================================##
357
+ ## Wrap content in parentheses if it's an ambiguous method call
358
+ ## (method call without parentheses that has arguments)
359
+ ##============================================================##
360
+ wrapped_content = needs_parentheses?(content_part) ? "(#{content_part})" : content_part
361
+
362
+ ##============================================================##
363
+ ## Format tag name as symbol or string depending on characters
364
+ ##============================================================##
365
+ formatted_tag = format_tag_name(tag_name)
366
+
348
367
  if attributes.empty?
349
- "<%= content_tag(:#{tag_name}, #{content}) %>"
368
+ base = "content_tag(#{formatted_tag}, #{wrapped_content})"
350
369
  else
351
370
  attrs_str = attributes.map do |name, value|
352
371
  key = normalize_attribute_name(name)
353
- ##============================================================##
354
- ## Check if value contains interpolation
355
- ##============================================================##
356
- if value&.include?('#{')
357
- end
358
- "#{key} => \"#{value}\""
372
+ quoted_value = quote_attribute_value(value)
373
+ "#{key} => #{quoted_value}"
359
374
  end.join(", ")
360
375
 
361
- "<%= content_tag(:#{tag_name}, #{content}, #{attrs_str}) %>"
376
+ base = "content_tag(#{formatted_tag}, #{wrapped_content}, #{attrs_str})"
362
377
  end
378
+
379
+ if modifier_part
380
+ "<%= #{base} #{modifier_part} %>"
381
+ else
382
+ "<%= #{base} %>"
383
+ end
384
+ end
385
+
386
+ ##============================================================##
387
+ ## Format tag name for content_tag
388
+ ## - Simple tags (div, span) -> :div, :span
389
+ ## - Tags with special chars (x-card) -> "x-card"
390
+ ##============================================================##
391
+ def format_tag_name(tag_name)
392
+ if tag_name.match?(/\A[a-z_][a-z0-9_]*\z/i)
393
+ ":#{tag_name}"
394
+ else
395
+ "\"#{tag_name}\""
396
+ end
397
+ end
398
+
399
+ ##============================================================##
400
+ ## Check if Ruby code needs parentheses when used as argument
401
+ ## Returns true for method calls without parentheses that have
402
+ ## arguments (which would be ambiguous in content_tag context)
403
+ ## Example: "tag t('x'), path, :class => 'y'" needs parentheses
404
+ ##============================================================##
405
+ def needs_parentheses?(erb_code)
406
+ return false unless erb_code
407
+
408
+ result = Prism.parse(erb_code)
409
+ return false unless result.success?
410
+
411
+ node = result.value.statements.body.first
412
+ return false unless node.is_a?(Prism::CallNode)
413
+
414
+ ##============================================================##
415
+ ## Check if it's a method call with arguments but no parentheses
416
+ ##============================================================##
417
+ has_arguments = node.arguments&.arguments&.any?
418
+ return false unless has_arguments
419
+
420
+ ##============================================================##
421
+ ## Check if parentheses are missing by looking at the source
422
+ ## If the method name is not immediately followed by '(', it
423
+ ## needs wrapping
424
+ ##============================================================##
425
+ method_name = node.name.to_s
426
+ source = erb_code.strip
427
+
428
+ ##============================================================##
429
+ ## Find position after method name (accounting for receiver)
430
+ ##============================================================##
431
+ if node.receiver
432
+ ##============================================================##
433
+ ## For calls like "tag.div" or "f.input", find the method call
434
+ ##============================================================##
435
+ call_loc = node.call_operator_loc || node.message_loc
436
+ return false unless call_loc
437
+
438
+ after_method = call_loc.end_offset + method_name.length
439
+ else
440
+ after_method = method_name.length
441
+ end
442
+
443
+ ##============================================================##
444
+ ## Check if character after method name is '('
445
+ ##============================================================##
446
+ return false if after_method >= source.length
447
+
448
+ source[after_method] != "("
449
+ rescue StandardError
450
+ false
451
+ end
452
+
453
+ ##============================================================##
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
458
+ ##============================================================##
459
+ def quote_attribute_value(value)
460
+ return '""' if value.nil? || value.empty?
461
+
462
+ has_interpolation = value.include?('#{')
463
+ has_double_quotes = value.include?('"')
464
+
465
+ if has_interpolation
466
+ ##============================================================##
467
+ ## Must use double quotes for interpolation, escape inner quotes
468
+ ##============================================================##
469
+ escaped = value.gsub('"', '\\"')
470
+ "\"#{escaped}\""
471
+ elsif has_double_quotes
472
+ ##============================================================##
473
+ ## Use single quotes to avoid escaping
474
+ ##============================================================##
475
+ "'#{value}'"
476
+ else
477
+ "\"#{value}\""
478
+ end
479
+ end
480
+
481
+ ##============================================================##
482
+ ## Extract if/unless modifier from Ruby code
483
+ ## Returns [content, modifier] or [content, nil]
484
+ ## Example: "foo if bar" => ["foo", "if bar"]
485
+ ##============================================================##
486
+ def extract_modifier(erb_code)
487
+ return [erb_code, nil] unless erb_code
488
+
489
+ result = Prism.parse(erb_code)
490
+ return [erb_code, nil] unless result.success?
491
+
492
+ node = result.value.statements.body.first
493
+
494
+ ##============================================================##
495
+ ## Check for if/unless modifier
496
+ ##============================================================##
497
+ if node.is_a?(Prism::IfNode) || node.is_a?(Prism::UnlessNode)
498
+ ##============================================================##
499
+ ## Only handle modifier form (no else, single statement body)
500
+ ##============================================================##
501
+ if node.consequent.nil? && node.statements&.body&.size == 1
502
+ keyword = node.is_a?(Prism::IfNode) ? "if" : "unless"
503
+ condition = node.predicate.slice
504
+ content = node.statements.body.first.slice
505
+ return [content, "#{keyword} #{condition}"]
506
+ end
507
+ end
508
+
509
+ [erb_code, nil]
510
+ rescue StandardError
511
+ [erb_code, nil]
363
512
  end
364
513
 
365
514
  ##============================================================##
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.90
4
+ version: 0.1.92
5
5
  platform: ruby
6
6
  authors:
7
7
  - immosquare