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 asSDWebImage/WebPlinks to its parent pod,SDWebImage, which is where cocoapods.org hosts it. The call is matched the wayMethodLinker#method_call_regexmatches it, sos.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://orhttps://URLs, such ass.homepageand the:gitsource, 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 ' or " and ampersands as &. 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, 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 .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
- In a project, add a file named
Reachability.podspecwith 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- Open the file in the repository view.
AFNetworking,SDWebImage/WebPandMasonryshould each link to cocoapods.org, with the subspec pointing atSDWebImage.MITshould link tohttps://choosealicense.com/licenses/mit/. Both GitHub URLs should link to themselves.Reachabilityand3.2should not be links.- Optional, for the cases the review raised. Replace the licence with
s.license = { :type => 'GPL-3.0' }and then withs.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=2rather 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.jspodspec_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.
-
podspecis 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. - Dependency calls are matched the way
MethodLinker#method_call_regexmatches 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: changedtrailer 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.