From 515d1ae679ea25687423be37abec74565d755921 Mon Sep 17 00:00:00 2001 From: Mohamed Emad <73320969+hulxv@users.noreply.github.com> Date: Sat, 29 Mar 2025 01:45:09 +0200 Subject: [PATCH] [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 --- clang-tools-extra/clang-doc/HTMLGenerator.cpp | 47 +++++++------------ clang-tools-extra/clang-doc/MDGenerator.cpp | 34 +++++++++----- .../clang-doc/Representation.cpp | 6 ++- clang-tools-extra/clang-doc/Representation.h | 6 ++- .../clang-doc/tool/ClangDocMain.cpp | 6 +++ .../test/clang-doc/basic-project.test | 27 +++++++++++ .../unittests/clang-doc/HTMLGeneratorTest.cpp | 8 ++-- 7 files changed, 85 insertions(+), 49 deletions(-) diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp index 9b95d082fdfe..f559933fc228 100644 --- a/clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -490,15 +490,15 @@ genReferencesBlock(const std::vector &References, } return Out; } +static std::unique_ptr writeSourceFileRef(const ClangDocContext &CDCtx, + const Location &L) { -static std::unique_ptr -writeFileDefinition(const Location &L, - std::optional RepositoryUrl = std::nullopt) { - if (!L.IsFileInRootDir && !RepositoryUrl) + if (!L.IsFileInRootDir && !CDCtx.RepositoryUrl) return std::make_unique( 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(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(" of file ")); auto LocFileNode = std::make_unique( @@ -530,6 +528,13 @@ writeFileDefinition(const Location &L, return Node; } +static void maybeWriteSourceFileRef(std::vector> &Out, + const ClangDocContext &CDCtx, + const std::optional &DefLoc) { + if (DefLoc) + Out.emplace_back(writeSourceFileRef(CDCtx, *DefLoc)); +} + static std::vector> 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(")")); - 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(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()) diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp index 7bef2c0db04d..5c782fcc10da 100644 --- a/clang-tools-extra/clang-doc/MDGenerator.cpp +++ b/clang-tools-extra/clang-doc/MDGenerator.cpp @@ -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 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 &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) diff --git a/clang-tools-extra/clang-doc/Representation.cpp b/clang-tools-extra/clang-doc/Representation.cpp index fd206fb6a18c..0947ecff7227 100644 --- a/clang-tools-extra/clang-doc/Representation.cpp +++ b/clang-tools-extra/clang-doc/Representation.cpp @@ -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 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); } } diff --git a/clang-tools-extra/clang-doc/Representation.h b/clang-tools-extra/clang-doc/Representation.h index 5473439cec31..2153b62864ee 100644 --- a/clang-tools-extra/clang-doc/Representation.h +++ b/clang-tools-extra/clang-doc/Representation.h @@ -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 UserStylesheets); + StringRef RepositoryUrl, StringRef RepositoryCodeLinePrefix, + StringRef Base, std::vector 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 RepositoryUrl; + // Prefix of line code for repository. + std::optional RepositoryLinePrefix; // Path of CSS stylesheets that will be copied to OutDirectory and used to // style all HTML files. std::vector UserStylesheets; diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp index a754f38a9544..c58d2060bc1e 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -105,6 +105,11 @@ URL of repository that hosts code. Used for links to definition locations.)"), llvm::cl::cat(ClangDocCategory)); +static llvm::cl::opt 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()}}; diff --git a/clang-tools-extra/test/clang-doc/basic-project.test b/clang-tools-extra/test/clang-doc/basic-project.test index 11beb7660040..ef26e5b8916b 100644 --- a/clang-tools-extra/test/clang-doc/basic-project.test +++ b/clang-tools-extra/test/clang-doc/basic-project.test @@ -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:

// SHAPE-REPOSITORY-NEXT: Defined at line // SHAPE-REPOSITORY-NEXT: 8 +// SHAPE-LINE-PREFIX: 8 // SHAPE-REPOSITORY-NEXT: of file // SHAPE-REPOSITORY-NEXT: Shape.h // SHAPE-REPOSITORY-NEXT:

@@ -98,6 +110,7 @@ // SHAPE-NO-REPOSITORY: Defined at line 13 of file .{{.}}include{{.}}Shape.h // SHAPE-REPOSITORY: Defined at line // SHAPE-REPOSITORY-NEXT: 13 +// SHAPE-LINE-PREFIX: 13 // SHAPE-REPOSITORY-NEXT: of file // SHAPE-REPOSITORY-NEXT: Shape.h @@ -109,6 +122,7 @@ // CALC-REPOSITORY:

// CALC-REPOSITORY-NEXT: Defined at line // CALC-REPOSITORY-NEXT: 8 +// CALC-LINE-PREFIX: 8 // CALC-REPOSITORY-NEXT: of file // CALC-REPOSITORY-NEXT: Calculator.h // CALC-REPOSITORY-NEXT:

@@ -121,6 +135,7 @@ // CALC-NO-REPOSITORY: Defined at line 3 of file .{{.}}src{{.}}Calculator.cpp // CALC-REPOSITORY: Defined at line // CALC-REPOSITORY-NEXT: 3 +// CALC-LINE-PREFIX: 3 // CALC-REPOSITORY-NEXT: of file // CALC-REPOSITORY-NEXT: Calculator.cpp @@ -133,6 +148,7 @@ // CALC-NO-REPOSITORY: Defined at line 7 of file .{{.}}src{{.}}Calculator.cpp // CALC-REPOSITORY: Defined at line // CALC-REPOSITORY-NEXT: 7 +// CALC-LINE-PREFIX: 7 // CALC-REPOSITORY-NEXT: of file // CALC-REPOSITORY-NEXT: Calculator.cpp @@ -145,6 +161,7 @@ // CALC-NO-REPOSITORY: Defined at line 11 of file .{{.}}src{{.}}Calculator.cpp // CALC-REPOSITORY: Defined at line // CALC-REPOSITORY-NEXT: 11 +// CALC-LINE-PREFIX: 11 // CALC-REPOSITORY-NEXT: of file // CALC-REPOSITORY-NEXT: Calculator.cpp @@ -157,6 +174,7 @@ // CALC-NO-REPOSITORY: Defined at line 15 of file .{{.}}src{{.}}Calculator.cpp // CALC-REPOSITORY: Defined at line // CALC-REPOSITORY-NEXT: 15 +// CALC-LINE-PREFIX: 15 // CALC-REPOSITORY-NEXT: of file // CALC-REPOSITORY-NEXT: Calculator.cpp @@ -172,6 +190,7 @@ // RECTANGLE-REPOSITORY:

// RECTANGLE-REPOSITORY-NEXT: Defined at line // RECTANGLE-REPOSITORY-NEXT: 10 +// RECTANGLE-LINE-PREFIX: 10 // RECTANGLE-REPOSITORY-NEXT: of file // RECTANGLE-REPOSITORY-NEXT: Rectangle.h // RECTANGLE-REPOSITORY-NEXT:

@@ -192,6 +211,7 @@ // RECTANGLE-NO-REPOSITORY: Defined at line 3 of file .{{.}}src{{.}}Rectangle.cpp // RECTANGLE-REPOSITORY: Defined at line // RECTANGLE-REPOSITORY-NEXT: 3 +// RECTANGLE-LINE-PREFIX: 3 // RECTANGLE-REPOSITORY-NEXT: of file // RECTANGLE-REPOSITORY-NEXT: Rectangle.cpp @@ -202,6 +222,7 @@ // RECTANGLE-NO-REPOSITORY: Defined at line 6 of file .{{.}}src{{.}}Rectangle.cpp // RECTANGLE-REPOSITORY: Defined at line // RECTANGLE-REPOSITORY-NEXT: 6 +// RECTANGLE-LINE-PREFIX: 6 // RECTANGLE-REPOSITORY-NEXT: of file // RECTANGLE-REPOSITORY-NEXT: Rectangle.cpp @@ -214,6 +235,7 @@ // RECTANGLE-NO-REPOSITORY: Defined at line 10 of file .{{.}}src{{.}}Rectangle.cpp // RECTANGLE-REPOSITORY: Defined at line // RECTANGLE-REPOSITORY-NEXT: 10 +// RECTANGLE-LINE-PREFIX: 10 // RECTANGLE-REPOSITORY-NEXT: of file // RECTANGLE-REPOSITORY-NEXT: Rectangle.cpp // HTML-RECTANGLE:
brief
@@ -226,6 +248,7 @@ // CIRCLE-REPOSITORY:

// CIRCLE-REPOSITORY-NEXT: Defined at line // CIRCLE-REPOSITORY-NEXT: 10 +// CIRCLE-LINE-PREFIX: 10 // CIRCLE-REPOSITORY-NEXT: of file // CIRCLE-REPOSITORY-NEXT: Circle.h // CIRCLE-REPOSITORY-NEXT:

@@ -246,6 +269,7 @@ // CIRCLE-NO-REPOSITORY: Defined at line 3 of file .{{.}}src{{.}}Circle.cpp // CIRCLE-REPOSITORY: Defined at line // CIRCLE-REPOSITORY-NEXT: 3 +// CIRCLE-LINE-PREFIX: 3 // CIRCLE-REPOSITORY-NEXT: of file // CIRCLE-REPOSITORY-NEXT: Circle.cpp @@ -256,6 +280,7 @@ // CIRCLE-NO-REPOSITORY: Defined at line 5 of file .{{.}}src{{.}}Circle.cpp // CIRCLE-REPOSITORY: Defined at line // CIRCLE-REPOSITORY-NEXT: 5 +// CIRCLE-LINE-PREFIX: 5 // CIRCLE-REPOSITORY-NEXT: of file // CIRCLE-REPOSITORY-NEXT: Circle.cpp @@ -268,6 +293,7 @@ // CIRCLE-NO-REPOSITORY: Defined at line 9 of file .{{.}}src{{.}}Circle.cpp // CIRCLE-REPOSITORY: Defined at line // CIRCLE-REPOSITORY-NEXT: 9 +// CIRCLE-LINE-PREFIX: 9 // CIRCLE-REPOSITORY-NEXT: of file // CIRCLE-REPOSITORY-NEXT: Circle.cpp @@ -384,3 +410,4 @@ // MD-INDEX: # C/C++ Reference // MD-INDEX: * Namespace: [GlobalNamespace](GlobalNamespace) + diff --git a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp index 66cf77d7b0f3..e440e11c07fd 100644 --- a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp @@ -28,9 +28,11 @@ std::unique_ptr getHTMLGenerator() { ClangDocContext getClangDocContext(std::vector 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");