-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement a count-offenses
command
#340
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,8 @@ def execute_command(args) | |
output_result(parse_run(args).check) | ||
when "update-todo", "update" | ||
output_result(parse_run(args).update_todo) | ||
when "show-offenses", "show" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if violations or offense is the right wording here. We should be more consistent with out wording internally. Since we've already gotten things like "offense formatters", this is probably fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree it would be nice to define the gem's vocabulary more precisely. It would not only help with understanding how to use it, but also make reading/writing code easier. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Big plus for this! We call these three things:
IMO violations and offenses are somewhat aggressive terms and TODO captures the desire to change/fix while being less aggressive and also shorter to write! I'd be in support of changing all language across the board to What do you all think? (We could start a discussion too.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I agree with using the word TODO for all.
I think we could use the same word for violations and offenses, but would not conflate that concept with todo.
I have a hunch that there might have been the intent to give violation and offense slightly different meanings, but 1. I'm not sure I'm right and 2. this might not be necessary, or could be expressed more explicitly:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interestingly, even though offense is used more often in the source code, violation (at least at Gusto) is used much more often by the client. Not sure if it's the same at Shopify, but here everyone uses the word violation, and it's probably in part because the word I can buy the different use cases for the words. Something is an offense for example in packwerk if there's a file that can't be parsed correctly. A violation is an offense, but is also what happens when everything works correctly in packwerk, but there's a "violation" of packwerk directives (e.g. dependency usage, private API usage, etc.). TODOs are recorded violations, perhaps short for "violations TODO." I'm not sure if all of this distinction is useful to the client using this system though, but also I haven't really heard of many folks confused about these terms besides us so perhaps it's not a problem! I'm just glad we replaced |
||
output_result(parse_run(args).show_offenses) | ||
when "validate" | ||
validate(args) | ||
when "version" | ||
|
@@ -118,6 +120,7 @@ def usage | |
Subcommands: | ||
init - set up packwerk | ||
check - run all checks | ||
show-offenses - displays all offenses | ||
update-todo - update package_todo.yml files | ||
validate - verify integrity of packwerk and package configuration | ||
version - output packwerk version | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,6 @@ def initialize(name:, config: nil) | |
@name = name | ||
@config = T.let(config || {}, T::Hash[String, T.untyped]) | ||
@dependencies = T.let(Array(@config["dependencies"]).freeze, T::Array[String]) | ||
@public_path = T.let(nil, T.nilable(String)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is never used. |
||
end | ||
|
||
sig { returns(T::Boolean) } | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,7 +46,10 @@ def update_todo | |
end | ||
|
||
run_context = RunContext.from_configuration(@configuration) | ||
offense_collection = find_offenses(run_context) | ||
offenses = find_offenses(run_context) { update_progress } | ||
|
||
offense_collection = OffenseCollection.new(@configuration.root_path) | ||
offense_collection.add_offenses(offenses) | ||
offense_collection.persist_package_todo_files(run_context.package_set) | ||
|
||
message = <<~EOS | ||
|
@@ -57,10 +60,25 @@ def update_todo | |
Cli::Result.new(message: message, status: offense_collection.errors.empty?) | ||
end | ||
|
||
sig { returns(Cli::Result) } | ||
def show_offenses | ||
run_context = RunContext.from_configuration(@configuration) | ||
all_offenses = find_offenses(run_context) | ||
|
||
message = @offenses_formatter.show_offenses(all_offenses) | ||
|
||
Cli::Result.new(message: message, status: true) | ||
gmcgibbon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
sig { returns(Cli::Result) } | ||
def check | ||
run_context = RunContext.from_configuration(@configuration) | ||
offense_collection = find_offenses(run_context, show_errors: true) | ||
offense_collection = OffenseCollection.new(@configuration.root_path) | ||
offenses = find_offenses(run_context) do |offenses| | ||
failed = offenses.any? { |offense| !offense_collection.listed?(offense) } | ||
update_progress(failed: failed) | ||
end | ||
offense_collection.add_offenses(offenses) | ||
|
||
messages = [ | ||
@offenses_formatter.show_offenses(offense_collection.outstanding_offenses), | ||
|
@@ -76,16 +94,25 @@ def check | |
|
||
private | ||
|
||
sig { params(run_context: RunContext, show_errors: T::Boolean).returns(OffenseCollection) } | ||
def find_offenses(run_context, show_errors: false) | ||
offense_collection = OffenseCollection.new(@configuration.root_path) | ||
sig do | ||
params( | ||
run_context: RunContext, | ||
block: T.nilable(T.proc.params( | ||
offenses: T::Array[Packwerk::Offense], | ||
).void) | ||
).returns(T::Array[Offense]) | ||
end | ||
def find_offenses(run_context, &block) | ||
all_offenses = T.let([], T::Array[Offense]) | ||
process_file = T.let(->(relative_file) do | ||
run_context.process_file(relative_file: relative_file).tap do |offenses| | ||
failed = show_errors && offenses.any? { |offense| !offense_collection.listed?(offense) } | ||
update_progress(failed: failed) | ||
end | ||
end, ProcessFileProc) | ||
process_file = if block | ||
T.let(proc do |relative_file| | ||
run_context.process_file(relative_file: relative_file).tap(&block) | ||
end, ProcessFileProc) | ||
else | ||
T.let(proc do |relative_file| | ||
run_context.process_file(relative_file: relative_file) | ||
end, ProcessFileProc) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move this to a private method? This is slightly repetitive, but I think it is justified with how many times this gets called. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean something like this? def process_file_proc(&block)
if block
T.let(proc do |relative_file|
run_context.process_file(relative_file: relative_file).tap(&block)
end, ProcessFileProc)
else
T.let(proc do |relative_file|
run_context.process_file(relative_file: relative_file)
end, ProcessFileProc)
end
end
# Then I can call it where it's used:
# ...
@progress_formatter.started_inspection(@relative_file_set) do
all_offenses = if @configuration.parallel?
Parallel.flat_map(@relative_file_set, &process_file_proc(&block))
else
serial_find_offenses(&process_file_proc(&block))
end
end
# ...
# (Or I can keep a local variable if that seems better.) |
||
|
||
@progress_formatter.started_inspection(@relative_file_set) do | ||
all_offenses = if @configuration.parallel? | ||
|
@@ -95,8 +122,7 @@ def find_offenses(run_context, show_errors: false) | |
end | ||
end | ||
|
||
all_offenses.each { |offense| offense_collection.add_offense(offense) } | ||
offense_collection | ||
all_offenses | ||
end | ||
|
||
sig { params(block: ProcessFileProc).returns(T::Array[Offense]) } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This points to a non-existent entry in the doc.