primer_view_components 0.0.52 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -0
- data/app/components/primer/tab_nav_component.rb +1 -1
- data/lib/primer/classify.rb +0 -2
- data/lib/primer/view_components/constants.rb +1 -1
- data/lib/primer/view_components/linters/argument_mappers/base.rb +2 -2
- data/lib/primer/view_components/linters/argument_mappers/button.rb +1 -1
- data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +4 -2
- 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 +2 -2
- data/lib/primer/view_components/linters/argument_mappers/label.rb +1 -1
- data/lib/primer/view_components/linters/autocorrectable.rb +4 -2
- data/lib/primer/view_components/linters/{helpers.rb → base_linter.rb} +61 -25
- 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 +2 -3
- 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/yard/docs_helper.rb +2 -0
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4df5bb105c8b48aed71b4652afbf6814257fcb2c344085b328c27f8f109a80c1
|
4
|
+
data.tar.gz: 72ff93288cac5eb1cabdc7cb8031c488452499203d768fcf3795bbe18cb31a43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3af26e886cc8998ef3d449471400319615c0e820ce7265316e6de6fda2b55340f7f7a82d3c7c78f5c4e393f60d7638126bd8e8a88675735bfe570d2958ee2484
|
7
|
+
data.tar.gz: aff9d0ca813126c33daab60f57bb01915ea8b779f17df99e648ec236f0da12bb9b16f54af24dfa871a50253663d71813533386457424f09f46a5458d5556a5b2
|
data/CHANGELOG.md
CHANGED
@@ -30,6 +30,48 @@ The category for changes related to documentation, testing and tooling. Also, fo
|
|
30
30
|
|
31
31
|
## main
|
32
32
|
|
33
|
+
## 0.0.53
|
34
|
+
|
35
|
+
### New
|
36
|
+
|
37
|
+
* Add autocorrection to `FlashComponent` linter when the context is basic text.
|
38
|
+
|
39
|
+
*Manuel Puyol*
|
40
|
+
|
41
|
+
### Updates
|
42
|
+
|
43
|
+
* Linters won't mark offenses when the ignore count is correct unless explicitly configured to do so.
|
44
|
+
|
45
|
+
*Manuel Puyol*
|
46
|
+
|
47
|
+
* Map the `for` argument when autofixing `ClipboardCopy` migrations.
|
48
|
+
|
49
|
+
*Kristján Oddsson*
|
50
|
+
|
51
|
+
* Add autocorrection for `CloseButton` linter.
|
52
|
+
|
53
|
+
*Manuel Puyol*
|
54
|
+
|
55
|
+
### Bug fixes
|
56
|
+
|
57
|
+
* Linters won't convert HTML special elements.
|
58
|
+
|
59
|
+
*Manuel Puyol*
|
60
|
+
|
61
|
+
### Misc
|
62
|
+
|
63
|
+
* Only run CHANGELOG CI on pull requests.
|
64
|
+
|
65
|
+
*Manuel Puyol*
|
66
|
+
|
67
|
+
* Run CI actions on pushes to main.
|
68
|
+
|
69
|
+
*Cameron Dutro*
|
70
|
+
|
71
|
+
* Get to 100% code coverage.
|
72
|
+
|
73
|
+
*Cameron Dutro*
|
74
|
+
|
33
75
|
## 0.0.52
|
34
76
|
|
35
77
|
### New
|
@@ -84,6 +126,10 @@ The category for changes related to documentation, testing and tooling. Also, fo
|
|
84
126
|
|
85
127
|
*Manuel Puyol*
|
86
128
|
|
129
|
+
* Add a linter generator.
|
130
|
+
|
131
|
+
*Manuel Puyol*
|
132
|
+
|
87
133
|
## 0.0.51
|
88
134
|
|
89
135
|
### Breaking changes
|
@@ -40,7 +40,7 @@ module Primer
|
|
40
40
|
|
41
41
|
# @example Default
|
42
42
|
# <%= render(Primer::TabNavComponent.new(label: "Default")) do |c| %>
|
43
|
-
# <% c.tab(selected: true, href: "#") { "Tab 1" }%>
|
43
|
+
# <% c.tab(selected: true, href: "#") { "Tab 1" } %>
|
44
44
|
# <% c.tab(href: "#") { "Tab 2" } %>
|
45
45
|
# <% c.tab(href: "#") { "Tab 3" } %>
|
46
46
|
# <% end %>
|
data/lib/primer/classify.rb
CHANGED
@@ -51,7 +51,7 @@ module ERBLint
|
|
51
51
|
erb_helper.raise_if_erb_block(classes_node)
|
52
52
|
|
53
53
|
system_arguments = system_arguments_to_args(classes_node.value)
|
54
|
-
args = classes_to_args(system_arguments[:classes])
|
54
|
+
args = classes_to_args(system_arguments[:classes]&.split || [])
|
55
55
|
|
56
56
|
invalid_classes = args[:classes].select { |class_name| Primer::Classify::Validation.invalid?(class_name) }
|
57
57
|
|
@@ -77,7 +77,7 @@ module ERBLint
|
|
77
77
|
# including a `classes` key that will contain all classes that the mapper couldn't handle.
|
78
78
|
# @returns { classes: Array, ... }
|
79
79
|
def classes_to_args(classes)
|
80
|
-
{ classes: classes
|
80
|
+
{ classes: classes }
|
81
81
|
end
|
82
82
|
|
83
83
|
def system_arguments_to_args(classes)
|
@@ -47,7 +47,7 @@ module ERBLint
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def classes_to_args(classes)
|
50
|
-
classes.
|
50
|
+
classes.each_with_object({ classes: [] }) do |class_name, acc|
|
51
51
|
next if class_name == "btn"
|
52
52
|
|
53
53
|
if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
|
@@ -8,10 +8,12 @@ module ERBLint
|
|
8
8
|
# Maps attributes in the clipboard-copy element to arguments for the ClipboardCopy component.
|
9
9
|
class ClipboardCopy < Base
|
10
10
|
DEFAULT_TAG = "clipboard-copy"
|
11
|
-
ATTRIBUTES = %w[value].freeze
|
11
|
+
ATTRIBUTES = %w[role tabindex for value id style].freeze
|
12
12
|
|
13
13
|
def attribute_to_args(attribute)
|
14
|
-
|
14
|
+
attr_name = attribute.name
|
15
|
+
|
16
|
+
{ attr_name.to_sym => erb_helper.convert(attribute) }
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module ArgumentMappers
|
8
|
+
# Maps classes in a close-button element to arguments for the CloseButton component.
|
9
|
+
class CloseButton < Base
|
10
|
+
ATTRIBUTES = %w[type].freeze
|
11
|
+
|
12
|
+
TYPE_OPTIONS = Primer::ViewComponents::Constants.get(
|
13
|
+
component: "Primer::CloseButton",
|
14
|
+
constant: "TYPE_OPTIONS"
|
15
|
+
).freeze
|
16
|
+
|
17
|
+
DEFAULT_TYPE = Primer::ViewComponents::Constants.get(
|
18
|
+
component: "Primer::CloseButton",
|
19
|
+
constant: "DEFAULT_TYPE"
|
20
|
+
).freeze
|
21
|
+
|
22
|
+
DEFAULT_CLASS = "close-button"
|
23
|
+
|
24
|
+
def attribute_to_args(attribute)
|
25
|
+
# button is the default type, so we don't need to do anything.
|
26
|
+
return {} if attribute.value == DEFAULT_TYPE
|
27
|
+
|
28
|
+
raise ConversionError, "CloseButton component does not support type \"#{attribute.value}\"" unless TYPE_OPTIONS.include?(attribute.value)
|
29
|
+
|
30
|
+
{ type: ":#{attribute.value}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
def classes_to_args(classes)
|
34
|
+
classes.each_with_object({ classes: [] }) do |class_name, acc|
|
35
|
+
next if class_name == DEFAULT_CLASS
|
36
|
+
|
37
|
+
acc[:classes] << class_name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module ArgumentMappers
|
8
|
+
# Maps classes in a flash element to arguments for the Flash component.
|
9
|
+
class Flash < Base
|
10
|
+
SCHEME_MAPPINGS = Primer::ViewComponents::Constants.get(
|
11
|
+
component: "Primer::FlashComponent",
|
12
|
+
constant: "SCHEME_MAPPINGS",
|
13
|
+
symbolize: true
|
14
|
+
).freeze
|
15
|
+
|
16
|
+
def classes_to_args(classes)
|
17
|
+
classes.each_with_object({ classes: [] }) do |class_name, acc|
|
18
|
+
next if class_name == "flash"
|
19
|
+
|
20
|
+
if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
|
21
|
+
acc[:scheme] = SCHEME_MAPPINGS[class_name]
|
22
|
+
elsif class_name == "flash-full"
|
23
|
+
acc[:full] = true
|
24
|
+
else
|
25
|
+
acc[:classes] << class_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -57,8 +57,8 @@ module ERBLint
|
|
57
57
|
return m[:rb].strip
|
58
58
|
end
|
59
59
|
|
60
|
-
#
|
61
|
-
|
60
|
+
# we use `source` instead of `value` because it does not convert encoded HTML entities.
|
61
|
+
attribute.value_node.loc.source.gsub("<%=", '#{').gsub("%>", "}")
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -31,7 +31,7 @@ module ERBLint
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def classes_to_args(classes)
|
34
|
-
classes.
|
34
|
+
classes.each_with_object({ classes: [] }) do |class_name, acc|
|
35
35
|
next if class_name == "Label"
|
36
36
|
|
37
37
|
if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
|
@@ -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
|
@@ -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,18 +21,24 @@ module ERBLint
|
|
15
21
|
].freeze
|
16
22
|
|
17
23
|
DUMP_FILE = ".erblint-counter-ignore.json"
|
24
|
+
DISALLOWED_CLASSES = [].freeze
|
18
25
|
CLASSES = [].freeze
|
19
26
|
REQUIRED_ARGUMENTS = [].freeze
|
20
27
|
|
21
|
-
|
28
|
+
class ConfigSchema < LinterConfig
|
29
|
+
property :override_ignores_if_correctable, accepts: [true, false], default: false, reader: :override_ignores_if_correctable?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.inherited(base)
|
33
|
+
super
|
22
34
|
base.include(ERBLint::LinterRegistry)
|
35
|
+
base.config_schema = ConfigSchema
|
23
36
|
end
|
24
37
|
|
25
38
|
def run(processed_source)
|
26
39
|
@total_offenses = 0
|
27
40
|
@offenses_not_corrected = 0
|
28
|
-
tags =
|
29
|
-
tag_tree = build_tag_tree(tags)
|
41
|
+
(tags, tag_tree) = build_tag_tree(processed_source)
|
30
42
|
|
31
43
|
tags.each do |tag|
|
32
44
|
next if tag.closing?
|
@@ -35,9 +47,10 @@ module ERBLint
|
|
35
47
|
classes = tag.attributes["class"]&.value&.split(" ") || []
|
36
48
|
tag_tree[tag][:offense] = false
|
37
49
|
|
50
|
+
next if (classes & self.class::DISALLOWED_CLASSES).any?
|
38
51
|
next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
|
39
52
|
|
40
|
-
args = map_arguments(tag)
|
53
|
+
args = map_arguments(tag, tag_tree[tag])
|
41
54
|
correction = correction(args)
|
42
55
|
|
43
56
|
attributes = tag.attributes.each.map(&:name).join(" ")
|
@@ -55,8 +68,7 @@ module ERBLint
|
|
55
68
|
@total_offenses += 1
|
56
69
|
# We always fix the offenses using blocks. The closing tag corresponds to `<% end %>`.
|
57
70
|
if h[:correctable]
|
58
|
-
|
59
|
-
add_offense(h[:closing].loc, h[:message], "<% end %>")
|
71
|
+
add_correction(tag, h)
|
60
72
|
else
|
61
73
|
@offenses_not_corrected += 1
|
62
74
|
generate_offense(self.class, processed_source, tag, h[:message])
|
@@ -82,11 +94,16 @@ module ERBLint
|
|
82
94
|
|
83
95
|
private
|
84
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
|
+
|
85
102
|
# Override this function to convert the HTML element attributes to argument for a component.
|
86
103
|
#
|
87
104
|
# @return [Hash] if possible to map all attributes to arguments.
|
88
105
|
# @return [Nil] if cannot map to arguments.
|
89
|
-
def map_arguments(_tag)
|
106
|
+
def map_arguments(_tag, _tag_tree)
|
90
107
|
nil
|
91
108
|
end
|
92
109
|
|
@@ -122,31 +139,44 @@ module ERBLint
|
|
122
139
|
# This assumes that the AST provided represents valid HTML, where each tag has a corresponding closing tag.
|
123
140
|
# From the tags, we build a structured tree which represents the tag hierarchy.
|
124
141
|
# With this, we are able to know where the tags start and end.
|
125
|
-
def build_tag_tree(
|
142
|
+
def build_tag_tree(processed_source)
|
143
|
+
nodes = processed_source.ast.children
|
126
144
|
tag_tree = {}
|
145
|
+
tags = []
|
127
146
|
current_opened_tag = nil
|
128
147
|
|
129
|
-
|
130
|
-
if tag
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
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
|
135
153
|
|
136
|
-
|
137
|
-
|
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
|
138
159
|
|
139
|
-
|
160
|
+
next
|
161
|
+
end
|
162
|
+
|
163
|
+
self_closing = self_closing?(tag)
|
140
164
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
165
|
+
tag_tree[tag] = {
|
166
|
+
tag: tag,
|
167
|
+
closing: self_closing ? tag : nil,
|
168
|
+
parent: current_opened_tag,
|
169
|
+
children: []
|
170
|
+
}
|
145
171
|
|
146
|
-
|
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
|
147
177
|
end
|
148
178
|
|
149
|
-
tag_tree
|
179
|
+
[tags, tag_tree]
|
150
180
|
end
|
151
181
|
|
152
182
|
def self_closing?(tag)
|
@@ -173,6 +203,12 @@ module ERBLint
|
|
173
203
|
end
|
174
204
|
end
|
175
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
|
+
|
176
212
|
if @offenses_not_corrected.zero?
|
177
213
|
# have to adjust to get `\n` so we delete the whole line
|
178
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,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/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
|
@@ -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
|
@@ -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/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 <
|
11
|
-
include Helpers
|
10
|
+
class LabelComponentMigrationCounter < BaseLinter
|
12
11
|
include Autocorrectable
|
13
12
|
|
14
13
|
TAGS = Primer::ViewComponents::Constants.get(
|
data/lib/yard/docs_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: primer_view_components
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.53
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-08-
|
11
|
+
date: 2021-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionview
|
@@ -324,6 +324,20 @@ dependencies:
|
|
324
324
|
- - ">="
|
325
325
|
- !ruby/object:Gem::Version
|
326
326
|
version: '0'
|
327
|
+
- !ruby/object:Gem::Dependency
|
328
|
+
name: timecop
|
329
|
+
requirement: !ruby/object:Gem::Requirement
|
330
|
+
requirements:
|
331
|
+
- - ">="
|
332
|
+
- !ruby/object:Gem::Version
|
333
|
+
version: '0'
|
334
|
+
type: :development
|
335
|
+
prerelease: false
|
336
|
+
version_requirements: !ruby/object:Gem::Requirement
|
337
|
+
requirements:
|
338
|
+
- - ">="
|
339
|
+
- !ruby/object:Gem::Version
|
340
|
+
version: '0'
|
327
341
|
- !ruby/object:Gem::Dependency
|
328
342
|
name: yard
|
329
343
|
requirement: !ruby/object:Gem::Requirement
|
@@ -486,16 +500,18 @@ files:
|
|
486
500
|
- lib/primer/view_components/linters/argument_mappers/base.rb
|
487
501
|
- lib/primer/view_components/linters/argument_mappers/button.rb
|
488
502
|
- lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb
|
503
|
+
- lib/primer/view_components/linters/argument_mappers/close_button.rb
|
489
504
|
- lib/primer/view_components/linters/argument_mappers/conversion_error.rb
|
505
|
+
- lib/primer/view_components/linters/argument_mappers/flash.rb
|
490
506
|
- lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb
|
491
507
|
- lib/primer/view_components/linters/argument_mappers/label.rb
|
492
508
|
- lib/primer/view_components/linters/argument_mappers/system_arguments.rb
|
493
509
|
- lib/primer/view_components/linters/autocorrectable.rb
|
510
|
+
- lib/primer/view_components/linters/base_linter.rb
|
494
511
|
- lib/primer/view_components/linters/button_component_migration_counter.rb
|
495
512
|
- lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb
|
496
513
|
- lib/primer/view_components/linters/close_button_component_migration_counter.rb
|
497
514
|
- lib/primer/view_components/linters/flash_component_migration_counter.rb
|
498
|
-
- lib/primer/view_components/linters/helpers.rb
|
499
515
|
- lib/primer/view_components/linters/label_component_migration_counter.rb
|
500
516
|
- lib/primer/view_components/statuses.rb
|
501
517
|
- lib/primer/view_components/version.rb
|