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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +159 -0
- data/app/components/primer/alpha/tab_nav.html.erb +11 -0
- data/app/components/primer/alpha/tab_nav.rb +130 -0
- data/app/components/primer/{tab_nav_component.html.erb → alpha/tab_panels.html.erb} +3 -8
- data/app/components/primer/alpha/tab_panels.rb +82 -0
- data/app/components/primer/alpha/underline_nav.html.erb +15 -0
- data/app/components/primer/alpha/underline_nav.rb +137 -0
- data/app/components/primer/{underline_nav_component.html.erb → alpha/underline_panels.html.erb} +3 -8
- data/app/components/primer/alpha/underline_panels.rb +86 -0
- data/app/components/primer/base_component.rb +1 -1
- data/app/components/primer/beta/avatar_stack.rb +9 -9
- data/app/components/primer/{breadcrumb_component.html.erb → beta/breadcrumbs.html.erb} +2 -1
- data/app/components/primer/beta/breadcrumbs.rb +61 -0
- data/app/components/primer/beta/truncate.html.erb +5 -0
- data/app/components/primer/beta/truncate.rb +110 -0
- data/app/components/primer/border_box_component.rb +27 -1
- data/app/components/primer/clipboard_copy.rb +1 -1
- data/app/components/primer/dropdown.rb +7 -7
- data/app/components/primer/icon_button.rb +1 -1
- data/app/components/primer/navigation/tab_component.rb +8 -6
- data/app/components/primer/octicon_component.rb +6 -1
- data/app/components/primer/progress_bar_component.rb +0 -3
- data/app/components/primer/tab_container_component.rb +1 -1
- data/app/lib/primer/class_name_helper.rb +14 -13
- data/app/lib/primer/fetch_or_fallback_helper.rb +2 -0
- data/app/lib/primer/octicon/cache.rb +10 -2
- data/app/lib/primer/tab_nav_helper.rb +35 -0
- data/app/lib/primer/tabbed_component_helper.rb +5 -5
- data/app/lib/primer/underline_nav_helper.rb +44 -0
- data/app/lib/primer/view_helper.rb +1 -0
- data/lib/primer/classify/cache.rb +0 -6
- data/lib/primer/classify/flex.rb +1 -1
- data/lib/primer/classify/functional_colors.rb +1 -1
- data/lib/primer/classify/utilities.rb +17 -2
- data/lib/primer/classify/utilities.yml +35 -0
- data/lib/primer/classify/validation.rb +18 -0
- data/lib/primer/classify.rb +4 -13
- data/lib/primer/view_components/constants.rb +1 -1
- data/lib/primer/view_components/linters/argument_mappers/base.rb +34 -8
- data/lib/primer/view_components/linters/argument_mappers/button.rb +5 -6
- data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +4 -3
- data/lib/primer/view_components/linters/argument_mappers/close_button.rb +43 -0
- data/lib/primer/view_components/linters/argument_mappers/flash.rb +32 -0
- data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +48 -5
- data/lib/primer/view_components/linters/argument_mappers/label.rb +3 -4
- data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +5 -7
- data/lib/primer/view_components/linters/autocorrectable.rb +6 -4
- data/lib/primer/view_components/linters/{helpers.rb → base_linter.rb} +69 -29
- data/lib/primer/view_components/linters/button_component_migration_counter.rb +4 -3
- data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +3 -4
- data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +110 -3
- data/lib/primer/view_components/linters/flash_component_migration_counter.rb +18 -3
- data/lib/primer/view_components/linters/label_component_migration_counter.rb +2 -3
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/rubocop/config/default.yml +5 -0
- data/lib/rubocop/cop/primer/base_cop.rb +28 -0
- data/lib/rubocop/cop/primer/deprecated_arguments.rb +263 -0
- data/lib/rubocop/cop/primer/no_tag_memoize.rb +1 -0
- data/lib/rubocop/cop/primer/primer_octicon.rb +178 -0
- data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +4 -32
- data/lib/rubocop/cop/primer.rb +1 -2
- data/lib/tasks/coverage.rake +4 -0
- data/lib/tasks/docs.rake +10 -8
- data/lib/tasks/utilities.rake +7 -3
- data/lib/yard/docs_helper.rb +6 -3
- data/static/arguments.yml +82 -64
- data/static/classes.yml +10 -0
- data/static/constants.json +44 -30
- data/static/statuses.json +10 -6
- metadata +57 -18
- data/app/components/primer/auto_complete/auto_component.d.ts +0 -1
- data/app/components/primer/auto_complete/auto_component.js +0 -1
- data/app/components/primer/breadcrumb_component.rb +0 -57
- data/app/components/primer/tab_nav_component.rb +0 -151
- data/app/components/primer/underline_nav_component.rb +0 -187
- 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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
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}\
|
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
|
-
#
|
10
|
-
|
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.
|
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 =
|
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
|
-
|
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(
|
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
|
-
|
126
|
-
if tag
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
|
133
|
-
|
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
|
-
|
163
|
+
self_closing = self_closing?(tag)
|
136
164
|
|
137
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
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 "
|
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 <
|
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 "
|
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 <
|
11
|
-
include Helpers
|
10
|
+
class ClipboardCopyComponentMigrationCounter < BaseLinter
|
12
11
|
include Autocorrectable
|
13
12
|
|
14
13
|
TAGS = %w[clipboard-copy].freeze
|
15
|
-
|
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 "
|
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 <
|
9
|
-
include
|
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 "
|
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 <
|
9
|
-
include
|
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
|