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'andpod "Name"link the pod name to its page on cocoapods.org. A subspec such asSDWebImage/WebPlinks to its parent pod,SDWebImage, because that is where cocoapods.org hosts it.- A pod pinned to a git source, with either
:git =>or thegit:shorthand, links to that source instead of its registry entry. This is whatBaseLinker#external_urldoes on the backend. - Strings that are themselves URLs, such as the
sourceline 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;.
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, reusingcreateLinkandgenerateHLJSOpenTagfromdependency_linker_util.js.unescapecomes fromlodash-es, as it does inpackage_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
- In a project, add a
Podfilewith 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- Open the file in the repository view.
AFNetworkingshould link tohttps://cocoapods.org/pods/AFNetworkingandSDWebImage/WebPtohttps://cocoapods.org/pods/SDWebImage.SlackTextViewControllerandObjectionshould link to their git repositories, not to cocoapods.org, and the two git URLs should be links in their own right. ThesourceURL should be a link.MyAppand the version strings should not be.- 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=2rather 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.jspodfile_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.
-
podfileis removed fromLEGACY_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
createLinkhelper, so they carrytarget="_blank"andrel="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 =>andgit: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
:gitcase. - Regexes are built once at module scope, as the gemfile, gemspec and go_sum linkers do.
-
Changelog: changedtrailer 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.