in src/dotnet/APIView/APIViewWeb/Languages/CppLanguageService.cs [162:844]
private static void BuildNodes(CodeFileTokensBuilder builder, List<NavigationItem> navigation, List<CodeDiagnostic> diagnostic, CppAstNode root, string packageNamespace)
{
//Mapping of each namespace to it's leaf namespace nodes
//These leaf nodes are processed to generate and group them together
//C++ ast has declarations under same namespace in multiple files so these needs to be grouped for better presentation
var namespaceLeafMap = new Dictionary<string, List<CppAstNode>>();
var types = new HashSet<string>();
var namespaceNodes = root.inner.Where(n => n.kind == NamespaceDeclKind && n.name == RootNamespace);
bool foundFilterNamespace = false;
foreach (var node in namespaceNodes)
{
var namespacebldr = new StringBuilder();
var leafNamespaceNode = node;
var currentNode = node;
//Iterate until leaf namespace node and generate full namespace
while (currentNode?.kind == NamespaceDeclKind)
{
if (namespacebldr.Length > 0)
{
namespacebldr.Append("::");
}
namespacebldr.Append(currentNode.name);
leafNamespaceNode = currentNode;
currentNode = currentNode.inner?.FirstOrDefault(n => n.kind == NamespaceDeclKind);
if (leafNamespaceNode.inner?.Any(n => n.kind != NamespaceDeclKind) == true)
{
var nameSpace = namespacebldr.ToString();
if (!foundFilterNamespace && nameSpace.StartsWith(packageNamespace))
{
foundFilterNamespace = true;
}
if (!namespaceLeafMap.ContainsKey(nameSpace))
{
namespaceLeafMap[nameSpace] = new List<CppAstNode>();
}
namespaceLeafMap[nameSpace].Add(leafNamespaceNode);
}
}
}
foreach (var nameSpace in namespaceLeafMap.Keys)
{
// Filter namespace based on file name if any of the namespace matches file name pattern
// If no namespace matches file name then allow all namespaces to be part of review to avoid mandating file name convention
if ((!foundFilterNamespace || nameSpace.StartsWith(packageNamespace)) && !nameSpace.EndsWith(DetailsNamespacePostfix))
{
ProcessNamespaceNode(nameSpace);
builder.NewLine();
builder.NewLine();
}
}
void ProcessNamespaceNode(string nameSpace)
{
NavigationItem currentNamespace = new NavigationItem()
{
NavigationId = nameSpace,
Text = nameSpace,
Tags = { { "TypeKind", "namespace" } }
};
List<NavigationItem> currentNamespaceMembers = new List<NavigationItem>();
builder.Keyword("namespace");
builder.Space();
var namespaceTokens = nameSpace.Split("::");
foreach (var token in namespaceTokens)
{
builder.Text(token);
builder.Space();
builder.Punctuation("{");
builder.Space();
}
builder.NewLine();
//Process all nodes in namespace
foreach (var leafNamespaceNode in namespaceLeafMap[nameSpace])
{
if (leafNamespaceNode.inner != null)
{
foreach (var member in leafNamespaceNode.inner)
{
// Name space has mix of details namespace and classes
// API View should skip those sub details namespaces also
if (member.kind == NamespaceDeclKind)
continue;
builder.IncrementIndent();
ProcessNode(member, currentNamespaceMembers, nameSpace);
builder.DecrementIndent();
}
}
}
currentNamespace.ChildItems = currentNamespaceMembers.ToArray();
navigation.Add(currentNamespace);
builder.NewLine();
for (int i = 0; i < namespaceTokens.Length; i++)
builder.Punctuation("}");
builder.NewLine();
}
NavigationItem BuildDeclaration(string name, string kind, string parentId = "")
{
string definitionId = name;
if (!string.IsNullOrEmpty(parentId))
{
definitionId = parentId + "::" + name;
}
builder.Append(new CodeFileToken()
{
DefinitionId = definitionId,
Kind = CodeFileTokenKind.TypeName,
Value = name,
});
types.Add(name);
return new NavigationItem()
{
NavigationId = definitionId,
Text = name,
Tags = { { "TypeKind", kind } }
};
}
void BuildMemberDeclaration(string containerName, string name, string id = "")
{
if (string.IsNullOrEmpty(id))
{
id = name;
}
builder.Append(new CodeFileToken()
{
DefinitionId = containerName + "." + id,
Kind = CodeFileTokenKind.MemberName,
Value = name,
});
}
string GenerateUniqueMethodId(CppAstNode methodNode)
{
var bldr = new StringBuilder();
bldr.Append(methodNode.name);
if (methodNode.inner != null)
{
foreach (var parameterNode in methodNode.inner)
{
if (parameterNode.kind == ParmVarDeclKind)
{
bldr.Append(":");
bldr.Append(parameterNode.type.Replace(" ", "_"));
}
}
}
bldr.Append("::");
var type = string.IsNullOrEmpty(methodNode.type) ? "void" : methodNode.type;
bldr.Append(type.Replace(" ", "_"));
return bldr.ToString();
}
NavigationItem ProcessClassNode(CppAstNode node, string parentName, List<CppAstNode> templateParams = null)
{
NavigationItem navigationItem = null;
builder.Keyword(node.tagUsed);
builder.Space();
string nodeName = node.name;
if (templateParams != null)
{
nodeName += "<";
bool first = true;
foreach (var paramNode in templateParams)
{
if (!first)
{
nodeName += ", ";
}
nodeName += paramNode.name;
}
nodeName += ">";
}
if (!string.IsNullOrEmpty(nodeName))
{
navigationItem = BuildDeclaration(nodeName, node.tagUsed, parentName);
builder.Space();
}
var memberNavigations = new List<NavigationItem>();
var parents = node.inner?.Where(n => parentTypes.Contains(n.kind));
if (parents != null)
{
bool first = true;
//Show inheritance details
foreach (var parent in parents)
{
if (first)
{
builder.Punctuation(":");
builder.Space();
first = false;
}
else
{
builder.Punctuation(",");
builder.Space();
}
builder.Keyword(parent.access);
builder.Space();
if (parent.name != null)
{
BuildType(builder, parent.name, types);
}
}
builder.Space();
}
builder.NewLine();
builder.WriteIndent();
builder.Punctuation("{");
builder.NewLine();
//Double indentation for members since access modifier is not parent for members
builder.IncrementIndent();
builder.IncrementIndent();
bool hasFoundDefaultAccessMembers = false;
var id = parentName + "::" + node.name;
if (node.inner != null)
{
bool isPrivateMember = false;
string currentAccessModifier = "";
foreach (var childNode in node.inner)
{
if (childNode.kind == CxxRecordDeclKind && childNode.name == node.name)
continue;
// add public or protected access specifier
if (childNode.kind == AccessSpecDeclKind)
{
//Skip all private members
isPrivateMember = (childNode.access == AccessModifierPrivate);
if (isPrivateMember)
{
continue;
}
currentAccessModifier = childNode.access;
builder.DecrementIndent();
builder.WriteIndent();
builder.Keyword(childNode.access);
builder.Punctuation(":");
builder.IncrementIndent();
builder.NewLine();
}
else if (!isPrivateMember && !parentTypes.Contains(childNode.kind))
{
if (string.IsNullOrEmpty(currentAccessModifier) && !hasFoundDefaultAccessMembers)
{
hasFoundDefaultAccessMembers = true;
}
ProcessNode(childNode, memberNavigations, id);
}
}
}
builder.DecrementIndent();
builder.DecrementIndent();
builder.WriteIndent();
builder.Punctuation("}");
builder.Punctuation(";");
builder.NewLine();
builder.NewLine();
if (navigationItem != null)
{
navigationItem.ChildItems = memberNavigations.ToArray();
}
if (node.tagUsed == "class")
{
if (node.inner?.Any(n => n.isimplicit == true && n.kind == CxxConstructorDeclKind) == true)
{
diagnostic.Add(new CodeDiagnostic("", navigationItem.NavigationId, ImplicitConstrucorHintError, ""));
}
if (hasFoundDefaultAccessMembers)
{
diagnostic.Add(new CodeDiagnostic("", navigationItem.NavigationId, NonAccessModifierMemberError, ""));
}
}
return navigationItem;
}
NavigationItem ProcessEnumNode(CppAstNode node)
{
builder.Keyword("enum");
builder.Space();
var navigationItem = BuildDeclaration(node.name, "enum");
builder.NewLine();
builder.WriteIndent();
builder.Punctuation("{");
builder.NewLine();
builder.IncrementIndent();
if (node.inner != null)
{
foreach (var parameterNode in node.inner)
{
if (parameterNode.kind == EnumConstantDeclKind)
{
builder.WriteIndent();
BuildMemberDeclaration("", parameterNode.name);
if (parameterNode.inner?.FirstOrDefault(n => n.kind == "ConstantExpr") is CppAstNode
exprNode)
{
builder.Space();
builder.Punctuation("=");
builder.Space();
BuildExpression(builder, exprNode);
}
builder.Punctuation(",");
builder.NewLine();
}
}
}
builder.DecrementIndent();
builder.WriteIndent();
builder.Punctuation("};");
builder.NewLine();
builder.NewLine();
return navigationItem;
}
void ProcessFunctionDeclNode(CppAstNode node, string parentName)
{
if (node.isimplicit == true)
{
builder.Keyword("implicit");
builder.Space();
}
if (node.isvirtual == true)
{
builder.Keyword("virtual");
builder.Space();
}
if (node.inline == true)
{
builder.Keyword("inline");
builder.Space();
}
if (!string.IsNullOrEmpty(node.storageClass))
{
builder.Keyword(node.storageClass);
builder.Space();
}
if (node.type != null)
{
BuildType(builder, node.type, types);
builder.Space();
}
BuildMemberDeclaration(parentName, node.name, GenerateUniqueMethodId(node));
builder.Punctuation("(");
bool first = true;
if (node.inner != null)
{
bool isMultiLineArgs = node.inner.Count > 1;
builder.IncrementIndent();
foreach (var parameterNode in node.inner)
{
if (parameterNode.kind == ParmVarDeclKind)
{
if (first)
{
first = false;
}
else
{
builder.Punctuation(",");
builder.Space();
}
if (isMultiLineArgs)
{
builder.NewLine();
builder.WriteIndent();
}
BuildType(builder, parameterNode.type, types);
if (!string.IsNullOrEmpty(parameterNode.name))
{
builder.Space();
builder.Text(parameterNode.name);
}
}
}
builder.DecrementIndent();
}
builder.Punctuation(")");
//Add any postfix keywords if signature has any.
// Few expamples are 'const noexcept'
if (!string.IsNullOrEmpty(node.keywords))
{
foreach (var key in node.keywords.Split())
{
builder.Space();
builder.Keyword(key);
}
}
// If method is tagged as pure, delete, or default then it should be marked as "=<0|default|delete>"
if (node.ispure == true)
{
builder.Space();
builder.Punctuation("=");
builder.Space();
builder.Text("0");
}
else if (node.isdefault == true)
{
builder.Space();
builder.Punctuation("=");
builder.Space();
builder.Keyword("default");
}
else if (node.isdelete == true)
{
builder.Space();
builder.Punctuation("=");
builder.Space();
builder.Keyword("delete");
}
builder.Punctuation(";");
builder.NewLine();
}
NavigationItem ProcessTemplateClassDeclNode(CppAstNode node, string parentName)
{
NavigationItem returnValue = null;
builder.Keyword("template");
builder.Space();
if (node.inner != null)
{
bool first = true;
builder.Punctuation("<");
List<CppAstNode> templateParams = new List<CppAstNode>();
foreach (var childnode in node.inner.Where(n => n.kind == TemplateTypeParmDeclKind))
{
if (!first)
{
builder.Punctuation(",");
builder.Space();
}
templateParams.Add(childnode);
BuildType(builder, childnode.name, types);
}
builder.Punctuation(">");
builder.NewLine();
builder.WriteIndent();
foreach (var childnode in node.inner.Where(n => n.kind == CxxRecordDeclKind))
{
returnValue = ProcessClassNode(childnode, parentName, templateParams);
}
}
return returnValue;
}
NavigationItem ProcessTemplateClassSpecializationDeclNode(CppAstNode node, string parentName)
{
NavigationItem returnValue = null;
builder.Keyword("template");
builder.Space();
if (node.inner != null)
{
builder.Punctuation("<>");
List<CppAstNode> templateParams = new List<CppAstNode>();
foreach (var childnode in node.inner.Where(n => n.kind == TemplateArgumentKind))
{
templateParams.Add(childnode);
}
builder.NewLine();
builder.WriteIndent();
foreach (var childnode in node.inner.Where(n => n.kind == CxxRecordDeclKind))
{
returnValue = ProcessClassNode(childnode, parentName, templateParams);
}
}
return returnValue;
}
void ProcessTemplateFuncDeclNode(CppAstNode node, string parentName)
{
builder.Keyword("template");
builder.Space();
if (node.inner != null)
{
bool first = true;
builder.Punctuation("<");
foreach (var childnode in node.inner.Where(n => n.kind == TemplateTypeParmDeclKind))
{
if (!first)
{
builder.Punctuation(",");
builder.Space();
}
BuildType(builder, childnode.name, types);
}
builder.Punctuation(">");
builder.Space();
var methodNode = node.inner.FirstOrDefault(node => node.kind == CxxMethodDeclKind);
if (methodNode != null)
{
ProcessFunctionDeclNode(methodNode, parentName);
}
}
}
void ProcessTypeAlias(CppAstNode node)
{
builder.Keyword("using");
builder.Space();
builder.Text(node.name);
builder.Space();
builder.Punctuation("=");
builder.Space();
BuildType(builder, node.type, types);
builder.Punctuation(";");
builder.NewLine();
}
void ProcessVarDecNode(CppAstNode node, string parentName)
{
if (node.constexpr == true)
{
builder.Keyword("constexpr");
builder.Space();
}
if (!string.IsNullOrEmpty(node.storageClass))
{
builder.Keyword(node.storageClass);
builder.Space();
}
//Remove left most const from type if it is constexpr
string type = node.type;
if (node.constexpr == true && type.StartsWith("const"))
{
var regex = new Regex(Regex.Escape("const"));
type = regex.Replace(type, "", 1).Trim();
}
BuildType(builder, type, types);
builder.Space();
BuildMemberDeclaration(parentName, node.name);
if (node.inner?.FirstOrDefault() is CppAstNode
exprNode)
{
builder.Space();
builder.Punctuation("=");
builder.Space();
BuildExpression(builder, exprNode);
}
builder.Punctuation(";");
builder.NewLine();
}
void ProcessNode(CppAstNode node, List<NavigationItem> navigationItems, string parentName)
{
NavigationItem currentNavItem = null;
builder.WriteIndent();
switch (node.kind)
{
case CxxRecordDeclKind:
{
currentNavItem = ProcessClassNode(node, parentName);
builder.NewLine();
break;
}
case CxxConstructorDeclKind:
case CxxDestructorDeclKind:
case FunctionDeclKind:
case CxxMethodDeclKind:
{
ProcessFunctionDeclNode(node, parentName);
break;
}
case EnumDeclKind:
{
currentNavItem = ProcessEnumNode(node);
builder.NewLine();
break;
}
case FieldDeclKind:
case VarDeclKind:
{
ProcessVarDecNode(node, parentName);
break;
}
case TypeAliasDeclKind:
{
ProcessTypeAlias(node);
break;
}
case FunctionTemplateDeclKind:
{
ProcessTemplateFuncDeclNode(node, parentName);
break;
}
case ClassTemplateDeclKind:
{
currentNavItem = ProcessTemplateClassDeclNode(node, parentName);
break;
}
case ClassTemplateSpecializationDeclKind:
{
currentNavItem = ProcessTemplateClassSpecializationDeclNode(node, parentName);
break;
}
default:
builder.Text(node.ToString());
builder.NewLine();
break;
}
if (currentNavItem != null && navigationItems != null)
{
navigationItems.Add(currentNavItem);
}
}
void BuildType(CodeFileTokensBuilder builder, string type, HashSet<string> types)
{
foreach (Match typePartMatch in _typeTokenizer.Matches(type))
{
var typePart = typePartMatch.ToString();
if (_keywords.Contains(typePart))
{
builder.Keyword(typePart);
}
else if (typePart.Contains("::"))
{
// Handle type usage before it's defintition
var typeNamespace = typePart.Substring(0, typePart.LastIndexOf("::"));
string typeValue = typePart;
string navigateToId = "";
if (types.Contains(typePart) || namespaceLeafMap.ContainsKey(typeNamespace))
{
typeValue = typePart.Substring(typePart.LastIndexOf("::") + 2);
navigateToId = typePart;
}
builder.Append(new CodeFileToken()
{
Kind = CodeFileTokenKind.TypeName,
NavigateToId = navigateToId,
Value = typeValue
});
}
else
{
builder.Text(typePart);
}
}
}
}