Skip to content

Commit

Permalink
Refactor npm_and_yarn to use separate classes for npm, yarn, and pnpm…
Browse files Browse the repository at this point in the history
…, aligning with centralized package manager abstraction used across ecosystems.
  • Loading branch information
kbukum1 committed Oct 31, 2024
1 parent e34337c commit b55394d
Show file tree
Hide file tree
Showing 9 changed files with 716 additions and 26 deletions.
29 changes: 20 additions & 9 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/file_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,25 +182,36 @@ def inferred_npmrc # rubocop:disable Metrics/PerceivedComplexity

sig { returns(T.nilable(T.any(Integer, String))) }
def npm_version
@npm_version ||= T.let(package_manager.setup("npm"), T.nilable(T.any(Integer, String)))
@npm_version ||= T.let(package_manager_helper.setup("npm"), T.nilable(T.any(Integer, String)))
end

sig { returns(T.nilable(T.any(Integer, String))) }
def yarn_version
@yarn_version ||= T.let(package_manager.setup("yarn"), T.nilable(T.any(Integer, String)))
@yarn_version ||= T.let(package_manager_helper.setup("yarn"), T.nilable(T.any(Integer, String)))
end

sig { returns(T.nilable(T.any(Integer, String))) }
def pnpm_version
@pnpm_version ||= T.let(package_manager.setup("pnpm"), T.nilable(T.any(Integer, String)))
@pnpm_version ||= T.let(package_manager_helper.setup("pnpm"), T.nilable(T.any(Integer, String)))
end

sig { returns(PackageManager) }
def package_manager
@package_manager ||= T.let(PackageManager.new(
parsed_package_json,
lockfiles: { npm: package_lock || shrinkwrap, yarn: yarn_lock, pnpm: pnpm_lock }
), T.nilable(PackageManager))
sig { returns(PackageManagerHelper) }
def package_manager_helper
@package_manager_helper ||= T.let(
PackageManagerHelper.new(
parsed_package_json,
lockfiles: lockfiles
), T.nilable(PackageManagerHelper)
)
end

sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
def lockfiles
{
npm: package_lock || shrinkwrap,
yarn: yarn_lock,
pnpm: pnpm_lock
}
end

sig { returns(DependencyFile) }
Expand Down
76 changes: 75 additions & 1 deletion npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

module Dependabot
module NpmAndYarn
class FileParser < Dependabot::FileParsers::Base
class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/ClassLength
extend T::Sig

require "dependabot/file_parsers/base/dependency_set"
Expand Down Expand Up @@ -78,8 +78,82 @@ def parse
end
end

sig { returns(Ecosystem) }
def ecosystem
@ecosystem ||= T.let(
Ecosystem.new(
name: ECOSYSTEM,
package_manager: package_manager_helper.package_manager
),
T.nilable(Ecosystem)
)
end

private

sig { returns(PackageManagerHelper) }
def package_manager_helper
@package_manager_helper ||= T.let(
PackageManagerHelper.new(
parsed_package_json,
lockfiles: lockfiles
), T.nilable(PackageManagerHelper)
)
end

sig { returns(T::Hash[Symbol, T.nilable(Dependabot::DependencyFile)]) }
def lockfiles
{
npm: package_lock || shrinkwrap,
yarn: yarn_lock,
pnpm: pnpm_lock
}
end

sig { returns(T.untyped) }
def parsed_package_json
JSON.parse(T.must(package_json.content))
rescue JSON::ParserError
raise Dependabot::DependencyFileNotParseable, package_json.path
end

sig { returns(Dependabot::DependencyFile) }
def package_json
# Declare the instance variable with T.let and the correct type
@package_json ||= T.let(
T.must(dependency_files.find { |f| f.name == "package.json" }),
T.nilable(Dependabot::DependencyFile)
)
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def shrinkwrap
@shrinkwrap ||= T.let(dependency_files.find do |f|
f.name == "npm-shrinkwrap.json"
end, T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def package_lock
@package_lock ||= T.let(dependency_files.find do |f|
f.name == "package-lock.json"
end, T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def yarn_lock
@yarn_lock ||= T.let(dependency_files.find do |f|
f.name == "yarn.lock"
end, T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def pnpm_lock
@pnpm_lock ||= T.let(dependency_files.find do |f|
f.name == "pnpm-lock.yaml"
end, T.nilable(Dependabot::DependencyFile))
end

sig { returns(Dependabot::FileParsers::Base::DependencySet) }
def manifest_dependencies
dependency_set = DependencySet.new
Expand Down
21 changes: 14 additions & 7 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module Helpers
# Determines the npm version depends to the feature flag
# If the feature flag is enabled, we are going to use the minimum version npm 8
# Otherwise, we are going to use old versionining npm 6
sig { params(lockfile: DependencyFile).returns(Integer) }
sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
def self.npm_version_numeric(lockfile)
fallback_version_npm8 = Dependabot::Experiments.enabled?(:npm_fallback_version_above_v6)

Expand All @@ -46,9 +46,12 @@ def self.npm_version_numeric(lockfile)
npm_version_numeric_npm6_or_higher(lockfile)
end

sig { params(lockfile: DependencyFile).returns(Integer) }
sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
def self.npm_version_numeric_npm6_or_higher(lockfile)
lockfile_content = T.must(lockfile.content)
lockfile_content = lockfile&.content

return NPM_V8 if lockfile_content.nil? || lockfile_content.strip.empty?

return NPM_V8 if JSON.parse(lockfile_content)["lockfileVersion"].to_i >= 2

NPM_V6
Expand All @@ -60,9 +63,9 @@ def self.npm_version_numeric_npm6_or_higher(lockfile)
# - NPM 7 uses lockfileVersion 2
# - NPM 8 uses lockfileVersion 2
# - NPM 9 uses lockfileVersion 3
sig { params(lockfile: DependencyFile).returns(Integer) }
sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
def self.npm_version_numeric_npm8_or_higher(lockfile)
lockfile_content = lockfile.content
lockfile_content = lockfile&.content

# Return default NPM version if there's no lockfile or it's empty
return NPM_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty?
Expand All @@ -85,8 +88,12 @@ def self.npm_version_numeric_npm8_or_higher(lockfile)
NPM_DEFAULT_VERSION # Fallback to default npm version if parsing fails
end

sig { params(yarn_lock: DependencyFile).returns(Integer) }
sig { params(yarn_lock: T.nilable(DependencyFile)).returns(Integer) }
def self.yarn_version_numeric(yarn_lock)
lockfile_content = yarn_lock&.content

return YARN_DEFAULT_VERSION if lockfile_content.nil? || lockfile_content.strip.empty?

if yarn_berry?(yarn_lock)
YARN_DEFAULT_VERSION
else
Expand Down Expand Up @@ -117,7 +124,7 @@ def self.fetch_yarnrc_yml_value(key, default_value)

sig { params(package_lock: T.nilable(DependencyFile)).returns(T::Boolean) }
def self.npm8?(package_lock)
return true unless package_lock
return true unless package_lock&.content

npm_version_numeric(package_lock) == NPM_V8
end
Expand Down
Loading

0 comments on commit b55394d

Please sign in to comment.