# frozen_string_literal: truerequire_relative"user_interaction"classGem::SpecificationPolicyincludeGem::UserInteractionVALID_NAME_PATTERN=/\A[a-zA-Z0-9\.\-\_]+\z/# :nodoc:SPECIAL_CHARACTERS=/\A[#{Regexp.escape(".-_")}]+/# :nodoc:VALID_URI_PATTERN=%r{\Ahttps?:\/\/([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z}# :nodoc:METADATA_LINK_KEYS=%w[
homepage_uri
changelog_uri
source_code_uri
documentation_uri
wiki_uri
mailing_list_uri
bug_tracker_uri
download_uri
funding_uri
].freeze# :nodoc:definitialize(specification)@warnings=0@specification=specificationend### If set to true, run packaging-specific checks, as well.attr_accessor:packaging### Does a sanity check on the specification.## Raises InvalidSpecificationException if the spec does not pass the# checks.## It also performs some validations that do not raise but print warning# messages instead.defvalidate(strict=false)validate_required!validate_required_metadata!validate_optional(strict)ifpackaging||stricttrueend### Does a sanity check on the specification.## Raises InvalidSpecificationException if the spec does not pass the# checks.## Only runs checks that are considered necessary for the specification to be# functional.defvalidate_required!validate_nil_attributesvalidate_rubygems_versionvalidate_required_attributesvalidate_namevalidate_require_paths@specification.keep_only_files_and_directoriesvalidate_non_filesvalidate_self_inclusion_in_files_listvalidate_specification_versionvalidate_platformvalidate_array_attributesvalidate_authors_fieldvalidate_licenses_lengthvalidate_duplicate_dependenciesenddefvalidate_required_metadata!validate_metadatavalidate_lazy_metadataenddefvalidate_optional(strict)validate_licensesvalidate_permissionsvalidate_valuesvalidate_dependenciesvalidate_required_ruby_versionvalidate_extensionsvalidate_removed_attributesvalidate_unique_linksif@warnings>0ifstricterror"specification has warnings"elsealert_warninghelp_textendendend### Implementation for Specification#validate_for_resolutiondefvalidate_for_resolutionvalidate_required!end### Implementation for Specification#validate_metadatadefvalidate_metadatametadata=@specification.metadataunlessHash===metadataerror"metadata must be a hash"endmetadata.eachdo|key,value|entry="metadata['#{key}']"unlesskey.is_a?(String)error"metadata keys must be a String"endifkey.size>128error"metadata key is too large (#{key.size} > 128)"endunlessvalue.is_a?(String)error"#{entry} value must be a String"endifvalue.size>1024error"#{entry} value is too large (#{value.size} > 1024)"endnextunlessMETADATA_LINK_KEYS.include?keyunlessVALID_URI_PATTERN.match?(value)error"#{entry} has invalid link: #{value.inspect}"endendend### Checks that no duplicate dependencies are specified.defvalidate_duplicate_dependencies# :nodoc:# NOTE: see REFACTOR note in Gem::Dependency about types - this might be brittleseen=Gem::Dependency::TYPES.inject({}){|types,type|types.merge({type=>{}})}error_messages=[]@specification.dependencies.eachdo|dep|ifprev=seen[dep.type][dep.name]error_messages<<<<-MESSAGE
duplicate dependency on #{dep}, (#{prev.requirement}) use:
add_#{dep.type}_dependency \"#{dep.name}\", \"#{dep.requirement}\", \"#{prev.requirement}\" MESSAGEendseen[dep.type][dep.name]=dependiferror_messages.any?errorerror_messages.joinendend### Checks that the gem does not depend on itself.# Checks that dependencies use requirements as we recommend. Warnings are# issued when dependencies are open-ended or overly strict for semantic# versioning.defvalidate_dependencies# :nodoc:warning_messages=[]@specification.dependencies.eachdo|dep|ifdep.name==@specification.name# warn on self referencewarning_messages<<"Self referencing dependency is unnecessary and strongly discouraged."endprerelease_dep=dep.requirements_list.any?do|req|Gem::Requirement.new(req).prerelease?endwarning_messages<<"prerelease dependency on #{dep} is not recommended"ifprerelease_dep&&!@specification.version.prerelease?open_ended=dep.requirement.requirements.all?do|op,version|!version.prerelease?&&[">",">="].include?(op)endnextunlessopen_endedop,dep_version=dep.requirement.requirements.firstsegments=dep_version.segmentsbase=segments.first2recommendation=if[">",">="].include?(op)&&segments==[0]" use a bounded requirement, such as \"~> x.y\""elsebugfix=ifop==">"", \"> #{dep_version}\""elsifop==">="&&base!=segments", \">= #{dep_version}\""end" if #{dep.name} is semantically versioned, use:\n"\" add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join"."}\"#{bugfix}"endwarning_messages<<["open-ended dependency on #{dep} is not recommended",recommendation].join("\n")+"\n"endifwarning_messages.any?warning_messages.each{|warning_message|warningwarning_message}endenddefvalidate_required_ruby_versionif@specification.required_ruby_version.requirements==[Gem::Requirement::DefaultRequirement]warning"make sure you specify the oldest ruby version constraint (like \">= 3.0\") that you want your gem to support by setting the `required_ruby_version` gemspec attribute"endend### Issues a warning for each file to be packaged which is world-readable.## Implementation for Specification#validate_permissionsdefvalidate_permissionsreturnifGem.win_platform?@specification.files.eachdo|file|nextunlessFile.file?(file)nextifFile.stat(file).mode&0o444==0o444warning"#{file} is not world-readable"end@specification.executables.eachdo|name|exec=File.join@specification.bindir,namenextunlessFile.file?(exec)nextifFile.stat(exec).executable?warning"#{exec} is not executable"endendprivatedefvalidate_nil_attributesnil_attributes=Gem::Specification.non_nil_attributes.selectdo|attrname|@specification.instance_variable_get("@#{attrname}").nil?endreturnifnil_attributes.empty?error"#{nil_attributes.join", "} must not be nil"enddefvalidate_rubygems_versionreturnunlesspackagingrubygems_version=@specification.rubygems_versionreturnifrubygems_version==Gem::VERSIONwarning"expected RubyGems version #{Gem::VERSION}, was #{rubygems_version}"@specification.rubygems_version=Gem::VERSIONenddefvalidate_required_attributesGem::Specification.required_attributes.eachdo|symbol|unless@specification.sendsymbolerror"missing value for attribute #{symbol}"endendenddefvalidate_namename=@specification.nameif!name.is_a?(String)error"invalid value for attribute name: \"#{name.inspect}\" must be a string"elsif!/[a-zA-Z]/.match?(name)error"invalid value for attribute name: #{name.dump} must include at least one letter"elsif!VALID_NAME_PATTERN.match?(name)error"invalid value for attribute name: #{name.dump} can only include letters, numbers, dashes, and underscores"elsifSPECIAL_CHARACTERS.match?(name)error"invalid value for attribute name: #{name.dump} cannot begin with a period, dash, or underscore"endenddefvalidate_require_pathsreturnunless@specification.raw_require_paths.empty?error"specification must have at least one require_path"enddefvalidate_non_filesreturnunlesspackagingnon_files=@specification.files.reject{|x|File.file?(x)||File.symlink?(x)}unlessnon_files.empty?error"[\"#{non_files.join"\", \""}\"] are not files"endenddefvalidate_self_inclusion_in_files_listfile_name=@specification.file_namereturnunless@specification.files.include?(file_name)error"#{@specification.full_name} contains itself (#{file_name}), check your files list"enddefvalidate_specification_versionreturnif@specification.specification_version.is_a?(Integer)error"specification_version must be an Integer (did you mean version?)"enddefvalidate_platformplatform=@specification.platformcaseplatformwhenGem::Platform,Gem::Platform::RUBY# okelseerror"invalid platform #{platform.inspect}, see Gem::Platform"endenddefvalidate_array_attributesGem::Specification.array_attributes.eachdo|field|validate_array_attribute(field)endenddefvalidate_array_attribute(field)val=@specification.send(field)klass=casefieldwhen:dependenciesthenGem::DependencyelseStringendunlessArray===val&&val.all?{|x|x.is_a?(klass)||(field==:licenses&&x.nil?)}error"#{field} must be an Array of #{klass}"endenddefvalidate_authors_fieldreturnunless@specification.authors.empty?error"authors may not be empty"enddefvalidate_licenses_lengthlicenses=@specification.licenseslicenses.eachdo|license|nextiflicense.nil?iflicense.length>64error"each license must be 64 characters or less"endendenddefvalidate_licenseslicenses=@specification.licenseslicenses.eachdo|license|nextifGem::Licenses.match?(license)||license.nil?license_id_deprecated=Gem::Licenses.deprecated_license_id?(license)exception_id_deprecated=Gem::Licenses.deprecated_exception_id?(license)suggestions=Gem::Licenses.suggestions(license)iflicense_id_deprecatedmain_message="License identifier '#{license}' is deprecated"elsifexception_id_deprecatedmain_message="Exception identifier at '#{license}' is deprecated"elsemain_message="License identifier '#{license}' is invalid"endmessage=<<-WARNING#{main_message}. Use an identifier from
https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license,
or set it to nil if you don't want to specify a license.
WARNINGmessage+="Did you mean #{suggestions.map{|s|"'#{s}'"}.join(",")}?\n"unlesssuggestions.nil?warning(message)endwarning<<-WARNINGiflicenses.empty?
licenses is empty, but is recommended. Use an license identifier from
https://spdx.org/licenses or '#{Gem::Licenses::NONSTANDARD}' for a nonstandard license,
or set it to nil if you don't want to specify a license.
WARNINGendLAZY='"FIxxxXME" or "TOxxxDO"'.gsub(/xxx/,"")LAZY_PATTERN=/\AFI XME|\ATO DO/xHOMEPAGE_URI_PATTERN=/\A[a-z][a-z\d+.-]*:/idefvalidate_lazy_metadataunless@specification.authors.grep(LAZY_PATTERN).empty?error"#{LAZY} is not an author"endunlessArray(@specification.email).grep(LAZY_PATTERN).empty?error"#{LAZY} is not an email"endifLAZY_PATTERN.match?(@specification.description)error"#{LAZY} is not a description"endifLAZY_PATTERN.match?(@specification.summary)error"#{LAZY} is not a summary"endhomepage=@specification.homepage# Make sure a homepage is valid HTTP/HTTPS URIifhomepage&&!homepage.empty?require_relative"vendor/uri/lib/uri"beginhomepage_uri=Gem::URI.parse(homepage)unless[Gem::URI::HTTP,Gem::URI::HTTPS].member?homepage_uri.classerror"\"#{homepage}\" is not a valid HTTP URI"endrescueGem::URI::InvalidURIErrorerror"\"#{homepage}\" is not a valid HTTP URI"endendenddefvalidate_values%w[author homepage summary files].eachdo|attribute|validate_attribute_present(attribute)endif@specification.description==@specification.summarywarning"description and summary are identical"end# TODO: raise at some given datewarning"deprecated autorequire specified"if@specification.autorequire@specification.executables.eachdo|executable|validate_shebang_line_in(executable)end@specification.files.select{|f|File.symlink?(f)}.eachdo|file|warning"#{file} is a symlink, which is not supported on all platforms"endenddefvalidate_attribute_present(attribute)value=@specification.sendattributewarning("no #{attribute} specified")ifvalue.nil?||value.empty?enddefvalidate_shebang_line_in(executable)executable_path=File.join(@specification.bindir,executable)returnifFile.read(executable_path,2)=="#!"warning"#{executable_path} is missing #! line"enddefvalidate_removed_attributes# :nodoc:@specification.removed_method_calls.eachdo|attr|warning("#{attr} is deprecated and ignored. Please remove this from your gemspec to ensure that your gem continues to build in the future.")endenddefvalidate_extensions# :nodoc:require_relative"ext"builder=Gem::Ext::Builder.new(@specification)validate_rake_extensions(builder)validate_rust_extensions(builder)enddefvalidate_rust_extensions(builder)# :nodoc:rust_extension=@specification.extensions.any?{|s|builder.builder_for(s).is_a?Gem::Ext::CargoBuilder}missing_cargo_lock=!@specification.files.any?{|f|f.end_with?("Cargo.lock")}error<<-ERRORifrust_extension&&missing_cargo_lock
You have specified rust based extension, but Cargo.lock is not part of the gem files. Please run `cargo generate-lockfile` or any other command to generate Cargo.lock and ensure it is added to your gem files section in gemspec.
ERRORenddefvalidate_rake_extensions(builder)# :nodoc:rake_extension=@specification.extensions.any?{|s|builder.builder_for(s)==Gem::Ext::RakeBuilder}rake_dependency=@specification.dependencies.any?{|d|d.name=="rake"&&d.type==:runtime}warning<<-WARNINGifrake_extension&&!rake_dependency
You have specified rake based extension, but rake is not added as runtime dependency. It is recommended to add rake as a runtime dependency in gemspec since there's no guarantee rake will be already installed.
WARNINGenddefvalidate_unique_linkslinks=@specification.metadata.slice(*METADATA_LINK_KEYS)grouped=links.group_by{|_key,uri|uri}grouped.eachdo|uri,copies|nextunlesscopies.length>1keys=copies.map(&:first).join("\n ")warning<<~WARNING
You have specified the uri:
#{uri}
for all of the following keys:
#{keys}
Only the first one will be shown on rubygems.org
WARNINGendenddefwarning(statement)# :nodoc:@warnings+=1alert_warningstatementenddeferror(statement)# :nodoc:raiseGem::InvalidSpecificationException,statementensurealert_warninghelp_textenddefhelp_text# :nodoc:"See https://guides.rubygems.org/specification-reference/ for help"endend