Merge branch 'dm-more-dependency-linkers' into 'master'

Autolink package names in more dependency files

See merge request !11226
This commit is contained in:
Grzegorz Bizon 2017-05-25 16:23:21 +00:00
commit d9835145f9
28 changed files with 1129 additions and 69 deletions

View file

@ -0,0 +1,4 @@
---
title: Autolink package names in more dependency files
merge_request:
author:

View file

@ -1,7 +1,16 @@
module Gitlab module Gitlab
module DependencyLinker module DependencyLinker
LINKERS = [ LINKERS = [
GemfileLinker GemfileLinker,
GemspecLinker,
PackageJsonLinker,
ComposerJsonLinker,
PodfileLinker,
PodspecLinker,
PodspecJsonLinker,
CartfileLinker,
GodepsJsonLinker,
RequirementsTxtLinker
].freeze ].freeze
def self.linker(blob_name) def self.linker(blob_name)

View file

@ -1,6 +1,9 @@
module Gitlab module Gitlab
module DependencyLinker module DependencyLinker
class BaseLinker class BaseLinker
URL_REGEX = %r{https?://[^'"]+}.freeze
REPO_REGEX = %r{[^/'"]+/[^/'"]+}.freeze
class_attribute :file_type class_attribute :file_type
def self.support?(blob_name) def self.support?(blob_name)
@ -26,59 +29,20 @@ 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 end
# Links package names in a method call or assignment string argument. def github_url(name)
# "https://github.com/#{name}"
# Example: end
# 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
method_names = Array(method_names).map { |name| Regexp.escape(name) } 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>}
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
# Links package names based on regex. # Links package names based on regex.
@ -86,13 +50,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
@ -104,6 +68,19 @@ module Gitlab
def highlighted_lines def highlighted_lines
@highlighted_lines ||= highlighted_text.lines @highlighted_lines ||= highlighted_text.lines
end end
def regexp_for_value(value, default: /[^'"]+/)
case value
when Array
Regexp.union(value.map { |v| regexp_for_value(v, default: default) })
when String
Regexp.escape(value)
when Regexp
value
else
default
end
end
end end
end end
end end

View file

@ -0,0 +1,14 @@
module Gitlab
module DependencyLinker
class CartfileLinker < MethodLinker
self.file_type = :cartfile
private
def link_dependencies
link_method_call('github', REPO_REGEX, &method(:github_url))
link_method_call(%w[github git binary], URL_REGEX, &:itself)
end
end
end
end

View file

@ -0,0 +1,10 @@
module Gitlab
module DependencyLinker
module Cocoapods
def package_url(name)
package = name.split("/", 2).first
"https://cocoapods.org/pods/#{package}"
end
end
end
end

View file

@ -0,0 +1,18 @@
module Gitlab
module DependencyLinker
class ComposerJsonLinker < PackageJsonLinker
self.file_type = :composer_json
private
def link_packages
link_packages_at_key("require", &method(:package_url))
link_packages_at_key("require-dev", &method(:package_url))
end
def package_url(name)
"https://packagist.org/packages/#{name}" if name =~ %r{\A#{REPO_REGEX}\z}
end
end
end
end

View file

@ -1,28 +1,31 @@
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
# Link `github: "user/repo"` to https://github.com/user/repo
link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/) do |name|
"https://github.com/#{name}"
end
# 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 `source "https://rubygems.org"` to https://rubygems.org
link_method_call("source", %r{https?://[^'"]+}) { |url| url }
end end
def package_url(name) def link_urls
"https://rubygems.org/gems/#{name}" # Link `github: "user/repo"` to https://github.com/user/repo
link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/, &method(:github_url))
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]}, &:itself)
# Link `source "https://rubygems.org"` to https://rubygems.org
link_method_call('source', URL_REGEX, &:itself)
end
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}"
end
end end
end end
end end

View file

@ -0,0 +1,18 @@
module Gitlab
module DependencyLinker
class GemspecLinker < MethodLinker
self.file_type = :gemspec
private
def link_dependencies
link_method_call('homepage', URL_REGEX, &:itself)
link_method_call('license', &method(:license_url))
link_method_call(%w[name add_dependency add_runtime_dependency add_development_dependency]) do |name|
"https://rubygems.org/gems/#{name}"
end
end
end
end
end

View file

@ -0,0 +1,26 @@
module Gitlab
module DependencyLinker
class GodepsJsonLinker < JsonLinker
NESTED_REPO_REGEX = %r{([^/]+/)+[^/]+?}.freeze
self.file_type = :godeps_json
private
def link_dependencies
link_json('ImportPath') do |path|
case path
when %r{\A(?<repo>gitlab\.com/#{NESTED_REPO_REGEX})\.git/(?<path>.+)\z},
%r{\A(?<repo>git(lab|hub)\.com/#{REPO_REGEX})/(?<path>.+)\z}
"https://#{$~[:repo]}/tree/master/#{$~[:path]}"
when /\Agolang\.org/
"https://godoc.org/#{path}"
else
"https://#{path}"
end
end
end
end
end
end

View file

@ -0,0 +1,44 @@
module Gitlab
module DependencyLinker
class JsonLinker < BaseLinker
def link
return highlighted_text unless json
super
end
private
# Links package names in a JSON key or values.
#
# Example:
# link_json('name')
# # Will link `package` in `"name": "package"`
#
# link_json('name', 'specific_package')
# # Will link `specific_package` in `"name": "specific_package"`
#
# link_json('name', /[^\/]+\/[^\/]+/)
# # Will link `user/repo` in `"name": "user/repo"`, but not `"name": "package"`
#
# link_json('specific_package', '1.0.1', link: :key)
# # Will link `specific_package` in `"specific_package": "1.0.1"`
def link_json(key, value = nil, link: :value, &url_proc)
key = regexp_for_value(key, default: /[^"]+/)
value = regexp_for_value(value, default: /[^"]+/)
if link == :value
value = /(?<name>#{value})/
else
key = /(?<name>#{key})/
end
link_regex(/"#{key}":\s*"#{value}"/, &url_proc)
end
def json
@json ||= JSON.parse(plain_text) rescue nil
end
end
end
end

View file

@ -0,0 +1,39 @@
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_name, value = nil, &url_proc)
method_name = regexp_for_value(method_name)
value = regexp_for_value(value)
regex = %r{
#{method_name} # 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

View file

@ -0,0 +1,44 @@
module Gitlab
module DependencyLinker
class PackageJsonLinker < JsonLinker
self.file_type = :package_json
private
def link_dependencies
link_json('name', json["name"], &method(:package_url))
link_json('license', &method(:license_url))
link_json(%w[homepage url], URL_REGEX, &:itself)
link_packages
end
def link_packages
link_packages_at_key("dependencies", &method(:package_url))
link_packages_at_key("devDependencies", &method(:package_url))
end
def link_packages_at_key(key, &url_proc)
dependencies = json[key]
return unless dependencies
dependencies.each do |name, version|
link_json(name, version, link: :key, &url_proc)
link_json(name) do |value|
case value
when /\A#{URL_REGEX}\z/
value
when /\A#{REPO_REGEX}\z/
github_url(value)
end
end
end
end
def package_url(name)
"https://npmjs.com/package/#{name}"
end
end
end
end

View file

@ -0,0 +1,15 @@
module Gitlab
module DependencyLinker
class PodfileLinker < GemfileLinker
include Cocoapods
self.file_type = :podfile
private
def link_packages
link_method_call('pod', &method(:package_url))
end
end
end
end

View file

@ -0,0 +1,32 @@
module Gitlab
module DependencyLinker
class PodspecJsonLinker < JsonLinker
include Cocoapods
self.file_type = :podspec_json
private
def link_dependencies
link_json('name', json["name"], &method(:package_url))
link_json('license', &method(:license_url))
link_json(%w[homepage git], URL_REGEX, &:itself)
link_packages_at_key("dependencies", &method(:package_url))
json["subspecs"]&.each do |subspec|
link_packages_at_key("dependencies", subspec, &method(:package_url))
end
end
def link_packages_at_key(key, root = json, &url_proc)
dependencies = root[key]
return unless dependencies
dependencies.each do |name, _|
link_regex(/"(?<name>#{Regexp.escape(name)})":\s*\[/, &url_proc)
end
end
end
end
end

View file

@ -0,0 +1,24 @@
module Gitlab
module DependencyLinker
class PodspecLinker < MethodLinker
include Cocoapods
STRING_REGEX = /['"](?<name>[^'"]+)['"]/.freeze
self.file_type = :podspec
private
def link_dependencies
link_method_call('homepage', URL_REGEX, &:itself)
link_regex(%r{(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]}, &:itself)
link_method_call('license', &method(:license_url))
link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))
link_method_call(%w[name dependency], &method(:package_url))
end
end
end
end

View file

@ -0,0 +1,17 @@
module Gitlab
module DependencyLinker
class RequirementsTxtLinker < BaseLinker
self.file_type = :requirements_txt
private
def link_dependencies
link_regex(/^(?<name>(?![a-z+]+:)[^#.-][^ ><=;\[]+)/) do |name|
"https://pypi.python.org/pypi/#{name}"
end
link_regex(%r{^(?<name>https?://[^ ]+)}, &:itself)
end
end
end
end

View file

@ -0,0 +1,74 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::CartfileLinker, lib: true do
describe '.support?' do
it 'supports Cartfile' do
expect(described_class.support?('Cartfile')).to be_truthy
end
it 'supports Cartfile.private' do
expect(described_class.support?('Cartfile.private')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('test.Cartfile')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "Cartfile" }
let(:file_content) do
<<-CONTENT.strip_heredoc
# Require version 2.3.1 or later
github "ReactiveCocoa/ReactiveCocoa" >= 2.3.1
# Require version 1.x
github "Mantle/Mantle" ~> 1.0 # (1.0 or later, but less than 2.0)
# Require exactly version 0.4.1
github "jspahrsummers/libextobjc" == 0.4.1
# Use the latest version
github "jspahrsummers/xcconfigs"
# Use the branch
github "jspahrsummers/xcconfigs" "branch"
# Use a project from GitHub Enterprise
github "https://enterprise.local/ghe/desktop/git-error-translations"
# Use a project from any arbitrary server, on the "development" branch
git "https://enterprise.local/desktop/git-error-translations2.git" "development"
# Use a local project
git "file:///directory/to/project" "branch"
# A binary only framework
binary "https://my.domain.com/release/MyFramework.json" ~> 2.3
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links dependencies' do
expect(subject).to include(link('ReactiveCocoa/ReactiveCocoa', 'https://github.com/ReactiveCocoa/ReactiveCocoa'))
expect(subject).to include(link('Mantle/Mantle', 'https://github.com/Mantle/Mantle'))
expect(subject).to include(link('jspahrsummers/libextobjc', 'https://github.com/jspahrsummers/libextobjc'))
expect(subject).to include(link('jspahrsummers/xcconfigs', 'https://github.com/jspahrsummers/xcconfigs'))
end
it 'links Git repos' do
expect(subject).to include(link('https://enterprise.local/ghe/desktop/git-error-translations', 'https://enterprise.local/ghe/desktop/git-error-translations'))
expect(subject).to include(link('https://enterprise.local/desktop/git-error-translations2.git', 'https://enterprise.local/desktop/git-error-translations2.git'))
end
it 'links binary-only frameworks' do
expect(subject).to include(link('https://my.domain.com/release/MyFramework.json', 'https://my.domain.com/release/MyFramework.json'))
end
end
end

View file

@ -0,0 +1,82 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::ComposerJsonLinker, lib: true do
describe '.support?' do
it 'supports composer.json' do
expect(described_class.support?('composer.json')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('composer.json.example')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "composer.json" }
let(:file_content) do
<<-CONTENT.strip_heredoc
{
"name": "laravel/laravel",
"homepage": "https://laravel.com/",
"description": "The Laravel Framework.",
"keywords": ["framework", "laravel"],
"license": "MIT",
"type": "project",
"repositories": [
{
"type": "git",
"url": "https://github.com/laravel/laravel.git"
}
],
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.2.*"
},
"require-dev": {
"fzaninotto/faker": "~1.4",
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~4.0",
"symfony/css-selector": "2.8.*|3.0.*",
"symfony/dom-crawler": "2.8.*|3.0.*"
}
}
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links the module name' do
expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
end
it 'links the homepage' do
expect(subject).to include(link('https://laravel.com/', 'https://laravel.com/'))
end
it 'links the repository URL' do
expect(subject).to include(link('https://github.com/laravel/laravel.git', 'https://github.com/laravel/laravel.git'))
end
it 'links the license' do
expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
end
it 'links dependencies' do
expect(subject).to include(link('laravel/framework', 'https://packagist.org/packages/laravel/framework'))
expect(subject).to include(link('fzaninotto/faker', 'https://packagist.org/packages/fzaninotto/faker'))
expect(subject).to include(link('mockery/mockery', 'https://packagist.org/packages/mockery/mockery'))
expect(subject).to include(link('phpunit/phpunit', 'https://packagist.org/packages/phpunit/phpunit'))
expect(subject).to include(link('symfony/css-selector', 'https://packagist.org/packages/symfony/css-selector'))
expect(subject).to include(link('symfony/dom-crawler', 'https://packagist.org/packages/symfony/dom-crawler'))
end
it 'does not link core dependencies' do
expect(subject).not_to include(link('php', 'https://packagist.org/packages/php'))
end
end
end

View file

@ -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

View file

@ -0,0 +1,66 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::GemspecLinker, lib: true do
describe '.support?' do
it 'supports *.gemspec' do
expect(described_class.support?('gitlab_git.gemspec')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('.gemspec.example')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "gitlab_git.gemspec" }
let(:file_content) do
<<-CONTENT.strip_heredoc
Gem::Specification.new do |s|
s.name = 'gitlab_git'
s.version = `cat VERSION`
s.date = Time.now.strftime('%Y-%m-%d')
s.summary = "Gitlab::Git library"
s.description = "GitLab wrapper around git objects"
s.authors = ["Dmitriy Zaporozhets"]
s.email = 'dmitriy.zaporozhets@gmail.com'
s.license = 'MIT'
s.files = `git ls-files lib/`.split('\n') << 'VERSION'
s.homepage = 'https://gitlab.com/gitlab-org/gitlab_git'
s.add_dependency('github-linguist', '~> 4.7.0')
s.add_dependency('activesupport', '~> 4.0')
s.add_dependency('rugged', '~> 0.24.0')
s.add_runtime_dependency('charlock_holmes', '~> 0.7.3')
s.add_development_dependency('listen', '~> 3.0.6')
end
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links the gem name' do
expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
end
it 'links the license' do
expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
end
it 'links the homepage' do
expect(subject).to include(link('https://gitlab.com/gitlab-org/gitlab_git', 'https://gitlab.com/gitlab-org/gitlab_git'))
end
it 'links dependencies' do
expect(subject).to include(link('github-linguist', 'https://rubygems.org/gems/github-linguist'))
expect(subject).to include(link('activesupport', 'https://rubygems.org/gems/activesupport'))
expect(subject).to include(link('rugged', 'https://rubygems.org/gems/rugged'))
expect(subject).to include(link('charlock_holmes', 'https://rubygems.org/gems/charlock_holmes'))
expect(subject).to include(link('listen', 'https://rubygems.org/gems/listen'))
end
end
end

View file

@ -0,0 +1,84 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::GodepsJsonLinker, lib: true do
describe '.support?' do
it 'supports Godeps.json' do
expect(described_class.support?('Godeps.json')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('Godeps.json.example')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "Godeps.json" }
let(:file_content) do
<<-CONTENT.strip_heredoc
{
"ImportPath": "gitlab.com/gitlab-org/gitlab-pages",
"GoVersion": "go1.5",
"Packages": [
"./..."
],
"Deps": [
{
"ImportPath": "github.com/kardianos/osext",
"Rev": "efacde03154693404c65e7aa7d461ac9014acd0c"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
},
{
"ImportPath": "github.com/stretchr/testify/require",
"Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
},
{
"ImportPath": "gitlab.com/group/project/path",
"Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
},
{
"ImportPath": "gitlab.com/group/subgroup/project.git/path",
"Rev": "1297dc01ed0a819ff634c89707081a4df43baf6b"
},
{
"ImportPath": "golang.org/x/crypto/ssh/terminal",
"Rev": "1351f936d976c60a0a48d728281922cf63eafb8d"
},
{
"ImportPath": "golang.org/x/net/http2",
"Rev": "b4e17d61b15679caf2335da776c614169a1b4643"
}
]
}
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links the package name' do
expect(subject).to include(link('gitlab.com/gitlab-org/gitlab-pages', 'https://gitlab.com/gitlab-org/gitlab-pages'))
end
it 'links GitHub repos' do
expect(subject).to include(link('github.com/kardianos/osext', 'https://github.com/kardianos/osext'))
expect(subject).to include(link('github.com/stretchr/testify/assert', 'https://github.com/stretchr/testify/tree/master/assert'))
expect(subject).to include(link('github.com/stretchr/testify/require', 'https://github.com/stretchr/testify/tree/master/require'))
end
it 'links GitLab projects' do
expect(subject).to include(link('gitlab.com/group/project/path', 'https://gitlab.com/group/project/tree/master/path'))
expect(subject).to include(link('gitlab.com/group/subgroup/project.git/path', 'https://gitlab.com/group/subgroup/project/tree/master/path'))
end
it 'links Golang packages' do
expect(subject).to include(link('golang.org/x/net/http2', 'https://godoc.org/golang.org/x/net/http2'))
end
end
end

View file

@ -0,0 +1,85 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::PackageJsonLinker, lib: true do
describe '.support?' do
it 'supports package.json' do
expect(described_class.support?('package.json')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('package.json.example')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "package.json" }
let(:file_content) do
<<-CONTENT.strip_heredoc
{
"name": "module-name",
"version": "10.3.1",
"repository": {
"type": "git",
"url": "https://github.com/vuejs/vue.git"
},
"homepage": "https://github.com/vuejs/vue#readme",
"dependencies": {
"primus": "*",
"async": "~0.8.0",
"express": "4.2.x",
"bigpipe": "bigpipe/pagelet",
"plates": "https://github.com/flatiron/plates/tarball/master"
},
"devDependencies": {
"vows": "^0.7.0",
"assume": "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0",
"pre-commit": "*"
},
"license": "MIT"
}
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links the module name' do
expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name'))
end
it 'links the homepage' do
expect(subject).to include(link('https://github.com/vuejs/vue#readme', 'https://github.com/vuejs/vue#readme'))
end
it 'links the repository URL' do
expect(subject).to include(link('https://github.com/vuejs/vue.git', 'https://github.com/vuejs/vue.git'))
end
it 'links the license' do
expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
end
it 'links dependencies' do
expect(subject).to include(link('primus', 'https://npmjs.com/package/primus'))
expect(subject).to include(link('async', 'https://npmjs.com/package/async'))
expect(subject).to include(link('express', 'https://npmjs.com/package/express'))
expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe'))
expect(subject).to include(link('plates', 'https://npmjs.com/package/plates'))
expect(subject).to include(link('vows', 'https://npmjs.com/package/vows'))
expect(subject).to include(link('assume', 'https://npmjs.com/package/assume'))
expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit'))
end
it 'links GitHub repos' do
expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet'))
end
it 'links Git repos' do
expect(subject).to include(link('https://github.com/flatiron/plates/tarball/master', 'https://github.com/flatiron/plates/tarball/master'))
end
end
end

View file

@ -0,0 +1,53 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::PodfileLinker, lib: true do
describe '.support?' do
it 'supports Podfile' do
expect(described_class.support?('Podfile')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('Podfile.lock')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "Podfile" }
let(:file_content) do
<<-CONTENT.strip_heredoc
source 'https://github.com/artsy/Specs.git'
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
inhibit_all_warnings!
target 'Artsy' do
pod 'AFNetworking', "~> 2.5"
pod 'Interstellar/Core', git: 'https://github.com/ashfurrow/Interstellar.git', branch: 'observable-unsubscribe'
end
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links sources' do
expect(subject).to include(link('https://github.com/artsy/Specs.git', 'https://github.com/artsy/Specs.git'))
expect(subject).to include(link('https://github.com/CocoaPods/Specs.git', 'https://github.com/CocoaPods/Specs.git'))
end
it 'links packages' do
expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar'))
end
it 'links Git repos' do
expect(subject).to include(link('https://github.com/ashfurrow/Interstellar.git', 'https://github.com/ashfurrow/Interstellar.git'))
end
end
end

View file

@ -0,0 +1,96 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::PodspecJsonLinker, lib: true do
describe '.support?' do
it 'supports *.podspec.json' do
expect(described_class.support?('Reachability.podspec.json')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('.podspec.json.example')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "AFNetworking.podspec.json" }
let(:file_content) do
<<-CONTENT.strip_heredoc
{
"name": "AFNetworking",
"version": "2.0.0",
"license": "MIT",
"summary": "A delightful iOS and OS X networking framework.",
"homepage": "https://github.com/AFNetworking/AFNetworking",
"authors": {
"Mattt Thompson": "m@mattt.me"
},
"source": {
"git": "https://github.com/AFNetworking/AFNetworking.git",
"tag": "2.0.0",
"submodules": true
},
"requires_arc": true,
"platforms": {
"ios": "6.0",
"osx": "10.8"
},
"public_header_files": "AFNetworking/*.h",
"subspecs": [
{
"name": "NSURLConnection",
"dependencies": {
"AFNetworking/Serialization": [
],
"AFNetworking/Reachability": [
],
"AFNetworking/Security": [
]
},
"source_files": [
"AFNetworking/AFURLConnectionOperation.{h,m}",
"AFNetworking/AFHTTPRequestOperation.{h,m}",
"AFNetworking/AFHTTPRequestOperationManager.{h,m}"
]
}
]
}
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links the gem name' do
expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
end
it 'links the license' do
expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
end
it 'links the homepage' do
expect(subject).to include(link('https://github.com/AFNetworking/AFNetworking', 'https://github.com/AFNetworking/AFNetworking'))
end
it 'links the source URL' do
expect(subject).to include(link('https://github.com/AFNetworking/AFNetworking.git', 'https://github.com/AFNetworking/AFNetworking.git'))
end
it 'links dependencies' do
expect(subject).to include(link('AFNetworking/Serialization', 'https://cocoapods.org/pods/AFNetworking'))
expect(subject).to include(link('AFNetworking/Reachability', 'https://cocoapods.org/pods/AFNetworking'))
expect(subject).to include(link('AFNetworking/Security', 'https://cocoapods.org/pods/AFNetworking'))
end
it 'does not link subspec names' do
expect(subject).not_to include(link('NSURLConnection', 'https://cocoapods.org/pods/NSURLConnection'))
end
end
end

View file

@ -0,0 +1,69 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::PodspecLinker, lib: true do
describe '.support?' do
it 'supports *.podspec' do
expect(described_class.support?('Reachability.podspec')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('.podspec.example')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "Reachability.podspec" }
let(:file_content) do
<<-CONTENT.strip_heredoc
Pod::Spec.new do |spec|
spec.name = 'Reachability'
spec.version = '3.1.0'
spec.license = { :type => 'GPL-3.0' }
spec.license = "MIT"
spec.license = { type: 'Apache-2.0' }
spec.homepage = 'https://github.com/tonymillion/Reachability'
spec.authors = { 'Tony Million' => 'tonymillion@gmail.com' }
spec.summary = 'ARC and GCD Compatible Reachability Class for iOS and OS X.'
spec.source = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.1.0' }
spec.source_files = 'Reachability.{h,m}'
spec.framework = 'SystemConfiguration'
spec.dependency 'AFNetworking', '~> 1.0'
spec.dependency 'RestKit/CoreData', '~> 0.20.0'
spec.ios.dependency 'MBProgressHUD', '~> 0.5'
end
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links the gem name' do
expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
end
it 'links the license' do
expect(subject).to include(link('GPL-3.0', 'http://choosealicense.com/licenses/gpl-3.0/'))
expect(subject).to include(link('MIT', 'http://choosealicense.com/licenses/mit/'))
expect(subject).to include(link('Apache-2.0', 'http://choosealicense.com/licenses/apache-2.0/'))
end
it 'links the homepage' do
expect(subject).to include(link('https://github.com/tonymillion/Reachability', 'https://github.com/tonymillion/Reachability'))
end
it 'links the source URL' do
expect(subject).to include(link('https://github.com/tonymillion/Reachability.git', 'https://github.com/tonymillion/Reachability.git'))
end
it 'links dependencies' do
expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
expect(subject).to include(link('RestKit/CoreData', 'https://cocoapods.org/pods/RestKit'))
expect(subject).to include(link('MBProgressHUD', 'https://cocoapods.org/pods/MBProgressHUD'))
end
end
end

View file

@ -0,0 +1,87 @@
require 'rails_helper'
describe Gitlab::DependencyLinker::RequirementsTxtLinker, lib: true do
describe '.support?' do
it 'supports requirements.txt' do
expect(described_class.support?('requirements.txt')).to be_truthy
end
it 'supports doc-requirements.txt' do
expect(described_class.support?('doc-requirements.txt')).to be_truthy
end
it 'does not support other files' do
expect(described_class.support?('requirements')).to be_falsey
end
end
describe '#link' do
let(:file_name) { "requirements.txt" }
let(:file_content) do
<<-CONTENT.strip_heredoc
#
####### example-requirements.txt #######
#
###### Requirements without Version Specifiers ######
nose
nose-cov
beautifulsoup4
#
###### Requirements with Version Specifiers ######
# See https://www.python.org/dev/peps/pep-0440/#version-specifiers
docopt == 0.6.1 # Version Matching. Must be version 0.6.1
keyring >= 4.1.1 # Minimum version 4.1.1
coverage != 3.5 # Version Exclusion. Anything except version 3.5
Mopidy-Dirble ~= 1.1 # Compatible release. Same as >= 1.1, == 1.*
#
###### Refer to other requirements files ######
-r other-requirements.txt
#
#
###### A particular file ######
./downloads/numpy-1.9.2-cp34-none-win32.whl
http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl
#
###### Additional Requirements without Version Specifiers ######
# Same as 1st section, just here to show that you can put things in any order.
rejected
green
#
Jinja2>=2.3
Pygments>=1.2
Sphinx>=1.3
docutils>=0.7
markupsafe
CONTENT
end
subject { Gitlab::Highlight.highlight(file_name, file_content) }
def link(name, url)
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
end
it 'links dependencies' do
expect(subject).to include(link('nose', 'https://pypi.python.org/pypi/nose'))
expect(subject).to include(link('nose-cov', 'https://pypi.python.org/pypi/nose-cov'))
expect(subject).to include(link('beautifulsoup4', 'https://pypi.python.org/pypi/beautifulsoup4'))
expect(subject).to include(link('docopt', 'https://pypi.python.org/pypi/docopt'))
expect(subject).to include(link('keyring', 'https://pypi.python.org/pypi/keyring'))
expect(subject).to include(link('coverage', 'https://pypi.python.org/pypi/coverage'))
expect(subject).to include(link('Mopidy-Dirble', 'https://pypi.python.org/pypi/Mopidy-Dirble'))
expect(subject).to include(link('rejected', 'https://pypi.python.org/pypi/rejected'))
expect(subject).to include(link('green', 'https://pypi.python.org/pypi/green'))
expect(subject).to include(link('Jinja2', 'https://pypi.python.org/pypi/Jinja2'))
expect(subject).to include(link('Pygments', 'https://pypi.python.org/pypi/Pygments'))
expect(subject).to include(link('Sphinx', 'https://pypi.python.org/pypi/Sphinx'))
expect(subject).to include(link('docutils', 'https://pypi.python.org/pypi/docutils'))
expect(subject).to include(link('markupsafe', 'https://pypi.python.org/pypi/markupsafe'))
end
it 'links URLs' do
expect(subject).to include(link('http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl', 'http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl'))
end
end
end

View file

@ -9,5 +9,77 @@ describe Gitlab::DependencyLinker, lib: true do
described_class.link(blob_name, nil, nil) described_class.link(blob_name, nil, nil)
end end
it 'links using GemspecLinker' do
blob_name = 'gitlab_git.gemspec'
expect(described_class::GemspecLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using PackageJsonLinker' do
blob_name = 'package.json'
expect(described_class::PackageJsonLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using ComposerJsonLinker' do
blob_name = 'composer.json'
expect(described_class::ComposerJsonLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using PodfileLinker' do
blob_name = 'Podfile'
expect(described_class::PodfileLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using PodspecLinker' do
blob_name = 'Reachability.podspec'
expect(described_class::PodspecLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using PodspecJsonLinker' do
blob_name = 'AFNetworking.podspec.json'
expect(described_class::PodspecJsonLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using CartfileLinker' do
blob_name = 'Cartfile'
expect(described_class::CartfileLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using GodepsJsonLinker' do
blob_name = 'Godeps.json'
expect(described_class::GodepsJsonLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
it 'links using RequirementsTxtLinker' do
blob_name = 'requirements.txt'
expect(described_class::RequirementsTxtLinker).to receive(:link)
described_class.link(blob_name, nil, nil)
end
end end
end end

View file

@ -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