danger-packwerk 0.7.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51e9864c6b966cd4b115e469a1f9fc2c2e316fd634590d43eea1e5030b353f0c
4
- data.tar.gz: fbe8f70cb7c3d79017ea379ba7d8ec6fae2961fba98bb254f6b9ff99b1fa30be
3
+ metadata.gz: cf8895f7ae2e94e0b1d09e146176b6777ba8f952fe03381380071114fcec2bd4
4
+ data.tar.gz: 2e68e2096375433cfad68d85ca8d5eac7855dcb542b34f79417a53b1e4c9eb1b
5
5
  SHA512:
6
- metadata.gz: 8cff9b5c28a2357e3c910cfa3c4ee0fd502267950165422ad95c74a07fd0f01f57c613e092670cc41de4d57921a9d88308b02d6de516b481b92a36bfa9fff19c
7
- data.tar.gz: fee7d3f4a94bed71d61ebcd878a63c8ad89bd60c9a44e4e18c4d2040442d2ede40483f28eb3ea66cefa69f3f0d5ecabadf3f3b4dc6b766b8b5d4e6ab2810fc4e
6
+ metadata.gz: bfbe7e86650ac170c89c014f7519d0d40abdb3d864b995d8cdb5ef2478ec3e4fe6f2e3afc4854dbc216e622a8adc99761bd7d9a420167553db6b70084446740f
7
+ data.tar.gz: b6729e1b52a5f25ae5da573d706bb1f3fc4217c750976dcd51aafd7611a5c7f53b4851727e3fd20cb66065570d2a99b0c4a4c367572c84fc08fb6918dfdfeb6b
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `danger-packwerk` integrates [`packwerk`](https://github.com/Shopify/packwerk) with [`danger`](https://github.com/danger/danger) to provide inline comments in PRs related to boundaries in a Rails application.
4
4
 
5
- ## Installation
5
+ ## Installation and Basic Usage
6
6
  Step 1: Add this line to your `Gemfile` (to whatever group your CI uses, as it is not needed in production) and `bundle install`:
7
7
 
8
8
  ```ruby
@@ -13,41 +13,50 @@ Step 2: Add these to your `Dangerfile`:
13
13
 
14
14
  ```ruby
15
15
  packwerk.check
16
- deprecated_references_yml_changes.check
16
+ package_todo_yml_changes.check
17
17
  ```
18
18
 
19
19
  That's it for basic usage!
20
20
 
21
- ## Usage
21
+ ## Advanced Usage
22
22
 
23
23
  There are currently two danger checks that ship with `danger-packwerk`:
24
24
  1) One that runs `bin/packwerk check` and leaves inline comments in source code on new violations
25
- 2) One that looks at changes to `deprecated_references.yml` files and leaves inline comments on added violations.
25
+ 2) One that looks at changes to `package_todo.yml` files and leaves inline comments on added violations.
26
26
 
27
27
  In upcoming iterations, we will include other danger checks, including:
28
28
  1) A danger check that detects changes to `package.yml` files and posts user-configurable messages on the `package.yml` files that are modified.
29
29
  2) A danger check that detects changes to `packwerk.yml` files and allows you to specify the action taken when that happens.
30
30
 
31
31
  ## packwerk.check
32
- ![This is an image displaying a comment from the Danger github bot. The comment is inline with the PR in Github and displays the following text. Dependency violation: ::FeatureFlag belongs to 'packs/feature_flags', but 'packs/gusto_slack' does not specify a dependency on 'packs/feature_flags'. Are we missing an abstraction? Is the code making the reference, and the referenced constant, in the right packages? Inference details: this is a reference to ::FeatureFlag which seems to be defined in packs/feature_flags/app/models/feature_flag.rb. To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations Privacy violation: '::FeatureFlag' is private to 'packs/feature_flags' but referenced from 'packs/gusto_slack'. Is there a public entrypoint in 'packs/feature_flags/app/public/' that you can use instead? Inference details: this is a reference to ::FeatureFlag which seems to be defined in packs/feature_flags/app/models/feature_flag.rb. To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations](docs/basic_usage.png)
32
+ ![This is an image displaying a comment from the Danger github bot after running bin/packwerk check.](docs/check_1.png)
33
+ ![This is an image displaying a comment from the Danger github bot after running bin/packwerk check with the "quick suggestions" accordian open](docs/check_2.png)
33
34
 
34
- Without any configuration, `packwerk.check` should just work. By default, it will post a maximum of 15 messages in a PR, using the default messaging from packwerk, and it will not fail the build.
35
+ Without any configuration, `packwerk.check` should just work. By default, it will post a maximum of 15 messages in a PR and it will not fail the build.
35
36
 
36
37
  `packwerk.check` can be configured to in the following ways:
37
38
 
38
39
  ### Change the message that displays in the markdown
39
- The default message displayed is from `bin/packwerk check`. To customize this this, pass in `offenses_formatter` to `packwerk.check` in your `Dangerfile`. Here's a simple example:
40
+ To customize the message in the GitHub comment, pass in `offenses_formatter` to `packwerk.check` in your `Dangerfile`. Here's a simple example:
40
41
  ```ruby
41
- packwerk.check(
42
- # Offenses are a T::Array[Packwerk::ReferenceOffense] => https://github.com/Shopify/packwerk/blob/main/lib/packwerk/reference_offense.rb
43
- offenses_formatter: -> (offenses) do
44
- "There are #{offenses.count} packwerk offenses on this line!"
42
+ class MyFormatter
43
+ extend T::Sig
44
+ include DangerPackwerk::Check::OffensesFormatter
45
+ # Packwerk::ReferenceOffense: https://github.com/Shopify/packwerk/blob/main/lib/packwerk/reference_offense.rb
46
+ sig { override.params(offenses: T::Array[Packwerk::ReferenceOffense], repo_link: String, org_name: String).returns(String) }
47
+ def format_offenses(offenses, repo_link, org_name)
48
+ # your logic here
45
49
  end
46
- )
50
+ end
51
+
52
+ packwerk.check(offenses_formatter: MyFormatter.new)
47
53
  ```
48
54
 
49
- A more advanced example could give more specific information about the violation and information specific to your organization or project about how to resolve. Here is a screenshot of what our message looks like at Gusto:
50
- ![This is an image displaying a comment from the Danger github bot. The comment is inline with the PR in Github and displays the following text: Hi there! It looks like FeatureFlag is private API of packs/feature_flags, which is also not in packs/gusto_slack's list of dependencies. Before you run bin/packwerk update-deprecations, read through How to Handle Dependency and Privacy Violations (with Flow Chart!). Here are some quick suggestions to resolve: Does the code you are writing live in the right pack? If not, try bin/move_to_pack -n packs/destination_pack -f packs/gusto_slack/app/services/slack/client.rb. Does FeatureFlag live in the right pack? If not, try bin/move_to_pack -n packs/destination_pack -f packs/feature_flags/app/models/feature_flag.rb. Do we actually want to depend on packs/feature_flags. If so, try adding packs/feature_flags to packs/gusto_slack/package.yml dependencies. If not, what can we change about the design so we do not have to depend on packs/feature_flags? Does API in packs/feature_flags/public support this use case? If not, can we work with @Gusto/product-infrastructure to create and use a public API? If FeatureFlag should already be public, try bin/make_public -f packs/feature_flags/app/models/feature_flag.rb. Need help? Join us in #ruby-modularity or provide feedback.](docs/advanced_usage.png)
55
+ If you'd like to keep the default messaging but add some context customized to your organization, you can pass that in as follows:
56
+ ```ruby
57
+ custom_help_message = "Need help? Check out our internal docs [here](www.example.com)"
58
+ packwerk.check(offenses_formatter: DangerPackwerk::Check::DefaultFormatter.new(custom_help_message: custom_help_message))
59
+ ```
51
60
 
52
61
  ### Fail the build on new violations
53
62
  Simply pass in `fail_build: true` into `check`, as such:
@@ -75,42 +84,48 @@ packwerk.check(
75
84
  )
76
85
  ```
77
86
 
78
- ## deprecated_references_yml_changes.check
79
- ![This is an image displaying a comment from the Danger github bot. The comment is inline with the PR in Github and displays the following text. We noticed you ran `bin/packwerk update-deprecations`. Make sure to read through the docs for other ways to resolve.](docs/basic_usage_2.png)
87
+ ## package_todo_yml_changes.check
88
+ ![This is an image displaying an inline comment from the Danger github bot.](docs/update.png)
80
89
 
81
- Without any configuration, `deprecated_references_yml_changes.check` should just work. By default, it will post a maximum of 15 messages in a PR, using default messaging defined within this gem.
90
+ Without any configuration, `package_todo_yml_changes.check` should just work. By default, it will post a maximum of 15 messages in a PR, using default messaging defined within this gem.
82
91
 
83
- `deprecated_references_yml_changes.check` can be configured to in the following ways:
92
+ `package_todo_yml_changes.check` can be configured to in the following ways:
84
93
 
85
94
  ### Change the message that displays in the markdown
86
- The default message displayed is from `lib/danger-packwerk/private/default_offenses_formatter.rb`. To customize this this, pass in `added_offenses_formatter` to `deprecated_references_yml_changes.check` in your `Dangerfile`. Here's a simple example:
95
+ To customize the message in the GitHub comment, pass in `offenses_formatter` to `package_todo_yml_changes.check` in your `Dangerfile`. Here's a simple example:
87
96
  ```ruby
88
- deprecated_references_yml_changes.check(
89
- # Offenses are a T::Array[DangerPackwerk::BasicReferenceOffense]
90
- added_offenses_formatter: -> (added_offenses) do
91
- "There are #{added_offenses.count} new violations this line!"
97
+ class MyFormatter
98
+ extend T::Sig
99
+ include DangerPackwerk::Update::OffensesFormatter
100
+ # DangerPackwerk::BasicReferenceOffense
101
+ sig { override.params(offenses: T::Array[DangerPackwerk::BasicReferenceOffense], repo_link: String, org_name: String).returns(String) }
102
+ def format_offenses(offenses, repo_link, org_name)
103
+ # your logic here
92
104
  end
93
- )
105
+ end
106
+
107
+ package_todo_yml_changes.check(offenses_formatter: MyFormatter.new)
94
108
  ```
95
109
 
96
- A more advanced example could give more specific information about the violation and information specific to your organization or project about how to resolve. Here is a screenshot of what our message looks like at Gusto:
97
- ![This is an image displaying a comment from the Danger github bot. The comment is inline with the PR in Github and displays the following text. Hi again! It looks like FeatureFlag is private API of packs/feature_flags, which is also not in packs/gusto_slack's list of dependencies.. We noticed you ran bin/packwerk update-deprecations. Make sure to read through How to Handle Dependency and Privacy Violations (with Flow Chart!). Could you add some context as a reply here about why we needed to add these violations packs/gusto_slack/package.yml is configured with notify_on_new_violations to notify @Gusto/product-infrastructure (#product-infrastructure) on new dependency violations. packs/feature_flags/package.yml is configured with notify_on_new_violations to notify @Gusto/product-infrastructure (#product-infrastructure) on new privacy violations
98
- Need help? Join us in #ruby-modularity or provide feedback.
99
- ](docs/advanced_usage_2.png)
110
+ If you'd like to keep the default messaging but add some context customized to your organization, you can pass that in as follows:
111
+ ```ruby
112
+ custom_help_message = "Need help? Check out our internal docs [here](www.example.com)"
113
+ package_todo_yml_changes.check(offenses_formatter: DangerPackwerk::Update::DefaultFormatter.new(custom_help_message: custom_help_message))
114
+ ```
100
115
 
101
116
  ### Change the max number of comments that will display
102
117
  If you do not change this, the default max is 15. More information about why we chose this number in the source code.
103
118
  ```ruby
104
- deprecated_references_yml_changes.check(max_comments: 3)
119
+ package_todo_yml_changes.check(max_comments: 3)
105
120
  ```
106
121
 
107
122
  ### Do something extra before we leave comments
108
123
  Maybe you want to notify slack or do something else before we leave comments.
109
124
 
110
125
  ```ruby
111
- deprecated_references_yml_changes.check(
112
- # violation_diff is a DangerPackwerk::ViolationDiff and changed_deprecated_references_ymls is a T::Array[String]
113
- before_comment: -> (violation_diff, changed_deprecated_references_ymls) do
126
+ package_todo_yml_changes.check(
127
+ # violation_diff is a DangerPackwerk::ViolationDiff and changed_package_todo_ymls is a T::Array[String]
128
+ before_comment: -> (violation_diff, changed_package_todo_ymls) do
114
129
  # Notify slack or otherwise do something extra!
115
130
  end
116
131
  )
@@ -4,8 +4,8 @@ module DangerPackwerk
4
4
  #
5
5
  # We call this BasicReferenceOffense as it is intended to have a subset of the interface of Packwerk::ReferenceOffense, located here:
6
6
  # https://github.com/Shopify/packwerk/blob/a22862b59f7760abf22bda6804d41a52d05301d8/lib/packwerk/reference_offense.rb#L1
7
- # However, we cannot actually construct a Packwerk::ReferenceOffense from `deprecated_referencs.yml` alone, since they are normally
8
- # constructed in packwerk when packwerk parses the AST and actually outputs `deprecated_references.yml`, a process in which some information,
7
+ # However, we cannot actually construct a Packwerk::ReferenceOffense from `package_todo.yml` alone, since they are normally
8
+ # constructed in packwerk when packwerk parses the AST and actually outputs `package_todo.yml`, a process in which some information,
9
9
  # such as the location where the constant is defined, is lost.
10
10
  #
11
11
  class BasicReferenceOffense < T::Struct
@@ -41,21 +41,21 @@ module DangerPackwerk
41
41
  const :type, String
42
42
  const :file_location, Location
43
43
 
44
- sig { params(deprecated_references_yml: String).returns(T::Array[BasicReferenceOffense]) }
45
- def self.from(deprecated_references_yml)
46
- deprecated_references_yml_pathname = Pathname.new(deprecated_references_yml)
44
+ sig { params(package_todo_yml: String).returns(T::Array[BasicReferenceOffense]) }
45
+ def self.from(package_todo_yml)
46
+ package_todo_yml_pathname = Pathname.new(package_todo_yml)
47
47
 
48
- from_package = ParsePackwerk.package_from_path(deprecated_references_yml_pathname)
48
+ from_package = ParsePackwerk.package_from_path(package_todo_yml_pathname)
49
49
  from_package_name = from_package.name
50
- violations = Private::DeprecatedReferences.from(deprecated_references_yml_pathname).violations
50
+ violations = Private::PackageTodo.from(package_todo_yml_pathname).violations
51
51
 
52
52
  # See the larger comment below for more information on why we need this information.
53
53
  # This is a small optimization that lets us find the location of referenced files within
54
- # a `deprecated_references.yml` file. Getting this now allows us to avoid reading through the file
54
+ # a `package_todo.yml` file. Getting this now allows us to avoid reading through the file
55
55
  # once for every referenced file in the inner loop below.
56
56
  file_reference_to_line_number_index = T.let({}, T::Hash[String, T::Array[Integer]])
57
57
  all_referenced_files = violations.flat_map(&:files).uniq
58
- deprecated_references_yml_pathname.readlines.each_with_index do |line, index|
58
+ package_todo_yml_pathname.readlines.each_with_index do |line, index|
59
59
  # We can use `find` here to exit early since each line will include one path that is unique to that file.
60
60
  # Paths should not be substrings of each other, since they are all paths relative to the root.
61
61
  file_on_line = all_referenced_files.find { |file| line.include?(file) }
@@ -69,24 +69,24 @@ module DangerPackwerk
69
69
  violations.flat_map do |violation|
70
70
  #
71
71
  # We identify two locations associated with this violation.
72
- # First, we find the reference to the constant within the `deprecated_references.yml` file.
73
- # We know that each constant reference can occur only once per `deprecated_references.yml` file
72
+ # First, we find the reference to the constant within the `package_todo.yml` file.
73
+ # We know that each constant reference can occur only once per `package_todo.yml` file
74
74
  # The reason for this is that we know that only one file in the codebase can define a constant, and packwerk's constant_resolver will actually
75
75
  # raise if this assumption is not true: https://github.com/Shopify/constant_resolver/blob/e78af0c8d5782b06292c068cfe4176e016c51b34/lib/constant_resolver.rb#L74
76
76
  #
77
- # Second, we find the reference to the specific file that references the constant within the `deprecated_references.yml` file.
78
- # This can occur multiple times per `deprecated_references.yml` file, but we know that the very first reference to the file after the class name key will be the one we care
77
+ # Second, we find the reference to the specific file that references the constant within the `package_todo.yml` file.
78
+ # This can occur multiple times per `package_todo.yml` file, but we know that the very first reference to the file after the class name key will be the one we care
79
79
  # about, so we take the first instance that occurs after the class is listed.
80
80
  #
81
- # Note though that since one constant reference in a `deprecated_referencs.yml` can be both a privacy and a dependency violation AND it can occur in many files,
81
+ # Note though that since one constant reference in a `package_todo.yml` can be both a privacy and a dependency violation AND it can occur in many files,
82
82
  # we need to group them. That is -- if `MyPrivateConstant` is both a dependency and a privacy violation AND it occurs in 10 files, that would represent 20 violations.
83
83
  # Therefore we will group all of those 20 into one message to the user rather than providing 20 messages.
84
84
  #
85
- _line, class_name_line_number = deprecated_references_yml_pathname.readlines.each_with_index.find do |line, _index|
85
+ _line, class_name_line_number = package_todo_yml_pathname.readlines.each_with_index.find do |line, _index|
86
86
  # If you have a class `::MyClass`, then you can get a false match if another constant in the file
87
87
  # is named `MyOtherClass::MyClassThing`. Therefore we include quotes in our match to ensure that we match
88
88
  # the constant and only the constant.
89
- # Right now `packwerk` `deprecated_references.yml` files typically use double quotes, but sometimes folks linters change this to single quotes.
89
+ # Right now `packwerk` `package_todo.yml` files typically use double quotes, but sometimes folks linters change this to single quotes.
90
90
  # To be defensive, we match against either.
91
91
  class_name_with_quote_boundaries = /["|']#{violation.class_name}["|']:/
92
92
  line.match?(class_name_with_quote_boundaries)
@@ -94,16 +94,16 @@ module DangerPackwerk
94
94
 
95
95
  if class_name_line_number.nil?
96
96
  debug_info = { class_name: violation.class_name, to_package_name: violation.to_package_name, type: violation.type }
97
- raise "Unable to find reference to violation #{debug_info} in #{deprecated_references_yml}"
97
+ raise "Unable to find reference to violation #{debug_info} in #{package_todo_yml}"
98
98
  end
99
99
 
100
100
  violation.files.map do |file|
101
101
  file_line_numbers = file_reference_to_line_number_index.fetch(file, [])
102
102
  file_line_number = file_line_numbers.select { |index| index > class_name_line_number }.min
103
- raise "Unable to find reference to violation #{{ file: file, to_package_name: violation.to_package_name, type: violation.type }} in #{deprecated_references_yml}" if file_line_number.nil?
103
+ raise "Unable to find reference to violation #{{ file: file, to_package_name: violation.to_package_name, type: violation.type }} in #{package_todo_yml}" if file_line_number.nil?
104
104
 
105
105
  # We add one to the line number since `each_with_index` is zero-based indexed but Github line numbers are one-based indexed
106
- file_location = Location.new(file: deprecated_references_yml, line_number: file_line_number + 1)
106
+ file_location = Location.new(file: package_todo_yml, line_number: file_line_number + 1)
107
107
 
108
108
  BasicReferenceOffense.new(
109
109
  class_name: violation.class_name,
@@ -0,0 +1,110 @@
1
+ # typed: strict
2
+
3
+ require 'code_ownership'
4
+
5
+ module DangerPackwerk
6
+ module Check
7
+ class DefaultFormatter
8
+ include OffensesFormatter
9
+ extend T::Sig
10
+
11
+ sig do
12
+ params(
13
+ custom_help_message: T.nilable(String)
14
+ ).void
15
+ end
16
+ def initialize(custom_help_message: nil)
17
+ @custom_help_message = custom_help_message
18
+ end
19
+
20
+ sig do
21
+ override.params(
22
+ offenses: T::Array[Packwerk::ReferenceOffense],
23
+ repo_link: String,
24
+ org_name: String
25
+ ).returns(String)
26
+ end
27
+ def format_offenses(offenses, repo_link, org_name)
28
+ reference_offense = T.must(offenses.first)
29
+ violation_types = offenses.map(&:violation_type)
30
+ referencing_file = reference_offense.reference.relative_path
31
+ referencing_file_pack = ParsePackwerk.package_from_path(referencing_file).name
32
+ # We remove leading double colons as they feel like an implementation detail of packwerk.
33
+ constant_name = reference_offense.reference.constant.name.delete_prefix('::')
34
+
35
+ constant_source_package_name = reference_offense.reference.constant.package.name
36
+
37
+ constant_location = reference_offense.reference.constant.location
38
+ constant_source_package = T.must(ParsePackwerk.all.find { |p| p.name == constant_source_package_name })
39
+ constant_source_package_ownership_info = Private::OwnershipInformation.for_package(constant_source_package, org_name)
40
+
41
+ disclaimer = 'Before you run `bin/packwerk update-deprecations`, check out these quick suggestions:'
42
+ referencing_code_in_right_pack = "- Does the code you are writing live in the right pack?\n - If not, try `bin/packs move packs/destination_pack #{referencing_file}`"
43
+ referenced_code_in_right_pack = "- Does #{constant_name} live in the right pack?\n - If not, try `bin/packs move packs/destination_pack #{constant_location}`"
44
+ dependency_violation_message = "- Do we actually want to depend on #{constant_source_package_name}?\n - If so, try `bin/packs add_dependency #{referencing_file_pack} #{constant_source_package_name}`\n - If not, what can we change about the design so we do not have to depend on #{constant_source_package_name}?"
45
+ team_to_work_with = constant_source_package_ownership_info.owning_team ? constant_source_package_ownership_info.markdown_link_to_github_members_no_tag : 'the pack owner'
46
+ privacy_violation_message = "- Does API in #{constant_source_package.name}/public support this use case?\n - If not, can we work with #{team_to_work_with} to create and use a public API?\n - If `#{constant_name}` should already be public, try `bin/packs make_public #{constant_location}`."
47
+
48
+ if violation_types.include?(::DangerPackwerk::DEPENDENCY_VIOLATION_TYPE) && violation_types.include?(::DangerPackwerk::PRIVACY_VIOLATION_TYPE)
49
+ <<~MESSAGE
50
+ **Packwerk Violation**
51
+ - Type: Privacy :lock: + Dependency :knot:
52
+ - Constant: [<ins>`#{constant_name}`</ins>](#{repo_link}/blob/main/#{constant_location})
53
+ - Owning pack: #{constant_source_package_name}
54
+ #{constant_source_package_ownership_info.ownership_copy}
55
+
56
+ <details><summary>Quick suggestions :bulb:</summary>
57
+
58
+ #{disclaimer}
59
+ #{referencing_code_in_right_pack}
60
+ #{referenced_code_in_right_pack}
61
+ #{dependency_violation_message}
62
+ #{privacy_violation_message}
63
+
64
+ </details>
65
+
66
+ _#{@custom_help_message}_
67
+ MESSAGE
68
+ elsif violation_types.include?(::DangerPackwerk::DEPENDENCY_VIOLATION_TYPE)
69
+ <<~MESSAGE
70
+ **Packwerk Violation**
71
+ - Type: Dependency :knot:
72
+ - Constant: [<ins>`#{constant_name}`</ins>](#{repo_link}/blob/main/#{constant_location})
73
+ - Owning pack: #{constant_source_package_name}
74
+ #{constant_source_package_ownership_info.ownership_copy}
75
+
76
+ <details><summary>Quick suggestions :bulb:</summary>
77
+
78
+ #{disclaimer}
79
+ #{referencing_code_in_right_pack}
80
+ #{referenced_code_in_right_pack}
81
+ #{dependency_violation_message}
82
+
83
+ </details>
84
+
85
+ _#{@custom_help_message}_
86
+ MESSAGE
87
+ else # violation_types.include?(::DangerPackwerk::PRIVACY_VIOLATION_TYPE)
88
+ <<~MESSAGE
89
+ **Packwerk Violation**
90
+ - Type: Privacy :lock:
91
+ - Constant: [<ins>`#{constant_name}`</ins>](#{repo_link}/blob/main/#{constant_location})
92
+ - Owning pack: #{constant_source_package_name}
93
+ #{constant_source_package_ownership_info.ownership_copy}
94
+
95
+ <details><summary>Quick suggestions :bulb:</summary>
96
+
97
+ #{disclaimer}
98
+ #{referencing_code_in_right_pack}
99
+ #{referenced_code_in_right_pack}
100
+ #{privacy_violation_message}
101
+
102
+ </details>
103
+
104
+ _#{@custom_help_message}_
105
+ MESSAGE
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+
3
+ require 'code_ownership'
4
+
5
+ module DangerPackwerk
6
+ module Check
7
+ module OffensesFormatter
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ interface!
12
+
13
+ sig do
14
+ abstract.params(
15
+ offenses: T::Array[Packwerk::ReferenceOffense],
16
+ repo_link: String,
17
+ org_name: String
18
+ ).returns(String)
19
+ end
20
+ def format_offenses(offenses, repo_link, org_name); end
21
+ end
22
+ end
23
+ end
@@ -9,7 +9,7 @@ require 'danger-packwerk/violation_diff'
9
9
  require 'open3'
10
10
 
11
11
  module DangerPackwerk
12
- class DangerDeprecatedReferencesYmlChanges < Danger::Plugin
12
+ class DangerPackageTodoYmlChanges < Danger::Plugin
13
13
  extend T::Sig
14
14
 
15
15
  # We choose 5 here because violation additions tend to fall into a bimodal distribution, where most PRs only add a handful (<10) of new violations,
@@ -17,30 +17,32 @@ module DangerPackwerk
17
17
  # Therefore we hope to capture the majority case of people making changes to code while not spamming PRs that do a big rename.
18
18
  # We set a max (rather than unlimited) to avoid GitHub rate limiting and general spam if a PR does some sort of mass rename.
19
19
  DEFAULT_MAX_COMMENTS = 5
20
- AddedOffensesFormatter = T.type_alias { T.proc.params(added_violations: T::Array[BasicReferenceOffense]).returns(String) }
21
- DEFAULT_ADDED_OFFENSES_FORMATTER = T.let(->(added_violations) { Private::DefaultAddedOffensesFormatter.format(added_violations) }, AddedOffensesFormatter)
22
- BeforeComment = T.type_alias { T.proc.params(violation_diff: ViolationDiff, changed_deprecated_references_ymls: T::Array[String]).void }
23
- DEFAULT_BEFORE_COMMENT = T.let(->(violation_diff, changed_deprecated_references_ymls) {}, BeforeComment)
20
+ BeforeComment = T.type_alias { T.proc.params(violation_diff: ViolationDiff, changed_package_todo_ymls: T::Array[String]).void }
21
+ DEFAULT_BEFORE_COMMENT = T.let(->(violation_diff, changed_package_todo_ymls) {}, BeforeComment)
24
22
 
25
23
  sig do
26
24
  params(
27
- added_offenses_formatter: AddedOffensesFormatter,
25
+ offenses_formatter: T.nilable(Update::OffensesFormatter),
28
26
  before_comment: BeforeComment,
29
27
  max_comments: Integer
30
28
  ).void
31
29
  end
32
30
  def check(
33
- added_offenses_formatter: DEFAULT_ADDED_OFFENSES_FORMATTER,
31
+ offenses_formatter: nil,
34
32
  before_comment: DEFAULT_BEFORE_COMMENT,
35
33
  max_comments: DEFAULT_MAX_COMMENTS
36
34
  )
37
- changed_deprecated_references_ymls = (git.modified_files + git.added_files + git.deleted_files).grep(DEPRECATED_REFERENCES_PATTERN)
35
+ offenses_formatter ||= Update::DefaultFormatter.new
36
+ repo_link = github.pr_json[:base][:repo][:html_url]
37
+ org_name = github.pr_json[:base][:repo][:owner][:login]
38
+
39
+ changed_package_todo_ymls = (git.modified_files + git.added_files + git.deleted_files).grep(PACKAGE_TODO_PATTERN)
38
40
 
39
41
  violation_diff = get_violation_diff
40
42
 
41
43
  before_comment.call(
42
44
  violation_diff,
43
- changed_deprecated_references_ymls.to_a
45
+ changed_package_todo_ymls.to_a
44
46
  )
45
47
 
46
48
  current_comment_count = 0
@@ -51,7 +53,7 @@ module DangerPackwerk
51
53
  location = T.must(violations.first).file_location
52
54
 
53
55
  markdown(
54
- added_offenses_formatter.call(violations),
56
+ offenses_formatter.format_offenses(violations, repo_link, org_name),
55
57
  line: location.line_number,
56
58
  file: location.file
57
59
  )
@@ -65,16 +67,16 @@ module DangerPackwerk
65
67
  added_violations = T.let([], T::Array[BasicReferenceOffense])
66
68
  removed_violations = T.let([], T::Array[BasicReferenceOffense])
67
69
 
68
- git.added_files.grep(DEPRECATED_REFERENCES_PATTERN).each do |added_deprecated_references_yml_file|
70
+ git.added_files.grep(PACKAGE_TODO_PATTERN).each do |added_package_todo_yml_file|
69
71
  # Since the file is added, we know on the base commit there are no violations related to this pack,
70
72
  # and that all violations from this file are new
71
- added_violations += BasicReferenceOffense.from(added_deprecated_references_yml_file)
73
+ added_violations += BasicReferenceOffense.from(added_package_todo_yml_file)
72
74
  end
73
75
 
74
- git.deleted_files.grep(DEPRECATED_REFERENCES_PATTERN).each do |deleted_deprecated_references_yml_file|
76
+ git.deleted_files.grep(PACKAGE_TODO_PATTERN).each do |deleted_package_todo_yml_file|
75
77
  # Since the file is deleted, we know on the HEAD commit there are no violations related to this pack,
76
78
  # and that all violations from this file are deleted
77
- deleted_violations = get_violations_before_patch_for(deleted_deprecated_references_yml_file)
79
+ deleted_violations = get_violations_before_patch_for(deleted_package_todo_yml_file)
78
80
  removed_violations += deleted_violations
79
81
  end
80
82
 
@@ -82,13 +84,13 @@ module DangerPackwerk
82
84
  renamed_files_before = git.renamed_files.map { |before_after_file| before_after_file[:before] }
83
85
  renamed_files_after = git.renamed_files.map { |before_after_file| before_after_file[:after] }
84
86
 
85
- git.modified_files.grep(DEPRECATED_REFERENCES_PATTERN).each do |modified_deprecated_references_yml_file|
86
- # We skip over modified files if one of the modified files is a renamed `deprecated_references.yml` file.
87
+ git.modified_files.grep(PACKAGE_TODO_PATTERN).each do |modified_package_todo_yml_file|
88
+ # We skip over modified files if one of the modified files is a renamed `package_todo.yml` file.
87
89
  # This allows us to rename packs while ignoring "new violations" in those renamed packs.
88
- next if renamed_files_before.include?(modified_deprecated_references_yml_file)
90
+ next if renamed_files_before.include?(modified_package_todo_yml_file)
89
91
 
90
- head_commit_violations = BasicReferenceOffense.from(modified_deprecated_references_yml_file)
91
- base_commit_violations = get_violations_before_patch_for(modified_deprecated_references_yml_file)
92
+ head_commit_violations = BasicReferenceOffense.from(modified_package_todo_yml_file)
93
+ base_commit_violations = get_violations_before_patch_for(modified_package_todo_yml_file)
92
94
  added_violations += head_commit_violations - base_commit_violations
93
95
  removed_violations += base_commit_violations - head_commit_violations
94
96
  end
@@ -126,9 +128,9 @@ module DangerPackwerk
126
128
 
127
129
  private
128
130
 
129
- sig { params(deprecated_references_yml_file: String).returns(T::Array[BasicReferenceOffense]) }
130
- def get_violations_before_patch_for(deprecated_references_yml_file)
131
- # The strategy to get the violations before this PR is to reverse the patch on each `deprecated_references.yml`.
131
+ sig { params(package_todo_yml_file: String).returns(T::Array[BasicReferenceOffense]) }
132
+ def get_violations_before_patch_for(package_todo_yml_file)
133
+ # The strategy to get the violations before this PR is to reverse the patch on each `package_todo.yml`.
132
134
  # A previous strategy attempted to use `git merge-base --fork-point`, but there are many situations where it returns
133
135
  # empty values. That strategy is fickle because it depends on the state of the `reflog` within the CI suite, which appears
134
136
  # to not be reliable to depend on.
@@ -137,24 +139,24 @@ module DangerPackwerk
137
139
  # the PR without needing to use git commands that interpret the branch history based on local git history.
138
140
  #
139
141
  # We apply the patch to the original file so that we can seamlessly reverse the patch applied to that file (since patches are coupled to
140
- # the files they modify). After parsing the violations from that `deprecated_references.yml` file with the patch reversed,
142
+ # the files they modify). After parsing the violations from that `package_todo.yml` file with the patch reversed,
141
143
  # we use a temporary copy of the original file to rewrite to it with the original contents.
142
144
  # Note that practically speaking, we don't need to rewrite the original contents (since we already fetched the
143
145
  # original contents above and the CI file system should be ephemeral). However, we do this anyways in case we later change these
144
146
  # assumptions, or another client's environment is different and expects these files not to be mutated.
145
147
 
146
148
  # Keep track of the original file contents. If the original file has been deleted, then we delete the file after inverting the patch at the end, rather than rewriting it.
147
- deprecated_references_yml_file_copy = (File.read(deprecated_references_yml_file) if File.exist?(deprecated_references_yml_file))
149
+ package_todo_yml_file_copy = (File.read(package_todo_yml_file) if File.exist?(package_todo_yml_file))
148
150
 
149
151
  Tempfile.create do |patch_file|
150
- # Normally we'd use `git.diff_for_file(deprecated_references_yml_file).patch` here, but there is a bug where it does not work for deleted files yet.
152
+ # Normally we'd use `git.diff_for_file(package_todo_yml_file).patch` here, but there is a bug where it does not work for deleted files yet.
151
153
  # I have a fix for that here: https://github.com/danger/danger/pull/1357
152
154
  # Until that lands, I'm just using the underlying implementation of that method to get the diff for a file.
153
155
  # Note that I might want to use a safe escape operator, `&.patch` and return gracefully if the patch cannot be found.
154
156
  # However I'd be interested in why that ever happens, so for now going to proceed as is.
155
157
  # (Note that better yet we'd have observability into these so I can just log under those circumstances rather than surfacing an error to the user,
156
158
  # but we don't have that quite yet.)
157
- patch_for_file = git.diff[deprecated_references_yml_file].patch
159
+ patch_for_file = git.diff[package_todo_yml_file].patch
158
160
  # This appears to be a known issue that patches require new lines at the end. It seems like this is an issue with Danger that
159
161
  # it gives us a patch without a newline.
160
162
  # https://stackoverflow.com/questions/18142870/git-error-fatal-corrupt-patch-at-line-36
@@ -163,13 +165,13 @@ module DangerPackwerk
163
165
  # https://git-scm.com/docs/git-apply
164
166
  _stdout, _stderr, _status = Open3.capture3("git apply --reverse #{patch_file.path}")
165
167
  # https://www.rubyguides.com/2019/05/ruby-tempfile/
166
- BasicReferenceOffense.from(deprecated_references_yml_file)
168
+ BasicReferenceOffense.from(package_todo_yml_file)
167
169
  end
168
170
  ensure
169
- if deprecated_references_yml_file_copy
170
- File.write(deprecated_references_yml_file, deprecated_references_yml_file_copy)
171
+ if package_todo_yml_file_copy
172
+ File.write(package_todo_yml_file, package_todo_yml_file_copy)
171
173
  else
172
- File.delete(deprecated_references_yml_file)
174
+ File.delete(package_todo_yml_file)
173
175
  end
174
176
  end
175
177
  end
@@ -19,8 +19,6 @@ module DangerPackwerk
19
19
  DEFAULT_MAX_COMMENTS = 15
20
20
  OnFailure = T.type_alias { T.proc.params(offenses: T::Array[Packwerk::ReferenceOffense]).void }
21
21
  DEFAULT_ON_FAILURE = T.let(->(offenses) {}, OnFailure)
22
- OffensesFormatter = T.type_alias { T.proc.params(offenses: T::Array[Packwerk::ReferenceOffense]).returns(String) }
23
- DEFAULT_OFFENSES_FORMATTER = T.let(->(offenses) { offenses.map(&:message).join("\n\n") }, OffensesFormatter)
24
22
  DEFAULT_FAIL = false
25
23
  DEFAULT_FAILURE_MESSAGE = 'Packwerk violations were detected! Please resolve them to unblock the build.'
26
24
 
@@ -35,8 +33,8 @@ module DangerPackwerk
35
33
 
36
34
  sig do
37
35
  params(
36
+ offenses_formatter: T.nilable(Check::OffensesFormatter),
38
37
  max_comments: Integer,
39
- offenses_formatter: OffensesFormatter,
40
38
  fail_build: T::Boolean,
41
39
  failure_message: String,
42
40
  on_failure: OnFailure,
@@ -44,13 +42,17 @@ module DangerPackwerk
44
42
  ).void
45
43
  end
46
44
  def check(
45
+ offenses_formatter: nil,
47
46
  max_comments: DEFAULT_MAX_COMMENTS,
48
- offenses_formatter: DEFAULT_OFFENSES_FORMATTER,
49
47
  fail_build: DEFAULT_FAIL,
50
48
  failure_message: DEFAULT_FAILURE_MESSAGE,
51
49
  on_failure: DEFAULT_ON_FAILURE,
52
50
  grouping_strategy: CommentGroupingStrategy::PerConstantPerLocation
53
51
  )
52
+ offenses_formatter ||= Check::DefaultFormatter.new
53
+ repo_link = github.pr_json[:base][:repo][:html_url]
54
+ org_name = github.pr_json[:base][:repo][:owner][:login]
55
+
54
56
  # This is important because by default, Danger will leave a concantenated list of all its messages if it can't find a commentable place in the
55
57
  # diff to leave its message. This is an especially bad UX because it will be a huge wall of text not connected to the source of the issue.
56
58
  # Furthermore, dismissing these ensures that something like moving a file from pack to pack does not trigger the danger message. That is,
@@ -120,7 +122,7 @@ module DangerPackwerk
120
122
  line_number = reference_offense.location&.line
121
123
  referencing_file = reference_offense.reference.relative_path
122
124
 
123
- message = offenses_formatter.call(unique_packwerk_reference_offenses)
125
+ message = offenses_formatter.format_offenses(unique_packwerk_reference_offenses, repo_link, org_name)
124
126
 
125
127
  markdown(message, file: referencing_file, line: line_number)
126
128
  end
@@ -60,6 +60,16 @@ module DangerPackwerk
60
60
  def show_stale_violations(offense_collection, for_files)
61
61
  ''
62
62
  end
63
+
64
+ sig { override.params(strict_mode_violations: T::Array[::Packwerk::ReferenceOffense]).returns(::String) }
65
+ def show_strict_mode_violations(strict_mode_violations)
66
+ ''
67
+ end
68
+
69
+ sig { override.returns(::String) }
70
+ def identifier
71
+ 'offenses_aggregator'
72
+ end
63
73
  end
64
74
  end
65
75
  end