packwerk-extensions 0.0.9 → 0.1.1

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: a4cd61088407945a3e4f58a0a5a3959759683e9186646a4fca4959b79bcbb9b3
4
- data.tar.gz: 855d5ce12ce1d3dba10f06e67e8c25e519d46073fc7eaaa1df67a9fc2777fa09
3
+ metadata.gz: 9f3568d8998c39960fd6e4233ea768c315a1f08731805eb3db487fb7409793e7
4
+ data.tar.gz: 76aa9b02df3ecced40da162a43339c0672ec7290ba8eed35b053958573606863
5
5
  SHA512:
6
- metadata.gz: 7efea5e86a203158a431c43acb5bf422257e5d9d1ec4ddddc13000b733ca8f000c713dbe72bf30cf7a735c5292a736de2570909cd7cc96182e61cc4af8c92804
7
- data.tar.gz: a265c58f6976133334b818847154d5fe3663144c1e956b607b6ec7d315de7c7d9086968329a438eb0dac9384b380f942b8bac8e36ca41345cbf08cc0f7029245
6
+ metadata.gz: 1eeacb48b733d694284681b6370e8080e88269504bdd6f793eb7e4c68fefc8732cc1f0225272f250c201fc669bf63d0086d36cc2476dc8b79ec9cd741d3f092f
7
+ data.tar.gz: fc41db9e4dc2563cafff6a98fa168a823816e2b68383ce0b8399fbee872d221774fe0189356412c4f7b6962baf6655b77ed5cb0c125ab8a5e916965d980646e3
data/README.md CHANGED
@@ -12,6 +12,23 @@ Currently, it ships the following checkers to help improve the boundaries betwee
12
12
  - A `visibility` checker that allows packages to be private except to an explicit group of other packages.
13
13
  - An experimental `architecture` checker that allows packages to specify their "layer" and requires that each layer only communicate with layers below it.
14
14
 
15
+ ## Installation
16
+
17
+ To register all checkers included in this gem, add the following to your `packwerk.yml`:
18
+
19
+ ```yaml
20
+ require: packwerk-extensions
21
+ ```
22
+
23
+ Alternatively, you can require individual checkers:
24
+
25
+ ```yaml
26
+ require:
27
+ - packwerk/privacy/checker
28
+ - packwerk/visibility/checker
29
+ - packwerk/architecture/checker
30
+ ```
31
+
15
32
  ## Privacy Checker
16
33
  The privacy checker extension was originally extracted from [packwerk](https://github.com/Shopify/packwerk).
17
34
 
@@ -39,6 +56,9 @@ Example:
39
56
  public_path: my/custom/path/
40
57
  ```
41
58
 
59
+ ### Using specific private constants
60
+ Sometimes it is desirable to only enforce privacy on a subset of constants in a package. You can do so by defining a `private_constants` list in your package.yml. Note that `enforce_privacy` must be set to `true` or `'strict'` for this to work.
61
+
42
62
  ### Package Privacy violation
43
63
  A constant that is private to its package has been referenced from outside of the package. Constants are declared private in their package’s `package.yml`.
44
64
 
@@ -2,6 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'packwerk/architecture/layers'
5
+ require 'packwerk/architecture/package'
6
+ require 'packwerk/architecture/validator'
5
7
 
6
8
  module Packwerk
7
9
  module Architecture
@@ -1,6 +1,9 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'packwerk/privacy/package'
5
+ require 'packwerk/privacy/validator'
6
+
4
7
  module Packwerk
5
8
  module Privacy
6
9
  # Checks whether a given reference references a private constant of another package.
@@ -27,7 +30,9 @@ module Packwerk
27
30
  return false if privacy_package.public_path?(reference.constant.location)
28
31
 
29
32
  privacy_option = privacy_package.enforce_privacy
30
- !enforcement_disabled?(privacy_option)
33
+ return false if enforcement_disabled?(privacy_option)
34
+
35
+ explicitly_private_constant?(reference.constant, explicitly_private_constants: privacy_package.private_constants)
31
36
  end
32
37
 
33
38
  sig do
@@ -60,6 +65,20 @@ module Packwerk
60
65
 
61
66
  private
62
67
 
68
+ sig do
69
+ params(
70
+ constant: ConstantContext,
71
+ explicitly_private_constants: T::Array[String]
72
+ ).returns(T::Boolean)
73
+ end
74
+ def explicitly_private_constant?(constant, explicitly_private_constants:)
75
+ return true if explicitly_private_constants.empty?
76
+
77
+ explicitly_private_constants.include?(constant.name) ||
78
+ # nested constants
79
+ explicitly_private_constants.any? { |epc| constant.name.start_with?("#{epc}::") }
80
+ end
81
+
63
82
  sig do
64
83
  params(privacy_option: T.nilable(T.any(T::Boolean, String, T::Array[String])))
65
84
  .returns(T::Boolean)
@@ -8,7 +8,8 @@ module Packwerk
8
8
 
9
9
  const :public_path, String
10
10
  const :user_defined_public_path, T.nilable(String)
11
- const :enforce_privacy, T.nilable(T.any(T::Boolean, String, T::Array[String]))
11
+ const :enforce_privacy, T.nilable(T.any(T::Boolean, String))
12
+ const :private_constants, T::Array[String]
12
13
 
13
14
  sig { params(path: String).returns(T::Boolean) }
14
15
  def public_path?(path)
@@ -23,7 +24,8 @@ module Packwerk
23
24
  Package.new(
24
25
  public_path: public_path_for(package),
25
26
  user_defined_public_path: user_defined_public_path(package),
26
- enforce_privacy: package.config['enforce_privacy']
27
+ enforce_privacy: package.config['enforce_privacy'],
28
+ private_constants: package.config['private_constants'] || []
27
29
  )
28
30
  end
29
31
 
@@ -19,6 +19,8 @@ module Packwerk
19
19
  results << check_enforce_privacy_setting(config_file_path, setting)
20
20
  end
21
21
 
22
+ results += verify_private_constants_setting(package_set, configuration)
23
+
22
24
  public_path_settings = package_manifests_settings_for(configuration, 'public_path')
23
25
  public_path_settings.each do |config_file_path, setting|
24
26
  results << check_public_path(config_file_path, setting)
@@ -29,11 +31,49 @@ module Packwerk
29
31
 
30
32
  sig { override.returns(T::Array[String]) }
31
33
  def permitted_keys
32
- %w[public_path enforce_privacy]
34
+ %w[public_path enforce_privacy private_constants]
33
35
  end
34
36
 
35
37
  private
36
38
 
39
+ sig { params(package_set: PackageSet, configuration: Configuration).returns(T::Array[Result]) }
40
+ def verify_private_constants_setting(package_set, configuration)
41
+ private_constants_setting = package_manifests_settings_for(configuration, 'private_constants')
42
+ results = T.let([], T::Array[Result])
43
+ resolver = ConstantResolver.new(
44
+ root_path: configuration.root_path,
45
+ load_paths: configuration.load_paths
46
+ )
47
+
48
+ private_constants_setting.each do |config_file_path, setting|
49
+ next if setting.nil?
50
+
51
+ unless setting.is_a?(Array)
52
+ results << Result.new(
53
+ ok: false,
54
+ error_value: "Invalid 'private_constants' setting: #{setting.inspect}"
55
+ )
56
+ next
57
+ end
58
+
59
+ constants = setting
60
+
61
+ results += assert_constants_can_be_loaded(constants, config_file_path)
62
+
63
+ constant_locations = constants.map { |c| [c, resolver.resolve(c)&.location] }
64
+
65
+ constant_locations.each do |name, location|
66
+ results << if location
67
+ check_private_constant_location(configuration, package_set, name, location, config_file_path)
68
+ else
69
+ private_constant_unresolvable(name, config_file_path)
70
+ end
71
+ end
72
+ end
73
+
74
+ results
75
+ end
76
+
37
77
  sig do
38
78
  params(config_file_path: String, setting: T.untyped).returns(Result)
39
79
  end
@@ -52,7 +92,7 @@ module Packwerk
52
92
  params(config_file_path: String, setting: T.untyped).returns(Result)
53
93
  end
54
94
  def check_enforce_privacy_setting(config_file_path, setting)
55
- if [TrueClass, FalseClass, Array, NilClass].include?(setting.class) || setting == 'strict'
95
+ if [TrueClass, FalseClass, NilClass].include?(setting.class) || setting == 'strict'
56
96
  Result.new(ok: true)
57
97
  else
58
98
  Result.new(
@@ -61,6 +101,55 @@ module Packwerk
61
101
  )
62
102
  end
63
103
  end
104
+
105
+ sig do
106
+ params(configuration: Configuration, package_set: PackageSet, name: T.untyped, location: T.untyped,
107
+ config_file_path: T.untyped).returns(Result)
108
+ end
109
+ def check_private_constant_location(configuration, package_set, name, location, config_file_path)
110
+ declared_package = package_set.package_from_path(relative_path(configuration, config_file_path))
111
+ constant_package = package_set.package_from_path(location)
112
+
113
+ if constant_package == declared_package
114
+ Result.new(ok: true)
115
+ else
116
+ Result.new(
117
+ ok: false,
118
+ error_value: "'#{name}' is declared as private in the '#{declared_package}' package but appears to be " \
119
+ "defined\nin the '#{constant_package}' package. Packwerk resolved it to #{location}."
120
+ )
121
+ end
122
+ end
123
+
124
+ sig { params(constants: T.untyped, config_file_path: String).returns(T::Array[Result]) }
125
+ def assert_constants_can_be_loaded(constants, config_file_path)
126
+ constants.map do |constant|
127
+ if constant.start_with?('::')
128
+ constant.try(&:constantize) && Result.new(ok: true)
129
+ else
130
+ error_value = "'#{constant}', listed in the 'private_constants' option " \
131
+ "in #{config_file_path}, is invalid.\nPrivate constants need to be " \
132
+ 'prefixed with the top-level namespace operator `::`.'
133
+ Result.new(
134
+ ok: false,
135
+ error_value: error_value
136
+ )
137
+ end
138
+ end
139
+ end
140
+
141
+ sig { params(name: T.untyped, config_file_path: T.untyped).returns(Result) }
142
+ def private_constant_unresolvable(name, config_file_path)
143
+ explicit_filepath = "#{(name.start_with?('::') ? name[2..] : name).underscore}.rb"
144
+
145
+ Result.new(
146
+ ok: false,
147
+ error_value: "'#{name}', listed in #{config_file_path}, could not be resolved.\n" \
148
+ "This is probably because it is an autovivified namespace - a namespace module that doesn't have a\n" \
149
+ "file explicitly defining it. Packwerk currently doesn't support declaring autovivified namespaces as\n" \
150
+ "private. Add a #{explicit_filepath} file to explicitly define the constant."
151
+ )
152
+ end
64
153
  end
65
154
  end
66
155
  end
@@ -1,6 +1,9 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'packwerk/visibility/package'
5
+ require 'packwerk/visibility/validator'
6
+
4
7
  module Packwerk
5
8
  module Visibility
6
9
  # Checks whether a given reference references a constant from a package that does not permit visibility
@@ -5,16 +5,8 @@ require 'sorbet-runtime'
5
5
  require 'packwerk'
6
6
 
7
7
  require 'packwerk/privacy/checker'
8
- require 'packwerk/privacy/package'
9
- require 'packwerk/privacy/validator'
10
-
11
8
  require 'packwerk/visibility/checker'
12
- require 'packwerk/visibility/package'
13
- require 'packwerk/visibility/validator'
14
-
15
9
  require 'packwerk/architecture/checker'
16
- require 'packwerk/architecture/package'
17
- require 'packwerk/architecture/validator'
18
10
 
19
11
  module Packwerk
20
12
  module Extensions
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: packwerk-extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 0.1.1
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-12-30 00:00:00.000000000 Z
11
+ date: 2023-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: packwerk
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 2.2.1
27
27
  - !ruby/object:Gem::Dependency
28
- name: rails
28
+ name: railties
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: 6.0.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: 6.0.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sorbet-runtime
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rubocop
113
127
  requirement: !ruby/object:Gem::Requirement