Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to load in extensions to packwerk #245

Merged
merged 1 commit into from
Oct 31, 2022

Conversation

alexevanczuk
Copy link
Contributor

Commits

  • Have FilesForProcessingTest use application fixture pattern
  • Add failing test
  • Make tests pass

What are you trying to accomplish?

As packwerk enables more types of configuration, we need an easy to use and consistent experience for clients trying to extend packwerk. We have three things that we want to be able to extend:

  • The offenses formatter, which is currently not extensible via the command line (without altering the binstub)
  • Custom parsers
  • Custom checkers

Ideally, the user can use a consistent pattern to define their extensions, such as defining a method that inherits a packwerk Sorbet interface. Regardless of what that interface is (this PR does not decide that), the user will need a way to have that code load when packwerk is executed. This PR creates this ability by requiring files as determined by the user in packwerk.yml.

What approach did you choose and why?

Since packwerk is a static analysis tool, we used rubocop as inspiration for this API:
https://docs.rubocop.org/rubocop/extensions.html#loading-extensions

What should reviewers focus on?

Let me know if there's another approach that makes more sense to you!

Type of Change

  • Bugfix
  • New feature
  • Non-breaking change (a change that doesn't alter functionality - i.e., code refactor, configs, etc.)

Checklist

  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • It is safe to rollback this change.

@alexevanczuk alexevanczuk requested a review from a team as a code owner October 27, 2022 19:26
@@ -275,3 +276,16 @@ Above is an example of a constant violation entry in `deprecated_references.yml`
* `components/merchant/app/public/merchant/generate_order.rb` - path to the file containing the violated constant

Violations exist within the package that makes a violating reference. This means privacy violations of your package can be found listed in `deprecated_references.yml` files in the packages with the reference to a private constant.

# Loading Extensions
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later, when we support offenses formatter, checkers, and parsers to be configured using a consistent API, we can add more information about how to configure these within this section.

@alexevanczuk alexevanczuk force-pushed the ae-add-require-directive branch 2 times, most recently from 8c0691f to 539950d Compare October 28, 2022 20:16
USAGE.md Show resolved Hide resolved
USAGE.md Show resolved Hide resolved
USAGE.md Show resolved Hide resolved
USAGE.md Show resolved Hide resolved
USAGE.md Show resolved Hide resolved
@alexevanczuk
Copy link
Contributor Author

Applied your suggestions @gmcgibbon ! Thank you for using the suggestions feature – made it really easy to respond to code review 🙏

@@ -0,0 +1,2 @@
module MySpecialExtension
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
module MySpecialExtension
module MyLocalExtension

I would also rename the file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed!

refute defined?(MySpecialExtension)
Configuration.from_path
assert defined?(MySpecialExtension)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
end
ensure
Object.send(:remove_const, :MyLocalExtension) if defined?(MyLocalExtension)
end

You need to clean up globals after the test is ran to make it not have side effects for other tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Thank you.

@@ -49,6 +49,12 @@ def initialize(configs = {}, config_path: nil)
@cache_directory = Pathname.new(configs["cache_directory"] || "tmp/cache/packwerk")
@config_path = config_path

if configs.key?("require")
configs["require"].each do |require_directive|
ExtensionLoader.load(require_directive, @root_path)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand why we need a module to do this. Why can't we just use require and assume relative paths are from the current working directory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't necessarily need a module – I just wanted to encapsulate its logic into one place. I made it a private module with private_constant. I also thought that it could potentially expand, so wanted to make room for that.

Let me know if you'd prefer to inline as private methods

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. You've changed my mind, this is worth keeping.

module Packwerk
# This class handles loading extensions to packwerk using the `require` directive
# in the `packwerk.yml` configuration.
class ExtensionLoader
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class ExtensionLoader
module ExtensionLoader

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change!

use_template(:skeleton)

required_things = []
require_method = ->(required_thing) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
require_method = ->(required_thing) do
mock_require_method = ->(required_thing) do

You could technically just check $LOADED_FEATURES, but I think is is cleaner 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed!

@@ -4,3 +4,6 @@ include:
exclude:
- "**/temp.rb"
parallel: false
require:
- ./config/my_local_extension.rb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- ./config/my_local_extension.rb
- ./config/my_local_extension

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, changed

@@ -4,3 +4,6 @@ include:
exclude:
- "**/temp.rb"
parallel: false
require:
- ./config/my_local_extension.rb
- pathname
Copy link
Member

@gmcgibbon gmcgibbon Oct 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- pathname
- my_gem_extension

Personally I think this would make more sense, but it breaks existing tests I think. This probably means we should make a new fixture since I wouldn't expect the skeleton / barebones fixture to reference extensions. Let's do this before merging.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I agree. I'll make a new fixture.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gmcgibbon You approved this already, but I just want to double check that my change is what you had in mind. I created a new fixture extended which let me better colocate test concerns related to extending packwerk.

Let me know if you're still good with it, in which case I'll squash the commits and merge in the PR.

@alexevanczuk alexevanczuk merged commit f906bb7 into main Oct 31, 2022
@alexevanczuk alexevanczuk deleted the ae-add-require-directive branch October 31, 2022 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants