summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@selenight.nl>2017-05-16 15:27:50 -0500
committerDouwe Maan <douwe@selenight.nl>2017-05-23 15:35:17 -0500
commit83747783e25b2d8b60efa2cb1aa9d6bd823fcdfe (patch)
treef9a41ffe3708b3ffda738b964b8932b3a213261e
parent02ad8c0c68b575ca4957e0a478714ef2144a9b8a (diff)
downloadgitlab-ce-83747783e25b2d8b60efa2cb1aa9d6bd823fcdfe.tar.gz
Autolink package names in package.json
-rw-r--r--lib/gitlab/dependency_linker.rb1
-rw-r--r--lib/gitlab/dependency_linker/base_linker.rb4
-rw-r--r--lib/gitlab/dependency_linker/json_linker.rb63
-rw-r--r--lib/gitlab/dependency_linker/package_json_linker.rb44
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb85
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb8
6 files changed, 205 insertions, 0 deletions
diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb
index 14fc506fbb7..88f4ebedb9f 100644
--- a/lib/gitlab/dependency_linker.rb
+++ b/lib/gitlab/dependency_linker.rb
@@ -3,6 +3,7 @@ module Gitlab
LINKERS = [
GemfileLinker,
GemspecLinker,
+ PackageJsonLinker,
].freeze
def self.linker(blob_name)
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index 019e670081e..15fa9f40116 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -2,6 +2,7 @@ module Gitlab
module DependencyLinker
class BaseLinker
URL_REGEX = %r{https?://[^'"]+}.freeze
+ REPO_REGEX = %r{[^/'"]+/[^/'"]+}.freeze
class_attribute :file_type
@@ -36,6 +37,9 @@ module Gitlab
Licensee::License.find(name)&.url
end
+ def github_url(name)
+ "https://github.com/#{name}"
+ end
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>}
diff --git a/lib/gitlab/dependency_linker/json_linker.rb b/lib/gitlab/dependency_linker/json_linker.rb
new file mode 100644
index 00000000000..021347b6429
--- /dev/null
+++ b/lib/gitlab/dependency_linker/json_linker.rb
@@ -0,0 +1,63 @@
+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 =
+ case key
+ when Array
+ Regexp.union(key.map { |name| Regexp.escape(name) })
+ when String
+ Regexp.escape(key)
+ when nil
+ '[^"]+'
+ else
+ key
+ end
+
+ value =
+ case value
+ when String
+ Regexp.escape(value)
+ when nil
+ '[^"]+'
+ else
+ value
+ end
+
+ 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
diff --git a/lib/gitlab/dependency_linker/package_json_linker.rb b/lib/gitlab/dependency_linker/package_json_linker.rb
new file mode 100644
index 00000000000..330c95f0880
--- /dev/null
+++ b/lib/gitlab/dependency_linker/package_json_linker.rb
@@ -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
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
new file mode 100644
index 00000000000..b5f2c387d6f
--- /dev/null
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 7ba987026a4..53960b0a662 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -17,5 +17,13 @@ describe Gitlab::DependencyLinker, lib: true do
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
end
end