danger-packwerk 0.7.1 → 0.8.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: 0ad3f46c259911564eb4b955fb62e8152962db5c5682a6ba6000054dde9d8128
4
+ data.tar.gz: ef61b403058b20869cda462629724943317e55f5f4b1bfe125f9be7323a634c4
5
5
  SHA512:
6
- metadata.gz: 8cff9b5c28a2357e3c910cfa3c4ee0fd502267950165422ad95c74a07fd0f01f57c613e092670cc41de4d57921a9d88308b02d6de516b481b92a36bfa9fff19c
7
- data.tar.gz: fee7d3f4a94bed71d61ebcd878a63c8ad89bd60c9a44e4e18c4d2040442d2ede40483f28eb3ea66cefa69f3f0d5ecabadf3f3b4dc6b766b8b5d4e6ab2810fc4e
6
+ metadata.gz: cfc452185f83aef5f9ff5e53c475ac7472d7cfcda477a0a1f1fcc863704df6e93358a6e6096f5d4f1afb68176c687c8748a8c2c2562458c03bac47d120de91b4
7
+ data.tar.gz: '08df3f5e1d6e6d7367c3dc85f464346cf479dd13b5d831b26847b194f90190cb33e429c92095517124ae78eb0d23479c2a3b1146ab5a1ef529bfe967b9cf9192'
data/README.md CHANGED
@@ -83,11 +83,11 @@ Without any configuration, `deprecated_references_yml_changes.check` should just
83
83
  `deprecated_references_yml_changes.check` can be configured to in the following ways:
84
84
 
85
85
  ### 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:
86
+ The default message displayed is from `lib/danger-packwerk/private/default_offenses_formatter.rb`. To customize this this, pass in `offenses_formatter` to `deprecated_references_yml_changes.check` in your `Dangerfile`. Here's a simple example:
87
87
  ```ruby
88
88
  deprecated_references_yml_changes.check(
89
89
  # Offenses are a T::Array[DangerPackwerk::BasicReferenceOffense]
90
- added_offenses_formatter: -> (added_offenses) do
90
+ offenses_formatter: -> (added_offenses) do
91
91
  "There are #{added_offenses.count} new violations this line!"
92
92
  end
93
93
  )
@@ -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?(Packwerk::ViolationType::Dependency) && violation_types.include?(Packwerk::ViolationType::Privacy)
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?(Packwerk::ViolationType::Dependency)
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?(Packwerk::ViolationType::Privacy)
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
@@ -17,23 +17,25 @@ 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
20
  BeforeComment = T.type_alias { T.proc.params(violation_diff: ViolationDiff, changed_deprecated_references_ymls: T::Array[String]).void }
23
21
  DEFAULT_BEFORE_COMMENT = T.let(->(violation_diff, changed_deprecated_references_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
  )
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
+
37
39
  changed_deprecated_references_ymls = (git.modified_files + git.added_files + git.deleted_files).grep(DEPRECATED_REFERENCES_PATTERN)
38
40
 
39
41
  violation_diff = get_violation_diff
@@ -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
  )
@@ -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
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+
3
+ require 'code_ownership'
4
+
5
+ module DangerPackwerk
6
+ module Private
7
+ class OwnershipInformation < T::Struct
8
+ extend T::Sig
9
+
10
+ const :owning_team, T.nilable(CodeTeams::Team)
11
+ const :github_team, T.nilable(String)
12
+ const :slack_channel, T.nilable(String)
13
+ const :org_name, T.nilable(String)
14
+
15
+ sig { params(package: ParsePackwerk::Package, org_name: String).returns(OwnershipInformation) }
16
+ def self.for_package(package, org_name)
17
+ team = CodeOwnership.for_package(package)
18
+
19
+ if team.nil?
20
+ OwnershipInformation.new
21
+ else
22
+ OwnershipInformation.new(
23
+ owning_team: team,
24
+ github_team: team.raw_hash.fetch('github', {}).fetch('team', nil),
25
+ slack_channel: team.raw_hash.fetch('slack', {}).fetch('room_for_humans', nil),
26
+ org_name: org_name
27
+ )
28
+ end
29
+ end
30
+
31
+ sig { returns(String) }
32
+ def ownership_copy
33
+ github_team_flow_sensitive = github_team
34
+ slack_channel_flow_sensitive = slack_channel
35
+
36
+ if owning_team && github_team_flow_sensitive && slack_channel_flow_sensitive
37
+ team_slack_link = markdown_link_to_slack_room
38
+ "- Owned by #{markdown_link_to_github_members_no_tag} (Slack: #{team_slack_link})"
39
+ else
40
+ '- This pack is unowned.'
41
+ end
42
+ end
43
+
44
+ sig { returns(String) }
45
+ def markdown_link_to_slack_room
46
+ "[<ins>#{slack_channel}</ins>](https://slack.com/app_redirect?channel=#{T.must(slack_channel).delete('#')})"
47
+ end
48
+
49
+ #
50
+ # Note this will NOT tag the team on Github, but it will link
51
+ # to the mentioned team's members page. If you want to tag and
52
+ # link the team, simply use the string and Github will handle it.
53
+ #
54
+ sig { returns(String) }
55
+ def markdown_link_to_github_members_no_tag
56
+ "[<ins>#{github_team}</ins>](https://github.com/orgs/#{org_name}/teams/#{T.must(github_team).gsub("@#{org_name}/", '')}/members)"
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,7 +1,7 @@
1
1
  # typed: strict
2
2
 
3
3
  require 'danger-packwerk/private/deprecated_references'
4
- require 'danger-packwerk/private/default_offenses_formatter'
4
+ require 'danger-packwerk/private/ownership_information'
5
5
  require 'constant_resolver'
6
6
 
7
7
  module DangerPackwerk
@@ -0,0 +1,67 @@
1
+ # typed: strict
2
+
3
+ module DangerPackwerk
4
+ module Update
5
+ class DefaultFormatter
6
+ extend T::Sig
7
+ include OffensesFormatter
8
+
9
+ sig do
10
+ params(
11
+ custom_help_message: T.nilable(String)
12
+ ).void
13
+ end
14
+ def initialize(custom_help_message: nil)
15
+ @custom_help_message = custom_help_message
16
+ end
17
+
18
+ sig { override.params(offenses: T::Array[BasicReferenceOffense], repo_link: String, org_name: String).returns(String) }
19
+ def format_offenses(offenses, repo_link, org_name)
20
+ violation = T.must(offenses.first)
21
+ referencing_file_pack = ParsePackwerk.package_from_path(violation.file)
22
+ # We remove leading double colons as they feel like an implementation detail of packwerk.
23
+ constant_name = violation.class_name.delete_prefix('::')
24
+ constant_source_package_name = violation.to_package_name
25
+
26
+ constant_source_package = T.must(ParsePackwerk.find(constant_source_package_name))
27
+ constant_source_package_owner = Private::OwnershipInformation.for_package(constant_source_package, org_name)
28
+
29
+ package_referring_to_constant_owner = Private::OwnershipInformation.for_package(referencing_file_pack, org_name)
30
+
31
+ disclaimer = 'We noticed you ran `bin/packwerk update-deprecations`. Check out [the docs](https://github.com/Shopify/packwerk/blob/main/RESOLVING_VIOLATIONS.md) to see other ways to resolve violations.'
32
+ pluralized_violation = offenses.count > 1 ? 'these violations' : 'this violation'
33
+ request_to_add_context = "- Could you add some context as a reply here about why we needed to add #{pluralized_violation}?\n"
34
+
35
+ dependency_violation_message = "- cc #{package_referring_to_constant_owner.github_team} (#{package_referring_to_constant_owner.markdown_link_to_slack_room}) for the dependency violation.\n" if package_referring_to_constant_owner.owning_team
36
+
37
+ privacy_violation_message = "- cc #{constant_source_package_owner.github_team} (#{constant_source_package_owner.markdown_link_to_slack_room}) for the privacy violation.\n" if constant_source_package_owner.owning_team
38
+
39
+ if offenses.any?(&:dependency?) && offenses.any?(&:privacy?)
40
+ <<~MESSAGE.chomp
41
+ Hi again! It looks like `#{constant_name}` is private API of `#{constant_source_package_name}`, which is also not in `#{referencing_file_pack.name}`'s list of dependencies.
42
+ #{disclaimer}
43
+
44
+ #{request_to_add_context}#{dependency_violation_message}#{privacy_violation_message}
45
+ #{@custom_help_message}
46
+ MESSAGE
47
+ elsif offenses.any?(&:dependency?)
48
+ <<~MESSAGE.chomp
49
+ Hi again! It looks like `#{constant_name}` belongs to `#{constant_source_package_name}`, which is not in `#{referencing_file_pack.name}`'s list of dependencies.
50
+ #{disclaimer}
51
+
52
+ #{request_to_add_context}#{dependency_violation_message}
53
+ #{@custom_help_message}
54
+ MESSAGE
55
+ else # violations.any?(&:privacy?)
56
+ <<~MESSAGE.chomp
57
+ Hi again! It looks like `#{constant_name}` is private API of `#{constant_source_package_name}`.
58
+ #{disclaimer}
59
+
60
+ #{request_to_add_context}#{privacy_violation_message}
61
+ #{@custom_help_message}
62
+ MESSAGE
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+
3
+ require 'code_ownership'
4
+
5
+ module DangerPackwerk
6
+ module Update
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[BasicReferenceOffense],
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module DangerPackwerk
5
- VERSION = '0.7.1'
5
+ VERSION = '0.8.0'
6
6
  end
@@ -9,4 +9,8 @@ module DangerPackwerk
9
9
 
10
10
  require 'danger-packwerk/danger_packwerk'
11
11
  require 'danger-packwerk/danger_deprecated_references_yml_changes'
12
+ require 'danger-packwerk/check/offenses_formatter'
13
+ require 'danger-packwerk/check/default_formatter'
14
+ require 'danger-packwerk/update/offenses_formatter'
15
+ require 'danger-packwerk/update/default_formatter'
12
16
  end