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

Include non-development gemspec dependencies in DependencyDetector check #1031

Merged
merged 10 commits into from
Oct 4, 2023

Conversation

andyw8
Copy link
Contributor

@andyw8 andyw8 commented Sep 20, 2023

Motivation

Addresses the server side of #1635. We will need another change for the extension: Shopify/vscode-ruby-lsp#814

Note

We should aim to launch this alongside the extension update, to avoid inconsistent behaviour.

Implementation

Using the same approach as we already have in SetupBunder#load_dependencies.

Automated Tests

Updated

Manual Tests

Start with the current (released version of Ruby LSP). We'll use Spoom verifiying.

In Spoom's gemspec, change minitest and minitest-reporters to be add_dependency instead of add_development_dependency. Open a test and confirm the code lens is not showing.

Now, point rubyLsp.branch to this branch. Confirm the code lenses.

@@ -44,7 +44,7 @@ def typechecker?
sig { params(gem_pattern: Regexp).returns(T::Boolean) }
def direct_dependency?(gem_pattern)
Bundler.with_original_env { Bundler.default_gemfile } &&
Bundler.locked_gems.dependencies.keys.grep(gem_pattern).any?
Bundler.locked_gems.specs.map(&:name).grep(gem_pattern).any?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@vinistock what do you think of this approach? The other way might be to keep Bundler.locked_gems.dependencies but separately read from .gemspec, then aggregate them.

@andyw8 andyw8 self-assigned this Sep 24, 2023
@andyw8 andyw8 added the enhancement New feature or request label Sep 24, 2023
@andyw8 andyw8 added this to the 2023-Q3 milestone Sep 24, 2023
@andyw8 andyw8 force-pushed the andyw8/include-gemspec-in-dependency-check branch from 3bcd88b to 6f43f53 Compare October 2, 2023 17:32
@andyw8 andyw8 changed the title WIP Include gemspec in Dependency check Include gemspec in Dependency check Oct 2, 2023
@andyw8 andyw8 force-pushed the andyw8/include-gemspec-in-dependency-check branch from 6f43f53 to a94279b Compare October 2, 2023 23:37
@andyw8 andyw8 changed the title Include gemspec in Dependency check Include non-development gemspec dependencies in DependencyDetector check Oct 2, 2023
@andyw8 andyw8 marked this pull request as ready for review October 2, 2023 23:42
@andyw8 andyw8 requested a review from a team as a code owner October 2, 2023 23:42
@andyw8 andyw8 requested review from Morriar and vinistock October 2, 2023 23:42
Comment on lines 83 to 85
Dir.glob("{,*}.gemspec").flat_map do |path|
Bundler.load_gemspec(path).dependencies.map(&:name)
end
Copy link
Member

@paracycle paracycle Oct 3, 2023

Choose a reason for hiding this comment

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

The gemspec should have already been loaded by the gemfile, so I don't think finding it and loading it is the best way to do this.

I would instead recommend something like:

Bundler.locked_gems.sources.find { Bundler::Source::Gemspec === _1 }&.gemspec&.dependencies

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, that seems cleaner. @vinistock, should we update the load_dependencies method to do the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just for illustration, with

  spec.add_dependency("minitest", "~> 5.0")
  spec.add_dependency("minitest-reporters")

we get:

irb(main):005:0> Bundler.locked_gems.dependencies.keys
=> ["bundler", "debug", "rake", "rubocop-shopify", "rubocop-sorbet", "spoom", "tapioca"]
irb(main):006:0> Bundler.load_gemspec(gemspec_path).dependencies.map(&:name)
=> ["bundler", "minitest", "minitest-reporters", "rake", "erubi", "sorbet-static-and-runtime", "syntax_tree", "thor"]

Copy link
Member

Choose a reason for hiding this comment

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

Let's make sure it is really equivalent and that we get the same results. In case we do, are you talking about load_dependencies in SetupBundler?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah SetupBundler#load_dependencies. But on second thoughts, let's keep this PR focused on the DependencyDetector.

lib/ruby_lsp/requests/support/dependency_detector.rb Outdated Show resolved Hide resolved

sig { returns(T::Array[String]) }
def gemspec_dependencies
Array(Bundler.locked_gems.sources.find { Bundler::Source::Gemspec === _1 }&.gemspec&.dependencies).map(&:name)
Copy link
Member

Choose a reason for hiding this comment

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

Bundler supports more than one gemspec in the same project, so I think this needs to be something like

Bundler.locked_gems.sources.each_with_object([]) do |source, dependencies|
  next unless Bundler::Source::Gemspec === source

  dependencies.concat(source.gemspec&.dependencies.map(&:name))
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Updated in 8af680d

Copy link
Member

@paracycle paracycle Oct 4, 2023

Choose a reason for hiding this comment

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

There is no need for each_with_object here:

Bundler.locked_gems.sources.flat_map do |source|
  next unless Bundler::Source::Gemspec === source

  Array(source.gemspec&.dependencies).map(&:name)
end.compact

Copy link
Member

Choose a reason for hiding this comment

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

Well, even better by doing the filtering first and then doing a flat map:

Bundler.locked_gems.sources.grep(Bundler::Source::Gemspec).flat_map { |source| Array(source.gemspec&.dependencies) }.map(&:name)

Copy link
Contributor Author

@andyw8 andyw8 Oct 4, 2023

Choose a reason for hiding this comment

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

That's almost what I had in 8af680d, but grepping by the class name is nice, I've updated.

Copy link
Member

Choose a reason for hiding this comment

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

I honestly, don't think any of those options are more readable than using each_with_object - in addition to them allocating more arrays. But whatever you all decide is fine.

@andyw8 andyw8 force-pushed the andyw8/include-gemspec-in-dependency-check branch from 083d5da to 216877e Compare October 4, 2023 14:03
@andyw8 andyw8 merged commit eecfdef into main Oct 4, 2023
29 checks passed
@andyw8 andyw8 deleted the andyw8/include-gemspec-in-dependency-check branch October 4, 2023 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants