Fixes https://github.com/microsoft/typespec/issues/3744
This commit is contained in:
m-nash 2024-07-10 10:16:20 -07:00 коммит произвёл GitHub
Родитель 4658a058e0
Коммит 737f244f0b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
9 изменённых файлов: 308 добавлений и 18 удалений

Просмотреть файл

@ -0,0 +1,160 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Primitives;
namespace Microsoft.Generator.CSharp.Providers
{
public class NamedTypeSymbolProvider : TypeProvider
{
private INamedTypeSymbol _namedTypeSymbol;
public NamedTypeSymbolProvider(INamedTypeSymbol namedTypeSymbol)
{
_namedTypeSymbol = namedTypeSymbol;
}
public override string RelativeFilePath => throw new InvalidOperationException("This type should not be writting in generation");
public override string Name => _namedTypeSymbol.Name;
protected override string GetNamespace() => GetFullyQualifiedNameFromDisplayString(_namedTypeSymbol.ContainingNamespace);
protected override PropertyProvider[] BuildProperties()
{
List<PropertyProvider> properties = new List<PropertyProvider>();
foreach (var propertySymbol in _namedTypeSymbol.GetMembers().OfType<IPropertySymbol>())
{
var propertyProvider = new PropertyProvider(
$"{GetPropertySummary(propertySymbol)}",
GetMethodSignatureModifiers(propertySymbol.DeclaredAccessibility),
GetCSharpType(propertySymbol.Type),
propertySymbol.Name,
new AutoPropertyBody(propertySymbol.SetMethod is not null));
properties.Add(propertyProvider);
}
return [.. properties];
}
private static string? GetPropertySummary(IPropertySymbol propertySymbol)
{
var xmlDocumentation = propertySymbol.GetDocumentationCommentXml();
if (!string.IsNullOrEmpty(xmlDocumentation))
{
var xDocument = XDocument.Parse(xmlDocumentation);
var summaryElement = xDocument.Descendants("summary").FirstOrDefault();
return summaryElement?.Value.Trim();
}
return null;
}
private static MethodSignatureModifiers GetMethodSignatureModifiers(Accessibility accessibility) => accessibility switch
{
Accessibility.Private => MethodSignatureModifiers.Private,
Accessibility.Protected => MethodSignatureModifiers.Protected,
Accessibility.Internal => MethodSignatureModifiers.Internal,
Accessibility.Public => MethodSignatureModifiers.Public,
_ => MethodSignatureModifiers.None
};
private static CSharpType GetCSharpType(ITypeSymbol typeSymbol)
{
var fullyQualifiedName = GetFullyQualifiedName(typeSymbol);
var pieces = fullyQualifiedName.Split('.');
//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))
{
return new CSharpType(
typeSymbol.Name,
string.Join('.', pieces.Take(pieces.Length - 1)),
typeSymbol.IsValueType,
typeSymbol.TypeKind == TypeKind.Enum,
typeSymbol.NullableAnnotation == NullableAnnotation.Annotated,
typeSymbol.ContainingType is not null ? GetCSharpType(typeSymbol.ContainingType) : null,
typeSymbol is INamedTypeSymbol namedTypeSymbol ? namedTypeSymbol.TypeArguments.Select(GetCSharpType).ToArray() : null,
typeSymbol.DeclaredAccessibility == Accessibility.Public,
typeSymbol.BaseType is not null ? GetCSharpType(typeSymbol.BaseType) : null);
}
var type = System.Type.GetType(fullyQualifiedName);
if (type is null)
{
throw new InvalidOperationException($"Unable to convert ITypeSymbol: {fullyQualifiedName} to a CSharpType");
}
return type;
}
private static string GetFullyQualifiedName(ITypeSymbol typeSymbol)
{
// Handle special cases for built-in types
switch (typeSymbol.SpecialType)
{
case SpecialType.System_Object:
return "System.Object";
case SpecialType.System_Void:
return "System.Void";
case SpecialType.System_Boolean:
return "System.Boolean";
case SpecialType.System_Char:
return "System.Char";
case SpecialType.System_SByte:
return "System.SByte";
case SpecialType.System_Byte:
return "System.Byte";
case SpecialType.System_Int16:
return "System.Int16";
case SpecialType.System_UInt16:
return "System.UInt16";
case SpecialType.System_Int32:
return "System.Int32";
case SpecialType.System_UInt32:
return "System.UInt32";
case SpecialType.System_Int64:
return "System.Int64";
case SpecialType.System_UInt64:
return "System.UInt64";
case SpecialType.System_Decimal:
return "System.Decimal";
case SpecialType.System_Single:
return "System.Single";
case SpecialType.System_Double:
return "System.Double";
case SpecialType.System_String:
return "System.String";
case SpecialType.System_DateTime:
return "System.DateTime";
}
// Handle array types
if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol)
{
var elementType = GetFullyQualifiedName(arrayTypeSymbol.ElementType);
return elementType + "[]";
}
// Handle generic types
if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType)
{
var genericArguments = string.Join(",", namedTypeSymbol.TypeArguments.Select(GetFullyQualifiedName));
var typeName = namedTypeSymbol.ConstructedFrom.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
return $"{typeName}[{genericArguments}]";
}
// Default to fully qualified name
return GetFullyQualifiedNameFromDisplayString(typeSymbol);
}
private static string GetFullyQualifiedNameFromDisplayString(ISymbol typeSymbol)
{
const string globalPrefix = "global::";
var fullyQualifiedName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
return fullyQualifiedName.StartsWith(globalPrefix, StringComparison.Ordinal) ? fullyQualifiedName.Substring(globalPrefix.Length) : fullyQualifiedName;
}
}
}

Просмотреть файл

@ -19,7 +19,7 @@ namespace Microsoft.Generator.CSharp.Tests
[Test]
public void TestInitialize()
{
string ns = "sample.namespace";
string ns = "Sample";
string? unknownStringProperty = "unknownPropertyValue";
bool? unknownBoolProp = false;

Просмотреть файл

@ -1,7 +1,7 @@
{
{
"output-folder": "./outputFolder",
"project-folder": "./projectFolder",
"namespace": "sample.namespace",
"namespace": "Sample",
"unknown-bool-property": false,
"library-name": "sample-library",
"unknown-string-property": "unknownPropertyValue"

Просмотреть файл

@ -225,7 +225,7 @@ namespace Microsoft.Generator.CSharp.Tests.Providers
var result = writer.ToString(false);
var builder = new StringBuilder();
builder.Append($"e.ToSerialInt32()").Append(NewLine)
.Append($"new global::sample.namespace.Models.MockInputEnum(1)");
.Append($"new global::Sample.Models.MockInputEnum(1)");
var expected = builder.ToString();
Assert.AreEqual(expected, result);
@ -286,7 +286,7 @@ namespace Microsoft.Generator.CSharp.Tests.Providers
var result = writer.ToString(false);
var builder = new StringBuilder();
builder.Append($"e.ToSerialSingle()").Append(NewLine)
.Append($"new global::sample.namespace.Models.MockInputEnum(1F)");
.Append($"new global::Sample.Models.MockInputEnum(1F)");
var expected = builder.ToString();
Assert.AreEqual(expected, result);
@ -347,7 +347,7 @@ namespace Microsoft.Generator.CSharp.Tests.Providers
var result = writer.ToString(false);
var builder = new StringBuilder();
builder.Append($"e.ToString()").Append(NewLine)
.Append($"new global::sample.namespace.Models.MockInputEnum(\"1\")");
.Append($"new global::Sample.Models.MockInputEnum(\"1\")");
var expected = builder.ToString();
Assert.AreEqual(expected, result);

Просмотреть файл

@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Providers;
using NUnit.Framework;
namespace Microsoft.Generator.CSharp.Tests.Providers
{
public class NamedTypeSymbolProviderTests
{
private NamedTypeSymbolProvider _namedTypeSymbolProvider;
private NamedSymbol _namedSymbol;
public NamedTypeSymbolProviderTests()
{
MockCodeModelPlugin.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 iNamedSymbol = GetSymbol(compilation.Assembly.Modules.First().GlobalNamespace, "NamedSymbol");
_namedTypeSymbolProvider = new NamedTypeSymbolProvider(iNamedSymbol!);
_namedSymbol = new NamedSymbol();
}
[Test]
public void ValidateName()
{
Assert.AreEqual(_namedSymbol.Name, _namedTypeSymbolProvider.Name);
}
[Test]
public void ValidateNamespace()
{
Assert.AreEqual("Sample.Models", _namedTypeSymbolProvider.Type.Namespace);
Assert.AreEqual(_namedSymbol.Type.Namespace, _namedTypeSymbolProvider.Type.Namespace);
}
[Test]
public void ValidateProperties()
{
Dictionary<string, PropertyProvider> properties = _namedTypeSymbolProvider.Properties.ToDictionary(p => p.Name);
foreach (var expected in _namedSymbol.Properties)
{
var actual = properties[expected.Name];
Assert.IsTrue(properties.ContainsKey(expected.Name));
Assert.AreEqual(expected.Name, actual.Name);
Assert.AreEqual($"{expected.Description}.", actual.Description.ToString()); // the writer adds a period
Assert.AreEqual(expected.Modifiers, actual.Modifiers);
Assert.AreEqual(expected.Type, actual.Type);
Assert.AreEqual(expected.Body.GetType(), actual.Body.GetType());
Assert.AreEqual(expected.Body.HasSetter, actual.Body.HasSetter);
}
}
private class NamedSymbol : TypeProvider
{
public override string RelativeFilePath => ".";
public override string Name => "NamedSymbol";
protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace;
protected override PropertyProvider[] BuildProperties()
{
return
[
new PropertyProvider($"IntProperty property", MethodSignatureModifiers.Public, typeof(int), "IntProperty", new AutoPropertyBody(true)),
new PropertyProvider($"StringProperty property no setter", MethodSignatureModifiers.Public, typeof(string), "StringProperty", new AutoPropertyBody(false)),
new PropertyProvider($"InternalStringProperty property no setter", MethodSignatureModifiers.Public, typeof(string), "InternalStringProperty", new AutoPropertyBody(false)),
new PropertyProvider($"PropertyTypeProperty property", MethodSignatureModifiers.Public, new PropertyType().Type, "PropertyTypeProperty", new AutoPropertyBody(true)),
];
}
}
private class PropertyType : TypeProvider
{
public override string RelativeFilePath => ".";
public override string Name => "PropertyType";
protected override PropertyProvider[] BuildProperties()
{
return
[
new PropertyProvider($"Foo property", MethodSignatureModifiers.Public, typeof(int), "Foo", new AutoPropertyBody(true)),
];
}
}
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())
{
return GetSymbol(childNamespaceSymbol, name);
}
foreach (INamedTypeSymbol symbol in namespaceSymbol.GetTypeMembers())
{
if (symbol.MetadataName == name)
{
return symbol;
}
}
return null;
}
}
}

Просмотреть файл

@ -24,7 +24,7 @@ namespace Microsoft.Generator.CSharp.Tests.Snippets
ArgumentSnippets.AssertNotNull(p).Write(writer);
Assert.AreEqual("global::sample.namespace.Argument.AssertNotNull(p1, nameof(p1));\n", writer.ToString(false));
Assert.AreEqual("global::Sample.Argument.AssertNotNull(p1, nameof(p1));\n", writer.ToString(false));
}
}
}

Просмотреть файл

@ -28,7 +28,7 @@ namespace Microsoft.Generator.CSharp.Tests
new InputPrimitiveType(InputPrimitiveTypeKind.String),
[new InputEnumTypeValue("value1", "value1", null), new InputEnumTypeValue("value2", "value2", null)],
true);
var expected = new CSharpType("SampleType", "sample.namespace.Models", true, true, false, null, null, true);
var expected = new CSharpType("SampleType", "Sample.Models", true, true, false, null, null, true);
var actual = CodeModelPlugin.Instance.TypeFactory.CreateCSharpType(input);
@ -50,7 +50,7 @@ namespace Microsoft.Generator.CSharp.Tests
[new InputEnumTypeValue("value1", "value1", null), new InputEnumTypeValue("value2", "value2", null)],
true);
var nullableInput = new InputNullableType(input);
var expected = new CSharpType("SampleType", "sample.namespace.Models", true, true, true, null, null, true);
var expected = new CSharpType("SampleType", "Sample.Models", true, true, true, null, null, true);
var actual = CodeModelPlugin.Instance.TypeFactory.CreateCSharpType(nullableInput);
@ -71,7 +71,7 @@ namespace Microsoft.Generator.CSharp.Tests
new InputPrimitiveType(InputPrimitiveTypeKind.String),
[new InputEnumTypeValue("value1", "value1", null), new InputEnumTypeValue("value2", "value2", null)],
true);
var expected = new CSharpType("SampleType", "sample.namespace.Models", true, true, false, null, null, true);
var expected = new CSharpType("SampleType", "Sample.Models", true, true, false, null, null, true);
var enumProvider = CodeModelPlugin.Instance.TypeFactory.CreateEnum(input);

Просмотреть файл

@ -3,20 +3,20 @@
#nullable disable
using System;
using sample.namespace;
using Sample;
namespace sample.namespace.Models
namespace Sample.Models
{
/// <summary> Test model. </summary>
public partial class TestModel
{
/// <summary> Initializes a new instance of <see cref="global::sample.namespace.Models.TestModel"/>. </summary>
/// <summary> Initializes a new instance of <see cref="global::Sample.Models.TestModel"/>. </summary>
/// <param name="requiredString"> Required string, illustrating a reference type property. </param>
/// <param name="requiredInt"> Required int, illustrating a value type property. </param>
/// <exception cref="global::System.ArgumentNullException"> <paramref name="requiredString"/> is null. </exception>
public TestModel(string requiredString, int requiredInt)
{
global::sample.namespace.Argument.AssertNotNull(requiredString, nameof(requiredString));
global::Sample.Argument.AssertNotNull(requiredString, nameof(requiredString));
RequiredString = requiredString;
RequiredInt = requiredInt;

Просмотреть файл

@ -3,20 +3,20 @@
#nullable disable
using System;
using sample.namespace;
using Sample;
namespace sample.namespace.Models
namespace Sample.Models
{
/// <summary> Test model. </summary>
public readonly partial struct TestModel
{
/// <summary> Initializes a new instance of <see cref="global::sample.namespace.Models.TestModel"/>. </summary>
/// <summary> Initializes a new instance of <see cref="global::Sample.Models.TestModel"/>. </summary>
/// <param name="requiredString"> Required string, illustrating a reference type property. </param>
/// <param name="requiredInt"> Required int, illustrating a value type property. </param>
/// <exception cref="global::System.ArgumentNullException"> <paramref name="requiredString"/> is null. </exception>
public TestModel(string requiredString, int requiredInt)
{
global::sample.namespace.Argument.AssertNotNull(requiredString, nameof(requiredString));
global::Sample.Argument.AssertNotNull(requiredString, nameof(requiredString));
RequiredString = requiredString;
RequiredInt = requiredInt;