/// <summary> /// Loop over the DocumentationCommentTriviaSyntaxes. Gather /// 1) concatenated XML, as a string; /// 2) whether or not the XML is valid; /// 3) set of type parameters covered by <typeparam> elements; /// 4) set of parameters covered by <param> elements; /// 5) list of <include> elements, as SyntaxNodes. /// </summary> /// <returns>True, if at least one documentation comment was processed; false, otherwise.</returns> /// <remarks>This was factored out for clarity, not because it's reusable.</remarks> private bool TryProcessDocumentationCommentTriviaNodes( Symbol symbol, bool isPartialMethodDefinitionPart, ImmutableArray<DocumentationCommentTriviaSyntax> docCommentNodes, bool reportParameterOrTypeParameterDiagnostics, out string withUnprocessedIncludes, out bool haveParseError, out HashSet<TypeParameterSymbol> documentedTypeParameters, out HashSet<ParameterSymbol> documentedParameters, out ImmutableArray<CSharpSyntaxNode> includeElementNodes) { Debug.Assert(!docCommentNodes.IsDefaultOrEmpty); bool haveWriter = _writer != null; bool processedDocComment = false; // Even if there are DocumentationCommentTriviaSyntax, we may not need to process any of them. ArrayBuilder<CSharpSyntaxNode> includeElementNodesBuilder = null; documentedParameters = null; documentedTypeParameters = null; // Saw an XmlException while parsing one of the DocumentationCommentTriviaSyntax nodes. haveParseError = false; // We're doing substitution and formatting per-trivia, rather than per-symbol, // because a single symbol can have both single-line and multi-line style // doc comments. foreach (DocumentationCommentTriviaSyntax trivia in docCommentNodes) { _cancellationToken.ThrowIfCancellationRequested(); bool reportDiagnosticsForCurrentTrivia = trivia.SyntaxTree.ReportDocumentationCommentDiagnostics(); // If we're writing XML or we need to report diagnostics (either in this particular piece of trivia, // or concerning undocumented [type] parameters), then we need to process this trivia node. if (!(haveWriter || reportDiagnosticsForCurrentTrivia || reportParameterOrTypeParameterDiagnostics)) { continue; } if (!processedDocComment) { // Since we have to throw away all the parts if any part is bad, we need to write to an intermediate temp. BeginTemporaryString(); if (_processIncludes) { includeElementNodesBuilder = ArrayBuilder<CSharpSyntaxNode>.GetInstance(); } // We DO want to write out partial method definition parts if we're processing includes // because we need to have XML to process. if (!isPartialMethodDefinitionPart || _processIncludes) { WriteLine("<member name=\"{0}\">", symbol.GetDocumentationCommentId()); Indent(); } processedDocComment = true; } // Will respect the DocumentationMode. string substitutedText = DocumentationCommentWalker.GetSubstitutedText(_compilation, _diagnostics, symbol, trivia, includeElementNodesBuilder, ref documentedParameters, ref documentedTypeParameters); string formattedXml = FormatComment(substitutedText); // It would be preferable to just parse the concatenated XML at the end of the loop (we wouldn't have // to wrap it in a root element and we wouldn't have to reparse in the IncludeElementExpander), but // then we wouldn't know whether or where to report a diagnostic. XmlException e = XmlDocumentationCommentTextReader.ParseAndGetException(formattedXml); if (e != null) { haveParseError = true; if (reportDiagnosticsForCurrentTrivia) { Location location = new SourceLocation(trivia.SyntaxTree, new TextSpan(trivia.SpanStart, 0)); _diagnostics.Add(ErrorCode.WRN_XMLParseError, location, GetDescription(e)); } } // For partial methods, all parts are validated, but only the implementation part is written to the XML stream. if (!isPartialMethodDefinitionPart || _processIncludes) { // This string already has indentation and line breaks, so don't call WriteLine - just write the text directly. Write(formattedXml); } } if (!processedDocComment) { withUnprocessedIncludes = null; includeElementNodes = default(ImmutableArray<CSharpSyntaxNode>); return false; } if (!isPartialMethodDefinitionPart || _processIncludes) { Unindent(); WriteLine("</member>"); } // Free the temp. withUnprocessedIncludes = GetAndEndTemporaryString(); // Free the builder, even if there was an error. includeElementNodes = _processIncludes ? includeElementNodesBuilder.ToImmutableAndFree() : default(ImmutableArray<CSharpSyntaxNode>); return true; }
/// <summary> /// Compile documentation comments on the symbol and write them to the stream if one is provided. /// </summary> public override void DefaultVisit(Symbol symbol) { _cancellationToken.ThrowIfCancellationRequested(); if (symbol.IsImplicitlyDeclared || symbol.IsAccessor()) { return; } if (_filterTree != null && !symbol.IsDefinedInSourceTree(_filterTree, _filterSpanWithinTree)) { return; } bool isPartialMethodDefinitionPart = symbol.IsPartialDefinition(); // CONSIDER: ignore this if isForSingleSymbol? if (isPartialMethodDefinitionPart) { MethodSymbol implementationPart = ((MethodSymbol)symbol).PartialImplementationPart; if ((object)implementationPart != null) { Visit(implementationPart); } } DocumentationMode maxDocumentationMode; ImmutableArray<DocumentationCommentTriviaSyntax> docCommentNodes; if (!TryGetDocumentationCommentNodes(symbol, out maxDocumentationMode, out docCommentNodes)) { // If the XML in any of the doc comments is invalid, skip all further processing (for this symbol) and // just write a comment saying that info was lost for this symbol. string message = ErrorFacts.GetMessage(MessageID.IDS_XMLIGNORED, CultureInfo.CurrentUICulture); WriteLine(string.Format(CultureInfo.CurrentUICulture, message, symbol.GetDocumentationCommentId())); return; } // If there are no doc comments, then no further work is required (other than to report a diagnostic if one is required). if (docCommentNodes.IsEmpty) { if (maxDocumentationMode >= DocumentationMode.Diagnose && RequiresDocumentationComment(symbol)) { // Report the error at a location in the tree that was parsing doc comments. Location location = GetLocationInTreeReportingDocumentationCommentDiagnostics(symbol); if (location != null) { _diagnostics.Add(ErrorCode.WRN_MissingXMLComment, location, symbol); } } return; } _cancellationToken.ThrowIfCancellationRequested(); bool reportParameterOrTypeParameterDiagnostics = GetLocationInTreeReportingDocumentationCommentDiagnostics(symbol) != null; string withUnprocessedIncludes; bool haveParseError; HashSet<TypeParameterSymbol> documentedTypeParameters; HashSet<ParameterSymbol> documentedParameters; ImmutableArray<CSharpSyntaxNode> includeElementNodes; if (!TryProcessDocumentationCommentTriviaNodes( symbol, isPartialMethodDefinitionPart, docCommentNodes, reportParameterOrTypeParameterDiagnostics, out withUnprocessedIncludes, out haveParseError, out documentedTypeParameters, out documentedParameters, out includeElementNodes)) { return; } if (haveParseError) { // If the XML in any of the doc comments is invalid, skip all further processing (for this symbol) and // just write a comment saying that info was lost for this symbol. string message = ErrorFacts.GetMessage(MessageID.IDS_XMLIGNORED, CultureInfo.CurrentUICulture); WriteLine(string.Format(CultureInfo.CurrentUICulture, message, symbol.GetDocumentationCommentId())); return; } // If there are no include elements, then there's nothing to expand. if (!includeElementNodes.IsDefaultOrEmpty) { _cancellationToken.ThrowIfCancellationRequested(); // NOTE: we are expanding include elements AFTER formatting the comment, since the included text is pure // XML, not XML mixed with documentation comment trivia (e.g. ///). If we expanded them before formatting, // the formatting engine would have trouble determining what prefix to remove from each line. TextWriter expanderWriter = isPartialMethodDefinitionPart ? null : _writer; // Don't actually write partial method definition parts. IncludeElementExpander.ProcessIncludes(withUnprocessedIncludes, symbol, includeElementNodes, _compilation, ref documentedParameters, ref documentedTypeParameters, ref _includedFileCache, expanderWriter, _diagnostics, _cancellationToken); } else if (_writer != null && !isPartialMethodDefinitionPart) { // CONSIDER: The output would look a little different if we ran the XDocument through an XmlWriter. In particular, // formatting inside tags (e.g. <__tag___attr__=__"value"__>) would be normalized. Whitespace in elements would // (or should) not be affected. If we decide that this difference matters, we can run the XDocument through an XmlWriter. // Otherwise, just writing out the string saves a bunch of processing and does a better job of preserving whitespace. Write(withUnprocessedIncludes); } if (reportParameterOrTypeParameterDiagnostics) { _cancellationToken.ThrowIfCancellationRequested(); if (documentedParameters != null) { foreach (ParameterSymbol parameter in GetParameters(symbol)) { if (!documentedParameters.Contains(parameter)) { Location location = parameter.Locations[0]; Debug.Assert(location.SourceTree.ReportDocumentationCommentDiagnostics()); //Should be the same tree as for the symbol. // NOTE: parameter name, since the parameter would be displayed as just its type. _diagnostics.Add(ErrorCode.WRN_MissingParamTag, location, parameter.Name, symbol); } } } if (documentedTypeParameters != null) { foreach (TypeParameterSymbol typeParameter in GetTypeParameters(symbol)) { if (!documentedTypeParameters.Contains(typeParameter)) { Location location = typeParameter.Locations[0]; Debug.Assert(location.SourceTree.ReportDocumentationCommentDiagnostics()); //Should be the same tree as for the symbol. _diagnostics.Add(ErrorCode.WRN_MissingTypeParamTag, location, typeParameter, symbol); } } } } }