Link podspec dependencies and URLs in the source viewer

What does this MR do and why?

A .podspec is highlighted by the backend today. podspec is listed in LEGACY_FILE_TYPES, so the file is served through the legacy viewer, where the server-side DependencyLinker runs. The dependencies, licence and URLs are linked on master; nothing is broken, and until the file type leaves that list a frontend linker's output is never shown.

This MR moves the file type over to the frontend source viewer without losing those links. It adds a Highlight.js dependency linker for podspec, registers it in link_dependencies.js next to the existing gemfile, podspec_json, composer_json and go_sum linkers, and removes podspec from LEGACY_FILE_TYPES.

It links the same three things the Ruby linker does:

  • s.dependency 'Name' links the pod name to its page on cocoapods.org. A subspec such as SDWebImage/WebP links to its parent pod, SDWebImage, which is where cocoapods.org hosts it. The call is matched the way MethodLinker#method_call_regex matches it, so s.dependency('Name') and a call separated by tabs or extra spaces are linked too.
  • The licence links to choosealicense.com, both as a plain value (s.license = 'MIT') and as the type key of a hash, in either the { :type => 'MIT' } or the { type: 'MIT' } form.
  • String values that are http:// or https:// URLs, such as s.homepage and the :git source, become links to that URL.

Other strings in the file, such as s.name and version constraints, are left as plain highlighted text.

Two details about how the markup is matched, since they drove the shape of the regexes. The patterns are written against the output of wrapChildNodes, which rebuilds Highlight.js's result and escapes it with lodash, so quotes arrive as &#39; or &quot; and ampersands as &amp;. The URL match stops at whitespace and at <, because an interpolated URL such as "https://github.com/#{org}/repo.git" carries raw <span class="hljs-subst"> markup for the interpolated part. Such a URL is now left unlinked rather than linked with that markup inside the link text. URLs are unescaped before the link is built, because createLink escapes what it is given.

One deliberate difference from Ruby. Gitlab::DependencyLinker::BaseLinker#license_url resolves the identifier through Licensee::License.find(name)&.url and leaves an unknown licence as plain text. There is no equivalent in the frontend, so the value is lowercased into the choosealicense.com URL instead. The match is restricted to [\w.+-]+, so prose such as MIT License is not touched, but an identifier outside Licensee's set is still linked where Ruby would not link it. Happy to narrow this to a fixed list if you would rather have exact parity. Ruby also emits http://choosealicense.com/...; this uses https.

References

  • Resolves #367649
  • Sibling changes for the other Cocoa manifests: !255262 (merged) (Podfile) and !255265 (merged) (Cartfile)
  • Other file types moving to the new viewer: !255267 (merged) (requirements.txt) and !255271 (merged) (go.mod)
  • The linker follows the shape of gemfile_linker.js, reusing createLink and generateHLJSOpenTag from dependency_linker_util.js. unescape comes from lodash-es, as it does in package_json_linker.js.
  • Each of those merge requests removes an entry from LEGACY_FILE_TYPES. Removing enough entries brings the array under the line limit and Prettier collapses it, so expect a one-line conflict on some of the later merges.

Differences

Before: A .podspec was rendered by the legacy backend viewer, with links produced by Gitlab::DependencyLinker::PodspecLinker.

After: A .podspec is rendered by the frontend source viewer, with the same links produced client side. AFNetworking links to https://cocoapods.org/pods/AFNetworking and SDWebImage/WebP links to https://cocoapods.org/pods/SDWebImage, both opening in a new tab, whether the call uses parentheses, a space or a tab. MIT links to https://choosealicense.com/licenses/mit/, in the plain and both hash forms. The homepage and :git source URLs are clickable links to themselves, query string included. An interpolated URL is left unlinked. s.name and version strings are unchanged.

How to set up and validate locally

  1. In a project, add a file named Reachability.podspec with the following content:
Pod::Spec.new do |s|
  s.name         = 'Reachability'
  s.version      = '3.2'
  s.license      = 'MIT'
  s.homepage     = 'https://github.com/tonymillion/Reachability'
  s.source       = { :git => 'https://github.com/tonymillion/Reachability.git', :tag => 'v3.2' }
  s.dependency 'AFNetworking', '~> 3.0'
  s.dependency("SDWebImage/WebP")
  s.dependency	'Masonry'
end
  1. Open the file in the repository view.
  2. AFNetworking, SDWebImage/WebP and Masonry should each link to cocoapods.org, with the subspec pointing at SDWebImage. MIT should link to https://choosealicense.com/licenses/mit/. Both GitHub URLs should link to themselves. Reachability and 3.2 should not be links.
  3. Optional, for the cases the review raised. Replace the licence with s.license = { :type => 'GPL-3.0' } and then with s.license = { type: 'Apache-2.0' }; both should link. Set the homepage to 'https://example.com/a?x=1&y=2' and confirm the link keeps &y=2 rather than stopping at ?x=1. Set the source to "https://github.com/#{s.name}/repo.git" and confirm it is not linked at all, rather than linked with markup inside the link text.

Specs:

yarn jest spec/frontend/vue_shared/components/source_viewer/plugins/utils/podspec_linker_spec.js spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js spec/frontend/repository/components/blob_content_viewer_spec.js

podspec_linker_spec.js covers single and double quotes, subspec linking to the parent pod, parenthesised and tab-separated calls, the licence in all three forms, URL strings for the homepage and git source, a URL with an escaped query string, an interpolated URL being left alone, and that unrelated strings are untouched. link_dependencies_spec.js asserts the podspec file type is routed to the new linker. blob_content_viewer_spec.js covers the LEGACY_FILE_TYPES change.

MR acceptance checklist

  • The change is limited to the frontend source viewer; no backend, database, or API changes.
  • podspec is removed from LEGACY_FILE_TYPES, so the file is actually served by the frontend viewer and the linker's output is shown.
  • Links are built with the shared createLink helper, so they carry target="_blank" and rel="nofollow noreferrer noopener" like every other dependency linker.
  • Dependency calls are matched the way MethodLinker#method_call_regex matches them, so parentheses, tabs and extra spaces are covered.
  • The licence is linked in the plain form and in both hash forms, matching Gitlab::DependencyLinker::PodspecLinker.
  • A URL keeps its full query string and is unescaped before the link is built, so an escaped separator does not come back doubly escaped.
  • An interpolated URL is left unlinked rather than linked with raw markup inside the link text.
  • Only dependency names, the licence and URL strings are linked; nothing else in the file is rewritten.
  • Unit specs added for the linker and for its registration in link_dependencies.js; the source viewer plugin suite and the blob content viewer suite pass locally.
  • Changelog: changed trailer is on the commit, since this moves an existing behaviour to the frontend viewer rather than adding a new one.
  • No documentation change required; the links a reader sees are unchanged.
  • No feature flag needed; the change is small and isolated to one file type.
Edited by Jeston Singh

Merge request reports

Loading
Loading