primer_view_components 0.0.48 → 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 +43 -0
- data/app/components/primer/beta/avatar.rb +1 -1
- 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/image_crop.html.erb +4 -4
- data/app/components/primer/navigation/tab_component.rb +15 -1
- data/app/components/primer/tab_nav_component.rb +4 -3
- data/app/components/primer/truncate.rb +1 -1
- data/app/components/primer/underline_nav_component.rb +3 -2
- data/lib/primer/classify/utilities.rb +33 -12
- data/lib/primer/view_components.rb +34 -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/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 +42 -41
- data/lib/primer/view_components/linters/label_component_migration_counter.rb +25 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/tasks/constants.rake +12 -0
- data/lib/tasks/docs.rake +24 -23
- data/lib/tasks/utilities.rake +2 -10
- 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 +1 -1
- metadata +16 -4
- data/app/components/primer/avatar_stack_component.rb +0 -90
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "conversion_error"
|
4
|
+
require_relative "system_arguments"
|
5
|
+
require "primer/view_components/constants"
|
6
|
+
|
7
|
+
module ERBLint
|
8
|
+
module Linters
|
9
|
+
module ArgumentMappers
|
10
|
+
# Provides the base interface to implement an `ArgumentMapper`.
|
11
|
+
# Override attribute_to_args in a child class to customize its mapping behavior.
|
12
|
+
class Base
|
13
|
+
DEFAULT_TAG = nil
|
14
|
+
|
15
|
+
def initialize(tag)
|
16
|
+
@tag = tag
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
to_args.map { |k, v| "#{k}: #{v}" }.join(", ")
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_args
|
24
|
+
args = {}
|
25
|
+
|
26
|
+
args[:tag] = ":#{@tag.name}" unless self.class::DEFAULT_TAG.nil? || @tag.name == self.class::DEFAULT_TAG
|
27
|
+
|
28
|
+
@tag.attributes.each do |attribute|
|
29
|
+
args.merge!(attribute_to_args(attribute))
|
30
|
+
end
|
31
|
+
|
32
|
+
args
|
33
|
+
end
|
34
|
+
|
35
|
+
def attribute_to_args(attribute); end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,62 +1,53 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "system_arguments"
|
3
|
+
require_relative "base"
|
5
4
|
|
6
5
|
module ERBLint
|
7
6
|
module Linters
|
8
7
|
module ArgumentMappers
|
9
8
|
# Maps classes in a button element to arguments for the Button component.
|
10
|
-
class Button
|
11
|
-
|
12
|
-
"btn-primary" => ":primary",
|
13
|
-
"btn-danger" => ":danger",
|
14
|
-
"btn-outline" => ":outline",
|
15
|
-
"btn-invisible" => ":invisible",
|
16
|
-
"btn-link" => ":link"
|
17
|
-
}.freeze
|
9
|
+
class Button < Base
|
10
|
+
require "pry"
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
SCHEME_MAPPINGS = Primer::ViewComponents::Constants.get(
|
13
|
+
component: "Primer::ButtonComponent",
|
14
|
+
constant: "SCHEME_MAPPINGS",
|
15
|
+
symbolize: true
|
16
|
+
).freeze
|
23
17
|
|
24
|
-
|
18
|
+
VARIANT_MAPPINGS = Primer::ViewComponents::Constants.get(
|
19
|
+
component: "Primer::ButtonComponent",
|
20
|
+
constant: "VARIANT_MAPPINGS",
|
21
|
+
symbolize: true
|
22
|
+
).freeze
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def to_args
|
35
|
-
args = {}
|
24
|
+
TYPE_OPTIONS = Primer::ViewComponents::Constants.get(
|
25
|
+
component: "Primer::BaseButton",
|
26
|
+
constant: "TYPE_OPTIONS"
|
27
|
+
).freeze
|
28
|
+
DEFAULT_TAG = Primer::ViewComponents::Constants.get(
|
29
|
+
component: "Primer::BaseButton",
|
30
|
+
constant: "DEFAULT_TAG"
|
31
|
+
).freeze
|
36
32
|
|
37
|
-
|
33
|
+
def attribute_to_args(attribute)
|
34
|
+
attr_name = attribute.name
|
38
35
|
|
39
|
-
|
40
|
-
|
36
|
+
if attr_name == "class"
|
37
|
+
classes_to_args(attribute)
|
38
|
+
elsif attr_name == "disabled"
|
39
|
+
{ disabled: true }
|
40
|
+
elsif attr_name == "type"
|
41
|
+
# button is the default type, so we don't need to do anything.
|
42
|
+
return {} if attribute.value == "button"
|
41
43
|
|
42
|
-
|
43
|
-
args = args.merge(classes_to_args(attribute))
|
44
|
-
elsif attr_name == "disabled"
|
45
|
-
args[:disabled] = true
|
46
|
-
elsif attr_name == "type"
|
47
|
-
# button is the default type, so we don't need to do anything.
|
48
|
-
next if attribute.value == "button"
|
44
|
+
raise ConversionError, "Button component does not support type \"#{attribute.value}\"" unless TYPE_OPTIONS.include?(attribute.value)
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# Assume the attribute is a system argument.
|
55
|
-
args.merge!(SystemArguments.new(attribute).to_args)
|
56
|
-
end
|
46
|
+
{ type: ":#{attribute.value}" }
|
47
|
+
else
|
48
|
+
# Assume the attribute is a system argument.
|
49
|
+
SystemArguments.new(attribute).to_args
|
57
50
|
end
|
58
|
-
|
59
|
-
args
|
60
51
|
end
|
61
52
|
|
62
53
|
def classes_to_args(classes)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module ERBLint
|
6
|
+
module Linters
|
7
|
+
module ArgumentMappers
|
8
|
+
# Maps attributes in the clipboard-copy element to arguments for the ClipboardCopy component.
|
9
|
+
class ClipboardCopy < Base
|
10
|
+
DEFAULT_TAG = "clipboard-copy"
|
11
|
+
|
12
|
+
def attribute_to_args(attribute)
|
13
|
+
attr_name = attribute.name
|
14
|
+
|
15
|
+
if attr_name == "value"
|
16
|
+
{ value: attribute.value.to_json }
|
17
|
+
else
|
18
|
+
# Assume the attribute is a system argument.
|
19
|
+
SystemArguments.new(attribute).to_args
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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 label element to arguments for the Label component.
|
9
|
+
class Label < Base
|
10
|
+
SCHEME_MAPPINGS = Primer::ViewComponents::Constants.get(
|
11
|
+
component: "Primer::LabelComponent",
|
12
|
+
constant: "SCHEME_MAPPINGS",
|
13
|
+
symbolize: true
|
14
|
+
).freeze
|
15
|
+
|
16
|
+
VARIANT_MAPPINGS = Primer::ViewComponents::Constants.get(
|
17
|
+
component: "Primer::LabelComponent",
|
18
|
+
constant: "VARIANT_MAPPINGS",
|
19
|
+
symbolize: true
|
20
|
+
).freeze
|
21
|
+
|
22
|
+
DEFAULT_TAG = Primer::ViewComponents::Constants.get(
|
23
|
+
component: "Primer::LabelComponent",
|
24
|
+
constant: "DEFAULT_TAG"
|
25
|
+
).freeze
|
26
|
+
|
27
|
+
def attribute_to_args(attribute)
|
28
|
+
attr_name = attribute.name
|
29
|
+
|
30
|
+
if attr_name == "class"
|
31
|
+
classes_to_args(attribute)
|
32
|
+
elsif attr_name == "title"
|
33
|
+
{ title: attribute.value.to_json }
|
34
|
+
else
|
35
|
+
# Assume the attribute is a system argument.
|
36
|
+
SystemArguments.new(attribute).to_args
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def classes_to_args(classes)
|
41
|
+
classes.value.split(" ").each_with_object({}) do |class_name, acc|
|
42
|
+
next if class_name == "Label"
|
43
|
+
|
44
|
+
if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
|
45
|
+
acc[:scheme] = SCHEME_MAPPINGS[class_name]
|
46
|
+
elsif VARIANT_MAPPINGS[class_name] && acc[:variant].nil?
|
47
|
+
acc[:variant] = VARIANT_MAPPINGS[class_name]
|
48
|
+
else
|
49
|
+
raise ConversionError, "Cannot convert class \"#{class_name}\""
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
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
|
@@ -17,60 +18,60 @@ module ERBLint
|
|
17
18
|
|
18
19
|
def self.included(base)
|
19
20
|
base.include(ERBLint::LinterRegistry)
|
21
|
+
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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)
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
tags.each do |tag|
|
30
|
+
next if tag.closing?
|
31
|
+
next unless self.class::TAGS&.include?(tag.name)
|
30
32
|
|
31
|
-
|
33
|
+
classes = tag.attributes["class"]&.value&.split(" ") || []
|
32
34
|
|
33
|
-
|
35
|
+
tag_tree[tag][:offense] = false
|
34
36
|
|
35
|
-
|
37
|
+
next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
|
36
38
|
|
37
|
-
|
38
|
-
|
39
|
+
args = map_arguments(tag)
|
40
|
+
correction = correction(args)
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
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])
|
58
59
|
end
|
60
|
+
end
|
59
61
|
|
60
|
-
|
62
|
+
counter_correct?(processed_source)
|
61
63
|
|
62
|
-
|
63
|
-
|
64
|
+
dump_data(processed_source) if ENV["DUMP_LINT_DATA"] == "1"
|
65
|
+
end
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
+
def autocorrect(processed_source, offense)
|
68
|
+
return unless offense.context
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
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)
|
74
75
|
end
|
75
76
|
end
|
76
77
|
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
|