/// <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 &lt;typeparam&gt; elements;
        ///   4) set of parameters covered by &lt;param&gt; elements;
        ///   5) list of &lt;include&gt; 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);
                        }
                    }
                }
            }
        }