primer_view_components 0.0.51 → 0.0.55

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +159 -0
  3. data/app/components/primer/alpha/tab_nav.html.erb +11 -0
  4. data/app/components/primer/alpha/tab_nav.rb +130 -0
  5. data/app/components/primer/{tab_nav_component.html.erb → alpha/tab_panels.html.erb} +3 -8
  6. data/app/components/primer/alpha/tab_panels.rb +82 -0
  7. data/app/components/primer/alpha/underline_nav.html.erb +15 -0
  8. data/app/components/primer/alpha/underline_nav.rb +137 -0
  9. data/app/components/primer/{underline_nav_component.html.erb → alpha/underline_panels.html.erb} +3 -8
  10. data/app/components/primer/alpha/underline_panels.rb +86 -0
  11. data/app/components/primer/base_component.rb +1 -1
  12. data/app/components/primer/beta/avatar_stack.rb +9 -9
  13. data/app/components/primer/{breadcrumb_component.html.erb → beta/breadcrumbs.html.erb} +2 -1
  14. data/app/components/primer/beta/breadcrumbs.rb +61 -0
  15. data/app/components/primer/beta/truncate.html.erb +5 -0
  16. data/app/components/primer/beta/truncate.rb +110 -0
  17. data/app/components/primer/border_box_component.rb +27 -1
  18. data/app/components/primer/clipboard_copy.rb +1 -1
  19. data/app/components/primer/dropdown.rb +7 -7
  20. data/app/components/primer/icon_button.rb +1 -1
  21. data/app/components/primer/navigation/tab_component.rb +8 -6
  22. data/app/components/primer/octicon_component.rb +6 -1
  23. data/app/components/primer/progress_bar_component.rb +0 -3
  24. data/app/components/primer/tab_container_component.rb +1 -1
  25. data/app/lib/primer/class_name_helper.rb +14 -13
  26. data/app/lib/primer/fetch_or_fallback_helper.rb +2 -0
  27. data/app/lib/primer/octicon/cache.rb +10 -2
  28. data/app/lib/primer/tab_nav_helper.rb +35 -0
  29. data/app/lib/primer/tabbed_component_helper.rb +5 -5
  30. data/app/lib/primer/underline_nav_helper.rb +44 -0
  31. data/app/lib/primer/view_helper.rb +1 -0
  32. data/lib/primer/classify/cache.rb +0 -6
  33. data/lib/primer/classify/flex.rb +1 -1
  34. data/lib/primer/classify/functional_colors.rb +1 -1
  35. data/lib/primer/classify/utilities.rb +17 -2
  36. data/lib/primer/classify/utilities.yml +35 -0
  37. data/lib/primer/classify/validation.rb +18 -0
  38. data/lib/primer/classify.rb +4 -13
  39. data/lib/primer/view_components/constants.rb +1 -1
  40. data/lib/primer/view_components/linters/argument_mappers/base.rb +34 -8
  41. data/lib/primer/view_components/linters/argument_mappers/button.rb +5 -6
  42. data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +4 -3
  43. data/lib/primer/view_components/linters/argument_mappers/close_button.rb +43 -0
  44. data/lib/primer/view_components/linters/argument_mappers/flash.rb +32 -0
  45. data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +48 -5
  46. data/lib/primer/view_components/linters/argument_mappers/label.rb +3 -4
  47. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +5 -7
  48. data/lib/primer/view_components/linters/autocorrectable.rb +6 -4
  49. data/lib/primer/view_components/linters/{helpers.rb → base_linter.rb} +69 -29
  50. data/lib/primer/view_components/linters/button_component_migration_counter.rb +4 -3
  51. data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +3 -4
  52. data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +110 -3
  53. data/lib/primer/view_components/linters/flash_component_migration_counter.rb +18 -3
  54. data/lib/primer/view_components/linters/label_component_migration_counter.rb +2 -3
  55. data/lib/primer/view_components/version.rb +1 -1
  56. data/lib/rubocop/config/default.yml +5 -0
  57. data/lib/rubocop/cop/primer/base_cop.rb +28 -0
  58. data/lib/rubocop/cop/primer/deprecated_arguments.rb +263 -0
  59. data/lib/rubocop/cop/primer/no_tag_memoize.rb +1 -0
  60. data/lib/rubocop/cop/primer/primer_octicon.rb +178 -0
  61. data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +4 -32
  62. data/lib/rubocop/cop/primer.rb +1 -2
  63. data/lib/tasks/coverage.rake +4 -0
  64. data/lib/tasks/docs.rake +10 -8
  65. data/lib/tasks/utilities.rake +7 -3
  66. data/lib/yard/docs_helper.rb +6 -3
  67. data/static/arguments.yml +82 -64
  68. data/static/classes.yml +10 -0
  69. data/static/constants.json +44 -30
  70. data/static/statuses.json +10 -6
  71. metadata +57 -18
  72. data/app/components/primer/auto_complete/auto_component.d.ts +0 -1
  73. data/app/components/primer/auto_complete/auto_component.js +0 -1
  74. data/app/components/primer/breadcrumb_component.rb +0 -57
  75. data/app/components/primer/tab_nav_component.rb +0 -151
  76. data/app/components/primer/underline_nav_component.rb +0 -187
  77. data/lib/primer/classify/functional_text_colors.rb +0 -64
@@ -27,12 +27,11 @@ module ERBLint
27
27
  ATTRIBUTES = %w[title].freeze
28
28
 
29
29
  def attribute_to_args(attribute)
30
- Helpers::ErbBlock.raise_if_erb_block(attribute)
31
- { title: attribute.value.to_json }
30
+ { title: erb_helper.convert(attribute) }
32
31
  end
33
32
 
34
33
  def classes_to_args(classes)
35
- classes.split(" ").each_with_object({}) do |class_name, acc|
34
+ classes.each_with_object({ classes: [] }) do |class_name, acc|
36
35
  next if class_name == "Label"
37
36
 
38
37
  if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
@@ -40,7 +39,7 @@ module ERBLint
40
39
  elsif VARIANT_MAPPINGS[class_name] && acc[:variant].nil?
41
40
  acc[:variant] = VARIANT_MAPPINGS[class_name]
42
41
  else
43
- raise ConversionError, "Cannot convert class \"#{class_name}\""
42
+ acc[:classes] << class_name
44
43
  end
45
44
  end
46
45
  end
@@ -11,9 +11,11 @@ module ERBLint
11
11
  STRING_PARAMETERS = %w[aria- data-].freeze
12
12
  TEST_SELECTOR_REGEX = /test_selector\((?<selector>.+)\)$/.freeze
13
13
 
14
- attr_reader :attribute
14
+ attr_reader :attribute, :erb_helper
15
+
15
16
  def initialize(attribute)
16
17
  @attribute = attribute
18
+ @erb_helper = Helpers::ErbBlock.new
17
19
  end
18
20
 
19
21
  def to_args
@@ -29,13 +31,9 @@ module ERBLint
29
31
 
30
32
  { test_selector: m[:selector].tr("'", '"') }
31
33
  elsif attr_name == "data-test-selector"
32
- Helpers::ErbBlock.raise_if_erb_block(attribute)
33
-
34
- { test_selector: attribute.value.to_json }
34
+ { test_selector: erb_helper.convert(attribute) }
35
35
  elsif attr_name.start_with?(*STRING_PARAMETERS)
36
- Helpers::ErbBlock.raise_if_erb_block(attribute)
37
-
38
- { "\"#{attr_name}\"" => attribute.value.to_json }
36
+ { "\"#{attr_name}\"" => erb_helper.convert(attribute) }
39
37
  else
40
38
  raise ConversionError, "Cannot convert attribute \"#{attr_name}\""
41
39
  end
@@ -4,9 +4,11 @@ require_relative "argument_mappers/conversion_error"
4
4
 
5
5
  module ERBLint
6
6
  module Linters
7
- # Helper methods for autocorrectable ERB linters.
7
+ # Provides the autocorrection functionality for the linter. Once included, you should define the following constants:
8
+ # * `ARGUMENT_MAPPER` - required - The class responsible for transforming classes and attributes into arguments for the component.
9
+ # * `COMPONENT` - required - The component name for the linter. It will be used to generate the correction.
8
10
  module Autocorrectable
9
- def map_arguments(tag)
11
+ def map_arguments(tag, _tag_tree)
10
12
  self.class::ARGUMENT_MAPPER.new(tag).to_s
11
13
  rescue ArgumentMappers::ConversionError
12
14
  nil
@@ -20,10 +22,10 @@ module ERBLint
20
22
  "#{correction} do %>"
21
23
  end
22
24
 
23
- def message(args)
25
+ def message(args, processed_source)
24
26
  return self.class::MESSAGE if args.nil?
25
27
 
26
- "#{self.class::MESSAGE}\n\nTry using:\n\n#{correction(args)}\n\nInstead of:\n"
28
+ "#{self.class::MESSAGE}\nTry using:\n\n#{correction(args)}\n\nYou can also run erblint in autocorrect mode:\n\nbundle exec erblint -a #{processed_source.filename}\n"
27
29
  end
28
30
  end
29
31
  end
@@ -4,10 +4,16 @@ require "json"
4
4
  require "openssl"
5
5
  require "primer/view_components/constants"
6
6
 
7
+ # :nocov:
8
+
7
9
  module ERBLint
8
10
  module Linters
9
- # Helper methods for linting ERB.
10
- module Helpers
11
+ # Provides the basic linter logic. When inherited, you should define:
12
+ # * `TAGS` - required - The HTML tags that the component supports. It will be used by the linter to match elements.
13
+ # * `MESSAGE` - required - The message shown when there's an offense.
14
+ # * `CLASSES` - optional - The CSS classes that the component needs. The linter will only match elements with one of those classes.
15
+ # * `REQUIRED_ARGUMENTS` - optional - A list of HTML attributes that are required by the component.
16
+ class BaseLinter < Linter
11
17
  # from https://github.com/Shopify/erb-lint/blob/6179ee2d9d681a6ec4dd02351a1e30eefa748d3d/lib/erb_lint/linters/self_closing_tag.rb
12
18
  SELF_CLOSING_TAGS = %w[
13
19
  area base br col command embed hr input keygen
@@ -15,33 +21,44 @@ module ERBLint
15
21
  ].freeze
16
22
 
17
23
  DUMP_FILE = ".erblint-counter-ignore.json"
24
+ DISALLOWED_CLASSES = [].freeze
25
+ CLASSES = [].freeze
26
+ REQUIRED_ARGUMENTS = [].freeze
27
+
28
+ class ConfigSchema < LinterConfig
29
+ property :override_ignores_if_correctable, accepts: [true, false], default: false, reader: :override_ignores_if_correctable?
30
+ end
18
31
 
19
- def self.included(base)
32
+ def self.inherited(base)
33
+ super
20
34
  base.include(ERBLint::LinterRegistry)
35
+ base.config_schema = ConfigSchema
21
36
  end
22
37
 
23
38
  def run(processed_source)
24
39
  @total_offenses = 0
25
40
  @offenses_not_corrected = 0
26
- tags = tags(processed_source)
27
- tag_tree = build_tag_tree(tags)
41
+ (tags, tag_tree) = build_tag_tree(processed_source)
28
42
 
29
43
  tags.each do |tag|
30
44
  next if tag.closing?
31
45
  next unless self.class::TAGS&.include?(tag.name)
32
46
 
33
47
  classes = tag.attributes["class"]&.value&.split(" ") || []
34
-
35
48
  tag_tree[tag][:offense] = false
36
49
 
50
+ next if (classes & self.class::DISALLOWED_CLASSES).any?
37
51
  next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
38
52
 
39
- args = map_arguments(tag)
53
+ args = map_arguments(tag, tag_tree[tag])
40
54
  correction = correction(args)
41
55
 
56
+ attributes = tag.attributes.each.map(&:name).join(" ")
57
+ matches_required_attributes = self.class::REQUIRED_ARGUMENTS.blank? || self.class::REQUIRED_ARGUMENTS.all? { |arg| attributes.match?(arg) }
58
+
42
59
  tag_tree[tag][:offense] = true
43
- tag_tree[tag][:correctable] = !correction.nil?
44
- tag_tree[tag][:message] = message(args)
60
+ tag_tree[tag][:correctable] = matches_required_attributes && !correction.nil?
61
+ tag_tree[tag][:message] = message(args, processed_source)
45
62
  tag_tree[tag][:correction] = correction
46
63
  end
47
64
 
@@ -51,8 +68,7 @@ module ERBLint
51
68
  @total_offenses += 1
52
69
  # We always fix the offenses using blocks. The closing tag corresponds to `<% end %>`.
53
70
  if h[:correctable]
54
- add_offense(tag.loc, h[:message], h[:correction])
55
- add_offense(h[:closing].loc, h[:message], "<% end %>")
71
+ add_correction(tag, h)
56
72
  else
57
73
  @offenses_not_corrected += 1
58
74
  generate_offense(self.class, processed_source, tag, h[:message])
@@ -78,11 +94,16 @@ module ERBLint
78
94
 
79
95
  private
80
96
 
97
+ def add_correction(tag, tag_tree)
98
+ add_offense(tag.loc, tag_tree[:message], tag_tree[:correction])
99
+ add_offense(tag_tree[:closing].loc, tag_tree[:message], "<% end %>")
100
+ end
101
+
81
102
  # Override this function to convert the HTML element attributes to argument for a component.
82
103
  #
83
104
  # @return [Hash] if possible to map all attributes to arguments.
84
105
  # @return [Nil] if cannot map to arguments.
85
- def map_arguments(_tag)
106
+ def map_arguments(_tag, _tag_tree)
86
107
  nil
87
108
  end
88
109
 
@@ -97,7 +118,7 @@ module ERBLint
97
118
  # Override this function to customize the linter message.
98
119
  #
99
120
  # @return [String] message to show on linter error.
100
- def message(_tag)
121
+ def message(_tag, _processed_source)
101
122
  self.class::MESSAGE
102
123
  end
103
124
 
@@ -118,31 +139,44 @@ module ERBLint
118
139
  # This assumes that the AST provided represents valid HTML, where each tag has a corresponding closing tag.
119
140
  # From the tags, we build a structured tree which represents the tag hierarchy.
120
141
  # With this, we are able to know where the tags start and end.
121
- def build_tag_tree(tags)
142
+ def build_tag_tree(processed_source)
143
+ nodes = processed_source.ast.children
122
144
  tag_tree = {}
145
+ tags = []
123
146
  current_opened_tag = nil
124
147
 
125
- tags.each do |tag|
126
- if tag.closing?
127
- if current_opened_tag && tag.name == current_opened_tag.name
128
- tag_tree[current_opened_tag][:closing] = tag
129
- current_opened_tag = tag_tree[current_opened_tag][:parent]
130
- end
148
+ nodes.each do |node|
149
+ if node.type == :tag
150
+ # get the tag from previously calculated list so the references are the same
151
+ tag = BetterHtml::Tree::Tag.from_node(node)
152
+ tags << tag
131
153
 
132
- next
133
- end
154
+ if tag.closing?
155
+ if current_opened_tag && tag.name == current_opened_tag.name
156
+ tag_tree[current_opened_tag][:closing] = tag
157
+ current_opened_tag = tag_tree[current_opened_tag][:parent]
158
+ end
159
+
160
+ next
161
+ end
134
162
 
135
- self_closing = self_closing?(tag)
163
+ self_closing = self_closing?(tag)
136
164
 
137
- tag_tree[tag] = {
138
- closing: self_closing ? tag : nil,
139
- parent: current_opened_tag
140
- }
165
+ tag_tree[tag] = {
166
+ tag: tag,
167
+ closing: self_closing ? tag : nil,
168
+ parent: current_opened_tag,
169
+ children: []
170
+ }
141
171
 
142
- current_opened_tag = tag unless self_closing
172
+ tag_tree[current_opened_tag][:children] << tag_tree[tag] if current_opened_tag
173
+ current_opened_tag = tag unless self_closing
174
+ elsif current_opened_tag
175
+ tag_tree[current_opened_tag][:children] << node
176
+ end
143
177
  end
144
178
 
145
- tag_tree
179
+ [tags, tag_tree]
146
180
  end
147
181
 
148
182
  def self_closing?(tag)
@@ -169,6 +203,12 @@ module ERBLint
169
203
  end
170
204
  end
171
205
 
206
+ # Unless explicitly set, we don't want to mark correctable offenses if the counter is correct.
207
+ if !@config.override_ignores_if_correctable? && expected_count == @total_offenses
208
+ clear_offenses
209
+ return
210
+ end
211
+
172
212
  if @offenses_not_corrected.zero?
173
213
  # have to adjust to get `\n` so we delete the whole line
174
214
  add_offense(processed_source.to_source_range(comment_node.loc.adjust(end_pos: 1)), "Unused erblint:count comment for #{rule_name}", "") if comment_node
@@ -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/button"
6
6
 
7
7
  module ERBLint
8
8
  module Linters
9
9
  # Counts the number of times a HTML button is used instead of the component.
10
- class ButtonComponentMigrationCounter < Linter
11
- include Helpers
10
+ class ButtonComponentMigrationCounter < BaseLinter
12
11
  include Autocorrectable
13
12
 
14
13
  TAGS = Primer::ViewComponents::Constants.get(
@@ -16,6 +15,8 @@ module ERBLint
16
15
  constant: "TAG_OPTIONS"
17
16
  ).freeze
18
17
 
18
+ # CloseButton component has preference when this class is seen in conjuction with `btn`.
19
+ DISALLOWED_CLASSES = %w[close-button].freeze
19
20
  CLASSES = %w[btn btn-link].freeze
20
21
  MESSAGE = "We are migrating buttons to use [Primer::ButtonComponent](https://primer.style/view-components/components/button), please try to use that instead of raw HTML."
21
22
  ARGUMENT_MAPPER = ArgumentMappers::Button
@@ -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"
@@ -1,16 +1,123 @@
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/close_button"
4
6
 
5
7
  module ERBLint
6
8
  module Linters
7
9
  # Counts the number of times a HTML clipboard-copy is used instead of the component.
8
- class CloseButtonComponentMigrationCounter < Linter
9
- include Helpers
10
+ class CloseButtonComponentMigrationCounter < BaseLinter
11
+ include Autocorrectable
10
12
 
11
13
  TAGS = %w[button].freeze
12
14
  CLASSES = %w[close-button].freeze
13
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
14
121
  end
15
122
  end
16
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