//===================================================================== /// <summary> /// This is used to merge an exclusion entry with the filter collection /// </summary> /// <param name="entryType">The entry type</param> /// <param name="fullName">The member's full name</param> /// <returns>True if merged without conflict or false if the merged member conflicted with an existing /// entry. The existing entry will take precedence.</returns> public bool MergeExclusionEntry(ApiEntryType entryType, string fullName) { ApiFilter newEntry; foreach (ApiFilter child in this) { if (child.FullName == fullName) { // If the exposure doesn't match, use the existing entry and ignore the merged entry if (child.IsExposed) { return(false); } child.IsProjectExclude = true; return(true); } } // It's a new one newEntry = new ApiFilter(entryType, fullName, false) { IsProjectExclude = true }; this.Add(newEntry); return(true); }
/// <summary> /// Constructor. This takes the API type and the full name /// </summary> /// <param name="apiType">The API entry type</param> /// <param name="name">The fully qualified name</param> /// <param name="exposed">True to expose it, false to hide it</param> internal ApiFilter(ApiEntryType apiType, string name, bool exposed) : this() { int pos; if (name == null) { name = String.Empty; } entryType = apiType; fullName = name; isExposed = exposed; // By default, we'll use the last part as the filter name unless it's a namespace pos = name.LastIndexOf('.'); if (entryType == ApiEntryType.Namespace || pos == -1) { this.FilterName = name; } else { this.FilterName = name.Substring(pos + 1); } }
/// <summary> /// Determine the API entry type from the ID and possible the subgroup /// </summary> /// <param name="apiType">The type character to convert</param> /// <param name="subgroup">The subgroup to use</param> /// <returns>An <see cref="ApiEntryType"/> indicating the entry type</returns> internal static ApiEntryType EntryTypeFromId(char apiType, string subgroup) { ApiEntryType entryType; switch(apiType) { case 'N': // Namespace entryType = ApiEntryType.Namespace; break; case 'T': // A type switch(subgroup) { case "structure": entryType = ApiEntryType.Structure; break; case "interface": entryType = ApiEntryType.Interface; break; case "enumeration": entryType = ApiEntryType.Enumeration; break; case "delegate": entryType = ApiEntryType.Delegate; break; default: entryType = ApiEntryType.Class; break; } break; default: // Must be a member of some sort switch(subgroup) { case "constructor": entryType = ApiEntryType.Constructor; break; case "operator": entryType = ApiEntryType.Operator; break; case "property": entryType = ApiEntryType.Property; break; case "event": entryType = ApiEntryType.Event; break; case "field": entryType = ApiEntryType.Field; break; default: entryType = ApiEntryType.Method; break; } break; } return entryType; }
/// <summary> /// This will determine the API entry type and visibility based on the information in the reflection /// information node. /// </summary> private void DetermineApiEntryType() { XmlNode subsubgroup; string subgroup, entryId; // Is it an inherited namespace? if(apiNode.Name == "namespace") { entryType = ApiEntryType.Namespace; return; } // Is it an inherited type? if(apiNode.Name == "type") { // Assume class, it'll be close enough entryType = ApiEntryType.Class; return; } // It's a documented or inherited member of some sort if(apiNode.Name == "element" || apiNode.Name == "type") entryId = apiNode.Attributes["api"].Value; // Inherited else { entryId = apiNode.Attributes["id"].Value; // Documented // Is it a namespace? if(entryId[0] == 'N') { entryType = ApiEntryType.Namespace; return; } } subsubgroup = apiNode.SelectSingleNode("apidata/@subsubgroup"); if(subsubgroup != null) subgroup = subsubgroup.Value; else subgroup = apiNode.SelectSingleNode("apidata/@subgroup").Value; entryType = EntryTypeFromId(entryId[0], subgroup); visibility = DetermineVisibility(entryId[0], apiNode); }
//===================================================================== /// <summary> /// This is used to generate the API filter collection used by MRefBuilder to exclude items from the /// reflection information file. /// </summary> /// <remarks>Namespaces and members with an <c><exclude /></c> tag in their comments are removed /// using the ripping feature as it is more efficient than searching for and removing them from the /// reflection file after it has been generated especially on large projects.</remarks> private void GenerateApiFilter() { XmlNodeList excludes; XmlNode docMember; List <string> ripList; string nameSpace, memberName, typeName, fullName; int pos; this.ReportProgress(BuildStep.GenerateApiFilter, "Generating API filter for MRefBuilder..."); if (this.ExecutePlugIns(ExecutionBehaviors.InsteadOf)) { return; } this.ExecutePlugIns(ExecutionBehaviors.Before); ripList = new List <string>(); // Add excluded namespaces foreach (NamespaceSummaryItem ns in project.NamespaceSummaries) { if (!ns.IsDocumented && !ns.IsGroup) { memberName = ns.Name; if (memberName[0] == '(') { memberName = "N:"; // Global namespace } else { memberName = "N:" + memberName; } ripList.Add(memberName); } } // If the namespace summaries don't contain an explicit entry for the global namespace, exclude it // by default. if (project.NamespaceSummaries[null] == null) { ripList.Add("N:"); } // Add members excluded via comments foreach (XmlCommentsFile comments in commentsFiles) { excludes = comments.Members.SelectNodes("//exclude/.."); foreach (XmlNode member in excludes) { // It should appear at the same level as <summary> so that we can find the member name in the // parent node. if (member.Attributes["name"] == null) { this.ReportProgress(" Incorrect placement of <exclude/> tag. Unable to locate " + "member name."); continue; } memberName = member.Attributes["name"].Value; if (!ripList.Contains(memberName)) { ripList.Add(memberName); } } } // Sort by entry type and name so that we create the collection from the namespace down to the // members. ripList.Sort((x, y) => { ApiEntryType xType = ApiFilter.ApiEntryTypeFromLetter(x[0]), yType = ApiFilter.ApiEntryTypeFromLetter(y[0]); if (xType == yType) { return(String.Compare(x, y, StringComparison.Ordinal)); } return((int)xType - (int)yType); }); // Get the project's API filter and merge the members from the rip list var apiFilter = project.ApiFilter; // For the API filter to work, we have to nest the entries by namespace, type, and member. As such, // we have to break apart what we've got in the list and merge it with the stuff the user may have // specified using the project's API filter property. foreach (string member in ripList) { // Namespaces are easy if (member[0] == 'N') { if (!apiFilter.MergeExclusionEntry(ApiEntryType.Namespace, member.Substring(2))) { this.ReportWarning("BE0008", "Namespace '{0}' excluded via namespace comments " + "conflicted with API filter setting. Exclusion ignored.", member); } continue; } // Types and members are a bit tricky. Since we don't have any real context, we have to assume // that we can remove the last part and look it up. If a type entry isn't found, we can assume // it's the namespace. Where this can fail is on a nested class where the parent class is // lacking XML comments. Not much we can do about it in that case. if (member[0] == 'T') { fullName = nameSpace = member; typeName = member.Substring(2); memberName = null; } else { // Strip parameters. The ripping feature only goes to the name level. If one overload is // ripped, they are all ripped. pos = member.IndexOf('('); if (pos != -1) { fullName = memberName = member.Substring(0, pos); } else { fullName = memberName = member; } // Generic method pos = memberName.IndexOf("``", StringComparison.Ordinal); if (pos != -1) { memberName = memberName.Substring(0, pos); } pos = memberName.LastIndexOf('.'); memberName = memberName.Substring(pos + 1); typeName = fullName.Substring(2, pos - 2); nameSpace = "T:" + typeName; } for (int idx = 0; idx < commentsFiles.Count; idx++) { docMember = commentsFiles[idx].Members.SelectSingleNode("member[@name='" + nameSpace + "']"); if (docMember != null) { pos = nameSpace.LastIndexOf('.'); if (pos == -1) { nameSpace = "N:"; break; } else { nameSpace = nameSpace.Substring(0, pos); } idx = -1; } } nameSpace = nameSpace.Substring(2); // If the names still match, we probably didn't find comments for the type so assume the // namespace is the part up to the last period. if (nameSpace == typeName) { pos = nameSpace.LastIndexOf('.'); if (pos != -1) { nameSpace = nameSpace.Substring(0, pos); } else { nameSpace = "N:"; // Global namespace } } if (apiFilter.AddNamespaceChild(fullName, nameSpace, typeName, memberName)) { if (fullName.Length > 2) { // If it's a nested class, adjust the filter name fullName = typeName; typeName = typeName.Substring(nameSpace.Length + 1); if (typeName.IndexOf('.') != -1) { foreach (ApiFilter ns in apiFilter) { if (ns.FullName == nameSpace) { foreach (ApiFilter t in ns.Children) { if (t.FullName == fullName) { t.FilterName = typeName; break; } } break; } } } } } else { this.ReportWarning("BE0009", "'{0}' is marked with <exclude /> but conflicted with the " + "API filter setting. Exclusion ignored.", member); } } this.ExecutePlugIns(ExecutionBehaviors.After); }