# typed: strict# frozen_string_literal: truerequire"sorbet-runtime"require"dependabot/pull_request_creator/message_builder"moduleDependabotclassPullRequestCreatorclassMessageBuilderclassMetadataPresenterextendT::SigextendForwardablesig{returns(Dependabot::Dependency)}attr_reader:dependencysig{returns(Dependabot::Source)}attr_reader:sourcesig{returns(Dependabot::MetadataFinders::Base)}attr_reader:metadata_findersig{returns(T.nilable(T::Array[T::Hash[String,String]]))}attr_reader:vulnerabilities_fixedsig{returns(T.nilable(String))}attr_reader:github_redirection_servicedef_delegators:metadata_finder,:changelog_url,:changelog_text,:commits_url,:commits,:maintainer_changes,:releases_url,:releases_text,:source_url,:upgrade_guide_url,:upgrade_guide_textsigdoparams(dependency: Dependabot::Dependency,source: Dependabot::Source,metadata_finder: Dependabot::MetadataFinders::Base,vulnerabilities_fixed: T.nilable(T::Array[T::Hash[String,String]]),github_redirection_service: T.nilable(String)).voidenddefinitialize(dependency:,source:,metadata_finder:,vulnerabilities_fixed:,github_redirection_service:)@dependency=dependency@source=source@metadata_finder=metadata_finder@vulnerabilities_fixed=vulnerabilities_fixed@github_redirection_service=github_redirection_serviceendsig{returns(String)}defto_smsg=""msg+=vulnerabilities_cascademsg+=release_cascademsg+=changelog_cascademsg+=upgrade_guide_cascademsg+=commits_cascademsg+=maintainer_changes_cascademsg+=break_tagunlessmsg=="""\n"+sanitize_links_and_mentions(msg,unsafe: true)endprivatesig{returns(String)}defvulnerabilities_cascadereturn""unlessvulnerabilities_fixed&.any?msg=""T.must(vulnerabilities_fixed).eachdo|v|msg+=serialized_vulnerability_details(v)endmsg=sanitize_template_tags(msg)msg=sanitize_links_and_mentions(msg)build_details_tag(summary: "Vulnerabilities fixed",body: msg)endsig{returns(String)}defrelease_cascadereturn""unlessreleases_text&&releases_urlmsg="*Sourced from [#{dependency.display_name}'s releases]"\"(#{releases_url}).*\n\n"msg+=quote_and_truncate(releases_text)msg=link_issues(text: msg)msg=fix_relative_links(text: msg,base_url: source_url+"/blob/HEAD/")msg=sanitize_template_tags(msg)msg=sanitize_links_and_mentions(msg)build_details_tag(summary: "Release notes",body: msg)endsig{returns(String)}defchangelog_cascadereturn""unlesschangelog_url&&changelog_textmsg="*Sourced from "\"[#{dependency.display_name}'s changelog]"\"(#{changelog_url}).*\n\n"msg+=quote_and_truncate(changelog_text)msg=link_issues(text: msg)msg=fix_relative_links(text: msg,base_url: changelog_url)msg=sanitize_template_tags(msg)msg=sanitize_links_and_mentions(msg)build_details_tag(summary: "Changelog",body: msg)endsig{returns(String)}defupgrade_guide_cascadereturn""unlessupgrade_guide_url&&upgrade_guide_textmsg="*Sourced from "\"[#{dependency.display_name}'s upgrade guide]"\"(#{upgrade_guide_url}).*\n\n"msg+=quote_and_truncate(upgrade_guide_text)msg=link_issues(text: msg)msg=fix_relative_links(text: msg,base_url: upgrade_guide_url)msg=sanitize_template_tags(msg)msg=sanitize_links_and_mentions(msg)build_details_tag(summary: "Upgrade guide",body: msg)endsig{returns(String)}defcommits_cascadereturn""unlesscommits_url&&commitsmsg=""commits.last(10).reverse_eachdo|commit|title=commit[:message].strip.split("\n").firsttitle=title.slice(0..76)+"..."iftitle&&title.length>80title=title&.gsub(/(?<=[^\w.-])([_*`~])/,'\\1')sha=commit[:sha][0,7]msg+="- [`#{sha}`](#{commit[:html_url]}) #{title}\n"endmsg=msg.gsub(/\<.*?\>/){|tag|"\\#{tag}"}msg+=ifcommits.count>10"- Additional commits viewable in "\"[compare view](#{commits_url})\n"else"- See full diff in [compare view](#{commits_url})\n"endmsg=link_issues(text: msg)msg=sanitize_links_and_mentions(msg)build_details_tag(summary: "Commits",body: msg)endsig{returns(String)}defmaintainer_changes_cascadereturn""unlessmaintainer_changesbuild_details_tag(summary: "Maintainer changes",body: sanitize_links_and_mentions(maintainer_changes)+"\n")endsig{params(summary: String,body: String).returns(String)}defbuild_details_tag(summary:,body:)# Bitbucket does not support <details> tag (https://jira.atlassian.com/browse/BCLOUD-20231)# CodeCommit does not support the <details> tag (no url available)ifsource_provider_supports_html?msg="<details>\n<summary>#{summary}</summary>\n\n"msg+=bodymsg+"</details>\n"else"\n# #{summary}\n\n#{body}"endendsig{params(details: T::Hash[String,String]).returns(String)}defserialized_vulnerability_details(details)msg=vulnerability_source_line(details)msg+="> **#{T.must(details['title']).lines.map(&:strip).join(' ')}**\n"ifdetails["title"]if(description=details["description"])description.strip.lines.first(20).each{|line|msg+="> #{line}"}msg+=truncated_lineifdescription.strip.lines.count>20endmsg+="\n"unlessmsg.end_with?("\n")msg+="> \n"msg+=vulnerability_version_range_lines(details)msg+"\n"endsig{params(details: T::Hash[String,String]).returns(String)}defvulnerability_source_line(details)ifdetails["source_url"]&&details["source_name"]"*Sourced from [#{details['source_name']}]"\"(#{details['source_url']}).*\n\n"elsifdetails["source_name"]"*Sourced from #{details['source_name']}.*\n\n"else""endendsig{params(details: T::Hash[String,T.untyped]).returns(String)}defvulnerability_version_range_lines(details)msg=""%w(
patched_versions
unaffected_versions
affected_versions
).eachdo|tp|type=T.must(tp.split("_").first).capitalizenextunlessdetails[tp]versions_string=details[tp].any??details[tp].join("; "):"none"versions_string=versions_string.gsub(/(?<!\\)~/,'\~')msg+="> #{type} versions: #{versions_string}\n"endmsgendsig{params(text: String).returns(String)}deflink_issues(text:)IssueLinker.new(source_url: source_url).link_issues(text: text)endsig{params(text: String,base_url: String).returns(String)}deffix_relative_links(text:,base_url:)text.gsub(/\[.*?\]\([^)]+\)/)do|link|nextlinkiflink.include?("://")relative_path=T.must(T.must(link.match(/\((.*?)\)/)).captures.last)base=T.must(base_url.split("://").last).gsub(%r{[^/]*$},"")path=File.join(base,relative_path)absolute_path=base_url.sub(%r{(?<=://).*$},Pathname.new(path).cleanpath.to_s)link.gsub(relative_path,absolute_path)endendsig{params(text: String,limit: Integer).returns(String)}defquote_and_truncate(text,limit: 50)lines=text.split("\n")lines.first(limit).tapdo|limited_lines|limited_lines.map!{|line|"> #{line}\n"}limited_lines<<truncated_lineiflines.count>limitend.joinendsig{returns(String)}deftruncated_line# Tables can spill out of truncated details, so we close them"></tr></table> \n ... (truncated)\n"endsig{returns(String)}defbreak_tagsource_provider_supports_html??"\n<br />":"\n\n"endsig{returns(T::Boolean)}defsource_provider_supports_html?!%w(bitbucket codecommit).include?(source.provider)endsig{params(text: String,unsafe: T::Boolean).returns(String)}defsanitize_links_and_mentions(text,unsafe: false)LinkAndMentionSanitizer.new(github_redirection_service: github_redirection_service).sanitize_links_and_mentions(text: text,unsafe: unsafe,format_html: source_provider_supports_html?)endsig{params(text: String).returns(String)}defsanitize_template_tags(text)text.gsub(/\<.*?\>/)do|tag|tag_contents=tag.match(/\<(.*?)\>/)&.captures&.first&.strip# Unclosed calls to template overflow out of the blockquote block,# wrecking the rest of our PRs. Other tags don't share this problem.next"\\#{tag}"iftag_contents&.start_with?("template")tagendendendendendend