Customization fixes (#4385)
- ClientModelPlugin.AdditionalMetadataReferences now includes System.Memory.Data and System.Text.Json in order to allow Roslyn to parse types from these assemblies found in custom code. BinaryData and IJsonModel (which references Utf8JsonWriter) are common in customized partial classes. - AdditionalMetadataReferences were not added to the compilation for custom code which would cause Roslyn to not load the additional references correctly - Even after fixing the above two issues, framework types that are not a dependency of the generator, e.g. BinaryData, would not be found by Type.GetType call in NamedTypeSymbolProvider. To work around this issue, we will create a new CSharpType for such framework types using all of the ISymbol information as inputs. - Add handling for malformed xml docs
This commit is contained in:
Родитель
2010bc4490
Коммит
a5fc2ce620
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.ClientModel;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Text.Json;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Generator.CSharp.ClientModel
|
||||
|
@ -21,7 +22,12 @@ namespace Microsoft.Generator.CSharp.ClientModel
|
|||
|
||||
public override ScmTypeFactory TypeFactory { get; }
|
||||
|
||||
public override IReadOnlyList<MetadataReference> AdditionalMetadataReferences => [MetadataReference.CreateFromFile(typeof(ClientResult).Assembly.Location)];
|
||||
public override IReadOnlyList<MetadataReference> AdditionalMetadataReferences =>
|
||||
[
|
||||
MetadataReference.CreateFromFile(typeof(ClientResult).Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(BinaryData).Assembly.Location),
|
||||
MetadataReference.CreateFromFile(typeof(JsonSerializer).Assembly.Location)
|
||||
];
|
||||
|
||||
[ImportingConstructor]
|
||||
public ClientModelPlugin(GeneratorContext context)
|
||||
|
|
|
@ -176,7 +176,7 @@ namespace Microsoft.Generator.CSharp
|
|||
}
|
||||
|
||||
project = project
|
||||
.AddMetadataReferences(_assemblyMetadataReferences.Value)
|
||||
.AddMetadataReferences(_assemblyMetadataReferences.Value.Concat(CodeModelPlugin.Instance.AdditionalMetadataReferences))
|
||||
.WithCompilationOptions(new CSharpCompilationOptions(
|
||||
OutputKind.DynamicallyLinkedLibrary, metadataReferenceResolver: _metadataReferenceResolver.Value, nullableContextOptions: NullableContextOptions.Disable));
|
||||
|
||||
|
|
|
@ -456,8 +456,37 @@ namespace Microsoft.Generator.CSharp.Primitives
|
|||
return obj is CSharpType csType && Equals(csType, ignoreNullable: false);
|
||||
}
|
||||
|
||||
public bool Equals(Type type) =>
|
||||
IsFrameworkType && (type.IsGenericType ? type.GetGenericTypeDefinition() == FrameworkType && AreArgumentsEqual(type.GetGenericArguments()) : type == FrameworkType);
|
||||
public bool Equals(Type type)
|
||||
{
|
||||
if (IsFrameworkType)
|
||||
{
|
||||
return type.IsGenericType
|
||||
? type.GetGenericTypeDefinition() == FrameworkType && AreArgumentsEqual(type.GetGenericArguments())
|
||||
: type == FrameworkType;
|
||||
}
|
||||
|
||||
return Name == type.Name && Namespace == type.Namespace && IsValueType == type.IsValueType && AreArgumentsEqual(Arguments, type.GetGenericArguments())
|
||||
&& IsEnum == type.IsEnum && IsStruct == type.IsValueType && IsPublic == type.IsPublic
|
||||
&& ((type.IsEnum && _underlyingType! == type.GetEnumUnderlyingType()) || !type.IsEnum);
|
||||
}
|
||||
|
||||
private static bool AreArgumentsEqual(IReadOnlyList<CSharpType> arguments, IReadOnlyList<Type> genericArguments)
|
||||
{
|
||||
if (arguments.Count != genericArguments.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < arguments.Count; i++)
|
||||
{
|
||||
if (!arguments[i].Equals(genericArguments[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed override int GetHashCode()
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Generator.CSharp.Primitives;
|
||||
|
@ -22,7 +24,7 @@ namespace Microsoft.Generator.CSharp.Providers
|
|||
|
||||
private protected sealed override TypeProvider? GetCustomCodeView() => null;
|
||||
|
||||
protected override string BuildRelativeFilePath() => throw new InvalidOperationException("This type should not be writting in generation");
|
||||
protected override string BuildRelativeFilePath() => throw new InvalidOperationException("This type should not be writing in generation");
|
||||
|
||||
protected override string BuildName() => _namedTypeSymbol.Name;
|
||||
|
||||
|
@ -107,7 +109,7 @@ namespace Microsoft.Generator.CSharp.Providers
|
|||
methodSymbol.Name,
|
||||
GetSymbolXmlDoc(methodSymbol, "summary"),
|
||||
modifiers,
|
||||
GetCSharpType(methodSymbol.ReturnType),
|
||||
GetNullableCSharpType(methodSymbol.ReturnType),
|
||||
GetSymbolXmlDoc(methodSymbol, "returns"),
|
||||
[.. methodSymbol.Parameters.Select(p => ConvertToParameterProvider(methodSymbol, p))]);
|
||||
|
||||
|
@ -116,7 +118,7 @@ namespace Microsoft.Generator.CSharp.Providers
|
|||
return [.. methods];
|
||||
}
|
||||
|
||||
private static ParameterProvider ConvertToParameterProvider(IMethodSymbol methodSymbol, IParameterSymbol parameterSymbol)
|
||||
private ParameterProvider ConvertToParameterProvider(IMethodSymbol methodSymbol, IParameterSymbol parameterSymbol)
|
||||
{
|
||||
return new ParameterProvider(
|
||||
parameterSymbol.Name,
|
||||
|
@ -141,13 +143,35 @@ namespace Microsoft.Generator.CSharp.Providers
|
|||
var xmlDocumentation = propertySymbol.GetDocumentationCommentXml();
|
||||
if (!string.IsNullOrEmpty(xmlDocumentation))
|
||||
{
|
||||
var xDocument = XDocument.Parse(xmlDocumentation);
|
||||
XDocument xDocument = ParseXml(propertySymbol, xmlDocumentation);
|
||||
var summaryElement = xDocument.Descendants(tag).FirstOrDefault();
|
||||
return FormattableStringHelpers.FromString(summaryElement?.Value.Trim());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static XDocument ParseXml(ISymbol docsSymbol, string xmlDocumentation)
|
||||
{
|
||||
XDocument xDocument;
|
||||
try
|
||||
{
|
||||
xDocument = XDocument.Parse(xmlDocumentation);
|
||||
}
|
||||
catch (XmlException ex)
|
||||
{
|
||||
var files = new List<string>();
|
||||
foreach (var reference in docsSymbol.DeclaringSyntaxReferences)
|
||||
{
|
||||
files.Add(reference.SyntaxTree.FilePath);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Failed to parse XML documentation for {docsSymbol.Name}. " +
|
||||
$"The malformed XML documentation is located in one or more of the following files: {string.Join(',', files)}", ex);
|
||||
}
|
||||
|
||||
return xDocument;
|
||||
}
|
||||
|
||||
private static string? GetParameterXmlDocumentation(IMethodSymbol methodSymbol, IParameterSymbol parameterSymbol)
|
||||
{
|
||||
var xmlDocumentation = methodSymbol.GetDocumentationCommentXml();
|
||||
|
@ -157,7 +181,7 @@ namespace Microsoft.Generator.CSharp.Providers
|
|||
return null;
|
||||
}
|
||||
|
||||
var xmlDoc = XDocument.Parse(xmlDocumentation);
|
||||
var xmlDoc = ParseXml(methodSymbol, xmlDocumentation);
|
||||
var paramElement = xmlDoc.Descendants("param")
|
||||
.FirstOrDefault(e => e.Attribute("name")?.Value == parameterSymbol.Name);
|
||||
|
||||
|
@ -182,41 +206,37 @@ namespace Microsoft.Generator.CSharp.Providers
|
|||
_ => FieldModifiers.Public
|
||||
};
|
||||
|
||||
private static CSharpType GetCSharpType(ITypeSymbol typeSymbol)
|
||||
private CSharpType? GetNullableCSharpType(ITypeSymbol typeSymbol)
|
||||
{
|
||||
var fullyQualifiedName = GetFullyQualifiedName(typeSymbol);
|
||||
var pieces = fullyQualifiedName.Split('.');
|
||||
if (fullyQualifiedName == "System.Void")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return GetCSharpType(typeSymbol);
|
||||
}
|
||||
|
||||
private CSharpType GetCSharpType(ITypeSymbol typeSymbol)
|
||||
{
|
||||
var fullyQualifiedName = GetFullyQualifiedName(typeSymbol);
|
||||
var namedTypeSymbol = typeSymbol as INamedTypeSymbol;
|
||||
|
||||
//if fully qualified name is in the namespace of the library being emitted find it from the outputlibrary
|
||||
if (fullyQualifiedName.StartsWith(CodeModelPlugin.Instance.Configuration.RootNamespace, StringComparison.Ordinal))
|
||||
{
|
||||
bool isValueType = typeSymbol.IsValueType;
|
||||
bool isEnum = typeSymbol.TypeKind == TypeKind.Enum;
|
||||
return new CSharpType(
|
||||
typeSymbol.Name,
|
||||
string.Join('.', pieces.Take(pieces.Length - 1)),
|
||||
isValueType,
|
||||
typeSymbol.NullableAnnotation == NullableAnnotation.Annotated,
|
||||
typeSymbol.ContainingType is not null ? GetCSharpType(typeSymbol.ContainingType) : null,
|
||||
namedTypeSymbol is not null ? [.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)] : [],
|
||||
typeSymbol.DeclaredAccessibility == Accessibility.Public,
|
||||
isValueType && !isEnum,
|
||||
baseType: typeSymbol.BaseType is not null ? GetCSharpType(typeSymbol.BaseType) : null,
|
||||
underlyingEnumType: namedTypeSymbol is not null && namedTypeSymbol.EnumUnderlyingType is not null
|
||||
? GetCSharpType(namedTypeSymbol.EnumUnderlyingType).FrameworkType
|
||||
: null);
|
||||
return ConstructCSharpTypeFromSymbol(typeSymbol, fullyQualifiedName, namedTypeSymbol);
|
||||
}
|
||||
|
||||
Type? type = System.Type.GetType(fullyQualifiedName);
|
||||
if (type is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to convert ITypeSymbol: {fullyQualifiedName} to a CSharpType");
|
||||
if (typeSymbol.TypeKind == TypeKind.Error)
|
||||
throw new InvalidOperationException($"Unable to convert ITypeSymbol: {fullyQualifiedName} to a CSharpType in {Name}");
|
||||
|
||||
return ConstructCSharpTypeFromSymbol(typeSymbol, fullyQualifiedName, namedTypeSymbol);
|
||||
}
|
||||
|
||||
CSharpType result = new CSharpType(type);
|
||||
|
||||
if (namedTypeSymbol is not null && namedTypeSymbol.IsGenericType)
|
||||
{
|
||||
return result.MakeGenericType([.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)]);
|
||||
|
@ -225,6 +245,29 @@ namespace Microsoft.Generator.CSharp.Providers
|
|||
return result;
|
||||
}
|
||||
|
||||
private CSharpType ConstructCSharpTypeFromSymbol(
|
||||
ITypeSymbol typeSymbol,
|
||||
string fullyQualifiedName,
|
||||
INamedTypeSymbol? namedTypeSymbol)
|
||||
{
|
||||
bool isValueType = typeSymbol.IsValueType;
|
||||
bool isEnum = typeSymbol.TypeKind == TypeKind.Enum;
|
||||
var pieces = fullyQualifiedName.Split('.');
|
||||
return new CSharpType(
|
||||
typeSymbol.Name,
|
||||
string.Join('.', pieces.Take(pieces.Length - 1)),
|
||||
isValueType,
|
||||
typeSymbol.NullableAnnotation == NullableAnnotation.Annotated,
|
||||
typeSymbol.ContainingType is not null ? GetCSharpType(typeSymbol.ContainingType) : null,
|
||||
namedTypeSymbol is not null ? [.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)] : [],
|
||||
typeSymbol.DeclaredAccessibility == Accessibility.Public,
|
||||
isValueType && !isEnum,
|
||||
baseType: typeSymbol.BaseType is not null && typeSymbol.BaseType.TypeKind != TypeKind.Error ? GetCSharpType(typeSymbol.BaseType) : null,
|
||||
underlyingEnumType: namedTypeSymbol is not null && namedTypeSymbol.EnumUnderlyingType is not null
|
||||
? GetCSharpType(namedTypeSymbol.EnumUnderlyingType).FrameworkType
|
||||
: null);
|
||||
}
|
||||
|
||||
private static string GetFullyQualifiedName(ITypeSymbol typeSymbol)
|
||||
{
|
||||
// Handle special cases for built-in types
|
||||
|
|
|
@ -32,6 +32,26 @@ namespace Microsoft.Generator.CSharp.Tests.Primitives
|
|||
Assert.IsTrue(cst1.Equals(cst2));
|
||||
}
|
||||
|
||||
[TestCase(typeof(int))]
|
||||
[TestCase(typeof(string))]
|
||||
[TestCase(typeof(int[]))]
|
||||
[TestCase(typeof(string[]))]
|
||||
[TestCase(typeof(IDictionary<int, string>))]
|
||||
[TestCase(typeof(int?))]
|
||||
public void NonFrameworkTypeEqualsEquivalentFrameworkType(Type type)
|
||||
{
|
||||
var cSharpType = new CSharpType(
|
||||
type.Name,
|
||||
type.Namespace!,
|
||||
type.IsValueType,
|
||||
Nullable.GetUnderlyingType(type) != null,
|
||||
null,
|
||||
type.GetGenericArguments().Select(t => new CSharpType(t)).ToList(),
|
||||
true,
|
||||
type.IsValueType);
|
||||
Assert.IsTrue(cSharpType.Equals(type));
|
||||
}
|
||||
|
||||
[TestCase(typeof(int))]
|
||||
[TestCase(typeof(IList<>))]
|
||||
[TestCase(typeof(IList<int>))]
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Generator.CSharp.Primitives;
|
||||
using Microsoft.Generator.CSharp.Providers;
|
||||
|
||||
namespace Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders
|
||||
{
|
||||
public static class CompilationHelper
|
||||
{
|
||||
public static Compilation LoadCompilation(IEnumerable<TypeProvider> providers, IEnumerable<Type>? metadataReferenceTypes = default)
|
||||
{
|
||||
MockHelpers.LoadMockPlugin();
|
||||
List<SyntaxTree> files = new List<SyntaxTree>();
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
files.Add(GetTree(provider));
|
||||
}
|
||||
|
||||
return CSharpCompilation.Create(
|
||||
assemblyName: "TestAssembly",
|
||||
syntaxTrees: [.. files],
|
||||
references: [.. metadataReferenceTypes?.Select(t => MetadataReference.CreateFromFile(t.Assembly.Location)) ?? [], MetadataReference.CreateFromFile(typeof(object).Assembly.Location)]);
|
||||
}
|
||||
|
||||
private static SyntaxTree GetTree(TypeProvider provider)
|
||||
{
|
||||
var writer = new TypeProviderWriter(provider);
|
||||
var file = writer.Write();
|
||||
return CSharpSyntaxTree.ParseText(file.Content, path: Path.Join(provider.RelativeFilePath, provider.Name + ".cs"));
|
||||
}
|
||||
|
||||
internal static INamedTypeSymbol? GetSymbol(INamespaceSymbol namespaceSymbol, string name)
|
||||
{
|
||||
foreach (var childNamespaceSymbol in namespaceSymbol.GetNamespaceMembers())
|
||||
{
|
||||
return GetSymbol(childNamespaceSymbol, name);
|
||||
}
|
||||
|
||||
foreach (INamedTypeSymbol symbol in namespaceSymbol.GetTypeMembers())
|
||||
{
|
||||
if (symbol.MetadataName == name)
|
||||
{
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.ClientModel;
|
||||
using System.ClientModel.Primitives;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Generator.CSharp.Primitives;
|
||||
using Microsoft.Generator.CSharp.Providers;
|
||||
using Microsoft.Generator.CSharp.Snippets;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders
|
||||
{
|
||||
public class InterfaceTests
|
||||
{
|
||||
[Test]
|
||||
public void ValidateMethods()
|
||||
{
|
||||
var model = new Model();
|
||||
var compilation = CompilationHelper.LoadCompilation([model],
|
||||
[
|
||||
typeof(BinaryData),
|
||||
typeof(JsonSerializer),
|
||||
typeof(ClientResult)
|
||||
]);
|
||||
var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "Model");
|
||||
|
||||
var namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
|
||||
var methods = namedTypeSymbolProvider.Methods;
|
||||
|
||||
Assert.AreEqual(1, methods.Count);
|
||||
Assert.AreEqual("global::System.ClientModel.Primitives.IJsonModel<T>.Write", methods[0].Signature.Name);
|
||||
Assert.AreEqual(2, methods[0].Signature.Parameters.Count);
|
||||
Assert.IsTrue(methods[0].Signature.Parameters[0].Type.Equals(typeof(Utf8JsonWriter)));
|
||||
Assert.IsTrue(methods[0].Signature.Parameters[1].Type.Equals(typeof(ModelReaderWriterOptions)));
|
||||
Assert.IsNull(methods[0].Signature.ReturnType);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void VerifyPropertyCanBeLoaded()
|
||||
{
|
||||
var model = new Model();
|
||||
var compilation = CompilationHelper.LoadCompilation([model], [typeof(BinaryData)]);
|
||||
var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "Model");
|
||||
|
||||
var namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
|
||||
Assert.AreEqual(2, namedTypeSymbolProvider.Properties.Count);
|
||||
Assert.AreEqual("X", namedTypeSymbolProvider.Properties[0].Name);
|
||||
Assert.IsTrue(namedTypeSymbolProvider.Properties[0].Type.Equals(typeof(int)));
|
||||
Assert.AreEqual("Y", namedTypeSymbolProvider.Properties[1].Name);
|
||||
Assert.IsTrue(namedTypeSymbolProvider.Properties[1].Type.Equals(typeof(BinaryData)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void VerifyBaseTypeIsNull()
|
||||
{
|
||||
var model = new Model();
|
||||
var compilation = CompilationHelper.LoadCompilation([model]);
|
||||
var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "Model");
|
||||
|
||||
var namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
|
||||
Assert.IsNull(namedTypeSymbolProvider.Type.BaseType);
|
||||
}
|
||||
|
||||
private class Model : TypeProvider
|
||||
{
|
||||
protected override string BuildRelativeFilePath() => ".";
|
||||
|
||||
protected override string BuildName() => "Model";
|
||||
|
||||
protected override CSharpType[] BuildImplements()
|
||||
{
|
||||
return [new CSharpType(typeof(IJsonModel<>), Type)];
|
||||
}
|
||||
|
||||
protected override MethodProvider[] BuildMethods()
|
||||
{
|
||||
var sig = new MethodSignature("Write", $"", MethodSignatureModifiers.None, null, $"", [ new ParameterProvider("writer", $"", typeof(Utf8JsonWriter)), new ParameterProvider("options", $"", typeof(ModelReaderWriterOptions)) ], null, null, null, new CSharpType(typeof(IJsonModel<>)));
|
||||
return [new MethodProvider(sig, Snippet.ThrowExpression(Snippet.Null), this, null)];
|
||||
}
|
||||
|
||||
protected override PropertyProvider[] BuildProperties()
|
||||
{
|
||||
return
|
||||
[
|
||||
new PropertyProvider($"", MethodSignatureModifiers.Public, new CSharpType(typeof(int)), "X",
|
||||
new AutoPropertyBody(true), this),
|
||||
new PropertyProvider($"", MethodSignatureModifiers.Public, new CSharpType(typeof(BinaryData)), "Y",
|
||||
new AutoPropertyBody(true), this),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,13 +6,12 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Generator.CSharp.Primitives;
|
||||
using Microsoft.Generator.CSharp.Providers;
|
||||
using NUnit.Framework;
|
||||
using static Microsoft.Generator.CSharp.Snippets.Snippet;
|
||||
|
||||
namespace Microsoft.Generator.CSharp.Tests.Providers
|
||||
namespace Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders
|
||||
{
|
||||
public class NamedTypeSymbolProviderTests
|
||||
{
|
||||
|
@ -21,18 +20,7 @@ namespace Microsoft.Generator.CSharp.Tests.Providers
|
|||
|
||||
public NamedTypeSymbolProviderTests()
|
||||
{
|
||||
MockHelpers.LoadMockPlugin();
|
||||
|
||||
List<SyntaxTree> files =
|
||||
[
|
||||
GetTree(new NamedSymbol()),
|
||||
GetTree(new PropertyType())
|
||||
];
|
||||
|
||||
var compilation = CSharpCompilation.Create(
|
||||
assemblyName: "TestAssembly",
|
||||
syntaxTrees: [.. files],
|
||||
references: [MetadataReference.CreateFromFile(typeof(object).Assembly.Location)]);
|
||||
var compilation = CompilationHelper.LoadCompilation([new NamedSymbol(), new PropertyType()]);
|
||||
var iNamedSymbol = GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol");
|
||||
|
||||
_namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
|
||||
|
@ -209,13 +197,6 @@ namespace Microsoft.Generator.CSharp.Tests.Providers
|
|||
protected override string BuildName() => "PropertyType";
|
||||
}
|
||||
|
||||
private static SyntaxTree GetTree(TypeProvider provider)
|
||||
{
|
||||
var writer = new TypeProviderWriter(provider);
|
||||
var file = writer.Write();
|
||||
return CSharpSyntaxTree.ParseText(file.Content);
|
||||
}
|
||||
|
||||
internal static INamedTypeSymbol? GetSymbol(INamespaceSymbol namespaceSymbol, string name)
|
||||
{
|
||||
foreach (var childNamespaceSymbol in namespaceSymbol.GetNamespaceMembers())
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.ClientModel.Primitives;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Xml;
|
||||
using Microsoft.Generator.CSharp.Primitives;
|
||||
using Microsoft.Generator.CSharp.Providers;
|
||||
using Microsoft.Generator.CSharp.Snippets;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Microsoft.Generator.CSharp.Tests.Providers.NamedTypeSymbolProviders
|
||||
{
|
||||
public class XmlDocsTests
|
||||
{
|
||||
[Test]
|
||||
public void InvalidPropertyDocsThrows()
|
||||
{
|
||||
var model = new InvalidPropertyDocsModel();
|
||||
var compilation = CompilationHelper.LoadCompilation(new[] { model });
|
||||
var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, nameof(InvalidPropertyDocsModel));
|
||||
|
||||
var namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => _ = namedTypeSymbolProvider.Properties);
|
||||
Assert.IsInstanceOf<XmlException>(ex!.InnerException);
|
||||
StringAssert.Contains(
|
||||
$"The malformed XML documentation is located in one or more of the following files: .{Path.DirectorySeparatorChar}{nameof(InvalidPropertyDocsModel)}.cs",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ValidDocsDoNotThrow()
|
||||
{
|
||||
var model = new ValidDocsModel();
|
||||
var compilation = CompilationHelper.LoadCompilation(new[] { model });
|
||||
var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, nameof(ValidDocsModel));
|
||||
|
||||
var namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
|
||||
Assert.AreEqual(2, namedTypeSymbolProvider.Properties.Count);
|
||||
Assert.AreEqual("X", namedTypeSymbolProvider.Properties[0].Name);
|
||||
Assert.IsTrue(namedTypeSymbolProvider.Properties[0].Type.Equals(typeof(int)));
|
||||
Assert.AreEqual("Y", namedTypeSymbolProvider.Properties[1].Name);
|
||||
Assert.IsTrue(namedTypeSymbolProvider.Properties[1].Type.Equals(typeof(string)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void InvalidParameterDocsThrows()
|
||||
{
|
||||
var model = new InvalidParameterDocsModel();
|
||||
var compilation = CompilationHelper.LoadCompilation(new[] { model });
|
||||
var iNamedSymbol = CompilationHelper.GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, nameof(InvalidParameterDocsModel));
|
||||
|
||||
var namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => _ = namedTypeSymbolProvider.Methods);
|
||||
Assert.IsInstanceOf<XmlException>(ex!.InnerException);
|
||||
StringAssert.Contains(
|
||||
$"The malformed XML documentation is located in one or more of the following files: .{Path.DirectorySeparatorChar}{nameof(InvalidParameterDocsModel)}.cs",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
private class InvalidPropertyDocsModel : TypeProvider
|
||||
{
|
||||
protected override string BuildRelativeFilePath() => ".";
|
||||
|
||||
protected override string BuildName() => nameof(InvalidPropertyDocsModel);
|
||||
|
||||
protected override PropertyProvider[] BuildProperties()
|
||||
{
|
||||
return
|
||||
[
|
||||
new PropertyProvider($"This is an invalid description because it is missing closing slash <see cref=\"Y\">", MethodSignatureModifiers.Public, new CSharpType(typeof(int)), "X",
|
||||
new AutoPropertyBody(true), this),
|
||||
new PropertyProvider($"", MethodSignatureModifiers.Public, new CSharpType(typeof(string)), "Y",
|
||||
new AutoPropertyBody(true), this),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private class InvalidParameterDocsModel : TypeProvider
|
||||
{
|
||||
protected override string BuildRelativeFilePath() => ".";
|
||||
|
||||
protected override string BuildName() => nameof(InvalidParameterDocsModel);
|
||||
|
||||
protected override MethodProvider[] BuildMethods()
|
||||
{
|
||||
var sig = new MethodSignature("Write", $"", MethodSignatureModifiers.Public, null, $"", [ new ParameterProvider("value", $"This is an invalid description because it is missing closing slash <see cref=\"Y\">", typeof(int)), new ParameterProvider("options", $"", typeof(string)) ]);
|
||||
return [new MethodProvider(sig, Snippet.ThrowExpression(Snippet.Null), this, null)];
|
||||
}
|
||||
}
|
||||
|
||||
private class ValidDocsModel : TypeProvider
|
||||
{
|
||||
protected override string BuildRelativeFilePath() => ".";
|
||||
|
||||
protected override string BuildName() => nameof(ValidDocsModel);
|
||||
|
||||
protected override PropertyProvider[] BuildProperties()
|
||||
{
|
||||
return
|
||||
[
|
||||
new PropertyProvider($"This is a valid description <see cref=\"Y\"/>", MethodSignatureModifiers.Public, new CSharpType(typeof(int)), "X",
|
||||
new AutoPropertyBody(true), this),
|
||||
new PropertyProvider($"", MethodSignatureModifiers.Public, new CSharpType(typeof(string)), "Y",
|
||||
new AutoPropertyBody(true), this),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче