primer_view_components 0.0.48 → 0.0.49

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/app/components/primer/beta/avatar.rb +1 -1
  4. data/app/components/primer/{avatar_stack_component.html.erb → beta/avatar_stack.html.erb} +0 -0
  5. data/app/components/primer/beta/avatar_stack.rb +92 -0
  6. data/app/components/primer/image_crop.html.erb +4 -4
  7. data/app/components/primer/navigation/tab_component.rb +15 -1
  8. data/app/components/primer/tab_nav_component.rb +4 -3
  9. data/app/components/primer/truncate.rb +1 -1
  10. data/app/components/primer/underline_nav_component.rb +3 -2
  11. data/lib/primer/classify/utilities.rb +33 -12
  12. data/lib/primer/view_components.rb +34 -6
  13. data/lib/primer/view_components/constants.rb +55 -0
  14. data/lib/primer/view_components/linters/argument_mappers/base.rb +39 -0
  15. data/lib/primer/view_components/linters/argument_mappers/button.rb +35 -44
  16. data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +25 -0
  17. data/lib/primer/view_components/linters/argument_mappers/label.rb +56 -0
  18. data/lib/primer/view_components/linters/autocorrectable.rb +30 -0
  19. data/lib/primer/view_components/linters/button_component_migration_counter.rb +9 -23
  20. data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +21 -0
  21. data/lib/primer/view_components/linters/helpers.rb +42 -41
  22. data/lib/primer/view_components/linters/label_component_migration_counter.rb +25 -0
  23. data/lib/primer/view_components/version.rb +1 -1
  24. data/lib/tasks/constants.rake +12 -0
  25. data/lib/tasks/docs.rake +24 -23
  26. data/lib/tasks/utilities.rake +2 -10
  27. data/lib/yard/docs_helper.rb +12 -3
  28. data/static/arguments.yml +977 -0
  29. data/static/assets/view-components.svg +18 -0
  30. data/static/classes.yml +174 -0
  31. data/static/constants.json +628 -0
  32. data/static/statuses.json +1 -1
  33. metadata +16 -4
  34. 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 "conversion_error"
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
- SCHEME_MAPPINGS = {
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
- VARIANT_MAPPINGS = {
20
- "btn-sm" => ":small",
21
- "btn-large" => ":large"
22
- }.freeze
12
+ SCHEME_MAPPINGS = Primer::ViewComponents::Constants.get(
13
+ component: "Primer::ButtonComponent",
14
+ constant: "SCHEME_MAPPINGS",
15
+ symbolize: true
16
+ ).freeze
23
17
 
24
- TYPE_OPTIONS = %w[button reset submit].freeze
18
+ VARIANT_MAPPINGS = Primer::ViewComponents::Constants.get(
19
+ component: "Primer::ButtonComponent",
20
+ constant: "VARIANT_MAPPINGS",
21
+ symbolize: true
22
+ ).freeze
25
23
 
26
- def initialize(tag)
27
- @tag = tag
28
- end
29
-
30
- def to_s
31
- to_args.map { |k, v| "#{k}: #{v}" }.join(", ")
32
- end
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
- args[:tag] = ":#{@tag.name}" unless @tag.name == "button"
33
+ def attribute_to_args(attribute)
34
+ attr_name = attribute.name
38
35
 
39
- @tag.attributes.each do |attribute|
40
- attr_name = attribute.name
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
- if attr_name == "class"
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
- raise ConversionError, "Button component does not support type \"#{attribute.value}\"" unless TYPE_OPTIONS.include?(attribute.value)
51
-
52
- args[:type] = ":#{attribute.value}"
53
- else
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
- private
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
- define_method "run" do |processed_source|
22
- @total_offenses = 0
23
- @offenses_not_corrected = 0
24
- tags = tags(processed_source)
25
- tag_tree = build_tag_tree(tags)
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
- tags.each do |tag|
28
- next if tag.closing?
29
- next unless self.class::TAGS&.include?(tag.name)
29
+ tags.each do |tag|
30
+ next if tag.closing?
31
+ next unless self.class::TAGS&.include?(tag.name)
30
32
 
31
- classes = tag.attributes["class"]&.value&.split(" ") || []
33
+ classes = tag.attributes["class"]&.value&.split(" ") || []
32
34
 
33
- tag_tree[tag][:offense] = false
35
+ tag_tree[tag][:offense] = false
34
36
 
35
- next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
37
+ next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
36
38
 
37
- args = map_arguments(tag)
38
- correction = correction(args)
39
+ args = map_arguments(tag)
40
+ correction = correction(args)
39
41
 
40
- tag_tree[tag][:offense] = true
41
- tag_tree[tag][:correctable] = !correction.nil?
42
- tag_tree[tag][:message] = message(args)
43
- tag_tree[tag][:correction] = correction
44
- end
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
- tag_tree.each do |tag, h|
47
- next unless h[:offense]
48
-
49
- @total_offenses += 1
50
- # We always fix the offenses using blocks. The closing tag corresponds to `<% end %>`.
51
- if h[:correctable]
52
- add_offense(tag.loc, h[:message], h[:correction])
53
- add_offense(h[:closing].loc, h[:message], "<% end %>")
54
- else
55
- @offenses_not_corrected += 1
56
- generate_offense(self.class, processed_source, tag, h[:message])
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
- counter_correct?(processed_source)
62
+ counter_correct?(processed_source)
61
63
 
62
- dump_data(processed_source) if ENV["DUMP_LINT_DATA"] == "1"
63
- end
64
+ dump_data(processed_source) if ENV["DUMP_LINT_DATA"] == "1"
65
+ end
64
66
 
65
- define_method "autocorrect" do |processed_source, offense|
66
- return unless offense.context
67
+ def autocorrect(processed_source, offense)
68
+ return unless offense.context
67
69
 
68
- lambda do |corrector|
69
- if offense.context.include?(counter_disable)
70
- correct_counter(corrector, processed_source, offense)
71
- else
72
- corrector.replace(offense.source_range, offense.context)
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