[clang-doc] Add --repository-line-prefix argument (#131280)

This PR adds a new command-line option that allows users to specify the
prefix used for line-based anchors in repository URLs. Different
repository interfaces use different formats for line anchors (GitHub
uses `#L123`, googlesource uses `#123`, etc.). This option enables users
to customize the line prefix to match their repository platform without
requiring hard-coded values for each service.

Fixes #59814
This commit is contained in:
Mohamed Emad 2025-03-29 01:45:09 +02:00 committed by GitHub
parent c8a70f4c6e
commit 515d1ae679
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 85 additions and 49 deletions

View File

@ -490,15 +490,15 @@ genReferencesBlock(const std::vector<Reference> &References,
}
return Out;
}
static std::unique_ptr<TagNode> writeSourceFileRef(const ClangDocContext &CDCtx,
const Location &L) {
static std::unique_ptr<TagNode>
writeFileDefinition(const Location &L,
std::optional<StringRef> RepositoryUrl = std::nullopt) {
if (!L.IsFileInRootDir && !RepositoryUrl)
if (!L.IsFileInRootDir && !CDCtx.RepositoryUrl)
return std::make_unique<TagNode>(
HTMLTag::TAG_P, "Defined at line " + std::to_string(L.LineNumber) +
" of file " + L.Filename);
SmallString<128> FileURL(RepositoryUrl.value_or(""));
SmallString<128> FileURL(CDCtx.RepositoryUrl.value_or(""));
llvm::sys::path::append(
FileURL, llvm::sys::path::Style::posix,
// If we're on Windows, the file name will be in the wrong format, and
@ -516,11 +516,9 @@ writeFileDefinition(const Location &L,
std::make_unique<TagNode>(HTMLTag::TAG_A, std::to_string(L.LineNumber));
// The links to a specific line in the source code use the github /
// googlesource notation so it won't work for all hosting pages.
// FIXME: we probably should have a configuration setting for line number
// rendering in the HTML. For example, GitHub uses #L22, while googlesource
// uses #22 for line numbers.
LocNumberNode->Attributes.emplace_back(
"href", (FileURL + "#" + std::to_string(L.LineNumber)).str());
"href", formatv("{0}#{1}{2}", FileURL,
CDCtx.RepositoryLinePrefix.value_or(""), L.LineNumber));
Node->Children.emplace_back(std::move(LocNumberNode));
Node->Children.emplace_back(std::make_unique<TextNode>(" of file "));
auto LocFileNode = std::make_unique<TagNode>(
@ -530,6 +528,13 @@ writeFileDefinition(const Location &L,
return Node;
}
static void maybeWriteSourceFileRef(std::vector<std::unique_ptr<TagNode>> &Out,
const ClangDocContext &CDCtx,
const std::optional<Location> &DefLoc) {
if (DefLoc)
Out.emplace_back(writeSourceFileRef(CDCtx, *DefLoc));
}
static std::vector<std::unique_ptr<TagNode>>
genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList);
@ -749,13 +754,7 @@ genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
Out.emplace_back(std::move(Table));
if (I.DefLoc) {
if (!CDCtx.RepositoryUrl)
Out.emplace_back(writeFileDefinition(*I.DefLoc));
else
Out.emplace_back(
writeFileDefinition(*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
}
maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc);
std::string Description;
if (!I.Description.empty())
@ -798,13 +797,7 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
}
FunctionHeader->Children.emplace_back(std::make_unique<TextNode>(")"));
if (I.DefLoc) {
if (!CDCtx.RepositoryUrl)
Out.emplace_back(writeFileDefinition(*I.DefLoc));
else
Out.emplace_back(writeFileDefinition(
*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
}
maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc);
std::string Description;
if (!I.Description.empty())
@ -865,13 +858,7 @@ genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
InfoTitle = (getTagType(I.TagType) + " " + I.Name).str();
Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H1, InfoTitle));
if (I.DefLoc) {
if (!CDCtx.RepositoryUrl)
Out.emplace_back(writeFileDefinition(*I.DefLoc));
else
Out.emplace_back(writeFileDefinition(
*I.DefLoc, StringRef{*CDCtx.RepositoryUrl}));
}
maybeWriteSourceFileRef(Out, CDCtx, I.DefLoc);
std::string Description;
if (!I.Description.empty())

View File

@ -10,7 +10,9 @@
#include "Representation.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
using namespace llvm;
@ -50,22 +52,28 @@ static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
OS << std::string(Num, '#') + " " + Text << "\n\n";
}
static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L,
raw_ostream &OS) {
static void writeSourceFileRef(const ClangDocContext &CDCtx, const Location &L,
raw_ostream &OS) {
if (!CDCtx.RepositoryUrl) {
OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber)
<< "*";
} else {
OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber)
<< "](" << StringRef{*CDCtx.RepositoryUrl}
<< llvm::sys::path::relative_path(L.Filename) << "#"
<< std::to_string(L.LineNumber) << ")"
<< "*";
OS << formatv("*Defined at [#{0}{1}{2}](#{0}{1}{3})*",
CDCtx.RepositoryLinePrefix.value_or(""), L.LineNumber,
L.Filename, *CDCtx.RepositoryUrl);
}
OS << "\n\n";
}
static void maybeWriteSourceFileRef(llvm::raw_ostream &OS,
const ClangDocContext &CDCtx,
const std::optional<Location> &DefLoc) {
if (DefLoc)
writeSourceFileRef(CDCtx, *DefLoc, OS);
}
static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
if (I.Kind == "FullComment") {
for (const auto &Child : I.Children)
@ -142,8 +150,8 @@ static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
for (const auto &N : I.Members)
Members << "| " << N.Name << " |\n";
writeLine(Members.str(), OS);
if (I.DefLoc)
writeFileDefinition(CDCtx, *I.DefLoc, OS);
maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
for (const auto &C : I.Description)
writeDescription(C, OS);
@ -170,8 +178,8 @@ static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
writeLine(genItalic(I.ReturnType.Type.QualName + " " + I.Name + "(" +
Stream.str() + ")"),
OS);
if (I.DefLoc)
writeFileDefinition(CDCtx, *I.DefLoc, OS);
maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
for (const auto &C : I.Description)
writeDescription(C, OS);
@ -230,8 +238,8 @@ static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
llvm::raw_ostream &OS) {
writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
if (I.DefLoc)
writeFileDefinition(CDCtx, *I.DefLoc, OS);
maybeWriteSourceFileRef(OS, CDCtx, I.DefLoc);
if (!I.Description.empty()) {
for (const auto &C : I.Description)

View File

@ -367,7 +367,8 @@ void Index::sort() {
ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,
StringRef ProjectName, bool PublicOnly,
StringRef OutDirectory, StringRef SourceRoot,
StringRef RepositoryUrl, StringRef Base,
StringRef RepositoryUrl,
StringRef RepositoryLinePrefix, StringRef Base,
std::vector<std::string> UserStylesheets)
: ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly),
OutDirectory(OutDirectory), UserStylesheets(UserStylesheets), Base(Base) {
@ -381,6 +382,9 @@ ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,
if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with("http://") &&
!RepositoryUrl.starts_with("https://"))
this->RepositoryUrl->insert(0, "https://");
if (!RepositoryLinePrefix.empty())
this->RepositoryLinePrefix = std::string(RepositoryLinePrefix);
}
}

View File

@ -507,8 +507,8 @@ struct ClangDocContext {
ClangDocContext() = default;
ClangDocContext(tooling::ExecutionContext *ECtx, StringRef ProjectName,
bool PublicOnly, StringRef OutDirectory, StringRef SourceRoot,
StringRef RepositoryUrl, StringRef Base,
std::vector<std::string> UserStylesheets);
StringRef RepositoryUrl, StringRef RepositoryCodeLinePrefix,
StringRef Base, std::vector<std::string> UserStylesheets);
tooling::ExecutionContext *ECtx;
std::string ProjectName; // Name of project clang-doc is documenting.
bool PublicOnly; // Indicates if only public declarations are documented.
@ -518,6 +518,8 @@ struct ClangDocContext {
// the file is in this dir.
// URL of repository that hosts code used for links to definition locations.
std::optional<std::string> RepositoryUrl;
// Prefix of line code for repository.
std::optional<std::string> RepositoryLinePrefix;
// Path of CSS stylesheets that will be copied to OutDirectory and used to
// style all HTML files.
std::vector<std::string> UserStylesheets;

View File

@ -105,6 +105,11 @@ URL of repository that hosts code.
Used for links to definition locations.)"),
llvm::cl::cat(ClangDocCategory));
static llvm::cl::opt<std::string> RepositoryCodeLinePrefix(
"repository-line-prefix",
llvm::cl::desc("Prefix of line code for repository."),
llvm::cl::cat(ClangDocCategory));
enum OutputFormatTy {
md,
yaml,
@ -273,6 +278,7 @@ Example usage for a project using a compile commands database:
OutDirectory,
SourceRoot,
RepositoryUrl,
RepositoryCodeLinePrefix,
BaseDirectory,
{UserStylesheets.begin(), UserStylesheets.end()}};

View File

@ -3,6 +3,17 @@
// RUN: clang-doc --format=html --output=%t/docs --executor=all-TUs %t/build/compile_commands.json
// RUN: FileCheck %s -input-file=%t/docs/index_json.js -check-prefix=JSON-INDEX
// RUN: FileCheck %s -input-file=%t/docs/GlobalNamespace/Shape.html -check-prefix=HTML-SHAPE
// RUN: FileCheck %s -input-file=%t/docs/GlobalNamespace/Calculator.html -check-prefix=HTML-CALC
// RUN: FileCheck %s -input-file=%t/docs/GlobalNamespace/Rectangle.html -check-prefix=HTML-RECTANGLE
// RUN: FileCheck %s -input-file=%t/docs/GlobalNamespace/Circle.html -check-prefix=HTML-CIRCLE
// RUN: clang-doc --format=html --output=%t/docs-with-prefix --executor=all-TUs %t/build/compile_commands.json --repository=https://repository.com --repository-line-prefix=L
// RUN: FileCheck %s -input-file=%t/docs-with-prefix/GlobalNamespace/Shape.html -check-prefixes=HTML-SHAPE,SHAPE-LINE-PREFIX
// RUN: FileCheck %s -input-file=%t/docs-with-prefix/GlobalNamespace/Calculator.html -check-prefixes=HTML-CALC,CALC-LINE-PREFIX
// RUN: FileCheck %s -input-file=%t/docs-with-prefix/GlobalNamespace/Rectangle.html -check-prefixes=HTML-RECTANGLE,RECTANGLE-LINE-PREFIX
// RUN: FileCheck %s -input-file=%t/docs-with-prefix/GlobalNamespace/Circle.html -check-prefixes=HTML-CIRCLE,CIRCLE-LINE-PREFIX
// RUN: FileCheck %s -input-file=%t/docs/GlobalNamespace/Shape.html -check-prefixes=HTML-SHAPE,SHAPE-NO-REPOSITORY
// RUN: FileCheck %s -input-file=%t/docs/GlobalNamespace/Calculator.html -check-prefixes=HTML-CALC,CALC-NO-REPOSITORY
// RUN: FileCheck %s -input-file=%t/docs/GlobalNamespace/Rectangle.html -check-prefixes=HTML-RECTANGLE,RECTANGLE-NO-REPOSITORY
@ -75,6 +86,7 @@
// SHAPE-REPOSITORY: <p>
// SHAPE-REPOSITORY-NEXT: Defined at line
// SHAPE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Shape.h#8">8</a>
// SHAPE-LINE-PREFIX: <a href="https://repository.com/./include/Shape.h#L8">8</a>
// SHAPE-REPOSITORY-NEXT: of file
// SHAPE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Shape.h">Shape.h</a>
// SHAPE-REPOSITORY-NEXT: </p>
@ -98,6 +110,7 @@
// SHAPE-NO-REPOSITORY: Defined at line 13 of file .{{.}}include{{.}}Shape.h
// SHAPE-REPOSITORY: Defined at line
// SHAPE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Shape.h#13">13</a>
// SHAPE-LINE-PREFIX: <a href="https://repository.com/./include/Shape.h#L13">13</a>
// SHAPE-REPOSITORY-NEXT: of file
// SHAPE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Shape.h">Shape.h</a>
@ -109,6 +122,7 @@
// CALC-REPOSITORY: <p>
// CALC-REPOSITORY-NEXT: Defined at line
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./include/Calculator.h#8">8</a>
// CALC-LINE-PREFIX: <a href="https://repository.com/./include/Calculator.h#L8">8</a>
// CALC-REPOSITORY-NEXT: of file
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./include/Calculator.h">Calculator.h</a>
// CALC-REPOSITORY-NEXT: </p>
@ -121,6 +135,7 @@
// CALC-NO-REPOSITORY: Defined at line 3 of file .{{.}}src{{.}}Calculator.cpp
// CALC-REPOSITORY: Defined at line
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp#3">3</a>
// CALC-LINE-PREFIX: <a href="https://repository.com/./src/Calculator.cpp#L3">3</a>
// CALC-REPOSITORY-NEXT: of file
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp">Calculator.cpp</a>
@ -133,6 +148,7 @@
// CALC-NO-REPOSITORY: Defined at line 7 of file .{{.}}src{{.}}Calculator.cpp
// CALC-REPOSITORY: Defined at line
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp#7">7</a>
// CALC-LINE-PREFIX: <a href="https://repository.com/./src/Calculator.cpp#L7">7</a>
// CALC-REPOSITORY-NEXT: of file
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp">Calculator.cpp</a>
@ -145,6 +161,7 @@
// CALC-NO-REPOSITORY: Defined at line 11 of file .{{.}}src{{.}}Calculator.cpp
// CALC-REPOSITORY: Defined at line
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp#11">11</a>
// CALC-LINE-PREFIX: <a href="https://repository.com/./src/Calculator.cpp#L11">11</a>
// CALC-REPOSITORY-NEXT: of file
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp">Calculator.cpp</a>
@ -157,6 +174,7 @@
// CALC-NO-REPOSITORY: Defined at line 15 of file .{{.}}src{{.}}Calculator.cpp
// CALC-REPOSITORY: Defined at line
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp#15">15</a>
// CALC-LINE-PREFIX: <a href="https://repository.com/./src/Calculator.cpp#L15">15</a>
// CALC-REPOSITORY-NEXT: of file
// CALC-REPOSITORY-NEXT: <a href="https://repository.com/./src/Calculator.cpp">Calculator.cpp</a>
@ -172,6 +190,7 @@
// RECTANGLE-REPOSITORY: <p>
// RECTANGLE-REPOSITORY-NEXT: Defined at line
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Rectangle.h#10">10</a>
// RECTANGLE-LINE-PREFIX: <a href="https://repository.com/./include/Rectangle.h#L10">10</a>
// RECTANGLE-REPOSITORY-NEXT: of file
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Rectangle.h">Rectangle.h</a>
// RECTANGLE-REPOSITORY-NEXT: </p>
@ -192,6 +211,7 @@
// RECTANGLE-NO-REPOSITORY: Defined at line 3 of file .{{.}}src{{.}}Rectangle.cpp
// RECTANGLE-REPOSITORY: Defined at line
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Rectangle.cpp#3">3</a>
// RECTANGLE-LINE-PREFIX: <a href="https://repository.com/./src/Rectangle.cpp#L3">3</a>
// RECTANGLE-REPOSITORY-NEXT: of file
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Rectangle.cpp">Rectangle.cpp</a>
@ -202,6 +222,7 @@
// RECTANGLE-NO-REPOSITORY: Defined at line 6 of file .{{.}}src{{.}}Rectangle.cpp
// RECTANGLE-REPOSITORY: Defined at line
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Rectangle.cpp#6">6</a>
// RECTANGLE-LINE-PREFIX: <a href="https://repository.com/./src/Rectangle.cpp#L6">6</a>
// RECTANGLE-REPOSITORY-NEXT: of file
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Rectangle.cpp">Rectangle.cpp</a>
@ -214,6 +235,7 @@
// RECTANGLE-NO-REPOSITORY: Defined at line 10 of file .{{.}}src{{.}}Rectangle.cpp
// RECTANGLE-REPOSITORY: Defined at line
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Rectangle.cpp#10">10</a>
// RECTANGLE-LINE-PREFIX: <a href="https://repository.com/./src/Rectangle.cpp#L10">10</a>
// RECTANGLE-REPOSITORY-NEXT: of file
// RECTANGLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Rectangle.cpp">Rectangle.cpp</a>
// HTML-RECTANGLE: <div>brief</div>
@ -226,6 +248,7 @@
// CIRCLE-REPOSITORY: <p>
// CIRCLE-REPOSITORY-NEXT: Defined at line
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Circle.h#10">10</a>
// CIRCLE-LINE-PREFIX: <a href="https://repository.com/./include/Circle.h#L10">10</a>
// CIRCLE-REPOSITORY-NEXT: of file
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./include/Circle.h">Circle.h</a>
// CIRCLE-REPOSITORY-NEXT: </p>
@ -246,6 +269,7 @@
// CIRCLE-NO-REPOSITORY: Defined at line 3 of file .{{.}}src{{.}}Circle.cpp
// CIRCLE-REPOSITORY: Defined at line
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Circle.cpp#3">3</a>
// CIRCLE-LINE-PREFIX: <a href="https://repository.com/./src/Circle.cpp#L3">3</a>
// CIRCLE-REPOSITORY-NEXT: of file
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Circle.cpp">Circle.cpp</a>
@ -256,6 +280,7 @@
// CIRCLE-NO-REPOSITORY: Defined at line 5 of file .{{.}}src{{.}}Circle.cpp
// CIRCLE-REPOSITORY: Defined at line
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Circle.cpp#5">5</a>
// CIRCLE-LINE-PREFIX: <a href="https://repository.com/./src/Circle.cpp#L5">5</a>
// CIRCLE-REPOSITORY-NEXT: of file
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Circle.cpp">Circle.cpp</a>
@ -268,6 +293,7 @@
// CIRCLE-NO-REPOSITORY: Defined at line 9 of file .{{.}}src{{.}}Circle.cpp
// CIRCLE-REPOSITORY: Defined at line
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Circle.cpp#9">9</a>
// CIRCLE-LINE-PREFIX: <a href="https://repository.com/./src/Circle.cpp#L9">9</a>
// CIRCLE-REPOSITORY-NEXT: of file
// CIRCLE-REPOSITORY-NEXT: <a href="https://repository.com/./src/Circle.cpp">Circle.cpp</a>
@ -384,3 +410,4 @@
// MD-INDEX: # C/C++ Reference
// MD-INDEX: * Namespace: [GlobalNamespace](GlobalNamespace)

View File

@ -28,9 +28,11 @@ std::unique_ptr<Generator> getHTMLGenerator() {
ClangDocContext
getClangDocContext(std::vector<std::string> UserStylesheets = {},
StringRef RepositoryUrl = "", StringRef Base = "") {
ClangDocContext CDCtx{{}, "test-project", {}, {},
{}, RepositoryUrl, Base, UserStylesheets};
StringRef RepositoryUrl = "",
StringRef RepositoryLinePrefix = "", StringRef Base = "") {
ClangDocContext CDCtx{
{}, "test-project", {}, {}, {}, RepositoryUrl, RepositoryLinePrefix,
Base, UserStylesheets};
CDCtx.UserStylesheets.insert(
CDCtx.UserStylesheets.begin(),
"../share/clang/clang-doc-default-stylesheet.css");