File: DocumentationComments\DocumentationModeTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Symbol\Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Symbol.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
{
    public class DocumentationModeTests : CSharpTestBase
    {
        [Fact]
        public void XmlSyntaxError_Inline()
        {
            var xml = @"<unclosed>";
 
            var expectedText = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <!-- Badly formed XML comment ignored for member ""T:Partial"" -->
        <!-- Badly formed XML comment ignored for member ""T:Parse"" -->
        <!-- Badly formed XML comment ignored for member ""T:Diagnose"" -->
    </members>
</doc>
".Trim();
 
            TestInline(xml, expectedText,
                // Diagnose.cs(4,1): warning CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'unclosed'.'
                //  */
                Diagnostic(ErrorCode.WRN_XMLParseError, "").WithArguments("unclosed"),
                // Diagnose.cs(9,1): warning CS1570: XML comment has badly formed XML -- 'Expected an end tag for element 'unclosed'.'
                //  */
                Diagnostic(ErrorCode.WRN_XMLParseError, "").WithArguments("unclosed"));
        }
 
        [ClrOnlyFact(ClrOnlyReason.DocumentationComment, Skip = "https://github.com/dotnet/roslyn/issues/8807")]
        public void XmlSyntaxError_Included()
        {
            var xml = @"<unclosed>";
            var xpath = "*";
 
            var expectedTextTemplate = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name=""T:Partial"">
            Parse: <!-- Badly formed XML file ""{0}"" cannot be included -->
            Diagnose: <!-- Badly formed XML file ""{0}"" cannot be included -->
        </member>
        <member name=""T:Parse"">
            <!-- Badly formed XML file ""{0}"" cannot be included -->
        </member>
        <member name=""T:Diagnose"">
            <!-- Badly formed XML file ""{0}"" cannot be included -->
        </member>
    </members>
</doc>
".Trim();
 
            // Diagnostics are from types Diagnose and Partial.
            TestIncluded(xml, xpath, expectedTextTemplate, /*fallbackToErrorCodeOnlyForNonEnglish*/ true,
                // ff1abe1df1d7.xml(1,11): warning CS1592: Badly formed XML in included comments file -- 'Unexpected end of file has occurred. The following elements are not closed: unclosed.'
                Diagnostic(ErrorCode.WRN_XMLParseIncludeError).WithArguments("Unexpected end of file has occurred. The following elements are not closed: unclosed."),
                // ff1abe1df1d7.xml(1,11): warning CS1592: Badly formed XML in included comments file -- 'Unexpected end of file has occurred. The following elements are not closed: unclosed.'
                Diagnostic(ErrorCode.WRN_XMLParseIncludeError).WithArguments("Unexpected end of file has occurred. The following elements are not closed: unclosed."));
        }
 
        [Fact]
        public void CrefSyntaxError_Inline()
        {
            var xml = @"<see cref='#' />";
 
            var expectedText = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name=""T:Partial"">
            Parse
            <see cref='!:#' />
            Diagnose
            <see cref='!:#' />
        </member>
        <member name=""T:Parse"">
            <see cref='!:#' />
        </member>
        <member name=""T:Diagnose"">
            <see cref='!:#' />
        </member>
    </members>
</doc>
".Trim();
 
            TestInline(xml, expectedText,
                // Diagnose.cs(3,12): warning CS1584: XML comment has syntactically incorrect cref attribute '#'
                // <see cref='#' />
                Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "#").WithArguments("#"),
                // Diagnose.cs(3,12): warning CS1658: Identifier expected. See also error CS1001.
                // <see cref='#' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, "#").WithArguments("Identifier expected", "1001"),
                // Diagnose.cs(3,12): warning CS1658: Unexpected character '#'. See also error CS1056.
                // <see cref='#' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, "").WithArguments("Unexpected character '#'", "1056"),
 
                // Diagnose.cs(9,12): warning CS1584: XML comment has syntactically incorrect cref attribute '#'
                // <see cref='#' />
                Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "#").WithArguments("#"),
                // Diagnose.cs(9,12): warning CS1658: Identifier expected. See also error CS1001.
                // <see cref='#' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, "#").WithArguments("Identifier expected", "1001"),
                // Diagnose.cs(9,12): warning CS1658: Unexpected character '#'. See also error CS1056.
                // <see cref='#' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, "").WithArguments("Unexpected character '#'", "1056"));
        }
 
        [Fact]
        public void CrefSyntaxError_Included()
        {
            var xml = @"<see cref='#' />";
            var xpath = "see";
 
            var expectedTextTemplate = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name=""T:Partial"">
            Parse: <see cref=""!:#"" />
            Diagnose: <see cref=""!:#"" />
        </member>
        <member name=""T:Parse"">
            <see cref=""!:#"" />
        </member>
        <member name=""T:Diagnose"">
            <see cref=""!:#"" />
        </member>
    </members>
</doc>
".Trim();
 
            TestIncluded(xml, xpath, expectedTextTemplate, includeElement => new[]
            {
                // ExpandIncludes.cs(2,5): warning CS1584: XML comment has syntactically incorrect cref attribute '#'
                // /// <include file='d6f61c210f5e.xml' path='see' />
                Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, includeElement).WithArguments("#"),
                // ExpandIncludes.cs(2,5): warning CS1658: Identifier expected. See also error CS1001.
                // /// <include file='d6f61c210f5e.xml' path='see' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, includeElement).WithArguments("Identifier expected", "1001"),
                // ExpandIncludes.cs(2,5): warning CS1658: Unexpected character '#'. See also error CS1056.
                // /// <include file='d6f61c210f5e.xml' path='see' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, includeElement).WithArguments("Unexpected character '#'", "1056"),
 
                // ExpandIncludes.cs(5,21): warning CS1584: XML comment has syntactically incorrect cref attribute '#'
                // /// ExpandIncludes: <include file='d6f61c210f5e.xml' path='see' />
                Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, includeElement).WithArguments("#"),
                // ExpandIncludes.cs(5,21): warning CS1658: Identifier expected. See also error CS1001.
                // /// ExpandIncludes: <include file='d6f61c210f5e.xml' path='see' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, includeElement).WithArguments("Identifier expected", "1001"),
                // ExpandIncludes.cs(5,21): warning CS1658: Unexpected character '#'. See also error CS1056.
                // /// ExpandIncludes: <include file='d6f61c210f5e.xml' path='see' />
                Diagnostic(ErrorCode.WRN_ErrorOverride, includeElement).WithArguments("Unexpected character '#'", "1056")
            });
        }
 
        [Fact]
        public void CrefSemanticError_Inline()
        {
            var xml = @"<see cref='NotFound' />";
 
            var expectedText = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name=""T:Partial"">
            Parse
            <see cref='!:NotFound' />
            Diagnose
            <see cref='!:NotFound' />
        </member>
        <member name=""T:Parse"">
            <see cref='!:NotFound' />
        </member>
        <member name=""T:Diagnose"">
            <see cref='!:NotFound' />
        </member>
    </members>
</doc>
".Trim();
 
            TestInline(xml, expectedText,
                // Diagnose.cs(9,12): warning CS1574: XML comment has cref attribute 'NotFound' that could not be resolved
                // <see cref='NotFound' />
                Diagnostic(ErrorCode.WRN_BadXMLRef, "NotFound").WithArguments("NotFound"),
                // ExpandIncludes.cs(9,12): warning CS1574: XML comment has cref attribute 'NotFound' that could not be resolved
                // <see cref='NotFound' />
                Diagnostic(ErrorCode.WRN_BadXMLRef, "NotFound").WithArguments("NotFound"));
        }
 
        [Fact]
        public void CrefSemanticError_Included()
        {
            var xml = @"<see cref='NotFound' />";
            var xpath = "see";
 
            var expectedTextTemplate = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name=""T:Partial"">
            Parse: <see cref=""!:NotFound"" />
            Diagnose: <see cref=""!:NotFound"" />
        </member>
        <member name=""T:Parse"">
            <see cref=""!:NotFound"" />
        </member>
        <member name=""T:Diagnose"">
            <see cref=""!:NotFound"" />
        </member>
    </members>
</doc>
".Trim();
 
            TestIncluded(xml, xpath, expectedTextTemplate, includeElement => new[]
            {
                // ExpandIncludes.cs(2,5): warning CS1574: XML comment has cref attribute 'NotFound' that could not be resolved
                // /// <include file='5127bff2acf3.xml' path='see' />
                Diagnostic(ErrorCode.WRN_BadXMLRef, includeElement).WithArguments("NotFound"),
 
                // ExpandIncludes.cs(5,21): warning CS1574: XML comment has cref attribute 'NotFound' that could not be resolved
                // /// ExpandIncludes: <include file='5127bff2acf3.xml' path='see' />
                Diagnostic(ErrorCode.WRN_BadXMLRef, includeElement).WithArguments("NotFound")
            });
        }
 
        [Fact]
        public void NameSemanticError_Inline()
        {
            var xml = @"<typeparam name='NotFound' />";
 
            var expectedText = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name=""T:Partial"">
            Parse
            <typeparam name='NotFound' />
            Diagnose
            <typeparam name='NotFound' />
        </member>
        <member name=""T:Parse"">
            <typeparam name='NotFound' />
        </member>
        <member name=""T:Diagnose"">
            <typeparam name='NotFound' />
        </member>
    </members>
</doc>
".Trim();
 
            TestInline(xml, expectedText,
                // Diagnose.cs(3,18): warning CS1711: XML comment has a typeparam tag for 'NotFound', but there is no type parameter by that name
                // <typeparam name='NotFound' />
                Diagnostic(ErrorCode.WRN_UnmatchedTypeParamTag, "NotFound").WithArguments("NotFound"),
                // Diagnose.cs(9,18): warning CS1711: XML comment has a typeparam tag for 'NotFound', but there is no type parameter by that name
                // <typeparam name='NotFound' />
                Diagnostic(ErrorCode.WRN_UnmatchedTypeParamTag, "NotFound").WithArguments("NotFound"));
        }
 
        [Fact]
        public void NameSemanticError_Included()
        {
            var xml = @"<typeparam name='NotFound' />";
            var xpath = "typeparam";
 
            var expectedTextTemplate = @"
<?xml version=""1.0""?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name=""T:Partial"">
            Parse: <typeparam name=""NotFound"" />
            Diagnose: <typeparam name=""NotFound"" />
        </member>
        <member name=""T:Parse"">
            <typeparam name=""NotFound"" />
        </member>
        <member name=""T:Diagnose"">
            <typeparam name=""NotFound"" />
        </member>
    </members>
</doc>
".Trim();
 
            TestIncluded(xml, xpath, expectedTextTemplate, includeElement => new[]
            {
                // ExpandIncludes.cs(5,21): warning CS1711: XML comment has a typeparam tag for 'NotFound', but there is no type parameter by that name
                // /// ExpandIncludes: <include file='3590e97bd224.xml' path='typeparam' />
                Diagnostic(ErrorCode.WRN_UnmatchedTypeParamTag, includeElement).WithArguments("NotFound"),
                // ExpandIncludes.cs(2,5): warning CS1711: XML comment has a typeparam tag for 'NotFound', but there is no type parameter by that name
                // /// <include file='3590e97bd224.xml' path='typeparam' />
                Diagnostic(ErrorCode.WRN_UnmatchedTypeParamTag, includeElement).WithArguments("NotFound")
            });
        }
 
        private static void TestInline(string xml, string expectedText, params DiagnosticDescription[] expectedDiagnostics)
        {
            var sourceTemplate = @"
/**
{0}
 */
class {1} {{ }}
 
/**
{1}
{0}
 */
partial class Partial {{ }}
";
 
            var trees = AllModes.Select(mode =>
                Parse(string.Format(sourceTemplate, xml, mode), string.Format("{0}.cs", mode), GetOptions(mode)));
 
            var comp = CreateCompilation(trees.ToArray(), assemblyName: "Test");
            comp.VerifyDiagnostics(expectedDiagnostics);
 
            var actualText = GetDocumentationCommentText(comp, expectedDiagnostics: null);
            Assert.Equal(expectedText, actualText);
        }
 
        private void TestIncluded(string xml, string xpath, string expectedTextTemplate, bool fallbackToErrorCodeOnlyForNonEnglish, params DiagnosticDescription[] expectedDiagnostics)
        {
            TestIncluded(xml, xpath, expectedTextTemplate, unused => expectedDiagnostics, fallbackToErrorCodeOnlyForNonEnglish);
        }
 
        private void TestIncluded(string xml, string xpath, string expectedTextTemplate, Func<string, DiagnosticDescription[]> makeExpectedDiagnostics, bool fallbackToErrorCodeOnlyForNonEnglish = false)
        {
            var xmlFile = Temp.CreateFile(extension: ".xml").WriteAllText(xml);
            var xmlFilePath = xmlFile.Path;
 
            string includeElement = string.Format(@"<include file='{0}' path='{1}' />", xmlFilePath, xpath);
            var sourceTemplate = @"
/// {0}
class {1} {{ }}
 
/// {1}: {0}
partial class Partial {{ }}
";
 
            var trees = AllModes.Select(mode =>
                Parse(string.Format(sourceTemplate, includeElement, mode), string.Format("{0}.cs", mode), GetOptions(mode)));
 
            var comp = CreateCompilation(
                trees.ToArray(),
                options: TestOptions.ReleaseDll.WithXmlReferenceResolver(XmlFileResolver.Default),
                assemblyName: "Test");
 
            comp.GetDiagnostics().Verify(fallbackToErrorCodeOnlyForNonEnglish: fallbackToErrorCodeOnlyForNonEnglish, expected: makeExpectedDiagnostics(includeElement));
 
            var actualText = GetDocumentationCommentText(comp, expectedDiagnostics: null);
            var expectedText = string.Format(expectedTextTemplate, TestHelpers.AsXmlCommentText(xmlFilePath));
            Assert.Equal(expectedText, actualText);
        }
 
        private static CSharpParseOptions GetOptions(DocumentationMode mode)
        {
            return TestOptions.Regular.WithDocumentationMode(mode);
        }
 
        private static IEnumerable<DocumentationMode> AllModes
        {
            get
            {
                var modes = Enumerable.Range((int)DocumentationMode.None, DocumentationMode.Diagnose - DocumentationMode.None + 1).Select(i => (DocumentationMode)i);
                AssertEx.All(modes, mode => mode.IsValid());
                return modes;
            }
        }
    }
}