primer_view_components 0.0.60 → 0.0.64
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +155 -1
- data/app/components/primer/alpha/border_box/header.rb +1 -2
- data/app/components/primer/alpha/button_marketing.rb +4 -4
- data/app/components/primer/alpha/layout.html.erb +5 -0
- data/app/components/primer/alpha/layout.rb +276 -0
- data/app/components/primer/alpha/tab_nav.rb +1 -1
- data/app/components/primer/alpha/tab_panels.rb +2 -2
- data/app/components/primer/alpha/underline_nav.rb +2 -1
- data/app/components/primer/alpha/underline_panels.rb +2 -2
- data/app/components/primer/base_button.rb +1 -5
- data/app/components/primer/base_component.rb +14 -37
- data/app/components/primer/beta/auto_complete/item.rb +1 -1
- data/app/components/primer/beta/auto_complete.rb +4 -2
- data/app/components/primer/beta/avatar.rb +1 -1
- data/app/components/primer/beta/blankslate.html.erb +15 -0
- data/app/components/primer/beta/blankslate.rb +241 -0
- data/app/components/primer/beta/breadcrumbs.rb +2 -2
- data/app/components/primer/beta/text.rb +1 -1
- data/app/components/primer/blankslate_component.rb +1 -1
- data/app/components/primer/border_box_component.rb +1 -1
- data/app/components/primer/box_component.rb +3 -2
- data/app/components/primer/button_component.html.erb +3 -9
- data/app/components/primer/button_component.rb +50 -19
- data/app/components/primer/button_group.rb +1 -8
- data/app/components/primer/clipboard_copy.rb +1 -1
- data/app/components/primer/close_button.rb +1 -1
- data/app/components/primer/component.rb +79 -2
- data/app/components/primer/counter_component.rb +1 -1
- data/app/components/primer/details_component.rb +1 -1
- data/app/components/primer/dropdown/menu.rb +1 -1
- data/app/components/primer/dropdown.html.erb +0 -1
- data/app/components/primer/dropdown.rb +1 -0
- data/app/components/primer/dropdown_menu_component.rb +1 -1
- data/app/components/primer/flash_component.rb +2 -1
- data/app/components/primer/flex_component.rb +16 -16
- data/app/components/primer/flex_item_component.rb +1 -1
- data/app/components/primer/hellip_button.rb +39 -0
- data/app/components/primer/hidden_text_expander.rb +19 -7
- data/app/components/primer/image.rb +1 -1
- data/app/components/primer/image_crop.rb +2 -1
- data/app/components/primer/layout_component.rb +1 -0
- data/app/components/primer/local_time.rb +1 -1
- data/app/components/primer/markdown.rb +1 -1
- data/app/components/primer/menu_component.rb +2 -1
- data/app/components/primer/navigation/tab_component.rb +1 -0
- data/app/components/primer/octicon_component.rb +4 -2
- data/app/components/primer/octicon_symbols_component.rb +2 -2
- data/app/components/primer/popover_component.rb +3 -3
- data/app/components/primer/progress_bar_component.rb +7 -6
- data/app/components/primer/spinner_component.html.erb +11 -3
- data/app/components/primer/spinner_component.rb +6 -7
- data/app/components/primer/subhead_component.rb +4 -2
- data/app/components/primer/tab_container_component.rb +1 -1
- data/app/components/primer/time_ago_component.rb +1 -1
- data/app/components/primer/timeline_item_component.rb +4 -3
- data/app/components/primer/tooltip.rb +1 -0
- data/app/lib/primer/octicon/cache.rb +4 -10
- data/lib/primer/classify/utilities.rb +26 -23
- data/lib/primer/classify/utilities.yml +298 -68
- data/lib/primer/classify.rb +94 -180
- data/lib/primer/view_components/engine.rb +2 -1
- data/lib/primer/view_components/linters/base_linter.rb +3 -52
- data/lib/primer/view_components/linters/blankslate_api_migration.rb +152 -0
- data/lib/primer/view_components/linters/blankslate_component_migration_counter.rb +1 -1
- data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +2 -4
- data/lib/primer/view_components/linters/helpers/rubocop_helpers.rb +14 -0
- data/lib/primer/view_components/linters/tag_tree_helpers.rb +61 -0
- data/lib/primer/view_components/linters/two_column_layout_migration_counter.rb +158 -0
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/rubocop/cop/primer/deprecated_layout_component.rb +30 -0
- data/lib/rubocop/cop/primer/primer_octicon.rb +1 -3
- data/lib/tasks/custom_utilities.yml +298 -0
- data/lib/tasks/docs.rake +50 -1
- data/lib/tasks/utilities.rake +6 -2
- data/static/arguments.yml +56 -71
- data/static/audited_at.json +5 -0
- data/static/classes.yml +35 -17
- data/static/constants.json +108 -7
- data/static/statuses.json +6 -1
- metadata +18 -13
- data/app/components/primer/auto_complete/auto_complete.d.ts +0 -1
- data/app/components/primer/auto_complete/auto_complete.js +0 -1
- data/app/components/primer/auto_complete/auto_component.d.ts +0 -1
- data/app/components/primer/auto_complete/auto_component.js +0 -1
- data/lib/primer/classify/cache.rb +0 -109
- data/lib/primer/classify/flex.rb +0 -111
@@ -4,6 +4,8 @@ require "json"
|
|
4
4
|
require "openssl"
|
5
5
|
require "primer/view_components/constants"
|
6
6
|
|
7
|
+
require_relative "tag_tree_helpers"
|
8
|
+
|
7
9
|
# :nocov:
|
8
10
|
|
9
11
|
module ERBLint
|
@@ -14,11 +16,7 @@ module ERBLint
|
|
14
16
|
# * `CLASSES` - optional - The CSS classes that the component needs. The linter will only match elements with one of those classes.
|
15
17
|
# * `REQUIRED_ARGUMENTS` - optional - A list of HTML attributes that are required by the component.
|
16
18
|
class BaseLinter < Linter
|
17
|
-
|
18
|
-
SELF_CLOSING_TAGS = %w[
|
19
|
-
area base br col command embed hr input keygen
|
20
|
-
link menuitem meta param source track wbr img
|
21
|
-
].freeze
|
19
|
+
include TagTreeHelpers
|
22
20
|
|
23
21
|
DUMP_FILE = ".erblint-counter-ignore.json"
|
24
22
|
DISALLOWED_CLASSES = [].freeze
|
@@ -136,53 +134,6 @@ module ERBLint
|
|
136
134
|
end
|
137
135
|
end
|
138
136
|
|
139
|
-
# This assumes that the AST provided represents valid HTML, where each tag has a corresponding closing tag.
|
140
|
-
# From the tags, we build a structured tree which represents the tag hierarchy.
|
141
|
-
# With this, we are able to know where the tags start and end.
|
142
|
-
def build_tag_tree(processed_source)
|
143
|
-
nodes = processed_source.ast.children
|
144
|
-
tag_tree = {}
|
145
|
-
tags = []
|
146
|
-
current_opened_tag = nil
|
147
|
-
|
148
|
-
nodes.each do |node|
|
149
|
-
if node.type == :tag
|
150
|
-
# get the tag from previously calculated list so the references are the same
|
151
|
-
tag = BetterHtml::Tree::Tag.from_node(node)
|
152
|
-
tags << tag
|
153
|
-
|
154
|
-
if tag.closing?
|
155
|
-
if current_opened_tag && tag.name == current_opened_tag.name
|
156
|
-
tag_tree[current_opened_tag][:closing] = tag
|
157
|
-
current_opened_tag = tag_tree[current_opened_tag][:parent]
|
158
|
-
end
|
159
|
-
|
160
|
-
next
|
161
|
-
end
|
162
|
-
|
163
|
-
self_closing = self_closing?(tag)
|
164
|
-
|
165
|
-
tag_tree[tag] = {
|
166
|
-
tag: tag,
|
167
|
-
closing: self_closing ? tag : nil,
|
168
|
-
parent: current_opened_tag,
|
169
|
-
children: []
|
170
|
-
}
|
171
|
-
|
172
|
-
tag_tree[current_opened_tag][:children] << tag_tree[tag] if current_opened_tag
|
173
|
-
current_opened_tag = tag unless self_closing
|
174
|
-
elsif current_opened_tag
|
175
|
-
tag_tree[current_opened_tag][:children] << node
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
[tags, tag_tree]
|
180
|
-
end
|
181
|
-
|
182
|
-
def self_closing?(tag)
|
183
|
-
tag.self_closing? || SELF_CLOSING_TAGS.include?(tag.name)
|
184
|
-
end
|
185
|
-
|
186
137
|
def tags(processed_source)
|
187
138
|
processed_source.parser.nodes_with_type(:tag).map { |tag_node| BetterHtml::Tree::Tag.from_node(tag_node) }
|
188
139
|
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/indent"
|
4
|
+
require_relative "helpers/rubocop_helpers"
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
module Linters
|
8
|
+
# Migrates from `Primer::BlankslateComponent` to `Primer::Beta::Blankslate`.
|
9
|
+
class BlankslateApiMigration < Linter
|
10
|
+
include ERBLint::LinterRegistry
|
11
|
+
include Helpers::RubocopHelpers
|
12
|
+
|
13
|
+
def run(processed_source)
|
14
|
+
processed_source.ast.descendants(:erb).each do |erb_node|
|
15
|
+
_, _, code_node = *erb_node
|
16
|
+
code = code_node.children.first.strip
|
17
|
+
|
18
|
+
next unless code.include?("Primer::BlankslateComponent")
|
19
|
+
# Don't fix custom blankslates
|
20
|
+
next if code.end_with?("do", "|")
|
21
|
+
|
22
|
+
line = erb_node.loc.source_line
|
23
|
+
indent = line.split("<%=").first.size
|
24
|
+
|
25
|
+
ast = erb_ast(code)
|
26
|
+
kwargs = ast.arguments.first.arguments.last
|
27
|
+
|
28
|
+
replacement = build_replacement_blankslate(kwargs, indent)
|
29
|
+
|
30
|
+
add_offense(processed_source.to_source_range(erb_node.loc), "`Primer::BlankslateComponent` is deprecated. `Primer::Beta::Blankslate` should be used instead", replacement)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def autocorrect(_, offense)
|
35
|
+
return unless offense.context
|
36
|
+
|
37
|
+
lambda do |corrector|
|
38
|
+
corrector.replace(offense.source_range, offense.context)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def build_blankslate_arguments(kwargs)
|
45
|
+
new_blankslate = {
|
46
|
+
arguments: {},
|
47
|
+
slots: {
|
48
|
+
visual_icon: {},
|
49
|
+
visual_image: {},
|
50
|
+
heading: {
|
51
|
+
tag: ":h2"
|
52
|
+
},
|
53
|
+
description: {},
|
54
|
+
primary_action: {},
|
55
|
+
secondary_action: {}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
kwargs&.pairs&.each do |pair|
|
60
|
+
source_value = pair.value.source
|
61
|
+
|
62
|
+
case pair.key.value.to_sym
|
63
|
+
when :title
|
64
|
+
new_blankslate[:slots][:heading][:content] = extract_value(pair.value)
|
65
|
+
when :title_tag
|
66
|
+
new_blankslate[:slots][:heading][:tag] = source_value
|
67
|
+
when :icon
|
68
|
+
new_blankslate[:slots][:visual_icon][:icon] = source_value
|
69
|
+
when :icon_size
|
70
|
+
new_blankslate[:slots][:visual_icon][:size] = source_value
|
71
|
+
when :image_src
|
72
|
+
new_blankslate[:slots][:visual_image][:src] = source_value
|
73
|
+
when :image_alt
|
74
|
+
new_blankslate[:slots][:visual_image][:alt] = source_value
|
75
|
+
when :description
|
76
|
+
new_blankslate[:slots][:description][:content] = extract_value(pair.value)
|
77
|
+
when :button_text
|
78
|
+
new_blankslate[:slots][:primary_action][:content] = extract_value(pair.value)
|
79
|
+
when :button_url
|
80
|
+
new_blankslate[:slots][:primary_action][:href] = source_value
|
81
|
+
when :button_classes
|
82
|
+
new_blankslate[:slots][:primary_action][:classes] = source_value
|
83
|
+
when :link_text
|
84
|
+
new_blankslate[:slots][:secondary_action][:content] = extract_value(pair.value)
|
85
|
+
when :link_url
|
86
|
+
new_blankslate[:slots][:secondary_action][:href] = source_value
|
87
|
+
when :large
|
88
|
+
next # Large does not exist anymore
|
89
|
+
else
|
90
|
+
new_blankslate[:arguments][pair.key.source] = source_value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
new_blankslate
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_replacement_blankslate(kwargs, indent)
|
98
|
+
data = build_blankslate_arguments(kwargs)
|
99
|
+
component_args = args_to_s(data[:arguments])
|
100
|
+
|
101
|
+
# If Blankslate has no heading, we don't update it.
|
102
|
+
return if data[:slots][:heading][:content].nil?
|
103
|
+
# If Blankslate sets both image and icon. don't update it.
|
104
|
+
return if data[:slots][:visual_icon].present? && data[:slots][:visual_image].present?
|
105
|
+
|
106
|
+
slots = data[:slots].map do |slot, slot_data|
|
107
|
+
next if slot_data.empty?
|
108
|
+
|
109
|
+
slot_args = args_to_s(slot_data.except(:content))
|
110
|
+
content = slot_data[:content]
|
111
|
+
|
112
|
+
if content
|
113
|
+
<<~HTML.indent(2)
|
114
|
+
<% c.#{slot}#{slot_args} do %>
|
115
|
+
#{content}
|
116
|
+
<% end %>
|
117
|
+
HTML
|
118
|
+
else
|
119
|
+
<<~HTML.indent(2)
|
120
|
+
<% c.#{slot}#{slot_args} %>
|
121
|
+
HTML
|
122
|
+
end
|
123
|
+
end.compact.join("\n").chomp
|
124
|
+
|
125
|
+
# Body needs to match the file indentation.
|
126
|
+
body = <<~HTML.indent(indent).chomp
|
127
|
+
#{slots}
|
128
|
+
<% end %>
|
129
|
+
HTML
|
130
|
+
|
131
|
+
# The render call will always be aligned.
|
132
|
+
"<%= render Primer::Beta::Blankslate.new#{component_args} do |c| %>\n#{body}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def args_to_s(args)
|
136
|
+
string_args = args.except(:__polymorphic_type).map { |k, v| "#{k}: #{v}" }.join(", ")
|
137
|
+
|
138
|
+
string_args = ":#{args[:__polymorphic_type]}, #{string_args}" if args[:__polymorphic_type]
|
139
|
+
|
140
|
+
return string_args if string_args.blank?
|
141
|
+
|
142
|
+
"(#{string_args})"
|
143
|
+
end
|
144
|
+
|
145
|
+
def extract_value(value)
|
146
|
+
return value.value if value.type == :str
|
147
|
+
|
148
|
+
"<%= #{value.source} %>"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -6,7 +6,7 @@ module ERBLint
|
|
6
6
|
module Linters
|
7
7
|
# Counts the number of times a HTML Blankslate is used instead of the component.
|
8
8
|
class BlankslateComponentMigrationCounter < BaseLinter
|
9
|
-
MESSAGE = "We are migrating Blankslate to use [Primer::
|
9
|
+
MESSAGE = "We are migrating Blankslate to use [Primer::Beta::Blankslate](https://primer.style/view-components/components/beta/blankslate), please try to use that instead of raw HTML."
|
10
10
|
CLASSES = %w[blankslate].freeze
|
11
11
|
TAGS = %w[div].freeze
|
12
12
|
end
|
@@ -3,12 +3,14 @@
|
|
3
3
|
require_relative "base_linter"
|
4
4
|
require_relative "autocorrectable"
|
5
5
|
require_relative "argument_mappers/close_button"
|
6
|
+
require_relative "helpers/rubocop_helpers"
|
6
7
|
|
7
8
|
module ERBLint
|
8
9
|
module Linters
|
9
10
|
# Counts the number of times a HTML clipboard-copy is used instead of the component.
|
10
11
|
class CloseButtonComponentMigrationCounter < BaseLinter
|
11
12
|
include Autocorrectable
|
13
|
+
include Helpers::RubocopHelpers
|
12
14
|
|
13
15
|
TAGS = %w[button].freeze
|
14
16
|
CLASSES = %w[close-button].freeze
|
@@ -109,10 +111,6 @@ module ERBLint
|
|
109
111
|
(kwargs.keys.map { |key| key.value.to_s } - ALLOWED_OCTICON_ARGS).present?
|
110
112
|
end
|
111
113
|
|
112
|
-
def erb_ast(code)
|
113
|
-
RuboCop::AST::ProcessedSource.new(code, RUBY_VERSION.to_f).ast
|
114
|
-
end
|
115
|
-
|
116
114
|
def icon(args)
|
117
115
|
return args.first.value.to_sym if args.first.type == :sym || args.first.type == :str
|
118
116
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Linters
|
5
|
+
module Helpers
|
6
|
+
# Provides helpers related to RuboCop.
|
7
|
+
module RubocopHelpers
|
8
|
+
def erb_ast(code)
|
9
|
+
RuboCop::AST::ProcessedSource.new(code, RUBY_VERSION.to_f).ast
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ERBLint
|
4
|
+
module Linters
|
5
|
+
# Helpers used by linters to organize HTML tags into abstract syntax trees.
|
6
|
+
module TagTreeHelpers
|
7
|
+
# from https://github.com/Shopify/erb-lint/blob/6179ee2d9d681a6ec4dd02351a1e30eefa748d3d/lib/erb_lint/linters/self_closing_tag.rb
|
8
|
+
SELF_CLOSING_TAGS = %w[
|
9
|
+
area base br col command embed hr input keygen
|
10
|
+
link menuitem meta param source track wbr img
|
11
|
+
].freeze
|
12
|
+
|
13
|
+
# This assumes that the AST provided represents valid HTML, where each tag has a corresponding closing tag.
|
14
|
+
# From the tags, we build a structured tree which represents the tag hierarchy.
|
15
|
+
# With this, we are able to know where the tags start and end.
|
16
|
+
def build_tag_tree(processed_source)
|
17
|
+
nodes = processed_source.ast.children
|
18
|
+
tag_tree = {}
|
19
|
+
tags = []
|
20
|
+
current_opened_tag = nil
|
21
|
+
|
22
|
+
nodes.each do |node|
|
23
|
+
if node.type == :tag
|
24
|
+
# get the tag from previously calculated list so the references are the same
|
25
|
+
tag = BetterHtml::Tree::Tag.from_node(node)
|
26
|
+
tags << tag
|
27
|
+
|
28
|
+
if tag.closing?
|
29
|
+
if current_opened_tag && tag.name == current_opened_tag.name
|
30
|
+
tag_tree[current_opened_tag][:closing] = tag
|
31
|
+
current_opened_tag = tag_tree[current_opened_tag][:parent]
|
32
|
+
end
|
33
|
+
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
self_closing = self_closing?(tag)
|
38
|
+
|
39
|
+
tag_tree[tag] = {
|
40
|
+
tag: tag,
|
41
|
+
closing: self_closing ? tag : nil,
|
42
|
+
parent: current_opened_tag,
|
43
|
+
children: []
|
44
|
+
}
|
45
|
+
|
46
|
+
tag_tree[current_opened_tag][:children] << tag_tree[tag] if current_opened_tag
|
47
|
+
current_opened_tag = tag unless self_closing
|
48
|
+
elsif current_opened_tag
|
49
|
+
tag_tree[current_opened_tag][:children] << node
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
[tags, tag_tree]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self_closing?(tag)
|
57
|
+
tag.self_closing? || SELF_CLOSING_TAGS.include?(tag.name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_linter"
|
4
|
+
require_relative "tag_tree_helpers"
|
5
|
+
|
6
|
+
module ERBLint
|
7
|
+
module Linters
|
8
|
+
# Counts the number of times a two column layout using col-* CSS classes is used instead of the layout component.
|
9
|
+
class TwoColumnLayoutMigrationCounter < BaseLinter
|
10
|
+
include LinterRegistry
|
11
|
+
include TagTreeHelpers
|
12
|
+
|
13
|
+
WIDTH_RANGE = (8..10).freeze
|
14
|
+
SIDEBAR_WIDTH_RANGE = (2..4).freeze
|
15
|
+
|
16
|
+
CONTAINER_CLASSES = %w[container-xl container-lg container-md container-sm].freeze
|
17
|
+
MESSAGE = "We are migrating two-column layouts to use "\
|
18
|
+
"[Primer::Alpha::Layout](https://primer.style/view-components/components/layout), "\
|
19
|
+
"please use that instead of raw HTML."
|
20
|
+
|
21
|
+
# :nodoc:
|
22
|
+
class Breakpoints
|
23
|
+
LABELS = %i[all sm md lg xl].freeze
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@map = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def set(breakpoint, value)
|
30
|
+
@map[breakpoint] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def min
|
34
|
+
LABELS.find { |label| @map[label] } || :all
|
35
|
+
end
|
36
|
+
|
37
|
+
def min_value
|
38
|
+
@map[min]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# :nodoc:
|
43
|
+
class Column
|
44
|
+
attr_reader :widths, :tag_tree
|
45
|
+
|
46
|
+
def initialize(widths, tag_tree)
|
47
|
+
@widths = widths
|
48
|
+
@tag_tree = tag_tree
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# :nodoc:
|
53
|
+
class Container
|
54
|
+
attr_reader :columns
|
55
|
+
|
56
|
+
def initialize(columns)
|
57
|
+
@columns = columns
|
58
|
+
end
|
59
|
+
|
60
|
+
def sidebar
|
61
|
+
sorted_columns.first
|
62
|
+
end
|
63
|
+
|
64
|
+
def main
|
65
|
+
sorted_columns.last
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def sorted_columns
|
71
|
+
@sorted_columns ||= columns.sort_by do |col|
|
72
|
+
col.widths.min_value || 0
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def run(processed_source)
|
78
|
+
@total_offenses = 0
|
79
|
+
@offenses_not_corrected = 0
|
80
|
+
|
81
|
+
tags, tag_tree = build_tag_tree(processed_source)
|
82
|
+
|
83
|
+
tags.each do |tag|
|
84
|
+
next if tag.closing?
|
85
|
+
next unless tag.name == "div"
|
86
|
+
|
87
|
+
classes = classes_from(tag)
|
88
|
+
next if (CONTAINER_CLASSES & classes).empty?
|
89
|
+
|
90
|
+
next unless metadata_from(tag_tree[tag])
|
91
|
+
|
92
|
+
@total_offenses += 1
|
93
|
+
@offenses_not_corrected += 1
|
94
|
+
|
95
|
+
generate_offense(self.class, processed_source, tag, MESSAGE)
|
96
|
+
end
|
97
|
+
|
98
|
+
counter_correct?(processed_source)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def metadata_from(tag_tree)
|
104
|
+
tags = tag_tree[:children].select { |c| c.is_a?(Hash) }
|
105
|
+
|
106
|
+
if d_flex?(tags)
|
107
|
+
container_from(tags.first)
|
108
|
+
else
|
109
|
+
container_from(tag_tree)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def d_flex?(tags)
|
114
|
+
tags.size == 1 && classes_from(tags.first[:tag]).include?("d-flex")
|
115
|
+
end
|
116
|
+
|
117
|
+
def container_from(columns_tag_tree)
|
118
|
+
columns = columns_from(columns_tag_tree)
|
119
|
+
return unless columns.size == 2
|
120
|
+
|
121
|
+
container = Container.new(columns)
|
122
|
+
|
123
|
+
main_min = container.main.widths.min_value
|
124
|
+
sidebar_min = container.sidebar.widths.min_value
|
125
|
+
return unless sidebar_min && main_min
|
126
|
+
return unless WIDTH_RANGE.include?(main_min)
|
127
|
+
return unless SIDEBAR_WIDTH_RANGE.include?(sidebar_min)
|
128
|
+
|
129
|
+
container
|
130
|
+
end
|
131
|
+
|
132
|
+
def columns_from(tag_tree)
|
133
|
+
tag_tree[:children].each_with_object([]) do |tag_data, tags_memo|
|
134
|
+
next unless tag_data.is_a?(Hash)
|
135
|
+
next unless tag_data[:tag].name == "div"
|
136
|
+
|
137
|
+
classes = classes_from(tag_data[:tag])
|
138
|
+
widths = Breakpoints.new
|
139
|
+
|
140
|
+
classes.each do |cls|
|
141
|
+
match = cls.match(/\Acol(?:-(xl|lg|md|sm))?-(\d{1,2})(?:-max)?\z/)
|
142
|
+
next unless match
|
143
|
+
|
144
|
+
breakpoint, width = match.captures
|
145
|
+
breakpoint ||= :all
|
146
|
+
widths.set(breakpoint.to_sym, width.to_i)
|
147
|
+
end
|
148
|
+
|
149
|
+
tags_memo << Column.new(widths, tag_data)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def classes_from(tag)
|
154
|
+
tag.attributes["class"]&.value&.split(" ") || []
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module Primer
|
8
|
+
# This cop ensures that the deprecated `Primer::LayoutComponent` isn't used.
|
9
|
+
#
|
10
|
+
# bad
|
11
|
+
# Primer::LayoutComponent.new(foo: :deprecated)
|
12
|
+
#
|
13
|
+
# good
|
14
|
+
# Primer::Alpha::Layout.new(foo: :deprecated)
|
15
|
+
class DeprecatedLayoutComponent < BaseCop
|
16
|
+
MSG = "Please try Primer::Alpha::Layout instead."
|
17
|
+
|
18
|
+
def_node_matcher :legacy_component?, <<~PATTERN
|
19
|
+
(send (const (const nil? :Primer) :LayoutComponent) :new ...)
|
20
|
+
PATTERN
|
21
|
+
|
22
|
+
def on_send(node)
|
23
|
+
return unless legacy_component?(node)
|
24
|
+
|
25
|
+
add_offense(node, message: MSG)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|