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:
Родитель
04b198693d
Коммит
cd6e362df8
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче