danger-packwerk 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +47 -32
- data/lib/danger-packwerk/basic_reference_offense.rb +19 -19
- data/lib/danger-packwerk/check/default_formatter.rb +3 -3
- data/lib/danger-packwerk/{danger_deprecated_references_yml_changes.rb → danger_package_todo_yml_changes.rb} +44 -32
- data/lib/danger-packwerk/danger_packwerk.rb +10 -2
- data/lib/danger-packwerk/packwerk_wrapper.rb +10 -0
- data/lib/danger-packwerk/private.rb +0 -1
- data/lib/danger-packwerk/version.rb +1 -1
- data/lib/danger-packwerk.rb +4 -2
- data/sorbet/rbi/gems/{packwerk@2.2.1.rbi → packwerk@2.2.1-7e8e7a50705833b41fe40c201eadb85ff9a1a422.rbi} +852 -231
- data/sorbet/rbi/gems/parse_packwerk@0.16.0.rbi +224 -0
- metadata +7 -8
- data/lib/danger-packwerk/private/deprecated_references.rb +0 -65
- data/sorbet/rbi/gems/parse_packwerk@0.12.1.rbi +0 -146
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 312a8c57ba777586f14b9464935701ec657c74537f5811e0cdd4d9484f6f1964
|
4
|
+
data.tar.gz: 6590f28585fc4e0b03c303fb968dfd8cb889348adf2aa2a71f9d901b9e8749d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ef0f83f1481c5e3362e03fd684dc8aa9a404cda136669b3146666cd1bb26dae15c3c0660033d9bbac52affd23a83005a218462223d623e97d8c73a6be563e2ba
|
7
|
+
data.tar.gz: 8cb95942be98ed171a421abd6008ed052803b3ec8502291e04201c5226b53f9a00dbd683c76618b25f3da5883d62bce1e6a9cdfbbe1d1c925feed404a6849890
|
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
|
-
|
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 `
|
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
|
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
|
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
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
50
|
-
|
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
|
-
##
|
79
|
-
![This is an image displaying
|
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, `
|
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
|
-
`
|
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
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
97
|
-
|
98
|
-
Need help?
|
99
|
-
|
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
|
-
|
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
|
-
|
112
|
-
# violation_diff is a DangerPackwerk::ViolationDiff and
|
113
|
-
before_comment: -> (violation_diff,
|
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 `
|
8
|
-
# constructed in packwerk when packwerk parses the AST and actually outputs `
|
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(
|
45
|
-
def self.from(
|
46
|
-
|
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(
|
48
|
+
from_package = ParsePackwerk.package_from_path(package_todo_yml_pathname)
|
49
49
|
from_package_name = from_package.name
|
50
|
-
violations =
|
50
|
+
violations = ParsePackwerk::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 `
|
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
|
-
|
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 `
|
73
|
-
# We know that each constant reference can occur only once per `
|
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 `
|
78
|
-
# This can occur multiple times per `
|
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 `
|
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 =
|
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` `
|
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 #{
|
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 #{
|
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:
|
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,
|
@@ -45,7 +45,7 @@ module DangerPackwerk
|
|
45
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
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
47
|
|
48
|
-
if violation_types.include?(
|
48
|
+
if violation_types.include?(::DangerPackwerk::DEPENDENCY_VIOLATION_TYPE) && violation_types.include?(::DangerPackwerk::PRIVACY_VIOLATION_TYPE)
|
49
49
|
<<~MESSAGE
|
50
50
|
**Packwerk Violation**
|
51
51
|
- Type: Privacy :lock: + Dependency :knot:
|
@@ -65,7 +65,7 @@ module DangerPackwerk
|
|
65
65
|
|
66
66
|
_#{@custom_help_message}_
|
67
67
|
MESSAGE
|
68
|
-
elsif violation_types.include?(
|
68
|
+
elsif violation_types.include?(::DangerPackwerk::DEPENDENCY_VIOLATION_TYPE)
|
69
69
|
<<~MESSAGE
|
70
70
|
**Packwerk Violation**
|
71
71
|
- Type: Dependency :knot:
|
@@ -84,7 +84,7 @@ module DangerPackwerk
|
|
84
84
|
|
85
85
|
_#{@custom_help_message}_
|
86
86
|
MESSAGE
|
87
|
-
else # violation_types.include?(
|
87
|
+
else # violation_types.include?(::DangerPackwerk::PRIVACY_VIOLATION_TYPE)
|
88
88
|
<<~MESSAGE
|
89
89
|
**Packwerk Violation**
|
90
90
|
- Type: Privacy :lock:
|
@@ -9,7 +9,7 @@ require 'danger-packwerk/violation_diff'
|
|
9
9
|
require 'open3'
|
10
10
|
|
11
11
|
module DangerPackwerk
|
12
|
-
class
|
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,32 +17,38 @@ 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
|
-
BeforeComment = T.type_alias { T.proc.params(violation_diff: ViolationDiff,
|
21
|
-
DEFAULT_BEFORE_COMMENT = T.let(->(violation_diff,
|
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)
|
22
|
+
DEFAULT_VIOLATION_TYPES = T.let([
|
23
|
+
DEPENDENCY_VIOLATION_TYPE,
|
24
|
+
PRIVACY_VIOLATION_TYPE
|
25
|
+
], T::Array[String])
|
22
26
|
|
23
27
|
sig do
|
24
28
|
params(
|
25
29
|
offenses_formatter: T.nilable(Update::OffensesFormatter),
|
26
30
|
before_comment: BeforeComment,
|
27
|
-
max_comments: Integer
|
31
|
+
max_comments: Integer,
|
32
|
+
violation_types: T::Array[String]
|
28
33
|
).void
|
29
34
|
end
|
30
35
|
def check(
|
31
36
|
offenses_formatter: nil,
|
32
37
|
before_comment: DEFAULT_BEFORE_COMMENT,
|
33
|
-
max_comments: DEFAULT_MAX_COMMENTS
|
38
|
+
max_comments: DEFAULT_MAX_COMMENTS,
|
39
|
+
violation_types: DEFAULT_VIOLATION_TYPES
|
34
40
|
)
|
35
41
|
offenses_formatter ||= Update::DefaultFormatter.new
|
36
42
|
repo_link = github.pr_json[:base][:repo][:html_url]
|
37
43
|
org_name = github.pr_json[:base][:repo][:owner][:login]
|
38
44
|
|
39
|
-
|
45
|
+
changed_package_todo_ymls = (git.modified_files + git.added_files + git.deleted_files).grep(PACKAGE_TODO_PATTERN)
|
40
46
|
|
41
|
-
violation_diff = get_violation_diff
|
47
|
+
violation_diff = get_violation_diff(violation_types)
|
42
48
|
|
43
49
|
before_comment.call(
|
44
50
|
violation_diff,
|
45
|
-
|
51
|
+
changed_package_todo_ymls.to_a
|
46
52
|
)
|
47
53
|
|
48
54
|
current_comment_count = 0
|
@@ -62,21 +68,21 @@ module DangerPackwerk
|
|
62
68
|
end
|
63
69
|
end
|
64
70
|
|
65
|
-
sig { returns(ViolationDiff) }
|
66
|
-
def get_violation_diff
|
71
|
+
sig { params(violation_types: T::Array[String]).returns(ViolationDiff) }
|
72
|
+
def get_violation_diff(violation_types)
|
67
73
|
added_violations = T.let([], T::Array[BasicReferenceOffense])
|
68
74
|
removed_violations = T.let([], T::Array[BasicReferenceOffense])
|
69
75
|
|
70
|
-
git.added_files.grep(
|
76
|
+
git.added_files.grep(PACKAGE_TODO_PATTERN).each do |added_package_todo_yml_file|
|
71
77
|
# Since the file is added, we know on the base commit there are no violations related to this pack,
|
72
78
|
# and that all violations from this file are new
|
73
|
-
added_violations += BasicReferenceOffense.from(
|
79
|
+
added_violations += BasicReferenceOffense.from(added_package_todo_yml_file)
|
74
80
|
end
|
75
81
|
|
76
|
-
git.deleted_files.grep(
|
82
|
+
git.deleted_files.grep(PACKAGE_TODO_PATTERN).each do |deleted_package_todo_yml_file|
|
77
83
|
# Since the file is deleted, we know on the HEAD commit there are no violations related to this pack,
|
78
84
|
# and that all violations from this file are deleted
|
79
|
-
deleted_violations = get_violations_before_patch_for(
|
85
|
+
deleted_violations = get_violations_before_patch_for(deleted_package_todo_yml_file)
|
80
86
|
removed_violations += deleted_violations
|
81
87
|
end
|
82
88
|
|
@@ -84,13 +90,13 @@ module DangerPackwerk
|
|
84
90
|
renamed_files_before = git.renamed_files.map { |before_after_file| before_after_file[:before] }
|
85
91
|
renamed_files_after = git.renamed_files.map { |before_after_file| before_after_file[:after] }
|
86
92
|
|
87
|
-
git.modified_files.grep(
|
88
|
-
# We skip over modified files if one of the modified files is a renamed `
|
93
|
+
git.modified_files.grep(PACKAGE_TODO_PATTERN).each do |modified_package_todo_yml_file|
|
94
|
+
# We skip over modified files if one of the modified files is a renamed `package_todo.yml` file.
|
89
95
|
# This allows us to rename packs while ignoring "new violations" in those renamed packs.
|
90
|
-
next if renamed_files_before.include?(
|
96
|
+
next if renamed_files_before.include?(modified_package_todo_yml_file)
|
91
97
|
|
92
|
-
head_commit_violations = BasicReferenceOffense.from(
|
93
|
-
base_commit_violations = get_violations_before_patch_for(
|
98
|
+
head_commit_violations = BasicReferenceOffense.from(modified_package_todo_yml_file)
|
99
|
+
base_commit_violations = get_violations_before_patch_for(modified_package_todo_yml_file)
|
94
100
|
added_violations += head_commit_violations - base_commit_violations
|
95
101
|
removed_violations += base_commit_violations - head_commit_violations
|
96
102
|
end
|
@@ -117,20 +123,26 @@ module DangerPackwerk
|
|
117
123
|
end
|
118
124
|
|
119
125
|
relevant_added_violations = added_violations.reject do |violation|
|
120
|
-
renamed_files_after.include?(violation.file) ||
|
126
|
+
renamed_files_after.include?(violation.file) ||
|
127
|
+
renamed_constants.include?(violation.class_name) ||
|
128
|
+
!violation_types.include?(violation.type)
|
129
|
+
end
|
130
|
+
|
131
|
+
relevant_removed_violations = removed_violations.select do |violation|
|
132
|
+
violation_types.include?(violation.type)
|
121
133
|
end
|
122
134
|
|
123
135
|
ViolationDiff.new(
|
124
136
|
added_violations: relevant_added_violations,
|
125
|
-
removed_violations:
|
137
|
+
removed_violations: relevant_removed_violations
|
126
138
|
)
|
127
139
|
end
|
128
140
|
|
129
141
|
private
|
130
142
|
|
131
|
-
sig { params(
|
132
|
-
def get_violations_before_patch_for(
|
133
|
-
# The strategy to get the violations before this PR is to reverse the patch on each `
|
143
|
+
sig { params(package_todo_yml_file: String).returns(T::Array[BasicReferenceOffense]) }
|
144
|
+
def get_violations_before_patch_for(package_todo_yml_file)
|
145
|
+
# The strategy to get the violations before this PR is to reverse the patch on each `package_todo.yml`.
|
134
146
|
# A previous strategy attempted to use `git merge-base --fork-point`, but there are many situations where it returns
|
135
147
|
# empty values. That strategy is fickle because it depends on the state of the `reflog` within the CI suite, which appears
|
136
148
|
# to not be reliable to depend on.
|
@@ -139,24 +151,24 @@ module DangerPackwerk
|
|
139
151
|
# the PR without needing to use git commands that interpret the branch history based on local git history.
|
140
152
|
#
|
141
153
|
# 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
|
142
|
-
# the files they modify). After parsing the violations from that `
|
154
|
+
# the files they modify). After parsing the violations from that `package_todo.yml` file with the patch reversed,
|
143
155
|
# we use a temporary copy of the original file to rewrite to it with the original contents.
|
144
156
|
# Note that practically speaking, we don't need to rewrite the original contents (since we already fetched the
|
145
157
|
# original contents above and the CI file system should be ephemeral). However, we do this anyways in case we later change these
|
146
158
|
# assumptions, or another client's environment is different and expects these files not to be mutated.
|
147
159
|
|
148
160
|
# 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.
|
149
|
-
|
161
|
+
package_todo_yml_file_copy = (File.read(package_todo_yml_file) if File.exist?(package_todo_yml_file))
|
150
162
|
|
151
163
|
Tempfile.create do |patch_file|
|
152
|
-
# Normally we'd use `git.diff_for_file(
|
164
|
+
# 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.
|
153
165
|
# I have a fix for that here: https://github.com/danger/danger/pull/1357
|
154
166
|
# Until that lands, I'm just using the underlying implementation of that method to get the diff for a file.
|
155
167
|
# Note that I might want to use a safe escape operator, `&.patch` and return gracefully if the patch cannot be found.
|
156
168
|
# However I'd be interested in why that ever happens, so for now going to proceed as is.
|
157
169
|
# (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,
|
158
170
|
# but we don't have that quite yet.)
|
159
|
-
patch_for_file = git.diff[
|
171
|
+
patch_for_file = git.diff[package_todo_yml_file].patch
|
160
172
|
# 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
|
161
173
|
# it gives us a patch without a newline.
|
162
174
|
# https://stackoverflow.com/questions/18142870/git-error-fatal-corrupt-patch-at-line-36
|
@@ -165,13 +177,13 @@ module DangerPackwerk
|
|
165
177
|
# https://git-scm.com/docs/git-apply
|
166
178
|
_stdout, _stderr, _status = Open3.capture3("git apply --reverse #{patch_file.path}")
|
167
179
|
# https://www.rubyguides.com/2019/05/ruby-tempfile/
|
168
|
-
BasicReferenceOffense.from(
|
180
|
+
BasicReferenceOffense.from(package_todo_yml_file)
|
169
181
|
end
|
170
182
|
ensure
|
171
|
-
if
|
172
|
-
File.write(
|
183
|
+
if package_todo_yml_file_copy
|
184
|
+
File.write(package_todo_yml_file, package_todo_yml_file_copy)
|
173
185
|
else
|
174
|
-
File.delete(
|
186
|
+
File.delete(package_todo_yml_file)
|
175
187
|
end
|
176
188
|
end
|
177
189
|
end
|
@@ -21,6 +21,10 @@ module DangerPackwerk
|
|
21
21
|
DEFAULT_ON_FAILURE = T.let(->(offenses) {}, OnFailure)
|
22
22
|
DEFAULT_FAIL = false
|
23
23
|
DEFAULT_FAILURE_MESSAGE = 'Packwerk violations were detected! Please resolve them to unblock the build.'
|
24
|
+
DEFAULT_VIOLATION_TYPES = T.let([
|
25
|
+
DEPENDENCY_VIOLATION_TYPE,
|
26
|
+
PRIVACY_VIOLATION_TYPE
|
27
|
+
], T::Array[String])
|
24
28
|
|
25
29
|
class CommentGroupingStrategy < ::T::Enum
|
26
30
|
enums do
|
@@ -38,6 +42,7 @@ module DangerPackwerk
|
|
38
42
|
fail_build: T::Boolean,
|
39
43
|
failure_message: String,
|
40
44
|
on_failure: OnFailure,
|
45
|
+
violation_types: T::Array[String],
|
41
46
|
grouping_strategy: CommentGroupingStrategy
|
42
47
|
).void
|
43
48
|
end
|
@@ -47,6 +52,7 @@ module DangerPackwerk
|
|
47
52
|
fail_build: DEFAULT_FAIL,
|
48
53
|
failure_message: DEFAULT_FAILURE_MESSAGE,
|
49
54
|
on_failure: DEFAULT_ON_FAILURE,
|
55
|
+
violation_types: DEFAULT_VIOLATION_TYPES,
|
50
56
|
grouping_strategy: CommentGroupingStrategy::PerConstantPerLocation
|
51
57
|
)
|
52
58
|
offenses_formatter ||= Check::DefaultFormatter.new
|
@@ -88,11 +94,13 @@ module DangerPackwerk
|
|
88
94
|
|
89
95
|
renamed_files = git.renamed_files.map { |before_after_file| before_after_file[:after] }
|
90
96
|
|
91
|
-
# Ignore references that have been renamed
|
92
97
|
packwerk_reference_offenses_to_care_about = packwerk_reference_offenses.reject do |packwerk_reference_offense|
|
93
98
|
constant_name = packwerk_reference_offense.reference.constant.name
|
94
99
|
filepath_that_defines_this_constant = Private.constant_resolver.resolve(constant_name)&.location
|
95
|
-
|
100
|
+
# Ignore references that have been renamed
|
101
|
+
renamed_files.include?(filepath_that_defines_this_constant) ||
|
102
|
+
# Ignore violations that are not in the allow-list of violation types to leave comments for
|
103
|
+
!violation_types.include?(packwerk_reference_offense.violation_type)
|
96
104
|
end
|
97
105
|
|
98
106
|
# We group by the constant name, line number, and reference path. Any offenses with these same values should only differ on what type of violation
|
@@ -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
|
data/lib/danger-packwerk.rb
CHANGED
@@ -5,10 +5,12 @@
|
|
5
5
|
require 'sorbet-runtime'
|
6
6
|
|
7
7
|
module DangerPackwerk
|
8
|
-
|
8
|
+
PACKAGE_TODO_PATTERN = T.let(/.*?package_todo\.yml\z/.freeze, Regexp)
|
9
|
+
DEPENDENCY_VIOLATION_TYPE = 'dependency'
|
10
|
+
PRIVACY_VIOLATION_TYPE = 'privacy'
|
9
11
|
|
10
12
|
require 'danger-packwerk/danger_packwerk'
|
11
|
-
require 'danger-packwerk/
|
13
|
+
require 'danger-packwerk/danger_package_todo_yml_changes'
|
12
14
|
require 'danger-packwerk/check/offenses_formatter'
|
13
15
|
require 'danger-packwerk/check/default_formatter'
|
14
16
|
require 'danger-packwerk/update/offenses_formatter'
|