Skip to content

Commit

Permalink
Merge pull request #5001 from dependabot/translate-types-to-libraries
Browse files Browse the repository at this point in the history
Map Package Name to Types Package Name
  • Loading branch information
landongrindheim committed Apr 18, 2022
2 parents f240549 + 997cade commit 8851caa
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 0 deletions.
63 changes: 63 additions & 0 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

module Dependabot
module NpmAndYarn
class PackageName
DEFINITELY_TYPED_SCOPE = /types/i.freeze
PACKAGE_NAME_REGEX = %r{
\A # beginning of string
(?=.{1,214}\z) # enforce length (1 - 214)
(@(?<scope>[a-z0-9\-~][a-z0-9\-\._~]*)\/)? # capture 'scope' if present
(?<name>[a-z0-9\-~][a-z0-9\-._~]*) # capture package name
\z # end of string
}xi.freeze # multi-line/case-insensitive

class InvalidPackageName < StandardError; end

def initialize(string)
match = PACKAGE_NAME_REGEX.match(string.to_s)
raise InvalidPackageName unless match

@scope = match[:scope]
@name = match[:name]
end

def to_s
if scoped?
"@#{@scope}/#{@name}"
else
@name.to_s
end
end

def <=>(other)
to_s.casecmp(other.to_s)
end

def eql?(other)
to_s.eql?(other.to_s)
end

def types_package_name
return self if types_package?

@types_package_name ||=
if scoped?
self.class.new("@types/#{@scope}__#{@name}")
else
self.class.new("@types/#{@name}")
end
end

private

def scoped?
!@scope.nil?
end

def types_package?
DEFINITELY_TYPED_SCOPE.match?(@scope)
end
end
end
end
121 changes: 121 additions & 0 deletions npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# frozen_string_literal: true

require "spec_helper"
require "dependabot/npm_and_yarn/package_name"

RSpec.describe Dependabot::NpmAndYarn::PackageName do
describe "initialization" do
it "raises a meaningful error if the input is not a valid package name" do
expect { described_class.new("馃し") }.to raise_error(described_class::InvalidPackageName)
expect { described_class.new([]) }.to raise_error(described_class::InvalidPackageName)
expect { described_class.new({}) }.to raise_error(described_class::InvalidPackageName)
expect { described_class.new(nil) }.to raise_error(described_class::InvalidPackageName)
expect { described_class.new("") }.to raise_error(described_class::InvalidPackageName)
expect { described_class.new(" prefixed-with-a-space") }.to raise_error(described_class::InvalidPackageName)
expect { described_class.new(".prefixed-with-a-dot") }.to raise_error(described_class::InvalidPackageName)
expect { described_class.new("!invalid") }.to raise_error(described_class::InvalidPackageName)
end
end

describe "#to_s" do
it "returns the name when no scope is present" do
jquery = "jquery"

package_name = described_class.new(jquery).to_s

expect(package_name).to eq(jquery)
end

it "returns the name with scope when a scope is present" do
babel_core = "@babel/core"

package_name_with_scope = described_class.new(babel_core).to_s

expect(package_name_with_scope).to eq(babel_core)
end
end

describe "#types_package_name" do
it "returns the corresponding types package name" do
lodash = "lodash"
lodash_types = "@types/lodash"

types_package_name = described_class.new(lodash).types_package_name

expect(types_package_name.to_s).to eq(lodash_types)
end

it "returns self if it is already a types package" do
stereo_types = "@types/stereo"

types_package_name = described_class.new(stereo_types).types_package_name

expect(types_package_name.to_s).to eq(stereo_types)
end

context "when given a scoped dependency name" do
it "returns the corresponding scoped types package name" do
babel_core = "@babel/core"
babel_core_types = "@types/babel__core"

types_package_name = described_class.new(babel_core).types_package_name

expect(types_package_name.to_s).to eq(babel_core_types)
end
end
end

describe "#eql?" do
it "compares the string representation of the package name" do
package = described_class.new("package")
package_again = described_class.new("package")

equality_check = package.eql?(package_again)

expect(equality_check).to be true
end

it "returns true for equivalent package names" do
react = described_class.new("react")
react_again = described_class.new("react")

equality_check = react.eql?(react_again)

expect(equality_check).to be true
end

it "returns false for non-equivalent package names" do
react = described_class.new("react")
vue = described_class.new("vue")

equality_check = react.eql?(vue)

expect(equality_check).to be false
end
end

describe "#<=>" do
it "provides affordances for sorting/comparison" do
first = described_class.new("first")
second = described_class.new("second")
third = described_class.new("third")

expect([third, second, first].sort).to eq([first, second, third])
end

it "ignores case" do
package_name_string = "jquery"
all_caps = described_class.new(package_name_string.upcase)
all_lower = described_class.new(package_name_string.downcase)

expect(all_lower <=> all_caps).to be_zero
end

it "allows for comparison with types packages" do
library = described_class.new("my-library")

expect([library, library.types_package_name].sort).
to eq([library.types_package_name, library])
end
end
end

0 comments on commit 8851caa

Please sign in to comment.