primer_view_components 0.0.49 → 0.0.53

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +158 -0
  3. data/app/components/primer/base_component.rb +2 -2
  4. data/app/components/primer/beta/avatar_stack.rb +9 -9
  5. data/app/components/primer/beta/truncate.html.erb +5 -0
  6. data/app/components/primer/beta/truncate.rb +110 -0
  7. data/app/components/primer/border_box_component.rb +27 -1
  8. data/app/components/primer/clipboard_copy.html.erb +2 -2
  9. data/app/components/primer/clipboard_copy.rb +1 -1
  10. data/app/components/primer/dropdown.rb +7 -7
  11. data/app/components/primer/icon_button.rb +1 -1
  12. data/app/components/primer/label_component.rb +13 -12
  13. data/app/components/primer/navigation/tab_component.rb +1 -1
  14. data/app/components/primer/progress_bar_component.rb +0 -3
  15. data/app/components/primer/tab_nav_component.rb +1 -1
  16. data/app/lib/primer/fetch_or_fallback_helper.rb +2 -0
  17. data/app/lib/primer/octicon/cache.rb +1 -1
  18. data/app/lib/primer/tabbed_component_helper.rb +1 -1
  19. data/app/lib/primer/view_helper.rb +1 -0
  20. data/lib/primer/classify/cache.rb +0 -5
  21. data/lib/primer/classify/flex.rb +1 -1
  22. data/lib/primer/classify/functional_colors.rb +1 -1
  23. data/lib/primer/classify/utilities.rb +19 -2
  24. data/lib/primer/classify/utilities.yml +16 -0
  25. data/lib/primer/classify/validation.rb +18 -0
  26. data/lib/primer/classify.rb +4 -18
  27. data/lib/primer/view_components/constants.rb +1 -1
  28. data/lib/primer/view_components/linters/argument_mappers/base.rb +63 -2
  29. data/lib/primer/view_components/linters/argument_mappers/button.rb +7 -11
  30. data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +2 -6
  31. data/lib/primer/view_components/linters/argument_mappers/close_button.rb +43 -0
  32. data/lib/primer/view_components/linters/argument_mappers/flash.rb +32 -0
  33. data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +67 -0
  34. data/lib/primer/view_components/linters/argument_mappers/label.rb +5 -12
  35. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +6 -5
  36. data/lib/primer/view_components/linters/autocorrectable.rb +6 -4
  37. data/lib/primer/view_components/linters/{helpers.rb → base_linter.rb} +69 -29
  38. data/lib/primer/view_components/linters/button_component_migration_counter.rb +4 -3
  39. data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +3 -4
  40. data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +123 -0
  41. data/lib/primer/view_components/linters/flash_component_migration_counter.rb +18 -3
  42. data/lib/primer/view_components/linters/label_component_migration_counter.rb +2 -3
  43. data/lib/primer/view_components/version.rb +1 -1
  44. data/lib/rubocop/config/default.yml +5 -0
  45. data/lib/rubocop/cop/primer/deprecated_arguments.rb +173 -0
  46. data/lib/rubocop/cop/primer/no_tag_memoize.rb +1 -0
  47. data/lib/rubocop/cop/primer/primer_octicon.rb +178 -0
  48. data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +12 -16
  49. data/lib/rubocop/cop/primer.rb +1 -2
  50. data/lib/tasks/coverage.rake +4 -0
  51. data/lib/tasks/docs.rake +3 -2
  52. data/lib/tasks/utilities.rake +7 -3
  53. data/lib/yard/docs_helper.rb +6 -3
  54. data/static/arguments.yml +7 -4
  55. data/static/classes.yml +8 -0
  56. data/static/constants.json +13 -1
  57. data/static/statuses.json +3 -1
  58. metadata +32 -9
@@ -1,18 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "helpers"
3
+ require_relative "base_linter"
4
4
  require_relative "autocorrectable"
5
5
  require_relative "argument_mappers/clipboard_copy"
6
6
 
7
7
  module ERBLint
8
8
  module Linters
9
9
  # Counts the number of times a HTML clipboard-copy is used instead of the component.
10
- class ClipboardCopyComponentMigrationCounter < Linter
11
- include Helpers
10
+ class ClipboardCopyComponentMigrationCounter < BaseLinter
12
11
  include Autocorrectable
13
12
 
14
13
  TAGS = %w[clipboard-copy].freeze
15
- CLASSES = %w[].freeze
14
+ REQUIRED_ARGUMENTS = [/for|value/, "aria-label"].freeze
16
15
  MESSAGE = "We are migrating clipboard-copy to use [Primer::ClipboardCopy](https://primer.style/view-components/components/clipboardcopy), please try to use that instead of raw HTML."
17
16
  ARGUMENT_MAPPER = ArgumentMappers::ClipboardCopy
18
17
  COMPONENT = "Primer::ClipboardCopy"
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_linter"
4
+ require_relative "autocorrectable"
5
+ require_relative "argument_mappers/close_button"
6
+
7
+ module ERBLint
8
+ module Linters
9
+ # Counts the number of times a HTML clipboard-copy is used instead of the component.
10
+ class CloseButtonComponentMigrationCounter < BaseLinter
11
+ include Autocorrectable
12
+
13
+ TAGS = %w[button].freeze
14
+ CLASSES = %w[close-button].freeze
15
+ MESSAGE = "We are migrating close-button to use [Primer::CloseButton](https://primer.style/view-components/components/closebutton), please try to use that instead of raw HTML."
16
+ ARGUMENT_MAPPER = ArgumentMappers::CloseButton
17
+ COMPONENT = "Primer::CloseButton"
18
+
19
+ ALLOWED_OCTICON_ARGS = %w[icon aria-label aria].freeze
20
+
21
+ private
22
+
23
+ def map_arguments(tag, tag_tree)
24
+ # We can only autocorrect cases where the tag only has an octicon as content.
25
+ return if tag_tree[:children].size != 1
26
+
27
+ nodes = tag_tree[:children].first.children
28
+ erb_nodes = nodes.select { |node| node.try(:type) == :erb }
29
+
30
+ # Don't correct if there are multiple ERB nodes.
31
+ return if erb_nodes.size != 1
32
+
33
+ _, _, code_node = *erb_nodes.first
34
+ code = code_node.children.first.strip
35
+ ast = erb_ast(code)
36
+
37
+ # We'll only autocorrect cases where the only content is an octicon.
38
+ if ast.method_name == :primer_octicon || ast.method_name == :octicon
39
+ octicon_kwargs = ast.arguments[1]
40
+ icon = icon(ast.arguments)
41
+ elsif ast.method_name == :render && code.include?("Primer::OcticonComponent")
42
+ octicon_kwargs = ast.arguments.first.arguments.last
43
+ icon = icon(ast.arguments.first.arguments)
44
+ else
45
+ return
46
+ end
47
+
48
+ # Don't autocorrect if using a custom icon
49
+ return unless icon == :x
50
+ # Don't autocorrect if the octicon has custom arguments
51
+ return if custom_attributes?(octicon_kwargs)
52
+
53
+ octicon_aria_label = aria_label_from_octicon(octicon_kwargs)
54
+ tag_aria_label = tag.attributes.each.find { |a| a.name == "aria-label" }
55
+
56
+ # Can't autocorrect if there is no aria-label.
57
+ return if octicon_aria_label.blank? && tag_aria_label.blank?
58
+
59
+ args = ARGUMENT_MAPPER.new(tag).to_s
60
+
61
+ # Argument mapper will add the `aria-label` if the tag has it.
62
+ return args if tag_aria_label.present?
63
+
64
+ aria_label_arg = "\"aria-label\": #{octicon_aria_label}"
65
+
66
+ return aria_label_arg if args.blank?
67
+
68
+ "#{args}, #{aria_label_arg}"
69
+ rescue ArgumentMappers::ConversionError
70
+ nil
71
+ end
72
+
73
+ # Overriding the basic correction since this component does not uses content blocks.
74
+ def correction(args)
75
+ return if args.nil?
76
+
77
+ correction = "<%= render #{self.class::COMPONENT}.new"
78
+ correction += "(#{args})" if args.present?
79
+ "#{correction} %>"
80
+ end
81
+
82
+ # Overriding the basic correction since this component will rewrite the whole tag block.
83
+ def add_correction(tag, tag_tree)
84
+ offense_loc = tag.loc.with(end_pos: tag_tree[:closing].loc.to_range.last)
85
+ add_offense(offense_loc, tag_tree[:message], tag_tree[:correction])
86
+ end
87
+
88
+ # Extracts the aria-label value from the octicon kwargs.
89
+ # It can either be in `"aria-label": "value"`` or `aria: { label: "value" } }`.
90
+ def aria_label_from_octicon(kwargs)
91
+ return if kwargs.blank? || kwargs.type != :hash || kwargs.pairs.blank?
92
+
93
+ aria_label = kwargs.pairs.find { |x| x.key.value == :"aria-label" }
94
+
95
+ return aria_label.value.source if aria_label
96
+
97
+ aria_hash = kwargs.pairs.find { |x| x.key.value == :aria }
98
+
99
+ return if aria_hash.blank?
100
+
101
+ aria_label = aria_hash.value.pairs.find { |x| x.key.value == :label }
102
+
103
+ aria_label&.value&.source
104
+ end
105
+
106
+ def custom_attributes?(kwargs)
107
+ return false if kwargs.blank? || kwargs.type != :hash || kwargs.pairs.blank?
108
+
109
+ (kwargs.keys.map { |key| key.value.to_s } - ALLOWED_OCTICON_ARGS).present?
110
+ end
111
+
112
+ def erb_ast(code)
113
+ RuboCop::AST::ProcessedSource.new(code, RUBY_VERSION.to_f).ast
114
+ end
115
+
116
+ def icon(args)
117
+ return args.first.value.to_sym if args.first.type == :sym || args.first.type == :str
118
+
119
+ args.last.pairs.find { |x| x.key.value == :icon }.value.value.to_sym
120
+ end
121
+ end
122
+ end
123
+ end
@@ -1,16 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "helpers"
3
+ require_relative "base_linter"
4
+ require_relative "autocorrectable"
5
+ require_relative "argument_mappers/flash"
4
6
 
5
7
  module ERBLint
6
8
  module Linters
7
9
  # Counts the number of times a HTML flash is used instead of the component.
8
- class FlashComponentMigrationCounter < Linter
9
- include Helpers
10
+ class FlashComponentMigrationCounter < BaseLinter
11
+ include Autocorrectable
10
12
 
11
13
  TAGS = %w[div].freeze
12
14
  CLASSES = %w[flash].freeze
13
15
  MESSAGE = "We are migrating flashes to use [Primer::FlashComponent](https://primer.style/view-components/components/flash), please try to use that instead of raw HTML."
16
+ ARGUMENT_MAPPER = ArgumentMappers::Flash
17
+ COMPONENT = "Primer::FlashComponent"
18
+
19
+ def map_arguments(tag, tag_tree)
20
+ # We can only autocorrect elements with simple text as content.
21
+ return nil if tag_tree[:children].size != 1
22
+ # Hash children indicates that there are tags in the content.
23
+ return nil if tag_tree[:children].first.is_a?(Hash)
24
+
25
+ ARGUMENT_MAPPER.new(tag).to_s
26
+ rescue ArgumentMappers::ConversionError
27
+ nil
28
+ end
14
29
  end
15
30
  end
16
31
  end
@@ -1,14 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "helpers"
3
+ require_relative "base_linter"
4
4
  require_relative "autocorrectable"
5
5
  require_relative "argument_mappers/label"
6
6
 
7
7
  module ERBLint
8
8
  module Linters
9
9
  # Counts the number of times a HTML label is used instead of the component.
10
- class LabelComponentMigrationCounter < Linter
11
- include Helpers
10
+ class LabelComponentMigrationCounter < BaseLinter
12
11
  include Autocorrectable
13
12
 
14
13
  TAGS = Primer::ViewComponents::Constants.get(
@@ -5,7 +5,7 @@ module Primer
5
5
  module VERSION
6
6
  MAJOR = 0
7
7
  MINOR = 0
8
- PATCH = 49
8
+ PATCH = 53
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
11
11
  end
@@ -10,3 +10,8 @@ Primer/SystemArgumentInsteadOfClass:
10
10
  Primer/NoTagMemoize:
11
11
  Enabled: false
12
12
 
13
+ Primer/PrimerOcticon:
14
+ Enabled: true
15
+
16
+ Primer/DeprecatedArguments:
17
+ Enabled: true
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+ require "primer/view_components/statuses"
5
+ require_relative "../../../../app/lib/primer/view_helper"
6
+
7
+ # :nocov:
8
+ module RuboCop
9
+ module Cop
10
+ module Primer
11
+ # This cop ensures that components don't use deprecated arguments
12
+ #
13
+ # bad
14
+ # Component.new(foo: :deprecated)
15
+ #
16
+ # good
17
+ # Component.new(foo: :bar)
18
+ class DeprecatedArguments < RuboCop::Cop::Cop
19
+ INVALID_MESSAGE = <<~STR
20
+ Avoid using deprecated arguments: https://primer.style/view-components/deprecated.
21
+ STR
22
+
23
+ # This is a hash of deprecated arguments and their replacements.
24
+ #
25
+ # * The top level key is the argument.
26
+ # * The second level key is the value.
27
+ # * The seceond level value is a string of the full replacement. e.g. "new_argument: :new_value"
28
+ # If the value is nil, then there is no replacement.
29
+ #
30
+ # e.g.
31
+ # DEPRECATED = {
32
+ # argument: {
33
+ # value: "new_argument: :new_value"
34
+ # }
35
+ # }
36
+ #
37
+ DEPRECATED = {
38
+ color: {
39
+ blue: "color: :text_link",
40
+ gray_dark: "color: :text_primary",
41
+ gray: "color: :text_secondary",
42
+ gray_light: "color: :text_tertiary",
43
+ green: "color: :text_success",
44
+ yellow: "color: :text_warning",
45
+ red: "color: :text_danger",
46
+ gray_0: nil,
47
+ gray_1: nil,
48
+ gray_2: nil,
49
+ gray_3: nil,
50
+ gray_4: nil,
51
+ gray_5: nil,
52
+ gray_6: nil,
53
+ gray_7: nil,
54
+ gray_8: nil,
55
+ gray_9: nil,
56
+ blue_0: nil,
57
+ blue_1: nil,
58
+ blue_2: nil,
59
+ blue_3: nil,
60
+ blue_4: nil,
61
+ blue_5: nil,
62
+ blue_6: nil,
63
+ blue_7: nil,
64
+ blue_8: nil,
65
+ blue_9: nil,
66
+ green_0: nil,
67
+ green_1: nil,
68
+ green_2: nil,
69
+ green_3: nil,
70
+ green_4: nil,
71
+ green_5: nil,
72
+ green_6: nil,
73
+ green_7: nil,
74
+ green_8: nil,
75
+ green_9: nil,
76
+ yellow_0: nil,
77
+ yellow_1: nil,
78
+ yellow_2: nil,
79
+ yellow_3: nil,
80
+ yellow_4: nil,
81
+ yellow_5: nil,
82
+ yellow_6: nil,
83
+ yellow_7: nil,
84
+ yellow_8: nil,
85
+ yellow_9: nil,
86
+ red_0: nil,
87
+ red_1: nil,
88
+ red_2: nil,
89
+ red_3: nil,
90
+ red_4: nil,
91
+ red_5: nil,
92
+ red_6: nil,
93
+ red_7: nil,
94
+ red_8: nil,
95
+ red_9: nil,
96
+ purple_0: nil,
97
+ purple_1: nil,
98
+ purple_2: nil,
99
+ purple_3: nil,
100
+ purple_4: nil,
101
+ purple_5: nil,
102
+ purple_6: nil,
103
+ purple_7: nil,
104
+ purple_8: nil,
105
+ purple_9: nil,
106
+ pink_0: nil,
107
+ pink_1: nil,
108
+ pink_2: nil,
109
+ pink_3: nil,
110
+ pink_4: nil,
111
+ pink_5: nil,
112
+ pink_6: nil,
113
+ pink_7: nil,
114
+ pink_8: nil,
115
+ pink_9: nil,
116
+ orange_0: nil,
117
+ orange_1: nil,
118
+ orange_2: nil,
119
+ orange_3: nil,
120
+ orange_4: nil,
121
+ orange_5: nil,
122
+ orange_6: nil,
123
+ orange_7: nil,
124
+ orange_8: nil,
125
+ orange_9: nil
126
+ }
127
+ }.freeze
128
+
129
+ def on_send(node)
130
+ return unless valid_node?(node)
131
+ return unless node.arguments?
132
+
133
+ # we are looking for hash arguments and they are always last
134
+ kwargs = node.arguments.last
135
+
136
+ return unless kwargs.type == :hash
137
+
138
+ kwargs.pairs.each do |pair|
139
+ # Skip if we're not dealing with a symbol
140
+ next if pair.key.type != :sym
141
+ next unless pair.value.type == :sym || pair.value.type == :str
142
+
143
+ key = pair.key.value
144
+ value = pair.value.value.to_sym
145
+
146
+ next unless DEPRECATED.key?(key) && DEPRECATED[key].key?(value)
147
+
148
+ add_offense(pair, message: INVALID_MESSAGE)
149
+ end
150
+ end
151
+
152
+ def autocorrect(node)
153
+ lambda do |corrector|
154
+ replacement = DEPRECATED[node.key.value][node.value.value.to_sym]
155
+ corrector.replace(node, replacement) if replacement.present?
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ # We only verify SystemArguments if it's a `.new` call on a component or
162
+ # a ViewHleper call.
163
+ def valid_node?(node)
164
+ view_helpers.include?(node.method_name) || (node.method_name == :new && ::Primer::ViewComponents::STATUSES.key?(node.receiver.const_name))
165
+ end
166
+
167
+ def view_helpers
168
+ ::Primer::ViewHelper::HELPERS.keys.map { |key| "primer_#{key}".to_sym }
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "rubocop"
4
4
 
5
+ # :nocov:
5
6
  module RuboCop
6
7
  module Cop
7
8
  module Primer
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop"
4
+ require "primer/classify/utilities"
5
+ require "primer/classify/validation"
6
+
7
+ # :nocov:
8
+ module RuboCop
9
+ module Cop
10
+ module Primer
11
+ # This cop ensures that components use System Arguments instead of CSS classes.
12
+ #
13
+ # bad
14
+ # octicon(:icon)
15
+ # octicon("icon")
16
+ # octicon("icon-with-daashes")
17
+ # octicon(@ivar)
18
+ # octicon(condition > "icon" : "other-icon")
19
+ #
20
+ # good
21
+ # primer_octicon(:icon)
22
+ # primer_octicon(:"icon-with-daashes")
23
+ # primer_octicon(@ivar)
24
+ # primer_octicon(condition > "icon" : "other-icon")
25
+ class PrimerOcticon < RuboCop::Cop::Cop
26
+ INVALID_MESSAGE = <<~STR
27
+ Replace the octicon helper with primer_octicon. See https://primer.style/view-components/components/octicon for details.
28
+ STR
29
+
30
+ SIZE_ATTRIBUTES = %w[height width size].freeze
31
+ STRING_ATTRIBUTES = %w[aria- data-].freeze
32
+ VALID_ATTRIBUTES = [*SIZE_ATTRIBUTES, *STRING_ATTRIBUTES, "class"].freeze
33
+
34
+ STRING_ATTRIBUTE_REGEX = Regexp.union(STRING_ATTRIBUTES).freeze
35
+ ATTRIBUTE_REGEX = Regexp.union(VALID_ATTRIBUTES).freeze
36
+ INVALID_ATTRIBUTE = -1
37
+
38
+ def on_send(node)
39
+ return unless node.method_name == :octicon
40
+ return unless node.arguments?
41
+
42
+ kwargs = kwargs(node)
43
+
44
+ return unless kwargs.type == :hash
45
+
46
+ attributes = kwargs.keys.map(&:value)
47
+
48
+ # Don't convert unknown attributes
49
+ return unless attributes.all? { |attribute| attribute.match?(ATTRIBUTE_REGEX) }
50
+ # Can't convert size
51
+ return if octicon_size_attributes(kwargs) == INVALID_ATTRIBUTE
52
+
53
+ # find class pair
54
+ classes = classes(kwargs)
55
+
56
+ return if classes == INVALID_ATTRIBUTE
57
+
58
+ # check if classes are convertible
59
+ if classes.present?
60
+ system_arguments = ::Primer::Classify::Utilities.classes_to_hash(classes)
61
+ invalid_classes = (system_arguments[:classes]&.split(" ") || []).select { |class_name| ::Primer::Classify::Validation.invalid?(class_name) }
62
+
63
+ # Uses system argument that can't be converted
64
+ return if invalid_classes.present?
65
+ end
66
+
67
+ add_offense(node, message: INVALID_MESSAGE)
68
+ end
69
+
70
+ def autocorrect(node)
71
+ lambda do |corrector|
72
+ kwargs = kwargs(node)
73
+
74
+ # Converting arguments for the component
75
+ classes = classes(kwargs)
76
+ size_attributes = transform_sizes(kwargs)
77
+ args = arguments_as_string(node, size_attributes, classes)
78
+
79
+ corrector.replace(node.loc.expression, "primer_octicon(#{args})")
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def transform_sizes(kwargs)
86
+ attributes = octicon_size_attributes(kwargs)
87
+
88
+ attributes.transform_values do |size|
89
+ if size.between?(10, 16)
90
+ ""
91
+ elsif size.between?(22, 26)
92
+ ":medium"
93
+ else
94
+ size
95
+ end
96
+ end
97
+ end
98
+
99
+ def octicon_size_attributes(kwargs)
100
+ kwargs.pairs.each_with_object({}) do |pair, h|
101
+ next unless SIZE_ATTRIBUTES.include?(pair.key.value.to_s)
102
+
103
+ # We only support string or int values.
104
+ case pair.value.type
105
+ when :int
106
+ h[pair.key.value] = pair.value.source.to_i
107
+ when :str
108
+ h[pair.key.value] = pair.value.value.to_i
109
+ else
110
+ return INVALID_ATTRIBUTE
111
+ end
112
+ end
113
+ end
114
+
115
+ def classes(kwargs)
116
+ # find class pair
117
+ class_arg = kwargs.pairs.find { |kwarg| kwarg.key.value == :class }
118
+
119
+ return if class_arg.blank?
120
+ return INVALID_ATTRIBUTE unless class_arg.value.type == :str
121
+
122
+ class_arg.value.value
123
+ end
124
+
125
+ def arguments_as_string(node, size_attributes, classes)
126
+ args = icon(node.arguments.first)
127
+ size_args = size_attributes_to_string(size_attributes)
128
+ string_args = string_args_to_string(node)
129
+
130
+ args = "#{args}, #{size_attributes_to_string(size_attributes)}" if size_args.present?
131
+ args = "#{args}, #{::Primer::Classify::Utilities.classes_to_args(classes)}" if classes.present?
132
+ args = "#{args}, #{string_args}" if string_args.present?
133
+
134
+ args
135
+ end
136
+
137
+ def size_attributes_to_string(size_attributes)
138
+ # No arguments if they map to the default size
139
+ return if size_attributes.blank? || size_attributes.values.all?(&:blank?)
140
+ # Return mapped argument to `size`
141
+ return "size: :medium" if size_attributes.values.any?(":medium")
142
+
143
+ size_attributes.map do |key, value|
144
+ "#{key}: #{value}"
145
+ end.join(", ")
146
+ end
147
+
148
+ def string_args_to_string(node)
149
+ kwargs = kwargs(node)
150
+
151
+ args = kwargs.pairs.each_with_object([]) do |pair, acc|
152
+ next unless pair.key.value.to_s.match?(STRING_ATTRIBUTE_REGEX)
153
+
154
+ key = pair.key.value.to_s == "data-test-selector" ? "test_selector" : "\"#{pair.key.value}\""
155
+ acc << "#{key}: #{pair.value.source}"
156
+ end
157
+
158
+ args.join(",")
159
+ end
160
+
161
+ def kwargs(node)
162
+ return node.arguments.last if node.arguments.size > 1
163
+
164
+ OpenStruct.new(keys: [], pairs: [], type: :hash)
165
+ end
166
+
167
+ def icon(node)
168
+ return node.source unless node.type == :str
169
+ return ":#{node.value}" unless node.value.include?("-")
170
+
171
+ # If the icon contains `-` we need to cast the string as a symbole
172
+ # E.g: `arrow-down` becomes `:"arrow-down"`
173
+ ":#{node.source}"
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end