implement the feature of changing the name of a model (#4285)

Fixes #4256 
Fixes #4261

In order to get our mocking system working properly and make everything
aligned, I changed the `SourceInputModel` to public and added it to the
`CodeModelPlugin` so that our plugin writers could override something on
it to do some advanced stuffs
This commit is contained in:
Dapeng Zhang 2024-09-05 05:24:41 +08:00 коммит произвёл GitHub
Родитель 04b198693d
Коммит cd6e362df8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
17 изменённых файлов: 202 добавлений и 283 удалений

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

@ -5,10 +5,12 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.ClientModel.Providers;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.SourceInput;
using Moq;
using Moq.Protected;
@ -96,6 +98,9 @@ namespace Microsoft.Generator.CSharp.ClientModel.Tests
mockPluginInstance.Setup(p => p.InputLibrary).Returns(createInputLibrary);
}
var sourceInputModel = new Mock<SourceInputModel>(() => new SourceInputModel(null)) { CallBase = true };
mockPluginInstance.Setup(p => p.SourceInputModel).Returns(sourceInputModel.Object);
codeModelInstance!.SetValue(null, mockPluginInstance.Object);
clientModelInstance!.SetValue(null, mockPluginInstance.Object);
mockPluginInstance.Object.Configure();

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

@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.SourceInput;
namespace Microsoft.Generator.CSharp
{
@ -14,7 +15,6 @@ namespace Microsoft.Generator.CSharp
{
private const string ConfigurationFileName = "Configuration.json";
private const string CodeModelFileName = "tspCodeModel.json";
private const string GeneratedFolderName = "Generated";
private static readonly string[] _filesToKeep = [ConfigurationFileName, CodeModelFileName];
@ -25,10 +25,11 @@ namespace Microsoft.Generator.CSharp
{
GeneratedCodeWorkspace.Initialize();
var outputPath = CodeModelPlugin.Instance.Configuration.OutputDirectory;
var generatedSourceOutputPath = ParseGeneratedSourceOutputPath(outputPath);
var generatedTestOutputPath = Path.Combine(outputPath, "..", "..", "tests", GeneratedFolderName);
var generatedSourceOutputPath = CodeModelPlugin.Instance.Configuration.ProjectGeneratedDirectory;
var generatedTestOutputPath = CodeModelPlugin.Instance.Configuration.TestGeneratedDirectory;
GeneratedCodeWorkspace workspace = await GeneratedCodeWorkspace.Create();
await CodeModelPlugin.Instance.InitializeSourceInputModelAsync();
var output = CodeModelPlugin.Instance.OutputLibrary;
Directory.CreateDirectory(Path.Combine(generatedSourceOutputPath, "Models"));
@ -82,23 +83,6 @@ namespace Microsoft.Generator.CSharp
}
}
/// <summary>
/// Parses and updates the output path for the generated code.
/// </summary>
/// <param name="outputPath">The output path.</param>
/// <returns>The parsed output path string.</returns>
internal static string ParseGeneratedSourceOutputPath(string outputPath)
{
if (!outputPath.EndsWith("src", StringComparison.Ordinal) && !outputPath.EndsWith("src/", StringComparison.Ordinal))
{
outputPath = Path.Combine(outputPath, "src");
}
outputPath = Path.Combine(outputPath, GeneratedFolderName);
return outputPath;
}
/// <summary>
/// Clears the output directory specified by <paramref name="path"/>. If <paramref name="filesToKeep"/> is not null,
/// the specified files in the output directory will not be deleted.

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

@ -4,10 +4,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.SourceInput;
namespace Microsoft.Generator.CSharp
{
@ -53,11 +55,11 @@ namespace Microsoft.Generator.CSharp
}
internal bool IsNewProject { get; set; }
private Lazy<InputLibrary> _inputLibrary;
// Extensibility points to be implemented by a plugin
public virtual TypeFactory TypeFactory { get; }
public virtual SourceInputModel SourceInputModel => _sourceInputModel ?? throw new InvalidOperationException($"SourceInputModel has not been initialized yet");
public virtual string LicenseString => string.Empty;
public virtual OutputLibrary OutputLibrary { get; } = new();
public virtual InputLibrary InputLibrary => _inputLibrary.Value;
@ -72,5 +74,12 @@ namespace Microsoft.Generator.CSharp
{
_visitors.Add(visitor);
}
private SourceInputModel? _sourceInputModel;
internal async Task InitializeSourceInputModelAsync()
{
GeneratedCodeWorkspace existingCode = GeneratedCodeWorkspace.CreateExistingCodeProject(Instance.Configuration.ProjectDirectory, Instance.Configuration.ProjectGeneratedDirectory);
_sourceInputModel = new SourceInputModel(await existingCode.GetCompilationAsync());
}
}
}

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

@ -20,6 +20,7 @@ namespace Microsoft.Generator.CSharp
"Enum",
];
private const string GeneratedFolderName = "Generated";
private const string ConfigurationFileName = "Configuration.json";
// for mocking
@ -144,6 +145,15 @@ namespace Microsoft.Generator.CSharp
private string? _projectDirectory;
internal string ProjectDirectory => _projectDirectory ??= Path.Combine(OutputDirectory, "src");
private string? _testProjectDirectory;
internal string TestProjectDirectory => _testProjectDirectory ??= Path.Combine(OutputDirectory, "tests");
private string? _projectGeneratedDirectory;
internal string ProjectGeneratedDirectory => _projectGeneratedDirectory ??= Path.Combine(ProjectDirectory, GeneratedFolderName);
private string? _testGeneratedDirectory;
internal string TestGeneratedDirectory => _testGeneratedDirectory ??= Path.Combine(TestProjectDirectory, GeneratedFolderName);
internal string LibraryName { get; }
/// <summary>

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

@ -47,6 +47,14 @@ namespace Microsoft.Generator.CSharp
_cachedProject = Task.Run(CreateGeneratedCodeProject);
}
internal async Task<CSharpCompilation> GetCompilationAsync()
{
var compilation = await _project.GetCompilationAsync();
Debug.Assert(compilation is CSharpCompilation);
return (CSharpCompilation)compilation;
}
public void AddPlainFiles(string name, string content)
{
PlainFiles.Add(name, content);
@ -140,17 +148,18 @@ namespace Microsoft.Generator.CSharp
return new GeneratedCodeWorkspace(generatedCodeProject);
}
public static GeneratedCodeWorkspace CreateExistingCodeProject(string outputDirectory)
internal static GeneratedCodeWorkspace CreateExistingCodeProject(string projectDirectory, string generatedDirectory)
{
var workspace = new AdhocWorkspace();
var newOptionSet = workspace.Options.WithChangedOption(FormattingOptions.NewLine, LanguageNames.CSharp, _newLine);
workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet));
Project project = workspace.AddProject("ExistingCode", LanguageNames.CSharp);
if (Path.IsPathRooted(outputDirectory))
if (Path.IsPathRooted(projectDirectory))
{
outputDirectory = Path.GetFullPath(outputDirectory);
project = AddDirectory(project, outputDirectory, null);
projectDirectory = Path.GetFullPath(projectDirectory);
project = AddDirectory(project, projectDirectory, skipPredicate: sourceFile => sourceFile.StartsWith(generatedDirectory));
}
project = project
@ -161,7 +170,7 @@ namespace Microsoft.Generator.CSharp
return new GeneratedCodeWorkspace(project);
}
public static async Task<Compilation?> CreatePreviousContractFromDll(string xmlDocumentationpath, string dllPath)
internal static async Task<Compilation?> CreatePreviousContractFromDll(string xmlDocumentationpath, string dllPath)
{
var workspace = new AdhocWorkspace();
Project project = workspace.AddProject("PreviousContract", LanguageNames.CSharp);

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

@ -6,13 +6,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Statements;
namespace Microsoft.Generator.CSharp.Providers
{
public class NamedTypeSymbolProvider : TypeProvider
internal sealed class NamedTypeSymbolProvider : TypeProvider
{
private INamedTypeSymbol _namedTypeSymbol;
@ -21,6 +20,8 @@ namespace Microsoft.Generator.CSharp.Providers
_namedTypeSymbol = namedTypeSymbol;
}
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 BuildName() => _namedTypeSymbol.Name;

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

@ -12,6 +12,17 @@ namespace Microsoft.Generator.CSharp.Providers
{
public abstract class TypeProvider
{
private Lazy<TypeProvider?> _customCodeView;
protected TypeProvider()
{
_customCodeView = new(GetCustomCodeView);
}
private protected virtual TypeProvider? GetCustomCodeView()
=> CodeModelPlugin.Instance.SourceInputModel.FindForType(GetNamespace(), BuildName());
public TypeProvider? CustomCodeView => _customCodeView.Value;
protected string? _deprecated;
/// <summary>
@ -22,7 +33,7 @@ namespace Microsoft.Generator.CSharp.Providers
private string? _relativeFilePath;
public string Name => _name ??= BuildName();
public string Name => _name ??= CustomCodeView?.Name ?? BuildName();
private string? _name;
@ -45,7 +56,7 @@ namespace Microsoft.Generator.CSharp.Providers
private CSharpType? _type;
public CSharpType Type => _type ??= new(
this,
GetNamespace(),
CustomCodeView?.GetNamespace() ?? GetNamespace(),
GetTypeArguments(),
GetBaseType());

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

@ -1,9 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.CodeAnalysis;
namespace Microsoft.Generator.CSharp.SourceInput
{
internal record ClientSourceInput(INamedTypeSymbol? ParentClientType);
}

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

@ -1,21 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Primitives;
namespace Microsoft.Generator.CSharp.SourceInput
{
internal abstract class CompilationCustomCode
{
protected Compilation _compilation;
public CompilationCustomCode(Compilation compilation)
{
_compilation = compilation;
}
internal abstract IMethodSymbol? FindMethod(string namespaceName, string typeName, string methodName, IEnumerable<CSharpType> parameters);
}
}

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

@ -1,96 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace Microsoft.Generator.CSharp.SourceInput
{
internal class ModelTypeMapping
{
private readonly Dictionary<string, ISymbol> _propertyMappings;
private readonly Dictionary<string, ISymbol> _codeGenMemberMappings;
private readonly Dictionary<string, SourcePropertySerializationMapping> _typeSerializationMappings;
public string[]? Usages { get; }
public string[]? Formats { get; }
public ModelTypeMapping(CodeGenAttributes codeGenAttributes, INamedTypeSymbol existingType)
{
_propertyMappings = new();
_codeGenMemberMappings = new();
_typeSerializationMappings = new();
foreach (ISymbol member in GetMembers(existingType))
{
// If member is defined in both base and derived class, use derived one
if (ShouldIncludeMember(member) && !_propertyMappings.ContainsKey(member.Name))
{
_propertyMappings[member.Name] = member;
}
foreach (var attributeData in member.GetAttributes())
{
// handle CodeGenMember attribute
if (codeGenAttributes.TryGetCodeGenMemberAttributeValue(attributeData, out var schemaMemberName))
{
_codeGenMemberMappings[schemaMemberName] = member;
}
}
}
foreach (var attributeData in existingType.GetAttributes())
{
// handle CodeGenModel attribute
if (codeGenAttributes.TryGetCodeGenModelAttributeValue(attributeData, out var usage, out var formats))
{
Usages = usage;
Formats = formats;
}
// handle CodeGenSerialization attribute
if (codeGenAttributes.TryGetCodeGenSerializationAttributeValue(attributeData, out var propertyName, out var serializationNames, out var serializationHook, out var deserializationHook, out var bicepSerializationHook) && !_typeSerializationMappings.ContainsKey(propertyName))
{
_typeSerializationMappings.Add(propertyName, new(propertyName, serializationNames, serializationHook, deserializationHook, bicepSerializationHook));
}
}
}
private static bool ShouldIncludeMember(ISymbol member)
{
// here we exclude those "CompilerGenerated" members, such as the backing field of a property which is also a field.
return !member.IsImplicitlyDeclared && member is IPropertySymbol or IFieldSymbol;
}
public ISymbol? GetMemberByOriginalName(string name)
=> _codeGenMemberMappings.TryGetValue(name, out var renamedSymbol) ?
renamedSymbol :
_propertyMappings.TryGetValue(name, out var memberSymbol) ? memberSymbol : null;
internal SourcePropertySerializationMapping? GetForMemberSerialization(string name)
=> _typeSerializationMappings.TryGetValue(name, out var serialization) ? serialization : null;
public IEnumerable<ISymbol> GetPropertiesWithSerialization()
{
// only the property with CodeGenSerialization attribute will be emitted into the serialization code.
foreach (var (propertyName, symbol) in _propertyMappings)
{
if (_typeSerializationMappings.ContainsKey(propertyName))
yield return symbol;
}
}
private static IEnumerable<ISymbol> GetMembers(INamedTypeSymbol? typeSymbol)
{
while (typeSymbol != null)
{
foreach (var symbol in typeSymbol.GetMembers())
{
yield return symbol;
}
typeSymbol = typeSymbol.BaseType;
}
}
}
}

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

@ -9,38 +9,38 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.Build.Construction;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Customization;
using Microsoft.Generator.CSharp.Primitives;
using Microsoft.Generator.CSharp.Providers;
using NuGet.Configuration;
namespace Microsoft.Generator.CSharp.SourceInput
{
internal sealed class SourceInputModel
public class SourceInputModel
{
private static SourceInputModel? _instance;
public static SourceInputModel Instance => _instance ?? throw new InvalidOperationException("SourceInputModel has not been initialized");
public static void Initialize(Compilation customization, CompilationCustomCode? existingCompilation = null)
{
_instance = new SourceInputModel(customization, existingCompilation);
}
private readonly CompilationCustomCode? _existingCompilation;
private readonly CodeGenAttributes _codeGenAttributes;
private readonly Dictionary<string, INamedTypeSymbol> _nameMap = new Dictionary<string, INamedTypeSymbol>(StringComparer.OrdinalIgnoreCase);
public Compilation Customization { get; }
public Compilation? PreviousContract { get; }
public Compilation? Customization { get; }
private Lazy<Compilation?> _previousContract;
public Compilation? PreviousContract => _previousContract.Value;
private SourceInputModel(Compilation customization, CompilationCustomCode? existingCompilation = null)
private readonly Lazy<IReadOnlyDictionary<string, INamedTypeSymbol>> _nameMap;
public SourceInputModel(Compilation? customization)
{
Customization = customization;
PreviousContract = LoadBaselineContract().GetAwaiter().GetResult();
_existingCompilation = existingCompilation;
_previousContract = new(() => LoadBaselineContract().GetAwaiter().GetResult());
_codeGenAttributes = new CodeGenAttributes();
_nameMap = new(PopulateNameMap);
}
private IReadOnlyDictionary<string, INamedTypeSymbol> PopulateNameMap()
{
var nameMap = new Dictionary<string, INamedTypeSymbol>();
if (Customization == null)
{
return nameMap;
}
IAssemblySymbol assembly = Customization.Assembly;
foreach (IModuleSymbol module in assembly.Modules)
@ -49,75 +49,28 @@ namespace Microsoft.Generator.CSharp.SourceInput
{
if (type is INamedTypeSymbol namedTypeSymbol && TryGetName(type, out var schemaName))
{
_nameMap.Add(schemaName, namedTypeSymbol);
nameMap.Add(schemaName, namedTypeSymbol);
}
}
}
return nameMap;
}
public IReadOnlyList<string>? GetServiceVersionOverrides()
public TypeProvider? FindForType(string ns, string name)
{
var osvAttributeType = Customization.GetTypeByMetadataName(typeof(CodeGenOverrideServiceVersionsAttribute).FullName!)!;
var osvAttribute = Customization.Assembly.GetAttributes()
.FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, osvAttributeType));
return osvAttribute?.ConstructorArguments[0].Values.Select(v => v.Value).OfType<string>().ToList();
}
internal ModelTypeMapping? CreateForModel(INamedTypeSymbol? symbol)
{
if (symbol == null)
if (Customization == null)
{
return null;
return new ModelTypeMapping(_codeGenAttributes, symbol);
}
internal IMethodSymbol? FindMethod(string namespaceName, string typeName, string methodName, IEnumerable<CSharpType> parameters)
{
return _existingCompilation?.FindMethod(namespaceName, typeName, methodName, parameters);
}
public INamedTypeSymbol? FindForType(string name)
{
var ns = CodeModelPlugin.Instance.Configuration.ModelNamespace;
}
var fullyQualifiedMetadataName = $"{ns}.{name}";
if (!_nameMap.TryGetValue(name, out var type) &&
!_nameMap.TryGetValue(fullyQualifiedMetadataName, out type))
if (!_nameMap.Value.TryGetValue(name, out var type) &&
!_nameMap.Value.TryGetValue(fullyQualifiedMetadataName, out type))
{
type = Customization.Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
}
return type;
}
internal bool TryGetClientSourceInput(INamedTypeSymbol type, [NotNullWhen(true)] out ClientSourceInput? clientSourceInput)
{
foreach (var attribute in type.GetAttributes())
{
var attributeType = attribute.AttributeClass;
while (attributeType != null)
{
if (attributeType.Name == CodeGenAttributes.CodeGenClientAttributeName)
{
INamedTypeSymbol? parentClientType = null;
foreach ((var argumentName, TypedConstant constant) in attribute.NamedArguments)
{
if (argumentName == nameof(CodeGenClientAttribute.ParentClient))
{
parentClientType = (INamedTypeSymbol?)constant.Value;
}
}
clientSourceInput = new ClientSourceInput(parentClientType);
return true;
}
attributeType = attributeType.BaseType;
}
}
clientSourceInput = null;
return false;
return type != null ? new NamedTypeSymbolProvider(type) : null;
}
private bool TryGetName(ISymbol symbol, [NotNullWhen(true)] out string? name)

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

@ -10,33 +10,6 @@ namespace Microsoft.Generator.CSharp.Tests
{
public class CSharpGenTests
{
// Validates that the output path is parsed correctly when provided
[Test]
public void TestGetOutputPath_OutputPathProvided()
{
var outputPath = "./outputDir";
var parsedOutputPath = CSharpGen.ParseGeneratedSourceOutputPath(outputPath);
var expectedPath = Path.Combine(outputPath, "src", "Generated");
var areEqual = string.Equals(expectedPath, parsedOutputPath, StringComparison.OrdinalIgnoreCase);
Assert.IsTrue(areEqual);
// append 'src' to the output path and validate that it is not appended again
TestOutputPathAppended(outputPath, expectedPath);
}
// Validates that the output path is parsed correctly when an empty string is provided
[Test]
public void TestGetConfigurationInputFilePath_DefaultPath()
{
var outputPath = "";
var parsedOutputPath = CSharpGen.ParseGeneratedSourceOutputPath(outputPath);
var expectedPath = Path.Combine("src", "Generated");
var areEqual = string.Equals(expectedPath, parsedOutputPath, StringComparison.OrdinalIgnoreCase);
Assert.IsTrue(areEqual);
}
// Validates that a valid plugin implementation is accepted
[Test]
public void TestCSharpGen_ValidPlugin()
@ -61,19 +34,5 @@ namespace Microsoft.Generator.CSharp.Tests
mockPlugin.Verify(m => m.Visitors, Times.Once);
mockVisitor.Verify(m => m.Visit(mockPlugin.Object.OutputLibrary), Times.Once);
}
private void TestOutputPathAppended(string outputPath, string expectedPath)
{
var srcPath = "src";
outputPath = Path.Combine(outputPath, srcPath);
var parsedOutputPath = CSharpGen.ParseGeneratedSourceOutputPath(outputPath);
var areEqual = string.Equals(expectedPath, parsedOutputPath, StringComparison.OrdinalIgnoreCase);
Assert.IsTrue(areEqual);
}
}
}

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

@ -49,10 +49,9 @@ namespace Microsoft.Generator.CSharp.Tests
}
// Validates that the output folder is parsed correctly from the configuration
[TestCaseSource("ParseConfigOutputFolderTestCases")]
[TestCaseSource(nameof(ParseConfigOutputFolderTestCases))]
public void TestParseConfig_OutputFolder(string mockJson, bool throwsError)
{
var expected = Path.GetFullPath(MockHelpers.TestHelpersFolder);
if (throwsError)
@ -64,6 +63,8 @@ namespace Microsoft.Generator.CSharp.Tests
var configuration = Configuration.Load(MockHelpers.TestHelpersFolder, mockJson);
Assert.AreEqual(expected, configuration.OutputDirectory);
Assert.AreEqual(Path.Combine(expected, "src", "Generated"), configuration.ProjectGeneratedDirectory);
Assert.AreEqual(Path.Combine(expected, "tests", "Generated"), configuration.TestGeneratedDirectory);
}
// Validates that the LibraryName field is parsed correctly from the configuration

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

@ -1,8 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
namespace Microsoft.Generator.CSharp.Tests
{
@ -11,15 +14,47 @@ namespace Microsoft.Generator.CSharp.Tests
private static readonly string _assemblyLocation = Path.GetDirectoryName(typeof(Helpers).Assembly.Location)!;
public static string GetExpectedFromFile(string? parameters = null)
{
return File.ReadAllText(GetAssetFilePath(parameters));
}
private static string GetAssetFilePath(string? parameters = null)
{
var stackTrace = new StackTrace();
var stackFrame = stackTrace.GetFrame(1);
var method = stackFrame!.GetMethod();
var stackFrame = GetRealMethodInvocation(stackTrace);
var method = stackFrame.GetMethod();
var callingClass = method!.DeclaringType;
var nsSplit = callingClass!.Namespace!.Split('.');
var ns = nsSplit[nsSplit.Length - 1];
var ns = nsSplit[^1];
var paramString = parameters is null ? string.Empty : $"({parameters})";
return File.ReadAllText(Path.Combine(_assemblyLocation, ns, "TestData", callingClass.Name, $"{method.Name}{paramString}.cs"));
return Path.Combine(_assemblyLocation, ns, "TestData", callingClass.Name, $"{method.Name}{paramString}.cs");
}
private static StackFrame GetRealMethodInvocation(StackTrace stackTrace)
{
int i = 1;
while (i < stackTrace.FrameCount)
{
var frame = stackTrace.GetFrame(i);
if (frame!.GetMethod()!.DeclaringType != typeof(Helpers))
{
return frame;
}
i++;
}
throw new InvalidOperationException($"There is no method invocation outside the {typeof(Helpers)} class in the stack trace");
}
public static Compilation GetCompilationFromFile(string? parameters = null)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(GetAssetFilePath(parameters)));
CSharpCompilation compilation = CSharpCompilation.Create("ExistingCode")
.WithOptions(new CSharpCompilationOptions(OutputKind.ConsoleApplication))
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(syntaxTree);
return compilation;
}
}
}

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

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.Tests.Common;
using NUnit.Framework;
namespace Microsoft.Generator.CSharp.Tests.Providers // the namespace here is crucial to get correct test data file.
{
public class ModelCustomizationTests
{
// Validates that the property body's setter is correctly set based on the property type
[TestCase]
public void TestCustomization_CanChangeModelName()
{
MockHelpers.LoadMockPlugin(customization: Helpers.GetCompilationFromFile());
var props = new[]
{
InputFactory.Property("prop1", InputFactory.Array(InputPrimitiveType.String))
};
var inputModel = InputFactory.Model("mockInputModel", properties: props);
var modelTypeProvider = new ModelProvider(inputModel);
var customCodeView = modelTypeProvider.CustomCodeView;
Assert.IsNotNull(customCodeView);
Assert.AreEqual("CustomizedModel", modelTypeProvider.Type.Name);
Assert.AreEqual("NewNamespace.Models", modelTypeProvider.Type.Namespace);
Assert.AreEqual(customCodeView?.Name, modelTypeProvider.Type.Name);
Assert.AreEqual(customCodeView?.Type.Namespace, modelTypeProvider.Type.Namespace);
}
}
}

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

@ -0,0 +1,25 @@
#nullable disable
using System;
namespace NewNamespace
{
// TODO: if we decide to use the public APIs, we do not have to define this attribute here. Tracking: https://github.com/Azure/autorest.csharp/issues/4551
[AttributeUsage(AttributeTargets.Class)]
internal class CodeGenTypeAttribute : Attribute
{
public string OriginalName { get; }
public CodeGenTypeAttribute(string originalName)
{
OriginalName = originalName;
}
}
}
namespace NewNamespace.Models
{
[CodeGenType("MockInputModel")]
public partial class CustomizedModel
{
}
}

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

@ -1,14 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.IO;
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Primitives;
using Moq.Protected;
using Moq;
using Microsoft.Generator.CSharp.SourceInput;
using Microsoft.Generator.CSharp.Tests.Common;
using Moq;
using Moq.Protected;
namespace Microsoft.Generator.CSharp.Tests
{
@ -20,7 +21,8 @@ namespace Microsoft.Generator.CSharp.Tests
Func<InputType, CSharpType>? createCSharpTypeCore = null,
Func<OutputLibrary>? createOutputLibrary = null,
string? configuration = null,
InputModelType[]? inputModelTypes = null)
InputModelType[]? inputModelTypes = null,
Compilation? customization = null)
{
var configFilePath = Path.Combine(AppContext.BaseDirectory, TestHelpersFolder);
// initialize the singleton instance of the plugin
@ -47,7 +49,13 @@ namespace Microsoft.Generator.CSharp.Tests
mockPlugin.SetupGet(p => p.TypeFactory).Returns(mockTypeFactory.Object);
var sourceInputModel = new Mock<SourceInputModel>(() => new SourceInputModel(customization)) { CallBase = true };
mockPlugin.Setup(p => p.SourceInputModel).Returns(sourceInputModel.Object);
CodeModelPlugin.Instance = mockPlugin.Object;
return mockPlugin;
}
}