private void PortMissingCommentsForMember(DocsMember dMemberToUpdate) { string docId = dMemberToUpdate.DocIdEscaped; IntelliSenseXmlMember?tsMemberToPort = IntelliSenseXmlComments.Members.FirstOrDefault(x => x.Name == docId); TryGetEIIMember(dMemberToUpdate, out DocsMember? interfacedMember); if (tsMemberToPort != null || interfacedMember != null) { TryPortMissingSummaryForAPI(dMemberToUpdate, tsMemberToPort, interfacedMember); TryPortMissingRemarksForAPI(dMemberToUpdate, tsMemberToPort, interfacedMember, Config.SkipInterfaceRemarks); TryPortMissingParamsForAPI(dMemberToUpdate, tsMemberToPort, interfacedMember); TryPortMissingTypeParamsForAPI(dMemberToUpdate, tsMemberToPort, interfacedMember); TryPortMissingExceptionsForMember(dMemberToUpdate, tsMemberToPort); // Properties sometimes don't have a <value> but have a <returns> if (dMemberToUpdate.MemberType == "Property") { TryPortMissingPropertyForMember(dMemberToUpdate, tsMemberToPort, interfacedMember); } else if (dMemberToUpdate.MemberType == "Method") { TryPortMissingMethodForMember(dMemberToUpdate, tsMemberToPort, interfacedMember); } if (dMemberToUpdate.Changed) { ModifiedAPIs.AddIfNotExists(dMemberToUpdate.DocId); ModifiedFiles.AddIfNotExists(dMemberToUpdate.FilePath); } } }
private void TryPortMissingMethodForMember(DocsMember dMemberToUpdate, TripleSlashMember?tsMemberToPort, DocsMember?interfacedMember) { if (IsEmpty(dMemberToUpdate.Returns)) { string name = string.Empty; string value = string.Empty; bool isEII = false; // Bug: Sometimes a void return value shows up as not documented, skip those if (dMemberToUpdate.ReturnType == "System.Void") { ProblematicAPIs.AddIfNotExists($"Unexpected System.Void return value in Method=[{dMemberToUpdate.DocId}]"); } else if (tsMemberToPort != null && !IsEmpty(tsMemberToPort.Returns)) { name = tsMemberToPort.Name; value = tsMemberToPort.Returns; } else if (interfacedMember != null && !IsEmpty(interfacedMember.Returns)) { name = interfacedMember.MemberName; value = interfacedMember.Returns; isEII = true; } if (!IsEmpty(value)) { dMemberToUpdate.Returns = value; string message = $"METHOD {GetIsEII(isEII)} RETURNS"; PrintModifiedMember(message, dMemberToUpdate.FilePath, dMemberToUpdate.DocId); TotalModifiedIndividualElements++; } } }
private void TryPortMissingRemarksForAPI(IDocsAPI dApiToUpdate, IntelliSenseXmlMember?tsMemberToPort, DocsMember?interfacedMember, bool skipInterfaceRemarks) { if (dApiToUpdate.Kind == APIKind.Type && !Config.PortTypeRemarks || dApiToUpdate.Kind == APIKind.Member && !Config.PortMemberRemarks) { return; } if (dApiToUpdate.Remarks.IsDocsEmpty()) { bool isEII = false; string name = string.Empty; string value = string.Empty; // Try to port IntelliSense xml comments if (tsMemberToPort != null && !tsMemberToPort.Remarks.IsDocsEmpty()) { dApiToUpdate.Remarks = tsMemberToPort.Remarks; name = tsMemberToPort.Name; value = tsMemberToPort.Remarks; } // or try to find if it implements a documented interface // which only happens in docs members (types have a null interfacedMember passed) else if (interfacedMember != null && !interfacedMember.Remarks.IsDocsEmpty()) { DocsMember memberToUpdate = (DocsMember)dApiToUpdate; // Only attempt to port if the member name is the same as the interfaced member docid without prefix if (memberToUpdate.MemberName == interfacedMember.DocId[2..]) { string dMemberToUpdateTypeDocIdNoPrefix = memberToUpdate.ParentType.DocId[2..];
private void TryPortMissingTypeParamsForMember(DocsMember dMemberToUpdate, TripleSlashMember?tsMemberToPort, DocsMember?interfacedMember) { if (tsMemberToPort != null) { foreach (TripleSlashTypeParam tsTypeParam in tsMemberToPort.TypeParams) { bool isEII = false; string name = string.Empty; string value = string.Empty; DocsTypeParam dTypeParam = dMemberToUpdate.TypeParams.FirstOrDefault(x => x.Name == tsTypeParam.Name); bool created = false; if (dTypeParam == null) { ProblematicAPIs.AddIfNotExists($"TypeParam=[{tsTypeParam.Name}] in Member=[{dMemberToUpdate.DocId}]"); dTypeParam = dMemberToUpdate.AddTypeParam(tsTypeParam.Name, XmlHelper.GetNodesInPlainText(tsTypeParam.XETypeParam)); created = true; } // But it can still be empty, try to retrieve it if (IsEmpty(dTypeParam.Value)) { // try to port triple slash comments if (!IsEmpty(tsTypeParam.Value)) { name = tsTypeParam.Name; value = tsTypeParam.Value; } // or try to find if it implements a documented interface else if (interfacedMember != null) { DocsTypeParam interfacedTypeParam = interfacedMember.TypeParams.FirstOrDefault(x => x.Name == dTypeParam.Name); if (interfacedTypeParam != null) { name = interfacedTypeParam.Name; value = interfacedTypeParam.Value; isEII = true; } } } if (!IsEmpty(value)) { dTypeParam.Value = value; string message = $"MEMBER {GetIsEII(isEII)} TYPEPARAM ({GetIsCreated(created)})"; PrintModifiedMember(message, dTypeParam.ParentAPI.FilePath, dMemberToUpdate.DocId); TotalModifiedIndividualElements++; } } } }
private void TryPortMissingPropertyForMember(DocsMember dMemberToUpdate, TripleSlashMember?tsMemberToPort, DocsMember?interfacedMember) { if (IsEmpty(dMemberToUpdate.Value)) { string name = string.Empty; string value = string.Empty; bool isEII = false; // Issue: sometimes properties have their TS string in Value, sometimes in Returns if (tsMemberToPort != null) { name = tsMemberToPort.Name; if (!IsEmpty(tsMemberToPort.Value)) { value = tsMemberToPort.Value; } else if (!IsEmpty(tsMemberToPort.Returns)) { value = tsMemberToPort.Returns; } } // or try to find if it implements a documented interface else if (interfacedMember != null) { name = interfacedMember.MemberName; if (!IsEmpty(interfacedMember.Value)) { value = interfacedMember.Value; } else if (!IsEmpty(interfacedMember.Returns)) { value = interfacedMember.Returns; } if (!string.IsNullOrEmpty(value)) { isEII = true; } } if (!IsEmpty(value)) { dMemberToUpdate.Value = value; string message = $"MEMBER {GetIsEII(isEII)} PROPERTY"; PrintModifiedMember(message, dMemberToUpdate.FilePath, dMemberToUpdate.DocId); TotalModifiedIndividualElements++; } } }
private bool IsTypeAllowed(DocsAPI api) { // All types are allowed if (Config.ExcludedTypes.Count() == 0 && Config.IncludedTypes.Count() == 0) { return(true); } string typeName; string typeFullName; if (api is DocsType) { DocsType type = (DocsType)api; typeName = type.Name; typeFullName = type.FullName; } else if (api is DocsMember) { DocsMember member = (DocsMember)api; typeName = member.ParentType.Name; typeFullName = member.ParentType.FullName; } else { throw new InvalidCastException(); } if (Config.ExcludedTypes.Count() > 0) { if (Config.ExcludedTypes.Contains(typeName) || Config.ExcludedTypes.Contains(typeFullName)) { return(false); } } if (Config.IncludedTypes.Count() > 0) { if (Config.IncludedTypes.Contains(typeName) || Config.IncludedTypes.Contains(typeFullName)) { return(true); } } return(false); }
private void TryPortMissingExceptionsForMember(DocsMember dMemberToUpdate, TripleSlashMember?tsMemberToPort) { if (Config.SkipExceptions) { return; } if (tsMemberToPort != null) { // Exceptions are a special case: If a new one is found in code, but does not exist in docs, the whole element needs to be added foreach (TripleSlashException tsException in tsMemberToPort.Exceptions) { DocsException dException = dMemberToUpdate.Exceptions.FirstOrDefault(x => x.Cref == tsException.Cref); bool created = false; // First time adding the cref if (dException == null) { AddedExceptions.AddIfNotExists($"Exception=[{tsException.Cref}] in Member=[{dMemberToUpdate.DocId}]"); dException = dMemberToUpdate.AddException(tsException.Cref, XmlHelper.GetNodesInPlainText(tsException.XEException)); created = true; } // If cref exists, check if the text has already been appended else { XElement formattedException = tsException.XEException; string value = XmlHelper.GetNodesInPlainText(formattedException); if (!dException.Value.Contains(value)) { AddedExceptions.AddIfNotExists($"Exception=[{tsException.Cref}] in Member=[{dMemberToUpdate.DocId}]"); dException.AppendException(value); created = true; } } if (created || (!IsEmpty(tsException.Value) && IsEmpty(dException.Value))) { string message = string.Format("EXCEPTION ({0})", created ? "CREATED" : "MODIFIED"); PrintModifiedMember(message, dException.ParentAPI.FilePath, dException.Cref); TotalModifiedIndividualElements++; } } } }
private void PortMissingCommentsForMember(DocsMember dMemberToUpdate) { if (!CanAnalyzeAPI(dMemberToUpdate)) { return; } TripleSlashMember tsMemberToPort = TripleSlashComments.Members.FirstOrDefault(x => x.Name == dMemberToUpdate.DocIdEscaped); TryGetEIIMember(dMemberToUpdate, out DocsMember? interfacedMember); if (tsMemberToPort != null || interfacedMember != null) { TryPortMissingSummaryForAPI(dMemberToUpdate, tsMemberToPort, interfacedMember); TryPortMissingRemarksForAPI(dMemberToUpdate, tsMemberToPort, interfacedMember); TryPortMissingParamsForAPI(dMemberToUpdate, tsMemberToPort, interfacedMember); TryPortMissingTypeParamsForMember(dMemberToUpdate, tsMemberToPort, interfacedMember); TryPortMissingExceptionsForMember(dMemberToUpdate, tsMemberToPort); // Properties sometimes don't have a <value> but have a <returns> if (dMemberToUpdate.MemberType == "Property") { TryPortMissingPropertyForMember(dMemberToUpdate, tsMemberToPort, interfacedMember); } else if (dMemberToUpdate.MemberType == "Method") { TryPortMissingMethodForMember(dMemberToUpdate, tsMemberToPort, interfacedMember); } if (dMemberToUpdate.Changed) { ModifiedAPIs.AddIfNotExists(dMemberToUpdate.DocId); ModifiedFiles.AddIfNotExists(dMemberToUpdate.FilePath); } } }
private void TryPortMissingRemarksForAPI(IDocsAPI dApiToUpdate, TripleSlashMember?tsMemberToPort, DocsMember?interfacedMember) { if (Config.SkipRemarks) { return; } if (IsEmpty(dApiToUpdate.Remarks)) { bool isEII = false; string name = string.Empty; string value = string.Empty; // Try to port triple slash comments if (tsMemberToPort != null && !IsEmpty(tsMemberToPort.Remarks)) { dApiToUpdate.Remarks = tsMemberToPort.Remarks; name = tsMemberToPort.Name; value = tsMemberToPort.Remarks; } // or try to find if it implements a documented interface else if (interfacedMember != null && !IsEmpty(interfacedMember.Remarks)) { string eiiMessage = string.Empty; DocsMember memberToUpdate = (DocsMember)dApiToUpdate; // Special text for EIIs in Remarks if (memberToUpdate.MemberName == interfacedMember.DocId.Substring(2)) { string dMemberToUpdateTypeDocIdNoPrefix = memberToUpdate.ParentType.DocId.Substring(2); string interfacedMemberTypeDocIdNoPrefix = interfacedMember.ParentType.DocId.Substring(2); eiiMessage = $"This member is an explicit interface member implementation. It can be used only when the <xref:{dMemberToUpdateTypeDocIdNoPrefix}> instance is cast to an <xref:{interfacedMemberTypeDocIdNoPrefix}> interface.{Environment.NewLine + Environment.NewLine}"; } string original = string.Empty; if (!interfacedMember.Remarks.Contains(Configuration.ToBeAdded)) { original = interfacedMember.Remarks .CleanRemarksText("##Remarks") .CleanRemarksText("## Remarks") .CleanRemarksText("<![CDATA[") .CleanRemarksText("]]>"); } dApiToUpdate.Remarks = eiiMessage + original; name = interfacedMember.MemberName; value = interfacedMember.Remarks; isEII = true; } if (!IsEmpty(value)) { // Any member can have an empty remark string message = $"{dApiToUpdate.Prefix} {GetIsEII(isEII)} REMARKS"; PrintModifiedMember(message, dApiToUpdate.FilePath, dApiToUpdate.DocId); TotalModifiedIndividualElements++; } } }
/// <summary> /// If a Param is found in the DocsMember that did not exist in the Triple Slash member, it's possible the param was unexpectedly saved in the triple slash comments with a different name, so the user gets prompted to look for it. /// </summary> /// <param name="tsParam">The problematic triple slash param object.</param> /// <param name="dMember">The docs member where the param lives.</param> /// <param name="dParam">The docs param that was found to not match the triple slash param.</param> /// <returns></returns> private static bool TryPromptParam(TripleSlashParam tsParam, DocsMember dMember, out DocsParam dParam) { dParam = null; bool created = false; int option = -1; while (option == -1) { Log.Error("Problem in member {0} in file {1}!", dMember.DocId, dMember.FilePath); Log.Warning("The param from triple slash called '{0}' probably exists in code, but the name was not found in Docs. What would you like to do?", tsParam.Name); Log.Warning(" 1 - Type the correct name as it shows up in Docs."); Log.Warning(" 2 - Add the newly detected param to the Docs file (not recommended)."); Log.Warning(" Note: Whatever your choice, make sure to double check the affected Docs file after the tool finishes executing."); Log.Info(false, "Your answer [1,2]: "); if (!int.TryParse(Console.ReadLine(), out option)) { Log.Error("Invalid selection. Try again."); option = -1; } else { switch (option) { case 1: { string newName = string.Empty; while (string.IsNullOrWhiteSpace(newName)) { Log.Info(false, "Type the new name: "); newName = Console.ReadLine().Trim(); if (string.IsNullOrWhiteSpace(newName)) { Log.Error("Invalid selection. Try again."); } else if (newName == tsParam.Name) { Log.Error("You specified the same name. Try again."); newName = string.Empty; } else { dParam = dMember.Params.FirstOrDefault(x => x.Name == newName); if (dParam == null) { Log.Error("Could not find the param with the selected name. Try again."); newName = string.Empty; } else { Log.Success("Found the param with the selected name!"); } } } break; } case 2: { dParam = dMember.SaveParam(tsParam.XEParam); created = true; break; } default: { Log.Error("Invalid selection. Try again."); option = -1; break; } } } } return(created); }
private static bool TryPortMissingCommentsForMember(TripleSlashMember tsMember, DocsMember dMember) { bool modified = false; if (!IsEmpty(tsMember.Summary) && IsEmpty(dMember.Summary)) { // Any member can have an empty summary PrintModifiedMember("MEMBER SUMMARY", dMember.FilePath, tsMember.Name, dMember.DocId, tsMember.Summary, dMember.Summary); dMember.Summary = tsMember.Summary; TotalModifiedIndividualElements++; modified = true; } if (!IsEmpty(tsMember.Remarks) && IsEmpty(dMember.Remarks)) { // Any member can have an empty remark PrintModifiedMember("MEMBER REMARKS", dMember.FilePath, tsMember.Name, dMember.DocId, tsMember.Remarks, dMember.Remarks); dMember.Remarks = tsMember.Remarks; TotalModifiedIndividualElements++; modified = true; } // Properties and method returns save their values in different locations if (dMember.MemberType == "Property") { if (!IsEmpty(tsMember.Returns) && IsEmpty(dMember.Value)) { PrintModifiedMember("PROPERTY", dMember.FilePath, tsMember.Name, dMember.DocId, tsMember.Returns, dMember.Value); dMember.Value = tsMember.Returns; TotalModifiedIndividualElements++; modified = true; } } else if (dMember.MemberType == "Method") { if (!IsEmpty(tsMember.Returns) && IsEmpty(dMember.Returns)) { if (tsMember.Returns != null && dMember.ReturnType == "System.Void") { ProblematicAPIs.AddIfNotExists($"Returns=[{tsMember.Returns}] in Method=[{dMember.DocId}]"); } else { PrintModifiedMember("METHOD RETURN", dMember.FilePath, tsMember.Name, dMember.DocId, tsMember.Returns, dMember.Returns); dMember.Returns = tsMember.Returns; TotalModifiedIndividualElements++; modified = true; } } } // Triple slash params may cause errors if they are missing in the code side foreach (TripleSlashParam tsParam in tsMember.Params) { DocsParam dParam = dMember.Params.FirstOrDefault(x => x.Name == tsParam.Name); bool created = false; if (dParam == null) { ProblematicAPIs.AddIfNotExists($"Param=[{tsParam.Name}] in Member DocId=[{dMember.DocId}]"); created = TryPromptParam(tsParam, dMember, out dParam); } if (created || (!IsEmpty(tsParam.Value) && IsEmpty(dParam.Value))) { PrintModifiedMember(string.Format("PARAM ({0})", created ? "CREATED" : "MODIFIED"), dParam.FilePath, tsParam.Name, dParam.Name, tsParam.Value, dParam.Value); if (!created) { dParam.Value = tsParam.Value; } TotalModifiedIndividualElements++; modified = true; } } // Exceptions are a special case: If a new one is found in code, but does not exist in docs, the whole element needs to be added foreach (TripleSlashException tsException in tsMember.Exceptions) { DocsException dException = dMember.Exceptions.FirstOrDefault(x => x.Cref.EndsWith(tsException.Cref)); bool created = false; if (dException == null) { dException = dMember.SaveException(tsException.XEException); AddedExceptions.AddIfNotExists($"{dException.Cref} in {dMember.DocId}"); created = true; } if (created || (!IsEmpty(tsException.Value) && IsEmpty(dException.Value))) { PrintModifiedMember(string.Format("EXCEPTION ({0})", created ? "CREATED" : "MODIFIED"), dException.FilePath, tsException.Cref, dException.Cref, tsException.Value, dException.Value); if (!created) { dException.Value = tsException.Value; } TotalModifiedIndividualElements++; modified = true; } } foreach (TripleSlashTypeParam tsTypeParam in tsMember.TypeParams) { DocsTypeParam dTypeParam = dMember.TypeParams.FirstOrDefault(x => x.Name == tsTypeParam.Name); bool created = false; if (dTypeParam == null) { ProblematicAPIs.AddIfNotExists($"TypeParam=[{tsTypeParam.Name}] in Member=[{dMember.DocId}]"); dTypeParam = dMember.SaveTypeParam(tsTypeParam.XETypeParam); created = true; } if (created || (!IsEmpty(tsTypeParam.Value) && IsEmpty(dTypeParam.Value))) { PrintModifiedMember(string.Format("TYPE PARAM ({0})", created ? "CREATED" : "MODIFIED"), dTypeParam.FilePath, tsTypeParam.Name, dTypeParam.Name, tsTypeParam.Value, dTypeParam.Value); if (!created) { dTypeParam.Value = tsTypeParam.Value; } TotalModifiedIndividualElements++; modified = true; } } if (modified) { ModifiedAPIs.AddIfNotExists(dMember.DocId); } return(modified); }