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 +4 -4
- data/lib/immosquare-cleaner/version.rb +1 -1
- data/linters/erb_lint/custom_html_to_content_tag.rb +157 -8
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2af51f9a79564d3c4e797a60d559c8101baffafcb7653e53c527ea8cdb55544c
|
|
4
|
+
data.tar.gz: 5cb8a8cc680641237fb96511a543d9c4a191b16c611e5058a3082ebb82961425
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fc266e0b300ccf012802b850895a5690e5555df1eddc41f17f333c5a80ef90fb33256d22e72fb3184e99feb931416fd3fa18d18a21b687508ac519b40415ae2
|
|
7
|
+
data.tar.gz: 384648e167d646ecb0b70cc228d6b47feba045e9a8bc9ec4b209fcc3101fba86716bacaf89eb684ceb82491a1e172b885123bc3fd5c40e2148101e9d2a421c2e
|
|
@@ -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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
##============================================================##
|