primer_view_components 0.0.46 → 0.0.50

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +144 -0
  3. data/app/components/primer/base_component.rb +2 -2
  4. data/app/components/primer/beta/auto_complete.rb +159 -0
  5. data/app/components/primer/beta/auto_complete/auto_complete.d.ts +1 -0
  6. data/app/components/primer/{auto_complete → beta/auto_complete}/auto_complete.html.erb +0 -0
  7. data/app/components/primer/beta/auto_complete/auto_complete.js +1 -0
  8. data/app/components/primer/{auto_complete → beta/auto_complete}/auto_complete.ts +0 -0
  9. data/app/components/primer/beta/auto_complete/item.rb +44 -0
  10. data/app/components/primer/beta/avatar.rb +77 -0
  11. data/app/components/primer/{avatar_stack_component.html.erb → beta/avatar_stack.html.erb} +0 -0
  12. data/app/components/primer/beta/avatar_stack.rb +92 -0
  13. data/app/components/primer/clipboard_copy.html.erb +2 -2
  14. data/app/components/primer/component.rb +5 -1
  15. data/app/components/primer/details_component.rb +7 -7
  16. data/app/components/primer/image_crop.html.erb +4 -4
  17. data/app/components/primer/label_component.rb +13 -12
  18. data/app/components/primer/markdown.rb +9 -9
  19. data/app/components/primer/navigation/tab_component.rb +30 -2
  20. data/app/components/primer/popover_component.rb +6 -3
  21. data/app/components/primer/primer.d.ts +1 -1
  22. data/app/components/primer/primer.js +1 -1
  23. data/app/components/primer/primer.ts +1 -1
  24. data/app/components/primer/tab_nav_component.rb +4 -3
  25. data/app/components/primer/timeline_item_component.rb +2 -2
  26. data/app/components/primer/truncate.rb +6 -1
  27. data/app/components/primer/underline_nav_component.rb +4 -3
  28. data/app/lib/primer/octicon/cache.rb +1 -1
  29. data/lib/primer/classify.rb +4 -18
  30. data/lib/primer/classify/cache.rb +0 -5
  31. data/lib/primer/classify/utilities.rb +54 -22
  32. data/lib/primer/classify/utilities.yml +16 -0
  33. data/lib/primer/view_components.rb +34 -6
  34. data/lib/primer/view_components/constants.rb +55 -0
  35. data/lib/primer/view_components/linters/argument_mappers/base.rb +74 -0
  36. data/lib/primer/view_components/linters/argument_mappers/button.rb +32 -44
  37. data/lib/primer/view_components/linters/argument_mappers/clipboard_copy.rb +20 -0
  38. data/lib/primer/view_components/linters/argument_mappers/helpers/erb_block.rb +24 -0
  39. data/lib/primer/view_components/linters/argument_mappers/label.rb +50 -0
  40. data/lib/primer/view_components/linters/argument_mappers/system_arguments.rb +4 -1
  41. data/lib/primer/view_components/linters/autocorrectable.rb +30 -0
  42. data/lib/primer/view_components/linters/button_component_migration_counter.rb +9 -23
  43. data/lib/primer/view_components/linters/clipboard_copy_component_migration_counter.rb +21 -0
  44. data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +16 -0
  45. data/lib/primer/view_components/linters/helpers.rb +56 -38
  46. data/lib/primer/view_components/linters/label_component_migration_counter.rb +25 -0
  47. data/lib/primer/view_components/statuses.rb +14 -0
  48. data/lib/primer/view_components/version.rb +1 -1
  49. data/lib/rubocop/config/default.yml +12 -0
  50. data/lib/rubocop/cop/primer.rb +4 -0
  51. data/lib/rubocop/cop/primer/no_tag_memoize.rb +42 -0
  52. data/lib/rubocop/cop/primer/system_argument_instead_of_class.rb +75 -0
  53. data/lib/tasks/constants.rake +12 -0
  54. data/lib/tasks/docs.rake +87 -32
  55. data/lib/tasks/utilities.rake +4 -10
  56. data/lib/yard/docs_helper.rb +12 -3
  57. data/static/arguments.yml +973 -0
  58. data/static/assets/view-components.svg +18 -0
  59. data/static/classes.yml +174 -0
  60. data/static/constants.json +628 -0
  61. data/static/statuses.json +5 -5
  62. metadata +34 -13
  63. data/app/components/primer/auto_complete.rb +0 -157
  64. data/app/components/primer/auto_complete/item.rb +0 -42
  65. data/app/components/primer/avatar_component.rb +0 -75
  66. data/app/components/primer/avatar_stack_component.rb +0 -90
@@ -1,66 +1,54 @@
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
+ ATTRIBUTES = %w[disabled type].freeze
38
34
 
39
- @tag.attributes.each do |attribute|
40
- attr_name = attribute.name
35
+ def attribute_to_args(attribute)
36
+ attr_name = attribute.name
41
37
 
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"
38
+ if 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"
49
43
 
50
- raise ConversionError, "Button component does not support type \"#{attribute.value}\"" unless TYPE_OPTIONS.include?(attribute.value)
44
+ raise ConversionError, "Button component does not support type \"#{attribute.value}\"" unless TYPE_OPTIONS.include?(attribute.value)
51
45
 
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}" }
57
47
  end
58
-
59
- args
60
48
  end
61
49
 
62
50
  def classes_to_args(classes)
63
- classes.value.split(" ").each_with_object({}) do |class_name, acc|
51
+ classes.split(" ").each_with_object({}) do |class_name, acc|
64
52
  next if class_name == "btn"
65
53
 
66
54
  if SCHEME_MAPPINGS[class_name] && acc[:scheme].nil?
@@ -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
@@ -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
- define_method "run" do |processed_source|
20
- @offenses_not_corrected = 0
21
- tags = tags(processed_source)
22
- tag_tree = build_tag_tree(tags)
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
- classes = tag.attributes["class"]&.value&.split(" ") || []
29
+ tags.each do |tag|
30
+ next if tag.closing?
31
+ next unless self.class::TAGS&.include?(tag.name)
29
32
 
30
- tag_tree[tag][:offense] = false
33
+ classes = tag.attributes["class"]&.value&.split(" ") || []
31
34
 
32
- next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
35
+ tag_tree[tag][:offense] = false
33
36
 
34
- args = map_arguments(tag)
35
- correction = correction(args)
37
+ next unless self.class::CLASSES.blank? || (classes & self.class::CLASSES).any?
36
38
 
37
- tag_tree[tag][:offense] = true
38
- tag_tree[tag][:correctable] = !correction.nil?
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.each do |tag, h|
44
- next unless h[:offense]
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
- # We always fix the offenses using blocks. The closing tag corresponds to `<% end %>`.
47
- if h[:correctable]
48
- add_offense(tag.loc, h[:message], h[:correction])
49
- add_offense(h[:closing].loc, h[:message], "<% end %>")
50
- else
51
- @offenses_not_corrected += 1
52
- generate_offense(self.class, processed_source, tag, h[:message])
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
- define_method "autocorrect" do |processed_source, offense|
60
- return unless offense.context
62
+ counter_correct?(processed_source)
61
63
 
62
- lambda do |corrector|
63
- if offense.context.include?(counter_disable)
64
- correct_counter(corrector, processed_source, offense)
65
- else
66
- corrector.replace(offense.source_range, offense.context)
67
- end
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