From fa18d4c8a86eaa68a6c25e003580a5d4e7d110da Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Thu, 14 Apr 2022 16:21:38 -0400 Subject: [PATCH 1/7] Translate TypeScript Types <-> Libraries This is work that is being done to support updating TypeScript types packages at the same time that we update the packages that consume them. There are a couple of assumptions baked into this work: * Types packages will share the same name as the libraries consuming them * Libraries will follow the rules put forward by https://github.com/npm/validate-npm-package-name --- .../npm_and_yarn/type_script_library.rb | 64 +++++++++++++++++ .../dependabot/npm_and_yarn/types_package.rb | 68 +++++++++++++++++++ .../npm_and_yarn/type_script_library_spec.rb | 54 +++++++++++++++ .../npm_and_yarn/types_package_spec.rb | 46 +++++++++++++ 4 files changed, 232 insertions(+) create mode 100644 npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb create mode 100644 npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb create mode 100644 npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb create mode 100644 npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb new file mode 100644 index 00000000000..5c11ce26d3b --- /dev/null +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Dependabot + module NpmAndYarn + class TypeScriptLibrary + INVALID_CHARACTERS_REGEX = /[~()'!\*[[:space:]]]/.freeze + MAX_PACKAGE_NAME_LENGTH = 214 + TYPES_ORG = "@types/" + + def initialize(package_name) + @package_name = package_name.to_s + end + + def types_package + return "" if !valid? + + case + when scoped_library? + "@types/#{scoped_name}" + when types_package? + package_name + else + "@types/#{package_name}" + end + end + + private + + attr_reader :package_name + + def valid? + valid_length? && lowercased? && characters_valid? + end + + def valid_length? + package_name.length.positive? && + package_name.length <= MAX_PACKAGE_NAME_LENGTH + end + + def lowercased? + package_name == package_name.downcase + end + + def characters_valid? + !package_name.start_with?("_", ".") && + !package_name.match?(INVALID_CHARACTERS_REGEX) + end + + def scoped_library? + package_name.start_with?("@") && !types_package? + end + + def types_package? + package_name.start_with?(TYPES_ORG) + end + + def scoped_name + scoped_name_without_at = package_name.delete_prefix("@") + scope, package = scoped_name_without_at.split("/") + "#{scope}__#{package}" + end + end + end +end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb new file mode 100644 index 00000000000..10b641799de --- /dev/null +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module Dependabot + module NpmAndYarn + class TypesPackage + INVALID_CHARACTERS_REGEX = /[~()'!\*[[:space:]]]/.freeze + MAX_PACKAGE_NAME_LENGTH = 214 + TYPES_ORG = "@types/" + + def initialize(package_name) + @package_name = package_name.to_s + end + + def library + return "" if !valid? + + case + when scoped_typings_package? + unscoped_name_without_types_org + when typings_package? + name_without_types_org + else + "" + end + end + + private + + attr_reader :package_name + + def valid? + valid_length? && lowercased? && characters_valid? + end + + def valid_length? + package_name.length.positive? && + package_name.length <= MAX_PACKAGE_NAME_LENGTH + end + + def lowercased? + package_name == package_name.downcase + end + + def characters_valid? + !package_name.start_with?("_", ".") && + !name_without_types_org.start_with?("_", ".") && + !package_name.match?(INVALID_CHARACTERS_REGEX) + end + + def scoped_typings_package? + typings_package? && name_without_types_org.include?("__") + end + + def typings_package? + package_name.start_with?("#{TYPES_ORG}") + end + + def unscoped_name_without_types_org + scope, package = name_without_types_org.split("__") + "@#{scope}/#{package}" + end + + def name_without_types_org + package_name.sub(%r{^#{TYPES_ORG}}, "") + end + end + end +end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb new file mode 100644 index 00000000000..6ddc11f2ef9 --- /dev/null +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/npm_and_yarn/type_script_library" + +RSpec.describe Dependabot::NpmAndYarn::TypeScriptLibrary do + describe "#types_package" do + it "returns the corresponding types package name" do + lodash = "lodash" + lodash_types = "@types/lodash" + + types_package = described_class.new(lodash).types_package + + expect(types_package).to eq(lodash_types) + end + + it "returns the input if it is already a types package" do + stereo_types = "@types/stereo" + + types_package = described_class.new(stereo_types).types_package + + expect(types_package).to eq(stereo_types) + end + + it "trusts users to have meaningful package names" do + expect(described_class.new("🤷").types_package).to eq("@types/🤷") + expect(described_class.new([]).types_package).to eq("@types/[]") + expect(described_class.new({}).types_package).to eq("@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 = described_class.new(babel_core).types_package + + expect(types_package).to eq(babel_core_types) + end + end + + context "when the input is unmappable" do + it "returns an empty string" do + expect(described_class.new(nil).types_package).to eq("") + expect(described_class.new("").types_package).to eq("") + expect(described_class.new("NOT_A_DEPENDENCY").types_package).to eq("") + expect(described_class.new("@types/NOT_A_TYPES_DEFINITION").types_package).to eq("") + expect(described_class.new(" prefixed-with-a-space").types_package).to eq("") + expect(described_class.new(".prefixed-with-a-dot").types_package).to eq("") + expect(described_class.new("!invalid").types_package).to eq("") + end + end + end +end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb new file mode 100644 index 00000000000..07dc9376f7e --- /dev/null +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/npm_and_yarn/types_package" + +RSpec.describe Dependabot::NpmAndYarn::TypesPackage do + describe "#library" do + it "returns the corresponding dependency name" do + jquery_types = "@types/jquery" + jquery = "jquery" + + library = described_class.new(jquery_types).library + + expect(library).to eq(jquery) + end + + it "trusts users to have meaningful package names" do + expect(described_class.new("@types/🤷").library).to eq("🤷") + expect(described_class.new("@types/[]").library).to eq("[]") + expect(described_class.new("@types/{}").library).to eq("{}") + end + + + context "when given a scoped type definition dependency name" do + it "returns the corresponding scoped dependency name" do + babel_core_types = "@types/babel__core" + babel_core = "@babel/core" + + library = described_class.new(babel_core_types).library + + expect(library).to eq(babel_core) + end + end + + context "when the input cannot be translated" do + it "returns an empty string" do + expect(described_class.new("").library).to eq("") + expect(described_class.new("@types/").library).to eq("") + expect(described_class.new("@types/NOT_A_TYPES_DEFINITION").library).to eq("") + expect(described_class.new("@types/ prefixed-with-a-space").library).to eq("") + expect(described_class.new("@types/.prefixed-with-a-dot").library).to eq("") + expect(described_class.new("@types/!invalid").library).to eq("") + end + end + end +end From 51bd579bc25e6db06ac54775c2b5816627278112 Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Fri, 15 Apr 2022 09:58:11 -0400 Subject: [PATCH 2/7] Rely on Regexp for package name parsing Co-authored-by: Mattt --- .../npm_and_yarn/type_script_library.rb | 64 +++++++------------ .../npm_and_yarn/type_script_library_spec.rb | 35 +++++----- 2 files changed, 38 insertions(+), 61 deletions(-) diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb index 5c11ce26d3b..f38d1fa9152 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb @@ -3,61 +3,43 @@ module Dependabot module NpmAndYarn class TypeScriptLibrary - INVALID_CHARACTERS_REGEX = /[~()'!\*[[:space:]]]/.freeze - MAX_PACKAGE_NAME_LENGTH = 214 - TYPES_ORG = "@types/" + DEFINITELY_TYPED_SCOPE = /types/i + PACKAGE_NAME_REGEX = %r{ + \A # beginning of string + (?=.{1,214}\z) # enforce length (1 - 214) + (@(?[a-z0-9\-~][a-z0-9\-\._~]*)\/)? # capture 'scope' if present + (?[a-z0-9\-~][a-z0-9\-._~]*) # capture package name + \z # end of string + }xi.freeze # multi-line/case-insensitive - def initialize(package_name) - @package_name = package_name.to_s + 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 types_package - return "" if !valid? + return if types_package? - case - when scoped_library? - "@types/#{scoped_name}" - when types_package? - package_name + if scoped? + "@types/#{@scope}__#{@name}" else - "@types/#{package_name}" + "@types/#{@name}" end end private - attr_reader :package_name - - def valid? - valid_length? && lowercased? && characters_valid? - end - - def valid_length? - package_name.length.positive? && - package_name.length <= MAX_PACKAGE_NAME_LENGTH - end - - def lowercased? - package_name == package_name.downcase - end - - def characters_valid? - !package_name.start_with?("_", ".") && - !package_name.match?(INVALID_CHARACTERS_REGEX) - end - - def scoped_library? - package_name.start_with?("@") && !types_package? + def scoped? + !@scope.nil? end def types_package? - package_name.start_with?(TYPES_ORG) - end - - def scoped_name - scoped_name_without_at = package_name.delete_prefix("@") - scope, package = scoped_name_without_at.split("/") - "#{scope}__#{package}" + DEFINITELY_TYPED_SCOPE.match?(@scope) end end end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb index 6ddc11f2ef9..3b76ad9095d 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb @@ -4,6 +4,19 @@ require "dependabot/npm_and_yarn/type_script_library" RSpec.describe Dependabot::NpmAndYarn::TypeScriptLibrary 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 "#types_package" do it "returns the corresponding types package name" do lodash = "lodash" @@ -14,18 +27,12 @@ expect(types_package).to eq(lodash_types) end - it "returns the input if it is already a types package" do + it "returns nil if it is already a types package" do stereo_types = "@types/stereo" types_package = described_class.new(stereo_types).types_package - expect(types_package).to eq(stereo_types) - end - - it "trusts users to have meaningful package names" do - expect(described_class.new("🤷").types_package).to eq("@types/🤷") - expect(described_class.new([]).types_package).to eq("@types/[]") - expect(described_class.new({}).types_package).to eq("@types/{}") + expect(types_package).to be_nil end context "when given a scoped dependency name" do @@ -38,17 +45,5 @@ expect(types_package).to eq(babel_core_types) end end - - context "when the input is unmappable" do - it "returns an empty string" do - expect(described_class.new(nil).types_package).to eq("") - expect(described_class.new("").types_package).to eq("") - expect(described_class.new("NOT_A_DEPENDENCY").types_package).to eq("") - expect(described_class.new("@types/NOT_A_TYPES_DEFINITION").types_package).to eq("") - expect(described_class.new(" prefixed-with-a-space").types_package).to eq("") - expect(described_class.new(".prefixed-with-a-dot").types_package).to eq("") - expect(described_class.new("!invalid").types_package).to eq("") - end - end end end From 3aff802805668cdf7f48249d2bc0f613fb2b57d8 Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Fri, 15 Apr 2022 10:04:19 -0400 Subject: [PATCH 3/7] Remove unneeded package mapper We've decided that we're going to support uni-directional package translation for now. Co-authored-by: Mattt --- .../dependabot/npm_and_yarn/types_package.rb | 68 ------------------- .../npm_and_yarn/types_package_spec.rb | 46 ------------- 2 files changed, 114 deletions(-) delete mode 100644 npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb delete mode 100644 npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb deleted file mode 100644 index 10b641799de..00000000000 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/types_package.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -module Dependabot - module NpmAndYarn - class TypesPackage - INVALID_CHARACTERS_REGEX = /[~()'!\*[[:space:]]]/.freeze - MAX_PACKAGE_NAME_LENGTH = 214 - TYPES_ORG = "@types/" - - def initialize(package_name) - @package_name = package_name.to_s - end - - def library - return "" if !valid? - - case - when scoped_typings_package? - unscoped_name_without_types_org - when typings_package? - name_without_types_org - else - "" - end - end - - private - - attr_reader :package_name - - def valid? - valid_length? && lowercased? && characters_valid? - end - - def valid_length? - package_name.length.positive? && - package_name.length <= MAX_PACKAGE_NAME_LENGTH - end - - def lowercased? - package_name == package_name.downcase - end - - def characters_valid? - !package_name.start_with?("_", ".") && - !name_without_types_org.start_with?("_", ".") && - !package_name.match?(INVALID_CHARACTERS_REGEX) - end - - def scoped_typings_package? - typings_package? && name_without_types_org.include?("__") - end - - def typings_package? - package_name.start_with?("#{TYPES_ORG}") - end - - def unscoped_name_without_types_org - scope, package = name_without_types_org.split("__") - "@#{scope}/#{package}" - end - - def name_without_types_org - package_name.sub(%r{^#{TYPES_ORG}}, "") - end - end - end -end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb deleted file mode 100644 index 07dc9376f7e..00000000000 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/types_package_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" -require "dependabot/npm_and_yarn/types_package" - -RSpec.describe Dependabot::NpmAndYarn::TypesPackage do - describe "#library" do - it "returns the corresponding dependency name" do - jquery_types = "@types/jquery" - jquery = "jquery" - - library = described_class.new(jquery_types).library - - expect(library).to eq(jquery) - end - - it "trusts users to have meaningful package names" do - expect(described_class.new("@types/🤷").library).to eq("🤷") - expect(described_class.new("@types/[]").library).to eq("[]") - expect(described_class.new("@types/{}").library).to eq("{}") - end - - - context "when given a scoped type definition dependency name" do - it "returns the corresponding scoped dependency name" do - babel_core_types = "@types/babel__core" - babel_core = "@babel/core" - - library = described_class.new(babel_core_types).library - - expect(library).to eq(babel_core) - end - end - - context "when the input cannot be translated" do - it "returns an empty string" do - expect(described_class.new("").library).to eq("") - expect(described_class.new("@types/").library).to eq("") - expect(described_class.new("@types/NOT_A_TYPES_DEFINITION").library).to eq("") - expect(described_class.new("@types/ prefixed-with-a-space").library).to eq("") - expect(described_class.new("@types/.prefixed-with-a-dot").library).to eq("") - expect(described_class.new("@types/!invalid").library).to eq("") - end - end - end -end From a5cbcf65210fcd5501e5ff225352d78fb5e5eb4c Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Fri, 15 Apr 2022 10:07:45 -0400 Subject: [PATCH 4/7] Rename TypeScriptLibrary -> PackageName Co-authored-by: Mattt --- .../npm_and_yarn/{type_script_library.rb => package_name.rb} | 2 +- .../{type_script_library_spec.rb => package_name_spec.rb} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename npm_and_yarn/lib/dependabot/npm_and_yarn/{type_script_library.rb => package_name.rb} (97%) rename npm_and_yarn/spec/dependabot/npm_and_yarn/{type_script_library_spec.rb => package_name_spec.rb} (94%) diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb similarity index 97% rename from npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb rename to npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb index f38d1fa9152..ffc1127aceb 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/type_script_library.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb @@ -2,7 +2,7 @@ module Dependabot module NpmAndYarn - class TypeScriptLibrary + class PackageName DEFINITELY_TYPED_SCOPE = /types/i PACKAGE_NAME_REGEX = %r{ \A # beginning of string diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb similarity index 94% rename from npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb rename to npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb index 3b76ad9095d..a3872af82d0 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/type_script_library_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require "spec_helper" -require "dependabot/npm_and_yarn/type_script_library" +require "dependabot/npm_and_yarn/package_name" -RSpec.describe Dependabot::NpmAndYarn::TypeScriptLibrary do +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) From 041fa3b46f5d6fbff37b91898055adc81731f0f3 Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Fri, 15 Apr 2022 12:48:52 -0400 Subject: [PATCH 5/7] Expose an interface to compare package names Co-authored-by: Mattt --- .../dependabot/npm_and_yarn/package_name.rb | 14 ++++++- .../npm_and_yarn/package_name_spec.rb | 38 ++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb index ffc1127aceb..ec3541ccdd6 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb @@ -22,8 +22,20 @@ def initialize(string) @name = match[:name] end + def to_s + if scoped? + "@#{@scope}/#{@name}" + else + @name.to_s + end + end + + def <=>(other) + to_s <=> other.to_s + end + def types_package - return if types_package? + return self if types_package? if scoped? "@types/#{@scope}__#{@name}" diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb index a3872af82d0..6267b5b16ad 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb @@ -15,8 +15,26 @@ 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" do it "returns the corresponding types package name" do lodash = "lodash" @@ -27,12 +45,12 @@ expect(types_package).to eq(lodash_types) end - it "returns nil if it is already a types package" do + it "returns self if it is already a types package" do stereo_types = "@types/stereo" types_package = described_class.new(stereo_types).types_package - expect(types_package).to be_nil + expect(types_package.to_s).to eq(stereo_types) end context "when given a scoped dependency name" do @@ -46,4 +64,20 @@ end 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 "allows for comparison with types packages" do + library = described_class.new("my-library") + + expect([ library, library.types_package ].sort).to eq([ library.types_package, library ]) + end + end end From 525fbcf6f33a7faab4b95ff3564c01bbd4f4fec3 Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Fri, 15 Apr 2022 13:17:21 -0400 Subject: [PATCH 6/7] Pacify Rubocop --- npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb | 2 +- .../spec/dependabot/npm_and_yarn/package_name_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb index ec3541ccdd6..581b227eae4 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb @@ -3,7 +3,7 @@ module Dependabot module NpmAndYarn class PackageName - DEFINITELY_TYPED_SCOPE = /types/i + DEFINITELY_TYPED_SCOPE = /types/i.freeze PACKAGE_NAME_REGEX = %r{ \A # beginning of string (?=.{1,214}\z) # enforce length (1 - 214) diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb index 6267b5b16ad..746118ad771 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb @@ -71,13 +71,13 @@ second = described_class.new("second") third = described_class.new("third") - expect([ third, second, first ].sort).to eq([ first, second, third ]) + expect([third, second, first].sort).to eq([first, second, third]) end it "allows for comparison with types packages" do library = described_class.new("my-library") - expect([ library, library.types_package ].sort).to eq([ library.types_package, library ]) + expect([library, library.types_package].sort).to eq([library.types_package, library]) end end end From 997cadecd2b823f4b4c8ece5bae0a7b7c4777c9d Mon Sep 17 00:00:00 2001 From: Landon Grindheim Date: Mon, 18 Apr 2022 09:26:52 -0400 Subject: [PATCH 7/7] Extend PackageName with value object interface We should be able to compare two package names, and since they don't have an identity, it makes sense to treat them as values. One assumption I made here is that we'd want to be able to compare non-PackageName objects' string values with PackageName object string values (i.e. `PackageName.new("string") == "string"`) Co-authored-by: Mattt --- .../dependabot/npm_and_yarn/package_name.rb | 19 ++++--- .../npm_and_yarn/package_name_spec.rb | 54 ++++++++++++++++--- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb index 581b227eae4..fab9e42a0dc 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_name.rb @@ -31,17 +31,22 @@ def to_s end def <=>(other) - to_s <=> other.to_s + to_s.casecmp(other.to_s) end - def types_package + def eql?(other) + to_s.eql?(other.to_s) + end + + def types_package_name return self if types_package? - if scoped? - "@types/#{@scope}__#{@name}" - else - "@types/#{@name}" - end + @types_package_name ||= + if scoped? + self.class.new("@types/#{@scope}__#{@name}") + else + self.class.new("@types/#{@name}") + end end private diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb index 746118ad771..6ee71e321a2 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/package_name_spec.rb @@ -35,22 +35,22 @@ end end - describe "#types_package" do + describe "#types_package_name" do it "returns the corresponding types package name" do lodash = "lodash" lodash_types = "@types/lodash" - types_package = described_class.new(lodash).types_package + types_package_name = described_class.new(lodash).types_package_name - expect(types_package).to eq(lodash_types) + 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 = described_class.new(stereo_types).types_package + types_package_name = described_class.new(stereo_types).types_package_name - expect(types_package.to_s).to eq(stereo_types) + expect(types_package_name.to_s).to eq(stereo_types) end context "when given a scoped dependency name" do @@ -58,13 +58,42 @@ babel_core = "@babel/core" babel_core_types = "@types/babel__core" - types_package = described_class.new(babel_core).types_package + types_package_name = described_class.new(babel_core).types_package_name - expect(types_package).to eq(babel_core_types) + 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") @@ -74,10 +103,19 @@ 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].sort).to eq([library.types_package, library]) + expect([library, library.types_package_name].sort). + to eq([library.types_package_name, library]) end end end