Ejemplo n.º 1
0
            private IncludeElementExpander(
                Symbol memberSymbol,
                ImmutableArray <CSharpSyntaxNode> sourceIncludeElementNodes,
                CSharpCompilation compilation,
                HashSet <ParameterSymbol> documentedParameters,
                HashSet <TypeParameterSymbol> documentedTypeParameters,
                DocumentationCommentIncludeCache includedFileCache,
                BindingDiagnosticBag diagnostics,
                CancellationToken cancellationToken)
            {
                _memberSymbol = memberSymbol;
                _sourceIncludeElementNodes = sourceIncludeElementNodes;
                _compilation       = compilation;
                _diagnostics       = diagnostics;
                _cancellationToken = cancellationToken;

                _documentedParameters     = documentedParameters;
                _documentedTypeParameters = documentedTypeParameters;
                _includedFileCache        = includedFileCache;

                _nextSourceIncludeElementIndex = 0;
            }
            private IncludeElementExpander(
                Symbol memberSymbol,
                ImmutableArray<CSharpSyntaxNode> sourceIncludeElementNodes,
                CSharpCompilation compilation,
                HashSet<ParameterSymbol> documentedParameters,
                HashSet<TypeParameterSymbol> documentedTypeParameters,
                DocumentationCommentIncludeCache includedFileCache,
                DiagnosticBag diagnostics,
                CancellationToken cancellationToken)
            {
                this.memberSymbol = memberSymbol;
                this.sourceIncludeElementNodes = sourceIncludeElementNodes;
                this.compilation = compilation;
                this.diagnostics = diagnostics;
                this.cancellationToken = cancellationToken;

                this.documentedParameters = documentedParameters;
                this.documentedTypeParameters = documentedTypeParameters;
                this.includedFileCache = includedFileCache;

                this.nextSourceIncludeElementIndex = 0;
            }
Ejemplo n.º 3
0
            public static void ProcessIncludes(
                string unprocessed,
                Symbol memberSymbol,
                ImmutableArray <CSharpSyntaxNode> sourceIncludeElementNodes,
                CSharpCompilation compilation,
                ref HashSet <ParameterSymbol> documentedParameters,
                ref HashSet <TypeParameterSymbol> documentedTypeParameters,
                ref DocumentationCommentIncludeCache includedFileCache,
                TextWriter writer,
                BindingDiagnosticBag diagnostics,
                CancellationToken cancellationToken)
            {
                // If there are no include elements, then there's nothing to expand.
                // NOTE: By skipping parsing and re-writing, we avoid slightly
                // modifying the whitespace, as we would if we let the XmlWriter
                // do the writing.  This saves us a lot of work in the common case
                // but slightly reduces consistency when include elements are
                // present.
                if (sourceIncludeElementNodes.IsEmpty)
                {
                    if (writer != null)
                    {
                        writer.Write(unprocessed);
                    }
                    return;
                }

                XDocument doc;

                try
                {
                    // NOTE: XDocument.Parse seems to do a better job of preserving whitespace
                    // than XElement.Parse.
                    doc = XDocument.Parse(unprocessed, LoadOptions.PreserveWhitespace);
                }
                catch (XmlException e)
                {
                    // If one of the trees wasn't diagnosing doc comments, then an error might have slipped through.
                    // Otherwise, we shouldn't see exceptions from XDocument.Parse.
                    Debug.Assert(sourceIncludeElementNodes.All(syntax => syntax.SyntaxTree.Options.DocumentationMode < DocumentationMode.Diagnose),
                                 "Why didn't our parser catch this exception? " + e);
                    if (writer != null)
                    {
                        writer.Write(unprocessed);
                    }
                    return;
                }

                cancellationToken.ThrowIfCancellationRequested();

                IncludeElementExpander expander = new IncludeElementExpander(
                    memberSymbol,
                    sourceIncludeElementNodes,
                    compilation,
                    documentedParameters,
                    documentedTypeParameters,
                    includedFileCache,
                    diagnostics,
                    cancellationToken);

                foreach (XNode node in expander.Rewrite(doc, currentXmlFilePath: null, originatingSyntax: null))
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    if (writer != null)
                    {
                        writer.Write(node);
                    }
                }

                Debug.Assert(expander._nextSourceIncludeElementIndex == expander._sourceIncludeElementNodes.Length);

                documentedParameters     = expander._documentedParameters;
                documentedTypeParameters = expander._documentedTypeParameters;
                includedFileCache        = expander._includedFileCache;
            }
Ejemplo n.º 4
0
            private XNode[] RewriteIncludeElement(XElement includeElement, string currentXmlFilePath, CSharpSyntaxNode originatingSyntax, out string commentMessage)
            {
                Location location = GetIncludeElementLocation(includeElement, ref currentXmlFilePath, ref originatingSyntax);

                Debug.Assert(originatingSyntax != null);

                bool diagnose = originatingSyntax.SyntaxTree.ReportDocumentationCommentDiagnostics();

                if (!EnterIncludeElement(location))
                {
                    // NOTE: these must exist since we're already processed this node elsewhere in the call stack.
                    XAttribute fileAttr      = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.FileAttributeName));
                    XAttribute pathAttr      = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.PathAttributeName));
                    string     filePathValue = fileAttr.Value;
                    string     xpathValue    = pathAttr.Value;

                    if (diagnose)
                    {
                        _diagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, new LocalizableErrorArgument(MessageID.IDS_OperationCausedStackOverflow));
                    }

                    commentMessage = ErrorFacts.GetMessage(MessageID.IDS_XMLNOINCLUDE, CultureInfo.CurrentUICulture);

                    // Don't inspect the children - we're already in a cycle.
                    return(new XNode[] { new XComment(commentMessage), includeElement.Copy(copyAttributeAnnotations: false) });
                }

                DiagnosticBag includeDiagnostics = DiagnosticBag.GetInstance();

                try
                {
                    XAttribute fileAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.FileAttributeName));
                    XAttribute pathAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.PathAttributeName));

                    bool hasFileAttribute = fileAttr != null;
                    bool hasPathAttribute = pathAttr != null;
                    if (!hasFileAttribute || !hasPathAttribute)
                    {
                        var subMessage = hasFileAttribute ? MessageID.IDS_XMLMISSINGINCLUDEPATH.Localize() : MessageID.IDS_XMLMISSINGINCLUDEFILE.Localize();
                        includeDiagnostics.Add(ErrorCode.WRN_InvalidInclude, location, subMessage);
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLBADINCLUDE);
                        return(null);
                    }

                    string xpathValue    = pathAttr.Value;
                    string filePathValue = fileAttr.Value;

                    var resolver = _compilation.Options.XmlReferenceResolver;
                    if (resolver == null)
                    {
                        includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, new CodeAnalysisResourcesLocalizableErrorArgument(nameof(CodeAnalysisResources.XmlReferencesNotSupported)));
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                        return(null);
                    }

                    string resolvedFilePath = resolver.ResolveReference(filePathValue, currentXmlFilePath);

                    if (resolvedFilePath == null)
                    {
                        // NOTE: same behavior as IOException.
                        includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, new CodeAnalysisResourcesLocalizableErrorArgument(nameof(CodeAnalysisResources.FileNotFound)));
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                        return(null);
                    }

                    if (_includedFileCache == null)
                    {
                        _includedFileCache = new DocumentationCommentIncludeCache(resolver);
                    }

                    try
                    {
                        XDocument doc;

                        try
                        {
                            doc = _includedFileCache.GetOrMakeDocument(resolvedFilePath);
                        }
                        catch (IOException e)
                        {
                            // NOTE: same behavior as resolvedFilePath == null.
                            includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, e.Message);
                            commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                            return(null);
                        }

                        Debug.Assert(doc != null);

                        string     errorMessage;
                        bool       invalidXPath;
                        XElement[] loadedElements = XmlUtilities.TrySelectElements(doc, xpathValue, out errorMessage, out invalidXPath);
                        if (loadedElements == null)
                        {
                            includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, errorMessage);

                            commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                            if (invalidXPath)
                            {
                                // leave the include node as is
                                return(null);
                            }

                            if (location.IsInSource)
                            {
                                // As in Dev11, return only the comment - drop the include element.
                                return(new XNode[] { new XComment(commentMessage) });
                            }
                            else
                            {
                                commentMessage = null;
                                return(Array.Empty <XNode>());
                            }
                        }

                        if (loadedElements != null && loadedElements.Length > 0)
                        {
                            // change the current XML file path for nodes contained in the document:
                            XNode[] result = RewriteMany(loadedElements, resolvedFilePath, originatingSyntax);

                            // The elements could be rewritten away if they are includes that refer to invalid
                            // (but existing and accessible) XML files.  If this occurs, behave as if we
                            // had failed to find any XPath results (as in Dev11).
                            if (result.Length > 0)
                            {
                                // NOTE: in this case, we do NOT visit the children of the include element -
                                // they are dropped.
                                commentMessage = null;
                                return(result);
                            }
                        }

                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLNOINCLUDE);
                        return(null);
                    }
                    catch (XmlException e)
                    {
                        // NOTE: invalid XML is handled differently from other errors - we don't include the include element
                        // in the results and the location is in the included (vs includING) file.

                        Location errorLocation = XmlLocation.Create(e, resolvedFilePath);
                        includeDiagnostics.Add(ErrorCode.WRN_XMLParseIncludeError, errorLocation, GetDescription(e)); //NOTE: location is in included file.

                        if (location.IsInSource)
                        {
                            commentMessage = string.Format(ErrorFacts.GetMessage(MessageID.IDS_XMLIGNORED2, CultureInfo.CurrentUICulture), resolvedFilePath);

                            // As in Dev11, return only the comment - drop the include element.
                            return(new XNode[] { new XComment(commentMessage) });
                        }
                        else
                        {
                            commentMessage = null;
                            return(Array.Empty <XNode>());
                        }
                    }
                }
                finally
                {
                    if (diagnose)
                    {
                        _diagnostics.AddRange(includeDiagnostics);
                    }

                    includeDiagnostics.Free();

                    LeaveIncludeElement(location);
                }
            }
            public static void ProcessIncludes(
                string unprocessed,
                Symbol memberSymbol,
                ImmutableArray<CSharpSyntaxNode> sourceIncludeElementNodes,
                CSharpCompilation compilation,
                ref HashSet<ParameterSymbol> documentedParameters,
                ref HashSet<TypeParameterSymbol> documentedTypeParameters,
                ref DocumentationCommentIncludeCache includedFileCache,
                TextWriter writer,
                DiagnosticBag diagnostics,
                CancellationToken cancellationToken)
            {
                // If there are no include elements, then there's nothing to expand.
                // NOTE: By skipping parsing and re-writing, we avoid slightly
                // modifying the whitespace, as we would if we let the XmlWriter
                // do the writing.  This saves us a lot of work in the common case
                // but slightly reduces consistency when include elements are
                // present.
                if (sourceIncludeElementNodes.IsEmpty)
                {
                    if (writer != null)
                    {
                        writer.Write(unprocessed);
                    }
                    return;
                }

                XDocument doc;

                try
                {
                    // NOTE: XDocument.Parse seems to do a better job of preserving whitespace
                    // than XElement.Parse.
                    doc = XDocument.Parse(unprocessed, LoadOptions.PreserveWhitespace);
                }
                catch (XmlException e)
                {
                    // If one of the trees wasn't diagnosing doc comments, then an error might have slipped through.
                    // Otherwise, we shouldn't see exceptions from XDocument.Parse.
                    Debug.Assert(sourceIncludeElementNodes.All(syntax => syntax.SyntaxTree.Options.DocumentationMode < DocumentationMode.Diagnose),
                        "Why didn't our parser catch this exception? " + e);
                    if (writer != null)
                    {
                        writer.Write(unprocessed);
                    }
                    return;
                }

                cancellationToken.ThrowIfCancellationRequested();

                IncludeElementExpander expander = new IncludeElementExpander(
                    memberSymbol,
                    sourceIncludeElementNodes,
                    compilation,
                    documentedParameters,
                    documentedTypeParameters,
                    includedFileCache,
                    diagnostics,
                    cancellationToken);

                foreach (XNode node in expander.Rewrite(doc, currentXmlFilePath: null, originatingSyntax: null))
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    if (writer != null)
                    {
                        writer.Write(node);
                    }
                }

                Debug.Assert(expander.nextSourceIncludeElementIndex == expander.sourceIncludeElementNodes.Length);

                documentedParameters = expander.documentedParameters;
                documentedTypeParameters = expander.documentedTypeParameters;
                includedFileCache = expander.includedFileCache;
            }
            /// <remarks>
            /// This method boils down to Rewrite(XDocument.Load(fileAttrValue).XPathSelectElements(pathAttrValue)).  
            /// Everything else is error handling.
            /// </remarks>
            private XNode[] RewriteIncludeElement(XElement includeElement, string currentXmlFilePath, CSharpSyntaxNode originatingSyntax, out string commentMessage)
            {
                Location location = GetIncludeElementLocation(includeElement, ref currentXmlFilePath, ref originatingSyntax);
                Debug.Assert(originatingSyntax != null);

                bool diagnose = originatingSyntax.SyntaxTree.ReportDocumentationCommentDiagnostics();

                if (!EnterIncludeElement(location))
                {
                    // NOTE: these must exist since we're already processed this node elsewhere in the call stack.
                    XAttribute fileAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.FileAttributeName));
                    XAttribute pathAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.PathAttributeName));
                    string filePathValue = fileAttr.Value;
                    string xpathValue = pathAttr.Value;

                    if (diagnose)
                    {
                        diagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, CSharpResources.OperationCausedStackOverflow);
                    }

                    // TODO: use culture from compilation instead of invariant culture?
                    commentMessage = ErrorFacts.GetMessage(MessageID.IDS_XMLNOINCLUDE, CultureInfo.InvariantCulture);

                    // Don't inspect the children - we're already in a cycle.
                    return new XNode[] { new XComment(commentMessage), includeElement.Copy(copyAttributeAnnotations: false) };
                }

                DiagnosticBag includeDiagnostics = DiagnosticBag.GetInstance();

                try
                {
                    XAttribute fileAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.FileAttributeName));
                    XAttribute pathAttr = includeElement.Attribute(XName.Get(DocumentationCommentXmlNames.PathAttributeName));

                    bool hasFileAttribute = fileAttr != null;
                    bool hasPathAttribute = pathAttr != null;
                    if (!hasFileAttribute || !hasPathAttribute)
                    {
                        var subMessage = hasFileAttribute ? MessageID.IDS_XMLMISSINGINCLUDEPATH.Localize() : MessageID.IDS_XMLMISSINGINCLUDEFILE.Localize();
                        includeDiagnostics.Add(ErrorCode.WRN_InvalidInclude, location, subMessage);
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLBADINCLUDE);
                        return null;
                    }

                    string xpathValue = pathAttr.Value;
                    string filePathValue = fileAttr.Value;

                    var resolver = compilation.Options.XmlReferenceResolver;
                    if (resolver == null)
                    {
                        includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, CodeAnalysisResources.XmlReferencesNotSupported);
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                        return null;
                    }

                    string resolvedFilePath = resolver.ResolveReference(filePathValue, currentXmlFilePath);

                    if (resolvedFilePath == null)
                    {
                        // NOTE: same behavior as IOException.
                        includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, CodeAnalysisResources.FileNotFound);
                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                        return null;
                    }

                    if (includedFileCache == null)
                    {
                        includedFileCache = new DocumentationCommentIncludeCache(resolver);
                    }

                    try
                    {
                        XDocument doc;

                        try
                        {
                            doc = includedFileCache.GetOrMakeDocument(resolvedFilePath);
                        }
                        catch (IOException e)
                        {
                            // NOTE: same behavior as resolvedFilePath == null.
                            includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, e.Message);
                            commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                            return null;
                        }

                        Debug.Assert(doc != null);

                        XElement[] loadedElements;
                        try
                        {
                            var xpathResult = doc.XPathSelectElements(xpathValue);

                            // Throws InvalidOperationException if the result of the XPath is an XDocument:
                            loadedElements = (xpathResult != null) ? xpathResult.ToArray() : null;
                        }
                        catch (XPathException e)
                        {
                            includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, e.Message);
                            commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLFAILEDINCLUDE);
                            return null;
                        }
                        catch (InvalidOperationException e)
                        {
                            includeDiagnostics.Add(ErrorCode.WRN_FailedInclude, location, filePathValue, xpathValue, e.Message);

                            if (location.IsInSource)
                            {
                                commentMessage = ErrorFacts.GetMessage(MessageID.IDS_XMLFAILEDINCLUDE, CultureInfo.InvariantCulture);

                                // As in Dev11, return only the comment - drop the include element.
                                return new XNode[] { new XComment(commentMessage) };
                            }
                            else
                            {
                                commentMessage = null;
                                return SpecializedCollections.EmptyArray<XNode>();
                            }
                        }

                        if (loadedElements != null && loadedElements.Length > 0)
                        {
                            // change the current XML file path for nodes contained in the document:
                            XNode[] result = RewriteMany(loadedElements, resolvedFilePath, originatingSyntax);

                            // The elements could be rewritten away if they are includes that refer to invalid
                            // (but existing and accessible) XML files.  If this occurs, behave as if we
                            // had failed to find any XPath results (as in Dev11).
                            if (result.Length > 0)
                            {
                                // NOTE: in this case, we do NOT visit the children of the include element -
                                // they are dropped.
                                commentMessage = null;
                                return result;
                            }
                        }

                        commentMessage = MakeCommentMessage(location, MessageID.IDS_XMLNOINCLUDE);
                        return null;
                    }
                    catch (XmlException e)
                    {
                        // NOTE: invalid XML is handled differently from other errors - we don't include the include element
                        // in the results and the location is in the included (vs includING) file.

                        Location errorLocation = XmlLocation.Create(e, resolvedFilePath);
                        includeDiagnostics.Add(ErrorCode.WRN_XMLParseIncludeError, errorLocation, GetDescription(e)); //NOTE: location is in included file.

                        if (location.IsInSource)
                        {
                            commentMessage = string.Format(ErrorFacts.GetMessage(MessageID.IDS_XMLIGNORED2, CultureInfo.InvariantCulture), resolvedFilePath);

                            // As in Dev11, return only the comment - drop the include element.
                            return new XNode[] { new XComment(commentMessage) };
                        }
                        else
                        {
                            commentMessage = null;
                            return SpecializedCollections.EmptyArray<XNode>();
                        }
                    }
                }
                finally
                {
                    if (diagnose)
                    {
                        diagnostics.AddRange(includeDiagnostics);
                    }

                    includeDiagnostics.Free();

                    LeaveIncludeElement(location);
                }
            }