Link Podfile dependencies to cocoapods.org in the source viewer

What does this MR do and why?

A Podfile is highlighted by the backend today. podfile is listed in LEGACY_FILE_TYPES, so the file is served through the legacy viewer, where the server-side DependencyLinker runs. Pod names 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 podfile, registers it in link_dependencies.js alongside the existing gemfile, podspec_json, composer_json and go_sum linkers, and removes podfile from LEGACY_FILE_TYPES.

It links the same things the Ruby linker does:

  • pod 'Name' and pod "Name" link the pod name to its page on cocoapods.org. A subspec such as SDWebImage/WebP links to its parent pod, SDWebImage, because that is where cocoapods.org hosts it.
  • A pod pinned to a git source, with either :git => or the git: shorthand, links to that source instead of its registry entry. This is what BaseLinker#external_url does on the backend.
  • Strings that are themselves URLs, such as the source line and a standalone git ref, link to the URL.

Strings that are neither, such as target names and version constraints, are left alone.

Three details about how the markup is matched, since they drove the shape of this change:

The git source is matched against the line's plain text, with tags stripped and entities unescaped, using the same pattern as Gitlab::DependencyLinker::GemfileLinker::GIT_REGEX. Highlight.js splits :git into several nested spans, so the characters never appear adjacent in the marked-up line and no pattern written against that markup can match it. Working from plain text also avoids the nested quantifiers the earlier pattern needed, which made long lines expensive to reject.

The URL match stops at whitespace and at <. An interpolated URL such as source "https://#{host}/specs.git" carries raw <span class="hljs-subst"> markup for the interpolated part, and 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 and the value arrives already escaped, so an ampersand in a query string came back as &amp;amp;.

Known gap, for a follow-up rather than this MR: pod 'X', :github => 'owner/repo' still links to cocoapods.org. The Ruby linker sends it to github.com through GemfileLinker::GITHUB_REGEX and external_url. It is the same shape of fix as the git source and would sit on the same plain text. Happy to fold it in here instead if you would rather not leave it.

References

  • Resolves #367648
  • Sibling changes for the other Cocoa manifests: !255264 (merged) (podspec) 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 Podfile was rendered by the legacy backend viewer, with links produced by Gitlab::DependencyLinker::PodfileLinker.

After: A Podfile 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. A pod pinned with :git => or git: links to that repository rather than to cocoapods.org, and the URL itself is linked too. The source URL is a link, query string included. An interpolated URL is left unlinked. target names and version strings are unchanged.

How to set up and validate locally

  1. In a project, add a Podfile with the following content:
source 'https://cdn.cocoapods.org/'

target 'MyApp' do
  pod 'AFNetworking', '~> 3.0'
  pod "SDWebImage/WebP", '~> 5.0'
  pod 'SlackTextViewController', :git => 'https://github.com/gitterHQ/SlackTextViewController'
  pod 'Objection', git: 'https://github.com/atomicobject/objection.git'
end
  1. Open the file in the repository view.
  2. AFNetworking should link to https://cocoapods.org/pods/AFNetworking and SDWebImage/WebP to https://cocoapods.org/pods/SDWebImage. SlackTextViewController and Objection should link to their git repositories, not to cocoapods.org, and the two git URLs should be links in their own right. The source URL should be a link. MyApp and the version strings should not be.
  3. Optional, for the cases the review raised. Change the source to 'https://example.com/specs.git?a=1&b=2' and confirm the link keeps &b=2 rather than stopping at ?a=1, and that the rendered href is not doubly escaped. Change it to "https://#{host}/specs.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/podfile_linker_spec.js spec/frontend/vue_shared/components/source_viewer/plugins/link_dependencies_spec.js spec/frontend/repository/components/blob_content_viewer_spec.js

podfile_linker_spec.js builds its input by running Highlight.js with the wrap_child_nodes plugin, the way the source viewer builds it, so the examples are written in Podfile source rather than in hand-written markup. It covers both quote styles, subspecs linking to the parent pod, the source URL, a pod pinned with :git => and one with git:, a git source on a different line not being picked up, an escaped query string, an interpolated URL being left alone, and unrelated strings being untouched. link_dependencies_spec.js asserts the podfile 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.
  • podfile 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.
  • The git source is matched against the line's plain text with the same pattern as GemfileLinker::GIT_REGEX, so both :git => and git: work regardless of how Highlight.js splits the line into spans.
  • That pattern has no nested quantifiers, so a long line is rejected in linear time.
  • 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.
  • The linker spec drives real Highlight.js output rather than hand-written markup, which is what previously hid the :git case.
  • Regexes are built once at module scope, as the gemfile, gemspec and go_sum linkers do.
  • Changelog: changed trailer is on the first commit. Squash is off, so it lands as written.
  • 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