modularization_statistics 1.37.0 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 252db0bdd5ec50e555d34ce4c7468df4a5bf731274fafd137a319eaf1a1100b9
4
- data.tar.gz: 8983a207179da6a93b34464f98c3b478a23087b34946f63d4c1cf95992d10f9e
3
+ metadata.gz: c11bc9704708d387bff6b5d7245832c6d57c9291a7dcc306513e3942afb53311
4
+ data.tar.gz: 7d5403d37a5811f5ef1798a3a8bfb96b304124ada289b75dba14db0844fd252e
5
5
  SHA512:
6
- metadata.gz: cebc018b3841416b6194e3cef53779f3529f2d56463bbd8c6d79a6fb322f9c1c1a81d0a357fb8140cd48853abe6d5d1de160e707414b2d71eccbdbbb36dc9993
7
- data.tar.gz: d837d652fed0baca80bfc6e1613d1ab75e1ac5b5b7d798c7df544a53e3c27c5c20ab82d8d39891aa29034541086d0e6a6b3e806d22b5b575c836951a1f96cac7
6
+ metadata.gz: 2c82a234d983ddc50d494f6eb8933633a0b61a7e82783d375f5d22b02c1e12fb476f9fb70ca35885ecadc879dd1fc94179cf6db3b1d52f47501afe38c6349651
7
+ data.tar.gz: ec00762292cdec0eb441ad424bdd5e80733a9acd418925f82c92142988df9d4b3b6843b8e4de1d9b8190e48cf23f6818b2e4f7176bf263a6541fa490181c38b0
@@ -17,36 +17,34 @@ module ModularizationStatistics
17
17
  all_metrics = []
18
18
  app_level_tag = Tag.for('app', app_name)
19
19
  package_tags = T.let([app_level_tag], T::Array[Tag])
20
- protected_packages = packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
21
20
 
22
21
  all_metrics << GaugeMetric.for('all_packages.count', packages.count, package_tags)
23
22
  all_metrics << GaugeMetric.for('all_packages.dependencies.count', packages.sum { |package| package.dependencies.count }, package_tags)
24
- all_metrics << GaugeMetric.for('all_packages.dependency_violations.count', protected_packages.sum { |package| Metrics.file_count(package.violations.select(&:dependency?)) }, package_tags)
25
- all_metrics << GaugeMetric.for('all_packages.privacy_violations.count', protected_packages.sum { |package| Metrics.file_count(package.violations.select(&:privacy?)) }, package_tags)
23
+ all_metrics << GaugeMetric.for('all_packages.dependency_violations.count', packages.sum { |package| Metrics.file_count(package.violations.select(&:dependency?)) }, package_tags)
24
+ all_metrics << GaugeMetric.for('all_packages.privacy_violations.count', packages.sum { |package| Metrics.file_count(package.violations.select(&:privacy?)) }, package_tags)
26
25
  all_metrics << GaugeMetric.for('all_packages.enforcing_dependencies.count', packages.count(&:enforces_dependencies?), package_tags)
27
26
  all_metrics << GaugeMetric.for('all_packages.enforcing_privacy.count', packages.count(&:enforces_privacy?), package_tags)
28
27
 
29
28
  all_metrics << GaugeMetric.for('all_packages.notify_on_package_yml_changes.count', packages.count { |p| p.metadata['notify_on_package_yml_changes'] }, package_tags)
30
29
  all_metrics << GaugeMetric.for('all_packages.notify_on_new_violations.count', packages.count { |p| p.metadata['notify_on_new_violations'] }, package_tags)
31
30
 
32
- all_metrics << GaugeMetric.for('all_packages.with_violations.count', protected_packages.count { |package| package.violations.any? }, package_tags)
31
+ all_metrics << GaugeMetric.for('all_packages.with_violations.count', packages.count { |package| package.violations.any? }, package_tags)
33
32
  all_metrics += Metrics::PublicUsage.get_public_usage_metrics('all_packages', packages, package_tags)
34
33
  all_metrics << GaugeMetric.for('all_packages.has_readme.count', packages.count { |package| Metrics.has_readme?(package) }, package_tags)
35
34
 
36
- all_metrics += Metrics::ProtectionUsage.get_protections_metrics('all_packages', protected_packages, package_tags)
37
- all_metrics += Metrics::RubocopProtectionsExclusions.get_rubocop_exclusions('all_packages', protected_packages, package_tags)
35
+ all_metrics += Metrics::ProtectionUsage.get_protections_metrics('all_packages', packages, package_tags)
36
+ all_metrics += Metrics::RubocopProtectionsExclusions.get_rubocop_exclusions('all_packages', packages, package_tags)
38
37
  all_metrics << GaugeMetric.for('all_packages.package_based_file_ownership.count', packages.count { |package| !package.metadata['owner'].nil? }, package_tags)
39
38
 
40
- inbound_violations_by_package = protected_packages.flat_map(&:violations).group_by(&:to_package_name)
39
+ inbound_violations_by_package = packages.flat_map(&:violations).group_by(&:to_package_name)
41
40
 
42
- protected_packages.each do |protected_package|
43
- package = protected_package.original_package
41
+ packages.each do |package|
44
42
  package_tags = Metrics.tags_for_package(package, app_name)
45
43
 
46
44
  #
47
45
  # VIOLATIONS (implicit dependencies)
48
46
  #
49
- outbound_violations = protected_package.violations
47
+ outbound_violations = package.violations
50
48
  inbound_violations = inbound_violations_by_package[package.name] || []
51
49
  all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
52
50
  all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
@@ -62,7 +60,7 @@ module ModularizationStatistics
62
60
 
63
61
  all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_package', [package], package_tags)
64
62
 
65
- protected_package.violations.group_by(&:to_package_name).each do |to_package_name, violations|
63
+ package.violations.group_by(&:to_package_name).each do |to_package_name, violations|
66
64
  to_package = ParsePackwerk.find(to_package_name)
67
65
  if to_package.nil?
68
66
  raise StandardError, "Could not find matching package #{to_package_name}"
@@ -16,25 +16,25 @@ module ModularizationStatistics
16
16
  def self.get_package_metrics_by_team(all_packages, app_name)
17
17
  all_metrics = T.let([], T::Array[GaugeMetric])
18
18
  app_level_tag = Tag.for('app', app_name)
19
- all_protected_packages = all_packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
20
- all_protected_packages.group_by { |protected_package| CodeOwnership.for_package(protected_package.original_package)&.name }.each do |team_name, protected_packages_by_team|
19
+
20
+ all_packages.group_by { |package| CodeOwnership.for_package(package)&.name }.each do |team_name, packages_by_team|
21
21
  # We look at `all_packages` because we care about ALL inbound violations across all teams
22
- inbound_violations_by_package = all_protected_packages.flat_map(&:violations).group_by(&:to_package_name)
22
+ inbound_violations_by_package = all_packages.flat_map(&:violations).group_by(&:to_package_name)
23
23
 
24
24
  team_tags = Metrics.tags_for_team(team_name) + [app_level_tag]
25
- all_metrics << GaugeMetric.for('by_team.all_packages.count', protected_packages_by_team.count, team_tags)
26
- all_metrics += Metrics::ProtectionUsage.get_protections_metrics('by_team', protected_packages_by_team, team_tags)
27
- all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_team', protected_packages_by_team.map(&:original_package), team_tags)
25
+ all_metrics << GaugeMetric.for('by_team.all_packages.count', packages_by_team.count, team_tags)
26
+ all_metrics += Metrics::ProtectionUsage.get_protections_metrics('by_team', packages_by_team, team_tags)
27
+ all_metrics += Metrics::PublicUsage.get_public_usage_metrics('by_team', packages_by_team, team_tags)
28
28
 
29
- all_metrics << GaugeMetric.for('by_team.notify_on_package_yml_changes.count', protected_packages_by_team.count { |p| p.metadata['notify_on_package_yml_changes'] }, team_tags)
30
- all_metrics << GaugeMetric.for('by_team.notify_on_new_violations.count', protected_packages_by_team.count { |p| p.metadata['notify_on_new_violations'] }, team_tags)
29
+ all_metrics << GaugeMetric.for('by_team.notify_on_package_yml_changes.count', packages_by_team.count { |p| p.metadata['notify_on_package_yml_changes'] }, team_tags)
30
+ all_metrics << GaugeMetric.for('by_team.notify_on_new_violations.count', packages_by_team.count { |p| p.metadata['notify_on_new_violations'] }, team_tags)
31
31
 
32
32
  #
33
33
  # VIOLATIONS (implicit dependencies)
34
34
  #
35
- outbound_violations = protected_packages_by_team.flat_map(&:violations)
35
+ outbound_violations = packages_by_team.flat_map(&:violations)
36
36
  # Here we only look at packages_by_team because we only care about inbound violations onto packages for this team
37
- inbound_violations = protected_packages_by_team.flat_map { |package| inbound_violations_by_package[package.name] || [] }
37
+ inbound_violations = packages_by_team.flat_map { |package| inbound_violations_by_package[package.name] || [] }
38
38
  all_dependency_violations = (outbound_violations + inbound_violations).select(&:dependency?)
39
39
  all_privacy_violations = (outbound_violations + inbound_violations).select(&:privacy?)
40
40
 
@@ -47,7 +47,7 @@ module ModularizationStatistics
47
47
  all_metrics << GaugeMetric.for('by_team.outbound_privacy_violations.count', Metrics.file_count(outbound_violations.select(&:privacy?)), team_tags)
48
48
  all_metrics << GaugeMetric.for('by_team.inbound_privacy_violations.count', Metrics.file_count(inbound_violations.select(&:privacy?)), team_tags)
49
49
 
50
- all_metrics << GaugeMetric.for('by_team.has_readme.count', protected_packages_by_team.count { |protected_package| Metrics.has_readme?(protected_package.original_package) }, team_tags)
50
+ all_metrics << GaugeMetric.for('by_team.has_readme.count', packages_by_team.count { |package| Metrics.has_readme?(package) }, team_tags)
51
51
 
52
52
  grouped_outbound_violations = outbound_violations.group_by do |violation|
53
53
  to_package = ParsePackwerk.find(violation.to_package_name)
@@ -7,8 +7,9 @@ module ModularizationStatistics
7
7
  class ProtectionUsage
8
8
  extend T::Sig
9
9
 
10
- sig { params(prefix: String, protected_packages: T::Array[PackageProtections::ProtectedPackage], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
11
- def self.get_protections_metrics(prefix, protected_packages, package_tags)
10
+ sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
11
+ def self.get_protections_metrics(prefix, packages, package_tags)
12
+ protected_packages = packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
12
13
  PackageProtections.all.flat_map do |protection|
13
14
  PackageProtections::ViolationBehavior.each_value.map do |violation_behavior|
14
15
  # https://github.com/Gusto/package_protections/pull/42 changed the public API of these violation behaviors.
@@ -22,11 +23,126 @@ module ModularizationStatistics
22
23
  }
23
24
  violation_behavior_name = violation_behavior_map[violation_behavior]
24
25
  metric_name = "#{prefix}.#{protection.identifier}.#{violation_behavior_name}.count"
25
- count_of_packages = protected_packages.count { |p| p.violation_behavior_for(protection.identifier) == violation_behavior }
26
+ count_of_packages = protected_packages.count do |p|
27
+ #
28
+ # This is temporarily in place until we migrate off of `package_protections` in favor of `rubocop-packs`.
29
+ # At that point, we want to delete this branch and instead it we'd probably have two separate branches.
30
+ # One branch would look at `enforce_x` and `metadata.strictly_enforce_x`.
31
+ # The other branch would look at `.pack_rubocop.yml`.
32
+ # Later on, we could generalize this so that it automatically incorporates new cops from `rubocop-packs`,
33
+ # or even new packwerk plugins.
34
+ #
35
+ # Regardless, we'll want to keep the way we are naming these behaviors for now to preserve historical trends in the data.
36
+ #
37
+ if p.metadata['protections']
38
+ p.violation_behavior_for(protection.identifier) == violation_behavior
39
+ else
40
+ should_count_package?(p.original_package, protection, violation_behavior)
41
+ end
42
+ end
26
43
  GaugeMetric.for(metric_name, count_of_packages, package_tags)
27
44
  end
28
45
  end
29
46
  end
47
+
48
+ #
49
+ # Later, when we remove package protections, we can make this simpler by iterating over
50
+ # packwerk checkers and rubocop packs specifically. That would let us use a common, simple
51
+ # strategy to get metrics for both of them. For the first iteration, we'll want to continue
52
+ # to map the old names of things to the "protection" names. After that, I think we will want to
53
+ # extract that mapping into a tool that transforms the metrics that can be optionally turned off
54
+ # so that we can see metrics that are more closely connected to the new API.
55
+ # e.g. instead of `all_packages.prevent_this_package_from_violating_its_stated_dependencies.fail_on_any.count`, we'd see
56
+ # e.g. instead of `all_packages.checkers.enforce_dependencies.strict.count`, we'd see
57
+ # e.g. instead of `all_packages.prevent_this_package_from_creating_other_namespaces.fail_on_new.count`, we'd see
58
+ # e.g. instead of `all_packages.cops.packs_namespaceconvention.true.count`, we'd see
59
+ #
60
+ sig do
61
+ params(
62
+ package: ParsePackwerk::Package,
63
+ protection: PackageProtections::ProtectionInterface,
64
+ violation_behavior: PackageProtections::ViolationBehavior
65
+ ).returns(T::Boolean)
66
+ end
67
+ def self.should_count_package?(package, protection, violation_behavior)
68
+ if protection.identifier == 'prevent_this_package_from_violating_its_stated_dependencies'
69
+ strict_mode = package.metadata['strictly_enforce_dependencies']
70
+ enabled = package.enforces_dependencies?
71
+
72
+ case violation_behavior
73
+ when PackageProtections::ViolationBehavior::FailOnAny
74
+ !!strict_mode
75
+ when PackageProtections::ViolationBehavior::FailNever
76
+ !enabled
77
+ when PackageProtections::ViolationBehavior::FailOnNew
78
+ enabled && !strict_mode
79
+ else
80
+ T.absurd(violation_behavior)
81
+ end
82
+ elsif protection.identifier == 'prevent_other_packages_from_using_this_packages_internals'
83
+ strict_mode = package.metadata['strictly_enforce_privacy']
84
+ enabled = package.enforces_privacy?
85
+
86
+ case violation_behavior
87
+ when PackageProtections::ViolationBehavior::FailOnAny
88
+ !!strict_mode
89
+ when PackageProtections::ViolationBehavior::FailNever
90
+ !enabled
91
+ when PackageProtections::ViolationBehavior::FailOnNew
92
+ enabled && !strict_mode
93
+ else
94
+ T.absurd(violation_behavior)
95
+ end
96
+ elsif protection.identifier == 'prevent_other_packages_from_using_this_package_without_explicit_visibility'
97
+ case violation_behavior
98
+ when PackageProtections::ViolationBehavior::FailOnAny
99
+ # We'd probably not want to support this right away
100
+ false
101
+ when PackageProtections::ViolationBehavior::FailNever
102
+ # We'd need to add this to `parse_packwerk` so that we can get other arbitrary top-level keys.
103
+ # Alternatively we can put this in `metadata` for the time being to unblock us.
104
+ # package.config['enforce_visibility']
105
+ !package.metadata['enforce_visibility']
106
+ when PackageProtections::ViolationBehavior::FailOnNew
107
+ !!package.metadata['enforce_visibility']
108
+ else
109
+ T.absurd(violation_behavior)
110
+ end
111
+ else
112
+ # Otherwise, we're in a rubocop case
113
+ rubocop_yml_file = package.directory.join('.rubocop.yml')
114
+ return false if !rubocop_yml_file.exist?
115
+ rubocop_yml = YAML.load_file(rubocop_yml_file)
116
+ protection = T.cast(protection, PackageProtections::RubocopProtectionInterface)
117
+ # We will likely want a rubocop-packs API for this, to be able to ask if a cop is enabled for a pack.
118
+ # It's possible we will want to allow these to be enabled at the top-level `.rubocop.yml`,
119
+ # in which case we wouldn't get the right metrics with this approach. However, we can also accept
120
+ # that as a current limitation.
121
+ cop_map = {
122
+ 'PackageProtections/TypedPublicApi' => 'Packs/TypedPublicApi',
123
+ 'PackageProtections/NamespacedUnderPackageName' => 'Packs/NamespaceConvention',
124
+ 'PackageProtections/OnlyClassMethods' => 'Packs/ClassMethodsAsPublicApis',
125
+ 'PackageProtections/RequireDocumentedPublicApis' => 'Packs/RequireDocumentedPublicApis',
126
+ }
127
+ # We want to use the cop names from `rubocop-packs`. Eventually, we'll just literate over these
128
+ # cop names directly, or ask `rubocop-packs` for the list of cops to care about.
129
+ cop_config = rubocop_yml[cop_map[protection.cop_name]]
130
+ return false if cop_config.nil?
131
+ enabled = cop_config['Enabled']
132
+ strict_mode = cop_config['FailureMode'] == 'strict'
133
+
134
+ case violation_behavior
135
+ when PackageProtections::ViolationBehavior::FailOnAny
136
+ !!strict_mode
137
+ when PackageProtections::ViolationBehavior::FailNever
138
+ !enabled
139
+ when PackageProtections::ViolationBehavior::FailOnNew
140
+ enabled && !strict_mode
141
+ else
142
+ T.absurd(violation_behavior)
143
+ end
144
+ end
145
+ end
30
146
  end
31
147
  end
32
148
  end
@@ -7,8 +7,10 @@ module ModularizationStatistics
7
7
  class RubocopProtectionsExclusions
8
8
  extend T::Sig
9
9
 
10
- sig { params(prefix: String, protected_packages: T::Array[PackageProtections::ProtectedPackage], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
11
- def self.get_rubocop_exclusions(prefix, protected_packages, package_tags)
10
+ sig { params(prefix: String, packages: T::Array[ParsePackwerk::Package], package_tags: T::Array[Tag]).returns(T::Array[GaugeMetric]) }
11
+ def self.get_rubocop_exclusions(prefix, packages, package_tags)
12
+ protected_packages = packages.map { |p| PackageProtections::ProtectedPackage.from(p) }
13
+
12
14
  rubocop_based_package_protections = T.cast(PackageProtections.all.select { |p| p.is_a?(PackageProtections::RubocopProtectionInterface) }, T::Array[PackageProtections::RubocopProtectionInterface])
13
15
  rubocop_based_package_protections.flat_map do |rubocop_based_package_protection|
14
16
  metric_name = "#{prefix}.#{rubocop_based_package_protection.identifier}.rubocop_exclusions.count"
@@ -17,6 +19,7 @@ module ModularizationStatistics
17
19
  end
18
20
  end
19
21
 
22
+ # TODO: `rubocop-packs` may want to expose API for this
20
23
  sig { params(package: ParsePackwerk::Package, protection: PackageProtections::RubocopProtectionInterface).returns(Integer) }
21
24
  def self.exclude_count_for_package_and_protection(package, protection)
22
25
  rubocop_todo = package.directory.join('.rubocop_todo.yml')
@@ -15,7 +15,7 @@ module ParsePackwerk
15
15
  sig { params(name: ::String).returns(T.nilable(::ParsePackwerk::Package)) }
16
16
  def find(name); end
17
17
 
18
- sig { params(file_path: T.any(::Pathname, ::String)).returns(T.nilable(::ParsePackwerk::Package)) }
18
+ sig { params(file_path: T.any(::Pathname, ::String)).returns(::ParsePackwerk::Package) }
19
19
  def package_from_path(file_path); end
20
20
 
21
21
  sig { params(package: ::ParsePackwerk::Package).void }
@@ -51,6 +51,7 @@ end
51
51
 
52
52
  ParsePackwerk::DEFAULT_EXCLUDE_GLOBS = T.let(T.unsafe(nil), Array)
53
53
  ParsePackwerk::DEFAULT_PACKAGE_PATHS = T.let(T.unsafe(nil), Array)
54
+ ParsePackwerk::DEFAULT_PUBLIC_PATH = T.let(T.unsafe(nil), String)
54
55
  ParsePackwerk::DEPENDENCIES = T.let(T.unsafe(nil), String)
55
56
  ParsePackwerk::DEPRECATED_REFERENCES_YML_NAME = T.let(T.unsafe(nil), String)
56
57
 
@@ -81,6 +82,7 @@ end
81
82
 
82
83
  ParsePackwerk::PACKAGE_YML_NAME = T.let(T.unsafe(nil), String)
83
84
  ParsePackwerk::PACKWERK_YML_NAME = T.let(T.unsafe(nil), String)
85
+ ParsePackwerk::PUBLIC_PATH = T.let(T.unsafe(nil), String)
84
86
 
85
87
  class ParsePackwerk::Package < ::T::Struct
86
88
  const :dependencies, T::Array[::String]
@@ -88,6 +90,7 @@ class ParsePackwerk::Package < ::T::Struct
88
90
  const :enforce_privacy, T::Boolean
89
91
  const :metadata, T::Hash[T.untyped, T.untyped]
90
92
  const :name, ::String
93
+ const :public_path, ::String, default: T.unsafe(nil)
91
94
 
92
95
  sig { returns(::Pathname) }
93
96
  def directory; end
@@ -98,6 +101,12 @@ class ParsePackwerk::Package < ::T::Struct
98
101
  sig { returns(T::Boolean) }
99
102
  def enforces_privacy?; end
100
103
 
104
+ sig { returns(::Pathname) }
105
+ def public_directory; end
106
+
107
+ sig { returns(T::Array[::ParsePackwerk::Violation]) }
108
+ def violations; end
109
+
101
110
  sig { returns(::Pathname) }
102
111
  def yml; end
103
112
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modularization_statistics
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.37.0
4
+ version: 1.39.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-07 00:00:00.000000000 Z
11
+ date: 2022-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: code_teams
@@ -194,7 +194,7 @@ files:
194
194
  - sorbet/rbi/gems/dogapi@1.45.0.rbi
195
195
  - sorbet/rbi/gems/manual.rbi
196
196
  - sorbet/rbi/gems/package_protections@1.4.0.rbi
197
- - sorbet/rbi/gems/parse_packwerk@0.12.0.rbi
197
+ - sorbet/rbi/gems/parse_packwerk@0.14.0.rbi
198
198
  - sorbet/rbi/gems/rspec@3.10.0.rbi
199
199
  - sorbet/rbi/todo.rbi
200
200
  homepage: https://github.com/rubyatscale/modularization_statistics
@@ -220,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
220
  - !ruby/object:Gem::Version
221
221
  version: '0'
222
222
  requirements: []
223
- rubygems_version: 3.3.7
223
+ rubygems_version: 3.1.6
224
224
  signing_key:
225
225
  specification_version: 4
226
226
  summary: A gem to collect statistics about modularization progress in a Rails application