Skip to content

Commit f9cba85

Browse files
authored
Add support for listing top architecture violations (#104)
* Merge list_top_X_violations commands into a single list_top_violations DRYer code. * Add support for listing top architecture violations
1 parent 6b9e818 commit f9cba85

8 files changed

Lines changed: 77 additions & 173 deletions

File tree

README.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,21 +49,16 @@ modify packs/from_pack/package.yml's list of dependencies and add packs/to_pack.
4949

5050
This command will also sort the list and make it unique.
5151

52-
## List the top dependency violations of packs/your_pack
53-
`bin/packs list_top_dependency_violations packs/your_pack`
52+
## List the top violations of a specific type for packs/your_pack.
53+
`bin/packs list_top_violations type [ packs/your_pack ]`
5454

55-
Want to see who is depending on you? Not sure how your pack's code is being used in an unstated way
55+
Possible types are: dependency, privacy, architecture.
5656

57-
You can use this command to list the top dependency violations.
57+
Want to see who is depending on you? Not sure how your pack's code is being used in an unstated way? You can use this command to list the top dependency violations.
5858

59-
If no pack name is passed in, this will list out violations across all packs.
60-
61-
## List the top privacy violations of packs/your_pack
62-
`bin/packs list_top_privacy_violations packs/your_pack`
63-
64-
Want to create interfaces? Not sure how your pack's code is being used?
59+
Want to create interfaces? Not sure how your pack's code is being used? You can use this command to list the top privacy violations.
6560

66-
You can use this command to list the top privacy violations.
61+
Want to focus on the big picture first? You can use this command to list the top architecture violations.
6762

6863
If no pack name is passed in, this will list out violations across all packs.
6964

lib/packs.rb

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -186,31 +186,18 @@ def self.move_to_parent!(
186186

187187
sig do
188188
params(
189+
type: String,
189190
pack_name: T.nilable(String),
190191
limit: Integer
191192
).void
192193
end
193-
def self.list_top_privacy_violations(
194+
def self.list_top_violations(
195+
type:,
194196
pack_name:,
195197
limit:
196198
)
197-
Private::PackRelationshipAnalyzer.list_top_privacy_violations(
198-
pack_name,
199-
limit
200-
)
201-
end
202-
203-
sig do
204-
params(
205-
pack_name: T.nilable(String),
206-
limit: Integer
207-
).void
208-
end
209-
def self.list_top_dependency_violations(
210-
pack_name:,
211-
limit:
212-
)
213-
Private::PackRelationshipAnalyzer.list_top_dependency_violations(
199+
Private::PackRelationshipAnalyzer.list_top_violations(
200+
type,
214201
pack_name,
215202
limit
216203
)

lib/packs/cli.rb

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,36 +31,31 @@ def add_dependency(from_pack, to_pack)
3131
exit_successfully
3232
end
3333

34-
desc 'list_top_dependency_violations packs/your_pack', 'List the top dependency violations of packs/your_pack'
34+
POSIBLE_TYPES = T.let(%w[dependency privacy architecture], T::Array[String])
35+
desc 'list_top_violations type [ packs/your_pack ]', 'List the top violations of a specific type for packs/your_pack.'
3536
long_desc <<~LONG_DESC
36-
Want to see who is depending on you? Not sure how your pack's code is being used in an unstated way
37+
Possible types are: #{POSIBLE_TYPES.join(', ')}.
3738
38-
You can use this command to list the top dependency violations.
39+
Want to see who is depending on you? Not sure how your pack's code is being used in an unstated way? You can use this command to list the top dependency violations.
3940
40-
If no pack name is passed in, this will list out violations across all packs.
41-
LONG_DESC
42-
option :limit, type: :numeric, default: 10, aliases: :l, banner: 'Specify the limit of constants to analyze'
43-
sig { params(pack_name: String).void }
44-
def list_top_dependency_violations(pack_name)
45-
Packs.list_top_dependency_violations(
46-
pack_name: pack_name,
47-
limit: options[:limit]
48-
)
49-
exit_successfully
50-
end
51-
52-
desc 'list_top_privacy_violations packs/your_pack', 'List the top privacy violations of packs/your_pack'
53-
long_desc <<~LONG_DESC
54-
Want to create interfaces? Not sure how your pack's code is being used?
41+
Want to create interfaces? Not sure how your pack's code is being used? You can use this command to list the top privacy violations.
5542
56-
You can use this command to list the top privacy violations.
43+
Want to focus on the big picture first? You can use this command to list the top architecture violations.
5744
5845
If no pack name is passed in, this will list out violations across all packs.
5946
LONG_DESC
6047
option :limit, type: :numeric, default: 10, aliases: :l, banner: 'Specify the limit of constants to analyze'
61-
sig { params(pack_name: String).void }
62-
def list_top_privacy_violations(pack_name)
63-
Packs.list_top_privacy_violations(
48+
sig do
49+
params(
50+
type: String,
51+
pack_name: T.nilable(String)
52+
).void
53+
end
54+
def list_top_violations(type, pack_name = nil)
55+
raise StandardError, "Invalid type #{type}. Possible types are: #{POSIBLE_TYPES.join(', ')}" unless POSIBLE_TYPES.include?(type)
56+
57+
Packs.list_top_violations(
58+
type: type,
6459
pack_name: pack_name,
6560
limit: options[:limit]
6661
)

lib/packs/private/interactive_cli/use_cases/query.rb

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def user_facing_name
2121
def perform!(prompt)
2222
selection = prompt.select('For one pack or all packs?', ['One pack', 'All packs'])
2323
if selection == 'All packs'
24-
# Probably should just make `list_top_dependency_violations` take in an array of things
24+
# Probably should just make `list_top_violations` take in an array of things
2525
# Better yet we might just want to replace these functions with `QueryPackwerk`
2626
selected_pack = nil
2727
else
@@ -30,19 +30,13 @@ def perform!(prompt)
3030

3131
limit = prompt.ask('Specify the limit of constants to analyze', default: 10, convert: :integer)
3232

33-
selection = prompt.select('Are you interested in dependency or privacy violations?', %w[Dependency Privacy], default: 'Privacy')
33+
selection = prompt.select('Are you interested in dependency, or privacy violations?', %w[Dependency Privacy Architecture], default: 'Privacy')
3434

35-
if selection == 'Dependency'
36-
Packs.list_top_dependency_violations(
37-
pack_name: selected_pack,
38-
limit: limit
39-
)
40-
else
41-
Packs.list_top_privacy_violations(
42-
pack_name: selected_pack,
43-
limit: limit
44-
)
45-
end
35+
Packs.list_top_violations(
36+
type: selection.downcase,
37+
pack_name: selected_pack,
38+
limit: limit
39+
)
4640
end
4741
end
4842
end

lib/packs/private/pack_relationship_analyzer.rb

Lines changed: 4 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,74 +7,12 @@ module PackRelationshipAnalyzer
77

88
sig do
99
params(
10+
type: String,
1011
pack_name: T.nilable(String),
1112
limit: Integer
1213
).void
1314
end
14-
def self.list_top_privacy_violations(pack_name, limit)
15-
all_packages = ParsePackwerk.all
16-
if pack_name.nil?
17-
to_package_names = all_packages.map(&:name)
18-
else
19-
pack_name = Private.clean_pack_name(pack_name)
20-
package = all_packages.find { |p| p.name == pack_name }
21-
if package.nil?
22-
raise StandardError, "Can not find package with name #{pack_name}. Make sure the argument is of the form `packs/my_pack/`"
23-
end
24-
25-
to_package_names = [pack_name]
26-
end
27-
28-
violations_by_count = {}
29-
total_pack_violation_count = 0
30-
31-
Logging.section('👋 Hi there') do
32-
intro = Packs.config.user_event_logger.before_list_top_privacy_violations(pack_name, limit)
33-
Logging.print_bold_green(intro)
34-
end
35-
36-
# TODO: This is a copy of the implementation below. We may want to refactor out this implementation detail before making changes that apply to both.
37-
all_packages.each do |client_package|
38-
client_package.violations.select(&:privacy?).each do |violation|
39-
next unless to_package_names.include?(violation.to_package_name)
40-
41-
if pack_name.nil?
42-
violated_symbol = "#{violation.class_name} (#{violation.to_package_name})"
43-
else
44-
violated_symbol = violation.class_name
45-
end
46-
violations_by_count[violated_symbol] ||= {}
47-
violations_by_count[violated_symbol][:total_count] ||= 0
48-
violations_by_count[violated_symbol][:by_package] ||= {}
49-
violations_by_count[violated_symbol][:by_package][client_package.name] ||= 0
50-
violations_by_count[violated_symbol][:total_count] += violation.files.count
51-
violations_by_count[violated_symbol][:by_package][client_package.name] += violation.files.count
52-
total_pack_violation_count += violation.files.count
53-
end
54-
end
55-
56-
Logging.print("Total Count: #{total_pack_violation_count}")
57-
58-
sorted_violations = violations_by_count.sort_by { |violated_symbol, count_info| [-count_info[:total_count], violated_symbol] }
59-
sorted_violations.first(limit).each do |violated_symbol, count_info|
60-
percentage_of_total = (count_info[:total_count] * 100.0 / total_pack_violation_count).round(2)
61-
Logging.print(violated_symbol)
62-
Logging.print(" - Total Count: #{count_info[:total_count]} (#{percentage_of_total}% of total)")
63-
64-
Logging.print(' - By package:')
65-
count_info[:by_package].sort_by { |client_package_name, count| [-count, client_package_name] }.each do |client_package_name, count|
66-
Logging.print(" - #{client_package_name}: #{count}")
67-
end
68-
end
69-
end
70-
71-
sig do
72-
params(
73-
pack_name: T.nilable(String),
74-
limit: Integer
75-
).void
76-
end
77-
def self.list_top_dependency_violations(pack_name, limit)
15+
def self.list_top_violations(type, pack_name, limit)
7816
all_packages = ParsePackwerk.all
7917

8018
if pack_name.nil?
@@ -90,16 +28,15 @@ def self.list_top_dependency_violations(pack_name, limit)
9028
end
9129

9230
Logging.section('👋 Hi there') do
93-
intro = Packs.config.user_event_logger.before_list_top_dependency_violations(pack_name, limit)
31+
intro = Packs.config.user_event_logger.before_list_top_violations(type, pack_name, limit)
9432
Logging.print_bold_green(intro)
9533
end
9634

9735
violations_by_count = {}
9836
total_pack_violation_count = 0
9937

100-
# TODO: This is a copy of the implementation above. We may want to refactor out this implementation detail before making changes that apply to both.
10138
all_packages.each do |client_package|
102-
client_package.violations.select(&:dependency?).each do |violation|
39+
client_package.violations.select { _1.type == type }.each do |violation|
10340
next unless to_package_names.include?(violation.to_package_name)
10441

10542
if pack_name.nil?

lib/packs/user_event_logger.rb

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def on_create_public_directory_todo(pack_name)
117117
You can prevent other packs from using private API by using packwerk.
118118
119119
Want to find how your private API is being used today?
120-
Try running: `bin/packs list_top_privacy_violations #{pack_name}`
120+
Try running: `bin/packs list_top_violations privacy #{pack_name}`
121121
122122
Want to move something into this folder?
123123
Try running: `bin/packs make_public #{pack_name}/path/to/file.rb`
@@ -149,41 +149,20 @@ def on_create_readme_todo(pack_name)
149149
MSG
150150
end
151151

152-
sig { params(pack_name: T.nilable(String), limit: Integer).returns(String) }
153-
def before_list_top_dependency_violations(pack_name, limit)
152+
sig { params(type: String, pack_name: T.nilable(String), limit: Integer).returns(String) }
153+
def before_list_top_violations(type, pack_name, limit)
154154
if pack_name.nil?
155155
<<~PACK_CONTENT
156-
You are listing top #{limit} dependency violations for all packs. See #{documentation_link} for other utilities!
157-
Pass in a limit to display more or less, e.g. `bin/packs list_top_dependency_violations #{pack_name} -l 1000`
156+
You are listing top #{limit} #{type} violations for all packs. See #{documentation_link} for other utilities!
157+
Pass in a limit to display more or less, e.g. `bin/packs list_top_violations #{type} #{pack_name} -l 1000`
158158
159159
This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
160160
Anything not in pack_name/app/public is considered private API.
161161
PACK_CONTENT
162162
else
163163
<<~PACK_CONTENT
164-
You are listing top #{limit} dependency violations for #{pack_name}. See #{documentation_link} for other utilities!
165-
Pass in a limit to display more or less, e.g. `bin/packs list_top_dependency_violations #{pack_name} -l 1000`
166-
167-
This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
168-
Anything not in #{pack_name}/app/public is considered private API.
169-
PACK_CONTENT
170-
end
171-
end
172-
173-
sig { params(pack_name: T.nilable(String), limit: Integer).returns(String) }
174-
def before_list_top_privacy_violations(pack_name, limit)
175-
if pack_name.nil?
176-
<<~PACK_CONTENT
177-
You are listing top #{limit} privacy violations for all packs. See #{documentation_link} for other utilities!
178-
Pass in a limit to display more or less, e.g. `bin/packs list_top_privacy_violations #{pack_name} -l 1000`
179-
180-
This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
181-
Anything not in pack_name/app/public is considered private API.
182-
PACK_CONTENT
183-
else
184-
<<~PACK_CONTENT
185-
You are listing top #{limit} privacy violations for #{pack_name}. See #{documentation_link} for other utilities!
186-
Pass in a limit to display more or less, e.g. `bin/packs list_top_privacy_violations #{pack_name} -l 1000`
164+
You are listing top #{limit} #{type} violations for #{pack_name}. See #{documentation_link} for other utilities!
165+
Pass in a limit to display more or less, e.g. `bin/packs list_top_violations #{type} #{pack_name} -l 1000`
187166
188167
This script is intended to help you find which of YOUR pack's private classes, constants, or modules other packs are using the most.
189168
Anything not in #{pack_name}/app/public is considered private API.

spec/packs/private/cli_spec.rb

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,36 @@ def expect_failure
3232
end
3333
end
3434

35-
describe '#list_top_dependency_violations' do
35+
describe '#list_top_violations for dependency' do
3636
it 'lists the top dependency violations' do
37-
expect(Packs).to receive(:list_top_dependency_violations).with(
37+
expect(Packs).to receive(:list_top_violations).with(
38+
type: 'dependency',
3839
pack_name: 'packs/your_pack',
3940
limit: 10
4041
)
41-
described_class.start(['list_top_dependency_violations', 'packs/your_pack'])
42+
described_class.start(['list_top_violations', 'dependency', 'packs/your_pack'])
4243
end
4344
end
4445

45-
describe '#list_top_privacy_violations' do
46+
describe '#list_top_violations for privacy' do
4647
it 'lists the top privacy violations' do
47-
expect(Packs).to receive(:list_top_privacy_violations).with(
48+
expect(Packs).to receive(:list_top_violations).with(
49+
type: 'privacy',
4850
pack_name: 'packs/your_pack',
4951
limit: 10
5052
)
51-
described_class.start(['list_top_privacy_violations', 'packs/your_pack'])
53+
described_class.start(['list_top_violations', 'privacy', 'packs/your_pack'])
54+
end
55+
end
56+
57+
describe '#list_top_violations for architecture' do
58+
it 'lists the top architecture violations' do
59+
expect(Packs).to receive(:list_top_violations).with(
60+
type: 'architecture',
61+
pack_name: 'packs/your_pack',
62+
limit: 10
63+
)
64+
described_class.start(['list_top_violations', 'architecture', 'packs/your_pack'])
5265
end
5366
end
5467

0 commit comments

Comments
 (0)