From 83747783e25b2d8b60efa2cb1aa9d6bd823fcdfe Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 16 May 2017 15:27:50 -0500 Subject: [PATCH] Autolink package names in package.json --- lib/gitlab/dependency_linker.rb | 1 + lib/gitlab/dependency_linker/base_linker.rb | 4 + lib/gitlab/dependency_linker/json_linker.rb | 63 ++++++++++++++ .../dependency_linker/package_json_linker.rb | 44 ++++++++++ .../package_json_linker_spec.rb | 85 +++++++++++++++++++ spec/lib/gitlab/dependency_linker_spec.rb | 8 ++ 6 files changed, 205 insertions(+) create mode 100644 lib/gitlab/dependency_linker/json_linker.rb create mode 100644 lib/gitlab/dependency_linker/package_json_linker.rb create mode 100644 spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb 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) %{#{ERB::Util.html_escape_once(name)}} 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 = "(?#{value})" + else + key = "(?#{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) + %{#{name}} + 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