primer_view_components 0.0.45 → 0.0.49
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 +194 -0
- data/app/components/primer/{auto_complete_component.d.ts → auto_complete/auto_component.d.ts} +0 -0
- data/app/components/primer/{auto_complete_component.js → auto_complete/auto_component.js} +0 -0
- data/app/components/primer/base_component.rb +36 -7
- data/app/components/primer/beta/auto_complete.rb +159 -0
- data/app/components/primer/beta/auto_complete/auto_complete.d.ts +1 -0
- data/app/components/primer/{auto_complete → beta/auto_complete}/auto_complete.html.erb +0 -0
- data/app/components/primer/beta/auto_complete/auto_complete.js +1 -0
- data/app/components/primer/{auto_complete → beta/auto_complete}/auto_complete.ts +0 -0
- data/app/components/primer/beta/auto_complete/item.rb +44 -0
- data/app/components/primer/beta/avatar.rb +77 -0
- data/app/components/primer/{avatar_stack_component.html.erb → beta/avatar_stack.html.erb} +0 -0
- data/app/components/primer/beta/avatar_stack.rb +92 -0
- data/app/components/primer/border_box_component.rb +3 -0
- data/app/components/primer/component.rb +9 -1
- data/app/components/primer/details_component.rb +12 -8
- data/app/components/primer/image_crop.html.erb +4 -4
- data/app/components/primer/image_crop.rb +1 -1
- data/app/components/primer/markdown.rb +9 -9
- data/app/components/primer/menu_component.rb +7 -3
- data/app/components/primer/navigation/tab_component.rb +34 -6
- data/app/components/primer/popover_component.rb +6 -3
- data/app/components/primer/primer.d.ts +1 -1
- data/app/components/primer/primer.js +1 -1
- data/app/components/primer/primer.ts +1 -1
- data/app/components/primer/tab_nav_component.rb +9 -6
- data/app/components/primer/timeline_item_component.rb +2 -2
- data/app/components/primer/tooltip.rb +1 -1
- data/app/components/primer/truncate.rb +6 -1
- data/app/components/primer/underline_nav_component.rb +13 -6
- data/{app/lib → lib}/primer/classify.rb +12 -39
- data/{app/lib → lib}/primer/classify/cache.rb +6 -20
- data/{app/lib → lib}/primer/classify/flex.rb +0 -0
- data/{app/lib → lib}/primer/classify/functional_background_colors.rb +2 -0
- data/{app/lib → lib}/primer/classify/functional_border_colors.rb +2 -0
- data/{app/lib → lib}/primer/classify/functional_colors.rb +0 -0
- data/{app/lib → lib}/primer/classify/functional_text_colors.rb +2 -0
- data/{app/lib → lib}/primer/classify/grid.rb +0 -0
- data/{app/lib → lib}/primer/classify/utilities.rb +54 -22
- data/{app/lib → lib}/primer/classify/utilities.yml +124 -0
- data/lib/primer/view_components.rb +35 -6
- data/lib/primer/view_components/constants.rb +55 -0
- data/lib/primer/view_components/linters/argument_mappers/base.rb +39 -0
- data/lib/primer/view_components/linters/argument_mappers/button.rb +35 -44
- data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +25 -0
- data/lib/primer/view_components/linters/argument_mappers/label.rb +56 -0
- data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +1 -2
- data/lib/primer/view_components/linters/autocorrectable.rb +30 -0
- data/lib/primer/view_components/linters/button_component_migration_counter.rb +9 -23
- data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +21 -0
- data/lib/primer/view_components/linters/helpers.rb +56 -38
- data/lib/primer/view_components/linters/label_component_migration_counter.rb +25 -0
- data/lib/primer/view_components/statuses.rb +14 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/rubocop/config/default.yml +12 -0
- data/lib/rubocop/cop/primer.rb +4 -0
- data/lib/rubocop/cop/primer/no_tag_memoize.rb +42 -0
- data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +75 -0
- data/lib/tasks/constants.rake +12 -0
- data/lib/tasks/docs.rake +89 -34
- data/lib/tasks/utilities.rake +9 -11
- data/lib/yard/docs_helper.rb +12 -3
- data/static/arguments.yml +977 -0
- data/static/assets/view-components.svg +18 -0
- data/static/classes.yml +174 -0
- data/static/constants.json +628 -0
- data/static/statuses.json +5 -5
- metadata +44 -27
- data/app/components/primer/auto_complete.rb +0 -156
- data/app/components/primer/auto_complete/item.rb +0 -42
- data/app/components/primer/avatar_component.rb +0 -75
- data/app/components/primer/avatar_stack_component.rb +0 -84
- data/app/components/primer/details_menu_component.d.ts +0 -1
- data/app/components/primer/details_menu_component.js +0 -1
@@ -32,8 +32,7 @@ module ERBLint
|
|
32
32
|
elsif attr_name.start_with?(*STRING_PARAMETERS)
|
33
33
|
raise ConversionError, "Cannot convert attribute \"#{attr_name}\" because its value contains an erb block" if attribute.value_node&.children&.any? { |n| n.try(:type) == :erb }
|
34
34
|
|
35
|
-
#
|
36
|
-
{ "\"#{attr_name}\"" => attribute.value_node ? attribute.value.to_json : true }
|
35
|
+
{ "\"#{attr_name}\"" => attribute.value.to_json }
|
37
36
|
else
|
38
37
|
raise ConversionError, "Cannot convert attribute \"#{attr_name}\""
|
39
38
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "argument_mappers/conversion_error"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
# Helper methods for autocorrectable ERB linters.
|
8
|
+
module Autocorrectable
|
9
|
+
def map_arguments(tag)
|
10
|
+
self.class::ARGUMENT_MAPPER.new(tag).to_s
|
11
|
+
rescue ArgumentMappers::ConversionError
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def correction(args)
|
16
|
+
return nil if args.nil?
|
17
|
+
|
18
|
+
correction = "<%= render #{self.class::COMPONENT}.new"
|
19
|
+
correction += "(#{args})" if args.present?
|
20
|
+
"#{correction} do %>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def message(args)
|
24
|
+
return self.class::MESSAGE if args.nil?
|
25
|
+
|
26
|
+
"#{self.class::MESSAGE}\n\nTry using:\n\n#{correction(args)}\n\nInstead of:\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "helpers"
|
4
|
+
require_relative "autocorrectable"
|
4
5
|
require_relative "argument_mappers/button"
|
5
6
|
|
6
7
|
module ERBLint
|
@@ -8,32 +9,17 @@ module ERBLint
|
|
8
9
|
# Counts the number of times a HTML button is used instead of the component.
|
9
10
|
class ButtonComponentMigrationCounter < Linter
|
10
11
|
include Helpers
|
12
|
+
include Autocorrectable
|
13
|
+
|
14
|
+
TAGS = Primer::ViewComponents::Constants.get(
|
15
|
+
component: "Primer::BaseButton",
|
16
|
+
constant: "TAG_OPTIONS"
|
17
|
+
).freeze
|
11
18
|
|
12
|
-
TAGS = %w[button summary a].freeze
|
13
19
|
CLASSES = %w[btn btn-link].freeze
|
14
20
|
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."
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def map_arguments(tag)
|
19
|
-
ArgumentMappers::Button.new(tag).to_s
|
20
|
-
rescue ArgumentMappers::ConversionError
|
21
|
-
nil
|
22
|
-
end
|
23
|
-
|
24
|
-
def correction(args)
|
25
|
-
return nil if args.nil?
|
26
|
-
|
27
|
-
correction = "<%= render Primer::ButtonComponent.new"
|
28
|
-
correction += "(#{args})" if args.present?
|
29
|
-
"#{correction} do %>"
|
30
|
-
end
|
31
|
-
|
32
|
-
def message(args)
|
33
|
-
return MESSAGE if args.nil?
|
34
|
-
|
35
|
-
"#{MESSAGE}\n\nTry using:\n\n#{correction(args)}\n\nInstead of:\n"
|
36
|
-
end
|
21
|
+
ARGUMENT_MAPPER = ArgumentMappers::Button
|
22
|
+
COMPONENT = "Primer::ButtonComponent"
|
37
23
|
end
|
38
24
|
end
|
39
25
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helpers"
|
4
|
+
require_relative "autocorrectable"
|
5
|
+
require_relative "argument_mappers/clipboard_copy"
|
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 ClipboardCopyComponentMigrationCounter < Linter
|
11
|
+
include Helpers
|
12
|
+
include Autocorrectable
|
13
|
+
|
14
|
+
TAGS = %w[clipboard-copy].freeze
|
15
|
+
CLASSES = %w[].freeze
|
16
|
+
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
|
+
ARGUMENT_MAPPER = ArgumentMappers::ClipboardCopy
|
18
|
+
COMPONENT = "Primer::ClipboardCopy"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "json"
|
4
4
|
require "openssl"
|
5
|
+
require "primer/view_components/constants"
|
5
6
|
|
6
7
|
module ERBLint
|
7
8
|
module Linters
|
@@ -13,58 +14,64 @@ module ERBLint
|
|
13
14
|
link menuitem meta param source track wbr img
|
14
15
|
].freeze
|
15
16
|
|
17
|
+
DUMP_FILE = ".erblint-counter-ignore.json"
|
18
|
+
|
16
19
|
def self.included(base)
|
17
20
|
base.include(ERBLint::LinterRegistry)
|
21
|
+
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
tags.each do |tag|
|
25
|
-
next if tag.closing?
|
26
|
-
next unless self.class::TAGS&.include?(tag.name)
|
23
|
+
def run(processed_source)
|
24
|
+
@total_offenses = 0
|
25
|
+
@offenses_not_corrected = 0
|
26
|
+
tags = tags(processed_source)
|
27
|
+
tag_tree = build_tag_tree(tags)
|
27
28
|
|
28
|
-
|
29
|
+
tags.each do |tag|
|
30
|
+
next if tag.closing?
|
31
|
+
next unless self.class::TAGS&.include?(tag.name)
|
29
32
|
|
30
|
-
|
33
|
+
classes = tag.attributes["class"]&.value&.split(" ") || []
|
31
34
|
|
32
|
-
|
35
|
+
tag_tree[tag][:offense] = false
|
33
36
|
|
34
|
-
|
35
|
-
correction = correction(args)
|
37
|
+
next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
-
tag_tree[tag][:message] = message(args)
|
40
|
-
tag_tree[tag][:correction] = correction
|
41
|
-
end
|
39
|
+
args = map_arguments(tag)
|
40
|
+
correction = correction(args)
|
42
41
|
|
43
|
-
tag_tree
|
44
|
-
|
42
|
+
tag_tree[tag][:offense] = true
|
43
|
+
tag_tree[tag][:correctable] = !correction.nil?
|
44
|
+
tag_tree[tag][:message] = message(args)
|
45
|
+
tag_tree[tag][:correction] = correction
|
46
|
+
end
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
48
|
+
tag_tree.each do |tag, h|
|
49
|
+
next unless h[:offense]
|
50
|
+
|
51
|
+
@total_offenses += 1
|
52
|
+
# We always fix the offenses using blocks. The closing tag corresponds to `<% end %>`.
|
53
|
+
if h[:correctable]
|
54
|
+
add_offense(tag.loc, h[:message], h[:correction])
|
55
|
+
add_offense(h[:closing].loc, h[:message], "<% end %>")
|
56
|
+
else
|
57
|
+
@offenses_not_corrected += 1
|
58
|
+
generate_offense(self.class, processed_source, tag, h[:message])
|
54
59
|
end
|
55
|
-
|
56
|
-
counter_correct?(processed_source)
|
57
60
|
end
|
58
61
|
|
59
|
-
|
60
|
-
return unless offense.context
|
62
|
+
counter_correct?(processed_source)
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
64
|
+
dump_data(processed_source) if ENV["DUMP_LINT_DATA"] == "1"
|
65
|
+
end
|
66
|
+
|
67
|
+
def autocorrect(processed_source, offense)
|
68
|
+
return unless offense.context
|
69
|
+
|
70
|
+
lambda do |corrector|
|
71
|
+
if offense.context.include?(counter_disable)
|
72
|
+
correct_counter(corrector, processed_source, offense)
|
73
|
+
else
|
74
|
+
corrector.replace(offense.source_range, offense.context)
|
68
75
|
end
|
69
76
|
end
|
70
77
|
end
|
@@ -186,6 +193,17 @@ module ERBLint
|
|
186
193
|
offense = ["#{klass_name}:#{message}", tag.node.loc.source].join("\n")
|
187
194
|
add_offense(processed_source.to_source_range(tag.loc), offense, replacement)
|
188
195
|
end
|
196
|
+
|
197
|
+
def dump_data(processed_source)
|
198
|
+
return if @total_offenses.zero?
|
199
|
+
|
200
|
+
data = File.exist?(DUMP_FILE) ? JSON.parse(File.read(DUMP_FILE)) : {}
|
201
|
+
|
202
|
+
data[processed_source.filename] ||= {}
|
203
|
+
data[processed_source.filename][self.class.name.demodulize] = @total_offenses
|
204
|
+
|
205
|
+
File.write(DUMP_FILE, JSON.pretty_generate(data))
|
206
|
+
end
|
189
207
|
end
|
190
208
|
end
|
191
209
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helpers"
|
4
|
+
require_relative "autocorrectable"
|
5
|
+
require_relative "argument_mappers/label"
|
6
|
+
|
7
|
+
module ERBLint
|
8
|
+
module Linters
|
9
|
+
# Counts the number of times a HTML label is used instead of the component.
|
10
|
+
class LabelComponentMigrationCounter < Linter
|
11
|
+
include Helpers
|
12
|
+
include Autocorrectable
|
13
|
+
|
14
|
+
TAGS = Primer::ViewComponents::Constants.get(
|
15
|
+
component: "Primer::LabelComponent",
|
16
|
+
constant: "TAG_OPTIONS"
|
17
|
+
).freeze
|
18
|
+
|
19
|
+
CLASSES = %w[Label].freeze
|
20
|
+
MESSAGE = "We are migrating labels to use [Primer::LabelComponent](https://primer.style/view-components/components/label), please try to use that instead of raw HTML."
|
21
|
+
ARGUMENT_MAPPER = ArgumentMappers::Label
|
22
|
+
COMPONENT = "Primer::LabelComponent"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module Primer
|
8
|
+
# This cop ensures that tags are not set with ||=
|
9
|
+
#
|
10
|
+
# bad
|
11
|
+
# @system_arguments[:tag] ||= :h1
|
12
|
+
#
|
13
|
+
# good
|
14
|
+
# @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
|
15
|
+
#
|
16
|
+
# good
|
17
|
+
# @system_arguments[:tag] = :h2
|
18
|
+
class NoTagMemoize < RuboCop::Cop::Cop
|
19
|
+
INVALID_MESSAGE = <<~STR
|
20
|
+
Avoid `[:tag] ||=`. Instead, try one of the following:
|
21
|
+
- Don't allow consumers to update the tag by having a fixed tag (e.g. `system_arguments[:tag] = :div`)
|
22
|
+
- Use the `fetch_or_fallback` helper to only allow a tag from a restricted list.
|
23
|
+
STR
|
24
|
+
|
25
|
+
def_node_search :tag_memoized?, <<~PATTERN
|
26
|
+
(or-asgn
|
27
|
+
(send
|
28
|
+
_
|
29
|
+
_
|
30
|
+
(sym :tag)
|
31
|
+
)
|
32
|
+
_
|
33
|
+
)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_or_asgn(node)
|
37
|
+
add_offense(node, message: INVALID_MESSAGE) if tag_memoized?(node)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
require "primer/classify/utilities"
|
5
|
+
require "primer/view_components/statuses"
|
6
|
+
|
7
|
+
module RuboCop
|
8
|
+
module Cop
|
9
|
+
module Primer
|
10
|
+
# This cop ensures that components use System Arguments instead of CSS classes.
|
11
|
+
#
|
12
|
+
# bad
|
13
|
+
# Component.new(classes: "mr-1")
|
14
|
+
#
|
15
|
+
# good
|
16
|
+
# Component.new(mr: 1)
|
17
|
+
class SystemArgumentInsteadOfClass < RuboCop::Cop::Cop
|
18
|
+
INVALID_MESSAGE = <<~STR
|
19
|
+
Avoid using CSS classes when you can use System Arguments: https://primer.style/view-components/system-arguments.
|
20
|
+
STR
|
21
|
+
|
22
|
+
def on_send(node)
|
23
|
+
return unless node.method_name == :new
|
24
|
+
return unless ::Primer::ViewComponents::STATUSES.key?(node.receiver.const_name)
|
25
|
+
return unless node.arguments?
|
26
|
+
|
27
|
+
# we are looking for hash arguments and they are always last
|
28
|
+
kwargs = node.arguments.last
|
29
|
+
|
30
|
+
return unless kwargs.type == :hash
|
31
|
+
|
32
|
+
# find classes pair
|
33
|
+
classes_arg = kwargs.pairs.find { |kwarg| kwarg.key.value == :classes }
|
34
|
+
|
35
|
+
return if classes_arg.nil?
|
36
|
+
return unless classes_arg.value.type == :str
|
37
|
+
|
38
|
+
# get actual classes
|
39
|
+
classes = classes_arg.value.value
|
40
|
+
|
41
|
+
system_arguments = ::Primer::Classify::Utilities.classes_to_hash(classes)
|
42
|
+
|
43
|
+
# no classes are fixable
|
44
|
+
return if system_arguments[:classes] == classes
|
45
|
+
|
46
|
+
add_offense(classes_arg, message: INVALID_MESSAGE)
|
47
|
+
end
|
48
|
+
|
49
|
+
def autocorrect(node)
|
50
|
+
lambda do |corrector|
|
51
|
+
system_arguments = ::Primer::Classify::Utilities.classes_to_hash(node.value.value)
|
52
|
+
corrector.replace(node.loc.expression, arguments_as_string(system_arguments))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def arguments_as_string(system_arguments)
|
59
|
+
system_arguments.map do |key, value|
|
60
|
+
val = case value
|
61
|
+
when Symbol
|
62
|
+
":#{value}"
|
63
|
+
when String
|
64
|
+
value.to_json
|
65
|
+
else
|
66
|
+
value
|
67
|
+
end
|
68
|
+
|
69
|
+
"#{key}: #{val}"
|
70
|
+
end.join(", ")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :constants do
|
4
|
+
task :dump do
|
5
|
+
require File.expand_path("./../../demo/config/environment.rb", __dir__)
|
6
|
+
require "primer/view_components"
|
7
|
+
# Loads all components for `.descendants` to work properly
|
8
|
+
Dir["./app/components/primer/**/*.rb"].sort.each { |file| require file }
|
9
|
+
|
10
|
+
Primer::ViewComponents.dump_constants
|
11
|
+
end
|
12
|
+
end
|