Skip to content
This repository has been archived by the owner on May 3, 2024. It is now read-only.

Commit

Permalink
Add support for nested packs (#20)
Browse files Browse the repository at this point in the history
* bump parse_packwerk

* bust cache after getting packwerk packages

* Remove unused method

* add failing test

* put existing tests in context

* make tests pass for nested packs

* add new failing tests

* add note to self

* use other implementation

* comment out new tests

* uncomment new tests

* use safe navigation operator

* correct tests

* remove comment

* bump version
  • Loading branch information
Alex Evanczuk authored Aug 13, 2022
1 parent 34139b5 commit ccd28c5
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 226 deletions.
12 changes: 6 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
package_protections (1.3.0)
package_protections (1.4.0)
activesupport
parse_packwerk
rubocop
Expand All @@ -11,7 +11,7 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activesupport (7.0.3)
activesupport (7.0.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
Expand All @@ -20,12 +20,12 @@ GEM
coderay (1.1.3)
concurrent-ruby (1.1.10)
diff-lcs (1.5.0)
i18n (1.10.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
method_source (1.0.0)
minitest (5.16.1)
minitest (5.16.2)
parallel (1.21.0)
parse_packwerk (0.10.1)
parse_packwerk (0.12.0)
sorbet-runtime
parser (3.1.0.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -96,7 +96,7 @@ GEM
thor (>= 1.2.0)
yard-sorbet
thor (1.2.1)
tzinfo (2.0.4)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.1.0)
unparser (0.6.3)
Expand Down
10 changes: 0 additions & 10 deletions lib/package_protections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,6 @@ def self.private_cop_config(identifier)
Private.private_cop_config(identifier)
end

sig do
params(
package_names: T::Array[String],
all_packages: T::Array[ParsePackwerk::Package]
).returns(T::Array[ParsePackwerk::Package])
end
def self.packages_for_names(package_names, all_packages)
Private.packages_for_names(package_names, all_packages)
end

sig { void }
def self.bust_cache!
Private.bust_cache!
Expand Down
21 changes: 0 additions & 21 deletions lib/package_protections/private.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,27 +88,6 @@ def self.set_defaults!(packages, protection_identifiers:, verbose:)
end
end

sig do
params(
package_names: T::Array[String],
all_packages: T::Array[ParsePackwerk::Package]
).returns(T::Array[ParsePackwerk::Package])
end
def self.packages_for_names(package_names, all_packages)
all_packages_indexed_by_name = {}
all_packages.each { |package| all_packages_indexed_by_name[package.name] = package }

package_names.map do |package_name|
clean_pack_name = package_name.gsub(%r{/$}, '')
package = all_packages_indexed_by_name[clean_pack_name]
if package.nil?
raise "Sorry, we couldn't find a package with name #{package_name}. Here are all of the package names we know about: #{all_packages.map(&:name).sort.inspect}"
end

package
end
end

sig { params(root_pathname: Pathname).returns(String) }
def self.rubocop_yml(root_pathname:)
protected_packages = Dir.chdir(root_pathname) { all_protected_packages }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ def on_new_investigation
# This cop only works for files in `app`
return if !relative_filename.include?('app/')

match = relative_filename.match(%r{((#{::PackageProtections::EXPECTED_PACK_DIRECTORIES.join("|")})/.*?)/})
package_name = match && match[1]

package_name = ParsePackwerk.package_from_path(relative_filename)&.name
return if package_name.nil?

return if relative_filepath.extname != '.rb'
Expand Down Expand Up @@ -105,7 +103,11 @@ def unmet_preconditions_for_behavior(behavior, package)

# The reason for this is precondition is the `MultipleNamespacesProtection` assumes this to work properly.
# To remove this precondition, we need to modify `MultipleNamespacesProtection` to be more generalized!
if ::PackageProtections::EXPECTED_PACK_DIRECTORIES.include?(Pathname.new(package.name).dirname.to_s) || package.name == ParsePackwerk::ROOT_PACKAGE_NAME
is_root_package = package.name == ParsePackwerk::ROOT_PACKAGE_NAME
in_allowed_directory = ::PackageProtections::EXPECTED_PACK_DIRECTORIES.any? do |expected_package_directory|
package.directory.to_s.start_with?(expected_package_directory)
end
if in_allowed_directory || is_root_package
nil
else
"Package #{package.name} must be located in one of #{::PackageProtections::EXPECTED_PACK_DIRECTORIES.join(', ')} (or be the root) to use this protection"
Expand Down
2 changes: 1 addition & 1 deletion package_protections.gemspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Gem::Specification.new do |spec|
spec.name = 'package_protections'
spec.version = '1.3.0'
spec.version = '1.4.0'
spec.authors = ['Gusto Engineers']
spec.email = ['[email protected]']
spec.summary = 'Package protections for Rails apps'
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 43 additions & 64 deletions spec/package_protections_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
end

def get_packages
ParsePackwerk.bust_cache!
ParsePackwerk.all
end

Expand Down Expand Up @@ -734,6 +735,48 @@ def get_new_violations
offenses = PackageProtections.get_offenses(packages: get_packages, new_violations: [])
expect(offenses).to be_empty
end

context 'the package is nested' do
let(:apples_package_yml_with_namespace_protection_set_to_fail_on_new) do
write_package_yml('packs/apples/subpack', global_namespaces: apples_global_namespaces, protections: { 'prevent_this_package_from_creating_other_namespaces' => 'fail_on_new' })
end

context 'global_namespaces is unset' do
let(:apples_global_namespaces) { [] }

it 'generates the expected rubocop.yml entries' do
apples_package_yml_with_namespace_protection_set_to_fail_on_new
cop_config = get_resulting_rubocop['PackageProtections/NamespacedUnderPackageName']
expect(cop_config['Exclude']).to eq(nil)
expect(cop_config['Include']).to eq(['packs/apples/subpack/app/**/*', 'packs/apples/subpack/lib/**/*'])
expect(cop_config['Enabled']).to eq(true)
end
end

context 'global_namespaces is set' do
let(:apples_global_namespaces) { %w[AppleTrees Ciders Apples] }

it 'generates the expected rubocop.yml entries' do
apples_package_yml_with_namespace_protection_set_to_fail_on_new
cop_config = get_resulting_rubocop['PackageProtections/NamespacedUnderPackageName']
expect(cop_config['Exclude']).to eq(nil)
expect(cop_config['Include']).to eq(['packs/apples/subpack/app/**/*', 'packs/apples/subpack/lib/**/*'])
expect(cop_config['Enabled']).to eq(true)
end

it 'retrieves the right rubocop metadata' do
apples_package_yml_with_namespace_protection_set_to_fail_on_new
private_cop_config = PackageProtections.private_cop_config('prevent_this_package_from_creating_other_namespaces')
expect(private_cop_config['packs/apples/subpack']).to eq({ 'GlobalNamespaces' => %w[AppleTrees Ciders Apples] })
end
end

it 'is implemented by Rubocop' do
apples_package_yml_with_namespace_protection_set_to_fail_on_new
offenses = PackageProtections.get_offenses(packages: get_packages, new_violations: [])
expect(offenses).to be_empty
end
end
end

context 'set to fail_on_any' do
Expand Down Expand Up @@ -1281,68 +1324,4 @@ def get_new_violations
})
end
end

describe 'packages_for_names' do
context 'can find the package' do
it 'returns the package' do
write_file('package.yml', <<~YML.strip)
enforce_dependencies: true
enforce_privacy: true
YML

write_file('packs/apples/package.yml', <<~YML.strip)
enforce_dependencies: true
enforce_privacy: true
YML

write_file('packs/zebras/package.yml', <<~YML.strip)
enforce_dependencies: true
enforce_privacy: true
YML

packages = PackageProtections.packages_for_names(['packs/apples'], get_packages)
expect(packages.count).to eq 1
expect(packages.first.name).to eq 'packs/apples'
end
end

context 'cannot find the package' do
it 'prints out available packages in sorted order' do
write_file('package.yml', <<~YML.strip)
enforce_dependencies: true
enforce_privacy: true
YML

write_file('packs/apples/package.yml', <<~YML.strip)
enforce_dependencies: true
enforce_privacy: true
YML

write_file('packs/zebras/package.yml', <<~YML.strip)
enforce_dependencies: true
enforce_privacy: true
YML

expected_error_message = <<~MESSAGE
Sorry, we couldn't find a package with name packs/elephants. Here are all of the package names we know about: [".", "packs/apples", "packs/zebras"]
MESSAGE

expect { PackageProtections.packages_for_names(['packs/elephants'], get_packages) }
.to raise_error expected_error_message.chomp
end
end

context 'package is root package, and we refer to it using the name .' do
it 'returns the package' do
write_file('package.yml', <<~YML.strip)
enforce_dependencies: true
enforce_privacy: true
YML

packages = PackageProtections.packages_for_names([ParsePackwerk::ROOT_PACKAGE_NAME], get_packages)
expect(packages.count).to eq 1
expect(packages.first.name).to eq ParsePackwerk::ROOT_PACKAGE_NAME
end
end
end
end
Loading

0 comments on commit ccd28c5

Please sign in to comment.