[clang] 5aab45f - [clang][extract-api] Add global record support
Zixu Wang via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 16 15:15:25 PDT 2022
Author: Zixu Wang
Date: 2022-03-16T15:13:55-07:00
New Revision: 5aab45f430669d7d2af51386819d071b26c3c89c
URL: https://github.com/llvm/llvm-project/commit/5aab45f430669d7d2af51386819d071b26c3c89c
DIFF: https://github.com/llvm/llvm-project/commit/5aab45f430669d7d2af51386819d071b26c3c89c.diff
LOG: [clang][extract-api] Add global record support
Add facilities for extract-api:
- Structs/classes to hold collected API information: `APIRecord`, `API`
- Structs/classes for API information:
- `AvailabilityInfo`: aggregated availbility information
- `DeclarationFragments`: declaration fragments
- `DeclarationFragmentsBuilder`: helper class to build declaration
fragments for various types/declarations
- `FunctionSignature`: function signature
- Serialization: `Serializer`
- Add output file for `ExtractAPIAction`
- Refactor `clang::RawComment::getFormattedText` to provide an
additional `getFormattedLines` for a more detailed view of comment lines
used for the SymbolGraph format
Add support for global records (global variables and functions)
- Add `GlobalRecord` based on `APIRecord` to store global records'
information
- Implement `VisitVarDecl` and `VisitFunctionDecl` in `ExtractAPIVisitor` to
collect information
- Implement serialization for global records
- Add test case for global records
Differential Revision: https://reviews.llvm.org/D119479
Added:
clang/include/clang/SymbolGraph/API.h
clang/include/clang/SymbolGraph/AvailabilityInfo.h
clang/include/clang/SymbolGraph/DeclarationFragments.h
clang/include/clang/SymbolGraph/FrontendActions.h
clang/include/clang/SymbolGraph/Serialization.h
clang/lib/SymbolGraph/API.cpp
clang/lib/SymbolGraph/CMakeLists.txt
clang/lib/SymbolGraph/DeclarationFragments.cpp
clang/lib/SymbolGraph/ExtractAPIConsumer.cpp
clang/lib/SymbolGraph/Serialization.cpp
clang/test/SymbolGraph/global_record.c
Modified:
clang/include/clang/AST/RawCommentList.h
clang/include/clang/Frontend/FrontendActions.h
clang/lib/AST/RawCommentList.cpp
clang/lib/CMakeLists.txt
clang/lib/Frontend/CMakeLists.txt
clang/lib/FrontendTool/CMakeLists.txt
clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
clang/test/Driver/extract-api.c
Removed:
clang/lib/Frontend/ExtractAPIConsumer.cpp
################################################################################
diff --git a/clang/include/clang/AST/RawCommentList.h b/clang/include/clang/AST/RawCommentList.h
index a18432c2b7682..1bb8d7ce40a90 100644
--- a/clang/include/clang/AST/RawCommentList.h
+++ b/clang/include/clang/AST/RawCommentList.h
@@ -139,6 +139,21 @@ class RawComment {
std::string getFormattedText(const SourceManager &SourceMgr,
DiagnosticsEngine &Diags) const;
+ struct CommentLine {
+ std::string Text;
+ PresumedLoc Begin;
+ PresumedLoc End;
+
+ CommentLine(StringRef Text, PresumedLoc Begin, PresumedLoc End)
+ : Text(Text), Begin(Begin), End(End) {}
+ };
+
+ /// Returns sanitized comment text as separated lines with locations in
+ /// source, suitable for further processing and rendering requiring source
+ /// locations.
+ std::vector<CommentLine> getFormattedLines(const SourceManager &SourceMgr,
+ DiagnosticsEngine &Diags) const;
+
/// Parse the comment, assuming it is attached to decl \c D.
comments::FullComment *parse(const ASTContext &Context,
const Preprocessor *PP, const Decl *D) const;
diff --git a/clang/include/clang/Frontend/FrontendActions.h b/clang/include/clang/Frontend/FrontendActions.h
index 8eceb81723d23..9b5b757034a6a 100644
--- a/clang/include/clang/Frontend/FrontendActions.h
+++ b/clang/include/clang/Frontend/FrontendActions.h
@@ -271,12 +271,6 @@ class PrintDependencyDirectivesSourceMinimizerAction : public FrontendAction {
bool usesPreprocessorOnly() const override { return true; }
};
-class ExtractAPIAction : public ASTFrontendAction {
-protected:
- std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
- StringRef InFile) override;
-};
-
//===----------------------------------------------------------------------===//
// Preprocessor Actions
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/SymbolGraph/API.h b/clang/include/clang/SymbolGraph/API.h
new file mode 100644
index 0000000000000..39cc7a699ed86
--- /dev/null
+++ b/clang/include/clang/SymbolGraph/API.h
@@ -0,0 +1,138 @@
+//===- SymbolGraph/API.h ----------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines SymbolGraph API records.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SYMBOLGRAPH_API_H
+#define LLVM_CLANG_SYMBOLGRAPH_API_H
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/RawCommentList.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/SymbolGraph/AvailabilityInfo.h"
+#include "clang/SymbolGraph/DeclarationFragments.h"
+#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Casting.h"
+
+namespace clang {
+namespace symbolgraph {
+
+using DocComment = std::vector<RawComment::CommentLine>;
+
+struct APIRecord {
+ StringRef Name;
+ StringRef USR;
+ PresumedLoc Location;
+ AvailabilityInfo Availability;
+ LinkageInfo Linkage;
+ DocComment Comment;
+ DeclarationFragments Declaration;
+ DeclarationFragments SubHeading;
+
+ /// Discriminator for LLVM-style RTTI (dyn_cast<> et al.)
+ enum RecordKind {
+ RK_Global,
+ };
+
+private:
+ const RecordKind Kind;
+
+public:
+ RecordKind getKind() const { return Kind; }
+
+ APIRecord() = delete;
+
+ APIRecord(RecordKind Kind, StringRef Name, StringRef USR,
+ PresumedLoc Location, const AvailabilityInfo &Availability,
+ LinkageInfo Linkage, const DocComment &Comment,
+ DeclarationFragments Declaration, DeclarationFragments SubHeading)
+ : Name(Name), USR(USR), Location(Location), Availability(Availability),
+ Linkage(Linkage), Comment(Comment), Declaration(Declaration),
+ SubHeading(SubHeading), Kind(Kind) {}
+
+ // Pure virtual destructor to make APIRecord abstract
+ virtual ~APIRecord() = 0;
+};
+
+enum class GVKind : uint8_t {
+ Unknown = 0,
+ Variable = 1,
+ Function = 2,
+};
+
+struct GlobalRecord : APIRecord {
+ GVKind GlobalKind;
+ FunctionSignature Signature;
+
+ GlobalRecord(GVKind Kind, StringRef Name, StringRef USR, PresumedLoc Loc,
+ const AvailabilityInfo &Availability, LinkageInfo Linkage,
+ const DocComment &Comment, DeclarationFragments Declaration,
+ DeclarationFragments SubHeading, FunctionSignature Signature)
+ : APIRecord(RK_Global, Name, USR, Loc, Availability, Linkage, Comment,
+ Declaration, SubHeading),
+ GlobalKind(Kind), Signature(Signature) {}
+
+ static bool classof(const APIRecord *Record) {
+ return Record->getKind() == RK_Global;
+ }
+};
+
+class API {
+public:
+ API(const llvm::Triple &Target, const LangOptions &LangOpts)
+ : Target(Target), LangOpts(LangOpts) {}
+
+ const llvm::Triple &getTarget() const { return Target; }
+ const LangOptions &getLangOpts() const { return LangOpts; }
+
+ GlobalRecord *addGlobal(GVKind Kind, StringRef Name, StringRef USR,
+ PresumedLoc Loc, const AvailabilityInfo &Availability,
+ LinkageInfo Linkage, const DocComment &Comment,
+ DeclarationFragments Declaration,
+ DeclarationFragments SubHeading,
+ FunctionSignature Signature);
+
+ GlobalRecord *addGlobalVar(StringRef Name, StringRef USR, PresumedLoc Loc,
+ const AvailabilityInfo &Availability,
+ LinkageInfo Linkage, const DocComment &Comment,
+ DeclarationFragments Declaration,
+ DeclarationFragments SubHeading);
+
+ GlobalRecord *addFunction(StringRef Name, StringRef USR, PresumedLoc Loc,
+ const AvailabilityInfo &Availability,
+ LinkageInfo Linkage, const DocComment &Comment,
+ DeclarationFragments Declaration,
+ DeclarationFragments SubHeading,
+ FunctionSignature Signature);
+
+ StringRef recordUSR(const Decl *D);
+ StringRef copyString(StringRef String, llvm::BumpPtrAllocator &Allocator);
+ StringRef copyString(StringRef String);
+
+ using GlobalRecordMap = llvm::MapVector<StringRef, GlobalRecord *>;
+
+ const GlobalRecordMap &getGlobals() const { return Globals; }
+
+private:
+ llvm::BumpPtrAllocator Allocator;
+ const llvm::Triple Target;
+ const LangOptions LangOpts;
+
+ GlobalRecordMap Globals;
+};
+
+} // namespace symbolgraph
+} // namespace clang
+
+#endif // LLVM_CLANG_SYMBOLGRAPH_API_H
diff --git a/clang/include/clang/SymbolGraph/AvailabilityInfo.h b/clang/include/clang/SymbolGraph/AvailabilityInfo.h
new file mode 100644
index 0000000000000..5a5e4253b11d5
--- /dev/null
+++ b/clang/include/clang/SymbolGraph/AvailabilityInfo.h
@@ -0,0 +1,66 @@
+//===- SymbolGraph/AvailabilityInfo.h - Availability Info -------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines the Availability Info for a declaration.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SYMBOLGRAPH_AVAILABILITY_INFO_H
+#define LLVM_CLANG_SYMBOLGRAPH_AVAILABILITY_INFO_H
+
+#include "llvm/Support/Error.h"
+#include "llvm/Support/VersionTuple.h"
+#include "llvm/Support/raw_ostream.h"
+
+using llvm::VersionTuple;
+
+namespace clang {
+namespace symbolgraph {
+
+struct AvailabilityInfo {
+ VersionTuple Introduced;
+ VersionTuple Deprecated;
+ VersionTuple Obsoleted;
+ bool Unavailable{false};
+ bool UnconditionallyDeprecated{false};
+ bool UnconditionallyUnavailable{false};
+
+ explicit AvailabilityInfo(bool Unavailable = false)
+ : Unavailable(Unavailable) {}
+
+ AvailabilityInfo(VersionTuple I, VersionTuple D, VersionTuple O, bool U,
+ bool UD, bool UU)
+ : Introduced(I), Deprecated(D), Obsoleted(O), Unavailable(U),
+ UnconditionallyDeprecated(UD), UnconditionallyUnavailable(UU) {}
+
+ bool isDefault() const { return *this == AvailabilityInfo(); }
+ bool isUnavailable() const { return Unavailable; }
+ bool isUnconditionallyDeprecated() const { return UnconditionallyDeprecated; }
+ bool isUnconditionallyUnavailable() const {
+ return UnconditionallyUnavailable;
+ }
+
+ friend bool operator==(const AvailabilityInfo &Lhs,
+ const AvailabilityInfo &Rhs);
+};
+
+inline bool operator==(const AvailabilityInfo &Lhs,
+ const AvailabilityInfo &Rhs) {
+ return std::tie(Lhs.Introduced, Lhs.Deprecated, Lhs.Obsoleted,
+ Lhs.Unavailable, Lhs.UnconditionallyDeprecated,
+ Lhs.UnconditionallyUnavailable) ==
+ std::tie(Rhs.Introduced, Rhs.Deprecated, Rhs.Obsoleted,
+ Rhs.Unavailable, Rhs.UnconditionallyDeprecated,
+ Rhs.UnconditionallyUnavailable);
+}
+
+} // namespace symbolgraph
+} // namespace clang
+
+#endif // LLVM_CLANG_SYMBOLGRAPH_AVAILABILITY_INFO_H
diff --git a/clang/include/clang/SymbolGraph/DeclarationFragments.h b/clang/include/clang/SymbolGraph/DeclarationFragments.h
new file mode 100644
index 0000000000000..a1cd7b52b25b1
--- /dev/null
+++ b/clang/include/clang/SymbolGraph/DeclarationFragments.h
@@ -0,0 +1,140 @@
+//===- SymbolGraph/DeclarationFragments.h -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines SymbolGraph Declaration Fragments related classes.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SYMBOLGRAPH_DECLARATION_FRAGMENTS_H
+#define LLVM_CLANG_SYMBOLGRAPH_DECLARATION_FRAGMENTS_H
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "llvm/ADT/StringRef.h"
+#include <vector>
+
+namespace clang {
+namespace symbolgraph {
+
+class DeclarationFragments {
+public:
+ DeclarationFragments() = default;
+
+ enum class FragmentKind {
+ None,
+ Keyword,
+ Attribute,
+ NumberLiteral,
+ StringLiteral,
+ Identifier,
+ TypeIdentifier,
+ GenericParameter,
+ ExternalParam,
+ InternalParam,
+ Text,
+ };
+
+ struct Fragment {
+ std::string Spelling;
+ FragmentKind Kind;
+ std::string PreciseIdentifier;
+
+ Fragment(StringRef Spelling, FragmentKind Kind, StringRef PreciseIdentifier)
+ : Spelling(Spelling), Kind(Kind), PreciseIdentifier(PreciseIdentifier) {
+ }
+ };
+
+ const std::vector<Fragment> &getFragments() const { return Fragments; }
+
+ DeclarationFragments &append(StringRef Spelling, FragmentKind Kind,
+ StringRef PreciseIdentifier = "") {
+ if (Kind == FragmentKind::Text && !Fragments.empty() &&
+ Fragments.back().Kind == FragmentKind::Text) {
+ Fragments.back().Spelling.append(Spelling.data(), Spelling.size());
+ } else {
+ Fragments.emplace_back(Spelling, Kind, PreciseIdentifier);
+ }
+ return *this;
+ }
+
+ DeclarationFragments &append(DeclarationFragments &&Other) {
+ Fragments.insert(Fragments.end(),
+ std::make_move_iterator(Other.Fragments.begin()),
+ std::make_move_iterator(Other.Fragments.end()));
+ Other.Fragments.clear();
+ return *this;
+ }
+
+ DeclarationFragments &appendSpace();
+
+ static StringRef getFragmentKindString(FragmentKind Kind);
+ static FragmentKind parseFragmentKindFromString(StringRef S);
+
+private:
+ std::vector<Fragment> Fragments;
+};
+
+class FunctionSignature {
+public:
+ FunctionSignature() = default;
+
+ struct Parameter {
+ std::string Name;
+ DeclarationFragments Fragments;
+
+ Parameter(StringRef Name, DeclarationFragments Fragments)
+ : Name(Name), Fragments(Fragments) {}
+ };
+
+ const std::vector<Parameter> &getParameters() const { return Parameters; }
+ const DeclarationFragments &getReturnType() const { return ReturnType; }
+
+ FunctionSignature &addParameter(StringRef Name,
+ DeclarationFragments Fragments) {
+ Parameters.emplace_back(Name, Fragments);
+ return *this;
+ }
+
+ void setReturnType(DeclarationFragments RT) { ReturnType = RT; }
+
+ bool empty() const {
+ return Parameters.empty() && ReturnType.getFragments().empty();
+ }
+
+private:
+ std::vector<Parameter> Parameters;
+ DeclarationFragments ReturnType;
+};
+
+class DeclarationFragmentsBuilder {
+public:
+ static DeclarationFragments getFragmentsForVar(const VarDecl *);
+ static DeclarationFragments getFragmentsForFunction(const FunctionDecl *);
+ static DeclarationFragments getSubHeading(const NamedDecl *);
+ static FunctionSignature getFunctionSignature(const FunctionDecl *);
+
+private:
+ DeclarationFragmentsBuilder() = delete;
+
+ static DeclarationFragments getFragmentsForType(const QualType, ASTContext &,
+ DeclarationFragments &);
+ static DeclarationFragments getFragmentsForType(const Type *, ASTContext &,
+ DeclarationFragments &);
+ static DeclarationFragments getFragmentsForNNS(const NestedNameSpecifier *,
+ ASTContext &,
+ DeclarationFragments &);
+ static DeclarationFragments getFragmentsForQualifiers(const Qualifiers quals);
+ static DeclarationFragments getFragmentsForParam(const ParmVarDecl *);
+};
+
+} // namespace symbolgraph
+} // namespace clang
+
+#endif // LLVM_CLANG_SYMBOLGRAPH_DECLARATION_FRAGMENTS_H
diff --git a/clang/include/clang/SymbolGraph/FrontendActions.h b/clang/include/clang/SymbolGraph/FrontendActions.h
new file mode 100644
index 0000000000000..47e973b39f554
--- /dev/null
+++ b/clang/include/clang/SymbolGraph/FrontendActions.h
@@ -0,0 +1,33 @@
+//===- SymbolGraph/FrontendActions.h -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines SymbolGraph frontend actions.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SYMBOLGRAPH_FRONTEND_ACTIONS_H
+#define LLVM_CLANG_SYMBOLGRAPH_FRONTEND_ACTIONS_H
+
+#include "clang/Frontend/FrontendAction.h"
+
+namespace clang {
+
+class ExtractAPIAction : public ASTFrontendAction {
+protected:
+ std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
+ StringRef InFile) override;
+
+public:
+ static std::unique_ptr<llvm::raw_pwrite_stream>
+ CreateOutputFile(CompilerInstance &CI, StringRef InFile);
+};
+
+} // namespace clang
+
+#endif // LLVM_CLANG_SYMBOLGRAPH_FRONTEND_ACTIONS_H
diff --git a/clang/include/clang/SymbolGraph/Serialization.h b/clang/include/clang/SymbolGraph/Serialization.h
new file mode 100644
index 0000000000000..335f5f779314a
--- /dev/null
+++ b/clang/include/clang/SymbolGraph/Serialization.h
@@ -0,0 +1,58 @@
+//===- SymbolGraph/Serialization.h ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines the SymbolGraph serializer and parser.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_SYMBOLGRAPH_SERIALIZATION_H
+#define LLVM_CLANG_SYMBOLGRAPH_SERIALIZATION_H
+
+#include "clang/SymbolGraph/API.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/VersionTuple.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace clang {
+namespace symbolgraph {
+
+using namespace llvm::json;
+
+struct SerializerOption {
+ bool Compact;
+};
+
+class Serializer {
+public:
+ Serializer(const API &API, SerializerOption Options = {})
+ : API(API), Options(Options) {}
+
+ Object serialize();
+ void serialize(raw_ostream &os);
+
+private:
+ Object serializeMetadata() const;
+ Object serializeModule() const;
+ Optional<Object> serializeAPIRecord(const APIRecord &Record) const;
+ void serializeGlobalRecord(const GlobalRecord &Record);
+
+ bool shouldSkip(const APIRecord &Record) const;
+
+ const API &API;
+ SerializerOption Options;
+ Array Symbols;
+ Array Relationships;
+
+ static const VersionTuple FormatVersion;
+};
+
+} // namespace symbolgraph
+} // namespace clang
+
+#endif // LLVM_CLANG_SYMBOLGRAPH_SERIALIZATION_H
diff --git a/clang/lib/AST/RawCommentList.cpp b/clang/lib/AST/RawCommentList.cpp
index a8d15036cab99..c3beb23228887 100644
--- a/clang/lib/AST/RawCommentList.cpp
+++ b/clang/lib/AST/RawCommentList.cpp
@@ -16,6 +16,7 @@
#include "clang/AST/CommentSema.h"
#include "clang/Basic/CharInfo.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Allocator.h"
using namespace clang;
@@ -362,6 +363,24 @@ std::string RawComment::getFormattedText(const SourceManager &SourceMgr,
if (CommentText.empty())
return "";
+ std::string Result;
+ for (const RawComment::CommentLine &Line :
+ getFormattedLines(SourceMgr, Diags))
+ Result += Line.Text + "\n";
+
+ auto LastChar = Result.find_last_not_of('\n');
+ Result.erase(LastChar + 1, Result.size());
+
+ return Result;
+}
+
+std::vector<RawComment::CommentLine>
+RawComment::getFormattedLines(const SourceManager &SourceMgr,
+ DiagnosticsEngine &Diags) const {
+ llvm::StringRef CommentText = getRawText(SourceMgr);
+ if (CommentText.empty())
+ return {};
+
llvm::BumpPtrAllocator Allocator;
// We do not parse any commands, so CommentOptions are ignored by
// comments::Lexer. Therefore, we just use default-constructed options.
@@ -371,13 +390,23 @@ std::string RawComment::getFormattedText(const SourceManager &SourceMgr,
CommentText.begin(), CommentText.end(),
/*ParseCommands=*/false);
- std::string Result;
+ std::vector<RawComment::CommentLine> Result;
// A column number of the first non-whitespace token in the comment text.
// We skip whitespace up to this column, but keep the whitespace after this
// column. IndentColumn is calculated when lexing the first line and reused
// for the rest of lines.
unsigned IndentColumn = 0;
+ // Record the line number of the last processed comment line.
+ // For block-style comments, an extra newline token will be produced after
+ // the end-comment marker, e.g.:
+ // /** This is a multi-line comment block.
+ // The lexer will produce two newline tokens here > */
+ // previousLine will record the line number when we previously saw a newline
+ // token and recorded a comment line. If we see another newline token on the
+ // same line, don't record anything in between.
+ unsigned PreviousLine = 0;
+
// Processes one line of the comment and adds it to the result.
// Handles skipping the indent at the start of the line.
// Returns false when eof is reached and true otherwise.
@@ -389,9 +418,14 @@ std::string RawComment::getFormattedText(const SourceManager &SourceMgr,
if (Tok.is(comments::tok::eof))
return false;
if (Tok.is(comments::tok::newline)) {
- Result += "\n";
+ PresumedLoc Loc = SourceMgr.getPresumedLoc(Tok.getLocation());
+ if (Loc.getLine() != PreviousLine) {
+ Result.emplace_back("", Loc, Loc);
+ PreviousLine = Loc.getLine();
+ }
return true;
}
+ SmallString<124> Line;
llvm::StringRef TokText = L.getSpelling(Tok, SourceMgr);
bool LocInvalid = false;
unsigned TokColumn =
@@ -417,32 +451,35 @@ std::string RawComment::getFormattedText(const SourceManager &SourceMgr,
WhitespaceLen,
std::max<int>(static_cast<int>(IndentColumn) - TokColumn, 0));
llvm::StringRef Trimmed = TokText.drop_front(SkipLen);
- Result += Trimmed;
+ Line += Trimmed;
+ // Get the beginning location of the adjusted comment line.
+ PresumedLoc Begin =
+ SourceMgr.getPresumedLoc(Tok.getLocation().getLocWithOffset(SkipLen));
+
// Lex all tokens in the rest of the line.
for (L.lex(Tok); Tok.isNot(comments::tok::eof); L.lex(Tok)) {
if (Tok.is(comments::tok::newline)) {
- Result += "\n";
+ // Get the ending location of the comment line.
+ PresumedLoc End = SourceMgr.getPresumedLoc(Tok.getLocation());
+ if (End.getLine() != PreviousLine) {
+ Result.emplace_back(Line, Begin, End);
+ PreviousLine = End.getLine();
+ }
return true;
}
- Result += L.getSpelling(Tok, SourceMgr);
+ Line += L.getSpelling(Tok, SourceMgr);
}
+ PresumedLoc End = SourceMgr.getPresumedLoc(Tok.getLocation());
+ Result.emplace_back(Line, Begin, End);
// We've reached the end of file token.
return false;
};
- auto DropTrailingNewLines = [](std::string &Str) {
- while (!Str.empty() && Str.back() == '\n')
- Str.pop_back();
- };
-
// Process first line separately to remember indent for the following lines.
- if (!LexLine(/*IsFirstLine=*/true)) {
- DropTrailingNewLines(Result);
+ if (!LexLine(/*IsFirstLine=*/true))
return Result;
- }
// Process the rest of the lines.
while (LexLine(/*IsFirstLine=*/false))
;
- DropTrailingNewLines(Result);
return Result;
}
diff --git a/clang/lib/CMakeLists.txt b/clang/lib/CMakeLists.txt
index 2b49fb9e96f08..6bee9a881ea66 100644
--- a/clang/lib/CMakeLists.txt
+++ b/clang/lib/CMakeLists.txt
@@ -23,6 +23,7 @@ add_subdirectory(DirectoryWatcher)
add_subdirectory(Index)
add_subdirectory(IndexSerialization)
add_subdirectory(StaticAnalyzer)
+add_subdirectory(SymbolGraph)
add_subdirectory(Format)
add_subdirectory(Testing)
add_subdirectory(Interpreter)
diff --git a/clang/lib/Frontend/CMakeLists.txt b/clang/lib/Frontend/CMakeLists.txt
index e147a1341e064..ca4ad8b2dab2e 100644
--- a/clang/lib/Frontend/CMakeLists.txt
+++ b/clang/lib/Frontend/CMakeLists.txt
@@ -20,7 +20,6 @@ add_clang_library(clangFrontend
DependencyFile.cpp
DependencyGraph.cpp
DiagnosticRenderer.cpp
- ExtractAPIConsumer.cpp
FrontendAction.cpp
FrontendActions.cpp
FrontendOptions.cpp
diff --git a/clang/lib/Frontend/ExtractAPIConsumer.cpp b/clang/lib/Frontend/ExtractAPIConsumer.cpp
deleted file mode 100644
index cdf67f3c327aa..0000000000000
--- a/clang/lib/Frontend/ExtractAPIConsumer.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#include "clang/AST/ASTConsumer.h"
-#include "clang/AST/RecursiveASTVisitor.h"
-#include "clang/Frontend/ASTConsumers.h"
-#include "clang/Frontend/CompilerInstance.h"
-#include "clang/Frontend/FrontendActions.h"
-
-using namespace clang;
-
-namespace {
-class ExtractAPIVisitor : public RecursiveASTVisitor<ExtractAPIVisitor> {
-public:
- bool VisitNamedDecl(NamedDecl *Decl) {
- llvm::outs() << Decl->getName() << "\n";
- return true;
- }
-};
-
-class ExtractAPIConsumer : public ASTConsumer {
-public:
- void HandleTranslationUnit(ASTContext &Context) override {
- Visitor.TraverseDecl(Context.getTranslationUnitDecl());
- }
-
-private:
- ExtractAPIVisitor Visitor;
-};
-} // namespace
-
-std::unique_ptr<ASTConsumer>
-ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
- return std::make_unique<ExtractAPIConsumer>();
-}
diff --git a/clang/lib/FrontendTool/CMakeLists.txt b/clang/lib/FrontendTool/CMakeLists.txt
index 7e11be0ce4c58..38033dea4f06f 100644
--- a/clang/lib/FrontendTool/CMakeLists.txt
+++ b/clang/lib/FrontendTool/CMakeLists.txt
@@ -9,6 +9,7 @@ set(link_libs
clangDriver
clangFrontend
clangRewriteFrontend
+ clangSymbolGraph
)
if(CLANG_ENABLE_ARCMT)
diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
index 8a8a13743762e..dcc20a5bb5cc3 100644
--- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
+++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
@@ -25,6 +25,7 @@
#include "clang/Rewrite/Frontend/FrontendActions.h"
#include "clang/StaticAnalyzer/Frontend/AnalyzerHelpFlags.h"
#include "clang/StaticAnalyzer/Frontend/FrontendActions.h"
+#include "clang/SymbolGraph/FrontendActions.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/BuryPointer.h"
diff --git a/clang/lib/SymbolGraph/API.cpp b/clang/lib/SymbolGraph/API.cpp
new file mode 100644
index 0000000000000..a4e0e219d4d33
--- /dev/null
+++ b/clang/lib/SymbolGraph/API.cpp
@@ -0,0 +1,83 @@
+//===- SymbolGraph/API.cpp --------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines SymbolGraph API records.
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/SymbolGraph/API.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentLexer.h"
+#include "clang/AST/RawCommentList.h"
+#include "clang/Index/USRGeneration.h"
+#include "llvm/Support/Allocator.h"
+
+namespace clang {
+namespace symbolgraph {
+
+APIRecord::~APIRecord() {}
+
+GlobalRecord *
+API::addGlobal(GVKind Kind, StringRef Name, StringRef USR, PresumedLoc Loc,
+ const AvailabilityInfo &Availability, LinkageInfo Linkage,
+ const DocComment &Comment, DeclarationFragments Fragments,
+ DeclarationFragments SubHeading, FunctionSignature Signature) {
+ auto Result = Globals.insert({Name, nullptr});
+ if (Result.second) {
+ GlobalRecord *Record = new (Allocator)
+ GlobalRecord{Kind, Name, USR, Loc, Availability,
+ Linkage, Comment, Fragments, SubHeading, Signature};
+ Result.first->second = Record;
+ }
+ return Result.first->second;
+}
+
+GlobalRecord *API::addGlobalVar(StringRef Name, StringRef USR, PresumedLoc Loc,
+ const AvailabilityInfo &Availability,
+ LinkageInfo Linkage, const DocComment &Comment,
+ DeclarationFragments Fragments,
+ DeclarationFragments SubHeading) {
+ return addGlobal(GVKind::Variable, Name, USR, Loc, Availability, Linkage,
+ Comment, Fragments, SubHeading, {});
+}
+
+GlobalRecord *API::addFunction(StringRef Name, StringRef USR, PresumedLoc Loc,
+ const AvailabilityInfo &Availability,
+ LinkageInfo Linkage, const DocComment &Comment,
+ DeclarationFragments Fragments,
+ DeclarationFragments SubHeading,
+ FunctionSignature Signature) {
+ return addGlobal(GVKind::Function, Name, USR, Loc, Availability, Linkage,
+ Comment, Fragments, SubHeading, Signature);
+}
+
+StringRef API::recordUSR(const Decl *D) {
+ SmallString<128> USR;
+ index::generateUSRForDecl(D, USR);
+ return copyString(USR);
+}
+
+StringRef API::copyString(StringRef String, llvm::BumpPtrAllocator &Allocator) {
+ if (String.empty())
+ return {};
+
+ if (Allocator.identifyObject(String.data()))
+ return String;
+
+ void *Ptr = Allocator.Allocate(String.size(), 1);
+ memcpy(Ptr, String.data(), String.size());
+ return StringRef(reinterpret_cast<const char *>(Ptr), String.size());
+}
+
+StringRef API::copyString(StringRef String) {
+ return copyString(String, Allocator);
+}
+
+} // namespace symbolgraph
+} // namespace clang
diff --git a/clang/lib/SymbolGraph/CMakeLists.txt b/clang/lib/SymbolGraph/CMakeLists.txt
new file mode 100644
index 0000000000000..715937f89ddfb
--- /dev/null
+++ b/clang/lib/SymbolGraph/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(LLVM_LINK_COMPONENTS
+ Support
+ )
+
+add_clang_library(clangSymbolGraph
+ API.cpp
+ ExtractAPIConsumer.cpp
+ DeclarationFragments.cpp
+ Serialization.cpp
+
+ LINK_LIBS
+ clangAST
+ clangBasic
+ clangFrontend
+ clangIndex
+ )
diff --git a/clang/lib/SymbolGraph/DeclarationFragments.cpp b/clang/lib/SymbolGraph/DeclarationFragments.cpp
new file mode 100644
index 0000000000000..4a00629ae9150
--- /dev/null
+++ b/clang/lib/SymbolGraph/DeclarationFragments.cpp
@@ -0,0 +1,434 @@
+//===- SymbolGraph/DeclarationFragments.cpp ---------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines SymbolGraph Declaration Fragments related classes.
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/SymbolGraph/DeclarationFragments.h"
+#include "clang/Index/USRGeneration.h"
+#include "llvm/ADT/StringSwitch.h"
+
+namespace clang {
+namespace symbolgraph {
+
+DeclarationFragments &DeclarationFragments::appendSpace() {
+ if (!Fragments.empty()) {
+ Fragment Last = Fragments.back();
+ if (Last.Kind == FragmentKind::Text) {
+ if (Last.Spelling.back() != ' ') {
+ Last.Spelling.push_back(' ');
+ }
+ } else {
+ append(" ", FragmentKind::Text);
+ }
+ }
+
+ return *this;
+}
+
+StringRef DeclarationFragments::getFragmentKindString(
+ DeclarationFragments::FragmentKind Kind) {
+ switch (Kind) {
+ case DeclarationFragments::FragmentKind::None:
+ return "none";
+ case DeclarationFragments::FragmentKind::Keyword:
+ return "keyword";
+ case DeclarationFragments::FragmentKind::Attribute:
+ return "attribute";
+ case DeclarationFragments::FragmentKind::NumberLiteral:
+ return "number";
+ case DeclarationFragments::FragmentKind::StringLiteral:
+ return "string";
+ case DeclarationFragments::FragmentKind::Identifier:
+ return "identifier";
+ case DeclarationFragments::FragmentKind::TypeIdentifier:
+ return "typeIdentifier";
+ case DeclarationFragments::FragmentKind::GenericParameter:
+ return "genericParameter";
+ case DeclarationFragments::FragmentKind::ExternalParam:
+ return "externalParam";
+ case DeclarationFragments::FragmentKind::InternalParam:
+ return "internalParam";
+ case DeclarationFragments::FragmentKind::Text:
+ return "text";
+ }
+
+ llvm_unreachable("Unhandled FragmentKind");
+}
+
+DeclarationFragments::FragmentKind
+DeclarationFragments::parseFragmentKindFromString(StringRef S) {
+ return llvm::StringSwitch<FragmentKind>(S)
+ .Case("keyword", DeclarationFragments::FragmentKind::Keyword)
+ .Case("attribute", DeclarationFragments::FragmentKind::Attribute)
+ .Case("number", DeclarationFragments::FragmentKind::NumberLiteral)
+ .Case("string", DeclarationFragments::FragmentKind::StringLiteral)
+ .Case("identifier", DeclarationFragments::FragmentKind::Identifier)
+ .Case("typeIdentifier",
+ DeclarationFragments::FragmentKind::TypeIdentifier)
+ .Case("genericParameter",
+ DeclarationFragments::FragmentKind::GenericParameter)
+ .Case("internalParam", DeclarationFragments::FragmentKind::InternalParam)
+ .Case("externalParam", DeclarationFragments::FragmentKind::ExternalParam)
+ .Case("text", DeclarationFragments::FragmentKind::Text)
+ .Default(DeclarationFragments::FragmentKind::None);
+}
+
+// NNS stores C++ nested name specifiers, which are prefixes to qualified names.
+// Build declaration fragments for NNS recursively so that we have the USR for
+// every part in a qualified name, and also leaves the actual underlying type
+// cleaner for its own fragment.
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForNNS(const NestedNameSpecifier *NNS,
+ ASTContext &Context,
+ DeclarationFragments &After) {
+ DeclarationFragments Fragments;
+ if (NNS->getPrefix())
+ Fragments.append(getFragmentsForNNS(NNS->getPrefix(), Context, After));
+
+ switch (NNS->getKind()) {
+ case NestedNameSpecifier::Identifier:
+ Fragments.append(NNS->getAsIdentifier()->getName(),
+ DeclarationFragments::FragmentKind::Identifier);
+ break;
+
+ case NestedNameSpecifier::Namespace: {
+ const NamespaceDecl *NS = NNS->getAsNamespace();
+ if (NS->isAnonymousNamespace())
+ return Fragments;
+ SmallString<128> USR;
+ index::generateUSRForDecl(NS, USR);
+ Fragments.append(NS->getName(),
+ DeclarationFragments::FragmentKind::Identifier, USR);
+ break;
+ }
+
+ case NestedNameSpecifier::NamespaceAlias: {
+ const NamespaceAliasDecl *Alias = NNS->getAsNamespaceAlias();
+ SmallString<128> USR;
+ index::generateUSRForDecl(Alias, USR);
+ Fragments.append(Alias->getName(),
+ DeclarationFragments::FragmentKind::Identifier, USR);
+ break;
+ }
+
+ case NestedNameSpecifier::Global:
+ // The global specifier `::` at the beginning. No stored value.
+ break;
+
+ case NestedNameSpecifier::Super:
+ // Microsoft's `__super` specifier.
+ Fragments.append("__super", DeclarationFragments::FragmentKind::Keyword);
+ break;
+
+ case NestedNameSpecifier::TypeSpecWithTemplate:
+ // A type prefixed by the `template` keyword.
+ Fragments.append("template", DeclarationFragments::FragmentKind::Keyword);
+ Fragments.appendSpace();
+ // Fallthrough after adding the keyword to handle the actual type.
+ LLVM_FALLTHROUGH;
+
+ case NestedNameSpecifier::TypeSpec: {
+ const Type *T = NNS->getAsType();
+ // FIXME: Handle C++ template specialization type
+ Fragments.append(getFragmentsForType(T, Context, After));
+ break;
+ }
+ }
+
+ // Add the separator text `::` for this segment.
+ return Fragments.append("::", DeclarationFragments::FragmentKind::Text);
+}
+
+// Recursively build the declaration fragments for an underlying `Type` with
+// qualifiers removed.
+DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForType(
+ const Type *T, ASTContext &Context, DeclarationFragments &After) {
+ assert(T && "invalid type");
+
+ DeclarationFragments Fragments;
+
+ // Declaration fragments of a pointer type is the declaration fragments of
+ // the pointee type followed by a `*`, except for Objective-C `id` and `Class`
+ // pointers, where we do not spell out the `*`.
+ if (T->isPointerType() ||
+ (T->isObjCObjectPointerType() &&
+ !T->getAs<ObjCObjectPointerType>()->isObjCIdOrClassType())) {
+ return Fragments
+ .append(getFragmentsForType(T->getPointeeType(), Context, After))
+ .append(" *", DeclarationFragments::FragmentKind::Text);
+ }
+
+ // Declaration fragments of a lvalue reference type is the declaration
+ // fragments of the underlying type followed by a `&`.
+ if (const LValueReferenceType *LRT = dyn_cast<LValueReferenceType>(T))
+ return Fragments
+ .append(
+ getFragmentsForType(LRT->getPointeeTypeAsWritten(), Context, After))
+ .append(" &", DeclarationFragments::FragmentKind::Text);
+
+ // Declaration fragments of a rvalue reference type is the declaration
+ // fragments of the underlying type followed by a `&&`.
+ if (const RValueReferenceType *RRT = dyn_cast<RValueReferenceType>(T))
+ return Fragments
+ .append(
+ getFragmentsForType(RRT->getPointeeTypeAsWritten(), Context, After))
+ .append(" &&", DeclarationFragments::FragmentKind::Text);
+
+ // Declaration fragments of an array-typed variable have two parts:
+ // 1. the element type of the array that appears before the variable name;
+ // 2. array brackets `[(0-9)?]` that appear after the variable name.
+ if (const ArrayType *AT = T->getAsArrayTypeUnsafe()) {
+ // Build the "after" part first because the inner element type might also
+ // be an array-type. For example `int matrix[3][4]` which has a type of
+ // "(array 3 of (array 4 of ints))."
+ // Push the array size part first to make sure they are in the right order.
+ After.append("[", DeclarationFragments::FragmentKind::Text);
+
+ switch (AT->getSizeModifier()) {
+ case ArrayType::Normal:
+ break;
+ case ArrayType::Static:
+ Fragments.append("static", DeclarationFragments::FragmentKind::Keyword);
+ break;
+ case ArrayType::Star:
+ Fragments.append("*", DeclarationFragments::FragmentKind::Text);
+ break;
+ }
+
+ if (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(AT)) {
+ // FIXME: right now this would evaluate any expressions/macros written in
+ // the original source to concrete values. For example
+ // `int nums[MAX]` -> `int nums[100]`
+ // `char *str[5 + 1]` -> `char *str[6]`
+ SmallString<128> Size;
+ CAT->getSize().toStringUnsigned(Size);
+ After.append(Size, DeclarationFragments::FragmentKind::NumberLiteral);
+ }
+
+ After.append("]", DeclarationFragments::FragmentKind::Text);
+
+ return Fragments.append(
+ getFragmentsForType(AT->getElementType(), Context, After));
+ }
+
+ // An ElaboratedType is a sugar for types that are referred to using an
+ // elaborated keyword, e.g., `struct S`, `enum E`, or (in C++) via a
+ // qualified name, e.g., `N::M::type`, or both.
+ if (const ElaboratedType *ET = dyn_cast<ElaboratedType>(T)) {
+ ElaboratedTypeKeyword Keyword = ET->getKeyword();
+ if (Keyword != ETK_None) {
+ Fragments
+ .append(ElaboratedType::getKeywordName(Keyword),
+ DeclarationFragments::FragmentKind::Keyword)
+ .appendSpace();
+ }
+
+ if (const NestedNameSpecifier *NNS = ET->getQualifier())
+ Fragments.append(getFragmentsForNNS(NNS, Context, After));
+
+ // After handling the elaborated keyword or qualified name, build
+ // declaration fragments for the desugared underlying type.
+ return Fragments.append(getFragmentsForType(ET->desugar(), Context, After));
+ }
+
+ // Everything we care about has been handled now, reduce to the canonical
+ // unqualified base type.
+ QualType Base = T->getCanonicalTypeUnqualified();
+
+ // Default fragment builder for other kinds of types (BuiltinType etc.)
+ SmallString<128> USR;
+ clang::index::generateUSRForType(Base, Context, USR);
+ Fragments.append(Base.getAsString(),
+ DeclarationFragments::FragmentKind::TypeIdentifier, USR);
+
+ return Fragments;
+}
+
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForQualifiers(const Qualifiers Quals) {
+ DeclarationFragments Fragments;
+ if (Quals.hasConst())
+ Fragments.append("const", DeclarationFragments::FragmentKind::Keyword);
+ if (Quals.hasVolatile())
+ Fragments.append("volatile", DeclarationFragments::FragmentKind::Keyword);
+ if (Quals.hasRestrict())
+ Fragments.append("restrict", DeclarationFragments::FragmentKind::Keyword);
+
+ return Fragments;
+}
+
+DeclarationFragments DeclarationFragmentsBuilder::getFragmentsForType(
+ const QualType QT, ASTContext &Context, DeclarationFragments &After) {
+ assert(!QT.isNull() && "invalid type");
+
+ if (const ParenType *PT = dyn_cast<ParenType>(QT)) {
+ After.append(")", DeclarationFragments::FragmentKind::Text);
+ return getFragmentsForType(PT->getInnerType(), Context, After)
+ .append("(", DeclarationFragments::FragmentKind::Text);
+ }
+
+ const SplitQualType SQT = QT.split();
+ DeclarationFragments QualsFragments = getFragmentsForQualifiers(SQT.Quals),
+ TypeFragments =
+ getFragmentsForType(SQT.Ty, Context, After);
+ if (QualsFragments.getFragments().empty())
+ return TypeFragments;
+
+ // Use east qualifier for pointer types
+ // For example:
+ // ```
+ // int * const
+ // ^---- ^----
+ // type qualifier
+ // ^-----------------
+ // const pointer to int
+ // ```
+ // should not be reconstructed as
+ // ```
+ // const int *
+ // ^---- ^--
+ // qualifier type
+ // ^---------------- ^
+ // pointer to const int
+ // ```
+ if (SQT.Ty->isAnyPointerType())
+ return TypeFragments.appendSpace().append(std::move(QualsFragments));
+
+ return QualsFragments.appendSpace().append(std::move(TypeFragments));
+}
+
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForVar(const VarDecl *Var) {
+ DeclarationFragments Fragments;
+ StorageClass SC = Var->getStorageClass();
+ if (SC != SC_None)
+ Fragments
+ .append(VarDecl::getStorageClassSpecifierString(SC),
+ DeclarationFragments::FragmentKind::Keyword)
+ .appendSpace();
+ QualType T =
+ Var->getTypeSourceInfo()
+ ? Var->getTypeSourceInfo()->getType()
+ : Var->getASTContext().getUnqualifiedObjCPointerType(Var->getType());
+
+ // Capture potential fragments that needs to be placed after the variable name
+ // ```
+ // int nums[5];
+ // char (*ptr_to_array)[6];
+ // ```
+ DeclarationFragments After;
+ return Fragments.append(getFragmentsForType(T, Var->getASTContext(), After))
+ .appendSpace()
+ .append(Var->getName(), DeclarationFragments::FragmentKind::Identifier)
+ .append(std::move(After));
+}
+
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForParam(const ParmVarDecl *Param) {
+ DeclarationFragments Fragments, After;
+
+ QualType T = Param->getTypeSourceInfo()
+ ? Param->getTypeSourceInfo()->getType()
+ : Param->getASTContext().getUnqualifiedObjCPointerType(
+ Param->getType());
+
+ DeclarationFragments TypeFragments =
+ getFragmentsForType(T, Param->getASTContext(), After);
+
+ if (Param->isObjCMethodParameter())
+ Fragments.append("(", DeclarationFragments::FragmentKind::Text)
+ .append(std::move(TypeFragments))
+ .append(")", DeclarationFragments::FragmentKind::Text);
+ else
+ Fragments.append(std::move(TypeFragments)).appendSpace();
+
+ return Fragments
+ .append(Param->getName(),
+ DeclarationFragments::FragmentKind::InternalParam)
+ .append(std::move(After));
+}
+
+DeclarationFragments
+DeclarationFragmentsBuilder::getFragmentsForFunction(const FunctionDecl *Func) {
+ DeclarationFragments Fragments;
+ // FIXME: Handle template specialization
+ switch (Func->getStorageClass()) {
+ case SC_None:
+ case SC_PrivateExtern:
+ break;
+ case SC_Extern:
+ Fragments.append("extern", DeclarationFragments::FragmentKind::Keyword)
+ .appendSpace();
+ break;
+ case SC_Static:
+ Fragments.append("static", DeclarationFragments::FragmentKind::Keyword)
+ .appendSpace();
+ break;
+ case SC_Auto:
+ case SC_Register:
+ llvm_unreachable("invalid for functions");
+ }
+ // FIXME: Handle C++ function specifiers: constexpr, consteval, explicit, etc.
+
+ // FIXME: Is `after` actually needed here?
+ DeclarationFragments After;
+ Fragments
+ .append(getFragmentsForType(Func->getReturnType(), Func->getASTContext(),
+ After))
+ .appendSpace()
+ .append(Func->getName(), DeclarationFragments::FragmentKind::Identifier)
+ .append(std::move(After));
+
+ Fragments.append("(", DeclarationFragments::FragmentKind::Text);
+ for (unsigned i = 0, end = Func->getNumParams(); i != end; ++i) {
+ if (i)
+ Fragments.append(", ", DeclarationFragments::FragmentKind::Text);
+ Fragments.append(getFragmentsForParam(Func->getParamDecl(i)));
+ }
+ Fragments.append(")", DeclarationFragments::FragmentKind::Text);
+
+ // FIXME: Handle exception specifiers: throw, noexcept
+ return Fragments;
+}
+
+FunctionSignature
+DeclarationFragmentsBuilder::getFunctionSignature(const FunctionDecl *Func) {
+ FunctionSignature Signature;
+
+ for (const auto *Param : Func->parameters()) {
+ StringRef Name = Param->getName();
+ DeclarationFragments Fragments = getFragmentsForParam(Param);
+
+ Signature.addParameter(Name, Fragments);
+ }
+
+ DeclarationFragments After;
+ DeclarationFragments Returns =
+ getFragmentsForType(Func->getReturnType(), Func->getASTContext(), After)
+ .append(std::move(After));
+
+ Signature.setReturnType(Returns);
+
+ return Signature;
+}
+
+// Subheading of a symbol defaults to its name.
+DeclarationFragments
+DeclarationFragmentsBuilder::getSubHeading(const NamedDecl *Decl) {
+ DeclarationFragments Fragments;
+ if (!Decl->getName().empty())
+ Fragments.append(Decl->getName(),
+ DeclarationFragments::FragmentKind::Identifier);
+ return Fragments;
+}
+
+} // namespace symbolgraph
+} // namespace clang
diff --git a/clang/lib/SymbolGraph/ExtractAPIConsumer.cpp b/clang/lib/SymbolGraph/ExtractAPIConsumer.cpp
new file mode 100644
index 0000000000000..45943c09938f4
--- /dev/null
+++ b/clang/lib/SymbolGraph/ExtractAPIConsumer.cpp
@@ -0,0 +1,205 @@
+//===- ExtractAPIConsumer.cpp -----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines the ExtractAPI AST visitor to collect API information.
+///
+//===----------------------------------------------------------------------===//
+//
+
+#include "clang/AST/ASTConsumer.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/ParentMapContext.h"
+#include "clang/AST/RawCommentList.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/Frontend/ASTConsumers.h"
+#include "clang/Frontend/CompilerInstance.h"
+#include "clang/SymbolGraph/API.h"
+#include "clang/SymbolGraph/AvailabilityInfo.h"
+#include "clang/SymbolGraph/DeclarationFragments.h"
+#include "clang/SymbolGraph/FrontendActions.h"
+#include "clang/SymbolGraph/Serialization.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace symbolgraph;
+
+namespace {
+class ExtractAPIVisitor : public RecursiveASTVisitor<ExtractAPIVisitor> {
+public:
+ explicit ExtractAPIVisitor(ASTContext &Context)
+ : Context(Context),
+ API(Context.getTargetInfo().getTriple(), Context.getLangOpts()) {}
+
+ const API &getAPI() const { return API; }
+
+ bool VisitVarDecl(const VarDecl *Decl) {
+ // Skip function parameters.
+ if (isa<ParmVarDecl>(Decl))
+ return true;
+
+ // Skip non-global variables in records (struct/union/class).
+ if (Decl->getDeclContext()->isRecord())
+ return true;
+
+ // Skip local variables inside function or method.
+ if (!Decl->isDefinedOutsideFunctionOrMethod())
+ return true;
+
+ // If this is a template but not specialization or instantiation, skip.
+ if (Decl->getASTContext().getTemplateOrSpecializationInfo(Decl) &&
+ Decl->getTemplateSpecializationKind() == TSK_Undeclared)
+ return true;
+
+ StringRef Name = Decl->getName();
+ StringRef USR = API.recordUSR(Decl);
+ PresumedLoc Loc =
+ Context.getSourceManager().getPresumedLoc(Decl->getLocation());
+ AvailabilityInfo Availability = getAvailability(Decl);
+ LinkageInfo Linkage = Decl->getLinkageAndVisibility();
+ DocComment Comment;
+ if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
+ Comment = RawComment->getFormattedLines(Context.getSourceManager(),
+ Context.getDiagnostics());
+ DeclarationFragments Declaration =
+ DeclarationFragmentsBuilder::getFragmentsForVar(Decl);
+ DeclarationFragments SubHeading =
+ DeclarationFragmentsBuilder::getSubHeading(Decl);
+
+ API.addGlobalVar(Name, USR, Loc, Availability, Linkage, Comment,
+ Declaration, SubHeading);
+ return true;
+ }
+
+ bool VisitFunctionDecl(const FunctionDecl *Decl) {
+ if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
+ // Skip member function in class templates.
+ if (Method->getParent()->getDescribedClassTemplate() != nullptr)
+ return true;
+
+ // Skip methods in records.
+ for (auto P : Context.getParents(*Method)) {
+ if (P.get<CXXRecordDecl>())
+ return true;
+ }
+
+ // Skip ConstructorDecl and DestructorDecl.
+ if (isa<CXXConstructorDecl>(Method) || isa<CXXDestructorDecl>(Method))
+ return true;
+ }
+
+ // Skip templated functions.
+ switch (Decl->getTemplatedKind()) {
+ case FunctionDecl::TK_NonTemplate:
+ break;
+ case FunctionDecl::TK_MemberSpecialization:
+ case FunctionDecl::TK_FunctionTemplateSpecialization:
+ if (auto *TemplateInfo = Decl->getTemplateSpecializationInfo()) {
+ if (!TemplateInfo->isExplicitInstantiationOrSpecialization())
+ return true;
+ }
+ break;
+ case FunctionDecl::TK_FunctionTemplate:
+ case FunctionDecl::TK_DependentFunctionTemplateSpecialization:
+ return true;
+ }
+
+ StringRef Name = Decl->getName();
+ StringRef USR = API.recordUSR(Decl);
+ PresumedLoc Loc =
+ Context.getSourceManager().getPresumedLoc(Decl->getLocation());
+ AvailabilityInfo Availability = getAvailability(Decl);
+ LinkageInfo Linkage = Decl->getLinkageAndVisibility();
+ DocComment Comment;
+ if (auto *RawComment = Context.getRawCommentForDeclNoCache(Decl))
+ Comment = RawComment->getFormattedLines(Context.getSourceManager(),
+ Context.getDiagnostics());
+ DeclarationFragments Declaration =
+ DeclarationFragmentsBuilder::getFragmentsForFunction(Decl);
+ DeclarationFragments SubHeading =
+ DeclarationFragmentsBuilder::getSubHeading(Decl);
+ FunctionSignature Signature =
+ DeclarationFragmentsBuilder::getFunctionSignature(Decl);
+
+ API.addFunction(Name, USR, Loc, Availability, Linkage, Comment, Declaration,
+ SubHeading, Signature);
+ return true;
+ }
+
+private:
+ AvailabilityInfo getAvailability(const Decl *D) const {
+ StringRef PlatformName = Context.getTargetInfo().getPlatformName();
+
+ AvailabilityInfo Availability;
+ for (const auto *RD : D->redecls()) {
+ for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
+ if (A->getPlatform()->getName() != PlatformName)
+ continue;
+ Availability = AvailabilityInfo(A->getIntroduced(), A->getDeprecated(),
+ A->getObsoleted(), A->getUnavailable(),
+ /* UnconditionallyDeprecated */ false,
+ /* UnconditionallyUnavailable */ false);
+ break;
+ }
+
+ if (const auto *A = RD->getAttr<UnavailableAttr>())
+ if (!A->isImplicit()) {
+ Availability.Unavailable = true;
+ Availability.UnconditionallyUnavailable = true;
+ }
+
+ if (const auto *A = RD->getAttr<DeprecatedAttr>())
+ if (!A->isImplicit())
+ Availability.UnconditionallyDeprecated = true;
+ }
+
+ return Availability;
+ }
+
+ ASTContext &Context;
+ API API;
+};
+
+class ExtractAPIConsumer : public ASTConsumer {
+public:
+ ExtractAPIConsumer(ASTContext &Context, std::unique_ptr<raw_pwrite_stream> OS)
+ : Visitor(Context), OS(std::move(OS)) {}
+
+ void HandleTranslationUnit(ASTContext &Context) override {
+ Visitor.TraverseDecl(Context.getTranslationUnitDecl());
+ Serializer Serializer(Visitor.getAPI());
+ Serializer.serialize(*OS);
+ }
+
+private:
+ ExtractAPIVisitor Visitor;
+ std::unique_ptr<raw_pwrite_stream> OS;
+};
+} // namespace
+
+std::unique_ptr<ASTConsumer>
+ExtractAPIAction::CreateASTConsumer(CompilerInstance &CI, StringRef InFile) {
+ std::unique_ptr<raw_pwrite_stream> OS = CreateOutputFile(CI, InFile);
+ if (!OS)
+ return nullptr;
+ return std::make_unique<ExtractAPIConsumer>(CI.getASTContext(),
+ std::move(OS));
+}
+
+std::unique_ptr<raw_pwrite_stream>
+ExtractAPIAction::CreateOutputFile(CompilerInstance &CI, StringRef InFile) {
+ std::unique_ptr<raw_pwrite_stream> OS =
+ CI.createDefaultOutputFile(/*Binary=*/false, InFile, /*Extension=*/"json",
+ /*RemoveFileOnSignal=*/false);
+ if (!OS)
+ return nullptr;
+ return OS;
+}
diff --git a/clang/lib/SymbolGraph/Serialization.cpp b/clang/lib/SymbolGraph/Serialization.cpp
new file mode 100644
index 0000000000000..b07b19a93ac6c
--- /dev/null
+++ b/clang/lib/SymbolGraph/Serialization.cpp
@@ -0,0 +1,332 @@
+//===- SymbolGraph/Serialization.cpp ----------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Defines the SymbolGraph serializer and parser.
+///
+//===----------------------------------------------------------------------===//
+
+#include "clang/SymbolGraph/Serialization.h"
+#include "clang/Basic/Version.h"
+#include "clang/SymbolGraph/API.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/VersionTuple.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace clang::symbolgraph;
+using namespace llvm;
+using namespace llvm::json;
+
+namespace {
+
+static void serializeObject(Object &Paren, StringRef Key,
+ Optional<Object> Obj) {
+ if (Obj)
+ Paren[Key] = std::move(Obj.getValue());
+}
+
+static void serializeArray(Object &Paren, StringRef Key,
+ Optional<Array> Array) {
+ if (Array)
+ Paren[Key] = std::move(Array.getValue());
+}
+
+// SymbolGraph: SemanticVersion
+static Optional<Object> serializeSemanticVersion(const VersionTuple &V) {
+ if (V.empty())
+ return None;
+
+ Object Version;
+ Version["major"] = V.getMajor();
+ Version["minor"] = V.getMinor().getValueOr(0);
+ Version["patch"] = V.getSubminor().getValueOr(0);
+ return Version;
+}
+
+static Object serializeOperatingSystem(const Triple &T) {
+ Object OS;
+ OS["name"] = T.getOSTypeName(T.getOS());
+ serializeObject(OS, "minimumVersion",
+ serializeSemanticVersion(T.getMinimumSupportedOSVersion()));
+ return OS;
+}
+
+// SymbolGraph: Platform
+static Object serializePlatform(const Triple &T) {
+ Object Platform;
+ Platform["architecture"] = T.getArchName();
+ Platform["vendor"] = T.getVendorName();
+ Platform["operatingSystem"] = serializeOperatingSystem(T);
+ return Platform;
+}
+
+// SymbolGraph: SourcePosition
+static Object serializeSourcePosition(const PresumedLoc &Loc,
+ bool IncludeFileURI = false) {
+ assert(Loc.isValid() && "invalid source position");
+
+ Object SourcePosition;
+ SourcePosition["line"] = Loc.getLine();
+ SourcePosition["character"] = Loc.getColumn();
+
+ if (IncludeFileURI) {
+ std::string FileURI = "file://";
+ FileURI += sys::path::convert_to_slash(Loc.getFilename());
+ SourcePosition["uri"] = FileURI;
+ }
+
+ return SourcePosition;
+}
+
+// SymbolGraph: SourceRange
+static Object serializeSourceRange(const PresumedLoc &BeginLoc,
+ const PresumedLoc &EndLoc) {
+ Object SourceRange;
+ serializeObject(SourceRange, "start", serializeSourcePosition(BeginLoc));
+ serializeObject(SourceRange, "end", serializeSourcePosition(EndLoc));
+ return SourceRange;
+}
+
+// SymbolGraph: AvailabilityItem
+static Optional<Object> serializeAvailability(const AvailabilityInfo &Avail) {
+ if (Avail.isDefault())
+ return None;
+
+ Object Availbility;
+ serializeObject(Availbility, "introducedVersion",
+ serializeSemanticVersion(Avail.Introduced));
+ serializeObject(Availbility, "deprecatedVersion",
+ serializeSemanticVersion(Avail.Deprecated));
+ serializeObject(Availbility, "obsoletedVersion",
+ serializeSemanticVersion(Avail.Obsoleted));
+ if (Avail.isUnavailable())
+ Availbility["isUnconditionallyUnavailable"] = true;
+ if (Avail.isUnconditionallyDeprecated())
+ Availbility["isUnconditionallyDeprecated"] = true;
+
+ return Availbility;
+}
+
+static StringRef getLanguageName(const LangOptions &LangOpts) {
+ auto Language =
+ LangStandard::getLangStandardForKind(LangOpts.LangStd).getLanguage();
+ switch (Language) {
+ case Language::C:
+ return "c";
+ case Language::ObjC:
+ return "objc";
+
+ // Unsupported language currently
+ case Language::CXX:
+ case Language::ObjCXX:
+ case Language::OpenCL:
+ case Language::OpenCLCXX:
+ case Language::CUDA:
+ case Language::RenderScript:
+ case Language::HIP:
+
+ // Languages that the frontend cannot parse and compile
+ case Language::Unknown:
+ case Language::Asm:
+ case Language::LLVM_IR:
+ llvm_unreachable("Unsupported language kind");
+ }
+
+ llvm_unreachable("Unhandled language kind");
+}
+
+// SymbolGraph: Symbol::identifier
+static Object serializeIdentifier(const APIRecord &Record,
+ const LangOptions &LangOpts) {
+ Object Identifier;
+ Identifier["precise"] = Record.USR;
+ Identifier["interfaceLanguage"] = getLanguageName(LangOpts);
+
+ return Identifier;
+}
+
+// SymbolGraph: DocComment
+static Optional<Object> serializeDocComment(const DocComment &Comment) {
+ if (Comment.empty())
+ return None;
+
+ Object DocComment;
+ Array LinesArray;
+ for (const auto &CommentLine : Comment) {
+ Object Line;
+ Line["text"] = CommentLine.Text;
+ serializeObject(Line, "range",
+ serializeSourceRange(CommentLine.Begin, CommentLine.End));
+ LinesArray.emplace_back(std::move(Line));
+ }
+ serializeArray(DocComment, "lines", LinesArray);
+
+ return DocComment;
+}
+
+static Optional<Array>
+serializeDeclarationFragments(const DeclarationFragments &DF) {
+ if (DF.getFragments().empty())
+ return None;
+
+ Array Fragments;
+ for (const auto &F : DF.getFragments()) {
+ Object Fragment;
+ Fragment["spelling"] = F.Spelling;
+ Fragment["kind"] = DeclarationFragments::getFragmentKindString(F.Kind);
+ if (!F.PreciseIdentifier.empty())
+ Fragment["preciseIdentifier"] = F.PreciseIdentifier;
+ Fragments.emplace_back(std::move(Fragment));
+ }
+
+ return Fragments;
+}
+
+static Optional<Object>
+serializeFunctionSignature(const FunctionSignature &FS) {
+ if (FS.empty())
+ return None;
+
+ Object Signature;
+ serializeArray(Signature, "returns",
+ serializeDeclarationFragments(FS.getReturnType()));
+
+ Array Parameters;
+ for (const auto &P : FS.getParameters()) {
+ Object Parameter;
+ Parameter["name"] = P.Name;
+ serializeArray(Parameter, "declarationFragments",
+ serializeDeclarationFragments(P.Fragments));
+ Parameters.emplace_back(std::move(Parameter));
+ }
+
+ if (!Parameters.empty())
+ Signature["parameters"] = std::move(Parameters);
+
+ return Signature;
+}
+
+static Object serializeNames(const APIRecord &Record) {
+ Object Names;
+ Names["title"] = Record.Name;
+ serializeArray(Names, "subHeading",
+ serializeDeclarationFragments(Record.SubHeading));
+
+ return Names;
+}
+
+// SymbolGraph: Symbol::kind
+static Object serializeSymbolKind(const APIRecord &Record,
+ const LangOptions &LangOpts) {
+ Object Kind;
+ switch (Record.getKind()) {
+ case APIRecord::RK_Global:
+ auto *GR = dyn_cast<GlobalRecord>(&Record);
+ switch (GR->GlobalKind) {
+ case GVKind::Function:
+ Kind["identifier"] = (getLanguageName(LangOpts) + ".func").str();
+ Kind["displayName"] = "Function";
+ break;
+ case GVKind::Variable:
+ Kind["identifier"] = (getLanguageName(LangOpts) + ".var").str();
+ Kind["displayName"] = "Global Variable";
+ break;
+ case GVKind::Unknown:
+ // Unknown global kind
+ break;
+ }
+ break;
+ }
+
+ return Kind;
+}
+
+} // namespace
+
+const VersionTuple Serializer::FormatVersion{0, 5, 3};
+
+Object Serializer::serializeMetadata() const {
+ Object Metadata;
+ serializeObject(Metadata, "formatVersion",
+ serializeSemanticVersion(FormatVersion));
+ Metadata["generator"] = clang::getClangFullVersion();
+ return Metadata;
+}
+
+Object Serializer::serializeModule() const {
+ Object Module;
+ // FIXME: What to put in here?
+ Module["name"] = "";
+ serializeObject(Module, "platform", serializePlatform(API.getTarget()));
+ return Module;
+}
+
+bool Serializer::shouldSkip(const APIRecord &Record) const {
+ // Skip unconditionally unavailable symbols
+ if (Record.Availability.isUnconditionallyUnavailable())
+ return true;
+
+ return false;
+}
+
+Optional<Object> Serializer::serializeAPIRecord(const APIRecord &Record) const {
+ if (shouldSkip(Record))
+ return None;
+
+ Object Obj;
+ serializeObject(Obj, "identifier",
+ serializeIdentifier(Record, API.getLangOpts()));
+ serializeObject(Obj, "kind", serializeSymbolKind(Record, API.getLangOpts()));
+ serializeObject(Obj, "names", serializeNames(Record));
+ serializeObject(
+ Obj, "location",
+ serializeSourcePosition(Record.Location, /*IncludeFileURI=*/true));
+ serializeObject(Obj, "availbility",
+ serializeAvailability(Record.Availability));
+ serializeObject(Obj, "docComment", serializeDocComment(Record.Comment));
+ serializeArray(Obj, "declarationFragments",
+ serializeDeclarationFragments(Record.Declaration));
+
+ return Obj;
+}
+
+void Serializer::serializeGlobalRecord(const GlobalRecord &Record) {
+ auto Obj = serializeAPIRecord(Record);
+ if (!Obj)
+ return;
+
+ if (Record.GlobalKind == GVKind::Function)
+ serializeObject(*Obj, "parameters",
+ serializeFunctionSignature(Record.Signature));
+
+ Symbols.emplace_back(std::move(*Obj));
+}
+
+Object Serializer::serialize() {
+ Object Root;
+ serializeObject(Root, "metadata", serializeMetadata());
+ serializeObject(Root, "module", serializeModule());
+
+ for (const auto &Global : API.getGlobals())
+ serializeGlobalRecord(*Global.second);
+
+ Root["symbols"] = std::move(Symbols);
+ Root["relationhips"] = std::move(Relationships);
+
+ return Root;
+}
+
+void Serializer::serialize(raw_ostream &os) {
+ Object root = serialize();
+ if (Options.Compact)
+ os << formatv("{0}", Value(std::move(root))) << "\n";
+ else
+ os << formatv("{0:2}", Value(std::move(root))) << "\n";
+}
diff --git a/clang/test/Driver/extract-api.c b/clang/test/Driver/extract-api.c
index 4aba3ba9f61ab..4a332817d8de5 100644
--- a/clang/test/Driver/extract-api.c
+++ b/clang/test/Driver/extract-api.c
@@ -8,9 +8,3 @@
// EXTRACT-API-PHASES: 2: compiler, {1}, api-information
// EXTRACT-API-PHASES-NOT: 3:
// EXTRACT-API-PHASES: END
-
-// FIXME: Check for the dummy output now to verify that the custom action was executed.
-// RUN: %clang -extract-api %s | FileCheck -check-prefix DUMMY-OUTPUT %s
-
-void dummy_function(void);
-// DUMMY-OUTPUT: dummy_function
diff --git a/clang/test/SymbolGraph/global_record.c b/clang/test/SymbolGraph/global_record.c
new file mode 100644
index 0000000000000..ba4bf967e6302
--- /dev/null
+++ b/clang/test/SymbolGraph/global_record.c
@@ -0,0 +1,367 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s at INPUT_DIR@%/t at g" %t/reference.output.json.in >> \
+// RUN: %t/reference.output.json
+// RUN: %clang -extract-api -target arm64-apple-macosx \
+// RUN: %t/input.c -o %t/output.json | FileCheck -allow-empty %s
+// RUN: sed -e "s@\"generator\": \"clang.*\"@\"generator\": \"clang\"@g" \
+// RUN: %t/output.json >> %t/output-normalized.json
+// RUN:
diff %t/reference.output.json %t/output-normalized.json
+
+// CHECK-NOT: error:
+// CHECK-NOT: warning:
+
+//--- input.c
+int num;
+
+/**
+ * \brief Add two numbers.
+ * \param [in] x A number.
+ * \param [in] y Another number.
+ * \param [out] res The result of x + y.
+ */
+void add(const int x, const int y, int *res);
+
+char unavailable __attribute__((unavailable));
+
+//--- reference.output.json.in
+{
+ "metadata": {
+ "formatVersion": {
+ "major": 0,
+ "minor": 5,
+ "patch": 3
+ },
+ "generator": "clang"
+ },
+ "module": {
+ "name": "",
+ "platform": {
+ "architecture": "arm64",
+ "operatingSystem": {
+ "minimumVersion": {
+ "major": 11,
+ "minor": 0,
+ "patch": 0
+ },
+ "name": "macosx"
+ },
+ "vendor": "apple"
+ }
+ },
+ "relationhips": [],
+ "symbols": [
+ {
+ "declarationFragments": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "num"
+ }
+ ],
+ "identifier": {
+ "interfaceLanguage": "c",
+ "precise": "c:@num"
+ },
+ "kind": {
+ "displayName": "Global Variable",
+ "identifier": "c.var"
+ },
+ "location": {
+ "character": 5,
+ "line": 1,
+ "uri": "file://INPUT_DIR/input.c"
+ },
+ "names": {
+ "subHeading": [
+ {
+ "kind": "identifier",
+ "spelling": "num"
+ }
+ ],
+ "title": "num"
+ }
+ },
+ {
+ "declarationFragments": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "identifier",
+ "spelling": "add"
+ },
+ {
+ "kind": "text",
+ "spelling": "("
+ },
+ {
+ "kind": "keyword",
+ "spelling": "const"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "internalParam",
+ "spelling": "x"
+ },
+ {
+ "kind": "text",
+ "spelling": ", "
+ },
+ {
+ "kind": "keyword",
+ "spelling": "const"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "internalParam",
+ "spelling": "y"
+ },
+ {
+ "kind": "text",
+ "spelling": ", "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": " *"
+ },
+ {
+ "kind": "internalParam",
+ "spelling": "res"
+ },
+ {
+ "kind": "text",
+ "spelling": ")"
+ }
+ ],
+ "docComment": {
+ "lines": [
+ {
+ "range": {
+ "end": {
+ "character": 4,
+ "line": 3
+ },
+ "start": {
+ "character": 4,
+ "line": 3
+ }
+ },
+ "text": ""
+ },
+ {
+ "range": {
+ "end": {
+ "character": 27,
+ "line": 4
+ },
+ "start": {
+ "character": 3,
+ "line": 4
+ }
+ },
+ "text": " \\brief Add two numbers."
+ },
+ {
+ "range": {
+ "end": {
+ "character": 30,
+ "line": 5
+ },
+ "start": {
+ "character": 3,
+ "line": 5
+ }
+ },
+ "text": " \\param [in] x A number."
+ },
+ {
+ "range": {
+ "end": {
+ "character": 36,
+ "line": 6
+ },
+ "start": {
+ "character": 3,
+ "line": 6
+ }
+ },
+ "text": " \\param [in] y Another number."
+ },
+ {
+ "range": {
+ "end": {
+ "character": 41,
+ "line": 7
+ },
+ "start": {
+ "character": 3,
+ "line": 7
+ }
+ },
+ "text": " \\param [out] res The result of x + y."
+ },
+ {
+ "range": {
+ "end": {
+ "character": 4,
+ "line": 8
+ },
+ "start": {
+ "character": 1,
+ "line": 8
+ }
+ },
+ "text": " "
+ }
+ ]
+ },
+ "identifier": {
+ "interfaceLanguage": "c",
+ "precise": "c:@F at add"
+ },
+ "kind": {
+ "displayName": "Function",
+ "identifier": "c.func"
+ },
+ "location": {
+ "character": 6,
+ "line": 9,
+ "uri": "file://INPUT_DIR/input.c"
+ },
+ "names": {
+ "subHeading": [
+ {
+ "kind": "identifier",
+ "spelling": "add"
+ }
+ ],
+ "title": "add"
+ },
+ "parameters": {
+ "parameters": [
+ {
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "const"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "internalParam",
+ "spelling": "x"
+ }
+ ],
+ "name": "x"
+ },
+ {
+ "declarationFragments": [
+ {
+ "kind": "keyword",
+ "spelling": "const"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": " "
+ },
+ {
+ "kind": "internalParam",
+ "spelling": "y"
+ }
+ ],
+ "name": "y"
+ },
+ {
+ "declarationFragments": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:I",
+ "spelling": "int"
+ },
+ {
+ "kind": "text",
+ "spelling": " *"
+ },
+ {
+ "kind": "internalParam",
+ "spelling": "res"
+ }
+ ],
+ "name": "res"
+ }
+ ],
+ "returns": [
+ {
+ "kind": "typeIdentifier",
+ "preciseIdentifier": "c:v",
+ "spelling": "void"
+ }
+ ]
+ }
+ }
+ ]
+}
More information about the cfe-commits
mailing list