primer_view_components 0.0.47 → 0.0.51

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +113 -0
  3. data/app/components/primer/base_component.rb +2 -2
  4. data/app/components/primer/beta/auto_complete.rb +2 -2
  5. data/app/components/primer/beta/avatar.rb +1 -1
  6. data/app/components/primer/{avatar_stack_component.html.erb → beta/avatar_stack.html.erb} +0 -0
  7. data/app/components/primer/beta/avatar_stack.rb +92 -0
  8. data/app/components/primer/clipboard_copy.html.erb +2 -2
  9. data/app/components/primer/component.rb +5 -1
  10. data/app/components/primer/image_crop.html.erb +4 -4
  11. data/app/components/primer/label_component.rb +13 -12
  12. data/app/components/primer/navigation/tab_component.rb +30 -2
  13. data/app/components/primer/tab_nav_component.rb +4 -3
  14. data/app/components/primer/truncate.rb +1 -1
  15. data/app/components/primer/underline_nav_component.rb +3 -2
  16. data/app/lib/primer/octicon/cache.rb +1 -1
  17. data/lib/primer/classify.rb +0 -10
  18. data/lib/primer/classify/cache.rb +0 -5
  19. data/lib/primer/classify/utilities.rb +35 -12
  20. data/lib/primer/classify/utilities.yml +16 -0
  21. data/lib/primer/view_components.rb +34 -6
  22. data/lib/primer/view_components/constants.rb +55 -0
  23. data/lib/primer/view_components/linters/argument_mappers/base.rb +74 -0
  24. data/lib/primer/view_components/linters/argument_mappers/button.rb +32 -44
  25. data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +20 -0
  26. data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +24 -0
  27. data/lib/primer/view_components/linters/argument_mappers/label.rb +50 -0
  28. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +4 -1
  29. data/lib/primer/view_components/linters/autocorrectable.rb +30 -0
  30. data/lib/primer/view_components/linters/button_component_migration_counter.rb +9 -23
  31. data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +21 -0
  32. data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +16 -0
  33. data/lib/primer/view_components/linters/helpers.rb +42 -41
  34. data/lib/primer/view_components/linters/label_component_migration_counter.rb +25 -0
  35. data/lib/primer/view_components/version.rb +1 -1
  36. data/lib/rubocop/config/default.yml +12 -0
  37. data/lib/rubocop/cop/primer.rb +4 -0
  38. data/lib/rubocop/cop/primer/no_tag_memoize.rb +42 -0
  39. data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +85 -0
  40. data/lib/tasks/constants.rake +12 -0
  41. data/lib/tasks/docs.rake +24 -23
  42. data/lib/tasks/utilities.rake +4 -10
  43. data/lib/yard/docs_helper.rb +12 -3
  44. data/static/arguments.yml +973 -0
  45. data/static/assets/view-components.svg +18 -0
  46. data/static/classes.yml +174 -0
  47. data/static/constants.json +628 -0
  48. data/static/statuses.json +1 -1
  49. metadata +26 -8
  50. data/app/components/primer/avatar_stack_component.rb +0 -90
@@ -0,0 +1,20 @@
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
+ ATTRIBUTES = %w[value].freeze
12
+
13
+ def attribute_to_args(attribute)
14
+ Helpers::ErbBlock.raise_if_erb_block(attribute)
15
+ { value: attribute.value.to_json }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../conversion_error"
4
+
5
+ module ERBLint
6
+ module Linters
7
+ module ArgumentMappers
8
+ module Helpers
9
+ # provides helpers to identify and deal with ERB blocks.
10
+ class ErbBlock
11
+ class << self
12
+ def raise_if_erb_block(attribute)
13
+ raise ERBLint::Linters::ArgumentMappers::ConversionError, "Cannot convert attribute \"#{attribute.name}\" because its value contains an erb block" if any?(attribute)
14
+ end
15
+
16
+ def any?(attribute)
17
+ attribute.value_node&.children&.any? { |n| n.try(:type) == :erb }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,50 @@
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
+ ATTRIBUTES = %w[title].freeze
28
+
29
+ def attribute_to_args(attribute)
30
+ Helpers::ErbBlock.raise_if_erb_block(attribute)
31
+ { title: attribute.value.to_json }
32
+ end
33
+
34
+ def classes_to_args(classes)
35
+ classes.split(" ").each_with_object({}) do |class_name, acc|
36
+ next if class_name == "Label"
37
+
38
+ if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
39
+ acc[:scheme] = SCHEME_MAPPINGS[class_name]
40
+ elsif VARIANT_MAPPINGS[class_name] && acc[:variant].nil?
41
+ acc[:variant] = VARIANT_MAPPINGS[class_name]
42
+ else
43
+ raise ConversionError, "Cannot convert class \"#{class_name}\""
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "conversion_error"
4
+ require_relative "helpers/erb_block"
4
5
 
5
6
  module ERBLint
6
7
  module Linters
@@ -28,9 +29,11 @@ module ERBLint
28
29
 
29
30
  { test_selector: m[:selector].tr("'", '"') }
30
31
  elsif attr_name == "data-test-selector"
32
+ Helpers::ErbBlock.raise_if_erb_block(attribute)
33
+
31
34
  { test_selector: attribute.value.to_json }
32
35
  elsif attr_name.start_with?(*STRING_PARAMETERS)
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 }
36
+ Helpers::ErbBlock.raise_if_erb_block(attribute)
34
37
 
35
38
  { "\"#{attr_name}\"" => attribute.value.to_json }
36
39
  else
@@ -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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers"
4
+
5
+ module ERBLint
6
+ module Linters
7
+ # Counts the number of times a HTML clipboard-copy is used instead of the component.
8
+ class CloseButtonComponentMigrationCounter < Linter
9
+ include Helpers
10
+
11
+ TAGS = %w[button].freeze
12
+ CLASSES = %w[close-button].freeze
13
+ 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."
14
+ end
15
+ end
16
+ 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
@@ -5,7 +5,7 @@ module Primer
5
5
  module VERSION
6
6
  MAJOR = 0
7
7
  MINOR = 0
8
- PATCH = 47
8
+ PATCH = 51
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH].join(".")
11
11
  end
@@ -0,0 +1,12 @@
1
+ require:
2
+ - rubocop/cop/primer
3
+
4
+ AllCops:
5
+ DisabledByDefault: true
6
+
7
+ Primer/SystemArgumentInsteadOfClass:
8
+ Enabled: true
9
+
10
+ Primer/NoTagMemoize:
11
+ Enabled: false
12
+
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop/cop/primer/no_tag_memoize"
4
+ require "rubocop/cop/primer/system_argument_instead_of_class"
@@ -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