package_protections 0.66.0 → 1.1.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: 2aa463f6b2b4909e574bf47e2f5b4f330a48c074ada35eb451339a3ba6d35840
4
- data.tar.gz: 07ead3e39863fed6327a677d1eff361e8f00a55db847df59b8e05a244e75228c
3
+ metadata.gz: 64a576d5910c24c2f966b5f8db6c025af93a31b866c3956cd98b54adbc7d52da
4
+ data.tar.gz: e8a696185cdffdb44eb1a136ad4dba73498d249faacadcb731d1559eb7e94847
5
5
  SHA512:
6
- metadata.gz: 4e2fa4b640763d49d80adc1e3f400c617eaf348df01a5126d875cc4d6c4c2ec3c3b719ee3bb54923f2eaf86f48ed09159fdf90088c72d97e88a40971d16b759c
7
- data.tar.gz: 7496fb3bc5d5727729432e5b9447c52b01428679aa0269bf5b92049a922e91c831656cb21f32ddd56c76444c9c7826b332ff7704f12327dca4cf17dd452ef3db
6
+ metadata.gz: 63194b36d9bd8edf9faab0136a58994b9a93dcfb49001ab75d057dbb7bf2522b1b235ffae8352ffbe6b3d3f13c8f5f171a3e7498ade041787ff72cd466762958
7
+ data.tar.gz: 67417571f0fe028ef5960985518f753de09a7b253ff56dd878f4dec89e3ca0e796a9f928da78db68da1cd29e47bd6bbd56ebff4a4882941bf6635033eb2bf76b
data/README.md CHANGED
@@ -8,7 +8,7 @@ The intent of this gem is two fold:
8
8
  This gem ships with the following checks
9
9
  1) Your package is not introducing dependencies that are not intended (via `packwerk` `enforce_dependencies`)
10
10
  2) Other packages are not using the private API of your package (via `packwerk` `enforce_privacy`)
11
- 3) Your package has a typed public API (via the `rubocop` `Sorbet/StrictSigil` cop)
11
+ 3) Your package has a typed public API (via the `rubocop` `PackageProtections/TypedPublicApi` cop)
12
12
  4) Your package only creates a single namespace (via the `rubocop` `PackageProtections/NamespacedUnderPackageName` cop)
13
13
  4) Your package is only visible to a select number of packages (via the `packwerk` `enforce_privacy` cop)
14
14
 
@@ -141,21 +141,21 @@ end
141
141
  In this example, `MyCustomProtection` needs to implement the `PackageProtections::ProtectionInterface` (for protections powered by `packwerk` that look at new and existing violations) OR `PackageProtections::RubocopProtectionInterface` (for protections powered by `rubocop` that look at the AST). It's recommended to take a look at the existing protections as examples. If you're having any trouble with this, please file an issue and we'll be glad to help.
142
142
 
143
143
  ## Incorporating into your CI Pipeline
144
- Your CI pipeline can execute the public API ta and fail if there are any offenses.
144
+ Your CI pipeline can execute the public API and fail if there are any offenses.
145
145
 
146
146
  ## Discussions, Issues, Questions, and More
147
147
  To keep things organized, here are some recommended homes:
148
148
  ### Issues:
149
- https://github.com/bigrails/package_protections/issues
149
+ https://github.com/rubyatscale/package_protections/issues
150
150
 
151
151
  ### Questions:
152
- https://github.com/bigrails/package_protections/discussions/categories/q-a
152
+ https://github.com/rubyatscale/package_protections/discussions/categories/q-a
153
153
 
154
154
  ### General discussions:
155
- https://github.com/bigrails/package_protections/discussions/categories/general
155
+ https://github.com/rubyatscale/package_protections/discussions/categories/general
156
156
 
157
157
  ### Ideas, new features, requests for change:
158
- https://github.com/bigrails/package_protections/discussions/categories/ideas
158
+ https://github.com/rubyatscale/package_protections/discussions/categories/ideas
159
159
 
160
160
  ### Showcasing your work:
161
- https://github.com/bigrails/package_protections/discussions/categories/show-and-tell
161
+ https://github.com/rubyatscale/package_protections/discussions/categories/show-and-tell
@@ -28,8 +28,8 @@ module PackageProtections
28
28
  [
29
29
  Private::OutgoingDependencyProtection.new,
30
30
  Private::IncomingPrivacyProtection.new,
31
- Private::TypedApiProtection.new,
32
- Private::MultipleNamespacesProtection.new,
31
+ RuboCop::Cop::PackageProtections::TypedPublicApi.new,
32
+ RuboCop::Cop::PackageProtections::NamespacedUnderPackageName.new,
33
33
  Private::VisibilityProtection.new
34
34
  ]
35
35
  end
@@ -3,11 +3,9 @@
3
3
 
4
4
  require 'package_protections/private/colorized_string'
5
5
  require 'package_protections/private/output'
6
- require 'package_protections/private/typed_api_protection'
7
6
  require 'package_protections/private/incoming_privacy_protection'
8
7
  require 'package_protections/private/outgoing_dependency_protection'
9
8
  require 'package_protections/private/metadata_modifiers'
10
- require 'package_protections/private/multiple_namespaces_protection'
11
9
  require 'package_protections/private/visibility_protection'
12
10
  require 'package_protections/private/configuration'
13
11
 
@@ -17,14 +17,14 @@ module PackageProtections
17
17
  invalid_identifiers = metadata.keys - valid_identifiers
18
18
 
19
19
  if invalid_identifiers.any?
20
- raise IncorrectPublicApiUsageError.new("Invalid configuration for package `#{original_package.name}`. The metadata keys #{invalid_identifiers.inspect} are not valid behaviors under the `protection` metadata namespace. Valid keys are #{valid_identifiers.inspect}. See https://github.com/bigrails/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
20
+ raise IncorrectPublicApiUsageError.new("Invalid configuration for package `#{original_package.name}`. The metadata keys #{invalid_identifiers.inspect} are not valid behaviors under the `protection` metadata namespace. Valid keys are #{valid_identifiers.inspect}. See https://github.com/rubyatscale/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
21
21
  end
22
22
 
23
23
  protections = {}
24
24
  metadata.each_key do |protection_key|
25
25
  protection = PackageProtections.with_identifier(protection_key)
26
26
  if !protection
27
- raise IncorrectPublicApiUsageError.new("Invalid configuration for package `#{original_package.name}`. The metadata key #{protection_key} is not a valid behaviors under the `protection` metadata namespace. Valid keys are #{valid_identifiers.inspect}. See https://github.com/bigrails/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
27
+ raise IncorrectPublicApiUsageError.new("Invalid configuration for package `#{original_package.name}`. The metadata key #{protection_key} is not a valid behaviors under the `protection` metadata namespace. Valid keys are #{valid_identifiers.inspect}. See https://github.com/rubyatscale/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
28
28
  end
29
29
 
30
30
  protections[protection.identifier] = get_violation_behavior(protection, metadata, original_package)
@@ -57,7 +57,7 @@ module PackageProtections
57
57
  behavior = ViolationBehavior.from_raw_value(metadata[protection.identifier])
58
58
  unmet_preconditions = protection.unmet_preconditions_for_behavior(behavior, package)
59
59
  if !unmet_preconditions.nil?
60
- raise IncorrectPublicApiUsageError.new("#{protection.identifier} protection does not have the valid preconditions. #{unmet_preconditions}. See https://github.com/bigrails/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
60
+ raise IncorrectPublicApiUsageError.new("#{protection.identifier} protection does not have the valid preconditions. #{unmet_preconditions}. See https://github.com/rubyatscale/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
61
61
  end
62
62
 
63
63
  behavior
@@ -3,26 +3,6 @@
3
3
  # typed: strict
4
4
  module PackageProtections
5
5
  module RubocopProtectionInterface
6
- include ProtectionInterface
7
- extend T::Sig
8
- extend T::Helpers
9
-
10
- abstract!
11
-
12
- sig do
13
- abstract
14
- .params(packages: T::Array[ProtectedPackage])
15
- .returns(T::Array[CopConfig])
16
- end
17
- def cop_configs(packages); end
18
-
19
- sig do
20
- params(package: ProtectedPackage).returns(T::Hash[T.untyped, T.untyped])
21
- end
22
- def custom_cop_config(package)
23
- {}
24
- end
25
-
26
6
  class CopConfig < T::Struct
27
7
  extend T::Sig
28
8
  const :name, String
@@ -32,16 +12,7 @@ module PackageProtections
32
12
 
33
13
  sig { returns(String) }
34
14
  def to_rubocop_yml_compatible_format
35
- cop_config = {
36
- 'Enabled' => enabled,
37
- # Inherit mode ensures that the client can still use the cop outside of the context of package protections.
38
- # For example, if a user wanted to use `Sorbet/StrictSigil` to keep *all* of their package strictly typed,
39
- # this would permit that configuration. Likewise, this would permit a user to override portions of rubocop-implemented
40
- # package protections. For example, they could have a hard-to-type portion of their public API (GraphQL maybe?).
41
- # This would permit them to tell rubocop to selectively not enforce package protections in a particular place.
42
- # See more: https://docs.rubocop.org/rubocop/configuration.html#merging-arrays-using-inherit_mode
43
- 'inherit_mode' => { 'merge' => %w[Include Exclude] }
44
- }
15
+ cop_config = { 'Enabled' => enabled }
45
16
 
46
17
  if include_paths.any?
47
18
  cop_config['Include'] = include_paths
@@ -55,6 +26,40 @@ module PackageProtections
55
26
  end
56
27
  end
57
28
 
29
+ include ProtectionInterface
30
+ extend T::Sig
31
+ extend T::Helpers
32
+
33
+ abstract!
34
+
35
+ ###########################################################################
36
+ # Abstract Methods: These are methods that the client needs to implement
37
+ ############################################################################
38
+ sig { abstract.returns(String) }
39
+ def cop_name; end
40
+
41
+ sig do
42
+ abstract.params(file: String).returns(String)
43
+ end
44
+ def message_for_fail_on_any(file); end
45
+
46
+ sig { abstract.returns(T::Array[String]) }
47
+ def included_globs_for_pack; end
48
+
49
+ ###########################################################################
50
+ # Overriddable Methods: These are methods that the client can override,
51
+ # but a default is provided.
52
+ ############################################################################
53
+ sig do
54
+ params(package: ProtectedPackage).returns(T::Hash[T.untyped, T.untyped])
55
+ end
56
+ def custom_cop_config(package)
57
+ {}
58
+ end
59
+
60
+ sig { override.params(behavior: ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
61
+ def unmet_preconditions_for_behavior(behavior, package); end
62
+
58
63
  sig do
59
64
  override.params(
60
65
  new_violations: T::Array[PerFileViolation]
@@ -82,6 +87,66 @@ module PackageProtections
82
87
  end
83
88
  end
84
89
 
90
+ sig do
91
+ override.params(
92
+ protected_packages: T::Array[ProtectedPackage]
93
+ ).returns(T::Array[Offense])
94
+ end
95
+ def get_offenses_for_existing_violations(protected_packages)
96
+ exclude_list = exclude_for_rule(cop_name)
97
+ offenses = []
98
+
99
+ protected_packages.each do |package|
100
+ violation_behavior = package.violation_behavior_for(identifier)
101
+
102
+ case violation_behavior
103
+ when ViolationBehavior::FailNever, ViolationBehavior::FailOnNew
104
+ next
105
+ when ViolationBehavior::FailOnAny
106
+ # Continue
107
+ else
108
+ T.absurd(violation_behavior)
109
+ end
110
+
111
+ package.original_package.directory.glob('**/**/*.*').each do |relative_path_to_file|
112
+ next unless exclude_list.include?(relative_path_to_file.to_s)
113
+
114
+ file = relative_path_to_file.to_s
115
+ offenses << Offense.new(
116
+ file: file,
117
+ message: message_for_fail_on_any(file),
118
+ violation_type: identifier,
119
+ package: package.original_package
120
+ )
121
+ end
122
+ end
123
+
124
+ offenses
125
+ end
126
+
127
+ sig do
128
+ params(packages: T::Array[ProtectedPackage])
129
+ .returns(T::Array[CopConfig])
130
+ end
131
+ def cop_configs(packages)
132
+ include_paths = T.let([], T::Array[String])
133
+ packages.each do |p|
134
+ next unless p.violation_behavior_for(identifier).enabled?
135
+
136
+ included_globs_for_pack.each do |glob|
137
+ include_paths << p.original_package.directory.join(glob).to_s
138
+ end
139
+ end
140
+
141
+ [
142
+ CopConfig.new(
143
+ name: cop_name,
144
+ enabled: include_paths.any?,
145
+ include_paths: include_paths
146
+ )
147
+ ]
148
+ end
149
+
85
150
  private
86
151
 
87
152
  sig { params(rule: String).returns(T::Set[String]) }
@@ -19,7 +19,7 @@ module PackageProtections
19
19
  rescue KeyError
20
20
  # Let's not encourage "unknown." That's mostly considered an internal value if nothing is specified.
21
21
  acceptable_values = ViolationBehavior.values.map(&:serialize) - ['unknown']
22
- raise IncorrectPublicApiUsageError.new("The metadata value #{value} is not a valid behavior. Double check your spelling! Acceptable values are #{acceptable_values}. See https://github.com/bigrails/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
22
+ raise IncorrectPublicApiUsageError.new("The metadata value #{value} is not a valid behavior. Double check your spelling! Acceptable values are #{acceptable_values}. See https://github.com/rubyatscale/package_protections#readme for more info") # rubocop:disable Style/RaiseArgs
23
23
  end
24
24
 
25
25
  sig { returns(T::Boolean) }
@@ -10,7 +10,7 @@ require 'rubocop-sorbet'
10
10
 
11
11
  #
12
12
  # Welcome to PackageProtections!
13
- # See https://github.com/bigrails/package_protections#readme for more info
13
+ # See https://github.com/rubyatscale/package_protections#readme for more info
14
14
  #
15
15
  # This file is a reference for the available API to `package_protections`, but all implementation details are private
16
16
  # (which is why we delegate to `Private` for the actual implementation).
@@ -37,6 +37,7 @@ module PackageProtections
37
37
 
38
38
  # Implementation of rubocop-based protections
39
39
  require 'rubocop/cop/package_protections/namespaced_under_package_name'
40
+ require 'rubocop/cop/package_protections/typed_public_api'
40
41
 
41
42
  class << self
42
43
  extend T::Sig
@@ -1,4 +1,4 @@
1
- # typed: ignore
1
+ # typed: true
2
2
 
3
3
  # For String#camelize
4
4
  require 'active_support/core_ext/string/inflections'
@@ -7,7 +7,10 @@ module RuboCop
7
7
  module Cop
8
8
  module PackageProtections
9
9
  class NamespacedUnderPackageName < Base
10
+ extend T::Sig
11
+
10
12
  include RangeHelp
13
+ include ::PackageProtections::RubocopProtectionInterface
11
14
 
12
15
  def on_new_investigation
13
16
  absolute_filepath = Pathname.new(processed_source.file_path)
@@ -39,7 +42,7 @@ module RuboCop
39
42
  #
40
43
  # Therefore, for our implementation, we substitute out the non-namespace producing portions of the filename to count the number of namespaces.
41
44
  # Note this will *not work* properly in applications that have different assumptions about autoloading.
42
- package_last_name = package_name.split('/').last
45
+ package_last_name = T.must(package_name.split('/').last)
43
46
  path_without_package_base = relative_filename.gsub(%r{#{package_name}/app/}, '')
44
47
  if path_without_package_base.include?('concerns')
45
48
  autoload_folder_name = path_without_package_base.split('/').first(2).join('/')
@@ -85,6 +88,77 @@ module RuboCop
85
88
  end
86
89
  end
87
90
 
91
+ IDENTIFIER = 'prevent_this_package_from_creating_other_namespaces'.freeze
92
+
93
+ sig { override.returns(String) }
94
+ def identifier
95
+ IDENTIFIER
96
+ end
97
+
98
+ sig { override.params(behavior: ::PackageProtections::ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
99
+ def unmet_preconditions_for_behavior(behavior, package)
100
+ if !behavior.enabled? && !package.metadata['global_namespaces'].nil?
101
+ "Invalid configuration for package `#{package.name}`. `#{identifier}` must be turned on to use `global_namespaces` configuration."
102
+ else
103
+ # We don't need to validate if the behavior is currentely fail_never
104
+ return if behavior.fail_never?
105
+
106
+ # The reason for this is precondition is the `MultipleNamespacesProtection` assumes this to work properly.
107
+ # To remove this precondition, we need to modify `MultipleNamespacesProtection` to be more generalized!
108
+ if ::PackageProtections::EXPECTED_PACK_DIRECTORIES.include?(Pathname.new(package.name).dirname.to_s) || package.name == ParsePackwerk::ROOT_PACKAGE_NAME
109
+ nil
110
+ else
111
+ "Package #{package.name} must be located in one of #{::PackageProtections::EXPECTED_PACK_DIRECTORIES.join(', ')} (or be the root) to use this protection"
112
+ end
113
+ end
114
+ end
115
+
116
+ sig { override.returns(T::Array[String]) }
117
+ def included_globs_for_pack
118
+ [
119
+ 'app/**/*',
120
+ 'lib/**/*'
121
+ ]
122
+ end
123
+
124
+ sig do
125
+ params(package: ::PackageProtections::ProtectedPackage).returns(T::Hash[T.untyped, T.untyped])
126
+ end
127
+ def custom_cop_config(package)
128
+ {
129
+ 'GlobalNamespaces' => package.metadata['global_namespaces']
130
+ }
131
+ end
132
+
133
+ sig do
134
+ override.params(file: String).returns(String)
135
+ end
136
+ def message_for_fail_on_any(file)
137
+ "`#{file}` should be namespaced under the package namespace"
138
+ end
139
+
140
+ sig { override.returns(String) }
141
+ def cop_name
142
+ 'PackageProtections/NamespacedUnderPackageName'
143
+ end
144
+
145
+ sig { override.returns(String) }
146
+ def humanized_protection_name
147
+ 'Multiple Namespaces Violations'
148
+ end
149
+
150
+ sig { override.returns(String) }
151
+ def humanized_protection_description
152
+ <<~MESSAGE
153
+ These files cannot have ANY modules/classes that are not submodules of the package's allowed namespaces.
154
+ This is failing because these files are in `.rubocop_todo.yml` under `#{cop_name}`.
155
+ If you want to be able to ignore these files, you'll need to open the file's package's `package.yml` file and
156
+ change `#{IDENTIFIER}` to `#{::PackageProtections::ViolationBehavior::FailOnNew.serialize}`
157
+
158
+ See https://go/packwerk_cheatsheet_namespaces for more info.
159
+ MESSAGE
160
+ end
161
+
88
162
  private
89
163
 
90
164
  def get_allowed_namespaces(package_name)
@@ -0,0 +1,77 @@
1
+ # typed: strict
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module PackageProtections
6
+ #
7
+ # This inherits from `Sorbet::StrictSigil` and doesn't change any behavior of it.
8
+ # The only reason we do this is so that configuration for this cop can live under a different cop namespace.
9
+ # This prevents this cop's configuration from clashing with other configurations for the same cop.
10
+ # A concrete example of this would be if a user is using this package protection to make sure public APIs are typed,
11
+ # and separately the application as a whole requiring strict typing in certain parts of the application.
12
+ #
13
+ # To prevent problems associated with needing to manage identical configurations for the same cop, we simply call it
14
+ # something else in the context of this protection.
15
+ #
16
+ # We can apply this same pattern if we want to use other cops in the context of package protections and prevent clashing.
17
+ #
18
+ class TypedPublicApi < Sorbet::StrictSigil
19
+ extend T::Sig
20
+
21
+ include ::PackageProtections::ProtectionInterface
22
+ include ::PackageProtections::RubocopProtectionInterface
23
+
24
+ IDENTIFIER = T.let('prevent_this_package_from_exposing_an_untyped_api'.freeze, String)
25
+
26
+ sig { override.returns(String) }
27
+ def identifier
28
+ IDENTIFIER
29
+ end
30
+
31
+ sig { override.params(behavior: ::PackageProtections::ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
32
+ def unmet_preconditions_for_behavior(behavior, package)
33
+ # We might decide that we should check that `package.enforces_privacy?` is true here too, since that signifies the app has decided they want
34
+ # a public api in `app/public`. For now, we say there are no preconditions, because the user can still make `app/public` even if they are not yet
35
+ # ready to enforce privacy, and they might want to enforce a typed API.
36
+ nil
37
+ end
38
+
39
+ sig { override.returns(String) }
40
+ def cop_name
41
+ 'PackageProtections/TypedPublicApi'
42
+ end
43
+
44
+ sig { override.returns(T::Array[String]) }
45
+ def included_globs_for_pack
46
+ [
47
+ 'app/public/**/*'
48
+ ]
49
+ end
50
+
51
+ sig do
52
+ override.params(file: String).returns(String)
53
+ end
54
+ def message_for_fail_on_any(file)
55
+ "#{file} should be `typed: strict`"
56
+ end
57
+
58
+ sig { override.returns(String) }
59
+ def humanized_protection_name
60
+ 'Typed API Violations'
61
+ end
62
+
63
+ sig { override.returns(String) }
64
+ def humanized_protection_description
65
+ <<~MESSAGE
66
+ These files cannot have ANY Ruby files in the public API that are not typed strict or higher.
67
+ This is failing because these files are in `.rubocop_todo.yml` under `#{cop_name}`.
68
+ If you want to be able to ignore these files, you'll need to open the file's package's `package.yml` file and
69
+ change `#{IDENTIFIER}` to `#{::PackageProtections::ViolationBehavior::FailOnNew.serialize}`
70
+
71
+ See https://go/packwerk_cheatsheet_typed_api for more info.
72
+ MESSAGE
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: package_protections
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.66.0
4
+ version: 1.1.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-06-10 00:00:00.000000000 Z
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -181,23 +181,22 @@ files:
181
181
  - lib/package_protections/private/configuration.rb
182
182
  - lib/package_protections/private/incoming_privacy_protection.rb
183
183
  - lib/package_protections/private/metadata_modifiers.rb
184
- - lib/package_protections/private/multiple_namespaces_protection.rb
185
184
  - lib/package_protections/private/outgoing_dependency_protection.rb
186
185
  - lib/package_protections/private/output.rb
187
- - lib/package_protections/private/typed_api_protection.rb
188
186
  - lib/package_protections/private/visibility_protection.rb
189
187
  - lib/package_protections/protected_package.rb
190
188
  - lib/package_protections/protection_interface.rb
191
189
  - lib/package_protections/rubocop_protection_interface.rb
192
190
  - lib/package_protections/violation_behavior.rb
193
191
  - lib/rubocop/cop/package_protections/namespaced_under_package_name.rb
194
- homepage: https://github.com/bigrails/package_protections
192
+ - lib/rubocop/cop/package_protections/typed_public_api.rb
193
+ homepage: https://github.com/rubyatscale/package_protections
195
194
  licenses:
196
195
  - MIT
197
196
  metadata:
198
- homepage_uri: https://github.com/bigrails/package_protections
199
- source_code_uri: https://github.com/bigrails/parse_packwerk
200
- changelog_uri: https://github.com/bigrails/parse_packwerk/releases
197
+ homepage_uri: https://github.com/rubyatscale/package_protections
198
+ source_code_uri: https://github.com/rubyatscale/parse_packwerk
199
+ changelog_uri: https://github.com/rubyatscale/parse_packwerk/releases
201
200
  allowed_push_host: https://rubygems.org
202
201
  post_install_message:
203
202
  rdoc_options: []
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # typed: strict
4
-
5
- module PackageProtections
6
- module Private
7
- class MultipleNamespacesProtection
8
- extend T::Sig
9
-
10
- include ProtectionInterface
11
- include RubocopProtectionInterface
12
-
13
- IDENTIFIER = 'prevent_this_package_from_creating_other_namespaces'
14
- COP_NAME = 'PackageProtections/NamespacedUnderPackageName'
15
-
16
- sig { override.returns(String) }
17
- def identifier
18
- IDENTIFIER
19
- end
20
-
21
- sig { override.params(behavior: ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
22
- def unmet_preconditions_for_behavior(behavior, package)
23
- if !behavior.enabled? && !package.metadata['global_namespaces'].nil?
24
- "Invalid configuration for package `#{package.name}`. `#{identifier}` must be turned on to use `global_namespaces` configuration."
25
- else
26
- # We don't need to validate if the behavior is currentely fail_never
27
- return if behavior.fail_never?
28
-
29
- # The reason for this is precondition is the `MultipleNamespacesProtection` assumes this to work properly.
30
- # To remove this precondition, we need to modify `MultipleNamespacesProtection` to be more generalized!
31
- if EXPECTED_PACK_DIRECTORIES.include?(Pathname.new(package.name).dirname.to_s) || package.name == ParsePackwerk::ROOT_PACKAGE_NAME
32
- nil
33
- else
34
- "Package #{package.name} must be located in one of #{EXPECTED_PACK_DIRECTORIES.join(', ')} (or be the root) to use this protection"
35
- end
36
- end
37
- end
38
-
39
- sig do
40
- override
41
- .params(packages: T::Array[ProtectedPackage])
42
- .returns(T::Array[CopConfig])
43
- end
44
- def cop_configs(packages)
45
- include_paths = T.let([], T::Array[String])
46
- packages.each do |p|
47
- next if p.name == ParsePackwerk::ROOT_PACKAGE_NAME
48
-
49
- if p.violation_behavior_for(identifier).enabled?
50
- include_paths << p.original_package.directory.join('app', '**', '*').to_s
51
- include_paths << p.original_package.directory.join('lib', '**', '*').to_s
52
- end
53
- end
54
-
55
- [
56
- CopConfig.new(
57
- name: COP_NAME,
58
- enabled: include_paths.any?,
59
- include_paths: include_paths
60
- )
61
- ]
62
- end
63
-
64
- sig do
65
- params(package: ProtectedPackage).returns(T::Hash[T.untyped, T.untyped])
66
- end
67
- def custom_cop_config(package)
68
- {
69
- 'GlobalNamespaces' => package.metadata['global_namespaces']
70
- }
71
- end
72
-
73
- sig do
74
- override.params(
75
- protected_packages: T::Array[ProtectedPackage]
76
- ).returns(T::Array[Offense])
77
- end
78
- def get_offenses_for_existing_violations(protected_packages)
79
- exclude_list = exclude_for_rule(COP_NAME)
80
- offenses = []
81
-
82
- protected_packages.each do |package|
83
- violation_behavior = package.violation_behavior_for(identifier)
84
-
85
- case violation_behavior
86
- when ViolationBehavior::FailNever, ViolationBehavior::FailOnNew
87
- next
88
- when ViolationBehavior::FailOnAny
89
- # Continue
90
- else
91
- T.absurd(violation_behavior)
92
- end
93
-
94
- package.original_package.directory.glob('**/**/*.*').each do |relative_path_to_file|
95
- next unless exclude_list.include?(relative_path_to_file.to_s)
96
-
97
- file = relative_path_to_file.to_s
98
- offenses << Offense.new(
99
- file: file,
100
- message: "`#{file}` should be namespaced under the package namespace",
101
- violation_type: identifier,
102
- package: package.original_package
103
- )
104
- end
105
- end
106
-
107
- offenses
108
- end
109
-
110
- sig { override.returns(String) }
111
- def humanized_protection_name
112
- 'Multiple Namespaces Violations'
113
- end
114
-
115
- sig { override.returns(String) }
116
- def humanized_protection_description
117
- <<~MESSAGE
118
- These files cannot have ANY modules/classes that are not submodules of the package's allowed namespaces.
119
- This is failing because these files are in `.rubocop_todo.yml` under `#{COP_NAME}`.
120
- If you want to be able to ignore these files, you'll need to open the file's package's `package.yml` file and
121
- change `#{IDENTIFIER}` to `#{ViolationBehavior::FailOnNew.serialize}`
122
-
123
- See https://go/packwerk_cheatsheet_namespaces for more info.
124
- MESSAGE
125
- end
126
- end
127
- end
128
- end
@@ -1,106 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- module PackageProtections
5
- module Private
6
- class TypedApiProtection
7
- extend T::Sig
8
-
9
- include ProtectionInterface
10
- include RubocopProtectionInterface
11
-
12
- IDENTIFIER = 'prevent_this_package_from_exposing_an_untyped_api'
13
- COP_NAME = 'Sorbet/StrictSigil'
14
-
15
- sig { override.returns(String) }
16
- def identifier
17
- IDENTIFIER
18
- end
19
-
20
- sig { override.params(behavior: ViolationBehavior, package: ParsePackwerk::Package).returns(T.nilable(String)) }
21
- def unmet_preconditions_for_behavior(behavior, package)
22
- # We might decide that we should check that `package.enforces_privacy?` is true here too, since that signifies the app has decided they want
23
- # a public api in `app/public`. For now, we say there are no preconditions, because the user can still make `app/public` even if they are not yet
24
- # ready to enforce privacy, and they might want to enforce a typed API.
25
- nil
26
- end
27
-
28
- sig do
29
- override
30
- .params(packages: T::Array[ProtectedPackage])
31
- .returns(T::Array[CopConfig])
32
- end
33
- def cop_configs(packages)
34
- include_paths = T.let([], T::Array[String])
35
- packages.each do |p|
36
- if p.violation_behavior_for(identifier).enabled?
37
- directory = p.original_package.directory
38
- include_paths << directory.join('app', 'public', '**', '*').to_s
39
- end
40
- end
41
-
42
- [
43
- CopConfig.new(
44
- name: COP_NAME,
45
- enabled: include_paths.any?,
46
- include_paths: include_paths
47
- )
48
- ]
49
- end
50
-
51
- sig do
52
- override.params(
53
- protected_packages: T::Array[ProtectedPackage]
54
- ).returns(T::Array[Offense])
55
- end
56
- def get_offenses_for_existing_violations(protected_packages)
57
- exclude_list = exclude_for_rule(COP_NAME)
58
-
59
- offenses = T.let([], T::Array[Offense])
60
- protected_packages.flat_map do |protected_package|
61
- violation_behavior = protected_package.violation_behavior_for(identifier)
62
-
63
- case violation_behavior
64
- when ViolationBehavior::FailNever, ViolationBehavior::FailOnNew
65
- next
66
- when ViolationBehavior::FailOnAny
67
- # Continue
68
- else
69
- T.absurd(violation_behavior)
70
- end
71
-
72
- protected_package.original_package.directory.glob('app/public/**/**/*.*').each do |relative_path_to_file|
73
- next unless exclude_list.include?(relative_path_to_file.to_s)
74
-
75
- file = relative_path_to_file.to_s
76
- offenses << Offense.new(
77
- file: file,
78
- message: "#{file} should be `typed: strict`",
79
- violation_type: identifier,
80
- package: protected_package.original_package
81
- )
82
- end
83
- end
84
-
85
- offenses
86
- end
87
-
88
- sig { override.returns(String) }
89
- def humanized_protection_name
90
- 'Typed API Violations'
91
- end
92
-
93
- sig { override.returns(String) }
94
- def humanized_protection_description
95
- <<~MESSAGE
96
- These files cannot have ANY Ruby files in the public API that are not typed strict or higher.
97
- This is failing because these files are in `.rubocop_todo.yml` under `#{COP_NAME}`.
98
- If you want to be able to ignore these files, you'll need to open the file's package's `package.yml` file and
99
- change `#{IDENTIFIER}` to `#{ViolationBehavior::FailOnNew.serialize}`
100
-
101
- See https://go/packwerk_cheatsheet_typed_api for more info.
102
- MESSAGE
103
- end
104
- end
105
- end
106
- end