diff --git a/src/Pretender.SourceGenerator/Emitter/CreateEmitter.cs b/src/Pretender.SourceGenerator/Emitter/CreateEmitter.cs index f645138..23287d4 100644 --- a/src/Pretender.SourceGenerator/Emitter/CreateEmitter.cs +++ b/src/Pretender.SourceGenerator/Emitter/CreateEmitter.cs @@ -1,27 +1,30 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using Microsoft.CodeAnalysis.Operations; using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Pretender.SourceGenerator.Writing; +using Pretender.SourceGenerator.Parser; namespace Pretender.SourceGenerator.Emitter { internal class CreateEmitter { private readonly IInvocationOperation _originalOperation; + private readonly KnownTypeSymbols _knownTypeSymbols; private readonly ImmutableArray? _typeArguments; private readonly ImmutableArray _locations; private readonly int _index; - public CreateEmitter(IInvocationOperation originalOperation, ImmutableArray? typeArguments, ImmutableArray locations, int index) + public CreateEmitter(IInvocationOperation originalOperation, KnownTypeSymbols knownTypeSymbols, ImmutableArray? typeArguments, ImmutableArray locations, int index) { _originalOperation = originalOperation; + _knownTypeSymbols = knownTypeSymbols; _typeArguments = typeArguments; _locations = locations; _index = index; } + public INamedTypeSymbol PretendType => (INamedTypeSymbol)_originalOperation.TargetMethod.ReturnType; + public IInvocationOperation Operation => _originalOperation; public void Emit(IndentedTextWriter writer, CancellationToken cancellationToken) @@ -63,7 +66,7 @@ public void Emit(IndentedTextWriter writer, CancellationToken cancellationToken) using (writer.WriteBlock()) { - writer.Write($"return new {returnType.ToPretendName()}(pretend"); + writer.Write($"return new {_knownTypeSymbols.GetPretendName(PretendType)}(pretend"); if (_typeArguments.HasValue) { @@ -81,23 +84,5 @@ public void Emit(IndentedTextWriter writer, CancellationToken cancellationToken) } } } - - private ImmutableArray CreateInterceptsAttributes() - { - var builder = ImmutableArray.CreateBuilder(_locations.Length); - - foreach (var location in _locations) - { - var attribute = Create(location.ToAttributeSyntax()); - builder.Add(attribute); - } - - return builder.MoveToImmutable(); - - static AttributeListSyntax Create(AttributeSyntax attribute) - { - return AttributeList(SingletonSeparatedList(attribute)); - } - } } } diff --git a/src/Pretender.SourceGenerator/Emitter/PretendEmitter.cs b/src/Pretender.SourceGenerator/Emitter/PretendEmitter.cs index 8b9f3d8..319b39a 100644 --- a/src/Pretender.SourceGenerator/Emitter/PretendEmitter.cs +++ b/src/Pretender.SourceGenerator/Emitter/PretendEmitter.cs @@ -6,17 +6,21 @@ namespace Pretender.SourceGenerator.Emitter { internal class PretendEmitter { - private readonly ITypeSymbol _pretendType; + private readonly INamedTypeSymbol _pretendType; + private readonly KnownTypeSymbols _knownTypeSymbols; private readonly IReadOnlyDictionary _methodStrategies; private readonly bool _fillExisting; - public PretendEmitter(ITypeSymbol pretendType, IReadOnlyDictionary methodStrategies, bool fillExisting) + public PretendEmitter(INamedTypeSymbol pretendType, KnownTypeSymbols knownTypeSymbols, bool fillExisting) { + _methodStrategies = knownTypeSymbols.GetTypesStrategies(pretendType); _pretendType = pretendType; - _methodStrategies = methodStrategies; + _knownTypeSymbols = knownTypeSymbols; _fillExisting = fillExisting; } + public INamedTypeSymbol PretendType => _pretendType; + public void Emit(IndentedTextWriter writer, CancellationToken token) { token.ThrowIfCancellationRequested(); @@ -28,7 +32,7 @@ public void Emit(IndentedTextWriter writer, CancellationToken token) } else { - writer.WriteLine($"file class {_pretendType.ToPretendName()} : {_pretendType.ToFullDisplayString()}"); + writer.WriteLine($"file class {_knownTypeSymbols.GetPretendName(PretendType)} : {_pretendType.ToFullDisplayString()}"); } using (writer.WriteBlock()) { @@ -36,7 +40,7 @@ public void Emit(IndentedTextWriter writer, CancellationToken token) foreach (var strategyEntry in _methodStrategies) { var strategy = strategyEntry.Value; - writer.Write($"public static readonly MethodInfo {strategy.UniqueName}_MethodInfo = typeof({_pretendType.ToPretendName()})"); + writer.Write($"public static readonly MethodInfo {strategy.UniqueName}_MethodInfo = typeof({_knownTypeSymbols.GetPretendName(PretendType)})"); strategyEntry.Value.EmitMethodGetter(writer, token); writer.WriteLine(skipIfPresent: true); } @@ -47,7 +51,7 @@ public void Emit(IndentedTextWriter writer, CancellationToken token) writer.WriteLine(); // main constructor - writer.WriteLine($"public {_pretendType.ToPretendName()}(Pretend<{_pretendType.ToFullDisplayString()}> pretend)"); + writer.WriteLine($"public {_knownTypeSymbols.GetPretendName(PretendType)}(Pretend<{_pretendType.ToFullDisplayString()}> pretend)"); using (writer.WriteBlock()) { writer.WriteLine("_pretend = pretend;"); diff --git a/src/Pretender.SourceGenerator/Emitter/SetupActionEmitter.cs b/src/Pretender.SourceGenerator/Emitter/SetupActionEmitter.cs index 3516cf7..6f6a42c 100644 --- a/src/Pretender.SourceGenerator/Emitter/SetupActionEmitter.cs +++ b/src/Pretender.SourceGenerator/Emitter/SetupActionEmitter.cs @@ -10,7 +10,7 @@ internal class SetupActionEmitter private readonly ImmutableArray _setupArgumentEmitters; private readonly KnownTypeSymbols _knownTypeSymbols; - public SetupActionEmitter(ITypeSymbol pretendType, IMethodSymbol setupMethod, ImmutableArray setupArgumentEmitters, KnownTypeSymbols knownTypeSymbols) + public SetupActionEmitter(INamedTypeSymbol pretendType, IMethodSymbol setupMethod, ImmutableArray setupArgumentEmitters, KnownTypeSymbols knownTypeSymbols) { PretendType = pretendType; SetupMethod = setupMethod; @@ -18,7 +18,7 @@ public SetupActionEmitter(ITypeSymbol pretendType, IMethodSymbol setupMethod, Im _knownTypeSymbols = knownTypeSymbols; } - public ITypeSymbol PretendType { get; } + public INamedTypeSymbol PretendType { get; } public IMethodSymbol SetupMethod { get; } public void Emit(IndentedTextWriter writer, CancellationToken cancellationToken) @@ -68,7 +68,7 @@ public void Emit(IndentedTextWriter writer, CancellationToken cancellationToken) var methodStrategy = _knownTypeSymbols.GetSingleMethodStrategy(SetupMethod); // TODO: default value - writer.WriteLine($"return new ReturningCompiledSetup<{PretendType.ToFullDisplayString()}, {returnType.ToUnknownTypeString()}>(pretend, {PretendType.ToPretendName()}.{methodStrategy.UniqueName}_MethodInfo, {matcherName}, expr.Target, defaultValue: default);"); + writer.WriteLine($"return new ReturningCompiledSetup<{PretendType.ToFullDisplayString()}, {returnType.ToUnknownTypeString()}>(pretend, {_knownTypeSymbols.GetPretendName(PretendType)}.{methodStrategy.UniqueName}_MethodInfo, {matcherName}, expr.Target, defaultValue: default);"); } else { diff --git a/src/Pretender.SourceGenerator/Parser/CreateParser.cs b/src/Pretender.SourceGenerator/Parser/CreateParser.cs index 8db069a..f8df8a9 100644 --- a/src/Pretender.SourceGenerator/Parser/CreateParser.cs +++ b/src/Pretender.SourceGenerator/Parser/CreateParser.cs @@ -27,6 +27,7 @@ public CreateParser(CreateInvocation createInvocation, ImmutableArray> _cachedTypeMethodNames = new(SymbolEqualityComparer.Default); + private readonly ConcurrentDictionary _cachedPretendNames = new(SymbolEqualityComparer.Default); + private readonly ConcurrentDictionary _pretendNameTracker = new(); public Compilation Compilation { get; } @@ -24,7 +26,7 @@ internal sealed class KnownTypeSymbols // Known abstractions with fakes public INamedTypeSymbol? MicrosoftExtensionsLoggingILogger { get; } public INamedTypeSymbol? MicrosoftExtensionsLoggingTestingFakeLogger { get; } - public INamedTypeSymbol? MicrosoftExtensionsLoggingAbstractionsNullLogger { get; } + public INamedTypeSymbol? MicrosoftExtensionsLoggingAbstractionsNullLogger { get; } public INamedTypeSymbol? MicrosoftExtensionsLoggingILoggerOfT { get; } @@ -109,5 +111,31 @@ public static bool IsPretend(INamedTypeSymbol type) ContainingAssembly.Name: "Pretender", }; } + + public string GetPretendName(INamedTypeSymbol type) + { + if (_cachedPretendNames.TryGetValue(type, out var name)) + { + return name; + } + + var prettyName = type.Name; + + if (_pretendNameTracker.TryGetValue(prettyName, out var nextNum)) + { + // This type has been tracked before + var nextName = $"Pretend{type.Name}{nextNum}"; + _pretendNameTracker[prettyName] = ++nextNum; + return nextName; + } + + // Never tracked before + + // Start with 1 + _pretendNameTracker[prettyName] = 1; + var firstName = $"Pretend{prettyName}"; + _cachedPretendNames[type] = firstName; + return firstName; + } } } diff --git a/src/Pretender.SourceGenerator/Parser/PretendParser.cs b/src/Pretender.SourceGenerator/Parser/PretendParser.cs index 502050f..31dd666 100644 --- a/src/Pretender.SourceGenerator/Parser/PretendParser.cs +++ b/src/Pretender.SourceGenerator/Parser/PretendParser.cs @@ -61,7 +61,7 @@ public PretendParser(PretendInvocation pretendInvocation, KnownTypeSymbols known cancellationToken.ThrowIfCancellationRequested(); - return (new PretendEmitter(PretendInvocation.PretendType, methodStrategies, PretendInvocation.FillExisting), null); + return (new PretendEmitter(PretendInvocation.PretendType, _knownTypeSymbols, PretendInvocation.FillExisting), null); } } } diff --git a/src/Pretender.SourceGenerator/Parser/SetupActionParser.cs b/src/Pretender.SourceGenerator/Parser/SetupActionParser.cs index c152dcf..8fbe6e1 100644 --- a/src/Pretender.SourceGenerator/Parser/SetupActionParser.cs +++ b/src/Pretender.SourceGenerator/Parser/SetupActionParser.cs @@ -10,12 +10,12 @@ namespace Pretender.SourceGenerator.Parser internal class SetupActionParser { private readonly IOperation _setupActionArgument; - private readonly ITypeSymbol _pretendType; + private readonly INamedTypeSymbol _pretendType; private readonly bool _forcePropertySetter; private readonly KnownTypeSymbols _knownTypeSymbols; // TODO: Should I have a higher IOperation kind here? Like InvocationOperation? - public SetupActionParser(IOperation setupActionArgument, ITypeSymbol pretendType, bool forcePropertySetter, KnownTypeSymbols knownTypeSymbols) + public SetupActionParser(IOperation setupActionArgument, INamedTypeSymbol pretendType, bool forcePropertySetter, KnownTypeSymbols knownTypeSymbols) { _setupActionArgument = setupActionArgument; _pretendType = pretendType; diff --git a/src/Pretender.SourceGenerator/Parser/SetupParser.cs b/src/Pretender.SourceGenerator/Parser/SetupParser.cs index 632839c..b2d3f37 100644 --- a/src/Pretender.SourceGenerator/Parser/SetupParser.cs +++ b/src/Pretender.SourceGenerator/Parser/SetupParser.cs @@ -27,7 +27,8 @@ public SetupParser(SetupInvocation setupInvocation, KnownTypeSymbols knownTypeSy // Setup calls are expected to be called from Pretend so the type argument gives us the type we are pretending // TODO: Assert the containing type maybe? - var pretendType = operation.TargetMethod.ContainingType.TypeArguments[0]; + // This should be a safe cast + var pretendType = (INamedTypeSymbol)operation.TargetMethod.ContainingType.TypeArguments[0]; var useSetMethod = operation.TargetMethod.Name == "SetupSet"; diff --git a/src/Pretender.SourceGenerator/Parser/VerifyParser.cs b/src/Pretender.SourceGenerator/Parser/VerifyParser.cs index dd18348..b0e6fac 100644 --- a/src/Pretender.SourceGenerator/Parser/VerifyParser.cs +++ b/src/Pretender.SourceGenerator/Parser/VerifyParser.cs @@ -28,7 +28,8 @@ public VerifyParser(VerifyInvocation verifyInvocation, KnownTypeSymbols knownTyp cancellationToken.ThrowIfCancellationRequested(); // Verify calls are expected to be called from Pretend so the type argument gives us the type we are pretending - var pretendType = operation.TargetMethod.ContainingType.TypeArguments[0]; + // This should be a safe cast + var pretendType = (INamedTypeSymbol)operation.TargetMethod.ContainingType.TypeArguments[0]; // TODO: This doesn't exist yet var useSetMethod = operation.TargetMethod.Name == "VerifySet"; diff --git a/src/Pretender.SourceGenerator/SymbolExtensions.cs b/src/Pretender.SourceGenerator/SymbolExtensions.cs index 2871210..7e77da6 100644 --- a/src/Pretender.SourceGenerator/SymbolExtensions.cs +++ b/src/Pretender.SourceGenerator/SymbolExtensions.cs @@ -125,147 +125,137 @@ public static ExpressionSyntax ToDefaultValueSyntax(this INamedTypeSymbol type, return LiteralExpression(SyntaxKind.DefaultLiteralExpression); } - public static string ToPretendName(this ITypeSymbol symbol) - { - return $"Pretend{symbol.Name}{SymbolEqualityComparer.Default.GetHashCode(symbol):X}"; - } - - public static string ToMethodInfoCacheName(this IMethodSymbol method) - { - return $"MethodInfo_{method.Name}_{SymbolEqualityComparer.Default.GetHashCode(method).ToString("X")}"; - } - public static string ToFullDisplayString(this ITypeSymbol type) { return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); } - public static void ScaffoldImplementation(this ITypeSymbol type, IndentedTextWriter writer, ScaffoldTypeOptions options) - { - Debug.Assert(!type.IsSealed, "I can't scaffold an implementation of a sealed class"); - - // Add the base type PretendIMyType : IMyType - writer.WriteLine($"file class {type.ToPretendName()} : {type.ToFullDisplayString()}"); - using (writer.WriteBlock()) - { - - } - - // Add fields first - //classDeclaration = classDeclaration.AddMembers([.. options.CustomFields]); - - // TODO: Only public and non-sealed? - var typeMembers = type.GetMembers(); - - - // The options indicate wanting to customize a constructor - if (options.CustomizeConstructor != null) - { - - } - - var members = new List(); - - (ParameterSyntax FirstParameter, StatementSyntax[] AdditionalBodyStatements) constructorCustomization = default; - foreach (var member in typeMembers) - { - if (member is IMethodSymbol constructor && constructor.MethodKind == MethodKind.Constructor) - { - ParameterSyntax[] constructorParameters; - int startingIndex = 0; - - if (constructorCustomization == default && options.CustomizeConstructor != null) - { - constructorCustomization = options.CustomizeConstructor(); - startingIndex++; - constructorParameters = new ParameterSyntax[constructor.Parameters.Length + 1]; - constructorParameters[0] = constructorCustomization.FirstParameter; - } - else - { - constructorParameters = new ParameterSyntax[constructor.Parameters.Length]; - } - - for (var i = startingIndex; i < constructorParameters.Length; i++) - { - constructorParameters[i] = constructor.Parameters[i - startingIndex].ToParameterSyntax(); - } - - var baseInitializer = ConstructorInitializer( - SyntaxKind.BaseConstructorInitializer, - ArgumentList( - SeparatedList(constructor.Parameters.Select(p => Argument(IdentifierName(p.Name)))))); - - var constructorDeclaration = ConstructorDeclaration(type.ToPretendName()) - .AddParameterListParameters(constructorParameters) - .WithInitializer(baseInitializer); - - if (constructorCustomization != default) - { - constructorDeclaration = constructorDeclaration - .AddBodyStatements(constructorCustomization.AdditionalBodyStatements!); - } - - members.Add(constructorDeclaration); - } - - if (member is IMethodSymbol method && method.MethodKind == MethodKind.Ordinary) - { - var methodDeclaration = method.ToMethodSyntax(); - var body = options.AddMethodBody(method); - methodDeclaration = methodDeclaration.WithBody(body); - members.Add(methodDeclaration); - } - - if (member is IPropertySymbol property) - { - var propertyDeclaration = property.ToPropertySyntax(); - - // TODO: Customize each body - if (property.GetMethod != null) - { - propertyDeclaration = propertyDeclaration - .AddAccessorListAccessors(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .WithBody(options.AddMethodBody(property.GetMethod))); - } - - if (property.SetMethod != null) - { - // TODO: Is this right? - var accessorKind = property.SetMethod.IsInitOnly - ? SyntaxKind.InitAccessorDeclaration - : SyntaxKind.SetAccessorDeclaration; - - propertyDeclaration = propertyDeclaration - .AddAccessorListAccessors(AccessorDeclaration(accessorKind) - .WithBody(options.AddMethodBody(property.SetMethod))); - } - - members.Add(propertyDeclaration); - } - - // TODO: Do constructors, and use CustomizeConstructor - // and set addedConstructor = true; - } - - if (constructorCustomization == default && options.CustomizeConstructor != null) - { - constructorCustomization = options.CustomizeConstructor(); - - var defaultConstructor = ConstructorDeclaration(type.ToPretendName()) - .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) - .WithParameterList(ParameterList(SingletonSeparatedList(constructorCustomization.FirstParameter))) - .AddBodyStatements(constructorCustomization.AdditionalBodyStatements) - .WithInheritDoc(); - - members.Insert(0, defaultConstructor); - } - - // TODO: Add GeneratedCodeAttribute - // TODO: Add ExcludeFromCodeCoverageAttribute - - //return classDeclaration.AddMembers([.. members]); - } + //public static void ScaffoldImplementation(this ITypeSymbol type, IndentedTextWriter writer, ScaffoldTypeOptions options) + //{ + // Debug.Assert(!type.IsSealed, "I can't scaffold an implementation of a sealed class"); + + // // Add the base type PretendIMyType : IMyType + // writer.WriteLine($"file class {type.ToPretendName()} : {type.ToFullDisplayString()}"); + // using (writer.WriteBlock()) + // { + + // } + + // // Add fields first + // //classDeclaration = classDeclaration.AddMembers([.. options.CustomFields]); + + // // TODO: Only public and non-sealed? + // var typeMembers = type.GetMembers(); + + + // // The options indicate wanting to customize a constructor + // if (options.CustomizeConstructor != null) + // { + + // } + + // var members = new List(); + + // (ParameterSyntax FirstParameter, StatementSyntax[] AdditionalBodyStatements) constructorCustomization = default; + // foreach (var member in typeMembers) + // { + // if (member is IMethodSymbol constructor && constructor.MethodKind == MethodKind.Constructor) + // { + // ParameterSyntax[] constructorParameters; + // int startingIndex = 0; + + // if (constructorCustomization == default && options.CustomizeConstructor != null) + // { + // constructorCustomization = options.CustomizeConstructor(); + // startingIndex++; + // constructorParameters = new ParameterSyntax[constructor.Parameters.Length + 1]; + // constructorParameters[0] = constructorCustomization.FirstParameter; + // } + // else + // { + // constructorParameters = new ParameterSyntax[constructor.Parameters.Length]; + // } + + // for (var i = startingIndex; i < constructorParameters.Length; i++) + // { + // constructorParameters[i] = constructor.Parameters[i - startingIndex].ToParameterSyntax(); + // } + + // var baseInitializer = ConstructorInitializer( + // SyntaxKind.BaseConstructorInitializer, + // ArgumentList( + // SeparatedList(constructor.Parameters.Select(p => Argument(IdentifierName(p.Name)))))); + + // var constructorDeclaration = ConstructorDeclaration(type.ToPretendName()) + // .AddParameterListParameters(constructorParameters) + // .WithInitializer(baseInitializer); + + // if (constructorCustomization != default) + // { + // constructorDeclaration = constructorDeclaration + // .AddBodyStatements(constructorCustomization.AdditionalBodyStatements!); + // } + + // members.Add(constructorDeclaration); + // } + + // if (member is IMethodSymbol method && method.MethodKind == MethodKind.Ordinary) + // { + // var methodDeclaration = method.ToMethodSyntax(); + // var body = options.AddMethodBody(method); + // methodDeclaration = methodDeclaration.WithBody(body); + // members.Add(methodDeclaration); + // } + + // if (member is IPropertySymbol property) + // { + // var propertyDeclaration = property.ToPropertySyntax(); + + // // TODO: Customize each body + // if (property.GetMethod != null) + // { + // propertyDeclaration = propertyDeclaration + // .AddAccessorListAccessors(AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + // .WithBody(options.AddMethodBody(property.GetMethod))); + // } + + // if (property.SetMethod != null) + // { + // // TODO: Is this right? + // var accessorKind = property.SetMethod.IsInitOnly + // ? SyntaxKind.InitAccessorDeclaration + // : SyntaxKind.SetAccessorDeclaration; + + // propertyDeclaration = propertyDeclaration + // .AddAccessorListAccessors(AccessorDeclaration(accessorKind) + // .WithBody(options.AddMethodBody(property.SetMethod))); + // } + + // members.Add(propertyDeclaration); + // } + + // // TODO: Do constructors, and use CustomizeConstructor + // // and set addedConstructor = true; + // } + + // if (constructorCustomization == default && options.CustomizeConstructor != null) + // { + // constructorCustomization = options.CustomizeConstructor(); + + // var defaultConstructor = ConstructorDeclaration(type.ToPretendName()) + // .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword))) + // .WithParameterList(ParameterList(SingletonSeparatedList(constructorCustomization.FirstParameter))) + // .AddBodyStatements(constructorCustomization.AdditionalBodyStatements) + // .WithInheritDoc(); + + // members.Insert(0, defaultConstructor); + // } + + // // TODO: Add GeneratedCodeAttribute + // // TODO: Add ExcludeFromCodeCoverageAttribute + + // //return classDeclaration.AddMembers([.. members]); + //} public static ImmutableArray GetApplicableMethods(this INamedTypeSymbol type) { @@ -278,119 +268,119 @@ public static ImmutableArray GetApplicableMethods(this INamedType .ToImmutableArray(); } - public static MethodDeclarationSyntax ToMethodSyntax(this IMethodSymbol method) - { - var methodDeclarationSyntax = MethodDeclaration( - method.ReturnType.AsUnknownTypeSyntax(), - method.Name - ); - - foreach (var typeParameter in method.TypeParameters) - { - methodDeclarationSyntax = methodDeclarationSyntax - .AddTypeParameterListParameters(typeParameter.ToTypeParameterSyntax()); - - // TODO: This isn't implemented - if (typeParameter.TryToTypeParameterConstraintClauseSyntax(out var typeParameterConstraints)) - { - methodDeclarationSyntax = methodDeclarationSyntax - .AddConstraintClauses(TypeParameterConstraintClause(typeParameter.Name) - .AddConstraints(typeParameterConstraints)); - } - } - - foreach (var parameter in method.Parameters) - { - methodDeclarationSyntax = methodDeclarationSyntax - .AddParameterListParameters(parameter.ToParameterSyntax()); - } - - methodDeclarationSyntax = methodDeclarationSyntax - .AddModifiers(Token(SyntaxKind.PublicKeyword)); - - return methodDeclarationSyntax; - } - - public static PropertyDeclarationSyntax ToPropertySyntax(this IPropertySymbol property) - { - var propertyDeclarationSyntax = PropertyDeclaration( - property.Type.AsUnknownTypeSyntax(), - property.Name) - .AddModifiers(Token(SyntaxKind.PublicKeyword)); - - return propertyDeclarationSyntax; - } - - public static ParameterSyntax ToParameterSyntax(this IParameterSymbol parameter) - { - var parameterSyntax = Parameter(Identifier(parameter.Name)) - .WithType(parameter.Type.AsUnknownTypeSyntax()); - - if (parameter.HasExplicitDefaultValue) - { - parameterSyntax = parameterSyntax - .WithDefault(EqualsValueClause(parameter.ToLiteralExpression())); - } - - var modifiers = new List(); - if (parameter.RefKind == RefKind.Ref) - { - modifiers.Add(Token(SyntaxKind.RefKeyword)); - } - else if (parameter.RefKind == RefKind.Out) - { - modifiers.Add(Token(SyntaxKind.OutKeyword)); - } - else if (parameter.RefKind == RefKind.RefReadOnly) - { - modifiers.Add(Token(SyntaxKind.RefKeyword)); - modifiers.Add(Token(SyntaxKind.ReadOnlyKeyword)); - } - - parameterSyntax = parameterSyntax - .AddModifiers([.. modifiers]); - - // TODO: Anything else I need to do? - // scoped? - // this - - return parameterSyntax; - } - - public static LiteralExpressionSyntax ToLiteralExpression(this IParameterSymbol parameterSymbol) - { - Debug.Assert(parameterSymbol.HasExplicitDefaultValue); - return ToLiteralExpression(parameterSymbol.ExplicitDefaultValue); - } - - private static LiteralExpressionSyntax ToLiteralExpression(object? value) - { - if (value == null) - { - return LiteralExpression(SyntaxKind.NullLiteralExpression); - } - - throw new NotImplementedException($"We don't support literals of {value.GetType()} yet."); - } - - public static TypeParameterSyntax ToTypeParameterSyntax(this ITypeParameterSymbol typeParameter) - { - var varianceKeyword = Token(typeParameter.Variance == VarianceKind.None - ? SyntaxKind.None - : typeParameter.Variance == VarianceKind.In - ? SyntaxKind.InKeyword - : SyntaxKind.OutKeyword); - - // TODO: Add attributes - return TypeParameter(typeParameter.Name) - .WithVarianceKeyword(varianceKeyword); - } - - public static bool TryToTypeParameterConstraintClauseSyntax(this ITypeParameterSymbol typeParameter, out TypeParameterConstraintSyntax[] typeParameterConstrains) - { - // TODO: Support this - typeParameterConstrains = []; - return false; - } + //public static MethodDeclarationSyntax ToMethodSyntax(this IMethodSymbol method) + //{ + // var methodDeclarationSyntax = MethodDeclaration( + // method.ReturnType.AsUnknownTypeSyntax(), + // method.Name + // ); + + // foreach (var typeParameter in method.TypeParameters) + // { + // methodDeclarationSyntax = methodDeclarationSyntax + // .AddTypeParameterListParameters(typeParameter.ToTypeParameterSyntax()); + + // // TODO: This isn't implemented + // if (typeParameter.TryToTypeParameterConstraintClauseSyntax(out var typeParameterConstraints)) + // { + // methodDeclarationSyntax = methodDeclarationSyntax + // .AddConstraintClauses(TypeParameterConstraintClause(typeParameter.Name) + // .AddConstraints(typeParameterConstraints)); + // } + // } + + // foreach (var parameter in method.Parameters) + // { + // methodDeclarationSyntax = methodDeclarationSyntax + // .AddParameterListParameters(parameter.ToParameterSyntax()); + // } + + // methodDeclarationSyntax = methodDeclarationSyntax + // .AddModifiers(Token(SyntaxKind.PublicKeyword)); + + // return methodDeclarationSyntax; + //} + + //public static PropertyDeclarationSyntax ToPropertySyntax(this IPropertySymbol property) + //{ + // var propertyDeclarationSyntax = PropertyDeclaration( + // property.Type.AsUnknownTypeSyntax(), + // property.Name) + // .AddModifiers(Token(SyntaxKind.PublicKeyword)); + + // return propertyDeclarationSyntax; + //} + + //public static ParameterSyntax ToParameterSyntax(this IParameterSymbol parameter) + //{ + // var parameterSyntax = Parameter(Identifier(parameter.Name)) + // .WithType(parameter.Type.AsUnknownTypeSyntax()); + + // if (parameter.HasExplicitDefaultValue) + // { + // parameterSyntax = parameterSyntax + // .WithDefault(EqualsValueClause(parameter.ToLiteralExpression())); + // } + + // var modifiers = new List(); + // if (parameter.RefKind == RefKind.Ref) + // { + // modifiers.Add(Token(SyntaxKind.RefKeyword)); + // } + // else if (parameter.RefKind == RefKind.Out) + // { + // modifiers.Add(Token(SyntaxKind.OutKeyword)); + // } + // else if (parameter.RefKind == RefKind.RefReadOnly) + // { + // modifiers.Add(Token(SyntaxKind.RefKeyword)); + // modifiers.Add(Token(SyntaxKind.ReadOnlyKeyword)); + // } + + // parameterSyntax = parameterSyntax + // .AddModifiers([.. modifiers]); + + // // TODO: Anything else I need to do? + // // scoped? + // // this + + // return parameterSyntax; + //} + + //public static LiteralExpressionSyntax ToLiteralExpression(this IParameterSymbol parameterSymbol) + //{ + // Debug.Assert(parameterSymbol.HasExplicitDefaultValue); + // return ToLiteralExpression(parameterSymbol.ExplicitDefaultValue); + //} + + //private static LiteralExpressionSyntax ToLiteralExpression(object? value) + //{ + // if (value == null) + // { + // return LiteralExpression(SyntaxKind.NullLiteralExpression); + // } + + // throw new NotImplementedException($"We don't support literals of {value.GetType()} yet."); + //} + + //public static TypeParameterSyntax ToTypeParameterSyntax(this ITypeParameterSymbol typeParameter) + //{ + // var varianceKeyword = Token(typeParameter.Variance == VarianceKind.None + // ? SyntaxKind.None + // : typeParameter.Variance == VarianceKind.In + // ? SyntaxKind.InKeyword + // : SyntaxKind.OutKeyword); + + // // TODO: Add attributes + // return TypeParameter(typeParameter.Name) + // .WithVarianceKeyword(varianceKeyword); + //} + + //public static bool TryToTypeParameterConstraintClauseSyntax(this ITypeParameterSymbol typeParameter, out TypeParameterConstraintSyntax[] typeParameterConstrains) + //{ + // // TODO: Support this + // typeParameterConstrains = []; + // return false; + //} } } diff --git a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Creates_PretendISimpleInterface19445CC_g.cs b/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Creates_PretendISimpleInterface19445CC_g.cs deleted file mode 100644 index 8fd86b4..0000000 --- a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Creates_PretendISimpleInterface19445CC_g.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - using System; - using System.CodeDom.Compiler; - - [GeneratedCode("Pretender.SourceGenerator", "1.0.0.0")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(string filePath, int line, int column) - { - } - } -} - -namespace Pretender.SourceGeneration -{ - using System.Runtime.CompilerServices; - using Pretender; - - file static class CreateInterceptors - { - [InterceptsLocation("MyTest.cs", 16, 38)] - internal static global::ISimpleInterface Create0(this Pretend pretend) - { - return new PretendISimpleInterface19445CC(pretend); - } - } -} \ No newline at end of file diff --git a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Creates_PretendISimpleInterface8199A3_g.cs b/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Creates_PretendISimpleInterface8199A3_g.cs deleted file mode 100644 index d85c642..0000000 --- a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Creates_PretendISimpleInterface8199A3_g.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - using System; - using System.CodeDom.Compiler; - - [GeneratedCode("Pretender.SourceGenerator", "1.0.0.0")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(string filePath, int line, int column) - { - } - } -} - -namespace Pretender.SourceGeneration -{ - using System.Runtime.CompilerServices; - using Pretender; - - file static class CreateInterceptors - { - [InterceptsLocation("MyTest.cs", 16, 38)] - internal static global::ISimpleInterface Create0(this Pretend pretend) - { - return new PretendISimpleInterface8199A3(pretend); - } - } -} \ No newline at end of file diff --git a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Setups_g.cs b/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Setups_g.cs deleted file mode 100644 index 1a8e791..0000000 --- a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Setups_g.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace System.Runtime.CompilerServices -{ - using System; - using System.CodeDom.Compiler; - - [GeneratedCode("Pretender.SourceGenerator", "1.0.0.0")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : Attribute - { - public InterceptsLocationAttribute(string filePath, int line, int column) - { - } - } -} - -namespace Pretender.SourceGeneration -{ - using System; - using System.Runtime.CompilerServices; - using System.Linq.Expressions; - using System.Threading.Tasks; - using Pretender; - using Pretender.Internals; - - file static class SetupInterceptors - { - [InterceptsLocation("MyTest.cs", 13, 6)] - internal static IPretendSetup Setup0(this Pretend pretend, Func setupExpression) - { - return pretend.GetOrCreateSetup(0, static (pretend, expr) => - { - return new ReturningCompiledSetup(pretend, PretendISimpleInterface19445CC.MethodInfo_get_Bar_2968A4B, matcher: Cache.NoOpMatcher, expr.Target, defaultValue: default); - }, setupExpression); - } - } -} \ No newline at end of file diff --git a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Type_PretendISimpleInterface19445CC_g.cs b/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Type_PretendISimpleInterface19445CC_g.cs deleted file mode 100644 index 05a85d9..0000000 --- a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Type_PretendISimpleInterface19445CC_g.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -#nullable enable -/// -namespace Pretender.SourceGeneration -{ - using System.Reflection; - using Pretender; - - /// - internal class PretendISimpleInterface19445CC : global::ISimpleInterface - { - public static readonly MethodInfo MethodInfo_Foo_2F42C5 = typeof(global::ISimpleInterface).GetMethod(nameof(Foo))!; - public static readonly MethodInfo MethodInfo_VoidMethod_24F1739 = typeof(global::ISimpleInterface).GetMethod(nameof(VoidMethod))!; - public static readonly MethodInfo MethodInfo_AsyncMethod_17D862F = typeof(global::ISimpleInterface).GetMethod(nameof(AsyncMethod))!; - public static readonly MethodInfo MethodInfo_AsyncReturningMethod_165B12 = typeof(global::ISimpleInterface).GetMethod(nameof(AsyncReturningMethod))!; - public static readonly MethodInfo MethodInfo_TryParse_35A6CCF = typeof(global::ISimpleInterface).GetMethod(nameof(TryParse))!; - public static readonly MethodInfo MethodInfo_get_Bar_2968A4B = typeof(global::ISimpleInterface).GetProperty(nameof(Bar)).GetMethod!; - public static readonly MethodInfo MethodInfo_set_Bar_28FB0B7 = typeof(global::ISimpleInterface).GetProperty(nameof(Bar)).SetMethod!; - private readonly Pretend _pretend; - /// - public PretendISimpleInterface19445CC(Pretend pretend) - { - _pretend = pretend; - } - - public string? Foo(string? bar, int baz) - { - object? [] __arguments = [bar, baz]; - var __callInfo = new CallInfo(MethodInfo_Foo_2F42C5, __arguments); - _pretend.Handle(__callInfo); - return (string? )__callInfo.ReturnValue; - } - - public void VoidMethod(bool baz) - { - object? [] __arguments = [baz]; - var __callInfo = new CallInfo(MethodInfo_VoidMethod_24F1739, __arguments); - _pretend.Handle(__callInfo); - } - - public global::System.Threading.Tasks.Task AsyncMethod() - { - object? [] __arguments = []; - var __callInfo = new CallInfo(MethodInfo_AsyncMethod_17D862F, __arguments); - _pretend.Handle(__callInfo); - return (global::System.Threading.Tasks.Task)__callInfo.ReturnValue; - } - - public global::System.Threading.Tasks.Task AsyncReturningMethod(string bar) - { - object? [] __arguments = [bar]; - var __callInfo = new CallInfo(MethodInfo_AsyncReturningMethod_165B12, __arguments); - _pretend.Handle(__callInfo); - return (global::System.Threading.Tasks.Task)__callInfo.ReturnValue; - } - - public bool TryParse(string thing, out bool myValue) - { - object? [] __arguments = [thing, myValue]; - var __callInfo = new CallInfo(MethodInfo_TryParse_35A6CCF, __arguments); - _pretend.Handle(__callInfo); - myValue = __arguments[1]; - return (bool)__callInfo.ReturnValue; - } - - public string Bar - { - get - { - object? [] __arguments = []; - var __callInfo = new CallInfo(MethodInfo_get_Bar_2968A4B, __arguments); - _pretend.Handle(__callInfo); - return (string)__callInfo.ReturnValue; - } - - set - { - object? [] __arguments = [value]; - var __callInfo = new CallInfo(MethodInfo_set_Bar_28FB0B7, __arguments); - _pretend.Handle(__callInfo); - } - } - } -} \ No newline at end of file diff --git a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Type_PretendISimpleInterface8199A3_g.cs b/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Type_PretendISimpleInterface8199A3_g.cs deleted file mode 100644 index be8eb69..0000000 --- a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_Type_PretendISimpleInterface8199A3_g.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -#nullable enable -/// -namespace Pretender.SourceGeneration -{ - using System.Reflection; - using Pretender; - - /// - internal class PretendISimpleInterface8199A3 : global::ISimpleInterface - { - public static readonly MethodInfo MethodInfo_Foo_3EB6F56 = typeof(global::ISimpleInterface).GetMethod(nameof(Foo))!; - public static readonly MethodInfo MethodInfo_VoidMethod_4CB5F7 = typeof(global::ISimpleInterface).GetMethod(nameof(VoidMethod))!; - public static readonly MethodInfo MethodInfo_AsyncMethod_3177B39 = typeof(global::ISimpleInterface).GetMethod(nameof(AsyncMethod))!; - public static readonly MethodInfo MethodInfo_AsyncReturningMethod_35E1C1E = typeof(global::ISimpleInterface).GetMethod(nameof(AsyncReturningMethod))!; - public static readonly MethodInfo MethodInfo_TryParse_3859BF9 = typeof(global::ISimpleInterface).GetMethod(nameof(TryParse))!; - public static readonly MethodInfo MethodInfo_get_Bar_3685A65 = typeof(global::ISimpleInterface).GetProperty(nameof(Bar)).GetMethod!; - public static readonly MethodInfo MethodInfo_set_Bar_2D53694 = typeof(global::ISimpleInterface).GetProperty(nameof(Bar)).SetMethod!; - private readonly Pretend _pretend; - /// - public PretendISimpleInterface8199A3(Pretend pretend) - { - _pretend = pretend; - } - - public string? Foo(string? bar, int baz) - { - object? [] __arguments = [bar, baz]; - var __callInfo = new CallInfo(MethodInfo_Foo_3EB6F56, __arguments); - _pretend.Handle(__callInfo); - return (string? )__callInfo.ReturnValue; - } - - public void VoidMethod(bool baz) - { - object? [] __arguments = [baz]; - var __callInfo = new CallInfo(MethodInfo_VoidMethod_4CB5F7, __arguments); - _pretend.Handle(__callInfo); - } - - public global::System.Threading.Tasks.Task AsyncMethod() - { - object? [] __arguments = []; - var __callInfo = new CallInfo(MethodInfo_AsyncMethod_3177B39, __arguments); - _pretend.Handle(__callInfo); - return (global::System.Threading.Tasks.Task)__callInfo.ReturnValue; - } - - public global::System.Threading.Tasks.Task AsyncReturningMethod(string bar) - { - object? [] __arguments = [bar]; - var __callInfo = new CallInfo(MethodInfo_AsyncReturningMethod_35E1C1E, __arguments); - _pretend.Handle(__callInfo); - return (global::System.Threading.Tasks.Task)__callInfo.ReturnValue; - } - - public bool TryParse(string thing, out bool myValue) - { - object? [] __arguments = [thing, myValue]; - var __callInfo = new CallInfo(MethodInfo_TryParse_3859BF9, __arguments); - _pretend.Handle(__callInfo); - myValue = __arguments[1]; - return (bool)__callInfo.ReturnValue; - } - - public string Bar - { - get - { - object? [] __arguments = []; - var __callInfo = new CallInfo(MethodInfo_get_Bar_3685A65, __arguments); - _pretend.Handle(__callInfo); - return (string)__callInfo.ReturnValue; - } - - set - { - object? [] __arguments = [value]; - var __callInfo = new CallInfo(MethodInfo_set_Bar_2D53694, __arguments); - _pretend.Handle(__callInfo); - } - } - } -} \ No newline at end of file diff --git a/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_g.cs b/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_g.cs new file mode 100644 index 0000000..f4011ef --- /dev/null +++ b/test/SourceGeneratorTests/Baselines/MainTests/ReturningMethod/Pretender_g.cs @@ -0,0 +1,124 @@ +namespace System.Runtime.CompilerServices +{ + using System; + using System.CodeDom.Compiler; + + [GeneratedCode("Pretender.SourceGenerator", "1.0.0.0")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : Attribute + { + public InterceptsLocationAttribute(string filePath, int line, int column) + { + } + } +} + +namespace Pretender.SourceGeneration +{ + using System; + using System.Reflection; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + using Pretender; + using Pretender.Internals; + + file class PretendISimpleInterface : global::ISimpleInterface + { + public static readonly MethodInfo Foo_MethodInfo = typeof(PretendISimpleInterface).GetMethod(nameof(Foo))!; + public static readonly MethodInfo VoidMethod_MethodInfo = typeof(PretendISimpleInterface).GetMethod(nameof(VoidMethod))!; + public static readonly MethodInfo AsyncMethod_MethodInfo = typeof(PretendISimpleInterface).GetMethod(nameof(AsyncMethod))!; + public static readonly MethodInfo AsyncReturningMethod_MethodInfo = typeof(PretendISimpleInterface).GetMethod(nameof(AsyncReturningMethod))!; + public static readonly MethodInfo TryParse_MethodInfo = typeof(PretendISimpleInterface).GetMethod(nameof(TryParse))!; + public static readonly MethodInfo get_Bar_MethodInfo = typeof(PretendISimpleInterface).GetProperty(nameof(Bar))!.GetMethod; + public static readonly MethodInfo set_Bar_MethodInfo = typeof(PretendISimpleInterface).GetProperty(nameof(Bar))!.SetMethod; + + private readonly Pretend _pretend; + + public PretendISimpleInterface(Pretend pretend) + { + _pretend = pretend; + } + + public string? Foo(string? bar, int baz) + { + object?[] __arguments__ = [bar, baz]; + var __callInfo__ = new CallInfo(Foo_MethodInfo, __arguments__); + _pretend.Handle(__callInfo__); + return (string?)__callInfo__.ReturnValue; + } + + public void VoidMethod(bool baz) + { + object?[] __arguments__ = [baz]; + var __callInfo__ = new CallInfo(VoidMethod_MethodInfo, __arguments__); + _pretend.Handle(__callInfo__); + } + + public global::System.Threading.Tasks.Task AsyncMethod() + { + object?[] __arguments__ = []; + var __callInfo__ = new CallInfo(AsyncMethod_MethodInfo, __arguments__); + _pretend.Handle(__callInfo__); + return (global::System.Threading.Tasks.Task)__callInfo__.ReturnValue; + } + + public global::System.Threading.Tasks.Task AsyncReturningMethod(string bar) + { + object?[] __arguments__ = [bar]; + var __callInfo__ = new CallInfo(AsyncReturningMethod_MethodInfo, __arguments__); + _pretend.Handle(__callInfo__); + return (global::System.Threading.Tasks.Task)__callInfo__.ReturnValue; + } + + public bool TryParse(string thing, out bool myValue) + { + object?[] __arguments__ = [thing, myValue]; + var __callInfo__ = new CallInfo(TryParse_MethodInfo, __arguments__); + _pretend.Handle(__callInfo__); + myValue = __arguments__[1]; + return (bool)__callInfo__.ReturnValue; + } + + public string Bar + { + get + { + object?[] __arguments__ = []; + var __callInfo__ = new CallInfo(get_Bar_MethodInfo, __arguments__); + _pretend.Handle(__callInfo__); + return (string)__callInfo__.ReturnValue; + } + set + { + object?[] __arguments__ = [value]; + var __callInfo__ = new CallInfo(set_Bar_MethodInfo, __arguments__); + _pretend.Handle(__callInfo__); + } + } + } + + file static class SetupInterceptors + { + [InterceptsLocation(@"MyTest.cs", 13, 6)] + internal static IPretendSetup Setup0(this Pretend pretend, Func setupExpression) + { + return pretend.GetOrCreateSetup(0, static (pretend, expr) => + { + return new ReturningCompiledSetup(pretend, PretendISimpleInterface.get_Bar_MethodInfo, Cache.NoOpMatcher, expr.Target, defaultValue: default); + }, setupExpression); + } + } + + file static class VerifyInterceptors + { + } + + file static class CreateInterceptors + { + [InterceptsLocation(@"MyTest.cs", 16, 38)] + internal static global::ISimpleInterface Create0(this Pretend pretend) + { + return new PretendISimpleInterface(pretend); + } + } +} \ No newline at end of file diff --git a/test/SourceGeneratorTests/Pretender.SourceGenerator.SpecTests.csproj b/test/SourceGeneratorTests/Pretender.SourceGenerator.SpecTests.csproj index 6010a8a..2f1cf40 100644 --- a/test/SourceGeneratorTests/Pretender.SourceGenerator.SpecTests.csproj +++ b/test/SourceGeneratorTests/Pretender.SourceGenerator.SpecTests.csproj @@ -41,4 +41,8 @@ + + + + diff --git a/test/SourceGeneratorTests/TestBase.cs b/test/SourceGeneratorTests/TestBase.cs index 4282500..9528f4f 100644 --- a/test/SourceGeneratorTests/TestBase.cs +++ b/test/SourceGeneratorTests/TestBase.cs @@ -122,7 +122,7 @@ private void CompareAgainstBaseline(GeneratedSourceResult result, string testMet using var reader = new StreamReader(stream); Assert.Equal(reader.ReadToEnd().ReplaceLineEndings(), result.SourceText.ToString().ReplaceLineEndings()); #else - var baseDirectory = new DirectoryInfo(typeof(TestBase).Assembly.GetAssemblyLocation()) + var baseDirectory = new DirectoryInfo(typeof(TestBase).Assembly.Location) .Parent?.Parent?.Parent?.Parent; if (baseDirectory == null || !baseDirectory.Exists)