tools/apiview/parsers/cpp-api-parser/ApiViewProcessor/JsonDumper.hpp (342 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT #pragma once #include "AstDumper.hpp" #include <iostream> #include <nlohmann/json.hpp> #include <string> #include <string_view> #include <unordered_set> using namespace nlohmann::literals; class JsonDumper : public AstDumper { nlohmann::json m_json; enum class TokenKinds { Text = 0, Newline = 1, Whitespace = 2, Punctuation = 3, Keyword = 4, LineIdMarker = 5, // use this if there are no visible tokens with ID on the line but you still // want to be able to leave a comment for it TypeName = 6, MemberName = 7, StringLiteral = 8, Literal = 9, Comment = 10, DocumentRangeStart = 11, DocumentRangeEnd = 12, DeprecatedRangeStart = 13, DeprecatedRangeEnd = 14, SkipDiffRangeStart = 15, SkipDiffRangeEnd = 16, FoldableSectionHeading = 17, FoldableSectionContentStart = 18, FoldableSectionContentEnd = 19, TableBegin = 20, TableEnd = 21, TableRowCount = 22, TableColumnCount = 23, TableColumnName = 24, TableCellBegin = 25, TableCellEnd = 26, LeafSectionPlaceholder = 27, ExternalLinkStart = 28, ExternalLinkEnd = 29, HiddenApiRangeStart = 30, HiddenApiRangeEnd = 31 }; // Validate that the json we've created won't cause problems for ApiView. void ValidateJson() { std::unordered_set<std::string> definitions; for (const auto& token : m_json["Tokens"]) { if (token.contains("DefinitionId") && token["DefinitionId"].is_string()) { auto definitionId = token["DefinitionId"].get<std::string>(); if (definitions.find(definitionId) != definitions.end()) { throw std::runtime_error("Duplicate DefinitionId: " + definitionId); } definitions.emplace(definitionId); } if (!token.contains("Value")) { throw std::runtime_error("Missing Value in token"); } if (!token.contains("Kind")) { throw std::runtime_error("Missing Kind in token"); } } } public: JsonDumper( std::string_view reviewName, std::string_view serviceName, std::string_view packageName, std::string_view packageVersion = "") : AstDumper() { m_json["Name"] = reviewName; m_json["Language"] = "C++"; m_json["ServiceName"] = serviceName; m_json["PackageName"] = packageName; if (!packageVersion.empty()) { m_json["PackageVersion"] = packageVersion; } m_json["Tokens"] = nlohmann::json::array(); } void DumpToFile(std::ostream& outfile) { ValidateJson(); outfile << m_json; } nlohmann::json const& GetJson() { return m_json; } // Each ApiView node has 4 mandatory members: // // DefinitionId: A unique value used to represent an entity where comments can be left. This MUST be unique. // NavigateToId: ID used in the Navigation pane for type navigation. // Value: Value to display in ApiView (mandatory) // Kind: Type of node, used for color coding output. virtual void InsertWhitespace(int count) override { std::string whiteSpace(count, ' '); m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", whiteSpace}, {"Kind", TokenKinds::Whitespace}}); UpdateCursor(count); } virtual void InsertNewline() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::Newline}}); } virtual void InsertKeyword(std::string_view const& keyword) override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", keyword}, {"Kind", TokenKinds::Keyword}}); UpdateCursor(keyword.size()); } virtual void InsertText(std::string_view const& text) override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", text}, {"Kind", TokenKinds::Text}}); UpdateCursor(text.size()); } virtual void InsertPunctuation(const char punctuation) override { std::string punctuationString{punctuation}; m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", punctuationString}, {"Kind", TokenKinds::Punctuation}}); UpdateCursor(1); } virtual void InsertLineIdMarker() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::LineIdMarker}}); // Not clear if this is used at all. } virtual void InsertIdentifier(std::string_view const& id) override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", id}, {"Kind", TokenKinds::TypeName}}); UpdateCursor(id.size()); } virtual void InsertTypeName(std::string_view const& type, std::string_view const& navigationId) override { m_json["Tokens"].push_back( {{"DefinitionId", navigationId}, {"NavigateToId", navigationId}, {"Value", type}, {"Kind", TokenKinds::TypeName}}); UpdateCursor(type.size()); } virtual void InsertMemberName( std::string_view const& member, std::string_view const& memberFullName) override { m_json["Tokens"].push_back( {{"DefinitionId", memberFullName}, {"NavigateToId", nullptr}, {"Value", member}, {"Kind", TokenKinds::MemberName}}); } virtual void InsertStringLiteral(std::string_view const& str) override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", str}, {"Kind", TokenKinds::StringLiteral}}); UpdateCursor(str.size()); } virtual void InsertLiteral(std::string_view const& str) override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", str}, {"Kind", TokenKinds::Literal}}); UpdateCursor(str.size()); } virtual void InsertComment(std::string_view const& comment) override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", comment}, {"Kind", TokenKinds::Comment}}); UpdateCursor(comment.size()); } virtual void AddDocumentRangeStart() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::DocumentRangeStart}}); } virtual void AddDocumentRangeEnd() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::DocumentRangeEnd}}); } virtual void AddExternalLinkStart(const std::string_view& url) override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", url}, {"Kind", TokenKinds::ExternalLinkEnd}}); } virtual void AddExternalLinkEnd() { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::ExternalLinkStart}}); } virtual void AddDeprecatedRangeStart() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::DeprecatedRangeStart}}); } virtual void AddDeprecatedRangeEnd() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::DeprecatedRangeEnd}}); } virtual void AddSkipDiffRangeStart() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::SkipDiffRangeStart}}); } virtual void AddSkipDiffRangeEnd() override { m_json["Tokens"].push_back( {{"DefinitionId", nullptr}, {"NavigateToId", nullptr}, {"Value", nullptr}, {"Kind", TokenKinds::SkipDiffRangeEnd}}); } nlohmann::json DoDumpTypeHierarchyNode( std::shared_ptr<TypeHierarchy::TypeHierarchyNode> const& node) { nlohmann::json newNode; newNode["Text"] = node->NodeName; newNode["NavigationId"] = node->NavigationId; for (auto const& [childName, child] : node->Children) { newNode["ChildItems"].push_back(DoDumpTypeHierarchyNode(child)); } switch (node->NodeClass) { case TypeHierarchy::TypeHierarchyClass::Class: newNode["Tags"]["TypeKind"] = "class"; break; case TypeHierarchy::TypeHierarchyClass::Assembly: newNode["Tags"]["TypeKind"] = "assembly"; break; case TypeHierarchy::TypeHierarchyClass::Delegate: newNode["Tags"]["TypeKind"] = "delegate"; break; case TypeHierarchy::TypeHierarchyClass::Enum: newNode["Tags"]["TypeKind"] = "enum"; break; case TypeHierarchy::TypeHierarchyClass::Interface: newNode["Tags"]["TypeKind"] = "interface"; break; case TypeHierarchy::TypeHierarchyClass::Struct: newNode["Tags"]["TypeKind"] = "struct"; break; case TypeHierarchy::TypeHierarchyClass::Namespace: newNode["Tags"]["TypeKind"] = "namespace"; break; case TypeHierarchy::TypeHierarchyClass::Unknown: newNode["Tags"]["TypeKind"] = "unknown"; break; } return newNode; } virtual void DumpTypeHierarchyNode( std::shared_ptr<TypeHierarchy::TypeHierarchyNode> const& node) override { m_json["Navigation"].push_back(DoDumpTypeHierarchyNode(node)); }; // Schema for diagnostic nodes (which live under the "Diagnostics" root node: // DiagnosticId:<Unique ID>. // Text: <Diagnostic message>, // HelpLinkUri: <Any URL to be listed on diagnostic.> OPTIONAL. // TargetId: <Definition ID of the token where you want to show the diagnostic> // Level: 1 - Info, 2 - Warning, 3 - Error OPTIONAL. // // The Diagnostic ID name is specific to the diagnostic being generated. Python and Java creates // ID using a counter in format AZ_PY_<Countrervalue> by python // parser and AZ_JAVA_<CountrerValue> by Java parser>, nlohmann::json DoDumpDiagnosticNode(ApiViewMessage const& error) { nlohmann::json newNode; newNode["DiagnosticId"] = error.DiagnosticId; newNode["Text"] = error.DiagnosticText; newNode["TargetId"] = error.TargetId; if (!error.HelpLinkUri.empty()) { newNode["HelpLinkUri"] = error.HelpLinkUri; } switch (error.Level) { case ApiViewMessage::MessageLevel::Info: newNode["Level"] = 1; break; case ApiViewMessage::MessageLevel::Warning: newNode["Level"] = 2; break; case ApiViewMessage::MessageLevel::Error: newNode["Level"] = 3; break; case ApiViewMessage::MessageLevel::None: break; } return newNode; } virtual void DumpMessageNode(ApiViewMessage const& error) override { m_json["Diagnostics"].push_back(DoDumpDiagnosticNode(error)); } };