Commit aa24feed authored by Douwe Maan's avatar Douwe Maan

Refactor DependencyLinker::BaseLinker

parent 52527be4
module Gitlab module Gitlab
module DependencyLinker module DependencyLinker
class BaseLinker class BaseLinker
URL_REGEX = %r{https?://[^'"]+}.freeze
class_attribute :file_type class_attribute :file_type
def self.support?(blob_name) def self.support?(blob_name)
...@@ -26,59 +28,17 @@ module Gitlab ...@@ -26,59 +28,17 @@ module Gitlab
private private
def package_url(name)
raise NotImplementedError
end
def link_dependencies def link_dependencies
raise NotImplementedError raise NotImplementedError
end end
def package_link(name, url = package_url(name)) def license_url(name)
return name unless url Licensee::License.find(name)&.url
%{<a href="#{ERB::Util.html_escape_once(url)}" rel="noopener noreferrer" target="_blank">#{ERB::Util.html_escape_once(name)}</a>}
end
# Links package names in a method call or assignment string argument.
#
# Example:
# link_method_call("gem")
# # Will link `package` in `gem "package"`, `gem("package")` and `gem = "package"`
#
# link_method_call("gem", "specific_package")
# # Will link `specific_package` in `gem "specific_package"`
#
# link_method_call("github", /[^\/]+\/[^\/]+/)
# # Will link `user/repo` in `github "user/repo"`, but not `github "package"`
#
# link_method_call(%w[add_dependency add_development_dependency])
# # Will link `spec.add_dependency "package"` and `spec.add_development_dependency "package"`
#
# link_method_call("name")
# # Will link `package` in `self.name = "package"`
def link_method_call(method_names, value = nil, &url_proc)
value =
case value
when String
Regexp.escape(value)
when nil
/[^'"]+/
else
value
end end
method_names = Array(method_names).map { |name| Regexp.escape(name) }
regex = %r{
#{Regexp.union(method_names)} # Method name
\s* # Whitespace
[(=]? # Opening brace or equals sign
\s* # Whitespace
['"](?<name>#{value})['"] # Package name in quotes
}x
link_regex(regex, &url_proc) def link_tag(name, url)
%{<a href="#{ERB::Util.html_escape_once(url)}" rel="nofollow noreferrer noopener" target="_blank">#{ERB::Util.html_escape_once(name)}</a>}
end end
# Links package names based on regex. # Links package names based on regex.
...@@ -86,13 +46,13 @@ module Gitlab ...@@ -86,13 +46,13 @@ module Gitlab
# Example: # Example:
# link_regex(/(github:|:github =>)\s*['"](?<name>[^'"]+)['"]/) # link_regex(/(github:|:github =>)\s*['"](?<name>[^'"]+)['"]/)
# # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"` # # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"`
def link_regex(regex) def link_regex(regex, &url_proc)
highlighted_lines.map!.with_index do |rich_line, i| highlighted_lines.map!.with_index do |rich_line, i|
marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe) marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe)
marker.mark(regex, group: :name) do |text, left:, right:| marker.mark(regex, group: :name) do |text, left:, right:|
url = block_given? ? yield(text) : package_url(text) url = yield(text)
package_link(text, url) url ? link_tag(text, url) : text
end end
end end
end end
......
module Gitlab module Gitlab
module DependencyLinker module DependencyLinker
class GemfileLinker < BaseLinker class GemfileLinker < MethodLinker
self.file_type = :gemfile self.file_type = :gemfile
private private
def link_dependencies def link_dependencies
# Link `gem "package_name"` to https://rubygems.org/gems/package_name link_urls
link_method_call("gem") link_packages
end
def link_urls
# Link `github: "user/repo"` to https://github.com/user/repo # Link `github: "user/repo"` to https://github.com/user/repo
link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/) do |name| link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/, &method(:github_url))
"https://github.com/#{name}"
end
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo # Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>https?://[^'"]+)['"]}) { |url| url } link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]}, &:itself)
# Link `source "https://rubygems.org"` to https://rubygems.org # Link `source "https://rubygems.org"` to https://rubygems.org
link_method_call("source", %r{https?://[^'"]+}) { |url| url } link_method_call('source', URL_REGEX, &:itself)
end end
def package_url(name) def link_packages
# Link `gem "package_name"` to https://rubygems.org/gems/package_name
link_method_call('gem') do |name|
"https://rubygems.org/gems/#{name}" "https://rubygems.org/gems/#{name}"
end end
end end
end end
end
end end
module Gitlab
module DependencyLinker
class MethodLinker < BaseLinker
private
# Links package names in a method call or assignment string argument.
#
# Example:
# link_method_call('gem')
# # Will link `package` in `gem "package"`, `gem("package")` and `gem = "package"`
#
# link_method_call('gem', 'specific_package')
# # Will link `specific_package` in `gem "specific_package"`
#
# link_method_call('github', /[^\/"]+\/[^\/"]+/)
# # Will link `user/repo` in `github "user/repo"`, but not `github "package"`
#
# link_method_call(%w[add_dependency add_development_dependency])
# # Will link `spec.add_dependency "package"` and `spec.add_development_dependency "package"`
#
# link_method_call('name')
# # Will link `package` in `self.name = "package"`
def link_method_call(method_names, value = nil, &url_proc)
method_names = Array(method_names).map { |name| Regexp.escape(name) }
value =
case value
when String
Regexp.escape(value)
when nil
/[^'"]+/
else
value
end
regex = %r{
#{Regexp.union(method_names)} # Method name
\s* # Whitespace
[(=]? # Opening brace or equals sign
\s* # Whitespace
['"](?<name>#{value})['"] # Package name in quotes
}x
link_regex(regex, &url_proc)
end
end
end
end
...@@ -33,7 +33,7 @@ describe Gitlab::DependencyLinker::GemfileLinker, lib: true do ...@@ -33,7 +33,7 @@ describe Gitlab::DependencyLinker::GemfileLinker, lib: true do
subject { Gitlab::Highlight.highlight(file_name, file_content) } subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url) def link(name, url)
%{<a href="#{url}" rel="noopener noreferrer" target="_blank">#{name}</a>} %{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end end
it 'links sources' do it 'links sources' do
......
...@@ -59,8 +59,6 @@ describe Gitlab::Highlight, lib: true do ...@@ -59,8 +59,6 @@ describe Gitlab::Highlight, lib: true do
end end
describe '#highlight' do describe '#highlight' do
subject { described_class.highlight(file_name, file_content, nowrap: false) }
it 'links dependencies via DependencyLinker' do it 'links dependencies via DependencyLinker' do
expect(Gitlab::DependencyLinker).to receive(:link). expect(Gitlab::DependencyLinker).to receive(:link).
with('file.name', 'Contents', anything).and_call_original with('file.name', 'Contents', anything).and_call_original
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment