diff --git a/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md b/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md index c78e476580..644dc4662c 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md +++ b/Source/Csla.Analyzers/Csla.Analyzers/AnalyzerReleases.Unshipped.md @@ -25,4 +25,6 @@ CSLA0019 | Usage | Error | FindRefAndOutParametersInOperationsAnalyzer CSLA0020 | Usage | Warning | ObjectAuthorizationRulesAttributeMissing CSLA0021 | Usage | Info | ObjectAuthorizationRulesPublic CSLA0022 | Usage | Warning | ObjectAuthorizationRulesStatic -CSLA0023 | Refactoring | Info | EvaluateManagedBackingFieldsNameofAnalyzer \ No newline at end of file +CSLA0023 | Refactoring | Info | EvaluateManagedBackingFieldsNameofAnalyzer +CSLA0024 | Usage | Warning | TypeWithOperationsShouldBePartialAnalyzer +CSLA0025 | Usage | Warning | UseGeneratedDataPortalExtensionAnalyzer \ No newline at end of file diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs b/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs index da07f4d178..8e142e79f4 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs @@ -98,6 +98,14 @@ public static class AnalyzerIdentifiers /// /// public const string EvaluateManagedBackingFieldsNameof = "CSLA0023"; + /// + /// + /// + public const string TypeWithOperationsShouldBePartial = "CSLA0024"; + /// + /// + /// + public const string UseGeneratedDataPortalExtension = "CSLA0025"; } diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.Designer.cs b/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.Designer.cs index 5556a12d37..0c8321331c 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.Designer.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.Designer.cs @@ -549,5 +549,59 @@ internal static string Shared_AddAttributeDescription { return ResourceManager.GetString("Shared_AddAttributeDescription", resourceCulture); } } + + /// + /// Looks up a localized string similar to Make type partial. + /// + internal static string TypeWithOperationsShouldBePartial_AddPartialDescription { + get { + return ResourceManager.GetString("TypeWithOperationsShouldBePartial_AddPartialDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type '{0}' has data portal operation methods and should be declared partial so the CSLA source generator can implement its operations interface. + /// + internal static string TypeWithOperationsShouldBePartial_Message { + get { + return ResourceManager.GetString("TypeWithOperationsShouldBePartial_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type with data portal operation methods should be partial. + /// + internal static string TypeWithOperationsShouldBePartial_Title { + get { + return ResourceManager.GetString("TypeWithOperationsShouldBePartial_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use the generated data portal extension method for '{0}' instead of calling '{1}' with untyped criteria. + /// + internal static string UseGeneratedDataPortalExtension_Message { + get { + return ResourceManager.GetString("UseGeneratedDataPortalExtension_Message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use generated data portal extension method. + /// + internal static string UseGeneratedDataPortalExtension_Title { + get { + return ResourceManager.GetString("UseGeneratedDataPortalExtension_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use '{0}'. + /// + internal static string UseGeneratedDataPortalExtension_UseExtensionDescription { + get { + return ResourceManager.GetString("UseGeneratedDataPortalExtension_UseExtensionDescription", resourceCulture); + } + } } } diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx b/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx index 031868b675..e380eae390 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx +++ b/Source/Csla.Analyzers/Csla.Analyzers/Properties/Resources.resx @@ -279,4 +279,22 @@ Authorization rules should be declared as static methods + + Type with data portal operation methods should be partial + + + Type '{0}' has data portal operation methods and should be declared partial so the CSLA source generator can implement its operations interface + + + Make type partial + + + Use generated data portal extension method + + + Use the generated data portal extension method for '{0}' instead of calling '{1}' with untyped criteria + + + Use '{0}' + \ No newline at end of file diff --git a/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAddPartialCodeFix.cs b/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAddPartialCodeFix.cs new file mode 100644 index 0000000000..e832a143f3 --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAddPartialCodeFix.cs @@ -0,0 +1,86 @@ +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Csla.Analyzers +{ + /// + /// Adds the partial modifier to a type with data portal + /// operation methods and to all of its containing types. + /// + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public sealed class TypeWithOperationsShouldBePartialAddPartialCodeFix + : CodeFixProvider + { + /// + /// + /// + public override ImmutableArray FixableDiagnosticIds => [Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial]; + + /// + /// + /// + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + /// + /// + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root is null) + { + return; + } + + context.CancellationToken.ThrowIfCancellationRequested(); + + var diagnostic = context.Diagnostics.First(); + var classNode = root.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf(); + if (classNode is null) + { + return; + } + + var nonPartialNodes = TypeWithOperationsShouldBePartialAnalyzer.GetNonPartialTypeDeclarations(classNode); + if (nonPartialNodes.Count == 0) + { + return; + } + + context.CancellationToken.ThrowIfCancellationRequested(); + + var newRoot = root.ReplaceNodes(nonPartialNodes, (_, rewritten) => AddPartialModifier(rewritten)); + var description = TypeWithOperationsShouldBePartialAddPartialCodeFixConstants.AddPartialDescription; + + context.RegisterCodeFix( + CodeAction.Create(description, + _ => Task.FromResult(context.Document.WithSyntaxRoot(newRoot)), description), diagnostic); + } + + private static TypeDeclarationSyntax AddPartialModifier(TypeDeclarationSyntax node) + { + if (node.Modifiers.Count == 0) + { + // No modifiers: the partial keyword takes over the leading trivia + // (indentation, comments) of the type keyword. + var keyword = node.Keyword; + var partialToken = SyntaxFactory.Token( + keyword.LeadingTrivia, SyntaxKind.PartialKeyword, SyntaxFactory.TriviaList(SyntaxFactory.Space)); + return node + .WithKeyword(keyword.WithLeadingTrivia(SyntaxFactory.TriviaList())) + .WithModifiers(SyntaxFactory.TokenList(partialToken)); + } + + // partial must be the last modifier, immediately before the type keyword. + var partialModifier = SyntaxFactory.Token(SyntaxKind.PartialKeyword) + .WithTrailingTrivia(SyntaxFactory.Space); + return node.WithModifiers(node.Modifiers.Add(partialModifier)); + } + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAnalyzer.cs b/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAnalyzer.cs new file mode 100644 index 0000000000..71cdc854df --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAnalyzer.cs @@ -0,0 +1,143 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; + +namespace Csla.Analyzers +{ + /// + /// Reports classes that declare data portal operation methods + /// (methods with [Create], [Fetch], etc.) when the + /// class or any of its containing types is not partial. + /// The CSLA source generator needs to add a partial declaration + /// to such types to implement their operations interface. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class TypeWithOperationsShouldBePartialAnalyzer + : DiagnosticAnalyzer + { + private static readonly string[] OperationAttributeMetadataNames = + [ + "Csla.CreateAttribute", + "Csla.FetchAttribute", + "Csla.InsertAttribute", + "Csla.UpdateAttribute", + "Csla.ExecuteAttribute", + "Csla.DeleteAttribute", + "Csla.DeleteSelfAttribute", + "Csla.CreateChildAttribute", + "Csla.FetchChildAttribute", + "Csla.InsertChildAttribute", + "Csla.UpdateChildAttribute", + "Csla.DeleteSelfChildAttribute", + "Csla.ExecuteChildAttribute" + ]; + + private static readonly DiagnosticDescriptor shouldBePartialRule = + new( + Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial, TypeWithOperationsShouldBePartialAnalyzerConstants.Title, + TypeWithOperationsShouldBePartialAnalyzerConstants.Message, Constants.Categories.Usage, + DiagnosticSeverity.Warning, true, + helpLinkUri: HelpUrlBuilder.Build( + Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial, nameof(TypeWithOperationsShouldBePartialAnalyzer))); + + /// + /// + /// + public override ImmutableArray SupportedDiagnostics => [shouldBePartialRule]; + + /// + /// + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationContext => + { + var operationAttributes = new HashSet(SymbolEqualityComparer.Default); + foreach (var metadataName in OperationAttributeMetadataNames) + { + var attributeType = compilationContext.Compilation.GetTypeByMetadataName(metadataName); + if (attributeType is not null) + { + operationAttributes.Add(attributeType); + } + } + + if (operationAttributes.Count == 0) + { + return; + } + + compilationContext.RegisterSymbolAction( + symbolContext => AnalyzeNamedType(symbolContext, operationAttributes), SymbolKind.NamedType); + }); + } + + private static void AnalyzeNamedType(SymbolAnalysisContext context, HashSet operationAttributes) + { + var typeSymbol = (INamedTypeSymbol)context.Symbol; + if (typeSymbol.TypeKind != TypeKind.Class || typeSymbol.IsRecord) + { + return; + } + + if (!HasOperationMethods(typeSymbol, operationAttributes)) + { + return; + } + + foreach (var reference in typeSymbol.DeclaringSyntaxReferences) + { + context.CancellationToken.ThrowIfCancellationRequested(); + + if (reference.GetSyntax(context.CancellationToken) is ClassDeclarationSyntax classNode && + GetNonPartialTypeDeclarations(classNode).Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create( + shouldBePartialRule, classNode.Identifier.GetLocation(), typeSymbol.Name)); + return; + } + } + } + + private static bool HasOperationMethods(INamedTypeSymbol typeSymbol, HashSet operationAttributes) + { + foreach (var member in typeSymbol.GetMembers()) + { + if (member is IMethodSymbol method) + { + foreach (var attribute in method.GetAttributes()) + { + if (attribute.AttributeClass is not null && operationAttributes.Contains(attribute.AttributeClass)) + { + return true; + } + } + } + } + + return false; + } + + /// + /// Gets the given type declaration and all of its containing + /// type declarations that are not declared partial. + /// + internal static List GetNonPartialTypeDeclarations(TypeDeclarationSyntax typeNode) + { + var result = new List(); + foreach (var node in typeNode.AncestorsAndSelf().OfType()) + { + if (!node.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + result.Add(node); + } + } + + return result; + } + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAnalyzerConstants.cs b/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAnalyzerConstants.cs new file mode 100644 index 0000000000..c0bc4d1e2b --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers/TypeWithOperationsShouldBePartialAnalyzerConstants.cs @@ -0,0 +1,31 @@ +using Csla.Analyzers.Properties; +using Microsoft.CodeAnalysis; + +namespace Csla.Analyzers +{ + /// + /// + /// + public static class TypeWithOperationsShouldBePartialAnalyzerConstants + { + /// + /// + /// + public static readonly LocalizableResourceString Title = new(nameof(Resources.TypeWithOperationsShouldBePartial_Title), Resources.ResourceManager, typeof(Resources)); + /// + /// + /// + public static readonly LocalizableResourceString Message = new(nameof(Resources.TypeWithOperationsShouldBePartial_Message), Resources.ResourceManager, typeof(Resources)); + } + + /// + /// + /// + public static class TypeWithOperationsShouldBePartialAddPartialCodeFixConstants + { + /// + /// + /// + public static string AddPartialDescription => Resources.TypeWithOperationsShouldBePartial_AddPartialDescription; + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionAnalyzer.cs b/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionAnalyzer.cs new file mode 100644 index 0000000000..b0d078e93f --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionAnalyzer.cs @@ -0,0 +1,524 @@ +// Portions of this analyzer are adapted from the NotDataPortalExtensionMethodUsedAnalyzer +// in Csla.DataPortalExtensions (https://github.com/StefanOssendorf/Csla.DataPortalExtensions), +// Copyright (c) Stefan Ossendorf, licensed under the MIT License. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using System.Collections.Immutable; + +namespace Csla.Analyzers +{ + /// + /// Reports calls to the untyped, criteria-based methods of + /// IDataPortal<T>, IChildDataPortal<T>, and + /// DataPortal<T> when T or its assembly is marked with + /// [DataPortalExtensions], meaning strongly typed extension + /// methods are generated and should be used instead. The diagnostic is + /// only reported when the generator produces an extension method that + /// can replace the call. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class UseGeneratedDataPortalExtensionAnalyzer + : DiagnosticAnalyzer + { + /// + /// Diagnostic property holding the names of the generated extension + /// methods that can replace the call, separated by semicolons. + /// + public const string ExtensionNamesProperty = "ExtensionNames"; + + private static readonly DiagnosticDescriptor useExtensionRule = + new( + Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, UseGeneratedDataPortalExtensionAnalyzerConstants.Title, + UseGeneratedDataPortalExtensionAnalyzerConstants.Message, Constants.Categories.Usage, + DiagnosticSeverity.Warning, true, + helpLinkUri: HelpUrlBuilder.Build( + Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, nameof(UseGeneratedDataPortalExtensionAnalyzer))); + + /// + /// + /// + public override ImmutableArray SupportedDiagnostics => [useExtensionRule]; + + /// + /// + /// + public override void Initialize(AnalysisContext context) + { + // Generated code must be skipped: the generated extension methods + // themselves call the untyped data portal methods. + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationContext => + { + var compilation = compilationContext.Compilation; + var extensionsAttribute = compilation.GetTypeByMetadataName("Csla.DataPortalExtensionsAttribute"); + if (extensionsAttribute is null) + { + return; + } + + var globalOptions = compilationContext.Options.AnalyzerConfigOptionsProvider.GlobalOptions; + var symbols = new KnownSymbols( + GetAsyncSuffix(globalOptions), + GenerateSync(globalOptions), + extensionsAttribute, + compilation.GetTypeByMetadataName("Csla.IDataPortal`1"), + compilation.GetTypeByMetadataName("Csla.IChildDataPortal`1"), + compilation.GetTypeByMetadataName("Csla.DataPortal`1"), + compilation.GetTypeByMetadataName("Csla.NoDataPortalExtensionAttribute"), + compilation.GetTypeByMetadataName("Csla.InjectAttribute"), + compilation.GetTypeByMetadataName("Csla.Core.ICslaObject")); + + compilationContext.RegisterOperationAction( + operationContext => AnalyzeInvocation(operationContext, symbols), OperationKind.Invocation); + }); + } + + private static void AnalyzeInvocation(OperationAnalysisContext context, KnownSymbols symbols) + { + var invocation = (IInvocationOperation)context.Operation; + var method = invocation.TargetMethod; + var containingType = method.ContainingType; + if (containingType is null || containingType.TypeArguments.Length != 1) + { + return; + } + + var definition = containingType.OriginalDefinition; + var isDataPortalClass = SymbolEqualityComparer.Default.Equals(definition, symbols.DataPortal); + var isRootPortal = isDataPortalClass || SymbolEqualityComparer.Default.Equals(definition, symbols.IDataPortal); + var isChildPortal = isDataPortalClass || SymbolEqualityComparer.Default.Equals(definition, symbols.IChildDataPortal); + if (!isRootPortal && !isChildPortal) + { + return; + } + + if (!IsCriteriaMethod(method, isRootPortal, isChildPortal)) + { + return; + } + + context.CancellationToken.ThrowIfCancellationRequested(); + + if (containingType.TypeArguments[0] is not INamedTypeSymbol businessType || + GetPrefix(businessType, symbols) is not { } prefix) + { + return; + } + + var extensionNames = GetGeneratedExtensionNames(businessType, prefix, method.Name, GetCriteriaArguments(invocation), context.Compilation, symbols); + if (extensionNames.Count == 0) + { + return; + } + + var properties = ImmutableDictionary.Empty.Add(ExtensionNamesProperty, string.Join(";", extensionNames)); + context.ReportDiagnostic(Diagnostic.Create( + useExtensionRule, GetLocation(invocation), properties, businessType.Name, method.Name)); + } + + private static bool IsCriteriaMethod(IMethodSymbol method, bool isRootPortal, bool isChildPortal) + { + switch (method.Name) + { + case "Create": + case "CreateAsync": + case "Fetch": + case "FetchAsync": + case "Delete": + case "DeleteAsync": + return isRootPortal; + case "Execute": + case "ExecuteAsync": + // Only the params object[] criteria overload, not Execute(T command) + return isRootPortal && method.Parameters.Length == 1 && method.Parameters[0].IsParams; + case "CreateChild": + case "CreateChildAsync": + case "FetchChild": + case "FetchChildAsync": + return isChildPortal; + default: + return false; + } + } + + /// + /// Mirrors the data portal extensions generator: returns the prefix of the + /// extension methods requested for the business type by its own + /// [DataPortalExtensions] attribute or by the attribute on its assembly, + /// or null when no extension methods are requested. + /// + private static string? GetPrefix(INamedTypeSymbol businessType, KnownSymbols symbols) + { + if (businessType.IsRecord || + FindAttribute(businessType.GetAttributes(), symbols.NoDataPortalExtensionAttribute) is not null) + { + return null; + } + + var typeAttribute = FindAttribute(businessType.GetAttributes(), symbols.DataPortalExtensionsAttribute); + var assemblyAttribute = FindAttribute(businessType.ContainingAssembly.GetAttributes(), symbols.DataPortalExtensionsAttribute); + if (typeAttribute is null && assemblyAttribute is null) + { + return null; + } + + return GetPrefix(typeAttribute) ?? GetPrefix(assemblyAttribute) ?? string.Empty; + } + + private static AttributeData? FindAttribute(ImmutableArray attributes, INamedTypeSymbol? attributeType) + { + foreach (var attribute in attributes) + { + if (attributeType is not null && SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeType)) + { + return attribute; + } + } + + return null; + } + + private static string? GetPrefix(AttributeData? attribute) + { + if (attribute is null) + { + return null; + } + + foreach (var namedArgument in attribute.NamedArguments) + { + if (namedArgument.Key == "Prefix") + { + return namedArgument.Value.Value as string ?? string.Empty; + } + } + + return null; + } + + /// + /// Mirrors the data portal extensions generator: returns the names of the + /// extension methods generated for operation methods of the kind called + /// that accept the criteria values passed. + /// + private static List GetGeneratedExtensionNames(INamedTypeSymbol businessType, string prefix, string portalMethodName, + ImmutableArray? criteriaArguments, Compilation compilation, KnownSymbols symbols) + { + var names = new List(); + if (businessType.IsAbstract || symbols.ICslaObject is null || + !businessType.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, symbols.ICslaObject))) + { + return names; + } + + for (var type = businessType; type is not null; type = type.ContainingType) + { + if (type.TypeParameters.Length > 0) + { + return names; + } + } + + if (IsPrivate(businessType)) + { + return names; + } + + if (prefix.Length > 0 && !SyntaxFacts.IsValidIdentifier(prefix)) + { + return names; + } + + var isAsync = portalMethodName.EndsWith("Async", StringComparison.Ordinal); + if (!isAsync && !symbols.GenerateSync) + { + return names; + } + + var kind = isAsync ? portalMethodName.Substring(0, portalMethodName.Length - "Async".Length) : portalMethodName; + var receiver = kind is "CreateChild" or "FetchChild" ? symbols.IChildDataPortal : symbols.IDataPortal; + var hiddenNames = receiver is null + ? ImmutableHashSet.Empty + : receiver.GetMembers().Select(m => m.Name).ToImmutableHashSet(); + var operationAttributeName = $"Csla.{kind}Attribute"; + + // Operation methods of this kind that generated code can dispatch to; + // ref/out parameters and a single object[] parameter are only invoked through reflection. + var operations = businessType.GetMembers() + .OfType() + .Where(m => m.MethodKind == MethodKind.Ordinary && !m.IsStatic && !m.IsGenericMethod) + .Where(m => m.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == operationAttributeName)) + .Where(m => !m.Parameters.Any(p => p.RefKind is RefKind.Ref or RefKind.Out)) + .Where(m => !(m.Parameters.Length == 1 && m.Parameters[0].Type is IArrayTypeSymbol { Rank: 1, ElementType.SpecialType: SpecialType.System_Object })) + .Select(m => (Method: m, Criteria: m.Parameters.Where(p => !IsInjected(p, symbols.InjectAttribute)).ToList())) + .ToList(); + + foreach (var (operation, criteria) in operations) + { + if (operation.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, symbols.NoDataPortalExtensionAttribute))) + { + continue; + } + + // Methods with the same criteria types differ only in injected parameters; + // the generator only produces an extension for the one the runtime prefers. + if (!IsPreferredAmongSameCriteria(operation, criteria, operations)) + { + continue; + } + + if (criteria.Any(p => IsPrivate(p.Type))) + { + continue; + } + + var baseName = operation.Name; + if (baseName.EndsWith("Async", StringComparison.Ordinal) && baseName.Length > "Async".Length) + { + baseName = baseName.Substring(0, baseName.Length - "Async".Length); + } + + var name = prefix + baseName + (isAsync ? symbols.AsyncSuffix : string.Empty); + if (hiddenNames.Contains(name)) + { + continue; + } + + if (criteriaArguments is { } arguments && !AcceptsArguments(criteria, arguments, compilation)) + { + continue; + } + + if (!names.Contains(name)) + { + names.Add(name); + } + } + + return names; + } + + private static bool IsPreferredAmongSameCriteria(IMethodSymbol operation, List criteria, + List<(IMethodSymbol Method, List Criteria)> operations) + { + var injectCount = operation.Parameters.Length - criteria.Count; + foreach (var (other, otherCriteria) in operations) + { + if (SymbolEqualityComparer.Default.Equals(other, operation) || + !otherCriteria.Select(p => p.Type).SequenceEqual(criteria.Select(p => p.Type), SymbolEqualityComparer.Default)) + { + continue; + } + + var otherInjectCount = other.Parameters.Length - otherCriteria.Count; + if (otherInjectCount > injectCount) + { + return false; + } + + // ties go to the first declared method + if (otherInjectCount == injectCount && + operations.FindIndex(o => SymbolEqualityComparer.Default.Equals(o.Method, other)) < + operations.FindIndex(o => SymbolEqualityComparer.Default.Equals(o.Method, operation))) + { + return false; + } + } + + return true; + } + + /// + /// The criteria values can be passed to the criteria parameters of the + /// generated extension method. + /// + private static bool AcceptsArguments(List criteria, ImmutableArray arguments, Compilation compilation) + { + var required = criteria.Count(p => !p.HasExplicitDefaultValue && !p.IsParams); + var hasParams = criteria.Count > 0 && criteria[criteria.Count - 1].IsParams; + if (arguments.Length < required || (!hasParams && arguments.Length > criteria.Count)) + { + return false; + } + + for (var i = 0; i < arguments.Length; i++) + { + var parameter = criteria[Math.Min(i, criteria.Count - 1)]; + var argument = arguments[i] is IConversionOperation { IsImplicit: true } conversion ? conversion.Operand : arguments[i]; + if (parameter.IsParams && parameter.Type is IArrayTypeSymbol paramsArray) + { + var isLastExpandable = i >= criteria.Count - 1; + if (isLastExpandable && (IsConvertible(argument, paramsArray.ElementType, compilation) || + (arguments.Length == criteria.Count && IsConvertible(argument, paramsArray, compilation)))) + { + continue; + } + + return false; + } + + if (!IsConvertible(argument, parameter.Type, compilation)) + { + return false; + } + } + + return true; + } + + private static bool IsConvertible(IOperation argument, ITypeSymbol target, Compilation compilation) + { + if (argument.Type is null) + { + // null literal or a typeless expression + return argument.ConstantValue is not { HasValue: true, Value: null } || + target.IsReferenceType || target.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; + } + + return compilation.ClassifyCommonConversion(argument.Type, target).IsImplicit; + } + + /// + /// The criteria values passed to the data portal method, or null when + /// they are passed as an existing array. + /// + private static ImmutableArray? GetCriteriaArguments(IInvocationOperation invocation) + { + if (invocation.Arguments.Length == 0) + { + return ImmutableArray.Empty; + } + + var argument = invocation.Arguments[invocation.Arguments.Length - 1]; + if (argument.ArgumentKind == ArgumentKind.ParamArray && argument.Value is IArrayCreationOperation { Initializer: { } initializer }) + { + return initializer.ElementValues; + } + + return null; + } + + private static bool IsInjected(IParameterSymbol parameter, INamedTypeSymbol? injectAttribute) + { + if (injectAttribute is null) + { + return false; + } + + foreach (var attribute in parameter.GetAttributes()) + { + for (var type = attribute.AttributeClass; type is not null; type = type.BaseType) + { + if (SymbolEqualityComparer.Default.Equals(type, injectAttribute)) + { + return true; + } + } + } + + return false; + } + + /// + /// The type, or a type it is composed of, is not accessible outside its containing type. + /// + private static bool IsPrivate(ITypeSymbol type) + { + switch (type) + { + case IArrayTypeSymbol array: + return IsPrivate(array.ElementType); + case INamedTypeSymbol named: + for (var current = named; current is not null; current = current.ContainingType) + { + if (current.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal)) + { + return true; + } + } + + return named.TypeArguments.Any(IsPrivate); + default: + return false; + } + } + + private static Location GetLocation(IInvocationOperation invocation) + { + if (invocation.Syntax is InvocationExpressionSyntax invocationSyntax) + { + switch (invocationSyntax.Expression) + { + case MemberAccessExpressionSyntax memberAccess: + return memberAccess.Name.GetLocation(); + case MemberBindingExpressionSyntax memberBinding: + return memberBinding.Name.GetLocation(); + } + } + + return invocation.Syntax.GetLocation(); + } + + /// + /// Mirrors the generator: synchronous extension methods are generated unless + /// the CslaGenerateSyncDataPortalExtensions MSBuild property is false, or + /// there is no async suffix so they would have the same names as the async methods. + /// + private static bool GenerateSync(AnalyzerConfigOptions options) + { + return GetAsyncSuffix(options).Length > 0 && + !(options.TryGetValue("build_property.CslaGenerateSyncDataPortalExtensions", out var value) && + string.Equals(value?.Trim(), "false", StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Mirrors the generator: async extension method names end with the + /// CslaDataPortalExtensionsAsyncSuffix MSBuild property, which defaults + /// to Async, is none for no suffix, and is ignored when it is not valid in an identifier. + /// + private static string GetAsyncSuffix(AnalyzerConfigOptions options) + { + if (options.TryGetValue("build_property.CslaDataPortalExtensionsAsyncSuffix", out var value) && value?.Trim() is { Length: > 0 } suffix) + { + if (string.Equals(suffix, "none", StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + + if (SyntaxFacts.IsValidIdentifier("_" + suffix)) + { + return suffix; + } + } + + return "Async"; + } + + private sealed class KnownSymbols( + string asyncSuffix, + bool generateSync, + INamedTypeSymbol dataPortalExtensionsAttribute, + INamedTypeSymbol? iDataPortal, + INamedTypeSymbol? iChildDataPortal, + INamedTypeSymbol? dataPortal, + INamedTypeSymbol? noDataPortalExtensionAttribute, + INamedTypeSymbol? injectAttribute, + INamedTypeSymbol? iCslaObject) + { + public string AsyncSuffix { get; } = asyncSuffix; + public bool GenerateSync { get; } = generateSync; + public INamedTypeSymbol DataPortalExtensionsAttribute { get; } = dataPortalExtensionsAttribute; + public INamedTypeSymbol? IDataPortal { get; } = iDataPortal; + public INamedTypeSymbol? IChildDataPortal { get; } = iChildDataPortal; + public INamedTypeSymbol? DataPortal { get; } = dataPortal; + public INamedTypeSymbol? NoDataPortalExtensionAttribute { get; } = noDataPortalExtensionAttribute; + public INamedTypeSymbol? InjectAttribute { get; } = injectAttribute; + public INamedTypeSymbol? ICslaObject { get; } = iCslaObject; + } + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionAnalyzerConstants.cs b/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionAnalyzerConstants.cs new file mode 100644 index 0000000000..e55f509f56 --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionAnalyzerConstants.cs @@ -0,0 +1,31 @@ +using Csla.Analyzers.Properties; +using Microsoft.CodeAnalysis; + +namespace Csla.Analyzers +{ + /// + /// + /// + public static class UseGeneratedDataPortalExtensionAnalyzerConstants + { + /// + /// + /// + public static readonly LocalizableResourceString Title = new(nameof(Resources.UseGeneratedDataPortalExtension_Title), Resources.ResourceManager, typeof(Resources)); + /// + /// + /// + public static readonly LocalizableResourceString Message = new(nameof(Resources.UseGeneratedDataPortalExtension_Message), Resources.ResourceManager, typeof(Resources)); + } + + /// + /// + /// + public static class UseGeneratedDataPortalExtensionCodeFixConstants + { + /// + /// + /// + public static string UseExtensionDescription => Resources.UseGeneratedDataPortalExtension_UseExtensionDescription; + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionCodeFix.cs b/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionCodeFix.cs new file mode 100644 index 0000000000..dda94146f3 --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers/UseGeneratedDataPortalExtensionCodeFix.cs @@ -0,0 +1,99 @@ +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Csla.Analyzers +{ + /// + /// Replaces a call to an untyped, criteria-based data portal method with + /// the generated extension method, adding a using directive for the + /// extension method's namespace when it is not in scope. + /// + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public sealed class UseGeneratedDataPortalExtensionCodeFix + : CodeFixProvider + { + /// + /// + /// + public override ImmutableArray FixableDiagnosticIds => [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]; + + /// + /// + /// + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + /// + /// + /// + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root is null) + { + return; + } + + var diagnostic = context.Diagnostics.First(); + if (!diagnostic.Properties.TryGetValue(UseGeneratedDataPortalExtensionAnalyzer.ExtensionNamesProperty, out var extensionNames) || + string.IsNullOrEmpty(extensionNames)) + { + return; + } + + if (root.FindToken(diagnostic.Location.SourceSpan.Start).Parent is not SimpleNameSyntax name || + name.FirstAncestorOrSelf() is not { } invocation) + { + return; + } + + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + if (semanticModel?.GetSymbolInfo(invocation, context.CancellationToken).Symbol is not IMethodSymbol { ContainingType.TypeArguments: [INamedTypeSymbol businessType] }) + { + return; + } + + foreach (var extensionName in extensionNames!.Split([';'], StringSplitOptions.RemoveEmptyEntries)) + { + var title = string.Format(UseGeneratedDataPortalExtensionCodeFixConstants.UseExtensionDescription, extensionName); + context.RegisterCodeFix( + CodeAction.Create(title, + _ => Task.FromResult(context.Document.WithSyntaxRoot( + ReplaceCall(root, semanticModel, invocation, name, extensionName, businessType))), + title), + diagnostic); + } + } + + private static SyntaxNode ReplaceCall(SyntaxNode root, SemanticModel semanticModel, InvocationExpressionSyntax invocation, + SimpleNameSyntax name, string extensionName, INamedTypeSymbol businessType) + { + // Generic type arguments of the untyped method do not apply to the extension method. + var newName = SyntaxFactory.IdentifierName(extensionName).WithTriviaFrom(name); + var newInvocation = invocation.ReplaceNode(name, newName); + var newRoot = root.ReplaceNode(invocation, newInvocation); + + // The extension class is declared in the business type's namespace. + var @namespace = businessType.ContainingNamespace; + if (@namespace is null || @namespace.IsGlobalNamespace || + semanticModel.GetSpeculativeSymbolInfo(invocation.SpanStart, newInvocation, SpeculativeBindingOption.BindAsExpression).Symbol is not null || + newRoot is not CompilationUnitSyntax compilationUnit) + { + return newRoot; + } + + var namespaceName = @namespace.ToDisplayString(); + if (compilationUnit.Usings.Any(u => u.Name?.ToString() == namespaceName)) + { + return newRoot; + } + + return compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceName))); + } + } +} diff --git a/Source/Csla.Benchmarks/Csla.Benchmarks.csproj b/Source/Csla.Benchmarks/Csla.Benchmarks.csproj index dba023d63f..b07477a812 100644 --- a/Source/Csla.Benchmarks/Csla.Benchmarks.csproj +++ b/Source/Csla.Benchmarks/Csla.Benchmarks.csproj @@ -10,6 +10,6 @@ - + diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/Csla.Generator.AutoImplementProperties.Attributes.CSharp.csproj b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/Csla.Generator.AutoImplementProperties.Attributes.CSharp.csproj deleted file mode 100644 index db90f85d61..0000000000 --- a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/Csla.Generator.AutoImplementProperties.Attributes.CSharp.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - netstandard2.0 - false - false - false - false - false - false - false - false - false - false - ..\..\..\..\..\Bin - ..\..\..\..\..\bin\packages\ - false - true - - diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/AutoImplement/ExtractedTypeDefinition.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/AutoImplement/ExtractedTypeDefinition.cs index 454cc8c442..6d22d89c27 100644 --- a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/AutoImplement/ExtractedTypeDefinition.cs +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/AutoImplement/ExtractedTypeDefinition.cs @@ -1,4 +1,5 @@ -using Csla.Generator.AutoImplementProperties.CSharp.AutoImplement; +using Csla.Generator.AutoImplementProperties.CSharp; +using Csla.Generator.AutoImplementProperties.CSharp.AutoImplement; /// /// The definition of a type, extracted from the syntax tree provided by Roslyn diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Csla.Generator.AutoImplementProperties.CSharp.csproj b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Csla.Generator.AutoImplementProperties.CSharp.csproj index fa8842bd4a..fb2304ec0b 100644 --- a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Csla.Generator.AutoImplementProperties.CSharp.csproj +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Csla.Generator.AutoImplementProperties.CSharp.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 @@ -13,33 +13,20 @@ false false true + $(NoWarn);RS2008 ..\..\..\..\..\Bin ..\..\..\..\..\bin\packages\ Csla.Generator.AutoImplementProperties.CSharp Csla.Generator.AutoImplementProperties.CSharp - Csla.Generator.AutoImplementProperties.CSharp - CSLA .NET Generator AutoImplementProperties for CSharp + CSLA .NET source generators for CSharp (bundled in the Csla package) true True - CSLA .NET Generators - CSLA;Roslyn;Generator - - - true - false - true + + false - - - - - - - - - \ No newline at end of file + diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/DataPortalExtensionsAnalyzer.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/DataPortalExtensionsAnalyzer.cs new file mode 100644 index 0000000000..fb7a313b87 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/DataPortalExtensionsAnalyzer.cs @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Reports data portal extension methods that are not generated +//----------------------------------------------------------------------- + +using System.Collections.Immutable; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalExtensions +{ + /// + /// Reports business types for which + /// does not generate some or all of the requested extension methods, and + /// why, along with invalid extension settings. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DataPortalExtensionsAnalyzer : DiagnosticAnalyzer + { + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + DataPortalOperationsDiagnostics.GenericExtensionsNotGenerated, + DataPortalOperationsDiagnostics.InvalidExtensionsTarget, + DataPortalOperationsDiagnostics.ExtensionNameHidden, + DataPortalOperationsDiagnostics.InaccessibleParameterType, + DataPortalOperationsDiagnostics.DuplicateExtensionMethod, + DataPortalOperationsDiagnostics.InvalidExtensionPrefix, + DataPortalOperationsDiagnostics.InvalidAsyncSuffix, + DataPortalOperationsDiagnostics.SyncExtensionsNotGenerated + ]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationContext => + { + var compilation = compilationContext.Compilation; + if (compilation.GetTypeByMetadataName(OperationDiscovery.DataPortalExtensionsAttributeName) is not { } extensionsAttribute) + return; + + var options = ExtensionOptions.Create(compilation, compilationContext.Options.AnalyzerConfigOptionsProvider.GlobalOptions); + var attributeKinds = DataPortalOperationsAnalyzer.GetOperationAttributeKinds(compilation); + var state = new AnalysisState(); + + compilationContext.RegisterSymbolAction(symbolContext => + { + if (AnalyzeType(symbolContext, extensionsAttribute, attributeKinds, options)) + state.ExtensionsRequested = true; + }, SymbolKind.NamedType); + + if (options.AssemblyEnabled && options.AssemblyPrefix.Length > 0 && !SyntaxFacts.IsValidIdentifier(options.AssemblyPrefix)) + { + compilationContext.RegisterSyntaxNodeAction( + syntaxContext => AnalyzeAssemblyAttributes(syntaxContext, extensionsAttribute, options), SyntaxKind.AttributeList); + } + + if (options.InvalidAsyncSuffix is not null || (options.GenerateSyncRequested && !options.GenerateSync)) + { + compilationContext.RegisterCompilationEndAction(endContext => + { + if (!state.ExtensionsRequested) + return; + if (options.InvalidAsyncSuffix is not null) + endContext.ReportDiagnostic(Diagnostic.Create(DataPortalOperationsDiagnostics.InvalidAsyncSuffix, Location.None, options.InvalidAsyncSuffix)); + if (options.GenerateSyncRequested && !options.GenerateSync) + endContext.ReportDiagnostic(Diagnostic.Create(DataPortalOperationsDiagnostics.SyncExtensionsNotGenerated, Location.None)); + }); + } + }); + } + + /// + /// Reports why extension methods are not generated for a type. Returns + /// true when extension methods are requested for the type. + /// + private static bool AnalyzeType(SymbolAnalysisContext context, INamedTypeSymbol extensionsAttribute, + Dictionary attributeKinds, ExtensionOptions options) + { + var type = (INamedTypeSymbol)context.Symbol; + if (type.TypeKind != TypeKind.Class) + return false; + + var compilation = context.Compilation; + var operationType = OperationDiscovery.GetOperationType(type, attributeKinds, compilation, context.CancellationToken); + if (operationType is null) + { + // A type marked with the attribute is reported even without operation methods. + if (!type.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, extensionsAttribute)) + || OperationDiscovery.BuildTypeHeader(type, compilation, context.CancellationToken) is not { } header) + return false; + operationType = new OperationTypeModel(header, new EquatableArray(Array.Empty())); + } + + var model = IncrementalDataPortalExtensionsGenerator.CreateModel(operationType, options, LocationInfo.From(type.Locations.FirstOrDefault())); + if (model is null) + return false; + + DataPortalExtensionsBuilder.Analyze(model, (descriptor, location, messageArgs) + => context.ReportDiagnostic(Diagnostic.Create(descriptor, location?.ToLocation(compilation), messageArgs))); + return true; + } + + private static void AnalyzeAssemblyAttributes(SyntaxNodeAnalysisContext context, INamedTypeSymbol extensionsAttribute, ExtensionOptions options) + { + var attributeList = (AttributeListSyntax)context.Node; + if (attributeList.Target?.Identifier.IsKind(SyntaxKind.AssemblyKeyword) != true) + return; + + foreach (var attribute in attributeList.Attributes) + { + if (context.SemanticModel.GetSymbolInfo(attribute, context.CancellationToken).Symbol?.ContainingType is { } attributeType + && SymbolEqualityComparer.Default.Equals(attributeType, extensionsAttribute)) + { + context.ReportDiagnostic(Diagnostic.Create( + DataPortalOperationsDiagnostics.InvalidExtensionPrefix, attribute.GetLocation(), context.Compilation.AssemblyName, options.AssemblyPrefix)); + } + } + } + + private sealed class AnalysisState + { + private volatile bool _extensionsRequested; + + public bool ExtensionsRequested + { + get => _extensionsRequested; + set => _extensionsRequested = value; + } + } + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/DataPortalExtensionsBuilder.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/DataPortalExtensionsBuilder.cs new file mode 100644 index 0000000000..0c3a4309f8 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/DataPortalExtensionsBuilder.cs @@ -0,0 +1,294 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Builds strongly typed data portal extension methods +//----------------------------------------------------------------------- +// Portions adapted from Csla.DataPortalExtensions by Stefan Ossendorf +// (https://github.com/StefanOssendorf/Csla.DataPortalExtensions), +// licensed under the MIT License. Copyright (c) 2023 Stefan Ossendorf. +//----------------------------------------------------------------------- + +using System.CodeDom.Compiler; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalExtensions +{ + /// + /// Builds the static class containing the strongly typed data portal + /// extension methods for a business type. + /// + internal static class DataPortalExtensionsBuilder + { + private const string ReceiverName = "portal"; + private const string InvokerName = "__invoker"; + + /// + /// Build the generated source for a business type. + /// Returns null when nothing is generated. + /// + public static string? Build(ExtensionGenerationModel model) + { + var type = model.Type; + var methods = Analyze(model); + if (methods.Count == 0) + return null; + + using var stringWriter = new StringWriter(); + var writer = new IndentedTextWriter(stringWriter, " "); + + writer.WriteLine("//"); + writer.WriteLine("#nullable enable"); + writer.WriteLineNoTabs(string.Empty); + + if (type.Namespace.Length > 0) + { + writer.WriteLine($"namespace {type.Namespace}"); + OpenBlock(writer); + } + + // Different business types can map to the same class name (for example + // a top-level Outer_Inner and a nested Outer.Inner). The partial parts + // then merge; only a public business type states the accessibility so + // the parts never conflict, and the methods carry their own accessibility. + var classModifiers = type.Visibility == TypeVisibility.Public ? "public static partial" : "static partial"; + writer.WriteLine("/// "); + writer.WriteLine($"/// Strongly typed data portal extension methods for ."); + writer.WriteLine("/// "); + writer.WriteLine($"{classModifiers} class {type.ExtensionClassName}"); + OpenBlock(writer); + + var isFirst = true; + foreach (var method in methods) + { + if (!isFirst) + writer.WriteLineNoTabs(string.Empty); + isFirst = false; + AppendMethod(writer, type, method); + } + + CloseBlock(writer); + + if (type.Namespace.Length > 0) + CloseBlock(writer); + + return stringWriter.ToString(); + } + + /// + /// Reports a diagnostic about a data portal extension method that is not generated. + /// + public delegate void DiagnosticReporter(DiagnosticDescriptor descriptor, LocationInfo? location, params object[] messageArgs); + + /// + /// Selects the extension methods generated for a business type. When + /// is supplied, also reports why any are not + /// generated. The generator passes no reporter, and the analyzer reports + /// the diagnostics, so both apply the same rules. + /// + public static List Analyze(ExtensionGenerationModel model, DiagnosticReporter? report = null) + { + var type = model.Type; + var none = new List(); + + if (type.IsGeneric) + { + report?.Invoke(DataPortalOperationsDiagnostics.GenericExtensionsNotGenerated, type.Location, type.TypeName); + return none; + } + + var invalidReason = type switch + { + { IsCslaObject: false } => "the type does not implement Csla.Core.ICslaObject", + { IsAbstract: true } => "the type is abstract", + { Visibility: TypeVisibility.Private } => "the type is not accessible outside its containing type", + _ => null + }; + if (invalidReason is not null) + { + report?.Invoke(DataPortalOperationsDiagnostics.InvalidExtensionsTarget, type.Location, type.TypeName, invalidReason); + return none; + } + + if (type.Prefix.Length > 0 && !SyntaxFacts.IsValidIdentifier(type.Prefix)) + { + // An invalid assembly prefix is reported once, on the assembly attribute. + if (!type.FromAssembly) + report?.Invoke(DataPortalOperationsDiagnostics.InvalidExtensionPrefix, type.Location, type.TypeName, type.Prefix); + return none; + } + + return SelectMethods(model, report); + } + + private static List SelectMethods(ExtensionGenerationModel model, DiagnosticReporter? report) + { + var type = model.Type; + var options = model.Options; + var result = new List(); + var signatures = new HashSet(StringComparer.Ordinal); + var (_, collisions) = DataPortalOperationsBuilder.SelectNamedDispatchMethods(model.Methods); + + foreach (var method in model.Methods) + { + if (method.NoExtension || !method.CanDispatch) + continue; + + // Methods sharing an operation name differ only in injected parameters, + // so they would produce identical extension methods; the runtime's + // choice is reported by the operations analyzer. + if (collisions.Any(c => ReferenceEquals(c.Loser, method))) + continue; + + var criteria = method.CriteriaParameters.ToList(); + var inaccessible = criteria.FirstOrDefault(p => p.Visibility == TypeVisibility.Private); + if (inaccessible is not null) + { + report?.Invoke(DataPortalOperationsDiagnostics.InaccessibleParameterType, method.Location, method.MethodDisplay, inaccessible.Name.TrimStart('@'), inaccessible.PatternTypeDisplay); + continue; + } + + var baseName = GetBaseName(method.MethodName.TrimStart('@')); + var isChild = method.Kind is "CreateChild" or "FetchChild"; + var receiverInterface = isChild ? "IChildDataPortal" : "IDataPortal"; + var hiddenNames = isChild ? options.ChildHiddenNames : options.RootHiddenNames; + var parameterTypes = string.Join(",", criteria.Select(p => p.PatternTypeDisplay + (p.IsNullableValueType ? "?" : string.Empty))); + + foreach (var isAsync in options.GenerateSync ? new[] { true, false } : new[] { true }) + { + var name = type.Prefix + baseName + (isAsync ? options.AsyncSuffix : string.Empty); + if (hiddenNames.Contains(name)) + { + report?.Invoke(DataPortalOperationsDiagnostics.ExtensionNameHidden, method.Location, name, type.TypeName, $"{receiverInterface}.{name}"); + continue; + } + + if (!signatures.Add($"{receiverInterface}.{name}({parameterTypes})")) + { + report?.Invoke(DataPortalOperationsDiagnostics.DuplicateExtensionMethod, method.Location, name, type.TypeName, method.MethodDisplay); + continue; + } + + result.Add(new ExtensionMethod(method, name, isAsync, isChild)); + } + } + + return result; + } + + /// + /// The operation method name without an Async suffix. + /// + internal static string GetBaseName(string methodName) + => methodName.EndsWith("Async", StringComparison.Ordinal) && methodName.Length > "Async".Length + ? methodName.Substring(0, methodName.Length - "Async".Length) + : methodName; + + private static void AppendMethod(IndentedTextWriter writer, ExtensionTypeModel type, ExtensionMethod extension) + { + var method = extension.Method; + var criteria = method.CriteriaParameters.ToList(); + var isDelete = method.Kind == "Delete"; + var criteriaNames = new HashSet(criteria.Select(p => p.Name.TrimStart('@')), StringComparer.Ordinal); + var receiver = GetUniqueName(ReceiverName, criteriaNames); + var invoker = GetUniqueName(InvokerName, criteriaNames); + var visibility = type.Visibility == TypeVisibility.Public && criteria.All(p => p.Visibility == TypeVisibility.Public) + ? "public" + : "internal"; + + var returnType = (extension.IsAsync, isDelete) switch + { + (true, true) => "global::System.Threading.Tasks.Task", + (true, false) => $"global::System.Threading.Tasks.Task<{type.FullyQualifiedName}>", + (false, true) => "void", + (false, false) => type.FullyQualifiedName + }; + + var portalType = extension.IsChild + ? $"global::Csla.IChildDataPortal<{type.FullyQualifiedName}>" + : $"global::Csla.IDataPortal<{type.FullyQualifiedName}>"; + var invokerType = extension.IsChild + ? $"global::Csla.IChildDataPortalOperationInvoker<{type.FullyQualifiedName}>" + : $"global::Csla.IDataPortalOperationInvoker<{type.FullyQualifiedName}>"; + + var parameters = new List { $"this {portalType} {receiver}" }; + foreach (var parameter in criteria) + { + var declaration = $"{(parameter.IsParams ? "params " : string.Empty)}{parameter.TypeDisplay} {parameter.Name}"; + if (parameter.DefaultValueExpression is not null) + declaration += $" = {parameter.DefaultValueExpression}"; + parameters.Add(declaration); + } + + var criteriaArray = criteria.Count == 0 + ? "global::System.Array.Empty()" + : $"new object?[] {{ {string.Join(", ", criteria.Select(p => p.Name))} }}"; + + var portalMethod = method.Kind + (extension.IsAsync ? "Async" : string.Empty); + var invokerMethod = method.Kind + "ByOperation" + (extension.IsAsync ? "Async" : string.Empty); + var invokerArguments = extension.IsChild + ? $"\"{method.OperationName}\", {criteriaArray}" + : $"\"{method.OperationName}\", {(method.RunLocal ? "true" : "false")}, {criteriaArray}"; + var fallbackArguments = criteria.Count == 0 ? string.Empty : criteriaArray; + var returnKeyword = isDelete && !extension.IsAsync ? string.Empty : "return "; + + writer.WriteLine("/// "); + writer.WriteLine($"/// Invokes the {Escape(method.MethodDisplay)} {method.Kind} operation of ."); + writer.WriteLine("/// "); + writer.WriteLine($"{visibility} static {returnType} {OperationDiscovery.EscapeIdentifier(extension.Name)}({string.Join(", ", parameters)})"); + OpenBlock(writer); + writer.WriteLine($"if ({receiver} is {invokerType} {invoker})"); + writer.Indent++; + if (isDelete && !extension.IsAsync) + { + writer.WriteLine($"{invoker}.{invokerMethod}({invokerArguments});"); + writer.Indent--; + writer.WriteLine("else"); + writer.Indent++; + writer.WriteLine($"{receiver}.{portalMethod}({fallbackArguments});"); + writer.Indent--; + } + else + { + writer.WriteLine($"{returnKeyword}{invoker}.{invokerMethod}({invokerArguments});"); + writer.Indent--; + writer.WriteLine($"{returnKeyword}{receiver}.{portalMethod}({fallbackArguments});"); + } + CloseBlock(writer); + } + + /// + /// Returns a name for a generated parameter or local that does not + /// collide with any operation parameter name. + /// + private static string GetUniqueName(string name, HashSet usedNames) + { + while (usedNames.Contains(name)) + name = "__" + name; + return name; + } + + private static string Escape(string text) => text.Replace("&", "&").Replace("<", "<").Replace(">", ">"); + + private static void OpenBlock(IndentedTextWriter writer) + { + writer.WriteLine("{"); + writer.Indent++; + } + + private static void CloseBlock(IndentedTextWriter writer) + { + writer.Indent--; + writer.WriteLine("}"); + } + + /// + /// A generated extension method for an operation method. + /// + internal sealed record ExtensionMethod(OperationMethodModel Method, string Name, bool IsAsync, bool IsChild); + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/ExtensionTypeModel.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/ExtensionTypeModel.cs new file mode 100644 index 0000000000..e1fabe2b39 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/ExtensionTypeModel.cs @@ -0,0 +1,198 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// A business type that gets data portal extension methods +//----------------------------------------------------------------------- +// Portions adapted from Csla.DataPortalExtensions by Stefan Ossendorf +// (https://github.com/StefanOssendorf/Csla.DataPortalExtensions), +// licensed under the MIT License. Copyright (c) 2023 Stefan Ossendorf. +//----------------------------------------------------------------------- + +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalExtensions +{ + /// + /// A business type that gets data portal extension methods, because it + /// is marked with [DataPortalExtensions] or its assembly is. + /// + internal sealed record ExtensionTypeModel + { + public required string Namespace { get; init; } + + /// Fully qualified business type name, e.g. "global::NS.PersonEdit". + public required string FullyQualifiedName { get; init; } + + /// Name of the generated static class, e.g. "PersonEditDataPortalExtensions". + public required string ExtensionClassName { get; init; } + + public required string TypeName { get; init; } + + /// File name friendly unique name. + public required string HintName { get; init; } + + public string Prefix { get; init; } = string.Empty; + + /// + /// The type is not marked with [DataPortalExtensions] itself; + /// extensions are requested by the assembly attribute. + /// + public bool FromAssembly { get; init; } + + public bool IsGeneric { get; init; } + + public bool IsAbstract { get; init; } + + public bool IsCslaObject { get; init; } + + public TypeVisibility Visibility { get; init; } + + /// Location of the type, set only for diagnostics reported by the analyzer. + public LocationInfo? Location { get; init; } + + /// + /// Creates the model for a type, or returns null when no extension + /// methods are requested for it. Types covered only by the assembly + /// attribute that cannot have extension methods are skipped; types + /// marked with the attribute are returned so the reason can be reported. + /// + public static ExtensionTypeModel? Create(OperationTypeHeader header, ExtensionOptions options, LocationInfo? location = null) + { + if (header.NoExtensions || header.IsRecord) + return null; + + var fromAssembly = !header.HasExtensionsAttribute; + if (fromAssembly) + { + if (!options.AssemblyEnabled) + return null; + if (header.IsGeneric || header.IsAbstract || !header.IsCslaObject || header.Visibility == TypeVisibility.Private) + return null; + } + + return new ExtensionTypeModel + { + Namespace = header.Namespace, + FullyQualifiedName = header.FullyQualifiedName, + ExtensionClassName = string.Concat(header.ContainerNames.Select(c => c + "_")) + header.TypeName + "DataPortalExtensions", + TypeName = header.TypeName, + HintName = header.HintName, + Prefix = header.ExtensionsPrefix ?? options.AssemblyPrefix, + FromAssembly = fromAssembly, + IsGeneric = header.IsGeneric, + IsAbstract = header.IsAbstract, + IsCslaObject = header.IsCslaObject, + Visibility = header.Visibility, + Location = location + }; + } + } + + /// + /// Compilation-wide settings for data portal extension generation. + /// + internal sealed record ExtensionOptions + { + /// + /// MSBuild property that controls whether synchronous extension methods + /// are generated. Set it to false to generate only the async methods. + /// + public const string GenerateSyncProperty = "build_property.CslaGenerateSyncDataPortalExtensions"; + + /// + /// MSBuild property that sets the suffix of async extension method names. + /// Defaults to Async; set it to none for no suffix. + /// + public const string AsyncSuffixProperty = "build_property.CslaDataPortalExtensionsAsyncSuffix"; + + public const string DefaultAsyncSuffix = "Async"; + + /// The assembly is marked with [DataPortalExtensions]. + public bool AssemblyEnabled { get; init; } + + /// The Prefix set by the assembly attribute. + public string AssemblyPrefix { get; init; } = string.Empty; + + /// The CslaGenerateSyncDataPortalExtensions property is not false. + public bool GenerateSyncRequested { get; init; } = true; + + /// Suffix of async method names. + public string AsyncSuffix { get; init; } = DefaultAsyncSuffix; + + /// The configured suffix, when it is not a valid identifier part and is ignored. + public string? InvalidAsyncSuffix { get; init; } + + /// + /// Synchronous methods are generated. They are not when they are turned off, + /// or when there is no async suffix and they would have the same names. + /// + public bool GenerateSync => GenerateSyncRequested && AsyncSuffix.Length > 0; + + /// + /// Names of instance members of IDataPortal<T>, which would hide + /// root extension methods with the same name. + /// + public EquatableArray RootHiddenNames { get; init; } = new(Array.Empty()); + + /// + /// Names of instance members of IChildDataPortal<T>, which would hide + /// child extension methods with the same name. + /// + public EquatableArray ChildHiddenNames { get; init; } = new(Array.Empty()); + + public static ExtensionOptions Create(Compilation compilation, AnalyzerConfigOptions globalOptions) + { + var assemblyAttribute = compilation.Assembly.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == OperationDiscovery.DataPortalExtensionsAttributeName); + + var generateSync = !(globalOptions.TryGetValue(GenerateSyncProperty, out var syncValue) + && string.Equals(syncValue?.Trim(), "false", StringComparison.OrdinalIgnoreCase)); + + var asyncSuffix = DefaultAsyncSuffix; + string? invalidAsyncSuffix = null; + if (globalOptions.TryGetValue(AsyncSuffixProperty, out var suffixValue) && suffixValue?.Trim() is { Length: > 0 } suffix) + { + if (string.Equals(suffix, "none", StringComparison.OrdinalIgnoreCase)) + asyncSuffix = string.Empty; + else if (SyntaxFacts.IsValidIdentifier("_" + suffix)) + asyncSuffix = suffix; + else + invalidAsyncSuffix = suffix; + } + + return new ExtensionOptions + { + AssemblyEnabled = assemblyAttribute is not null, + AssemblyPrefix = OperationDiscovery.GetPrefix(assemblyAttribute) ?? string.Empty, + GenerateSyncRequested = generateSync, + AsyncSuffix = asyncSuffix, + InvalidAsyncSuffix = invalidAsyncSuffix, + RootHiddenNames = GetMemberNames(compilation, "Csla.IDataPortal`1"), + ChildHiddenNames = GetMemberNames(compilation, "Csla.IChildDataPortal`1") + }; + } + + private static EquatableArray GetMemberNames(Compilation compilation, string metadataName) + { + var names = new SortedSet(StringComparer.Ordinal); + if (compilation.GetTypeByMetadataName(metadataName) is { } symbol) + { + foreach (var member in symbol.GetMembers()) + names.Add(member.Name); + } + return new EquatableArray(names); + } + } + + /// + /// A business type that gets data portal extension methods, its operation + /// methods, and the compilation-wide settings. + /// + internal sealed record ExtensionGenerationModel(ExtensionTypeModel Type, EquatableArray Methods, ExtensionOptions Options); +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/IncrementalDataPortalExtensionsGenerator.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/IncrementalDataPortalExtensionsGenerator.cs new file mode 100644 index 0000000000..144c729cd2 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalExtensions/IncrementalDataPortalExtensionsGenerator.cs @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Source generator for strongly typed data portal extension methods +//----------------------------------------------------------------------- +// Portions adapted from Csla.DataPortalExtensions by Stefan Ossendorf +// (https://github.com/StefanOssendorf/Csla.DataPortalExtensions), +// licensed under the MIT License. Copyright (c) 2023 Stefan Ossendorf. +//----------------------------------------------------------------------- + +using System.Text; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalExtensions +{ + /// + /// Incremental source generator that emits strongly typed extension methods + /// on IDataPortal<T> and IChildDataPortal<T> for each + /// data portal operation method of a business class marked with + /// [DataPortalExtensions], or of every eligible business class when + /// the assembly is marked. Diagnostics about the generated methods are + /// reported by . + /// + [Generator(LanguageNames.CSharp)] + public class IncrementalDataPortalExtensionsGenerator : IIncrementalGenerator + { + /// + /// Operation kinds that have a client-side data portal method. + /// + internal static readonly string[] SupportedKinds = ["Create", "Fetch", "Execute", "Delete", "CreateChild", "FetchChild"]; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var options = context.CompilationProvider + .Combine(context.AnalyzerConfigOptionsProvider) + .Select(static (pair, _) => ExtensionOptions.Create(pair.Left, pair.Right.GlobalOptions)) + .WithTrackingName(TrackingNames.ExtensionOptions); + + var models = IncrementalDataPortalOperationsGenerator.GetOperationTypes(context) + .SelectMany(static (types, _) => types) + .Combine(options) + .Select(static (pair, _) => CreateModel(pair.Left, pair.Right)) + .Where(static m => m is not null) + .Select(static (m, _) => m!) + .WithTrackingName(TrackingNames.ExtensionTypes); + + context.RegisterSourceOutput(models, static (spc, model) => + { + var source = DataPortalExtensionsBuilder.Build(model); + if (source is not null) + spc.AddSource($"{model.Type.HintName}.DataPortalExtensions.g.cs", SourceText.From(source, Encoding.UTF8)); + }); + } + + /// + /// Creates the generation model for a type with operation methods, or + /// returns null when no extension methods are requested for it. + /// + internal static ExtensionGenerationModel? CreateModel(OperationTypeModel operationType, ExtensionOptions options, LocationInfo? location = null) + { + var type = ExtensionTypeModel.Create(operationType.Type, options, location); + if (type is null) + return null; + + return new ExtensionGenerationModel( + type, + new EquatableArray(operationType.Methods.Where(m => SupportedKinds.Contains(m.Kind))), + options); + } + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsAnalyzer.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsAnalyzer.cs new file mode 100644 index 0000000000..88fbc9954c --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsAnalyzer.cs @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Reports data portal operation methods that share an operation name +//----------------------------------------------------------------------- + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations +{ + /// + /// Reports operation methods of a partial class that map to the same + /// data portal operation name, and how + /// dispatches them. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DataPortalOperationsAnalyzer : DiagnosticAnalyzer + { + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [DataPortalOperationsDiagnostics.DuplicateOperationName, DataPortalOperationsDiagnostics.AmbiguousOperationName]; + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterCompilationStartAction(compilationContext => + { + var attributeKinds = GetOperationAttributeKinds(compilationContext.Compilation); + if (attributeKinds.Count == 0) + return; + + compilationContext.RegisterSymbolAction(symbolContext => AnalyzeType(symbolContext, attributeKinds), SymbolKind.NamedType); + }); + } + + /// + /// The operation attribute types in the compilation, mapped to their operation kind. + /// + internal static Dictionary GetOperationAttributeKinds(Compilation compilation) + { + var result = new Dictionary(SymbolEqualityComparer.Default); + foreach (var kind in OperationDiscovery.OperationKinds) + { + if (compilation.GetTypeByMetadataName(OperationDiscovery.GetAttributeMetadataName(kind)) is { } attribute) + result[attribute] = kind; + } + return result; + } + + private static void AnalyzeType(SymbolAnalysisContext context, Dictionary attributeKinds) + { + var type = (INamedTypeSymbol)context.Symbol; + var model = OperationDiscovery.GetOperationType(type, attributeKinds, context.Compilation, context.CancellationToken); + if (model is null || !model.Type.IsPartial) + return; + + var (_, collisions) = DataPortalOperationsBuilder.SelectNamedDispatchMethods(model.Methods); + foreach (var collision in collisions) + { + context.ReportDiagnostic(Diagnostic.Create( + collision.IsAmbiguous ? DataPortalOperationsDiagnostics.AmbiguousOperationName : DataPortalOperationsDiagnostics.DuplicateOperationName, + collision.Loser.Location?.ToLocation(context.Compilation), + collision.Winner.MethodDisplay, collision.Loser.MethodDisplay, model.Type.TypeName, collision.Winner.OperationName)); + } + } + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsBuilder.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsBuilder.cs new file mode 100644 index 0000000000..ccff75a627 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsBuilder.cs @@ -0,0 +1,396 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Builds generated source for a type's data portal operations +//----------------------------------------------------------------------- + +using System.CodeDom.Compiler; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations +{ + /// + /// Builds the partial type declaration that exposes a type's data portal + /// operation methods through a nested IDataPortalOperations interface + /// and, for concrete types, implements IDataPortalOperationMapping + /// and IDataPortalOperationNamedMapping. + /// + internal static class DataPortalOperationsBuilder + { + private const string HelperType = "global::Csla.Server.DataPortalOperationHelper"; + private const string ServiceProviderExtensionsType = "global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions"; + private const string KeyedServiceProviderExtensionsType = "global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions"; + private const string OperationsLocal = "__operations"; + private const string ExceptionLocal = "__ex"; + + /// + /// Build the generated source for a type. + /// + public static string Build(OperationTypeModel model) + { + var type = model.Type; + var memberNames = GetInterfaceMemberNames(model.Methods); + + using var stringWriter = new StringWriter(); + var writer = new IndentedTextWriter(stringWriter, " "); + + writer.WriteLine("//"); + writer.WriteLine("#nullable enable"); + writer.WriteLine("#pragma warning disable CS1998 // Async method lacks 'await' operators"); + writer.WriteLineNoTabs(string.Empty); + + if (type.Namespace.Length > 0) + { + writer.WriteLine($"namespace {type.Namespace}"); + OpenBlock(writer); + } + + foreach (var container in type.ContainerDeclarations) + { + writer.WriteLine(container); + OpenBlock(writer); + } + + var emitMapping = !type.IsAbstract && !type.ImplementsOperationMapping; + var emitNamedMapping = !type.IsAbstract && !type.ImplementsNamedOperationMapping; + + var baseList = new List { $"{type.FullyQualifiedName}.{OperationDiscovery.OperationsInterfaceName}" }; + if (emitMapping) + baseList.Add("global::Csla.Server.IDataPortalOperationMapping"); + if (emitNamedMapping) + baseList.Add("global::Csla.Server.IDataPortalOperationNamedMapping"); + + writer.WriteLine($"{type.TypeDeclaration} : {string.Join(", ", baseList)}"); + OpenBlock(writer); + + AppendOperationsInterface(writer, model, memberNames); + writer.WriteLineNoTabs(string.Empty); + AppendExplicitImplementations(writer, model, memberNames); + + if (emitMapping) + { + writer.WriteLineNoTabs(string.Empty); + AppendInvokeOperationAsync(writer, model, memberNames); + } + + if (emitNamedMapping) + { + writer.WriteLineNoTabs(string.Empty); + AppendInvokeNamedOperationAsync(writer, model, memberNames); + } + + CloseBlock(writer); + + foreach (var _ in type.ContainerDeclarations) + CloseBlock(writer); + + if (type.Namespace.Length > 0) + CloseBlock(writer); + + return stringWriter.ToString(); + } + + /// + /// Selects the method used for each operation name in generated + /// dispatch. Like the runtime method resolution, when several methods + /// share an operation name (they differ only in injected parameters) + /// the one with the most injected parameters wins. When several methods + /// tie for the most injected parameters the runtime reports an ambiguous + /// match, so none of them is selected and the call is left to + /// reflection-based dispatch; the first declared of them is reported as + /// the winner of an ambiguous collision. Returns the selected methods + /// and any collisions. + /// + public static (List Selected, List Collisions) + SelectNamedDispatchMethods(EquatableArray methods) + { + var selected = new List(); + var collisions = new List(); + foreach (var group in methods.Where(m => m.CanDispatchByName).GroupBy(m => m.OperationName)) + { + var ordered = group.ToList(); + var winner = ordered[0]; + foreach (var candidate in ordered.Skip(1)) + { + if (candidate.InjectCount > winner.InjectCount) + winner = candidate; + } + var isAmbiguous = ordered.Count(m => m.InjectCount == winner.InjectCount) > 1; + if (!isAmbiguous) + selected.Add(winner); + foreach (var loser in ordered.Where(m => !ReferenceEquals(m, winner))) + collisions.Add(new OperationCollision(winner, loser, isAmbiguous)); + } + return (selected, collisions); + } + + /// + /// Methods that do not take part in generated dispatch because another + /// method shares their operation name. + /// + public static HashSet GetExcludedFromDispatch(List collisions) + { + var result = new HashSet(ReferenceEqualityComparer.Instance); + foreach (var collision in collisions) + { + result.Add(collision.Loser); + if (collision.IsAmbiguous) + result.Add(collision.Winner); + } + return result; + } + + private static Dictionary GetInterfaceMemberNames(EquatableArray methods) + { + var result = new Dictionary(ReferenceEqualityComparer.Instance); + var signatures = new HashSet(); + foreach (var method in methods) + { + var parameterTypes = string.Join(",", method.Parameters.Select(p => p.RefKindPrefix + p.PatternTypeDisplay + (p.IsNullableValueType ? "?" : string.Empty))); + var name = method.OperationName; + var suffix = 1; + while (!signatures.Add($"{name}({parameterTypes})")) + { + suffix++; + name = $"{method.OperationName}_{suffix}"; + } + result[method] = name; + } + return result; + } + + private static void AppendOperationsInterface(IndentedTextWriter writer, OperationTypeModel model, Dictionary memberNames) + { + writer.WriteLine("/// "); + writer.WriteLine("/// Data portal operation methods declared by this type."); + writer.WriteLine("/// Generated by CSLA .NET so the methods are statically referenced."); + writer.WriteLine("/// "); + writer.WriteLine("[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]"); + var modifiers = model.Type.HidesInheritedOperationsInterface ? "internal new interface" : "internal interface"; + writer.WriteLine($"{modifiers} {OperationDiscovery.OperationsInterfaceName}"); + OpenBlock(writer); + foreach (var method in model.Methods) + writer.WriteLine($"{method.ReturnTypeDisplay} {memberNames[method]}({FormatParameters(method)});"); + CloseBlock(writer); + } + + private static void AppendExplicitImplementations(IndentedTextWriter writer, OperationTypeModel model, Dictionary memberNames) + { + foreach (var method in model.Methods) + { + var arguments = string.Join(", ", method.Parameters.Select(p => ArgumentPrefix(p) + p.Name)); + writer.WriteLine($"{method.ReturnTypeDisplay} {OperationDiscovery.OperationsInterfaceName}.{memberNames[method]}({FormatParameters(method)}) => {method.MethodName}({arguments});"); + } + } + + private static void AppendInvokeOperationAsync(IndentedTextWriter writer, OperationTypeModel model, Dictionary memberNames) + { + writer.WriteLine("async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync("); + writer.Indent++; + writer.WriteLine("global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider)"); + writer.Indent--; + OpenBlock(writer); + + // Methods sharing an operation name differ only in injected parameters; + // only the one the runtime would select takes part in dispatch. + var (_, collisions) = SelectNamedDispatchMethods(model.Methods); + var excluded = GetExcludedFromDispatch(collisions); + var dispatchable = model.Methods.Where(m => m.CanDispatch && !excluded.Contains(m)).ToList(); + if (dispatchable.Count > 0) + writer.WriteLine($"var {OperationsLocal} = ({OperationDiscovery.OperationsInterfaceName})this;"); + + var candidateIndex = 0; + var isFirst = true; + foreach (var kind in OperationDiscovery.OperationKinds) + { + var methods = dispatchable.Where(m => m.Kind == kind).ToList(); + if (methods.Count == 0) + continue; + + writer.WriteLine($"{(isFirst ? "if" : "else if")} (operationType == typeof(global::Csla.{kind}Attribute))"); + isFirst = false; + OpenBlock(writer); + foreach (var method in methods) + { + // When several methods of this kind take the same number of criteria, + // require exact runtime types so a less specific overload declared + // first cannot capture the call; anything else falls back to reflection. + var requireExactTypes = methods.Count(m => m.CriteriaCount == method.CriteriaCount) > 1; + AppendDispatch(writer, method, memberNames[method], candidateIndex++, byName: false, requireExactTypes); + } + CloseBlock(writer); + } + + writer.WriteLine("throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria);"); + CloseBlock(writer); + } + + private static void AppendInvokeNamedOperationAsync(IndentedTextWriter writer, OperationTypeModel model, Dictionary memberNames) + { + writer.WriteLine("async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync("); + writer.Indent++; + writer.WriteLine("string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider)"); + writer.Indent--; + OpenBlock(writer); + + var (selected, _) = SelectNamedDispatchMethods(model.Methods); + if (selected.Count > 0) + { + writer.WriteLine($"var {OperationsLocal} = ({OperationDiscovery.OperationsInterfaceName})this;"); + writer.WriteLine("switch (operationName)"); + OpenBlock(writer); + var candidateIndex = 0; + foreach (var method in selected) + { + writer.WriteLine($"case \"{method.OperationName}\":"); + writer.Indent++; + AppendDispatch(writer, method, memberNames[method], candidateIndex++, byName: true, requireExactTypes: false); + writer.WriteLine("break;"); + writer.Indent--; + } + CloseBlock(writer); + } + + writer.WriteLine("throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria);"); + CloseBlock(writer); + } + + private static void AppendDispatch(IndentedTextWriter writer, OperationMethodModel method, string memberName, int candidateIndex, bool byName, bool requireExactTypes) + { + var criteria = method.CriteriaParameters.ToList(); + var conditions = new List(); + if (criteria.Count == 0) + { + conditions.Add("criteria is null or { Length: 0 }"); + } + else + { + conditions.Add($"criteria is {{ Length: {criteria.Count} }}"); + for (var i = 0; i < criteria.Count; i++) + { + var parameter = criteria[i]; + var variable = CriteriaVariable(candidateIndex, i); + if (byName && parameter.AcceptsNull) + { + conditions.Add($"(criteria[{i}] is null || criteria[{i}] is {parameter.PatternTypeDisplay})"); + } + else + { + conditions.Add($"criteria[{i}] is {parameter.PatternTypeDisplay} {variable}"); + if (requireExactTypes && !parameter.IsExactTypeMatch) + conditions.Add($"{variable}.GetType() == typeof({parameter.PatternTypeDisplay})"); + } + } + } + + writer.WriteLine($"if ({string.Join(" && ", conditions)})"); + OpenBlock(writer); + + if (method.IsAsync) + writer.WriteLine($"{HelperType}.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, \"{method.MethodName.TrimStart('@')}\");"); + + // Like reflection-based dispatch, exceptions thrown while resolving + // injected services or by the operation method are wrapped in + // CallMethodException. This also keeps an operation method throwing + // DataPortalOperationNotSupportedException from being treated as an + // unmatched operation and invoked again through reflection. + writer.WriteLine("try"); + OpenBlock(writer); + + var arguments = new List(); + var criteriaIndex = 0; + var injectIndex = 0; + foreach (var parameter in method.Parameters) + { + if (parameter.IsInjected) + { + var variable = $"__c{candidateIndex}i{injectIndex++}"; + writer.WriteLine($"var {variable} = {FormatServiceResolution(parameter)};"); + arguments.Add(ArgumentPrefix(parameter) + variable); + } + else + { + var i = criteriaIndex++; + string argument; + if (byName && parameter.AcceptsNull) + { + argument = parameter.IsNullableValueType + ? $"({parameter.TypeDisplay})criteria[{i}]" + : $"({parameter.TypeDisplay})criteria[{i}]!"; + // An explicit 'in' argument must be a variable, not a cast expression. + if (parameter.RefKindPrefix == "in ") + { + var variable = CriteriaVariable(candidateIndex, i); + writer.WriteLine($"var {variable} = {argument};"); + argument = variable; + } + } + else + { + argument = CriteriaVariable(candidateIndex, i); + } + arguments.Add(ArgumentPrefix(parameter) + argument); + } + } + + var call = $"{OperationsLocal}.{memberName}({string.Join(", ", arguments)})"; + writer.WriteLine(method.IsAsync ? $"await {call}.ConfigureAwait(false);" : $"{call};"); + CloseBlock(writer); + writer.WriteLine($"catch (global::System.Exception {ExceptionLocal})"); + OpenBlock(writer); + writer.WriteLine($"throw {HelperType}.CreateCallMethodException(this, \"{method.MethodName.TrimStart('@')}\", {ExceptionLocal});"); + CloseBlock(writer); + writer.WriteLine("return;"); + CloseBlock(writer); + } + + private static string FormatServiceResolution(OperationParameterModel parameter) + { + // Nullable services are registered and resolved as Nullable, as in reflection-based dispatch. + var serviceType = parameter.IsNullableValueType + ? $"typeof({parameter.PatternTypeDisplay}?)" + : $"typeof({parameter.PatternTypeDisplay})"; + // Same resolution calls as reflection-based dispatch. + var resolution = (parameter.ServiceKeyExpression, parameter.AllowNull) switch + { + (null, true) => $"serviceProvider.GetService({serviceType})", + (null, false) => $"{ServiceProviderExtensionsType}.GetRequiredService(serviceProvider, {serviceType})", + (var key, true) => $"{KeyedServiceProviderExtensionsType}.GetKeyedService(serviceProvider, {serviceType}, {key})", + (var key, false) => $"{KeyedServiceProviderExtensionsType}.GetRequiredKeyedService(serviceProvider, {serviceType}, {key})", + }; + return parameter.AllowNull + ? $"({parameter.TypeDisplay})({resolution})" + : $"({parameter.TypeDisplay})({resolution})!"; + } + + private static string CriteriaVariable(int candidateIndex, int criteriaIndex) => $"__c{candidateIndex}p{criteriaIndex}"; + + private static string FormatParameters(OperationMethodModel method) + => string.Join(", ", method.Parameters.Select(p => $"{p.RefKindPrefix}{p.TypeDisplay} {p.Name}")); + + private static string ArgumentPrefix(OperationParameterModel parameter) => parameter.RefKindPrefix; + + private static void OpenBlock(IndentedTextWriter writer) + { + writer.WriteLine("{"); + writer.Indent++; + } + + private static void CloseBlock(IndentedTextWriter writer) + { + writer.Indent--; + writer.WriteLine("}"); + } + + internal sealed class ReferenceEqualityComparer : IEqualityComparer + { + public static readonly ReferenceEqualityComparer Instance = new(); + + public bool Equals(OperationMethodModel? x, OperationMethodModel? y) => ReferenceEquals(x, y); + + public int GetHashCode(OperationMethodModel obj) => System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj); + } + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsDiagnostics.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsDiagnostics.cs new file mode 100644 index 0000000000..2f4befba85 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/DataPortalOperationsDiagnostics.cs @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Diagnostics reported by the data portal analyzers +//----------------------------------------------------------------------- + +using Microsoft.CodeAnalysis; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations +{ + /// + /// Diagnostic descriptors reported by the data portal operations and data + /// portal extensions analyzers, which apply the same rules as the generators. + /// The generators themselves report no diagnostics. + /// + internal static class DataPortalOperationsDiagnostics + { + private const string Category = "Csla.DataPortal"; + + private static string HelpLink(string id, string name) => + $"https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/{id}-{name}.md"; + + public const string DuplicateOperationNameId = "CSLADP002"; + public const string GenericExtensionsNotGeneratedId = "CSLADP003"; + public const string InvalidExtensionsTargetId = "CSLADP004"; + public const string ExtensionNameHiddenId = "CSLADP005"; + public const string InaccessibleParameterTypeId = "CSLADP006"; + public const string DuplicateExtensionMethodId = "CSLADP007"; + public const string InvalidExtensionPrefixId = "CSLADP008"; + public const string AmbiguousOperationNameId = "CSLADP009"; + public const string InvalidAsyncSuffixId = "CSLADP010"; + public const string SyncExtensionsNotGeneratedId = "CSLADP011"; + + public static readonly DiagnosticDescriptor DuplicateOperationName = new( + id: DuplicateOperationNameId, + title: "Data portal operation methods share an operation name", + messageFormat: "Methods '{0}' and '{1}' on '{2}' both map to data portal operation '{3}'; name-based dispatch uses '{0}'", + category: Category, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + helpLinkUri: HelpLink(DuplicateOperationNameId, nameof(DuplicateOperationName))); + + public static readonly DiagnosticDescriptor GenericExtensionsNotGenerated = new( + id: GenericExtensionsNotGeneratedId, + title: "Data portal extensions are not generated for generic types", + messageFormat: "Data portal extension methods are not generated for '{0}' because it is generic or nested in a generic type", + category: Category, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + helpLinkUri: HelpLink(GenericExtensionsNotGeneratedId, nameof(GenericExtensionsNotGenerated))); + + public static readonly DiagnosticDescriptor InvalidExtensionsTarget = new( + id: InvalidExtensionsTargetId, + title: "Invalid target for DataPortalExtensions", + messageFormat: "Data portal extension methods are not generated for '{0}': {1}", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(InvalidExtensionsTargetId, nameof(InvalidExtensionsTarget))); + + public static readonly DiagnosticDescriptor ExtensionNameHidden = new( + id: ExtensionNameHiddenId, + title: "Generated data portal extension would be hidden", + messageFormat: "The data portal extension method '{0}' for '{1}' is not generated because it would be hidden by the '{2}' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(ExtensionNameHiddenId, nameof(ExtensionNameHidden))); + + public static readonly DiagnosticDescriptor InaccessibleParameterType = new( + id: InaccessibleParameterTypeId, + title: "Operation parameter type is not accessible", + messageFormat: "The data portal extension method for '{0}' cannot be generated because parameter '{1}' has type '{2}', which is not accessible outside its containing type", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(InaccessibleParameterTypeId, nameof(InaccessibleParameterType))); + + public static readonly DiagnosticDescriptor DuplicateExtensionMethod = new( + id: DuplicateExtensionMethodId, + title: "Generated data portal extension would be duplicated", + messageFormat: "The data portal extension method '{0}' for '{1}' is not generated for '{2}' because another operation method already produces an extension method with the same signature", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(DuplicateExtensionMethodId, nameof(DuplicateExtensionMethod))); + + public static readonly DiagnosticDescriptor InvalidExtensionPrefix = new( + id: InvalidExtensionPrefixId, + title: "Invalid DataPortalExtensions prefix", + messageFormat: "Data portal extension methods are not generated for '{0}' because the prefix '{1}' is not a valid C# identifier", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(InvalidExtensionPrefixId, nameof(InvalidExtensionPrefix))); + + public static readonly DiagnosticDescriptor AmbiguousOperationName = new( + id: AmbiguousOperationNameId, + title: "Data portal operation methods are ambiguous", + messageFormat: "Methods '{0}' and '{1}' on '{2}' both map to data portal operation '{3}' with the same number of injected parameters; the data portal cannot choose between them and reports an ambiguous match", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(AmbiguousOperationNameId, nameof(AmbiguousOperationName))); + + public static readonly DiagnosticDescriptor InvalidAsyncSuffix = new( + id: InvalidAsyncSuffixId, + title: "Invalid data portal extensions async suffix", + messageFormat: "The CslaDataPortalExtensionsAsyncSuffix value '{0}' is not valid in a C# identifier; generated async data portal extension methods use the 'Async' suffix", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(InvalidAsyncSuffixId, nameof(InvalidAsyncSuffix)), + customTags: WellKnownDiagnosticTags.CompilationEnd); + + public static readonly DiagnosticDescriptor SyncExtensionsNotGenerated = new( + id: SyncExtensionsNotGeneratedId, + title: "Synchronous data portal extensions are not generated without an async suffix", + messageFormat: "Synchronous data portal extension methods are not generated because CslaDataPortalExtensionsAsyncSuffix is 'none', so they would have the same names as the async methods; set CslaGenerateSyncDataPortalExtensions to false", + category: Category, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: HelpLink(SyncExtensionsNotGeneratedId, nameof(SyncExtensionsNotGenerated)), + customTags: WellKnownDiagnosticTags.CompilationEnd); + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/IncrementalDataPortalOperationsGenerator.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/IncrementalDataPortalOperationsGenerator.cs new file mode 100644 index 0000000000..9a7cf5be9d --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/IncrementalDataPortalOperationsGenerator.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Source generator for data portal operation interfaces and dispatch +//----------------------------------------------------------------------- + +using System.Collections.Immutable; +using System.Text; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations +{ + /// + /// Incremental source generator that, for every partial class declaring + /// data portal operation methods, generates a nested internal + /// IDataPortalOperations interface naming each operation method and, + /// for concrete classes, implementations of IDataPortalOperationMapping + /// and IDataPortalOperationNamedMapping. + /// + [Generator(LanguageNames.CSharp)] + public class IncrementalDataPortalOperationsGenerator : IIncrementalGenerator + { + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var types = GetOperationTypes(context) + .SelectMany(static (types, _) => types) + .Where(static t => t.Type.IsPartial) + .WithTrackingName(TrackingNames.OperationTypes); + + // Diagnostics for these types are reported by DataPortalOperationsAnalyzer. + context.RegisterSourceOutput(types, static (spc, model) => + { + spc.AddSource( + $"{model.Type.HintName}.DataPortalOperations.g.cs", + SourceText.From(DataPortalOperationsBuilder.Build(model), Encoding.UTF8)); + }); + } + + /// + /// Discovers every type declaring data portal operation methods, grouped + /// by type so a partial class spread across several files yields one model. + /// + internal static IncrementalValueProvider> GetOperationTypes(IncrementalGeneratorInitializationContext context) + { + // A compilation without operation methods yields an empty array, not null. + var kinds = OperationDiscovery.OperationKinds; + var all = GetOperationMethods(context, kinds[0]); + for (var i = 1; i < kinds.Length; i++) + { + all = all.Combine(GetOperationMethods(context, kinds[i])) + .Select(static (pair, _) => pair.Left.AddRange(pair.Right)); + } + + return all + .Select(static (entries, ct) => OperationDiscovery.GroupByType(entries, ct)) + .WithTrackingName(TrackingNames.GroupOperationTypes); + } + + private static IncrementalValueProvider> GetOperationMethods(IncrementalGeneratorInitializationContext context, string kind) + => context.SyntaxProvider.ForAttributeWithMetadataName( + OperationDiscovery.GetAttributeMetadataName(kind), + predicate: static (node, _) => node is MethodDeclarationSyntax, + transform: (ctx, ct) => OperationDiscovery.ExtractMethod(ctx, kind, ct)) + .Where(static e => e is not null) + .Select(static (e, _) => e!) + .WithTrackingName(TrackingNames.ExtractOperationMethods) + .Collect(); + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/LocationInfo.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/LocationInfo.cs new file mode 100644 index 0000000000..a67aa83839 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/LocationInfo.cs @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Equatable source location for operation models and diagnostics +//----------------------------------------------------------------------- + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models +{ + /// + /// An equatable representation of a source location, safe to keep in + /// incremental generator models (unlike ). + /// + internal sealed record LocationInfo(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan) + { + /// + /// The location in the compilation's syntax tree for the file, so the + /// diagnostic can be suppressed in source; otherwise a file location. + /// + public Location ToLocation(Compilation compilation) + { + foreach (var tree in compilation.SyntaxTrees) + { + if (tree.FilePath == FilePath) + return Location.Create(tree, TextSpan); + } + return Location.Create(FilePath, TextSpan, LineSpan); + } + + public static LocationInfo? From(Location? location) + { + if (location?.SourceTree is null) + return null; + return new LocationInfo(location.SourceTree.FilePath, location.SourceSpan, location.GetLineSpan().Span); + } + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationMethodModel.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationMethodModel.cs new file mode 100644 index 0000000000..cfffac2c03 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationMethodModel.cs @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// A data portal operation method for one operation attribute +//----------------------------------------------------------------------- + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models +{ + /// + /// Two operation methods that share an operation name. When + /// is true the runtime cannot choose between + /// the methods, and is only the first declared. + /// + internal sealed record OperationCollision(OperationMethodModel Winner, OperationMethodModel Loser, bool IsAmbiguous); + + /// + /// A data portal operation method as seen through one operation + /// attribute. A method carrying several operation attributes + /// yields one model per attribute. + /// + internal sealed record OperationMethodModel + { + public required string MethodName { get; init; } + + /// Method signature for diagnostics, e.g. "Fetch(int)". + public string MethodDisplay { get; init; } = string.Empty; + + /// Operation kind, e.g. "Fetch" or "FetchChild". + public required string Kind { get; init; } + + /// Fully qualified return type, e.g. "void". + public required string ReturnTypeDisplay { get; init; } + + /// Returns Task, Task<T>, ValueTask or ValueTask<T>. + public bool IsAsync { get; init; } + + /// All parameters, criteria and injected, in declaration order. + public required EquatableArray Parameters { get; init; } + + /// Deterministic operation name, e.g. "Fetch__Int32". + public required string OperationName { get; init; } + + public bool RunLocal { get; init; } + + public bool NoExtension { get; init; } + + public LocationInfo? Location { get; init; } + + public IEnumerable CriteriaParameters => Parameters.Where(p => !p.IsInjected); + + public int CriteriaCount => Parameters.Count(p => !p.IsInjected); + + public int InjectCount => Parameters.Count(p => p.IsInjected); + + /// + /// The method can be invoked from generated dispatch code. Methods + /// taking a single object[] (which receive the whole criteria array) + /// or ref/out parameters are left to the reflection-based path. + /// + public bool CanDispatch => + !Parameters.Any(p => p.RefKindPrefix is "ref " or "out ") + && !(Parameters.Count == 1 && Parameters[0].IsObjectArray); + + /// + /// The method can take part in name-based dispatch. When criteria + /// reference a type parameter the generated name cannot match the + /// runtime-computed name. + /// + public bool CanDispatchByName => CanDispatch && !CriteriaParameters.Any(p => p.ContainsTypeParameter); + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationParameterModel.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationParameterModel.cs new file mode 100644 index 0000000000..5bbc6fef40 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationParameterModel.cs @@ -0,0 +1,75 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// A parameter of a data portal operation method +//----------------------------------------------------------------------- + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models +{ + /// + /// Effective accessibility of a parameter type, as seen from + /// a top-level type in the same assembly. + /// + internal enum TypeVisibility + { + Public, + Internal, + Private + } + + /// + /// A parameter of a data portal operation method. + /// + internal sealed record OperationParameterModel + { + /// The parameter name, escaped with @ when it is a keyword. + public required string Name { get; init; } + + /// Fully qualified type including nullable annotation. + public required string TypeDisplay { get; init; } + + /// + /// Type usable in an is pattern: nullable annotation removed and + /// unwrapped. + /// + public required string PatternTypeDisplay { get; init; } + + /// Type key used to compute the operation name. + public required string TypeKey { get; init; } + + /// "ref ", "out ", "in " or empty. + public string RefKindPrefix { get; init; } = string.Empty; + + public bool IsNullableValueType { get; init; } + + /// The runtime value may legitimately be null (reference type or Nullable). + public bool AcceptsNull { get; init; } + + /// + /// An is pattern on this type can only match that exact runtime + /// type (value types and sealed classes). + /// + public bool IsExactTypeMatch { get; init; } + + public bool ContainsTypeParameter { get; init; } + + public bool IsParams { get; init; } + + /// True when the type is exactly object[] (a CSLA params-array operation). + public bool IsObjectArray { get; init; } + + public bool IsInjected { get; init; } + + public bool AllowNull { get; init; } + + /// C# expression for the service key of a keyed [Inject], or null. + public string? ServiceKeyExpression { get; init; } + + /// C# expression for the default value (without "= "), or null. + public string? DefaultValueExpression { get; init; } + + public TypeVisibility Visibility { get; init; } + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationTypeModel.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationTypeModel.cs new file mode 100644 index 0000000000..59ab46b958 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/Models/OperationTypeModel.cs @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// A type that declares data portal operation methods +//----------------------------------------------------------------------- + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models +{ + /// + /// Type-level information about a class declaring data portal + /// operation methods. Computed independently for each method so a + /// partial class spread across files is grouped by . + /// + internal sealed record OperationTypeHeader + { + /// Fully qualified metadata name, used as the grouping key. + public required string MetadataName { get; init; } + + /// Namespace, or empty for the global namespace. + public required string Namespace { get; init; } + + /// Declarations of containing types, outermost first, e.g. "partial class Outer". + public required EquatableArray ContainerDeclarations { get; init; } + + /// Declaration of the type, e.g. "partial class PersonEdit<T>". + public required string TypeDeclaration { get; init; } + + /// Fully qualified type name with type parameters, e.g. "global::NS.PersonEdit". + public required string FullyQualifiedName { get; init; } + + /// Simple type name without type parameters. + public required string TypeName { get; init; } + + /// Containing type names, outermost first. + public required EquatableArray ContainerNames { get; init; } + + /// File name friendly unique name, e.g. "NS.Outer.PersonEdit_1". + public required string HintName { get; init; } + + /// The type and all containing types are declared partial. + public bool IsPartial { get; init; } + + public bool IsAbstract { get; init; } + + /// The type or a containing type is generic. + public bool IsGeneric { get; init; } + + /// An accessible base type already exposes a nested IDataPortalOperations. + public bool HidesInheritedOperationsInterface { get; init; } + + public bool ImplementsOperationMapping { get; init; } + + public bool ImplementsNamedOperationMapping { get; init; } + + public bool IsRecord { get; init; } + + /// The type implements Csla.Core.ICslaObject. + public bool IsCslaObject { get; init; } + + public TypeVisibility Visibility { get; init; } + + /// The type has the DataPortalExtensions attribute. + public bool HasExtensionsAttribute { get; init; } + + /// The Prefix set by the type's DataPortalExtensions attribute, or null when not set. + public string? ExtensionsPrefix { get; init; } + + /// The type has the NoDataPortalExtension attribute. + public bool NoExtensions { get; init; } + } + + /// + /// One discovered operation method together with its type header. + /// + internal sealed record OperationMethodEntry(OperationTypeHeader Type, OperationMethodModel Method); + + /// + /// A type and all of its data portal operation methods. + /// + internal sealed record OperationTypeModel(OperationTypeHeader Type, EquatableArray Methods); +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/OperationDiscovery.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/OperationDiscovery.cs new file mode 100644 index 0000000000..105d6e6f4f --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/OperationDiscovery.cs @@ -0,0 +1,551 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Extracts data portal operation models from Roslyn symbols +//----------------------------------------------------------------------- + +using System.Globalization; +using System.Text; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations.Models; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations +{ + /// + /// Extracts equatable data portal operation models from Roslyn symbols. + /// + internal static class OperationDiscovery + { + /// + /// The data portal operation kinds, in the order used for generated dispatch. + /// + public static readonly string[] OperationKinds = + [ + "Create", + "Fetch", + "Insert", + "Update", + "Execute", + "Delete", + "DeleteSelf", + "CreateChild", + "FetchChild", + "InsertChild", + "UpdateChild", + "DeleteSelfChild", + "ExecuteChild" + ]; + + public const string InjectAttributeName = "Csla.InjectAttribute"; + public const string RunLocalAttributeName = "Csla.RunLocalAttribute"; + public const string NoDataPortalExtensionAttributeName = "Csla.NoDataPortalExtensionAttribute"; + public const string DataPortalExtensionsAttributeName = "Csla.DataPortalExtensionsAttribute"; + public const string CslaObjectInterfaceName = "Csla.Core.ICslaObject"; + public const string OperationsInterfaceName = "IDataPortalOperations"; + + private const string OperationMappingName = "Csla.Server.IDataPortalOperationMapping"; + private const string NamedOperationMappingName = "Csla.Server.IDataPortalOperationNamedMapping"; + + /// + /// Fully qualified type with nullable reference annotations. + /// + internal static readonly SymbolDisplayFormat TypeFormat = SymbolDisplayFormat.FullyQualifiedFormat + .AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + + /// + /// Fully qualified type without nullable reference annotations, with tuples + /// expanded so the name can be used in an is pattern. + /// + internal static readonly SymbolDisplayFormat PatternFormat = SymbolDisplayFormat.FullyQualifiedFormat + .AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.ExpandValueTuple); + + private static readonly SymbolDisplayFormat MethodDisplayFormat = new( + memberOptions: SymbolDisplayMemberOptions.IncludeParameters, + parameterOptions: SymbolDisplayParameterOptions.IncludeType, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + public static string GetAttributeMetadataName(string kind) => $"Csla.{kind}Attribute"; + + /// + /// Extract the model for one operation method, as seen through one operation attribute. + /// + public static OperationMethodEntry? ExtractMethod(GeneratorAttributeSyntaxContext context, string kind, CancellationToken ct) + { + if (context.TargetSymbol is not IMethodSymbol method || !IsCandidateMethod(method)) + return null; + + var type = method.ContainingType; + if (type is null || type.TypeKind != TypeKind.Class) + return null; + + ct.ThrowIfCancellationRequested(); + + var compilation = context.SemanticModel.Compilation; + var header = BuildHeader(type, context.TargetNode, compilation); + var model = BuildMethod(method, kind); + return new OperationMethodEntry(header, model); + } + + /// + /// Build the model for a type's operation methods from its symbol, as + /// the generator pipeline does from the attributed method declarations. + /// Used by the analyzers. Returns null when the type declares no + /// operation methods. + /// + /// The type to inspect. + /// The operation attribute types, mapped to their operation kind. + /// The compilation containing the type. + /// Cancellation token. + public static OperationTypeModel? GetOperationType(INamedTypeSymbol type, IReadOnlyDictionary attributeKinds, Compilation compilation, CancellationToken ct) + { + if (type.TypeKind != TypeKind.Class) + return null; + + OperationTypeHeader? header = null; + var entries = new List(); + foreach (var method in type.GetMembers().OfType()) + { + if (!IsCandidateMethod(method)) + continue; + + foreach (var attribute in method.GetAttributes()) + { + if (attribute.AttributeClass is null || !attributeKinds.TryGetValue(attribute.AttributeClass, out var kind)) + continue; + + var node = method.DeclaringSyntaxReferences + .Select(r => r.GetSyntax(ct)) + .OfType() + .FirstOrDefault(n => n.AttributeLists.SelectMany(l => l.Attributes).Any()); + if (node is null) + continue; + + ct.ThrowIfCancellationRequested(); + header ??= BuildHeader(type, node, compilation); + entries.Add(new OperationMethodEntry(header with { IsPartial = IsPartialAllTheWay(node) }, BuildMethod(method, kind))); + } + } + + return entries.Count == 0 ? null : GroupByType(entries, ct)[0]; + } + + /// + /// Build the type header for a type that may declare no operation + /// methods. Used by the analyzers. Returns null when the type has no + /// source declaration. + /// + public static OperationTypeHeader? BuildTypeHeader(INamedTypeSymbol type, Compilation compilation, CancellationToken ct) + { + var nodes = type.DeclaringSyntaxReferences.Select(r => r.GetSyntax(ct)).ToList(); + if (nodes.Count == 0) + return null; + return BuildHeader(type, nodes[0], compilation) with { IsPartial = nodes.All(IsPartialAllTheWay) }; + } + + /// + /// Groups discovered operation methods by type so a partial class + /// spread across several files yields one model. + /// + public static EquatableArray GroupByType(IEnumerable entries, CancellationToken ct) + { + var result = new List(); + foreach (var group in entries.GroupBy(e => e.Type.MetadataName).OrderBy(g => g.Key, StringComparer.Ordinal)) + { + ct.ThrowIfCancellationRequested(); + var header = group.First().Type with { IsPartial = group.All(e => e.Type.IsPartial) }; + var methods = group + .Select(e => e.Method) + .OrderBy(m => m.Location?.FilePath ?? string.Empty, StringComparer.Ordinal) + .ThenBy(m => m.Location?.TextSpan.Start ?? 0) + .ThenBy(m => Array.IndexOf(OperationKinds, m.Kind)); + result.Add(new OperationTypeModel(header, new EquatableArray(methods))); + } + return new EquatableArray(result); + } + + /// + /// A method that the generator includes in the operations interface. + /// + internal static bool IsCandidateMethod(IMethodSymbol method) + => method.MethodKind == MethodKind.Ordinary && !method.IsStatic && !method.IsGenericMethod; + + private static OperationTypeHeader BuildHeader(INamedTypeSymbol type, SyntaxNode methodNode, Compilation compilation) + { + var containers = new List(); + for (var container = type.ContainingType; container is not null; container = container.ContainingType) + containers.Insert(0, container); + + var hintParts = new List(); + var @namespace = type.ContainingNamespace is { IsGlobalNamespace: false } ns ? ns.ToDisplayString() : string.Empty; + if (@namespace.Length > 0) + hintParts.Add(@namespace); + foreach (var container in containers) + hintParts.Add(GetHintSegment(container)); + hintParts.Add(GetHintSegment(type)); + + var isGeneric = type.TypeParameters.Length > 0 || containers.Any(c => c.TypeParameters.Length > 0); + var extensionsAttribute = type.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == DataPortalExtensionsAttributeName); + + return new OperationTypeHeader + { + MetadataName = GetFullMetadataName(type), + Namespace = @namespace, + ContainerDeclarations = new EquatableArray(containers.Select(GetPartialDeclaration)), + ContainerNames = new EquatableArray(containers.Select(c => c.Name)), + TypeDeclaration = GetPartialDeclaration(type), + FullyQualifiedName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + TypeName = type.Name, + HintName = string.Join(".", hintParts), + IsPartial = IsPartialAllTheWay(methodNode), + IsAbstract = type.IsAbstract, + IsGeneric = isGeneric, + HidesInheritedOperationsInterface = HidesInheritedOperationsInterface(type, compilation), + ImplementsOperationMapping = type.Interfaces.Any(i => i.ToDisplayString() == OperationMappingName), + ImplementsNamedOperationMapping = type.Interfaces.Any(i => i.ToDisplayString() == NamedOperationMappingName), + IsRecord = type.IsRecord, + IsCslaObject = type.AllInterfaces.Any(i => i.ToDisplayString() == CslaObjectInterfaceName), + Visibility = GetVisibility(type), + HasExtensionsAttribute = extensionsAttribute is not null, + ExtensionsPrefix = GetPrefix(extensionsAttribute), + NoExtensions = HasAttribute(type, NoDataPortalExtensionAttributeName) + }; + } + + /// + /// The Prefix set by a DataPortalExtensions attribute, or null when it is not set. + /// + internal static string? GetPrefix(AttributeData? extensionsAttribute) + { + if (extensionsAttribute is null) + return null; + foreach (var namedArgument in extensionsAttribute.NamedArguments) + { + if (namedArgument.Key == "Prefix") + return namedArgument.Value.Value as string ?? string.Empty; + } + return null; + } + + private static OperationMethodModel BuildMethod(IMethodSymbol method, string kind) + { + var parameters = method.Parameters.Select(BuildParameter).ToArray(); + var criteriaKeys = parameters.Where(p => !p.IsInjected).Select(p => p.TypeKey).ToArray(); + var operationName = criteriaKeys.Length == 0 ? kind : kind + "__" + string.Join("_", criteriaKeys); + + return new OperationMethodModel + { + MethodName = EscapeIdentifier(method.Name), + MethodDisplay = method.ToDisplayString(MethodDisplayFormat), + Kind = kind, + ReturnTypeDisplay = method.ReturnType.ToDisplayString(TypeFormat), + IsAsync = IsAwaitable(method.ReturnType), + Parameters = new EquatableArray(parameters), + OperationName = operationName, + RunLocal = HasAttribute(method, RunLocalAttributeName), + NoExtension = HasAttribute(method, NoDataPortalExtensionAttributeName), + Location = LocationInfo.From(method.Locations.FirstOrDefault()) + }; + } + + private static OperationParameterModel BuildParameter(IParameterSymbol parameter) + { + var type = parameter.Type; + var isNullableValueType = type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T }; + var patternType = isNullableValueType ? ((INamedTypeSymbol)type).TypeArguments[0] : type; + + var isInjected = false; + var allowNull = false; + string? serviceKey = null; + foreach (var attribute in parameter.GetAttributes()) + { + if (!IsOrDerivesFrom(attribute.AttributeClass, InjectAttributeName)) + continue; + isInjected = true; + foreach (var namedArgument in attribute.NamedArguments) + { + if (namedArgument.Key == "AllowNull" && namedArgument.Value.Value is bool value) + allowNull = value; + else if (namedArgument.Key == "Key" && !namedArgument.Value.IsNull) + serviceKey = FormatTypedConstant(namedArgument.Value); + } + } + + // Like reflection-based dispatch, a nullable parameter type makes the service optional. + if (isInjected && (isNullableValueType || (type.IsReferenceType && parameter.NullableAnnotation == NullableAnnotation.Annotated))) + allowNull = true; + + return new OperationParameterModel + { + Name = EscapeIdentifier(parameter.Name), + TypeDisplay = type.ToDisplayString(TypeFormat), + PatternTypeDisplay = patternType.ToDisplayString(PatternFormat), + TypeKey = GetOperationTypeKey(type), + RefKindPrefix = parameter.RefKind switch + { + RefKind.Ref => "ref ", + RefKind.Out => "out ", + RefKind.In => "in ", + _ => string.Empty + }, + IsNullableValueType = isNullableValueType, + AcceptsNull = type.IsReferenceType || isNullableValueType, + IsExactTypeMatch = patternType.IsValueType || (patternType.IsSealed && patternType.TypeKind != TypeKind.Array), + ContainsTypeParameter = ContainsTypeParameter(type), + IsParams = parameter.IsParams, + IsObjectArray = type is IArrayTypeSymbol { Rank: 1, ElementType.SpecialType: SpecialType.System_Object }, + IsInjected = isInjected, + AllowNull = allowNull, + ServiceKeyExpression = serviceKey, + DefaultValueExpression = parameter.HasExplicitDefaultValue ? FormatDefaultValue(type, parameter.ExplicitDefaultValue) : null, + Visibility = GetVisibility(type) + }; + } + + /// + /// Computes a deterministic type key for use in operation names. + /// Must produce the same result as DataPortalOperationNameHelper.GetTypeKey(Type) at runtime. + /// Arrays: elementType + "Array" (e.g. "Int32Array"). + /// Generic types: MetadataName with backtick replaced, then all type + /// arguments including those of containing types (e.g. "List_1_Int32"). + /// Other types: MetadataName (e.g. "Int32", "String"). + /// + internal static string GetOperationTypeKey(ITypeSymbol typeSymbol) + { + if (typeSymbol is IArrayTypeSymbol arrayType) + return GetOperationTypeKey(arrayType.ElementType) + "Array"; + + if (typeSymbol.TypeKind == TypeKind.Dynamic) + return "Object"; + + if (typeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType) + { + var typeArguments = new List(); + CollectTypeArguments(namedType, typeArguments); + var baseName = namedType.MetadataName.Replace('`', '_'); + return baseName + "_" + string.Join("_", typeArguments.Select(GetOperationTypeKey)); + } + + return typeSymbol.MetadataName; + } + + private static void CollectTypeArguments(INamedTypeSymbol type, List typeArguments) + { + if (type.ContainingType is not null) + CollectTypeArguments(type.ContainingType, typeArguments); + typeArguments.AddRange(type.TypeArguments); + } + + /// + /// Returns true when the type will expose a generated operations interface. + /// + internal static bool WillGenerateOperationsInterface(INamedTypeSymbol type) + { + if (type.TypeKind != TypeKind.Class) + return false; + + var hasOperations = type.GetMembers() + .OfType() + .Any(m => IsCandidateMethod(m) && m.GetAttributes().Any(a => IsOperationAttribute(a.AttributeClass))); + if (!hasOperations) + return false; + + return type.DeclaringSyntaxReferences.All(r => r.GetSyntax() is TypeDeclarationSyntax declaration && IsPartialAllTheWay(declaration)); + } + + internal static bool IsOperationAttribute(INamedTypeSymbol? attributeClass) + { + if (attributeClass is null || attributeClass.ContainingNamespace?.ToDisplayString() != "Csla") + return false; + var name = attributeClass.Name; + return name.EndsWith("Attribute") && OperationKinds.Contains(name.Substring(0, name.Length - "Attribute".Length)); + } + + private static bool HidesInheritedOperationsInterface(INamedTypeSymbol type, Compilation compilation) + { + for (var baseType = type.BaseType; baseType is not null && baseType.SpecialType != SpecialType.System_Object; baseType = baseType.BaseType) + { + var definition = baseType.OriginalDefinition; + if (definition.GetTypeMembers(OperationsInterfaceName).Any(t => compilation.IsSymbolAccessibleWithin(t, compilation.Assembly))) + return true; + if (SymbolEqualityComparer.Default.Equals(definition.ContainingAssembly, compilation.Assembly) && WillGenerateOperationsInterface(definition)) + return true; + } + return false; + } + + /// + /// The type declaration containing the node and all of its + /// containing type declarations have the partial modifier. + /// + internal static bool IsPartialAllTheWay(SyntaxNode node) + { + var found = false; + for (var current = node; current is not null; current = current.Parent) + { + if (current is TypeDeclarationSyntax typeDeclaration) + { + found = true; + if (!typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword)) + return false; + } + } + return found; + } + + private static string GetPartialDeclaration(INamedTypeSymbol type) + { + var keyword = type switch + { + { IsRecord: true, TypeKind: TypeKind.Struct } => "record struct", + { IsRecord: true } => "record", + { TypeKind: TypeKind.Struct } => "struct", + { TypeKind: TypeKind.Interface } => "interface", + _ => "class" + }; + var builder = new StringBuilder("partial ").Append(keyword).Append(' ').Append(EscapeIdentifier(type.Name)); + if (type.TypeParameters.Length > 0) + builder.Append('<').Append(string.Join(", ", type.TypeParameters.Select(t => EscapeIdentifier(t.Name)))).Append('>'); + return builder.ToString(); + } + + private static string GetHintSegment(INamedTypeSymbol type) + => type.TypeParameters.Length > 0 ? $"{type.Name}_{type.TypeParameters.Length}" : type.Name; + + internal static string GetFullMetadataName(INamedTypeSymbol type) + { + var name = type.MetadataName; + for (var container = type.ContainingType; container is not null; container = container.ContainingType) + name = container.MetadataName + "+" + name; + if (type.ContainingNamespace is { IsGlobalNamespace: false } ns) + name = ns.ToDisplayString() + "." + name; + return name; + } + + private static bool IsAwaitable(ITypeSymbol returnType) + { + if (returnType is not INamedTypeSymbol named) + return false; + var definition = named.OriginalDefinition; + if (definition.ContainingNamespace?.ToDisplayString() != "System.Threading.Tasks") + return false; + return definition.MetadataName is "Task" or "Task`1" or "ValueTask" or "ValueTask`1"; + } + + private static bool HasAttribute(ISymbol symbol, string attributeName) + => symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == attributeName); + + private static bool IsOrDerivesFrom(INamedTypeSymbol? type, string fullName) + { + for (var current = type; current is not null; current = current.BaseType) + { + if (current.ToDisplayString() == fullName) + return true; + } + return false; + } + + private static bool ContainsTypeParameter(ITypeSymbol type) => type switch + { + ITypeParameterSymbol => true, + IArrayTypeSymbol array => ContainsTypeParameter(array.ElementType), + INamedTypeSymbol named => named.TypeArguments.Any(ContainsTypeParameter) + || (named.ContainingType is not null && ContainsTypeParameter(named.ContainingType)), + _ => false + }; + + internal static TypeVisibility GetVisibility(ITypeSymbol type) + { + switch (type) + { + case IArrayTypeSymbol array: + return GetVisibility(array.ElementType); + case INamedTypeSymbol named: + var result = TypeVisibility.Public; + for (var current = named; current is not null; current = current.ContainingType) + result = Min(result, FromAccessibility(current.DeclaredAccessibility)); + foreach (var typeArgument in named.TypeArguments) + result = Min(result, GetVisibility(typeArgument)); + return result; + default: + return TypeVisibility.Public; + } + + static TypeVisibility Min(TypeVisibility left, TypeVisibility right) => (TypeVisibility)Math.Max((int)left, (int)right); + + static TypeVisibility FromAccessibility(Accessibility accessibility) => accessibility switch + { + Accessibility.Public => TypeVisibility.Public, + Accessibility.Internal or Accessibility.ProtectedOrInternal => TypeVisibility.Internal, + _ => TypeVisibility.Private + }; + } + + internal static string EscapeIdentifier(string identifier) + => SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None ? "@" + identifier : identifier; + + private static string FormatTypedConstant(TypedConstant constant) + { + if (constant.Kind == TypedConstantKind.Type && constant.Value is ITypeSymbol typeValue) + return $"typeof({typeValue.ToDisplayString(PatternFormat)})"; + if (constant.Kind == TypedConstantKind.Enum && constant.Type is ITypeSymbol enumType) + return FormatEnumValue(enumType, constant.Value); + return FormatPrimitive(constant.Value) ?? constant.ToCSharpString(); + } + + private static string FormatDefaultValue(ITypeSymbol type, object? value) + { + var isNullableValueType = type is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T }; + var targetType = isNullableValueType ? ((INamedTypeSymbol)type).TypeArguments[0] : type; + + if (value is null) + return type.IsReferenceType || isNullableValueType ? "null" : "default"; + + if (targetType.TypeKind == TypeKind.Enum) + return FormatEnumValue(targetType, value); + + return FormatPrimitive(value) ?? "default"; + } + + private static string FormatEnumValue(ITypeSymbol enumType, object? value) + { + var enumName = enumType.ToDisplayString(PatternFormat); + var member = enumType.GetMembers() + .OfType() + .FirstOrDefault(f => f.HasConstantValue && Equals(f.ConstantValue, value)); + return member is not null + ? $"{enumName}.{EscapeIdentifier(member.Name)}" + : $"({enumName})({Convert.ToString(value, CultureInfo.InvariantCulture)})"; + } + + private static string? FormatPrimitive(object? value) => value switch + { + null => "null", + string s => SymbolDisplay.FormatLiteral(s, quote: true), + char c => SymbolDisplay.FormatLiteral(c, quote: true), + bool b => b ? "true" : "false", + float f when float.IsNaN(f) => "float.NaN", + float f when float.IsPositiveInfinity(f) => "float.PositiveInfinity", + float f when float.IsNegativeInfinity(f) => "float.NegativeInfinity", + float f => f.ToString("R", CultureInfo.InvariantCulture) + "F", + double d when double.IsNaN(d) => "double.NaN", + double d when double.IsPositiveInfinity(d) => "double.PositiveInfinity", + double d when double.IsNegativeInfinity(d) => "double.NegativeInfinity", + double d => d.ToString("R", CultureInfo.InvariantCulture) + "D", + decimal m => m.ToString(CultureInfo.InvariantCulture) + "M", + long l => l.ToString(CultureInfo.InvariantCulture) + "L", + ulong ul => ul.ToString(CultureInfo.InvariantCulture) + "UL", + uint ui => ui.ToString(CultureInfo.InvariantCulture) + "U", + int i => i.ToString(CultureInfo.InvariantCulture), + // Casts keep the type of boxed values such as keyed service keys. + short sh => $"(short)({sh.ToString(CultureInfo.InvariantCulture)})", + ushort us => $"(ushort){us.ToString(CultureInfo.InvariantCulture)}", + byte b8 => $"(byte){b8.ToString(CultureInfo.InvariantCulture)}", + sbyte sb => $"(sbyte)({sb.ToString(CultureInfo.InvariantCulture)})", + _ => null + }; + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/TrackingNames.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/TrackingNames.cs new file mode 100644 index 0000000000..6a959422b1 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/DataPortalOperations/TrackingNames.cs @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Tracking names for validating incremental generator caching +//----------------------------------------------------------------------- + +namespace Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations +{ + /// + /// Names of incremental pipeline steps, used by tests to verify caching. + /// + public static class TrackingNames + { + /// Extraction of individual operation methods. + public const string ExtractOperationMethods = nameof(ExtractOperationMethods); + + /// Grouping of operation methods by containing type. + public const string GroupOperationTypes = nameof(GroupOperationTypes); + + /// Per-type models for operations generation. + public const string OperationTypes = nameof(OperationTypes); + + /// Build options for extension generation. + public const string ExtensionOptions = nameof(ExtensionOptions); + + /// Per-type models for extension generation. + public const string ExtensionTypes = nameof(ExtensionTypes); + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Internals/EquatableArray.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Internals/EquatableArray.cs new file mode 100644 index 0000000000..a1e0fbf7b4 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Internals/EquatableArray.cs @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Immutable array with value equality for incremental generator models +//----------------------------------------------------------------------- + +using System.Collections; + +namespace Csla.Generator.AutoImplementProperties.CSharp +{ + /// + /// An immutable array with value (sequence) equality, so incremental + /// generator models that contain collections compare correctly and + /// the pipeline can cache unchanged results. + /// + internal readonly struct EquatableArray : IEquatable>, IReadOnlyList + where T : IEquatable + { + public static readonly EquatableArray Empty = new([]); + + private readonly T[]? _array; + + public EquatableArray(T[] array) + { + _array = array; + } + + public EquatableArray(IEnumerable items) + { + _array = items.ToArray(); + } + + public T this[int index] => (_array ?? [])[index]; + + public int Count => _array?.Length ?? 0; + + public bool IsEmpty => Count == 0; + + public bool Equals(EquatableArray other) + { + var left = _array ?? []; + var right = other._array ?? []; + if (left.Length != right.Length) + return false; + for (var i = 0; i < left.Length; i++) + { + if (!EqualityComparer.Default.Equals(left[i], right[i])) + return false; + } + return true; + } + + public override bool Equals(object? obj) => obj is EquatableArray other && Equals(other); + + public override int GetHashCode() + { + var hash = new HashCode(); + foreach (var item in _array ?? []) + hash.Add(item); + return hash.ToHashCode(); + } + + public IEnumerator GetEnumerator() => ((IEnumerable)(_array ?? [])).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public static bool operator ==(EquatableArray left, EquatableArray right) => left.Equals(right); + + public static bool operator !=(EquatableArray left, EquatableArray right) => !left.Equals(right); + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Internals/HashCode.cs b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Internals/HashCode.cs new file mode 100644 index 0000000000..23490caa43 --- /dev/null +++ b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/Internals/HashCode.cs @@ -0,0 +1,106 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Minimal hash code combiner for netstandard2.0 +//----------------------------------------------------------------------- + +namespace Csla.Generator.AutoImplementProperties.CSharp +{ + /// + /// Minimal replacement for System.HashCode, which is not available + /// on netstandard2.0. Keeps the generator free of extra dependencies + /// so it ships as a single analyzer assembly. + /// + internal struct HashCode + { + private const int Seed = unchecked((int)2166136261); + private const int Prime = 16777619; + + private int _value; + private bool _started; + + public void Add(T value) + { + if (!_started) + { + _value = Seed; + _started = true; + } + _value = unchecked((_value ^ (value?.GetHashCode() ?? 0)) * Prime); + } + + public readonly int ToHashCode() => _started ? _value : Seed; + + public static int Combine(T1 v1) + { + var hash = new HashCode(); + hash.Add(v1); + return hash.ToHashCode(); + } + + public static int Combine(T1 v1, T2 v2) + { + var hash = new HashCode(); + hash.Add(v1); + hash.Add(v2); + return hash.ToHashCode(); + } + + public static int Combine(T1 v1, T2 v2, T3 v3) + { + var hash = new HashCode(); + hash.Add(v1); + hash.Add(v2); + hash.Add(v3); + return hash.ToHashCode(); + } + + public static int Combine(T1 v1, T2 v2, T3 v3, T4 v4) + { + var hash = new HashCode(); + hash.Add(v1); + hash.Add(v2); + hash.Add(v3); + hash.Add(v4); + return hash.ToHashCode(); + } + + public static int Combine(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5) + { + var hash = new HashCode(); + hash.Add(v1); + hash.Add(v2); + hash.Add(v3); + hash.Add(v4); + hash.Add(v5); + return hash.ToHashCode(); + } + + public static int Combine(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6) + { + var hash = new HashCode(); + hash.Add(v1); + hash.Add(v2); + hash.Add(v3); + hash.Add(v4); + hash.Add(v5); + hash.Add(v6); + return hash.ToHashCode(); + } + + public static int Combine(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) + { + var hash = new HashCode(); + hash.Add(v1); + hash.Add(v2); + hash.Add(v3); + hash.Add(v4); + hash.Add(v5); + hash.Add(v6); + hash.Add(v7); + return hash.ToHashCode(); + } + } +} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/tools/install.ps1 b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/tools/install.ps1 deleted file mode 100644 index 5fdd376ac6..0000000000 --- a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/tools/install.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -$generatorsPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve - -foreach($generatorsPath in $generatorsPaths) -{ - # Install the language agnostic source generators. - if (Test-Path $generatorsPath) - { - foreach ($generatorFilePath in Get-ChildItem $generatorsPath -Filter *.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($generatorFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($generatorsPath in $generatorsPaths) -{ - # Install language specific source generators. - $languageGeneratorsPath = join-path $generatorsPath $languageFolder - if (Test-Path $languageGeneratorsPath) - { - foreach ($generatorFilePath in Get-ChildItem $languageGeneratorsPath -Filter *.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($generatorFilePath.FullName) - } - } - } -} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/tools/uninstall.ps1 b/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/tools/uninstall.ps1 deleted file mode 100644 index dc5357b118..0000000000 --- a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.CSharp/tools/uninstall.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -$generatorsPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve - -foreach($generatorsPath in $generatorsPaths) -{ - # Uninstall the language agnostic source generators. - if (Test-Path $generatorsPath) - { - foreach ($generatorFilePath in Get-ChildItem $generatorsPath -Filter *.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Remove($generatorFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($generatorsPath in $generatorsPaths) -{ - # Uninstall language specific source generators. - $languageGeneratorsPath = join-path $generatorsPath $languageFolder - if (Test-Path $languageGeneratorsPath) - { - foreach ($generatorFilePath in Get-ChildItem $languageGeneratorsPath -Filter *.dll) - { - if($project.Object.AnalyzerReferences) - { - try - { - $project.Object.AnalyzerReferences.Remove($generatorFilePath.FullName) - } - catch - { - - } - } - } - } -} diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/readme.md b/Source/Csla.Generators/cs/AutoImplementProperties/readme.md index 29a0e176de..e168437268 100644 --- a/Source/Csla.Generators/cs/AutoImplementProperties/readme.md +++ b/Source/Csla.Generators/cs/AutoImplementProperties/readme.md @@ -5,8 +5,7 @@ This guide provides a step-by-step approach to using the source generator in the ## Prerequisites - Visual Studio 2019 or later with the .NET Core development workload installed. -- The CSLA.NET NuGet package added to your project. -- Ths CSLA.NET Source Generator NuGet package added to your project. +- The CSLA.NET NuGet package added to your project. Starting with CSLA 11 the source generators are included in the `Csla` package; the separate `Csla.Generator.AutoImplementProperties.CSharp` package is no longer used. ## Step 1: Define Your POCO Class diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj deleted file mode 100644 index 52f266960b..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Csla.Generator.DataPortalInterfaces.CSharp.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - netstandard2.0 - true - false - false - false - false - false - false - false - false - false - true - RS2008 - ..\..\..\..\..\Bin - ..\..\..\..\..\bin\packages\ - Csla.Generator.DataPortalInterfaces.CSharp - Csla.Generator.DataPortalInterfaces.CSharp - CSLA .NET Generator DataPortalInterfaces for CSharp - true - True - false - - - - - - - diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/DataPortalInterfaceBuilder.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/DataPortalInterfaceBuilder.cs deleted file mode 100644 index 0e136807f6..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/DataPortalInterfaceBuilder.cs +++ /dev/null @@ -1,287 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Builds generated source for data portal interface implementation -//----------------------------------------------------------------------- - -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; - -namespace Csla.Generator.DataPortalInterfaces.CSharp -{ - - /// - /// Builds the generated source code that provides an explicit - /// implementation of IDataPortalOperationMapping for a business type - /// - internal class DataPortalInterfaceBuilder - { - - /// - /// Build the partial type definition with the interface implementation - /// - internal GenerationResults BuildPartialTypeDefinition(ExtractedTypeDefinition typeDefinition) - { - using var stringWriter = new StringWriter(); - var textWriter = new IndentedTextWriter(stringWriter, " "); - - textWriter.WriteLine("//"); - textWriter.WriteLine("#nullable enable"); - textWriter.WriteLine(); - - AppendContainerDefinitions(textWriter, typeDefinition); - AppendTypeDefinition(textWriter, typeDefinition); - AppendBlockStart(textWriter); - - AppendInvokeOperationAsyncMethod(textWriter, typeDefinition); - textWriter.WriteLine(); - AppendInvokeNamedOperationAsyncMethod(textWriter, typeDefinition); - - AppendBlockEnd(textWriter); - AppendContainerDefinitionClosures(textWriter, typeDefinition); - - return new GenerationResults - { - FullyQualifiedName = typeDefinition.FullyQualifiedName, - GeneratedSource = stringWriter.ToString() - }; - } - - private void AppendBlockStart(IndentedTextWriter textWriter) - { - textWriter.WriteLine("{"); - textWriter.Indent++; - } - - private void AppendBlockEnd(IndentedTextWriter textWriter) - { - textWriter.Indent--; - textWriter.WriteLine("}"); - } - - private void AppendContainerDefinitions(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) - { - foreach (var containerDefinition in typeDefinition.ContainerDefinitions) - { - textWriter.WriteLine(containerDefinition.FullDefinition); - AppendBlockStart(textWriter); - } - } - - private void AppendContainerDefinitionClosures(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) - { - foreach (var containerDefinition in typeDefinition.ContainerDefinitions) - { - AppendBlockEnd(textWriter); - } - } - - private void AppendTypeDefinition(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) - { - textWriter.Write(typeDefinition.Scope); - textWriter.Write(" partial class "); - textWriter.Write(typeDefinition.TypeName); - textWriter.Write(typeDefinition.TypeParameters); - textWriter.WriteLine(" : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping"); - } - - private void AppendInvokeOperationAsyncMethod(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) - { - textWriter.WriteLine("async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync("); - textWriter.Indent++; - textWriter.WriteLine("global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider)"); - textWriter.Indent--; - AppendBlockStart(textWriter); - - // Group methods by their operation attribute types - var methodsByAttribute = GroupMethodsByAttribute(typeDefinition.OperationMethods); - - var isFirst = true; - foreach (var group in methodsByAttribute) - { - if (isFirst) - { - textWriter.Write("if"); - isFirst = false; - } - else - { - textWriter.Write("else if"); - } - - textWriter.WriteLine($" (operationType == typeof({group.Key}))"); - AppendBlockStart(textWriter); - - foreach (var method in group.Value) - { - AppendMethodDispatch(textWriter, method); - } - - AppendBlockEnd(textWriter); - } - - textWriter.WriteLine("throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria);"); - - AppendBlockEnd(textWriter); - } - - private void AppendMethodDispatch(IndentedTextWriter textWriter, ExtractedOperationMethod method) - { - var criteriaCount = method.CriteriaParameters.Count; - - if (criteriaCount == 0) - { - textWriter.WriteLine("if (criteria is null or { Length: 0 })"); - } - else - { - // Build pattern match: criteria is { Length: N } && criteria[0] is Type name && ... - var conditions = new List(); - conditions.Add($"criteria is {{ Length: {criteriaCount} }}"); - - for (int i = 0; i < criteriaCount; i++) - { - var param = method.CriteriaParameters[i]; - conditions.Add($"criteria[{i}] is {param.TypeDisplayName} p{i}_{SanitizeTypeName(param.TypeFullName)}"); - } - - textWriter.WriteLine($"if ({string.Join(" && ", conditions)})"); - } - - AppendBlockStart(textWriter); - - // Resolve injected parameters - foreach (var inject in method.InjectParameters) - { - if (inject.AllowNull) - { - textWriter.WriteLine($"var {inject.Name} = ({inject.TypeDisplayName}?)serviceProvider.GetService(typeof({inject.TypeDisplayName}));"); - } - else - { - textWriter.WriteLine($"var {inject.Name} = ({inject.TypeDisplayName})(serviceProvider.GetService(typeof({inject.TypeDisplayName})) ?? throw new global::System.InvalidOperationException($\"No service for type '{{typeof({inject.TypeDisplayName})}}' has been registered.\"));"); - } - } - - // Build method call - var args = new List(); - - // All parameters in original order (criteria + inject interleaved) - // We need to reconstruct the original parameter order - // The criteria params are in order, inject params are in order - // We emit them by building from both lists - var allParams = GetAllParametersInOrder(method); - foreach (var p in allParams) - { - if (p.IsInjected) - { - args.Add(p.Name); - } - else - { - var criteriaIndex = method.CriteriaParameters.IndexOf(p); - args.Add($"p{criteriaIndex}_{SanitizeTypeName(p.TypeFullName)}"); - } - } - - var argString = string.Join(", ", args); - - if (method.IsAsync) - { - textWriter.WriteLine($"await {method.MethodName}({argString}).ConfigureAwait(false);"); - } - else - { - textWriter.WriteLine($"{method.MethodName}({argString});"); - } - - textWriter.WriteLine("return;"); - AppendBlockEnd(textWriter); - } - - private static IList GetAllParametersInOrder(ExtractedOperationMethod method) - { - // Return all parameters in their original declaration order - // by merging criteria and inject lists - var all = new List(); - all.AddRange(method.CriteriaParameters); - all.AddRange(method.InjectParameters); - return all; - } - - private static Dictionary> GroupMethodsByAttribute( - IList methods) - { - var result = new Dictionary>(); - - foreach (var method in methods) - { - foreach (var attr in method.OperationAttributeNames) - { - if (!result.TryGetValue(attr, out var list)) - { - list = new List(); - result[attr] = list; - } - list.Add(method); - } - } - - return result; - } - - private void AppendInvokeNamedOperationAsyncMethod(IndentedTextWriter textWriter, ExtractedTypeDefinition typeDefinition) - { - textWriter.WriteLine("async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync("); - textWriter.Indent++; - textWriter.WriteLine("string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider)"); - textWriter.Indent--; - AppendBlockStart(textWriter); - - textWriter.WriteLine("switch (operationName)"); - AppendBlockStart(textWriter); - - // Collect all unique operation names across all methods - var emittedNames = new HashSet(); - foreach (var method in typeDefinition.OperationMethods) - { - foreach (var attr in method.OperationAttributeNames) - { - var operationName = method.GetOperationName(attr); - if (emittedNames.Add(operationName)) - { - textWriter.WriteLine($"case \"{operationName}\":"); - textWriter.Indent++; - AppendMethodDispatch(textWriter, method); - textWriter.WriteLine("break;"); - textWriter.Indent--; - } - } - } - - AppendBlockEnd(textWriter); - - textWriter.WriteLine("throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria);"); - - AppendBlockEnd(textWriter); - } - - private static string SanitizeTypeName(string typeName) - { - // Remove global:: prefix and dots, convert to safe identifier - return typeName - .Replace("global::", "") - .Replace(".", "_") - .Replace("<", "_") - .Replace(">", "_") - .Replace(",", "_") - .Replace(" ", "") - .Replace("?", "Nullable"); - } - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/ContainerDefinitionsExtractor.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/ContainerDefinitionsExtractor.cs deleted file mode 100644 index c128ed7de3..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/ContainerDefinitionsExtractor.cs +++ /dev/null @@ -1,110 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Extract container definitions for a type -//----------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Text; -using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery -{ - - /// - /// Extract the container definitions for a type declaration - /// - internal static class ContainerDefinitionsExtractor - { - - /// - /// Get the container definitions for a type declaration, - /// from outermost to innermost - /// - public static IReadOnlyList GetContainerDefinitions( - DefinitionExtractionContext context, - TypeDeclarationSyntax typeDeclaration) - { - var containers = new List(); - var parent = typeDeclaration.Parent; - - while (parent != null) - { - if (parent is TypeDeclarationSyntax parentType) - { - containers.Insert(0, BuildTypeContainer(parentType)); - } - else if (parent is BaseNamespaceDeclarationSyntax namespaceDecl) - { - containers.Insert(0, BuildNamespaceContainer(namespaceDecl)); - break; - } - parent = parent.Parent; - } - - return containers; - } - - private static ExtractedContainerDefinition BuildNamespaceContainer(BaseNamespaceDeclarationSyntax namespaceDecl) - { - var fullNamespace = GetFullNamespace(namespaceDecl); - return new ExtractedContainerDefinition - { - Name = fullNamespace, - FullDefinition = $"namespace {fullNamespace}" - }; - } - - private static string GetFullNamespace(BaseNamespaceDeclarationSyntax namespaceDecl) - { - var parts = new List(); - var current = namespaceDecl; - - while (current != null) - { - parts.Insert(0, current.Name.ToString()); - current = current.Parent as BaseNamespaceDeclarationSyntax; - } - - return string.Join('.', parts); - } - - private static ExtractedContainerDefinition BuildTypeContainer(TypeDeclarationSyntax typeDecl) - { - var sb = new StringBuilder(); - - foreach (var modifier in typeDecl.Modifiers) - { - if (modifier.IsKind(SyntaxKind.PublicKeyword) || - modifier.IsKind(SyntaxKind.InternalKeyword) || - modifier.IsKind(SyntaxKind.ProtectedKeyword) || - modifier.IsKind(SyntaxKind.PrivateKeyword)) - { - sb.Append(modifier.ValueText); - sb.Append(' '); - } - } - - sb.Append("partial "); - sb.Append(typeDecl.Keyword.ValueText); - sb.Append(' '); - sb.Append(typeDecl.Identifier.Text); - - if (typeDecl.TypeParameterList != null) - { - sb.Append(typeDecl.TypeParameterList.ToString()); - } - - return new ExtractedContainerDefinition - { - Name = typeDecl.Identifier.Text, - FullDefinition = sb.ToString() - }; - } - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/DefinitionExtractionContext.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/DefinitionExtractionContext.cs deleted file mode 100644 index a2b00831e1..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/DefinitionExtractionContext.cs +++ /dev/null @@ -1,52 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Wraps a SemanticModel for definition extraction -//----------------------------------------------------------------------- - -using Microsoft.CodeAnalysis; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery -{ - - /// - /// Wraps a SemanticModel for definition extraction, - /// providing helper methods for symbol resolution - /// - internal class DefinitionExtractionContext - { - private readonly SemanticModel _semanticModel; - - /// - /// Create a new DefinitionExtractionContext - /// - /// The Roslyn SemanticModel to use - public DefinitionExtractionContext(SemanticModel semanticModel) - { - _semanticModel = semanticModel; - } - - /// - /// Gets the SemanticModel - /// - public SemanticModel SemanticModel => _semanticModel; - - /// - /// Get the symbol for a syntax node - /// - public ISymbol? GetDeclaredSymbol(SyntaxNode node) - { - return _semanticModel.GetDeclaredSymbol(node); - } - - /// - /// Get the type info for a syntax node - /// - public TypeInfo GetTypeInfo(SyntaxNode node) - { - return _semanticModel.GetTypeInfo(node); - } - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/OperationMethodExtractor.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/OperationMethodExtractor.cs deleted file mode 100644 index 9625c3d2b2..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/OperationMethodExtractor.cs +++ /dev/null @@ -1,202 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Extract data portal operation methods from a type -//----------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Linq; -using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery -{ - - /// - /// Extract data portal operation methods from a type declaration - /// - internal static class OperationMethodExtractor - { - /// - /// All CSLA data portal operation attribute fully-qualified names - /// - private static readonly HashSet DataPortalAttributeNames = new HashSet - { - "Csla.CreateAttribute", - "Csla.FetchAttribute", - "Csla.InsertAttribute", - "Csla.UpdateAttribute", - "Csla.ExecuteAttribute", - "Csla.DeleteAttribute", - "Csla.DeleteSelfAttribute", - "Csla.CreateChildAttribute", - "Csla.FetchChildAttribute", - "Csla.InsertChildAttribute", - "Csla.UpdateChildAttribute", - "Csla.DeleteSelfChildAttribute", - "Csla.ExecuteChildAttribute" - }; - - private const string InjectAttributeName = "Csla.InjectAttribute"; - - /// - /// Extract all data portal operation methods from a type declaration - /// - public static IReadOnlyList ExtractOperationMethods( - DefinitionExtractionContext context, - ClassDeclarationSyntax classDeclaration) - { - var methods = new List(); - - foreach (var member in classDeclaration.Members) - { - if (member is MethodDeclarationSyntax methodDecl) - { - var operationMethod = TryExtractOperationMethod(context, methodDecl); - if (operationMethod != null) - { - methods.Add(operationMethod); - } - } - } - - return methods; - } - - private static ExtractedOperationMethod? TryExtractOperationMethod( - DefinitionExtractionContext context, - MethodDeclarationSyntax methodDecl) - { - var semanticModel = context.SemanticModel; - var methodSymbol = semanticModel.GetDeclaredSymbol(methodDecl) as IMethodSymbol; - if (methodSymbol == null) - return null; - - var operationAttributes = new List(); - - foreach (var attrData in methodSymbol.GetAttributes()) - { - var attrClassName = attrData.AttributeClass?.ToDisplayString(); - if (attrClassName != null && DataPortalAttributeNames.Contains(attrClassName)) - { - operationAttributes.Add(attrClassName); - } - } - - if (operationAttributes.Count == 0) - return null; - - var result = new ExtractedOperationMethod - { - MethodName = methodSymbol.Name, - IsAsync = IsAsyncMethod(methodSymbol) - }; - - foreach (var attr in operationAttributes) - { - result.OperationAttributeNames.Add(attr); - } - - foreach (var param in methodSymbol.Parameters) - { - var extractedParam = ExtractParameter(param); - if (extractedParam.IsInjected) - { - result.InjectParameters.Add(extractedParam); - } - else - { - result.CriteriaParameters.Add(extractedParam); - } - } - - return result; - } - - private static bool IsAsyncMethod(IMethodSymbol method) - { - if (method.ReturnType is INamedTypeSymbol namedType) - { - var displayName = namedType.ToDisplayString(); - return displayName == "System.Threading.Tasks.Task" || - displayName.StartsWith("System.Threading.Tasks.Task<"); - } - return false; - } - - private static ExtractedOperationParameter ExtractParameter(IParameterSymbol param) - { - var isInjected = false; - var allowNull = false; - - foreach (var attrData in param.GetAttributes()) - { - if (attrData.AttributeClass?.ToDisplayString() == InjectAttributeName) - { - isInjected = true; - - // Check for AllowNull = true - foreach (var namedArg in attrData.NamedArguments) - { - if (namedArg.Key == "AllowNull" && namedArg.Value.Value is bool val) - { - allowNull = val; - } - } - } - } - - return new ExtractedOperationParameter - { - Name = param.Name, - TypeFullName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - TypeDisplayName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), - TypeMetadataName = GetOperationTypeKey(param.Type), - IsInjected = isInjected, - AllowNull = allowNull - }; - } - - /// - /// Computes a deterministic type key for use in operation names. - /// Arrays: elementType + "Array" (e.g. "Int32Array") - /// Generic types: MetadataName with backtick replaced, then type args (e.g. "List_1_Int32") - /// Simple types: MetadataName (e.g. "Int32", "String") - /// - internal static string GetOperationTypeKey(ITypeSymbol typeSymbol) - { - if (typeSymbol is IArrayTypeSymbol arrayType) - { - return GetOperationTypeKey(arrayType.ElementType) + "Array"; - } - - if (typeSymbol is INamedTypeSymbol namedType && namedType.IsGenericType) - { - var baseName = namedType.MetadataName.Replace('`', '_'); - var typeArgs = string.Join('_', namedType.TypeArguments.Select(GetOperationTypeKey)); - return baseName + "_" + typeArgs; - } - - return typeSymbol.MetadataName; - } - - /// - /// Gets the base operation name from an attribute fully-qualified name. - /// Strips namespace and "Attribute" suffix - /// (e.g. "Csla.FetchAttribute" -> "Fetch") - /// - internal static string GetBaseOperationName(string attributeFullName) - { - var name = attributeFullName; - var dotIndex = name.LastIndexOf('.'); - if (dotIndex >= 0) - name = name.Substring(dotIndex + 1); - if (name.EndsWith("Attribute")) - name = name.Substring(0, name.Length - "Attribute".Length); - return name; - } - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/TypeDefinitionExtractor.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/TypeDefinitionExtractor.cs deleted file mode 100644 index 0dd95e4703..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Discovery/TypeDefinitionExtractor.cs +++ /dev/null @@ -1,129 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Extract the definition of a type for source generation -//----------------------------------------------------------------------- - -using System.Text; -using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Discovery -{ - - /// - /// Extract the definition of a type for which data portal interface - /// source generation is required - /// - internal static class TypeDefinitionExtractor - { - - /// - /// Extract the data needed for source generation from the syntax tree - /// - public static ExtractedTypeDefinition ExtractTypeDefinition( - DefinitionExtractionContext extractionContext, - ClassDeclarationSyntax targetTypeDeclaration) - { - var fullyQualifiedNameBuilder = new StringBuilder(); - - var definition = new ExtractedTypeDefinition - { - TypeName = GetTypeName(targetTypeDeclaration), - TypeParameters = GetTypeParameters(targetTypeDeclaration), - Namespace = GetNamespace(targetTypeDeclaration), - Scope = GetScopeDefinition(targetTypeDeclaration), - IsPartial = targetTypeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) - }; - - foreach (var containerDefinition in ContainerDefinitionsExtractor.GetContainerDefinitions(extractionContext, targetTypeDeclaration)) - { - definition.ContainerDefinitions.Add(containerDefinition); - fullyQualifiedNameBuilder.Append(containerDefinition.Name).Append('.'); - } - - foreach (var operationMethod in OperationMethodExtractor.ExtractOperationMethods(extractionContext, targetTypeDeclaration)) - { - definition.OperationMethods.Add(operationMethod); - } - - fullyQualifiedNameBuilder.Append(definition.TypeName); - definition.FullyQualifiedName = fullyQualifiedNameBuilder.ToString(); - - return definition; - } - - private static string GetNamespace(TypeDeclarationSyntax targetTypeDeclaration) - { - string nameSpace = string.Empty; - var potentialNamespaceParent = targetTypeDeclaration.Parent; - - while (potentialNamespaceParent != null && - potentialNamespaceParent is not NamespaceDeclarationSyntax && - potentialNamespaceParent is not FileScopedNamespaceDeclarationSyntax) - { - potentialNamespaceParent = potentialNamespaceParent.Parent; - } - - if (potentialNamespaceParent is BaseNamespaceDeclarationSyntax namespaceParent) - { - nameSpace = namespaceParent.Name.ToString(); - - while (true) - { - if (namespaceParent.Parent is not NamespaceDeclarationSyntax parent) - { - break; - } - - nameSpace = $"{parent.Name}.{nameSpace}"; - namespaceParent = parent; - } - } - - return nameSpace; - } - - private static string GetScopeDefinition(TypeDeclarationSyntax targetTypeDeclaration) - { - var scopeNameBuilder = new StringBuilder(); - - foreach (var modifier in targetTypeDeclaration.Modifiers) - { - if (modifier.IsKind(SyntaxKind.PublicKeyword) || - modifier.IsKind(SyntaxKind.InternalKeyword) || - modifier.IsKind(SyntaxKind.ProtectedKeyword) || - modifier.IsKind(SyntaxKind.PrivateKeyword)) - { - scopeNameBuilder.Append(modifier.ValueText); - scopeNameBuilder.Append(' '); - } - } - - if (scopeNameBuilder.Length < 1) - { - scopeNameBuilder.Append("internal "); - } - - return scopeNameBuilder.ToString().TrimEnd(); - } - - private static string GetTypeName(TypeDeclarationSyntax targetTypeDeclaration) - { - return targetTypeDeclaration.Identifier.ToString(); - } - - private static string GetTypeParameters(TypeDeclarationSyntax targetTypeDeclaration) - { - if (targetTypeDeclaration.TypeParameterList != null) - { - return targetTypeDeclaration.TypeParameterList.ToString(); - } - return string.Empty; - } - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedContainerDefinition.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedContainerDefinition.cs deleted file mode 100644 index 6f769bea7f..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedContainerDefinition.cs +++ /dev/null @@ -1,28 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// The definition of a container of a type -//----------------------------------------------------------------------- - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors -{ - - /// - /// The definition of a container of a type, extracted from the syntax tree provided by Roslyn - /// - public class ExtractedContainerDefinition - { - - /// - /// The name of the container - /// - public string Name { get; set; } = string.Empty; - - /// - /// The full definition of the container for use in source generation - /// - public string FullDefinition { get; set; } = string.Empty; - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationMethod.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationMethod.cs deleted file mode 100644 index 3256631030..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationMethod.cs +++ /dev/null @@ -1,62 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Represents a single data portal operation method -//----------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Linq; -using Csla.Generator.DataPortalInterfaces.CSharp.Discovery; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors -{ - - /// - /// Represents a single data portal operation method extracted - /// from the target type - /// - public class ExtractedOperationMethod - { - - /// - /// The name of the method - /// - public string MethodName { get; set; } = string.Empty; - - /// - /// Whether the method returns Task (async) - /// - public bool IsAsync { get; set; } - - /// - /// The fully qualified attribute type names on this method - /// (e.g. "Csla.CreateAttribute", "Csla.FetchAttribute") - /// - public IList OperationAttributeNames { get; } = new List(); - - /// - /// The criteria (non-injected) parameters - /// - public IList CriteriaParameters { get; } = new List(); - - /// - /// The injected parameters (marked with [Inject]) - /// - public IList InjectParameters { get; } = new List(); - - /// - /// Computes the deterministic operation name for a given attribute. - /// Format: "OperationType" for no criteria, "OperationType__Type1_Type2" for criteria. - /// - public string GetOperationName(string attributeFullName) - { - var baseName = OperationMethodExtractor.GetBaseOperationName(attributeFullName); - if (CriteriaParameters.Count == 0) - return baseName; - var typeKeys = string.Join('_', CriteriaParameters.Select(p => p.TypeMetadataName)); - return baseName + "__" + typeKeys; - } - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationParameter.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationParameter.cs deleted file mode 100644 index dc7d57fcd3..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedOperationParameter.cs +++ /dev/null @@ -1,51 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Represents a parameter of a data portal operation method -//----------------------------------------------------------------------- - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors -{ - - /// - /// Represents a parameter of a data portal operation method - /// - public class ExtractedOperationParameter - { - - /// - /// The parameter name - /// - public string Name { get; set; } = string.Empty; - - /// - /// The fully qualified type name of the parameter - /// - public string TypeFullName { get; set; } = string.Empty; - - /// - /// The type name suitable for use in generated code - /// (may include global:: prefix) - /// - public string TypeDisplayName { get; set; } = string.Empty; - - /// - /// The type's metadata name used for operation name computation - /// (e.g. "Int32", "String", "List_1_Int32") - /// - public string TypeMetadataName { get; set; } = string.Empty; - - /// - /// Whether this is an injected parameter - /// - public bool IsInjected { get; set; } - - /// - /// For injected parameters, whether null is allowed - /// (maps to [Inject(AllowNull = true)]) - /// - public bool AllowNull { get; set; } - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedTypeDefinition.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedTypeDefinition.cs deleted file mode 100644 index fee1ca3294..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/ExtractedTypeDefinition.cs +++ /dev/null @@ -1,75 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// The definition of a type with data portal operations -//----------------------------------------------------------------------- - -using System; -using System.Collections.Generic; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors -{ - - /// - /// The definition of a type with data portal operations, - /// extracted from the syntax tree provided by Roslyn - /// - public class ExtractedTypeDefinition : IEquatable - { - - /// - /// The namespace in which the type resides - /// - public string Namespace { get; set; } = string.Empty; - - /// - /// The scope of the class (e.g. "public", "internal") - /// - public string Scope { get; set; } = "public"; - - /// - /// The name of the type, excluding any namespace - /// - public string TypeName { get; set; } = string.Empty; - - /// - /// The type parameters (e.g. "<T>") or empty string - /// - public string TypeParameters { get; set; } = string.Empty; - - /// - /// The fully qualified name of the type, including namespace - /// - public string FullyQualifiedName { get; set; } = string.Empty; - - /// - /// The container definitions for this type - /// - public IList ContainerDefinitions { get; } = new List(); - - /// - /// The data portal operation methods on this type - /// - public IList OperationMethods { get; } = new List(); - - /// - /// Whether the class is partial - /// - public bool IsPartial { get; set; } - - /// - public bool Equals(ExtractedTypeDefinition other) - { - if (other is null) return false; - return FullyQualifiedName == other.FullyQualifiedName; - } - - /// - public override bool Equals(object? obj) => obj is ExtractedTypeDefinition other && Equals(other); - - /// - public override int GetHashCode() => FullyQualifiedName?.GetHashCode() ?? 0; - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/GenerationResults.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/GenerationResults.cs deleted file mode 100644 index 18edc012ab..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/Extractors/GenerationResults.cs +++ /dev/null @@ -1,28 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// The results of source generation -//----------------------------------------------------------------------- - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Extractors -{ - - /// - /// The results of source generation - /// - public class GenerationResults - { - - /// - /// The fully qualified name of the generated type - /// - public string FullyQualifiedName { get; set; } = string.Empty; - - /// - /// The source code that has been generated by the builder - /// - public string GeneratedSource { get; set; } = string.Empty; - } -} diff --git a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/IncrementalDataPortalInterfaceGenerator.cs b/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/IncrementalDataPortalInterfaceGenerator.cs deleted file mode 100644 index 644809bdc3..0000000000 --- a/Source/Csla.Generators/cs/DataPortalInterfaces/Csla.Generator.DataPortalInterfaces.CSharp/IncrementalDataPortalInterfaceGenerator.cs +++ /dev/null @@ -1,152 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -// Source generator for data portal operation interface implementations -//----------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Text; -using Csla.Generator.DataPortalInterfaces.CSharp.Discovery; -using Csla.Generator.DataPortalInterfaces.CSharp.Extractors; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; - -namespace Csla.Generator.DataPortalInterfaces.CSharp -{ - - /// - /// Incremental source generator that creates explicit implementations of - /// IDataPortalOperationMapping for business types with data portal operation methods. - /// - [Generator] - public class IncrementalDataPortalInterfaceGenerator : IIncrementalGenerator - { - /// - /// The short attribute names to look for on methods (syntactic check) - /// - private static readonly HashSet AttributeShortNames = new HashSet - { - "Create", "CreateAttribute", - "Fetch", "FetchAttribute", - "Insert", "InsertAttribute", - "Update", "UpdateAttribute", - "Execute", "ExecuteAttribute", - "Delete", "DeleteAttribute", - "DeleteSelf", "DeleteSelfAttribute", - "CreateChild", "CreateChildAttribute", - "FetchChild", "FetchChildAttribute", - "InsertChild", "InsertChildAttribute", - "UpdateChild", "UpdateChildAttribute", - "DeleteSelfChild", "DeleteSelfChildAttribute", - "ExecuteChild", "ExecuteChildAttribute" - }; - - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( - predicate: static (s, _) => IsCandidateClass(s), - transform: static (ctx, _) => TransformCandidate(ctx) - ).Where(static m => m is not null); - - context.RegisterSourceOutput(classDeclarations, static (spc, typeDefinition) => - { - if (!typeDefinition!.IsPartial) - { - // Emit diagnostic for non-partial class - var diagnostic = Diagnostic.Create( - Diagnostics.NonPartialClassWarning, - Location.None, - typeDefinition.TypeName); - spc.ReportDiagnostic(diagnostic); - return; - } - - var builder = new DataPortalInterfaceBuilder(); - var generationResults = builder.BuildPartialTypeDefinition(typeDefinition); - - spc.AddSource( - $"{generationResults.FullyQualifiedName}.DataPortalOperations.g.cs", - SourceText.From(generationResults.GeneratedSource, Encoding.UTF8)); - }); - } - - private static bool IsCandidateClass(SyntaxNode node) - { - if (node is not ClassDeclarationSyntax classDecl) - return false; - - // Skip abstract classes - they can't be directly instantiated by the data portal - if (classDecl.Modifiers.Any(SyntaxKind.AbstractKeyword)) - return false; - - // Check if any method has a data portal operation attribute - foreach (var member in classDecl.Members) - { - if (member is MethodDeclarationSyntax methodDecl) - { - foreach (var attrList in methodDecl.AttributeLists) - { - foreach (var attr in attrList.Attributes) - { - var name = GetAttributeName(attr); - if (AttributeShortNames.Contains(name)) - return true; - } - } - } - } - - return false; - } - - private static string GetAttributeName(AttributeSyntax attr) - { - switch (attr.Name) - { - case SimpleNameSyntax simpleName: - return simpleName.Identifier.Text; - case QualifiedNameSyntax qualifiedName: - return qualifiedName.Right.Identifier.Text; - default: - return attr.Name.ToString(); - } - } - - private static ExtractedTypeDefinition? TransformCandidate(GeneratorSyntaxContext ctx) - { - var classDecl = (ClassDeclarationSyntax)ctx.Node; - var extractionContext = new DefinitionExtractionContext(ctx.SemanticModel); - var typeDefinition = TypeDefinitionExtractor.ExtractTypeDefinition(extractionContext, classDecl); - - // Verify at least one method actually has a valid CSLA attribute (semantic check) - if (typeDefinition.OperationMethods.Count == 0) - return null; - - return typeDefinition; - } - } - - /// - /// Diagnostic descriptors for the data portal interface generator - /// - internal static class Diagnostics - { - /// - /// Warning when data portal operations are found on a non-partial class - /// - public static readonly DiagnosticDescriptor NonPartialClassWarning = new DiagnosticDescriptor( - id: "CSLADP001", - title: "Data portal operation class should be partial", - messageFormat: "Class '{0}' has data portal operation methods but is not partial. The source generator cannot generate the IDataPortalOperationMapping implementation.", - category: "Csla.DataPortal", - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); - } -} diff --git a/Source/Csla/BusinessBindingListBase.cs b/Source/Csla/BusinessBindingListBase.cs index 2d1f0d25c4..da6f9bdf22 100644 --- a/Source/Csla/BusinessBindingListBase.cs +++ b/Source/Csla/BusinessBindingListBase.cs @@ -23,7 +23,7 @@ namespace Csla /// Type of the child objects contained in the list. [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [Serializable] - public abstract class BusinessBindingListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] C> : + public abstract partial class BusinessBindingListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] C> : ExtendedBindingList, IContainsDeletedList, IEditableCollection, IUndoableObject, ICloneable, ISavable, ISavable, IParent, IDataPortalTarget, diff --git a/Source/Csla/BusinessDocumentBase.cs b/Source/Csla/BusinessDocumentBase.cs index e70d2c343f..07e44d60b7 100644 --- a/Source/Csla/BusinessDocumentBase.cs +++ b/Source/Csla/BusinessDocumentBase.cs @@ -31,7 +31,7 @@ namespace Csla /// Type of the business object being defined. /// Type of the child objects contained in the collection. [Serializable] - public abstract class BusinessDocumentBase< + public abstract partial class BusinessDocumentBase< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] C> : BusinessBase, diff --git a/Source/Csla/BusinessListBase.cs b/Source/Csla/BusinessListBase.cs index df8017ed87..2a56275050 100644 --- a/Source/Csla/BusinessListBase.cs +++ b/Source/Csla/BusinessListBase.cs @@ -28,7 +28,7 @@ namespace Csla [DebuggerStepThrough] #endif [Serializable] - public abstract class BusinessListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] C> : + public abstract partial class BusinessListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] C> : ObservableBindingList, IContainsDeletedList, ISavable, diff --git a/Source/Csla/CommandBase.cs b/Source/Csla/CommandBase.cs index 1c69800578..82942b8f56 100644 --- a/Source/Csla/CommandBase.cs +++ b/Source/Csla/CommandBase.cs @@ -41,7 +41,7 @@ namespace Csla /// /// [Serializable] - public abstract class CommandBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]T> : ManagedObjectBase, + public abstract partial class CommandBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]T> : ManagedObjectBase, IDataPortalTarget, IManageProperties, ICommandBase diff --git a/Source/Csla/Csla.csproj b/Source/Csla/Csla.csproj index 980cbcbf8a..a64c6a841c 100644 --- a/Source/Csla/Csla.csproj +++ b/Source/Csla/Csla.csproj @@ -109,13 +109,13 @@ - - true - analyzers/dotnet/cs/ - + + - + + + True True @@ -127,4 +127,19 @@ + + + $(TargetsForTfmSpecificContentInPackage);CslaPackBundledAnalyzers + + + + + + + + + + + diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/CslaIgnorePropertyAttribute.cs b/Source/Csla/CslaIgnorePropertyAttribute.cs similarity index 100% rename from Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/CslaIgnorePropertyAttribute.cs rename to Source/Csla/CslaIgnorePropertyAttribute.cs diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/CslaImplementPropertiesAttribute.cs b/Source/Csla/CslaImplementPropertiesAttribute.cs similarity index 100% rename from Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/CslaImplementPropertiesAttribute.cs rename to Source/Csla/CslaImplementPropertiesAttribute.cs diff --git a/Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/CslaImplementPropertiesInterfaceAttribute.cs b/Source/Csla/CslaImplementPropertiesInterfaceAttribute.cs similarity index 100% rename from Source/Csla.Generators/cs/AutoImplementProperties/Csla.Generator.AutoImplementProperties.Attributes.CSharp/CslaImplementPropertiesInterfaceAttribute.cs rename to Source/Csla/CslaImplementPropertiesInterfaceAttribute.cs diff --git a/Source/Csla/DataPortalExtensionsAttribute.cs b/Source/Csla/DataPortalExtensionsAttribute.cs new file mode 100644 index 0000000000..ba857a0541 --- /dev/null +++ b/Source/Csla/DataPortalExtensionsAttribute.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Requests generation of strongly typed data portal extension methods +//----------------------------------------------------------------------- + +namespace Csla +{ + /// + /// Requests that the CSLA source generator emit strongly typed + /// extension methods on and + /// for each data portal + /// operation method declared by the business class. + /// + /// + /// + /// Apply the attribute to a business class, or to an assembly + /// ([assembly: DataPortalExtensions]) to generate extension + /// methods for every business class in the assembly that can have + /// them: non-abstract, non-generic classes implementing + /// that are accessible outside their + /// containing type. Use + /// to exclude a class or an operation method. + /// + /// + /// When both are used, the set on the class takes + /// precedence over the prefix set on the assembly. + /// + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class DataPortalExtensionsAttribute : Attribute + { + /// + /// Gets or sets an optional prefix added to the name of + /// each generated extension method. Defaults to no prefix, + /// or on a class, to the prefix set on the assembly. + /// + public string? Prefix { get; set; } + } +} diff --git a/Source/Csla/DataPortalT.cs b/Source/Csla/DataPortalT.cs index 59cdb9adfb..88d59492f2 100644 --- a/Source/Csla/DataPortalT.cs +++ b/Source/Csla/DataPortalT.cs @@ -25,7 +25,8 @@ namespace Csla /// Type of business object. /// public class DataPortal<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> - : IDataPortal, IChildDataPortal, IDataPortal, IChildDataPortal where T : notnull, ICslaObject + : IDataPortal, IChildDataPortal, IDataPortal, IChildDataPortal, + IDataPortalOperationInvoker, IChildDataPortalOperationInvoker where T : notnull, ICslaObject { /// /// Gets or sets the current ApplicationContext object. @@ -66,16 +67,21 @@ private Reflection.ServiceProviderMethodCaller ServiceProviderMethodCaller private async Task DoCreateAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, object criteria, bool isSync, CancellationToken ct = default) { + _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); + var operationName = method != null ? Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo) : null; + return await DoCreateCoreAsync(objectType, criteria, isSync, operationName, method?.MethodInfo.RunLocal() ?? false, ct); + } + private async Task DoCreateCoreAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, object criteria, bool isSync, string? operationName, bool runLocal, CancellationToken ct = default) + { if (!await Rules.BusinessRules.HasPermissionAsync(_applicationContext, Rules.AuthorizationActions.CreateObject, objectType, Server.DataPortal.GetCriteriaArray(criteria), ct)) throw new Security.SecurityException(string.Format(Resources.UserNotAuthorizedException, "create", objectType.Name)); - _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); - var proxy = GetDataPortalProxy(method); - - var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote); - if (method != null) - dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo); + var proxy = GetDataPortalProxy(runLocal); + var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote) + { + OperationName = operationName + }; Server.DataPortalResult result = default!; try @@ -146,15 +152,21 @@ private async Task DoFetchAsync([DynamicallyAccessedMembers(DynamicallyA return await DoExecuteAsync(objectType, criteria, isSync); } + _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); + var operationName = method != null ? Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo) : null; + return await DoFetchCoreAsync(objectType, criteria, isSync, operationName, method?.MethodInfo.RunLocal() ?? false, ct); + } + + private async Task DoFetchCoreAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, object criteria, bool isSync, string? operationName, bool runLocal, CancellationToken ct = default) + { if (!await Rules.BusinessRules.HasPermissionAsync(_applicationContext, Rules.AuthorizationActions.GetObject, objectType, Server.DataPortal.GetCriteriaArray(criteria), ct)) throw new Security.SecurityException(string.Format(Resources.UserNotAuthorizedException, "get", objectType.Name)); - _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); - - var proxy = GetDataPortalProxy(method); - var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote); - if (method != null) - dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo); + var proxy = GetDataPortalProxy(runLocal); + var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote) + { + OperationName = operationName + }; Server.DataPortalResult result = default!; try @@ -181,17 +193,23 @@ private async Task DoFetchAsync([DynamicallyAccessedMembers(DynamicallyA private async Task DoExecuteAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, object criteria, bool isSync, CancellationToken ct = default) { + _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); + var operationName = method != null ? Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo) : null; + return await DoExecuteCoreAsync(objectType, criteria, isSync, operationName, method?.MethodInfo.RunLocal() ?? false, ct); + } + private async Task DoExecuteCoreAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, object criteria, bool isSync, string? operationName, bool runLocal, CancellationToken ct = default) + { if (!await Rules.BusinessRules.HasPermissionAsync(_applicationContext, Rules.AuthorizationActions.EditObject, objectType, Server.DataPortal.GetCriteriaArray(criteria), ct)) throw new Security.SecurityException(string.Format(Resources.UserNotAuthorizedException, "execute", objectType.Name)); - _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); - var proxy = GetDataPortalProxy(method); - var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote); - if (method != null) - dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo); + var proxy = GetDataPortalProxy(runLocal); + var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote) + { + OperationName = operationName + }; Server.DataPortalResult result = default!; try @@ -469,16 +487,21 @@ public Task UpdateAsync(T obj) private async Task DoDeleteAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, object criteria, bool isSync, CancellationToken ct = default) { + _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); + var operationName = method != null ? Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo) : null; + await DoDeleteCoreAsync(objectType, criteria, isSync, operationName, method?.MethodInfo.RunLocal() ?? false, ct); + } + private async Task DoDeleteCoreAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, object criteria, bool isSync, string? operationName, bool runLocal, CancellationToken ct = default) + { if (!await Rules.BusinessRules.HasPermissionAsync(_applicationContext, Rules.AuthorizationActions.DeleteObject, objectType, Server.DataPortal.GetCriteriaArray(criteria), ct)) throw new Security.SecurityException(string.Format(Resources.UserNotAuthorizedException, "delete", objectType.Name)); - _ = ServiceProviderMethodCaller.TryGetProviderMethodInfoFor(objectType, criteria, out var method); - var proxy = GetDataPortalProxy(method); - - var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote); - if (method != null) - dpContext.OperationName = Server.DataPortalOperationNameHelper.ComputeOperationName(method.MethodInfo); + var proxy = GetDataPortalProxy(runLocal); + var dpContext = new Server.DataPortalContext(_applicationContext, proxy.IsServerRemote) + { + OperationName = operationName + }; try { var result = await _cache.GetDataPortalResultAsync(objectType, criteria, DataPortalOperations.Delete, @@ -572,6 +595,125 @@ public async Task ExecuteAsync(params object?[]? criteria) return (T)await DoFetchAsync(typeof(T), Server.DataPortal.GetCriteriaFromArray(criteria), false); } + #region Pre-resolved operations + + /// + /// Returns the operation name supplied by generated code, after checking it is not null, empty or white space. + /// + private static string ValidateOperationName(string operationName) + { + if (operationName is null) + throw new ArgumentNullException(nameof(operationName)); + if (string.IsNullOrWhiteSpace(operationName)) + throw new ArgumentException(string.Format(Resources.StringNotNullOrWhiteSpaceException, nameof(operationName)), nameof(operationName)); + return operationName; + } + + /// + /// Converts the explicit criteria array supplied by generated code into + /// the criteria object used by the data portal. A single criteria value + /// that is itself an array of reference types (for example string[] or + /// object[]) is wrapped so it is not expanded into separate criteria values. + /// + private static object GetOperationCriteria(object?[]? criteria) + { + if (criteria is { Length: 1 } && criteria[0] is object[]) + return new Core.MobileList(criteria); + return Server.DataPortal.GetCriteriaFromArray(criteria); + } + + private static void WaitSynchronously(Task task) + { + try + { + task.Wait(); + } + catch (AggregateException ex) + { + if (ex.InnerExceptions.Count > 0) + throw ex.InnerExceptions[0]; + else + throw; + } + } + + private static TResult WaitSynchronously(Task task) + { + WaitSynchronously((Task)task); + return task.Result; + } + + /// + T IDataPortalOperationInvoker.CreateByOperation(string operationName, bool runLocal, object?[]? criteria) + => (T)WaitSynchronously(DoCreateCoreAsync(typeof(T), GetOperationCriteria(criteria), true, ValidateOperationName(operationName), runLocal)); + + /// + async Task IDataPortalOperationInvoker.CreateByOperationAsync(string operationName, bool runLocal, object?[]? criteria) + => (T)await DoCreateCoreAsync(typeof(T), GetOperationCriteria(criteria), false, ValidateOperationName(operationName), runLocal); + + /// + T IDataPortalOperationInvoker.FetchByOperation(string operationName, bool runLocal, object?[]? criteria) + => (T)WaitSynchronously(DoFetchByOperationAsync(ValidateOperationName(operationName), runLocal, criteria, true)); + + /// + async Task IDataPortalOperationInvoker.FetchByOperationAsync(string operationName, bool runLocal, object?[]? criteria) + => (T)await DoFetchByOperationAsync(ValidateOperationName(operationName), runLocal, criteria, false); + + private Task DoFetchByOperationAsync(string operationName, bool runLocal, object?[]? criteria, bool isSync) + { + // As with FetchAsync, fetching a command object is an execute + // operation; the fetch operation name does not apply to it. + if (typeof(ICommandObject).IsAssignableFrom(typeof(T))) + return DoExecuteCoreAsync(typeof(T), GetOperationCriteria(criteria), isSync, null, runLocal); + return DoFetchCoreAsync(typeof(T), GetOperationCriteria(criteria), isSync, operationName, runLocal); + } + + /// + T IDataPortalOperationInvoker.ExecuteByOperation(string operationName, bool runLocal, object?[]? criteria) + => (T)WaitSynchronously(DoExecuteCoreAsync(typeof(T), GetOperationCriteria(criteria), true, ValidateOperationName(operationName), runLocal)); + + /// + async Task IDataPortalOperationInvoker.ExecuteByOperationAsync(string operationName, bool runLocal, object?[]? criteria) + => (T)await DoExecuteCoreAsync(typeof(T), GetOperationCriteria(criteria), false, ValidateOperationName(operationName), runLocal); + + /// + void IDataPortalOperationInvoker.DeleteByOperation(string operationName, bool runLocal, object?[]? criteria) + => WaitSynchronously(DoDeleteCoreAsync(typeof(T), GetOperationCriteria(criteria), true, ValidateOperationName(operationName), runLocal)); + + /// + Task IDataPortalOperationInvoker.DeleteByOperationAsync(string operationName, bool runLocal, object?[]? criteria) + => DoDeleteCoreAsync(typeof(T), GetOperationCriteria(criteria), false, ValidateOperationName(operationName), runLocal); + + /// + T IChildDataPortalOperationInvoker.CreateChildByOperation(string operationName, object?[]? criteria) + { + var portal = new Server.ChildDataPortal(_applicationContext); + return (T)portal.CreateByOperation(typeof(T), operationName, criteria); + } + + /// + Task IChildDataPortalOperationInvoker.CreateChildByOperationAsync(string operationName, object?[]? criteria) + { + var portal = new Server.ChildDataPortal(_applicationContext); + return portal.CreateByOperationAsync(operationName, criteria); + } + + /// + T IChildDataPortalOperationInvoker.FetchChildByOperation(string operationName, object?[]? criteria) + { + var portal = new Server.ChildDataPortal(_applicationContext); + return (T)portal.FetchByOperation(typeof(T), operationName, criteria); + } + + /// + Task IChildDataPortalOperationInvoker.FetchChildByOperationAsync(string operationName, object?[]? criteria) + { + var portal = new Server.ChildDataPortal(_applicationContext); + return portal.FetchByOperationAsync(operationName, criteria); + } + + #endregion + private IDataPortalProxy GetDataPortalProxy(Reflection.ServiceProviderMethodInfo? method) { if (method != null) diff --git a/Source/Csla/DynamicBindingListBase.cs b/Source/Csla/DynamicBindingListBase.cs index 5d3410c27d..7ec955cf89 100644 --- a/Source/Csla/DynamicBindingListBase.cs +++ b/Source/Csla/DynamicBindingListBase.cs @@ -41,7 +41,7 @@ namespace Csla /// [Serializable] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - public abstract class DynamicBindingListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : + public abstract partial class DynamicBindingListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : ExtendedBindingList, IParent, Server.IDataPortalTarget, diff --git a/Source/Csla/DynamicListBase.cs b/Source/Csla/DynamicListBase.cs index b7eaace928..a8487db31e 100644 --- a/Source/Csla/DynamicListBase.cs +++ b/Source/Csla/DynamicListBase.cs @@ -42,7 +42,7 @@ namespace Csla /// /// [Serializable] - public abstract class DynamicListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> : + public abstract partial class DynamicListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> : #if ANDROID || IOS ExtendedBindingList, #else diff --git a/Source/Csla/IDataPortalOperationInvoker.cs b/Source/Csla/IDataPortalOperationInvoker.cs new file mode 100644 index 0000000000..1d941266de --- /dev/null +++ b/Source/Csla/IDataPortalOperationInvoker.cs @@ -0,0 +1,134 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Invokes pre-resolved data portal operations +//----------------------------------------------------------------------- + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Csla.Core; + +namespace Csla +{ + /// + /// Invokes data portal operations whose target method has already been + /// resolved at compile time. Used by generated data portal extension + /// methods; not intended to be called directly. + /// + /// + /// The operation name identifies the operation method on the server + /// and the runLocal flag reflects any + /// on that method, so the client does + /// not need to locate the method using reflection. + /// + /// Type of business object. + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IDataPortalOperationInvoker<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> + where T : ICslaObject + { + /// + /// Creates a business object using a pre-resolved create operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + T CreateByOperation(string operationName, bool runLocal, object?[]? criteria); + + /// + /// Creates a business object using a pre-resolved create operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + Task CreateByOperationAsync(string operationName, bool runLocal, object?[]? criteria); + + /// + /// Fetches a business object using a pre-resolved fetch operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + T FetchByOperation(string operationName, bool runLocal, object?[]? criteria); + + /// + /// Fetches a business object using a pre-resolved fetch operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + Task FetchByOperationAsync(string operationName, bool runLocal, object?[]? criteria); + + /// + /// Executes a command object using a pre-resolved execute operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + T ExecuteByOperation(string operationName, bool runLocal, object?[]? criteria); + + /// + /// Executes a command object using a pre-resolved execute operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + Task ExecuteByOperationAsync(string operationName, bool runLocal, object?[]? criteria); + + /// + /// Deletes a business object using a pre-resolved delete operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + void DeleteByOperation(string operationName, bool runLocal, object?[]? criteria); + + /// + /// Deletes a business object using a pre-resolved delete operation. + /// + /// Generated operation name. + /// True to run the operation on the client. + /// Criteria values, one element per criteria parameter. + Task DeleteByOperationAsync(string operationName, bool runLocal, object?[]? criteria); + } + + /// + /// Invokes child data portal operations whose target method has already + /// been resolved at compile time. Used by generated data portal extension + /// methods; not intended to be called directly. + /// + /// Type of business object. + [EditorBrowsable(EditorBrowsableState.Never)] + public interface IChildDataPortalOperationInvoker<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T> + where T : ICslaObject + { + /// + /// Creates a child business object using a pre-resolved create child operation. + /// + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + T CreateChildByOperation(string operationName, object?[]? criteria); + + /// + /// Creates a child business object using a pre-resolved create child operation. + /// + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + Task CreateChildByOperationAsync(string operationName, object?[]? criteria); + + /// + /// Fetches a child business object using a pre-resolved fetch child operation. + /// + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + T FetchChildByOperation(string operationName, object?[]? criteria); + + /// + /// Fetches a child business object using a pre-resolved fetch child operation. + /// + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + Task FetchChildByOperationAsync(string operationName, object?[]? criteria); + } +} diff --git a/Source/Csla/NameValueListBase.cs b/Source/Csla/NameValueListBase.cs index 7815f772ca..aeebc3889a 100644 --- a/Source/Csla/NameValueListBase.cs +++ b/Source/Csla/NameValueListBase.cs @@ -25,7 +25,7 @@ namespace Csla /// Type of the values. [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [Serializable] - public abstract class NameValueListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] K, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] V> : + public abstract partial class NameValueListBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] K, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] V> : ReadOnlyBindingList.NameValuePair>, ICloneable, IDataPortalTarget, diff --git a/Source/Csla/NoDataPortalExtensionAttribute.cs b/Source/Csla/NoDataPortalExtensionAttribute.cs new file mode 100644 index 0000000000..d079894e48 --- /dev/null +++ b/Source/Csla/NoDataPortalExtensionAttribute.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Excludes a class or operation method from data portal extension generation +//----------------------------------------------------------------------- + +namespace Csla +{ + /// + /// Excludes a business class, or a data portal operation method, from + /// strongly typed extension method generation requested by + /// . + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public sealed class NoDataPortalExtensionAttribute : Attribute; +} diff --git a/Source/Csla/ReadOnlyBase.cs b/Source/Csla/ReadOnlyBase.cs index 312c6b2d6a..c4f23dadf0 100644 --- a/Source/Csla/ReadOnlyBase.cs +++ b/Source/Csla/ReadOnlyBase.cs @@ -36,7 +36,7 @@ namespace Csla /// /// Type of the business object. [Serializable] - public abstract class ReadOnlyBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : BindableBase, + public abstract partial class ReadOnlyBase<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T> : BindableBase, IDataPortalTarget, IManageProperties, IReadOnlyBase, diff --git a/Source/Csla/ReadOnlyBindingListBase.cs b/Source/Csla/ReadOnlyBindingListBase.cs index ae84c39b65..a239118caf 100644 --- a/Source/Csla/ReadOnlyBindingListBase.cs +++ b/Source/Csla/ReadOnlyBindingListBase.cs @@ -22,7 +22,7 @@ namespace Csla [SuppressMessage( "Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [Serializable] - public abstract class ReadOnlyBindingListBase : + public abstract partial class ReadOnlyBindingListBase : ReadOnlyBindingList, IReadOnlyCollection, ICloneable, IDataPortalTarget, IUseApplicationContext where T : ReadOnlyBindingListBase diff --git a/Source/Csla/ReadOnlyListBase.cs b/Source/Csla/ReadOnlyListBase.cs index 9fba34f375..cae8705c78 100644 --- a/Source/Csla/ReadOnlyListBase.cs +++ b/Source/Csla/ReadOnlyListBase.cs @@ -24,7 +24,7 @@ namespace Csla /// Type of child objects contained in the list. [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] [Serializable] - public abstract class ReadOnlyListBase : + public abstract partial class ReadOnlyListBase : #if ANDROID || IOS Core.ReadOnlyBindingList, #else diff --git a/Source/Csla/Server/ChildDataPortal.cs b/Source/Csla/Server/ChildDataPortal.cs index ecfc6ca079..e97188a22c 100644 --- a/Source/Csla/Server/ChildDataPortal.cs +++ b/Source/Csla/Server/ChildDataPortal.cs @@ -6,8 +6,10 @@ // Invoke data portal methods on child //----------------------------------------------------------------------- +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using Csla.Core; +using Csla.Properties; namespace Csla.Server { @@ -43,7 +45,7 @@ public object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes. throw new ArgumentNullException(nameof(objectType)); return ExecuteWithAggregateExceptionHandling( - () => DoCreateAsync(objectType).Result + () => DoCreateAsync(objectType, null).Result ); } @@ -61,7 +63,7 @@ public object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes. throw new ArgumentNullException(nameof(objectType)); return ExecuteWithAggregateExceptionHandling( - () => DoCreateAsync(objectType, parameters).Result + () => DoCreateAsync(objectType, null, parameters).Result ); } @@ -70,7 +72,7 @@ public object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes. /// public async Task CreateAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() { - return (T) await DoCreateAsync(typeof(T)).ConfigureAwait(false); + return (T)await DoCreateAsync(typeof(T), null).ConfigureAwait(false); } /// @@ -81,10 +83,51 @@ public object Create([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes. /// public async Task CreateAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(params object?[]? parameters) { - return (T)await DoCreateAsync(typeof(T), parameters).ConfigureAwait(false); + return (T)await DoCreateAsync(typeof(T), null, parameters).ConfigureAwait(false); } - private async Task DoCreateAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, params object?[]? parameters) + /// + /// Create a new business object using a pre-resolved create child operation. + /// + /// Type of business object to create. + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + /// or is . + /// is or only consists of white spaces. + [EditorBrowsable(EditorBrowsableState.Never)] + public object CreateByOperation([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, string operationName, object?[]? parameters) + { + if (objectType is null) + throw new ArgumentNullException(nameof(objectType)); + if (operationName is null) + throw new ArgumentNullException(nameof(operationName)); + if (string.IsNullOrWhiteSpace(operationName)) + throw new ArgumentException(string.Format(Resources.StringNotNullOrWhiteSpaceException, nameof(operationName)), nameof(operationName)); + + return ExecuteWithAggregateExceptionHandling( + () => DoCreateAsync(objectType, operationName, parameters).Result + ); + } + + /// + /// Create a new business object using a pre-resolved create child operation. + /// + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + /// is . + /// is or only consists of white spaces. + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task CreateByOperationAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string operationName, object?[]? parameters) + { + if (operationName is null) + throw new ArgumentNullException(nameof(operationName)); + if (string.IsNullOrWhiteSpace(operationName)) + throw new ArgumentException(string.Format(Resources.StringNotNullOrWhiteSpaceException, nameof(operationName)), nameof(operationName)); + + return (T)await DoCreateAsync(typeof(T), operationName, parameters).ConfigureAwait(false); + } + + private async Task DoCreateAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, string? operationName, params object?[]? parameters) { DataPortalTarget? obj = null; var eventArgs = new DataPortalEventArgs(null, objectType, parameters, DataPortalOperations.Create); @@ -95,7 +138,7 @@ private async Task DoCreateAsync([DynamicallyAccessedMembers(Dynamically obj.Child_OnDataPortalInvoke(eventArgs); obj.MarkAsChild(); obj.MarkNew(); - await obj.CreateChildAsync(parameters).ConfigureAwait(false); + await obj.CreateChildAsync(operationName, parameters).ConfigureAwait(false); obj.Child_OnDataPortalInvokeComplete(eventArgs); return obj.Instance; @@ -134,7 +177,7 @@ public object Fetch([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.P throw new ArgumentNullException(nameof(objectType)); return ExecuteWithAggregateExceptionHandling( - () => DoFetchAsync(objectType).Result + () => DoFetchAsync(objectType, null).Result ); } @@ -152,7 +195,7 @@ public object Fetch([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.P throw new ArgumentNullException(nameof(objectType)); return ExecuteWithAggregateExceptionHandling( - () => DoFetchAsync(objectType, parameters).Result + () => DoFetchAsync(objectType, null, parameters).Result ); } @@ -161,7 +204,7 @@ public object Fetch([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.P /// public async Task FetchAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() { - return (T)await DoFetchAsync(typeof(T)).ConfigureAwait(false); + return (T)await DoFetchAsync(typeof(T), null).ConfigureAwait(false); } /// @@ -172,10 +215,51 @@ public object Fetch([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.P /// public async Task FetchAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(params object?[]? parameters) { - return (T)await DoFetchAsync(typeof(T), parameters).ConfigureAwait(false); + return (T)await DoFetchAsync(typeof(T), null, parameters).ConfigureAwait(false); + } + + /// + /// Get an existing business object using a pre-resolved fetch child operation. + /// + /// Type of business object to retrieve. + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + /// or is . + /// is or only consists of white spaces. + [EditorBrowsable(EditorBrowsableState.Never)] + public object FetchByOperation([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, string operationName, object?[]? parameters) + { + if (objectType is null) + throw new ArgumentNullException(nameof(objectType)); + if (operationName is null) + throw new ArgumentNullException(nameof(operationName)); + if (string.IsNullOrWhiteSpace(operationName)) + throw new ArgumentException(string.Format(Resources.StringNotNullOrWhiteSpaceException, nameof(operationName)), nameof(operationName)); + + return ExecuteWithAggregateExceptionHandling( + () => DoFetchAsync(objectType, operationName, parameters).Result + ); + } + + /// + /// Get an existing business object using a pre-resolved fetch child operation. + /// + /// Generated operation name. + /// Criteria values, one element per criteria parameter. + /// is . + /// is or only consists of white spaces. + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task FetchByOperationAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string operationName, object?[]? parameters) + { + if (operationName is null) + throw new ArgumentNullException(nameof(operationName)); + if (string.IsNullOrWhiteSpace(operationName)) + throw new ArgumentException(string.Format(Resources.StringNotNullOrWhiteSpaceException, nameof(operationName)), nameof(operationName)); + + return (T)await DoFetchAsync(typeof(T), operationName, parameters).ConfigureAwait(false); } - private async Task DoFetchAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, params object?[]? parameters) + private async Task DoFetchAsync([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type objectType, string? operationName, params object?[]? parameters) { DataPortalTarget? obj = null; var eventArgs = new DataPortalEventArgs(null, objectType, parameters, DataPortalOperations.Fetch); @@ -188,7 +272,7 @@ private async Task DoFetchAsync([DynamicallyAccessedMembers(DynamicallyA obj.Child_OnDataPortalInvoke(eventArgs); obj.MarkAsChild(); obj.MarkOld(); - await obj.FetchChildAsync(parameters).ConfigureAwait(false); + await obj.FetchChildAsync(operationName, parameters).ConfigureAwait(false); obj.Child_OnDataPortalInvokeComplete(eventArgs); return obj.Instance; } diff --git a/Source/Csla/Server/DataPortalOperationHelper.cs b/Source/Csla/Server/DataPortalOperationHelper.cs new file mode 100644 index 0000000000..fd48567787 --- /dev/null +++ b/Source/Csla/Server/DataPortalOperationHelper.cs @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Runtime support for source-generated data portal dispatch +//----------------------------------------------------------------------- + +using System.ComponentModel; +using Csla.Properties; +using Csla.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace Csla.Server +{ + /// + /// Runtime support used by source-generated data portal operation + /// dispatch code. Not intended to be called directly. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static class DataPortalOperationHelper + { + /// + /// Throws when a synchronous data portal call on the client + /// tries to invoke an asynchronous operation method, matching + /// the behavior of reflection-based dispatch. + /// + /// Business object declaring the method. + /// True if the client made a synchronous call. + /// Current service provider. + /// Name of the operation method. + /// or is . + public static void ThrowIfAsyncMethodOnSyncClient(object target, bool isSync, IServiceProvider serviceProvider, string methodName) + { + if (target is null) + throw new ArgumentNullException(nameof(target)); + if (serviceProvider is null) + throw new ArgumentNullException(nameof(serviceProvider)); + + if (!isSync) + return; + + if (serviceProvider.GetService() is { ExecutionLocation: ApplicationContext.ExecutionLocations.Server }) + return; + + throw CreateCallMethodException(target, methodName, + new NotSupportedException(string.Format(Resources.AsyncMethodOnSyncClientNotAllowed, methodName))); + } + + /// + /// Wraps an exception thrown by an operation method in a + /// , matching the behavior of + /// reflection-based dispatch. Because the exception is wrapped, an + /// operation method that throws + /// is not mistaken for an unmatched generated dispatch. + /// + /// Business object declaring the method. + /// Name of the operation method. + /// Exception thrown by the operation method. + /// is . + public static Exception CreateCallMethodException(object target, string methodName, Exception exception) + { + if (target is null) + throw new ArgumentNullException(nameof(target)); + + return new CallMethodException(target.GetType().Name + "." + methodName + " " + Resources.MethodCallFailed, exception); + } + } +} diff --git a/Source/Csla/Server/DataPortalTarget.cs b/Source/Csla/Server/DataPortalTarget.cs index c934ca1cae..d8e057a078 100644 --- a/Source/Csla/Server/DataPortalTarget.cs +++ b/Source/Csla/Server/DataPortalTarget.cs @@ -200,9 +200,26 @@ await _operationMapping.InvokeOperationAsync( await CallMethodTryAsyncDI(isSync, parameters).ConfigureAwait(false); } - private async Task InvokeChildOperationAsync(object?[]? parameters) + private async Task InvokeChildOperationAsync(object?[]? parameters, string? operationName = null) where T : DataPortalChildOperationAttribute { + // Try name-based dispatch first + if (operationName != null && _namedMapping != null) + { + try + { + var serviceProvider = _applicationContext.CurrentServiceProvider; + await _namedMapping.InvokeNamedOperationAsync( + operationName, false, parameters, serviceProvider + ).ConfigureAwait(false); + return; + } + catch (DataPortalOperationNotSupportedException) + { + // Fall through to criteria-based path + } + } + if (_operationMapping != null) { try @@ -231,6 +248,11 @@ public Task CreateChildAsync(params object?[]? parameters) return InvokeChildOperationAsync(parameters); } + public Task CreateChildAsync(string? operationName, object?[]? parameters) + { + return InvokeChildOperationAsync(parameters, operationName); + } + public Task FetchAsync(object criteria, bool isSync, string? operationName = null) { return InvokeOperationAsync(criteria, isSync, operationName); @@ -241,6 +263,11 @@ public Task FetchChildAsync(params object?[]? parameters) return InvokeChildOperationAsync(parameters); } + public Task FetchChildAsync(string? operationName, object?[]? parameters) + { + return InvokeChildOperationAsync(parameters, operationName); + } + public Task ExecuteAsync(object criteria, bool isSync, string? operationName = null) { return InvokeOperationAsync(criteria, isSync, operationName); diff --git a/Source/Csla/Server/IDataPortalOperationMapping.cs b/Source/Csla/Server/IDataPortalOperationMapping.cs index fb6d53ca4e..07694931f7 100644 --- a/Source/Csla/Server/IDataPortalOperationMapping.cs +++ b/Source/Csla/Server/IDataPortalOperationMapping.cs @@ -23,7 +23,10 @@ public interface IDataPortalOperationMapping /// Service provider for resolving injected dependencies /// A Task representing the async operation /// - /// Thrown when the operation/criteria combination is not handled by the generated code + /// Thrown when the operation/criteria combination is not handled by the generated code. + /// Exceptions thrown by the operation method itself must be wrapped (as + /// does) so they + /// are not mistaken for an unmatched operation, which would invoke it again through reflection. /// Task InvokeOperationAsync(Type operationType, bool isSync, object?[]? criteria, IServiceProvider serviceProvider); } diff --git a/Source/Csla/Server/IDataPortalOperationNamedMapping.cs b/Source/Csla/Server/IDataPortalOperationNamedMapping.cs index 9677c97084..9e90a1cf30 100644 --- a/Source/Csla/Server/IDataPortalOperationNamedMapping.cs +++ b/Source/Csla/Server/IDataPortalOperationNamedMapping.cs @@ -23,7 +23,10 @@ public interface IDataPortalOperationNamedMapping /// Service provider for resolving injected dependencies /// A Task representing the async operation /// - /// Thrown when the operation name is not handled by the generated code + /// Thrown when the operation name is not handled by the generated code. + /// Exceptions thrown by the operation method itself must be wrapped (as + /// does) so they + /// are not mistaken for an unmatched operation, which would invoke it again through reflection. /// Task InvokeNamedOperationAsync(string operationName, bool isSync, object?[]? criteria, IServiceProvider serviceProvider); } diff --git a/Source/Csla/buildTransitive/Csla.targets b/Source/Csla/buildTransitive/Csla.targets new file mode 100644 index 0000000000..afd344006c --- /dev/null +++ b/Source/Csla/buildTransitive/Csla.targets @@ -0,0 +1,27 @@ + + + + + + + + + + + <_CslaRetiredGeneratorPackage Include="@(PackageReference)" Condition="'%(Identity)' == 'Csla.Generator.AutoImplementProperties.CSharp'" /> + + + + diff --git a/Source/csla.build.sln b/Source/csla.build.sln index 59604984fd..715961a794 100644 --- a/Source/csla.build.sln +++ b/Source/csla.build.sln @@ -61,18 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoImplemen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerialization.CSharp", "Csla.Generators\cs\AutoSerialization\Csla.Generator.AutoSerialization.CSharp\Csla.Generator.AutoSerialization.CSharp.csproj", "{E838D906-6C66-40E7-3604-9CD97AFD47B0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoImplementProperties.Attributes.CSharp", "Csla.Generators\cs\AutoImplementProperties\Csla.Generator.AutoImplementProperties.Attributes.CSharp\Csla.Generator.AutoImplementProperties.Attributes.CSharp.csproj", "{CD5F6C1E-8D50-5A23-2733-ABB305796A22}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerialization.Attributes.CSharp", "Csla.Generators\cs\AutoSerialization\Csla.Generator.AutoSerialization.Attributes.CSharp\Csla.Generator.AutoSerialization.Attributes.CSharp.csproj", "{801AD1EE-6FA3-486F-9921-72342C0D1D9A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Csla.Generators", "Csla.Generators", "{4929775F-66F8-19A3-4CDD-3CBFB3383240}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cs", "cs", "{C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataPortalInterfaces", "DataPortalInterfaces", "{42F5FFB7-B37F-9894-5E51-AB6A953DA79E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.DataPortalInterfaces.CSharp", "Csla.Generators\cs\DataPortalInterfaces\Csla.Generator.DataPortalInterfaces.CSharp\Csla.Generator.DataPortalInterfaces.CSharp.csproj", "{0F476655-B155-432F-ADAC-CE9C77881950}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Channels.Wcf", "Csla.Channels.Wcf\Csla.Channels.Wcf.csproj", "{50A9114F-5D22-43EF-43CF-559234473966}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Testing", "Csla.Testing\Csla.Testing.csproj", "{5ECC7150-E415-4FD6-99A4-08110DB54EE1}" @@ -3183,206 +3173,6 @@ Global {E838D906-6C66-40E7-3604-9CD97AFD47B0}.Testing|x64.Build.0 = Debug|Any CPU {E838D906-6C66-40E7-3604-9CD97AFD47B0}.Testing|x86.ActiveCfg = Debug|Any CPU {E838D906-6C66-40E7-3604-9CD97AFD47B0}.Testing|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.AppStore|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.CD_ROM|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.DVD-5|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Any CPU.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|AnyOS.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|AnyOS.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|ARM.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|ARM.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|iPhone.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|iPhone.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|iPhoneSimulator.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Mixed Platforms.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Win32.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Win32.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Win64.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Win64.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x64.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x64.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x86.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x86.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Any CPU.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|AnyOS.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|AnyOS.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|ARM.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|ARM.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|iPhone.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|iPhone.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Win32.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Win32.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Win64.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Win64.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x64.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x64.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x86.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x86.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.SingleImage|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|AnyOS.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|AnyOS.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|iPhone.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|iPhone.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|iPhoneSimulator.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Mixed Platforms.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Win32.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Win32.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Win64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Win64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x86.Build.0 = Debug|Any CPU {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Ad-Hoc|AnyOS.ActiveCfg = Debug|Any CPU @@ -3583,206 +3373,6 @@ Global {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Testing|x64.Build.0 = Debug|Any CPU {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Testing|x86.ActiveCfg = Debug|Any CPU {801AD1EE-6FA3-486F-9921-72342C0D1D9A}.Testing|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.AppStore|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.CD_ROM|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Debug|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.DVD-5|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Debug|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Mono Release|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Any CPU.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|AnyOS.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|AnyOS.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|ARM.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|ARM.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhone.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhone.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win32.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win32.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win64.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|Win64.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x64.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x64.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x86.ActiveCfg = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Release|x86.Build.0 = Release|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.SingleImage|x86.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Any CPU.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Any CPU.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|AnyOS.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|AnyOS.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|ARM.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|ARM.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhone.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhone.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|iPhoneSimulator.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Mixed Platforms.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win32.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win32.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|Win64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x64.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x64.Build.0 = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x86.ActiveCfg = Debug|Any CPU - {0F476655-B155-432F-ADAC-CE9C77881950}.Testing|x86.Build.0 = Debug|Any CPU {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {50A9114F-5D22-43EF-43CF-559234473966}.Ad-Hoc|AnyOS.ActiveCfg = Release|Any CPU @@ -4406,11 +3996,7 @@ Global {55127B94-9D7B-4AB6-A2CD-2E66B91602E3} = {3C1B872F-4DDC-4758-82CE-A052E5909204} {3F0193BB-6B1D-E2A3-ABEB-538D3494C837} = {8736485A-15E4-42A0-91CC-AE785DAD5AB2} {E838D906-6C66-40E7-3604-9CD97AFD47B0} = {8736485A-15E4-42A0-91CC-AE785DAD5AB2} - {CD5F6C1E-8D50-5A23-2733-ABB305796A22} = {8736485A-15E4-42A0-91CC-AE785DAD5AB2} {801AD1EE-6FA3-486F-9921-72342C0D1D9A} = {8736485A-15E4-42A0-91CC-AE785DAD5AB2} - {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} = {4929775F-66F8-19A3-4CDD-3CBFB3383240} - {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} = {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} - {0F476655-B155-432F-ADAC-CE9C77881950} = {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} {50A9114F-5D22-43EF-43CF-559234473966} = {580424DE-C982-4E80-A948-EF42FC3AB110} {5ECC7150-E415-4FD6-99A4-08110DB54EE1} = {85799E16-9B3B-4BD5-98D6-238E31291134} {F6080E51-675F-47A8-B125-55DCF66359F8} = {A26EBA9F-1777-46F2-994A-4C4D3055873D} diff --git a/Source/csla.test.sln b/Source/csla.test.sln index 8376e00bd1..b71fe1bbc1 100644 --- a/Source/csla.test.sln +++ b/Source/csla.test.sln @@ -71,8 +71,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoImplemen EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerialization.CSharp", "Csla.Generators\cs\AutoSerialization\Csla.Generator.AutoSerialization.CSharp\Csla.Generator.AutoSerialization.CSharp.csproj", "{E838D906-6C66-40E7-3604-9CD97AFD47B0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoImplementProperties.Attributes.CSharp", "Csla.Generators\cs\AutoImplementProperties\Csla.Generator.AutoImplementProperties.Attributes.CSharp\Csla.Generator.AutoImplementProperties.Attributes.CSharp.csproj", "{CD5F6C1E-8D50-5A23-2733-ABB305796A22}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerialization.Attributes.CSharp", "Csla.Generators\cs\AutoSerialization\Csla.Generator.AutoSerialization.Attributes.CSharp\Csla.Generator.AutoSerialization.Attributes.CSharp.csproj", "{11AF2F64-BB8B-A5F7-DC5F-5BFD15CF9B2E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Windows.Tests", "tests\Csla.Windows.Tests\Csla.Windows.Tests.csproj", "{4A3CB14D-787F-4091-9BB3-53BB14D79C2A}" @@ -85,18 +83,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerializ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.AutoSerialization.CSharp.Tests", "tests\Csla.Generator.AutoSerialization.CSharp.Tests\Csla.Generator.AutoSerialization.CSharp.Tests.csproj", "{32BD631E-431B-95B5-2E49-160A06C1F2EB}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Csla.Generators", "Csla.Generators", "{4929775F-66F8-19A3-4CDD-3CBFB3383240}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cs", "cs", "{C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataPortalInterfaces", "DataPortalInterfaces", "{42F5FFB7-B37F-9894-5E51-AB6A953DA79E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.DataPortalInterfaces.CSharp", "Csla.Generators\cs\DataPortalInterfaces\Csla.Generator.DataPortalInterfaces.CSharp\Csla.Generator.DataPortalInterfaces.CSharp.csproj", "{D1ED800F-5D7F-43FA-BCAC-A0B244724103}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Generator.DataPortalInterfaces.CSharp.Tests", "tests\Csla.Generator.DataPortalInterfaces.CSharp.Tests\Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj", "{3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Channels.Wcf", "Csla.Channels.Wcf\Csla.Channels.Wcf.csproj", "{50A9114F-5D22-43EF-43CF-559234473966}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Csla.Testing", "Csla.Testing\Csla.Testing.csproj", "{45D65B40-1387-4D6F-AFA7-E8AE40E442FC}" @@ -1129,46 +1117,6 @@ Global {E838D906-6C66-40E7-3604-9CD97AFD47B0}.Testing|x64.Build.0 = Debug|Any CPU {E838D906-6C66-40E7-3604-9CD97AFD47B0}.Testing|x86.ActiveCfg = Debug|Any CPU {E838D906-6C66-40E7-3604-9CD97AFD47B0}.Testing|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Debug|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Debug|x86.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|Any CPU.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|ARM.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|ARM.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x64.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x64.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x86.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Mono Release|x86.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|Any CPU.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|ARM.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|ARM.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x64.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x64.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x86.ActiveCfg = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Release|x86.Build.0 = Release|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Any CPU.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|Any CPU.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|ARM.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|ARM.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x64.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x64.Build.0 = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x86.ActiveCfg = Debug|Any CPU - {CD5F6C1E-8D50-5A23-2733-ABB305796A22}.Testing|x86.Build.0 = Debug|Any CPU {11AF2F64-BB8B-A5F7-DC5F-5BFD15CF9B2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {11AF2F64-BB8B-A5F7-DC5F-5BFD15CF9B2E}.Debug|Any CPU.Build.0 = Debug|Any CPU {11AF2F64-BB8B-A5F7-DC5F-5BFD15CF9B2E}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -1409,86 +1357,6 @@ Global {32BD631E-431B-95B5-2E49-160A06C1F2EB}.Testing|x64.Build.0 = Debug|Any CPU {32BD631E-431B-95B5-2E49-160A06C1F2EB}.Testing|x86.ActiveCfg = Debug|Any CPU {32BD631E-431B-95B5-2E49-160A06C1F2EB}.Testing|x86.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|ARM.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|ARM.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x64.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x64.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x86.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Debug|x86.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|ARM.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x64.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x64.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x86.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Debug|x86.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|Any CPU.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|Any CPU.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|ARM.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|ARM.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x64.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x64.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x86.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Mono Release|x86.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|Any CPU.Build.0 = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|ARM.ActiveCfg = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|ARM.Build.0 = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x64.ActiveCfg = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x64.Build.0 = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x86.ActiveCfg = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Release|x86.Build.0 = Release|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|Any CPU.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|Any CPU.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|ARM.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|ARM.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x64.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x64.Build.0 = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x86.ActiveCfg = Debug|Any CPU - {D1ED800F-5D7F-43FA-BCAC-A0B244724103}.Testing|x86.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|ARM.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|ARM.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x64.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x64.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x86.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Debug|x86.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|ARM.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|ARM.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x64.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x64.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x86.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Debug|x86.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|Any CPU.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|Any CPU.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|ARM.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|ARM.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x64.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x64.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x86.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Mono Release|x86.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|Any CPU.Build.0 = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|ARM.ActiveCfg = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|ARM.Build.0 = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x64.ActiveCfg = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x64.Build.0 = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x86.ActiveCfg = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Release|x86.Build.0 = Release|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|Any CPU.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|Any CPU.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|ARM.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|ARM.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x64.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x64.Build.0 = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x86.ActiveCfg = Debug|Any CPU - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50}.Testing|x86.Build.0 = Debug|Any CPU {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {50A9114F-5D22-43EF-43CF-559234473966}.Debug|Any CPU.Build.0 = Debug|Any CPU {50A9114F-5D22-43EF-43CF-559234473966}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -1671,16 +1539,11 @@ Global {9D8A32E2-E82E-425C-A8EA-AC2BC17CCA0D} = {01AA1A57-727E-4665-93FF-6E8BDD316ADE} {3F0193BB-6B1D-E2A3-ABEB-538D3494C837} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {E838D906-6C66-40E7-3604-9CD97AFD47B0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {CD5F6C1E-8D50-5A23-2733-ABB305796A22} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {11AF2F64-BB8B-A5F7-DC5F-5BFD15CF9B2E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {D70E04EB-B295-AF38-818C-3972657B18E8} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {916A3B6C-8EFA-DAE5-09A8-7E38A03F033C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {3B24B172-0CD6-FD10-A771-1E90C7E69116} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {32BD631E-431B-95B5-2E49-160A06C1F2EB} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} = {4929775F-66F8-19A3-4CDD-3CBFB3383240} - {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} = {C6BA87DF-68CB-9DDE-CDC6-9DA753FB50D8} - {D1ED800F-5D7F-43FA-BCAC-A0B244724103} = {42F5FFB7-B37F-9894-5E51-AB6A953DA79E} - {3CA1B447-AD2D-4ED1-B005-0E9968ED9C50} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {50A9114F-5D22-43EF-43CF-559234473966} = {01AA1A57-727E-4665-93FF-6E8BDD316ADE} {45D65B40-1387-4D6F-AFA7-E8AE40E442FC} = {01AA1A57-727E-4665-93FF-6E8BDD316ADE} {8276B605-DBCF-484A-9603-BA1A6201A9F3} = {0AB3BF05-4346-4AA6-1389-037BE0695223} diff --git a/Source/tests/Csla.Analyzers.Tests/TestHelpers.cs b/Source/tests/Csla.Analyzers.Tests/TestHelpers.cs index df0cadd18d..b16c85af16 100644 --- a/Source/tests/Csla.Analyzers.Tests/TestHelpers.cs +++ b/Source/tests/Csla.Analyzers.Tests/TestHelpers.cs @@ -23,10 +23,10 @@ internal static async Task VerifyChangesAsync(List actions, string t } internal static async Task RunAnalysisAsync(string code, string[] diagnosticIds, - Action> diagnosticInspector = null) + Action> diagnosticInspector = null, Dictionary globalOptions = null) where T : DiagnosticAnalyzer, new() { - var diagnostics = await GetDiagnosticsAsync(code, new T()); + var diagnostics = await GetDiagnosticsAsync(code, new T(), globalOptions); Assert.AreEqual(diagnosticIds.Length, diagnostics.Count, nameof(diagnostics.Count)); foreach (var diagnosticId in diagnosticIds) @@ -37,14 +37,33 @@ internal static async Task RunAnalysisAsync(string code, string[] diagnosticI diagnosticInspector?.Invoke(diagnostics); } - internal static async Task> GetDiagnosticsAsync(string code, DiagnosticAnalyzer analyzer) + internal static async Task> GetDiagnosticsAsync(string code, DiagnosticAnalyzer analyzer, + Dictionary globalOptions = null) { var document = Create(code); + var options = new AnalyzerOptions([], new GlobalOptionsProvider(globalOptions ?? [])); var compilation = (await document.Project.GetCompilationAsync()) - .WithAnalyzers([analyzer]); + .WithAnalyzers([analyzer], options); return (await compilation.GetAnalyzerDiagnosticsAsync()).ToList(); } + /// + /// Supplies global build properties, such as build_property.* values, to analyzers. + /// + private sealed class GlobalOptionsProvider(Dictionary values) : AnalyzerConfigOptionsProvider + { + public override AnalyzerConfigOptions GlobalOptions { get; } = new Options(values); + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => GlobalOptions; + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => GlobalOptions; + + private sealed class Options(Dictionary values) : AnalyzerConfigOptions + { + public override bool TryGetValue(string key, out string value) => values.TryGetValue(key, out value); + } + } + internal static Document Create(string code) { var projectName = "Test"; diff --git a/Source/tests/Csla.Analyzers.Tests/TypeWithOperationsShouldBePartialAddPartialCodeFixTests.cs b/Source/tests/Csla.Analyzers.Tests/TypeWithOperationsShouldBePartialAddPartialCodeFixTests.cs new file mode 100644 index 0000000000..31b20d0c6a --- /dev/null +++ b/Source/tests/Csla.Analyzers.Tests/TypeWithOperationsShouldBePartialAddPartialCodeFixTests.cs @@ -0,0 +1,183 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Immutable; + +namespace Csla.Analyzers.Tests +{ + [TestClass] + public sealed class TypeWithOperationsShouldBePartialAddPartialCodeFixTests + { + [TestMethod] + public void VerifyGetFixableDiagnosticIds() + { + var fix = new TypeWithOperationsShouldBePartialAddPartialCodeFix(); + var ids = fix.FixableDiagnosticIds.ToList(); + + Assert.AreEqual(1, ids.Count, nameof(ids.Count)); + Assert.AreEqual(ids[0], Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial, + nameof(Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial)); + } + + [TestMethod] + public async Task VerifyGetFixesWhenClassIsNotPartial() + { + var code = + """ + using Csla; + using System; + + [Serializable] + public sealed class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + """; + var expected = + """ + using Csla; + using System; + + [Serializable] + public sealed partial class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + """; + await VerifyFixAsync(code, expected); + } + + [TestMethod] + public async Task VerifyGetFixesWhenNestedClassAndContainingTypesAreNotPartial() + { + var code = + """ + using Csla; + using System; + + class Outer + { + public static partial class Middle + { + internal static class Inner + { + /// + /// Business class + /// + [Serializable] + public abstract class A : BusinessBase + where T : A + { + [Fetch] + protected void Fetch(int id) { } + } + } + } + } + """; + var expected = + """ + using Csla; + using System; + + partial class Outer + { + public static partial class Middle + { + internal static partial class Inner + { + /// + /// Business class + /// + [Serializable] + public abstract partial class A : BusinessBase + where T : A + { + [Fetch] + protected void Fetch(int id) { } + } + } + } + } + """; + await VerifyFixAsync(code, expected); + } + + [TestMethod] + public async Task VerifyGetFixesWhenClassHasNoModifiers() + { + var code = + """ + using Csla; + using System; + + namespace N + { + /// + /// Business class + /// + class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + var expected = + """ + using Csla; + using System; + + namespace N + { + /// + /// Business class + /// + partial class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + await VerifyFixAsync(code, expected); + } + + private static async Task VerifyFixAsync(string code, string expected) + { + var document = TestHelpers.Create(code); + var diagnostics = await TestHelpers.GetDiagnosticsAsync(code, new TypeWithOperationsShouldBePartialAnalyzer()); + Assert.AreEqual(1, diagnostics.Count, nameof(diagnostics.Count)); + + var actions = new List(); + var codeActionRegistration = new Action>( + (a, _) => { actions.Add(a); }); + + var fix = new TypeWithOperationsShouldBePartialAddPartialCodeFix(); + var codeFixContext = new CodeFixContext(document, diagnostics[0], + codeActionRegistration, new CancellationToken(false)); + await fix.RegisterCodeFixesAsync(codeFixContext); + + Assert.AreEqual(1, actions.Count); + + await TestHelpers.VerifyChangesAsync(actions, + TypeWithOperationsShouldBePartialAddPartialCodeFixConstants.AddPartialDescription, document, + (model, newRoot) => + { + Assert.AreEqual(expected, newRoot.ToFullString()); + foreach (var typeNode in newRoot.DescendantNodes().OfType()) + { + Assert.IsTrue(typeNode.Modifiers.Any(SyntaxKind.PartialKeyword), typeNode.Identifier.Text); + } + }); + + var fixedDiagnostics = await TestHelpers.GetDiagnosticsAsync(expected, new TypeWithOperationsShouldBePartialAnalyzer()); + Assert.AreEqual(0, fixedDiagnostics.Count, nameof(fixedDiagnostics.Count)); + } + } +} diff --git a/Source/tests/Csla.Analyzers.Tests/TypeWithOperationsShouldBePartialAnalyzerTests.cs b/Source/tests/Csla.Analyzers.Tests/TypeWithOperationsShouldBePartialAnalyzerTests.cs new file mode 100644 index 0000000000..5e68fda82e --- /dev/null +++ b/Source/tests/Csla.Analyzers.Tests/TypeWithOperationsShouldBePartialAnalyzerTests.cs @@ -0,0 +1,264 @@ +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Csla.Analyzers.Tests +{ + [TestClass] + public sealed class TypeWithOperationsShouldBePartialAnalyzerTests + { + [TestMethod] + public void VerifySupportedDiagnostics() + { + var analyzer = new TypeWithOperationsShouldBePartialAnalyzer(); + var diagnostics = analyzer.SupportedDiagnostics; + Assert.AreEqual(1, diagnostics.Length); + + var diagnostic = diagnostics[0]; + Assert.AreEqual(Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial, diagnostic.Id, + nameof(DiagnosticDescriptor.Id)); + Assert.AreEqual("Type with data portal operation methods should be partial", diagnostic.Title.ToString(), + nameof(DiagnosticDescriptor.Title)); + Assert.AreEqual("Type '{0}' has data portal operation methods and should be declared partial so the CSLA source generator can implement its operations interface", + diagnostic.MessageFormat.ToString(), + nameof(DiagnosticDescriptor.MessageFormat)); + Assert.AreEqual(Constants.Categories.Usage, diagnostic.Category, + nameof(DiagnosticDescriptor.Category)); + Assert.AreEqual(DiagnosticSeverity.Warning, diagnostic.DefaultSeverity, + nameof(DiagnosticDescriptor.DefaultSeverity)); + Assert.AreEqual(HelpUrlBuilder.Build(Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial, nameof(TypeWithOperationsShouldBePartialAnalyzer)), + diagnostic.HelpLinkUri, + nameof(DiagnosticDescriptor.HelpLinkUri)); + } + + [TestMethod] + public async Task AnalyzeWhenClassIsNotPartialAndHasOperationMethod() + { + var code = + """ + using Csla; + using System; + + [Serializable] + public class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + """; + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial], + diagnostics => + { + Assert.AreEqual("Type 'A' has data portal operation methods and should be declared partial so the CSLA source generator can implement its operations interface", + diagnostics[0].GetMessage()); + var location = diagnostics[0].Location; + Assert.AreEqual("A", location.SourceTree.GetText().ToString(location.SourceSpan)); + }); + } + + [TestMethod] + public async Task AnalyzeWhenClassIsNotPartialAndHasChildOperationMethod() + { + var code = + """ + using Csla; + using System; + + [Serializable] + public class A : BusinessBase + { + [UpdateChild] + private void Child_Update() { } + } + """; + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial]); + } + + [TestMethod] + public async Task AnalyzeWhenClassIsNotPartialAndHasMultipleOperationMethods() + { + var code = + """ + using Csla; + using System; + + [Serializable] + public class A : BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + private void Fetch(int id) { } + + [Insert] + private void Insert() { } + } + """; + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial]); + } + + [TestMethod] + public async Task AnalyzeWhenClassIsPartial() + { + var code = + """ + using Csla; + using System; + + [Serializable] + public partial class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + """; + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenClassHasNoOperationMethods() + { + var code = + """ + using Csla; + using System; + + [Serializable] + public class A : BusinessBase + { + private void DataPortal_Fetch(int id) { } + + public void DoWork() { } + } + """; + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenNestedClassIsPartialButContainingClassIsNot() + { + var code = + """ + using Csla; + using System; + + public class Outer + { + [Serializable] + public partial class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial], + diagnostics => Assert.IsTrue(diagnostics[0].GetMessage().Contains("'A'"))); + } + + [TestMethod] + public async Task AnalyzeWhenNestedClassAndContainingClassesArePartial() + { + var code = + """ + using Csla; + using System; + + public partial class Outer + { + public static partial class Middle + { + [Serializable] + public partial class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + } + """; + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenNestedClassAndContainingClassAreNotPartial() + { + var code = + """ + using Csla; + using System; + + public class Outer + { + [Serializable] + public class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial]); + } + + [TestMethod] + public async Task AnalyzeWhenClassIsAbstract() + { + var code = + """ + using Csla; + using System; + + [Serializable] + public abstract class A : BusinessBase + where T : A + { + [Fetch] + protected void Fetch(int id) { } + } + """; + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.TypeWithOperationsShouldBePartial]); + } + + [TestMethod] + public async Task AnalyzeWhenTypeIsRecord() + { + var code = + """ + using Csla; + + public record A + { + [Fetch] + private void Fetch(int id) { } + } + """; + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenCodeIsGenerated() + { + var code = + """ + // + using Csla; + using System; + + [Serializable] + public class A : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + """; + await TestHelpers.RunAnalysisAsync(code, []); + } + } +} diff --git a/Source/tests/Csla.Analyzers.Tests/UseGeneratedDataPortalExtensionAnalyzerTests.cs b/Source/tests/Csla.Analyzers.Tests/UseGeneratedDataPortalExtensionAnalyzerTests.cs new file mode 100644 index 0000000000..41031a2879 --- /dev/null +++ b/Source/tests/Csla.Analyzers.Tests/UseGeneratedDataPortalExtensionAnalyzerTests.cs @@ -0,0 +1,499 @@ +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Csla.Analyzers.Tests +{ + [TestClass] + public sealed class UseGeneratedDataPortalExtensionAnalyzerTests + { + private const string BusinessTypes = + """ + [DataPortalExtensions] + [Serializable] + public partial class A : BusinessBase + { + [Create] + private void New() { } + + [Fetch] + private void GetById(int id) { } + + [Delete] + private void Remove(int id) { } + + [FetchChild] + private void LoadChild(int id) { } + + [CreateChild] + private void NewChild() { } + } + + [DataPortalExtensions] + [Serializable] + public partial class C : CommandBase + { + [Execute] + private void Run(int id) { } + } + + [Serializable] + public partial class B : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + """; + + private static string CreateCode(string body, string header = "", string additionalTypes = "", string assemblyAttributes = "") => + $$""" + {{header}} + using Csla; + using System; + using System.Threading.Tasks; + + {{assemblyAttributes}} + + {{BusinessTypes}} + + {{additionalTypes}} + + public class Consumer + { + public async Task Run(IDataPortal portal, IChildDataPortal childPortal, + DataPortal concretePortal, IDataPortal otherPortal, IDataPortal commandPortal, C command) + { + {{body}} + await Task.CompletedTask; + } + } + """; + + [TestMethod] + public void VerifySupportedDiagnostics() + { + var analyzer = new UseGeneratedDataPortalExtensionAnalyzer(); + var diagnostics = analyzer.SupportedDiagnostics; + Assert.AreEqual(1, diagnostics.Length); + + var diagnostic = diagnostics[0]; + Assert.AreEqual(Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, diagnostic.Id, + nameof(DiagnosticDescriptor.Id)); + Assert.AreEqual("Use generated data portal extension method", diagnostic.Title.ToString(), + nameof(DiagnosticDescriptor.Title)); + Assert.AreEqual("Use the generated data portal extension method for '{0}' instead of calling '{1}' with untyped criteria", + diagnostic.MessageFormat.ToString(), + nameof(DiagnosticDescriptor.MessageFormat)); + Assert.AreEqual(Constants.Categories.Usage, diagnostic.Category, + nameof(DiagnosticDescriptor.Category)); + Assert.AreEqual(DiagnosticSeverity.Warning, diagnostic.DefaultSeverity, + nameof(DiagnosticDescriptor.DefaultSeverity)); + Assert.AreEqual(HelpUrlBuilder.Build(Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, nameof(UseGeneratedDataPortalExtensionAnalyzer)), + diagnostic.HelpLinkUri, + nameof(DiagnosticDescriptor.HelpLinkUri)); + } + + [TestMethod] + public async Task AnalyzeWhenFetchAsyncIsCalledOnDataPortal() + { + var code = CreateCode("await portal.FetchAsync(1);"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension], + diagnostics => + { + Assert.AreEqual("Use the generated data portal extension method for 'A' instead of calling 'FetchAsync' with untyped criteria", + diagnostics[0].GetMessage()); + var location = diagnostics[0].Location; + Assert.AreEqual("FetchAsync", location.SourceTree.GetText().ToString(location.SourceSpan)); + }); + } + + [TestMethod] + public async Task AnalyzeWhenFetchIsCalledOnDataPortal() + { + var code = CreateCode("portal.Fetch(1);"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenCreateIsCalledOnDataPortal() + { + var code = CreateCode("portal.Create();"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenCreateAsyncIsCalledOnDataPortal() + { + var code = CreateCode("await portal.CreateAsync();"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenDeleteIsCalledOnDataPortal() + { + var code = CreateCode("portal.Delete(1);"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenDeleteAsyncIsCalledOnDataPortal() + { + var code = CreateCode("await portal.DeleteAsync(1);"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenExecuteWithCriteriaIsCalledOnDataPortal() + { + var code = CreateCode("commandPortal.Execute(1);"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenExecuteAsyncWithCriteriaIsCalledOnDataPortal() + { + var code = CreateCode("await commandPortal.ExecuteAsync(1);"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenExecuteWithCommandIsCalledOnDataPortal() + { + var code = CreateCode("commandPortal.Execute(command);"); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenExecuteAsyncWithCommandIsCalledOnDataPortal() + { + var code = CreateCode("await commandPortal.ExecuteAsync(command);"); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenFetchChildIsCalledOnChildDataPortal() + { + var code = CreateCode("childPortal.FetchChild(1);"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension], + diagnostics => Assert.AreEqual( + "Use the generated data portal extension method for 'A' instead of calling 'FetchChild' with untyped criteria", + diagnostics[0].GetMessage())); + } + + [TestMethod] + public async Task AnalyzeWhenCreateChildAsyncIsCalledOnChildDataPortal() + { + var code = CreateCode("await childPortal.CreateChildAsync();"); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenMethodsAreCalledOnConcreteDataPortal() + { + var code = CreateCode( + """ + concretePortal.Fetch(1); + await concretePortal.FetchChildAsync(1); + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenBusinessTypeDoesNotHaveDataPortalExtensionsAttribute() + { + var code = CreateCode( + """ + await otherPortal.FetchAsync(1); + otherPortal.Fetch(1); + """); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenUpdateIsCalledOnDataPortal() + { + var code = CreateCode( + """ + var item = await portal.CreateAsync(); + await portal.UpdateAsync(item); + portal.Update(item); + await concretePortal.UpdateAsync(item); + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension], + diagnostics => Assert.IsTrue(diagnostics[0].GetMessage().Contains("'CreateAsync'"))); + } + + [TestMethod] + public async Task AnalyzeWhenCriteriaArrayIsPassed() + { + var code = CreateCode( + """ + object[] criteria = [1]; + await portal.FetchAsync(criteria); + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenCriteriaCountDoesNotMatchAnyExtension() + { + var code = CreateCode( + """ + await portal.FetchAsync(1, 2); + await portal.CreateAsync(1); + """); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenCriteriaTypesDoNotMatchAnyExtension() + { + var code = CreateCode( + """ + await portal.FetchAsync("x"); + childPortal.FetchChild(1.5); + """); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenCriteriaConvertToExtensionParameters() + { + var code = CreateCode( + """ + short id = 1; + await portal.FetchAsync(id); + IDataPortal dPortal = null; + await dPortal.FetchAsync(null, "a", "b"); + """, + additionalTypes: + """ + [DataPortalExtensions] + [Serializable] + public partial class D : BusinessBase + { + [Fetch] + private void GetByNames(string category, params string[] names) { } + } + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenPreferredOverloadHasNoDataPortalExtension() + { + var code = CreateCode( + """ + IDataPortal dPortal = null; + await dPortal.FetchAsync(1); + """, + additionalTypes: + """ + public interface IDal { } + + [DataPortalExtensions] + [Serializable] + public partial class D : BusinessBase + { + [Fetch] + private void GetById(int id) { } + + [NoDataPortalExtension] + [Fetch] + private void GetById(int id, [Inject] IDal dal) { } + } + """); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenNoExtensionIsGeneratedForTheOperation() + { + var code = CreateCode( + """ + IDataPortal dPortal = null; + IChildDataPortal dChildPortal = null; + await dPortal.FetchAsync(1); + dPortal.Delete(1); + await dPortal.CreateAsync("a"); + await dPortal.ExecuteAsync(1); + dChildPortal.CreateChild(1); + """, + additionalTypes: + """ + [DataPortalExtensions] + [Serializable] + public partial class D : BusinessBase + { + private class Secret { } + + [NoDataPortalExtension] + [Fetch] + private void GetById(int id) { } + + [Delete] + private void Delete(int id) { } + + [Create] + private void NewWith(Secret secret) { } + + [Create] + private void NewByRef(ref string name) { } + + [FetchChild] + private void LoadChild(int id) { } + } + """); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenRootOperationNameIsOnlyAChildPortalMember() + { + var code = CreateCode( + """ + IDataPortal dPortal = null; + await dPortal.CreateAsync(1); + """, + additionalTypes: + """ + [DataPortalExtensions] + [Serializable] + public partial class D : BusinessBase + { + [Create] + private void CreateChild(int id) { } + } + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension]); + } + + [TestMethod] + public async Task AnalyzeWhenPrefixIsInvalid() + { + var code = CreateCode( + """ + IDataPortal dPortal = null; + await dPortal.FetchAsync(1); + """, + additionalTypes: + """ + [DataPortalExtensions(Prefix = "My-")] + [Serializable] + public partial class D : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + """); + await TestHelpers.RunAnalysisAsync(code, []); + } + + [TestMethod] + public async Task AnalyzeWhenSyncExtensionsAreNotGenerated() + { + var code = CreateCode( + """ + portal.Fetch(1); + childPortal.FetchChild(1); + commandPortal.Execute(1); + await portal.FetchAsync(1); + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension], + diagnostics => StringAssert.Contains(diagnostics[0].GetMessage(), "'FetchAsync'"), + new Dictionary { ["build_property.CslaGenerateSyncDataPortalExtensions"] = "false" }); + } + + [TestMethod] + public async Task AnalyzeWhenAssemblyHasDataPortalExtensionsAttribute() + { + var code = CreateCode( + """ + IDataPortal dPortal = null; + IDataPortal ePortal = null; + IDataPortal fPortal = null; + await dPortal.FetchAsync(1); + await ePortal.FetchAsync(1); + await fPortal.FetchAsync(1); + """, + assemblyAttributes: """[assembly: DataPortalExtensions(Prefix = "Portal")]""", + additionalTypes: + """ + [Serializable] + public partial class D : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + + [NoDataPortalExtension] + [Serializable] + public partial class E : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + + [DataPortalExtensions(Prefix = "My")] + [Serializable] + public partial class F : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension], + diagnostics => + { + var names = diagnostics + .Select(d => d.Properties[UseGeneratedDataPortalExtensionAnalyzer.ExtensionNamesProperty]) + .OrderBy(n => n) + .ToList(); + CollectionAssert.AreEqual(new[] { "MyFetchAsync", "PortalFetchAsync" }, names); + }); + } + + [TestMethod] + public async Task AnalyzeWhenAsyncSuffixIsConfigured() + { + var code = CreateCode( + """ + await portal.FetchAsync(1); + portal.Fetch(1); + """); + await TestHelpers.RunAnalysisAsync(code, + [Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension], + diagnostics => Assert.AreEqual("GetById", diagnostics[0].Properties[UseGeneratedDataPortalExtensionAnalyzer.ExtensionNamesProperty]), + new Dictionary { ["build_property.CslaDataPortalExtensionsAsyncSuffix"] = "none" }); + } + + [TestMethod] + public async Task AnalyzeWhenCodeIsGenerated() + { + var code = CreateCode( + """ + await portal.FetchAsync(1); + childPortal.FetchChild(1); + commandPortal.Execute(1); + """, + "// "); + await TestHelpers.RunAnalysisAsync(code, []); + } + } +} diff --git a/Source/tests/Csla.Analyzers.Tests/UseGeneratedDataPortalExtensionCodeFixTests.cs b/Source/tests/Csla.Analyzers.Tests/UseGeneratedDataPortalExtensionCodeFixTests.cs new file mode 100644 index 0000000000..0a312e068a --- /dev/null +++ b/Source/tests/Csla.Analyzers.Tests/UseGeneratedDataPortalExtensionCodeFixTests.cs @@ -0,0 +1,142 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Immutable; + +namespace Csla.Analyzers.Tests +{ + [TestClass] + public sealed class UseGeneratedDataPortalExtensionCodeFixTests + { + [TestMethod] + public void VerifyGetFixableDiagnosticIds() + { + var fix = new UseGeneratedDataPortalExtensionCodeFix(); + var ids = fix.FixableDiagnosticIds.ToList(); + + Assert.AreEqual(1, ids.Count, nameof(ids.Count)); + Assert.AreEqual(Constants.AnalyzerIdentifiers.UseGeneratedDataPortalExtension, ids[0]); + } + + [TestMethod] + public async Task VerifyGetFixesReplacesCall() + { + var code = + """ + using Csla; + using System; + using System.Threading.Tasks; + + [DataPortalExtensions] + [Serializable] + public partial class A : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + + public class Consumer + { + public Task Run(IDataPortal portal) => portal.FetchAsync(1); + } + """; + var expected = code.Replace("portal.FetchAsync(1)", "portal.GetByIdAsync(1)"); + await VerifyFixAsync(code, "GetByIdAsync", expected, expectedActionCount: 1); + } + + [TestMethod] + public async Task VerifyGetFixesAddsUsingForBusinessNamespace() + { + var code = + """ + using Csla; + using System; + using System.Threading.Tasks; + + namespace Models + { + [DataPortalExtensions(Prefix = "Portal")] + [Serializable] + public partial class A : BusinessBase + { + [FetchChild] + private void Load(int id, string name) { } + } + } + + namespace Consumers + { + public class Consumer + { + public Models.A Run(IChildDataPortal portal) => portal.FetchChild(1, "x"); + } + } + """; + var fixedCode = await ApplyFixAsync(code, "PortalLoad", expectedActionCount: 1); + + StringAssert.Contains(fixedCode, "portal.PortalLoad(1, \"x\")"); + StringAssert.Contains(fixedCode, "using Models;"); + } + + [TestMethod] + public async Task VerifyGetFixesOffersEachMatchingExtension() + { + var code = + """ + using Csla; + using System; + using System.Threading.Tasks; + + [DataPortalExtensions] + [Serializable] + public partial class A : BusinessBase + { + [Fetch] + private void GetById(int id) { } + + [Fetch] + private void GetByNumber(long number) { } + } + + public class Consumer + { + public Task Run(IDataPortal portal) => portal.FetchAsync(1); + } + """; + var fixedCode = await ApplyFixAsync(code, "GetByNumberAsync", expectedActionCount: 2); + + StringAssert.Contains(fixedCode, "portal.GetByNumberAsync(1)"); + } + + private static async Task VerifyFixAsync(string code, string extensionName, string expected, int expectedActionCount) + { + var fixedCode = await ApplyFixAsync(code, extensionName, expectedActionCount); + Assert.AreEqual(expected, fixedCode); + } + + private static async Task ApplyFixAsync(string code, string extensionName, int expectedActionCount) + { + var document = TestHelpers.Create(code); + var diagnostics = await TestHelpers.GetDiagnosticsAsync(code, new UseGeneratedDataPortalExtensionAnalyzer()); + Assert.AreEqual(1, diagnostics.Count, nameof(diagnostics.Count)); + + var actions = new List(); + var codeActionRegistration = new Action>( + (a, _) => { actions.Add(a); }); + + var fix = new UseGeneratedDataPortalExtensionCodeFix(); + var codeFixContext = new CodeFixContext(document, diagnostics[0], + codeActionRegistration, new CancellationToken(false)); + await fix.RegisterCodeFixesAsync(codeFixContext); + + Assert.AreEqual(expectedActionCount, actions.Count, nameof(actions.Count)); + + string result = null; + await TestHelpers.VerifyChangesAsync(actions, + string.Format(UseGeneratedDataPortalExtensionCodeFixConstants.UseExtensionDescription, extensionName), document, + (_, newRoot) => result = newRoot.ToFullString()); + return result; + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.TestObjects/Csla.Generator.AutoImplementProperties.TestObjects.csproj b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.TestObjects/Csla.Generator.AutoImplementProperties.TestObjects.csproj index a2e11603e6..4483b810f5 100644 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.TestObjects/Csla.Generator.AutoImplementProperties.TestObjects.csproj +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.TestObjects/Csla.Generator.AutoImplementProperties.TestObjects.csproj @@ -7,7 +7,6 @@ - diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Csla.Generator.AutoImplementProperties.CSharp.Tests.csproj b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Csla.Generator.AutoImplementProperties.CSharp.Tests.csproj index 0f761a40d6..d352f198e6 100644 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Csla.Generator.AutoImplementProperties.CSharp.Tests.csproj +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Csla.Generator.AutoImplementProperties.CSharp.Tests.csproj @@ -30,7 +30,7 @@ - + diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/DataPortalExtensionsGeneratorTests.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/DataPortalExtensionsGeneratorTests.cs new file mode 100644 index 0000000000..7a07590b55 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/DataPortalExtensionsGeneratorTests.cs @@ -0,0 +1,683 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +//----------------------------------------------------------------------- +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalExtensions; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations; +using Csla.Generator.AutoImplementProperties.CSharp.Tests.DataPortalOperations; + +namespace Csla.Generator.AutoImplementProperties.CSharp.Tests.DataPortalExtensions +{ + [TestClass] + public class DataPortalExtensionsGeneratorTests : VerifyBase + { + [TestMethod("Async and sync extensions for root operations")] + public async Task RootOperations() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Create] + private void New() { } + + [Fetch] + private async Task GetById(int id, [Inject] IDal dal) { await Task.CompletedTask; } + + [Delete] + private Task RemoveAsync(int id) => Task.CompletedTask; + + [RunLocal] + [Fetch] + private void GetLocal([Inject] IDal dal, string code) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Extensions for child operations")] + public async Task ChildOperations() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class LineItem : BusinessBase + { + [CreateChild] + private void NewChild() { } + + [FetchChild] + private void LoadChild(int id, string name) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Execute operation on a command object")] + public async Task ExecuteCommand() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public class RecalculateCommand : CommandBase + { + [Execute] + private void Run(int count) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Prefix gives extensions distinct names")] + public async Task Prefix() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions(Prefix = "Portal")] + public partial class PersonEdit : BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Names that would be hidden by data portal instance methods are reported")] + public async Task HiddenNames() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + + [Fetch] + private Task FetchAsync(string name) => Task.CompletedTask; + + [Fetch] + private void GetByCode(string code, int version) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("Hidden names are checked against the receiving portal interface only")] + public async Task ReceiverSpecificHiddenNames() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Create] + private void CreateChild(int id) { } + + [FetchChild] + private void Fetch(int id) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Keyword method names and parameters colliding with generated names")] + public async Task KeywordAndCollidingNames() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void @class(int portal, int __portal, int __invoker) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("A prefix that is not a valid identifier is reported")] + public async Task InvalidPrefix() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions(Prefix = "My-")] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("NoDataPortalExtension excludes a method")] + public async Task NoDataPortalExtension() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + + [NoDataPortalExtension] + [Fetch] + private void GetByName(string name) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Internal parameter types make the extension internal and private types are reported")] + public async Task ParameterAccessibility() + { + var source = """ + using Csla; + + namespace TestApp + { + internal class InternalCriteria { } + + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + private class PrivateCriteria { } + + [Fetch] + private void GetByInternal(InternalCriteria criteria) { } + + [Fetch] + private void GetByPrivate(PrivateCriteria criteria) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("Default parameter values including enums")] + public async Task DefaultValues() + { + var source = """ + using Csla; + + namespace TestApp + { + public enum Color { Red, Green = 5 } + + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id = 5, string? name = "x\"y", Color color = Color.Green, decimal amount = 1.5m, double? ratio = null, float scale = 2.25f, long big = -3, bool active = true, char marker = 'a') { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Array and params criteria")] + public async Task ArrayCriteria() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetByNames(string[] names) { } + + [Create] + private void NewWith(params int[] values) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Nested business type")] + public async Task NestedType() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class Outer + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + } + """; + + await Verify(source); + } + + [TestMethod("Business types mapping to the same extension class name merge into one partial class")] + public async Task ExtensionClassNameCollision() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class Outer_Inner : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + + internal partial class Outer + { + [DataPortalExtensions] + public partial class Inner : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + } + """; + + await Verify(source); + } + + [TestMethod("Internal business type generates an internal extension class")] + public async Task InternalType() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + internal partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + """; + + await Verify(source); + } + + [TestMethod("Generic business type is skipped")] + public async Task GenericTypeSkipped() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class Lookup : ReadOnlyBase> + { + [Fetch] + private void GetByKey(TKey key) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("Abstract and non-CSLA types are reported")] + public async Task InvalidTargets() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public abstract partial class PersonBase : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + + [DataPortalExtensions] + public class NotABusinessObject + { + [Fetch] + private void GetById(int id) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("Operations producing the same extension signature are reported")] + public async Task DuplicateExtension() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void Get(int id) { } + + [Create] + private void Get(int id, [Inject] System.IServiceProvider services) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("Pipeline stages are cached when the compilation does not change")] + public void StageCaching() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions(Prefix = "Portal")] + public partial class PersonEdit : BusinessBase + { + [Create] + private void Create() { } + + [FetchChild] + [Fetch] + private void Fetch(int id) { } + } + } + """; + + StageCachingTester.VerifyStageCaching(source, typeof(TrackingNames)); + } + + [TestMethod("Only async extensions are generated when CslaGenerateSyncDataPortalExtensions is false")] + public async Task AsyncOnly() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + private void GetById(int id) { } + + [Delete] + private void Remove(int id) { } + + [FetchChild] + private void LoadChild(int id) { } + + [Fetch] + private void Fetch(string name) { } + } + } + """; + + await VerifyWithDiagnostics(source, SyncDisabled); + } + + [TestMethod("The assembly attribute requests extensions for every eligible type")] + public async Task AssemblyAttribute() + { + var source = """ + using Csla; + + [assembly: DataPortalExtensions(Prefix = "Portal")] + + namespace TestApp + { + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + + [DataPortalExtensions(Prefix = "My")] + public partial class OrderEdit : BusinessBase + { + [Create] + private void Create() { } + } + + [DataPortalExtensions] + public partial class CustomerEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + + [NoDataPortalExtension] + public partial class Excluded : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + + public abstract partial class PersonBase : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + + public partial class Lookup : ReadOnlyBase> + { + [Fetch] + private void Fetch(TKey key) { } + } + + public class NotABusinessObject + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("An invalid assembly prefix is reported on the assembly attribute")] + public async Task AssemblyAttributeInvalidPrefix() + { + var source = """ + using Csla; + + [assembly: DataPortalExtensions(Prefix = "My-")] + + namespace TestApp + { + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + + public partial class OrderEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + """; + + await VerifyWithDiagnostics(source); + } + + [TestMethod("CslaDataPortalExtensionsAsyncSuffix sets the async method name suffix")] + public async Task CustomAsyncSuffix() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + """; + + await VerifyWithDiagnostics(source, Options(("CslaDataPortalExtensionsAsyncSuffix", "Task"))); + } + + [TestMethod("With no async suffix and sync methods turned off, async methods have no suffix")] + public async Task NoAsyncSuffix() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + + [FetchChild] + private System.Threading.Tasks.Task LoadAsync(int id) => System.Threading.Tasks.Task.CompletedTask; + } + } + """; + + await VerifyWithDiagnostics(source, Options(("CslaDataPortalExtensionsAsyncSuffix", "none"), ("CslaGenerateSyncDataPortalExtensions", "false"))); + } + + [TestMethod("With no async suffix, sync methods are not generated and a warning is reported")] + public async Task NoAsyncSuffixWithSync() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + """; + + await VerifyWithDiagnostics(source, Options(("CslaDataPortalExtensionsAsyncSuffix", "none"))); + } + + [TestMethod("An invalid async suffix is reported and the Async suffix is used")] + public async Task InvalidAsyncSuffix() + { + var source = """ + using Csla; + + namespace TestApp + { + [DataPortalExtensions] + public partial class PersonEdit : BusinessBase + { + [Fetch] + private void GetById(int id) { } + } + } + """; + + await VerifyWithDiagnostics(source, Options(("CslaDataPortalExtensionsAsyncSuffix", "As-ync"))); + } + + private static TestAnalyzerConfigOptionsProvider Options(params (string Name, string Value)[] properties) + => new(properties.ToDictionary(p => "build_property." + p.Name, p => p.Value)); + + private static TestAnalyzerConfigOptionsProvider SyncDisabled { get; } = + new(new Dictionary { ["build_property.CslaGenerateSyncDataPortalExtensions"] = "false" }); + + private static Task Verify(string source, [System.Runtime.CompilerServices.CallerFilePath] string sourceFile = "") + => DataPortalOperationsTestHelper.Verify(source, sourceFile: sourceFile); + + private static Task VerifyWithDiagnostics(string source, TestAnalyzerConfigOptionsProvider? optionsProvider = null, + [System.Runtime.CompilerServices.CallerFilePath] string sourceFile = "") + => DataPortalOperationsTestHelper.VerifyWithDiagnostics(source, optionsProvider: optionsProvider, sourceFile: sourceFile); + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ArrayCriteria#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ArrayCriteria#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..9767ff97ae --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ArrayCriteria#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,52 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetByNames(string[]) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByNamesAsync(this global::Csla.IDataPortal portal, string[] names) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__StringArray", false, new object?[] { names }); + return portal.FetchAsync(new object?[] { names }); + } + + /// + /// Invokes the GetByNames(string[]) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetByNames(this global::Csla.IDataPortal portal, string[] names) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__StringArray", false, new object?[] { names }); + return portal.Fetch(new object?[] { names }); + } + + /// + /// Invokes the NewWith(int[]) Create operation of . + /// + public static global::System.Threading.Tasks.Task NewWithAsync(this global::Csla.IDataPortal portal, params int[] values) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperationAsync("Create__Int32Array", false, new object?[] { values }); + return portal.CreateAsync(new object?[] { values }); + } + + /// + /// Invokes the NewWith(int[]) Create operation of . + /// + public static global::TestApp.PersonEdit NewWith(this global::Csla.IDataPortal portal, params int[] values) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperation("Create__Int32Array", false, new object?[] { values }); + return portal.Create(new object?[] { values }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.CustomerEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.CustomerEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..c93ce3b431 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.CustomerEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.CustomerEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class CustomerEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task PortalGetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::TestApp.CustomerEdit PortalGetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.OrderEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.OrderEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..8cd9a96481 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.OrderEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.OrderEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class OrderEditDataPortalExtensions + { + /// + /// Invokes the Create() Create operation of . + /// + public static global::System.Threading.Tasks.Task MyCreateAsync(this global::Csla.IDataPortal portal) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperationAsync("Create", false, global::System.Array.Empty()); + return portal.CreateAsync(); + } + + /// + /// Invokes the Create() Create operation of . + /// + public static global::TestApp.OrderEdit MyCreate(this global::Csla.IDataPortal portal) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperation("Create", false, global::System.Array.Empty()); + return portal.Create(); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..5c976cfa91 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the Fetch(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task PortalFetchAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the Fetch(int) Fetch operation of . + /// + public static global::TestApp.PersonEdit PortalFetch(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute.verified.txt new file mode 100644 index 0000000000..98c1405475 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttribute.verified.txt @@ -0,0 +1,4 @@ +{ + target: null, + Diagnostics: [] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttributeInvalidPrefix.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttributeInvalidPrefix.verified.txt new file mode 100644 index 0000000000..851c320f2e --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AssemblyAttributeInvalidPrefix.verified.txt @@ -0,0 +1,26 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + +[assembly: DataPortalExtensions(Prefix = "My-")] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +*/ + Source0.cs: (2,11)-(2,47), + Message: Data portal extension methods are not generated for 'Tests' because the prefix 'My-' is not a valid C# identifier, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP008, + Title: Invalid DataPortalExtensions prefix, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP008-InvalidExtensionPrefix.md, + MessageFormat: Data portal extension methods are not generated for '{0}' because the prefix '{1}' is not a valid C# identifier, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AsyncOnly#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AsyncOnly#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..dbeb0444ba --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AsyncOnly#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,42 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the Remove(int) Delete operation of . + /// + public static global::System.Threading.Tasks.Task RemoveAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.DeleteByOperationAsync("Delete__Int32", false, new object?[] { id }); + return portal.DeleteAsync(new object?[] { id }); + } + + /// + /// Invokes the LoadChild(int) FetchChild operation of . + /// + public static global::System.Threading.Tasks.Task LoadChildAsync(this global::Csla.IChildDataPortal portal, int id) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.FetchChildByOperationAsync("FetchChild__Int32", new object?[] { id }); + return portal.FetchChildAsync(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AsyncOnly.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AsyncOnly.verified.txt new file mode 100644 index 0000000000..3a49ff4fe0 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.AsyncOnly.verified.txt @@ -0,0 +1,47 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [Create] + private void Create() { } + ^^^^^^ + +*/ + Source0.cs: (8,17)-(8,23), + Message: The data portal extension method 'CreateAsync' for 'PersonEdit' is not generated because it would be hidden by the 'IDataPortal.CreateAsync' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP005, + Title: Generated data portal extension would be hidden, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP005-ExtensionNameHidden.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated because it would be hidden by the '{2}' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + }, + { + Location: /* + [Fetch] + private void Fetch(string name) { } + ^^^^^ + } +*/ + Source0.cs: (20,17)-(20,22), + Message: The data portal extension method 'FetchAsync' for 'PersonEdit' is not generated because it would be hidden by the 'IDataPortal.FetchAsync' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP005, + Title: Generated data portal extension would be hidden, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP005-ExtensionNameHidden.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated because it would be hidden by the '{2}' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..d9e94d57dd --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalExtensions.g.verified.cs @@ -0,0 +1,52 @@ +//HintName: TestApp.LineItem.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class LineItemDataPortalExtensions + { + /// + /// Invokes the NewChild() CreateChild operation of . + /// + public static global::System.Threading.Tasks.Task NewChildAsync(this global::Csla.IChildDataPortal portal) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.CreateChildByOperationAsync("CreateChild", global::System.Array.Empty()); + return portal.CreateChildAsync(); + } + + /// + /// Invokes the NewChild() CreateChild operation of . + /// + public static global::TestApp.LineItem NewChild(this global::Csla.IChildDataPortal portal) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.CreateChildByOperation("CreateChild", global::System.Array.Empty()); + return portal.CreateChild(); + } + + /// + /// Invokes the LoadChild(int, string) FetchChild operation of . + /// + public static global::System.Threading.Tasks.Task LoadChildAsync(this global::Csla.IChildDataPortal portal, int id, string name) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.FetchChildByOperationAsync("FetchChild__Int32_String", new object?[] { id, name }); + return portal.FetchChildAsync(new object?[] { id, name }); + } + + /// + /// Invokes the LoadChild(int, string) FetchChild operation of . + /// + public static global::TestApp.LineItem LoadChild(this global::Csla.IChildDataPortal portal, int id, string name) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.FetchChildByOperation("FetchChild__Int32_String", new object?[] { id, name }); + return portal.FetchChild(new object?[] { id, name }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.CustomAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.CustomAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..0cdffd5666 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.CustomAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdTask(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.CustomAsyncSuffix.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.CustomAsyncSuffix.verified.txt new file mode 100644 index 0000000000..98c1405475 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.CustomAsyncSuffix.verified.txt @@ -0,0 +1,4 @@ +{ + target: null, + Diagnostics: [] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DefaultValues#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DefaultValues#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..df34ccc885 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DefaultValues#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int, string, Color, decimal, double?, float, long, bool, char) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id = 5, string? name = "x\"y", global::TestApp.Color color = global::TestApp.Color.Green, decimal amount = 1.5M, double? ratio = null, float scale = 2.25F, long big = -3L, bool active = true, char marker = 'a') + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32_String_Color_Decimal_Nullable_1_Double_Single_Int64_Boolean_Char", false, new object?[] { id, name, color, amount, ratio, scale, big, active, marker }); + return portal.FetchAsync(new object?[] { id, name, color, amount, ratio, scale, big, active, marker }); + } + + /// + /// Invokes the GetById(int, string, Color, decimal, double?, float, long, bool, char) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetById(this global::Csla.IDataPortal portal, int id = 5, string? name = "x\"y", global::TestApp.Color color = global::TestApp.Color.Green, decimal amount = 1.5M, double? ratio = null, float scale = 2.25F, long big = -3L, bool active = true, char marker = 'a') + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32_String_Color_Decimal_Nullable_1_Double_Single_Int64_Boolean_Char", false, new object?[] { id, name, color, amount, ratio, scale, big, active, marker }); + return portal.Fetch(new object?[] { id, name, color, amount, ratio, scale, big, active, marker }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DuplicateExtension#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DuplicateExtension#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..e4a2bf9cc6 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DuplicateExtension#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the Get(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the Get(int) Fetch operation of . + /// + public static global::TestApp.PersonEdit Get(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DuplicateExtension.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DuplicateExtension.verified.txt new file mode 100644 index 0000000000..3168a54f5b --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.DuplicateExtension.verified.txt @@ -0,0 +1,47 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [Create] + private void Get(int id, [Inject] System.IServiceProvider services) { } + ^^^ + } +*/ + Source0.cs: (11,17)-(11,20), + Message: The data portal extension method 'Get' for 'PersonEdit' is not generated for 'Get(int, IServiceProvider)' because another operation method already produces an extension method with the same signature, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP007, + Title: Generated data portal extension would be duplicated, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP007-DuplicateExtensionMethod.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated for '{2}' because another operation method already produces an extension method with the same signature, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + }, + { + Location: /* + [Create] + private void Get(int id, [Inject] System.IServiceProvider services) { } + ^^^ + } +*/ + Source0.cs: (11,17)-(11,20), + Message: The data portal extension method 'GetAsync' for 'PersonEdit' is not generated for 'Get(int, IServiceProvider)' because another operation method already produces an extension method with the same signature, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP007, + Title: Generated data portal extension would be duplicated, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP007-DuplicateExtensionMethod.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated for '{2}' because another operation method already produces an extension method with the same signature, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExecuteCommand#TestApp.RecalculateCommand.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExecuteCommand#TestApp.RecalculateCommand.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..891dc2ac24 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExecuteCommand#TestApp.RecalculateCommand.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.RecalculateCommand.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class RecalculateCommandDataPortalExtensions + { + /// + /// Invokes the Run(int) Execute operation of . + /// + public static global::System.Threading.Tasks.Task RunAsync(this global::Csla.IDataPortal portal, int count) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.ExecuteByOperationAsync("Execute__Int32", false, new object?[] { count }); + return portal.ExecuteAsync(new object?[] { count }); + } + + /// + /// Invokes the Run(int) Execute operation of . + /// + public static global::TestApp.RecalculateCommand Run(this global::Csla.IDataPortal portal, int count) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.ExecuteByOperation("Execute__Int32", false, new object?[] { count }); + return portal.Execute(new object?[] { count }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExtensionClassNameCollision#TestApp.Outer.Inner.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExtensionClassNameCollision#TestApp.Outer.Inner.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..180184772f --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExtensionClassNameCollision#TestApp.Outer.Inner.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.Outer.Inner.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + static partial class Outer_InnerDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + internal static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + internal static global::TestApp.Outer.Inner GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExtensionClassNameCollision#TestApp.Outer_Inner.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExtensionClassNameCollision#TestApp.Outer_Inner.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..ad344da14e --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ExtensionClassNameCollision#TestApp.Outer_Inner.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.Outer_Inner.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class Outer_InnerDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::TestApp.Outer_Inner GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.GenericTypeSkipped.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.GenericTypeSkipped.verified.txt new file mode 100644 index 0000000000..436398fb14 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.GenericTypeSkipped.verified.txt @@ -0,0 +1,26 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [DataPortalExtensions] + public partial class Lookup : ReadOnlyBase> + ^^^^^^ + { +*/ + Source0.cs: (5,23)-(5,29), + Message: Data portal extension methods are not generated for 'Lookup' because it is generic or nested in a generic type, + Severity: Info, + WarningLevel: 1, + Descriptor: { + Id: CSLADP003, + Title: Data portal extensions are not generated for generic types, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP003-GenericExtensionsNotGenerated.md, + MessageFormat: Data portal extension methods are not generated for '{0}' because it is generic or nested in a generic type, + Category: Csla.DataPortal, + DefaultSeverity: Info, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.HiddenNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.HiddenNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..4539451731 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.HiddenNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetByCode(string, int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByCodeAsync(this global::Csla.IDataPortal portal, string code, int version) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__String_Int32", false, new object?[] { code, version }); + return portal.FetchAsync(new object?[] { code, version }); + } + + /// + /// Invokes the GetByCode(string, int) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetByCode(this global::Csla.IDataPortal portal, string code, int version) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__String_Int32", false, new object?[] { code, version }); + return portal.Fetch(new object?[] { code, version }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.HiddenNames.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.HiddenNames.verified.txt new file mode 100644 index 0000000000..2be6bce04f --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.HiddenNames.verified.txt @@ -0,0 +1,89 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [Fetch] + private void Fetch(int id) { } + ^^^^^ + +*/ + Source0.cs: (9,17)-(9,22), + Message: The data portal extension method 'Fetch' for 'PersonEdit' is not generated because it would be hidden by the 'IDataPortal.Fetch' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP005, + Title: Generated data portal extension would be hidden, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP005-ExtensionNameHidden.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated because it would be hidden by the '{2}' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + }, + { + Location: /* + [Fetch] + private void Fetch(int id) { } + ^^^^^ + +*/ + Source0.cs: (9,17)-(9,22), + Message: The data portal extension method 'FetchAsync' for 'PersonEdit' is not generated because it would be hidden by the 'IDataPortal.FetchAsync' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP005, + Title: Generated data portal extension would be hidden, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP005-ExtensionNameHidden.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated because it would be hidden by the '{2}' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + }, + { + Location: /* + [Fetch] + private Task FetchAsync(string name) => Task.CompletedTask; + ^^^^^^^^^^ + +*/ + Source0.cs: (12,17)-(12,27), + Message: The data portal extension method 'Fetch' for 'PersonEdit' is not generated because it would be hidden by the 'IDataPortal.Fetch' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP005, + Title: Generated data portal extension would be hidden, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP005-ExtensionNameHidden.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated because it would be hidden by the '{2}' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + }, + { + Location: /* + [Fetch] + private Task FetchAsync(string name) => Task.CompletedTask; + ^^^^^^^^^^ + +*/ + Source0.cs: (12,17)-(12,27), + Message: The data portal extension method 'FetchAsync' for 'PersonEdit' is not generated because it would be hidden by the 'IDataPortal.FetchAsync' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP005, + Title: Generated data portal extension would be hidden, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP005-ExtensionNameHidden.md, + MessageFormat: The data portal extension method '{0}' for '{1}' is not generated because it would be hidden by the '{2}' instance method; set DataPortalExtensions.Prefix to give the generated methods distinct names, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InternalType#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InternalType#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..223676d17b --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InternalType#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + internal static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + internal static global::TestApp.PersonEdit GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..75d40fc521 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidAsyncSuffix.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidAsyncSuffix.verified.txt new file mode 100644 index 0000000000..42ee9b8d12 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidAsyncSuffix.verified.txt @@ -0,0 +1,22 @@ +{ + target: null, + Diagnostics: [ + { + Message: The CslaDataPortalExtensionsAsyncSuffix value 'As-ync' is not valid in a C# identifier; generated async data portal extension methods use the 'Async' suffix, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP010, + Title: Invalid data portal extensions async suffix, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP010-InvalidAsyncSuffix.md, + MessageFormat: The CslaDataPortalExtensionsAsyncSuffix value '{0}' is not valid in a C# identifier; generated async data portal extension methods use the 'Async' suffix, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true, + CustomTags: [ + CompilationEnd + ] + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidPrefix.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidPrefix.verified.txt new file mode 100644 index 0000000000..7ce83d14a8 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidPrefix.verified.txt @@ -0,0 +1,26 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [DataPortalExtensions(Prefix = "My-")] + public partial class PersonEdit : BusinessBase + ^^^^^^^^^^ + { +*/ + Source0.cs: (5,23)-(5,33), + Message: Data portal extension methods are not generated for 'PersonEdit' because the prefix 'My-' is not a valid C# identifier, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP008, + Title: Invalid DataPortalExtensions prefix, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP008-InvalidExtensionPrefix.md, + MessageFormat: Data portal extension methods are not generated for '{0}' because the prefix '{1}' is not a valid C# identifier, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidTargets.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidTargets.verified.txt new file mode 100644 index 0000000000..2cc54ba51d --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.InvalidTargets.verified.txt @@ -0,0 +1,47 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [DataPortalExtensions] + public abstract partial class PersonBase : BusinessBase + ^^^^^^^^^^ + { +*/ + Source0.cs: (5,32)-(5,42), + Message: Data portal extension methods are not generated for 'PersonBase': the type is abstract, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP004, + Title: Invalid target for DataPortalExtensions, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP004-InvalidExtensionsTarget.md, + MessageFormat: Data portal extension methods are not generated for '{0}': {1}, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + }, + { + Location: /* + [DataPortalExtensions] + public class NotABusinessObject + ^^^^^^^^^^^^^^^^^^ + { +*/ + Source0.cs: (12,15)-(12,33), + Message: Data portal extension methods are not generated for 'NotABusinessObject': the type does not implement Csla.Core.ICslaObject, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP004, + Title: Invalid target for DataPortalExtensions, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP004-InvalidExtensionsTarget.md, + MessageFormat: Data portal extension methods are not generated for '{0}': {1}, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.KeywordAndCollidingNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.KeywordAndCollidingNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..d1a25ce04c --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.KeywordAndCollidingNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the class(int, int, int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task classAsync(this global::Csla.IDataPortal ____portal, int portal, int __portal, int __invoker) + { + if (____portal is global::Csla.IDataPortalOperationInvoker ____invoker) + return ____invoker.FetchByOperationAsync("Fetch__Int32_Int32_Int32", false, new object?[] { portal, __portal, __invoker }); + return ____portal.FetchAsync(new object?[] { portal, __portal, __invoker }); + } + + /// + /// Invokes the class(int, int, int) Fetch operation of . + /// + public static global::TestApp.PersonEdit @class(this global::Csla.IDataPortal ____portal, int portal, int __portal, int __invoker) + { + if (____portal is global::Csla.IDataPortalOperationInvoker ____invoker) + return ____invoker.FetchByOperation("Fetch__Int32_Int32_Int32", false, new object?[] { portal, __portal, __invoker }); + return ____portal.Fetch(new object?[] { portal, __portal, __invoker }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NestedType#TestApp.Outer.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NestedType#TestApp.Outer.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..100695840c --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NestedType#TestApp.Outer.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.Outer.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class Outer_PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::TestApp.Outer.PersonEdit GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..a391704571 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the LoadAsync(int) FetchChild operation of . + /// + public static global::System.Threading.Tasks.Task Load(this global::Csla.IChildDataPortal portal, int id) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.FetchChildByOperationAsync("FetchChild__Int32", new object?[] { id }); + return portal.FetchChildAsync(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffix.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffix.verified.txt new file mode 100644 index 0000000000..98c1405475 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffix.verified.txt @@ -0,0 +1,4 @@ +{ + target: null, + Diagnostics: [] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffixWithSync#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffixWithSync#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..5e68ffbfed --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffixWithSync#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffixWithSync.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffixWithSync.verified.txt new file mode 100644 index 0000000000..59ac0c1714 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoAsyncSuffixWithSync.verified.txt @@ -0,0 +1,22 @@ +{ + target: null, + Diagnostics: [ + { + Message: Synchronous data portal extension methods are not generated because CslaDataPortalExtensionsAsyncSuffix is 'none', so they would have the same names as the async methods; set CslaGenerateSyncDataPortalExtensions to false, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP011, + Title: Synchronous data portal extensions are not generated without an async suffix, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP011-SyncExtensionsNotGenerated.md, + MessageFormat: Synchronous data portal extension methods are not generated because CslaDataPortalExtensionsAsyncSuffix is 'none', so they would have the same names as the async methods; set CslaGenerateSyncDataPortalExtensions to false, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true, + CustomTags: [ + CompilationEnd + ] + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoDataPortalExtension#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoDataPortalExtension#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..75d40fc521 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.NoDataPortalExtension#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ParameterAccessibility#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ParameterAccessibility#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..ec21f4212f --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ParameterAccessibility#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,32 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the GetByInternal(InternalCriteria) Fetch operation of . + /// + internal static global::System.Threading.Tasks.Task GetByInternalAsync(this global::Csla.IDataPortal portal, global::TestApp.InternalCriteria criteria) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__InternalCriteria", false, new object?[] { criteria }); + return portal.FetchAsync(new object?[] { criteria }); + } + + /// + /// Invokes the GetByInternal(InternalCriteria) Fetch operation of . + /// + internal static global::TestApp.PersonEdit GetByInternal(this global::Csla.IDataPortal portal, global::TestApp.InternalCriteria criteria) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__InternalCriteria", false, new object?[] { criteria }); + return portal.Fetch(new object?[] { criteria }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ParameterAccessibility.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ParameterAccessibility.verified.txt new file mode 100644 index 0000000000..ed8a0a4d4e --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ParameterAccessibility.verified.txt @@ -0,0 +1,26 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [Fetch] + private void GetByPrivate(PrivateCriteria criteria) { } + ^^^^^^^^^^^^ + } +*/ + Source0.cs: (15,17)-(15,29), + Message: The data portal extension method for 'GetByPrivate(PrivateCriteria)' cannot be generated because parameter 'criteria' has type 'global::TestApp.PersonEdit.PrivateCriteria', which is not accessible outside its containing type, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP006, + Title: Operation parameter type is not accessible, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP006-InaccessibleParameterType.md, + MessageFormat: The data portal extension method for '{0}' cannot be generated because parameter '{1}' has type '{2}', which is not accessible outside its containing type, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.Prefix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.Prefix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..fc1e85e255 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.Prefix#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,52 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the Create() Create operation of . + /// + public static global::System.Threading.Tasks.Task PortalCreateAsync(this global::Csla.IDataPortal portal) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperationAsync("Create", false, global::System.Array.Empty()); + return portal.CreateAsync(); + } + + /// + /// Invokes the Create() Create operation of . + /// + public static global::TestApp.PersonEdit PortalCreate(this global::Csla.IDataPortal portal) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperation("Create", false, global::System.Array.Empty()); + return portal.Create(); + } + + /// + /// Invokes the Fetch(int) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task PortalFetchAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the Fetch(int) Fetch operation of . + /// + public static global::TestApp.PersonEdit PortalFetch(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ReceiverSpecificHiddenNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ReceiverSpecificHiddenNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..5394a5cbd4 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.ReceiverSpecificHiddenNames#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,52 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the CreateChild(int) Create operation of . + /// + public static global::System.Threading.Tasks.Task CreateChildAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperationAsync("Create__Int32", false, new object?[] { id }); + return portal.CreateAsync(new object?[] { id }); + } + + /// + /// Invokes the CreateChild(int) Create operation of . + /// + public static global::TestApp.PersonEdit CreateChild(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperation("Create__Int32", false, new object?[] { id }); + return portal.Create(new object?[] { id }); + } + + /// + /// Invokes the Fetch(int) FetchChild operation of . + /// + public static global::System.Threading.Tasks.Task FetchAsync(this global::Csla.IChildDataPortal portal, int id) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.FetchChildByOperationAsync("FetchChild__Int32", new object?[] { id }); + return portal.FetchChildAsync(new object?[] { id }); + } + + /// + /// Invokes the Fetch(int) FetchChild operation of . + /// + public static global::TestApp.PersonEdit Fetch(this global::Csla.IChildDataPortal portal, int id) + { + if (portal is global::Csla.IChildDataPortalOperationInvoker __invoker) + return __invoker.FetchChildByOperation("FetchChild__Int32", new object?[] { id }); + return portal.FetchChild(new object?[] { id }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.RootOperations#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.RootOperations#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs new file mode 100644 index 0000000000..cb9a6b747d --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalExtensions/Snapshots/DataPortalExtensionsGeneratorTests.RootOperations#TestApp.PersonEdit.DataPortalExtensions.g.verified.cs @@ -0,0 +1,93 @@ +//HintName: TestApp.PersonEdit.DataPortalExtensions.g.cs +// +#nullable enable + +namespace TestApp +{ + /// + /// Strongly typed data portal extension methods for . + /// + public static partial class PersonEditDataPortalExtensions + { + /// + /// Invokes the New() Create operation of . + /// + public static global::System.Threading.Tasks.Task NewAsync(this global::Csla.IDataPortal portal) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperationAsync("Create", false, global::System.Array.Empty()); + return portal.CreateAsync(); + } + + /// + /// Invokes the New() Create operation of . + /// + public static global::TestApp.PersonEdit New(this global::Csla.IDataPortal portal) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.CreateByOperation("Create", false, global::System.Array.Empty()); + return portal.Create(); + } + + /// + /// Invokes the GetById(int, IDal) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetByIdAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__Int32", false, new object?[] { id }); + return portal.FetchAsync(new object?[] { id }); + } + + /// + /// Invokes the GetById(int, IDal) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetById(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__Int32", false, new object?[] { id }); + return portal.Fetch(new object?[] { id }); + } + + /// + /// Invokes the RemoveAsync(int) Delete operation of . + /// + public static global::System.Threading.Tasks.Task RemoveAsync(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.DeleteByOperationAsync("Delete__Int32", false, new object?[] { id }); + return portal.DeleteAsync(new object?[] { id }); + } + + /// + /// Invokes the RemoveAsync(int) Delete operation of . + /// + public static void Remove(this global::Csla.IDataPortal portal, int id) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + __invoker.DeleteByOperation("Delete__Int32", false, new object?[] { id }); + else + portal.Delete(new object?[] { id }); + } + + /// + /// Invokes the GetLocal(IDal, string) Fetch operation of . + /// + public static global::System.Threading.Tasks.Task GetLocalAsync(this global::Csla.IDataPortal portal, string code) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperationAsync("Fetch__String", true, new object?[] { code }); + return portal.FetchAsync(new object?[] { code }); + } + + /// + /// Invokes the GetLocal(IDal, string) Fetch operation of . + /// + public static global::TestApp.PersonEdit GetLocal(this global::Csla.IDataPortal portal, string code) + { + if (portal is global::Csla.IDataPortalOperationInvoker __invoker) + return __invoker.FetchByOperation("Fetch__String", true, new object?[] { code }); + return portal.Fetch(new object?[] { code }); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/DataPortalOperationsGeneratorTests.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/DataPortalOperationsGeneratorTests.cs new file mode 100644 index 0000000000..fded39fbe1 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/DataPortalOperationsGeneratorTests.cs @@ -0,0 +1,736 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +//----------------------------------------------------------------------- +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations; + +namespace Csla.Generator.AutoImplementProperties.CSharp.Tests.DataPortalOperations +{ + [TestClass] + public class DataPortalOperationsGeneratorTests : VerifyBase + { + [TestMethod("Single Create operation with no parameters")] + public async Task SingleCreateNoParams() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Single Fetch operation with one criteria parameter")] + public async Task SingleFetchWithCriteria() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Operation method with Inject parameter")] + public async Task OperationWithInjectParam() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Insert] + private async Task Insert([Inject] IDal dal) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Operation method with Inject AllowNull parameter")] + public async Task OperationWithInjectAllowNull() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface ILogger { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private async Task Fetch(int id, [Inject(AllowNull = true)] ILogger? logger) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Multiple overloads of same Fetch operation")] + public async Task MultipleOverloads() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(string name) { } + + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Method with both root and child attributes")] + public async Task RootAndChildAttributes() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + [CreateChild] + private void Create() { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Async method returning Task")] + public async Task AsyncMethod() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private async Task Fetch(int id) { await Task.CompletedTask; } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("File-scoped namespace")] + public async Task FileScopedNamespace() + { + var source = """ + using Csla; + + namespace TestApp; + + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + private void Fetch(int id) { } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Complete CRUD operations")] + public async Task CompleteCrud() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + private void Fetch(int id) { } + + [Insert] + private async Task Insert([Inject] IDal dal) { await Task.CompletedTask; } + + [Update] + private async Task Update([Inject] IDal dal) { await Task.CompletedTask; } + + [DeleteSelf] + private async Task DeleteSelf([Inject] IDal dal) { await Task.CompletedTask; } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Delete operation with criteria")] + public async Task DeleteWithCriteria() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Delete] + private void Delete(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Child operations")] + public async Task ChildOperations() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + public partial class LineItem : Csla.BusinessBase + { + [CreateChild] + private void CreateChild() { } + + [FetchChild] + private void FetchChild(int id) { } + + [InsertChild] + private async Task InsertChild([Inject] IDal dal) { await Task.CompletedTask; } + + [UpdateChild] + private async Task UpdateChild([Inject] IDal dal) { await Task.CompletedTask; } + + [DeleteSelfChild] + private async Task DeleteSelfChild([Inject] IDal dal) { await Task.CompletedTask; } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Execute operation on command object")] + public async Task ExecuteCommand() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class MyCommand : Csla.CommandBase + { + [Execute] + private void Execute() { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Nested class with data portal operations")] + public async Task NestedClass() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class Outer + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Inject parameter declared before criteria keeps parameter order")] + public async Task InjectBeforeCriteria() + { + var source = """ + using Csla; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch([Inject] IDal dal, int id, [Inject(AllowNull = true)] IDal? backup, string name) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Array, nullable, generic and tuple criteria")] + public async Task ArrayNullableGenericCriteria() + { + var source = """ + using Csla; + using System.Collections.Generic; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int[] ids) { } + + [Fetch] + private void Fetch(int? id, string? name) { } + + [Fetch] + private void Fetch(List ids) { } + + [Fetch] + private void Fetch((int Id, string Name) key) { } + + [Fetch] + private void Fetch(Dictionary>[] map) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Partial class with operation methods spread across two files")] + public async Task PartialClassAcrossFiles() + { + var source1 = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + } + } + """; + + var source2 = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source1, source2); + } + + [TestMethod("Abstract class gets the operations interface only")] + public async Task AbstractClass() + { + var source = """ + using Csla; + + namespace TestApp + { + public abstract partial class PersonBase : Csla.BusinessBase + where T : PersonBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Derived class hides the operations interface of a base class in the same assembly")] + public async Task DerivedFromBaseWithOperations() + { + var source = """ + using Csla; + + namespace TestApp + { + public abstract partial class PersonBase : Csla.BusinessBase + where T : PersonBase + { + [Fetch] + private void Fetch(int id) { } + } + + public partial class PersonEdit : PersonBase + { + [Fetch] + private void Fetch(string name) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Generic business object excludes type parameter criteria from name dispatch")] + public async Task GenericBusinessObject() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class Lookup : Csla.ReadOnlyBase> + { + [Fetch] + private void Fetch(TKey key) { } + + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("RunLocal and ValueTask operation methods")] + public async Task RunLocalAndValueTask() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [RunLocal] + [Create] + private void Create() { } + + [Fetch] + private async ValueTask Fetch(int id) { await Task.Yield(); } + + [Fetch] + private ValueTask Fetch(string name) => new ValueTask(0); + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Keyed inject parameters")] + public async Task KeyedInject() + { + var source = """ + using Csla; + + namespace TestApp + { + public interface IDal { } + + public enum DalKind { Primary, Secondary } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id, [Inject(Key = "primary")] IDal dal, [Inject(Key = DalKind.Secondary, AllowNull = true)] IDal? secondary) { } + + [Create] + private void Create([Inject(Key = (short)-1)] IDal dal, [Inject(Key = (byte)2)] IDal other) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Overloads with the same operation name report a diagnostic")] + public async Task DuplicateOperationName() + { + var source = """ + using Csla; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id) { } + + [Fetch] + private void Fetch(int id, [Inject] IDal dal) { } + + [Fetch] + private void Load(int id) { } + } + } + """; + + await DataPortalOperationsTestHelper.VerifyWithDiagnostics(source); + } + + [TestMethod("Overloads tied on injected parameters are left to reflection and reported")] + public async Task AmbiguousOperationName() + { + var source = """ + using Csla; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id, [Inject] IDal dal) { } + + [Fetch] + private void Load(int id, [Inject] IDal dal) { } + + [Fetch] + private void Fetch(string name) { } + } + } + """; + + await DataPortalOperationsTestHelper.VerifyWithDiagnostics(source); + } + + [TestMethod("Overloads with the same criteria count require exact types in type-based dispatch")] + public async Task AmbiguousOverloads() + { + var source = """ + using Csla; + + namespace TestApp + { + public class Criteria { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(object value) { } + + [Fetch] + private void Fetch(Criteria criteria) { } + + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Params object array and keyword parameter names")] + public async Task ParamsArrayAndKeywordNames() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(params object[] values) { } + + [Create] + private void Create(int @class, string @event = "x") { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("In criteria parameters and nullable inject parameters")] + public async Task InCriteriaAndNullableInject() + { + var source = """ + using Csla; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(in string? name, in int id) { } + + [Create] + private void Create([Inject] int? count, [Inject] IDal? dal) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Non-partial class generates nothing")] + public async Task NonPartialClass() + { + var source = """ + using Csla; + + namespace TestApp + { + public class PersonEdit : Csla.BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Business objects without operation methods generate nothing")] + public async Task NoOperationMethods() + { + var source = """ + using Csla; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase + { + } + + public partial class PersonList : Csla.ReadOnlyListBase + { + private void Fetch(int id) { } + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Class that implements the named mapping itself only gets the operations interface and type mapping")] + public async Task ExistingNamedMapping() + { + var source = """ + using Csla; + using System; + using System.Threading.Tasks; + + namespace TestApp + { + public partial class PersonEdit : Csla.BusinessBase, Csla.Server.IDataPortalOperationNamedMapping + { + [Fetch] + private void Fetch(int id) { } + + public Task InvokeNamedOperationAsync(string operationName, bool isSync, object?[]? criteria, IServiceProvider serviceProvider) + => Task.CompletedTask; + } + } + """; + + await TestHelperVerify(source); + } + + [TestMethod("Pipeline stages are cached when the compilation does not change")] + public void StageCaching() + { + var source = """ + using Csla; + using System.Threading.Tasks; + + namespace TestApp + { + public interface IDal { } + + public partial class PersonEdit : Csla.BusinessBase + { + [Create] + private void Create() { } + + [Fetch] + [FetchChild] + private async Task Fetch(int id, [Inject] IDal dal) { await Task.CompletedTask; } + } + } + """; + + StageCachingTester.VerifyStageCaching(source, typeof(TrackingNames)); + } + + private static async Task TestHelperVerify(string source, params string[]? additionalSources) + { + await DataPortalOperationsTestHelper.Verify(source, additionalSources); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/DataPortalOperationsTestHelper.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/DataPortalOperationsTestHelper.cs new file mode 100644 index 0000000000..f250d0ba2b --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/DataPortalOperationsTestHelper.cs @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +//----------------------------------------------------------------------- +using System.Runtime.CompilerServices; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalExtensions; +using Csla.Generator.AutoImplementProperties.CSharp.DataPortalOperations; +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Csla.Generator.AutoImplementProperties.CSharp.Tests.DataPortalOperations +{ + public static class DataPortalOperationsTestHelper where T : IIncrementalGenerator, new() + { + /// + /// Runs the generator and verifies the output against snapshots, + /// asserting the generator reports no diagnostics and the resulting + /// compilation has no errors or warnings. + /// + public static Task Verify(string source, IEnumerable? additionalSources = null, + AnalyzerConfigOptionsProvider? optionsProvider = null, [CallerFilePath] string sourceFile = "") + { + var (driver, outputCompilation, diagnostics) = Run(source, additionalSources, optionsProvider); + + using (new AssertionScope()) + { + outputCompilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error + || (d.Severity == DiagnosticSeverity.Warning && IsGenerated(d))) + .Should().BeEmpty(); + diagnostics.Should().BeEmpty(); + } + + return Verifier.Verify(driver, sourceFile: sourceFile).UseDirectory("Snapshots"); + } + + /// + /// Runs the generator and the data portal analyzers, and verifies the + /// generated output and the analyzer diagnostics against snapshots. The + /// generator must report no diagnostics and the resulting compilation + /// must have no errors. + /// + public static Task VerifyWithDiagnostics(string source, IEnumerable? additionalSources = null, + AnalyzerConfigOptionsProvider? optionsProvider = null, [CallerFilePath] string sourceFile = "") + { + var (driver, outputCompilation, diagnostics) = Run(source, additionalSources, optionsProvider); + + using (new AssertionScope()) + { + outputCompilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error) + .Should().BeEmpty(); + diagnostics.Should().BeEmpty(); + } + + var analyzerDiagnostics = GetAnalyzerDiagnosticsAsync(outputCompilation, optionsProvider).GetAwaiter().GetResult(); + + return Verifier.Verify(driver, sourceFile: sourceFile) + .AppendValue("Diagnostics", analyzerDiagnostics) + .UseDirectory("Snapshots"); + } + + /// + /// Runs the data portal analyzers over a compilation and returns their + /// diagnostics in source order. + /// + public static async Task> GetAnalyzerDiagnosticsAsync(Compilation compilation, AnalyzerConfigOptionsProvider? optionsProvider = null) + { + System.Collections.Immutable.ImmutableArray analyzers = + [new DataPortalOperationsAnalyzer(), new DataPortalExtensionsAnalyzer()]; + var options = new CompilationWithAnalyzersOptions( + new AnalyzerOptions([], optionsProvider ?? TestAnalyzerConfigOptionsProvider.Empty), + onAnalyzerException: null, concurrentAnalysis: false, logAnalyzerExecutionTime: false, reportSuppressedDiagnostics: false); + var diagnostics = await compilation.WithAnalyzers(analyzers, options).GetAnalyzerDiagnosticsAsync(); + return diagnostics + .OrderBy(d => d.Location.SourceTree?.FilePath, StringComparer.Ordinal) + .ThenBy(d => d.Location.SourceSpan.Start) + .ThenBy(d => d.Id, StringComparer.Ordinal) + .ThenBy(d => d.GetMessage(System.Globalization.CultureInfo.InvariantCulture), StringComparer.Ordinal) + .ToList(); + } + + /// + /// Creates the compilation and generator driver used by the tests. + /// + public static (GeneratorDriver Driver, CSharpCompilation Compilation) Setup(string source, IEnumerable? additionalSources = null, bool trackSteps = false, + AnalyzerConfigOptionsProvider? optionsProvider = null) + { + var syntaxTrees = new List + { + CSharpSyntaxTree.ParseText(source, path: "Source0.cs") + }; + + var index = 1; + foreach (var additionalSource in additionalSources ?? []) + syntaxTrees.Add(CSharpSyntaxTree.ParseText(additionalSource, path: $"Source{index++}.cs")); + + var references = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location)) + .Select(a => MetadataReference.CreateFromFile(a.Location)) + .Concat([ + MetadataReference.CreateFromFile(typeof(FetchAttribute).Assembly.Location), + // Generated dispatch resolves [Inject] parameters with these extension methods. + MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions).Assembly.Location) + ]); + + var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + .WithNullableContextOptions(NullableContextOptions.Enable) + .WithSpecificDiagnosticOptions(new Dictionary { { "CS8019", ReportDiagnostic.Suppress } }); + + var compilation = CSharpCompilation.Create( + assemblyName: "Tests", + syntaxTrees: syntaxTrees, + references: references, + options: compilationOptions); + + var driverOptions = new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: trackSteps); + GeneratorDriver driver = CSharpGeneratorDriver.Create([new T().AsSourceGenerator()], optionsProvider: optionsProvider, driverOptions: driverOptions); + return (driver, compilation); + } + + private static bool IsGenerated(Diagnostic diagnostic) + => diagnostic.Location.SourceTree?.FilePath.EndsWith(".g.cs", StringComparison.Ordinal) == true; + + private static (GeneratorDriver Driver, Compilation OutputCompilation, System.Collections.Immutable.ImmutableArray Diagnostics) Run(string source, IEnumerable? additionalSources, + AnalyzerConfigOptionsProvider? optionsProvider) + { + var (driver, compilation) = Setup(source, additionalSources, optionsProvider: optionsProvider); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); + return (driver, outputCompilation, diagnostics); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AbstractClass#TestApp.PersonBase_1.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AbstractClass#TestApp.PersonBase_1.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..445348c475 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AbstractClass#TestApp.PersonBase_1.DataPortalOperations.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: TestApp.PersonBase_1.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonBase : global::TestApp.PersonBase.IDataPortalOperations + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOperationName#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOperationName#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..60bf1357c6 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOperationName#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,72 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id, global::TestApp.IDal dal); + void Fetch__Int32_2(int id, global::TestApp.IDal dal); + void Fetch__String(string name); + } + + void IDataPortalOperations.Fetch__Int32(int id, global::TestApp.IDal dal) => Fetch(id, dal); + void IDataPortalOperations.Fetch__Int32_2(int id, global::TestApp.IDal dal) => Load(id, dal); + void IDataPortalOperations.Fetch__String(string name) => Fetch(name); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is string __c0p0) + { + try + { + __operations.Fetch__String(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__String": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is string)) + { + try + { + __operations.Fetch__String((string)criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOperationName.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOperationName.verified.txt new file mode 100644 index 0000000000..894216f186 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOperationName.verified.txt @@ -0,0 +1,26 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [Fetch] + private void Load(int id, [Inject] IDal dal) { } + ^^^^ + +*/ + Source0.cs: (12,17)-(12,21), + Message: Methods 'Fetch(int, IDal)' and 'Load(int, IDal)' on 'PersonEdit' both map to data portal operation 'Fetch__Int32' with the same number of injected parameters; the data portal cannot choose between them and reports an ambiguous match, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: CSLADP009, + Title: Data portal operation methods are ambiguous, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP009-AmbiguousOperationName.md, + MessageFormat: Methods '{0}' and '{1}' on '{2}' both map to data portal operation '{3}' with the same number of injected parameters; the data portal cannot choose between them and reports an ambiguous match, + Category: Csla.DataPortal, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..027119f4c2 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AmbiguousOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,124 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Object(object value); + void Fetch__Criteria(global::TestApp.Criteria criteria); + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__Object(object value) => Fetch(value); + void IDataPortalOperations.Fetch__Criteria(global::TestApp.Criteria criteria) => Fetch(criteria); + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is object __c0p0 && __c0p0.GetType() == typeof(object)) + { + try + { + __operations.Fetch__Object(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is global::TestApp.Criteria __c1p0 && __c1p0.GetType() == typeof(global::TestApp.Criteria)) + { + try + { + __operations.Fetch__Criteria(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is int __c2p0) + { + try + { + __operations.Fetch__Int32(__c2p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Object": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is object)) + { + try + { + __operations.Fetch__Object((object)criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__Criteria": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is global::TestApp.Criteria)) + { + try + { + __operations.Fetch__Criteria((global::TestApp.Criteria)criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c2p0) + { + try + { + __operations.Fetch__Int32(__c2p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ArrayNullableGenericCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ArrayNullableGenericCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..5c2dee87fd --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ArrayNullableGenericCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,180 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32Array(int[] ids); + void Fetch__Nullable_1_Int32_String(int? id, string? name); + void Fetch__List_1_Int32(global::System.Collections.Generic.List ids); + void Fetch__ValueTuple_2_Int32_String((int Id, string Name) key); + void Fetch__Dictionary_2_String_List_1_Nullable_1_Int32Array(global::System.Collections.Generic.Dictionary>[] map); + } + + void IDataPortalOperations.Fetch__Int32Array(int[] ids) => Fetch(ids); + void IDataPortalOperations.Fetch__Nullable_1_Int32_String(int? id, string? name) => Fetch(id, name); + void IDataPortalOperations.Fetch__List_1_Int32(global::System.Collections.Generic.List ids) => Fetch(ids); + void IDataPortalOperations.Fetch__ValueTuple_2_Int32_String((int Id, string Name) key) => Fetch(key); + void IDataPortalOperations.Fetch__Dictionary_2_String_List_1_Nullable_1_Int32Array(global::System.Collections.Generic.Dictionary>[] map) => Fetch(map); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int[] __c0p0 && __c0p0.GetType() == typeof(int[])) + { + try + { + __operations.Fetch__Int32Array(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 2 } && criteria[0] is int __c1p0 && criteria[1] is string __c1p1) + { + try + { + __operations.Fetch__Nullable_1_Int32_String(__c1p0, __c1p1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is global::System.Collections.Generic.List __c2p0 && __c2p0.GetType() == typeof(global::System.Collections.Generic.List)) + { + try + { + __operations.Fetch__List_1_Int32(__c2p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is global::System.ValueTuple __c3p0) + { + try + { + __operations.Fetch__ValueTuple_2_Int32_String(__c3p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is global::System.Collections.Generic.Dictionary>[] __c4p0 && __c4p0.GetType() == typeof(global::System.Collections.Generic.Dictionary>[])) + { + try + { + __operations.Fetch__Dictionary_2_String_List_1_Nullable_1_Int32Array(__c4p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32Array": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is int[])) + { + try + { + __operations.Fetch__Int32Array((int[])criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__Nullable_1_Int32_String": + if (criteria is { Length: 2 } && (criteria[0] is null || criteria[0] is int) && (criteria[1] is null || criteria[1] is string)) + { + try + { + __operations.Fetch__Nullable_1_Int32_String((int?)criteria[0], (string?)criteria[1]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__List_1_Int32": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is global::System.Collections.Generic.List)) + { + try + { + __operations.Fetch__List_1_Int32((global::System.Collections.Generic.List)criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__ValueTuple_2_Int32_String": + if (criteria is { Length: 1 } && criteria[0] is global::System.ValueTuple __c3p0) + { + try + { + __operations.Fetch__ValueTuple_2_Int32_String(__c3p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__Dictionary_2_String_List_1_Nullable_1_Int32Array": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is global::System.Collections.Generic.Dictionary>[])) + { + try + { + __operations.Fetch__Dictionary_2_String_List_1_Nullable_1_Int32Array((global::System.Collections.Generic.Dictionary>[])criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..292debe11f --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,70 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + global::System.Threading.Tasks.Task Fetch__Int32(int id); + } + + global::System.Threading.Tasks.Task IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + await __operations.Fetch__Int32(__c0p0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + await __operations.Fetch__Int32(__c0p0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..278a334932 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs @@ -0,0 +1,204 @@ +//HintName: TestApp.LineItem.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class LineItem : global::TestApp.LineItem.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void CreateChild(); + void FetchChild__Int32(int id); + global::System.Threading.Tasks.Task InsertChild(global::TestApp.IDal dal); + global::System.Threading.Tasks.Task UpdateChild(global::TestApp.IDal dal); + global::System.Threading.Tasks.Task DeleteSelfChild(global::TestApp.IDal dal); + } + + void IDataPortalOperations.CreateChild() => CreateChild(); + void IDataPortalOperations.FetchChild__Int32(int id) => FetchChild(id); + global::System.Threading.Tasks.Task IDataPortalOperations.InsertChild(global::TestApp.IDal dal) => InsertChild(dal); + global::System.Threading.Tasks.Task IDataPortalOperations.UpdateChild(global::TestApp.IDal dal) => UpdateChild(dal); + global::System.Threading.Tasks.Task IDataPortalOperations.DeleteSelfChild(global::TestApp.IDal dal) => DeleteSelfChild(dal); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.CreateChild(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "CreateChild", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.FetchChildAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.FetchChild__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "FetchChild", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.InsertChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "InsertChild"); + try + { + var __c2i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.InsertChild(__c2i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "InsertChild", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.UpdateChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "UpdateChild"); + try + { + var __c3i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.UpdateChild(__c3i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "UpdateChild", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.DeleteSelfChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "DeleteSelfChild"); + try + { + var __c4i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.DeleteSelfChild(__c4i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "DeleteSelfChild", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "CreateChild": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.CreateChild(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "CreateChild", __ex); + } + return; + } + break; + case "FetchChild__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.FetchChild__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "FetchChild", __ex); + } + return; + } + break; + case "InsertChild": + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "InsertChild"); + try + { + var __c2i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.InsertChild(__c2i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "InsertChild", __ex); + } + return; + } + break; + case "UpdateChild": + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "UpdateChild"); + try + { + var __c3i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.UpdateChild(__c3i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "UpdateChild", __ex); + } + return; + } + break; + case "DeleteSelfChild": + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "DeleteSelfChild"); + try + { + var __c4i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.DeleteSelfChild(__c4i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "DeleteSelfChild", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..f5673b76b8 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,204 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Create(); + void Fetch__Int32(int id); + global::System.Threading.Tasks.Task Insert(global::TestApp.IDal dal); + global::System.Threading.Tasks.Task Update(global::TestApp.IDal dal); + global::System.Threading.Tasks.Task DeleteSelf(global::TestApp.IDal dal); + } + + void IDataPortalOperations.Create() => Create(); + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + global::System.Threading.Tasks.Task IDataPortalOperations.Insert(global::TestApp.IDal dal) => Insert(dal); + global::System.Threading.Tasks.Task IDataPortalOperations.Update(global::TestApp.IDal dal) => Update(dal); + global::System.Threading.Tasks.Task IDataPortalOperations.DeleteSelf(global::TestApp.IDal dal) => DeleteSelf(dal); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.InsertAttribute)) + { + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Insert"); + try + { + var __c2i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.Insert(__c2i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Insert", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.UpdateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Update"); + try + { + var __c3i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.Update(__c3i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Update", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.DeleteSelfAttribute)) + { + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "DeleteSelf"); + try + { + var __c4i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.DeleteSelf(__c4i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "DeleteSelf", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Insert": + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Insert"); + try + { + var __c2i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.Insert(__c2i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Insert", __ex); + } + return; + } + break; + case "Update": + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Update"); + try + { + var __c3i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.Update(__c3i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Update", __ex); + } + return; + } + break; + case "DeleteSelf": + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "DeleteSelf"); + try + { + var __c4i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.DeleteSelf(__c4i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "DeleteSelf", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..d98d640ff5 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,68 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Delete__Int32(int id); + } + + void IDataPortalOperations.Delete__Int32(int id) => Delete(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.DeleteAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Delete__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Delete", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Delete__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Delete__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Delete", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DerivedFromBaseWithOperations#TestApp.PersonBase_1.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DerivedFromBaseWithOperations#TestApp.PersonBase_1.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..445348c475 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DerivedFromBaseWithOperations#TestApp.PersonBase_1.DataPortalOperations.g.verified.cs @@ -0,0 +1,22 @@ +//HintName: TestApp.PersonBase_1.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonBase : global::TestApp.PersonBase.IDataPortalOperations + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DerivedFromBaseWithOperations#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DerivedFromBaseWithOperations#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..b1a9ebee86 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DerivedFromBaseWithOperations#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,68 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal new interface IDataPortalOperations + { + void Fetch__String(string name); + } + + void IDataPortalOperations.Fetch__String(string name) => Fetch(name); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is string __c0p0) + { + try + { + __operations.Fetch__String(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__String": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is string)) + { + try + { + __operations.Fetch__String((string)criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DuplicateOperationName#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DuplicateOperationName#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..ba756baa31 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DuplicateOperationName#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,74 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id); + void Fetch__Int32(int id, global::TestApp.IDal dal); + void Fetch__Int32_2(int id); + } + + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + void IDataPortalOperations.Fetch__Int32(int id, global::TestApp.IDal dal) => Fetch(id, dal); + void IDataPortalOperations.Fetch__Int32_2(int id) => Load(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + __operations.Fetch__Int32(__c0p0, __c0i0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + __operations.Fetch__Int32(__c0p0, __c0i0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DuplicateOperationName.verified.txt b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DuplicateOperationName.verified.txt new file mode 100644 index 0000000000..28768b3587 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.DuplicateOperationName.verified.txt @@ -0,0 +1,47 @@ +{ + target: null, + Diagnostics: [ + { + Location: /* + [Fetch] + private void Fetch(int id) { } + ^^^^^ + +*/ + Source0.cs: (9,17)-(9,22), + Message: Methods 'Fetch(int, IDal)' and 'Fetch(int)' on 'PersonEdit' both map to data portal operation 'Fetch__Int32'; name-based dispatch uses 'Fetch(int, IDal)', + Severity: Info, + WarningLevel: 1, + Descriptor: { + Id: CSLADP002, + Title: Data portal operation methods share an operation name, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP002-DuplicateOperationName.md, + MessageFormat: Methods '{0}' and '{1}' on '{2}' both map to data portal operation '{3}'; name-based dispatch uses '{0}', + Category: Csla.DataPortal, + DefaultSeverity: Info, + IsEnabledByDefault: true + } + }, + { + Location: /* + [Fetch] + private void Load(int id) { } + ^^^^ + } +*/ + Source0.cs: (15,17)-(15,21), + Message: Methods 'Fetch(int, IDal)' and 'Load(int)' on 'PersonEdit' both map to data portal operation 'Fetch__Int32'; name-based dispatch uses 'Fetch(int, IDal)', + Severity: Info, + WarningLevel: 1, + Descriptor: { + Id: CSLADP002, + Title: Data portal operation methods share an operation name, + HelpLink: https://github.com/MarimerLLC/csla/tree/main/docs/analyzers/CSLADP002-DuplicateOperationName.md, + MessageFormat: Methods '{0}' and '{1}' on '{2}' both map to data portal operation '{3}'; name-based dispatch uses '{0}', + Category: Csla.DataPortal, + DefaultSeverity: Info, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..40d9d287d8 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs @@ -0,0 +1,68 @@ +//HintName: TestApp.MyCommand.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class MyCommand : global::TestApp.MyCommand.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Execute(); + } + + void IDataPortalOperations.Execute() => Execute(); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.ExecuteAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Execute(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Execute", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Execute": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Execute(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Execute", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ExistingNamedMapping#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ExistingNamedMapping#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..30791f457c --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ExistingNamedMapping#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,44 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Fetch__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..fcf69747ae --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,99 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Create(); + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Create() => Create(); + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.GenericBusinessObject#TestApp.Lookup_1.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.GenericBusinessObject#TestApp.Lookup_1.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..19ebfa717c --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.GenericBusinessObject#TestApp.Lookup_1.DataPortalOperations.g.verified.cs @@ -0,0 +1,82 @@ +//HintName: TestApp.Lookup_1.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class Lookup : global::TestApp.Lookup.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__TKey(TKey key); + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__TKey(TKey key) => Fetch(key); + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is TKey __c0p0 && __c0p0.GetType() == typeof(TKey)) + { + try + { + __operations.Fetch__TKey(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Fetch__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.InCriteriaAndNullableInject#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.InCriteriaAndNullableInject#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..febe36be00 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.InCriteriaAndNullableInject#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,104 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__String_Int32(in string? name, in int id); + void Create(int? count, global::TestApp.IDal? dal); + } + + void IDataPortalOperations.Fetch__String_Int32(in string? name, in int id) => Fetch(in name, in id); + void IDataPortalOperations.Create(int? count, global::TestApp.IDal? dal) => Create(count, dal); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + var __c0i0 = (int?)(serviceProvider.GetService(typeof(int?))); + var __c0i1 = (global::TestApp.IDal?)(serviceProvider.GetService(typeof(global::TestApp.IDal))); + __operations.Create(__c0i0, __c0i1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 2 } && criteria[0] is string __c1p0 && criteria[1] is int __c1p1) + { + try + { + __operations.Fetch__String_Int32(in __c1p0, in __c1p1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__String_Int32": + if (criteria is { Length: 2 } && (criteria[0] is null || criteria[0] is string) && criteria[1] is int __c0p1) + { + try + { + var __c0p0 = (string?)criteria[0]!; + __operations.Fetch__String_Int32(in __c0p0, in __c0p1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + var __c1i0 = (int?)(serviceProvider.GetService(typeof(int?))); + var __c1i1 = (global::TestApp.IDal?)(serviceProvider.GetService(typeof(global::TestApp.IDal))); + __operations.Create(__c1i0, __c1i1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.InjectBeforeCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.InjectBeforeCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..85c75718ea --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.InjectBeforeCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,72 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32_String(global::TestApp.IDal dal, int id, global::TestApp.IDal? backup, string name); + } + + void IDataPortalOperations.Fetch__Int32_String(global::TestApp.IDal dal, int id, global::TestApp.IDal? backup, string name) => Fetch(dal, id, backup, name); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 2 } && criteria[0] is int __c0p0 && criteria[1] is string __c0p1) + { + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + var __c0i1 = (global::TestApp.IDal?)(serviceProvider.GetService(typeof(global::TestApp.IDal))); + __operations.Fetch__Int32_String(__c0i0, __c0p0, __c0i1, __c0p1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32_String": + if (criteria is { Length: 2 } && criteria[0] is int __c0p0 && (criteria[1] is null || criteria[1] is string)) + { + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + var __c0i1 = (global::TestApp.IDal?)(serviceProvider.GetService(typeof(global::TestApp.IDal))); + __operations.Fetch__Int32_String(__c0i0, __c0p0, __c0i1, (string)criteria[1]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.KeyedInject#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.KeyedInject#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..83860fee03 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.KeyedInject#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,107 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id, global::TestApp.IDal dal, global::TestApp.IDal? secondary); + void Create(global::TestApp.IDal dal, global::TestApp.IDal other); + } + + void IDataPortalOperations.Fetch__Int32(int id, global::TestApp.IDal dal, global::TestApp.IDal? secondary) => Fetch(id, dal, secondary); + void IDataPortalOperations.Create(global::TestApp.IDal dal, global::TestApp.IDal other) => Create(dal, other); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService(serviceProvider, typeof(global::TestApp.IDal), (short)(-1)))!; + var __c0i1 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService(serviceProvider, typeof(global::TestApp.IDal), (byte)2))!; + __operations.Create(__c0i0, __c0i1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + var __c1i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService(serviceProvider, typeof(global::TestApp.IDal), "primary"))!; + var __c1i1 = (global::TestApp.IDal?)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetKeyedService(serviceProvider, typeof(global::TestApp.IDal), global::TestApp.DalKind.Secondary)); + __operations.Fetch__Int32(__c1p0, __c1i0, __c1i1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService(serviceProvider, typeof(global::TestApp.IDal), "primary"))!; + var __c0i1 = (global::TestApp.IDal?)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetKeyedService(serviceProvider, typeof(global::TestApp.IDal), global::TestApp.DalKind.Secondary)); + __operations.Fetch__Int32(__c0p0, __c0i0, __c0i1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + var __c1i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService(serviceProvider, typeof(global::TestApp.IDal), (short)(-1)))!; + var __c1i1 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderKeyedServiceExtensions.GetRequiredKeyedService(serviceProvider, typeof(global::TestApp.IDal), (byte)2))!; + __operations.Create(__c1i0, __c1i1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..f8c7a1e844 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,96 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__String(string name); + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__String(string name) => Fetch(name); + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is string __c0p0) + { + try + { + __operations.Fetch__String(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__String": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is string)) + { + try + { + __operations.Fetch__String((string)criteria[0]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..fe4459a3d8 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,71 @@ +//HintName: TestApp.Outer.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class Outer + { + partial class PersonEdit : global::TestApp.Outer.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Fetch__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Fetch__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..3d662c9cd2 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,72 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + global::System.Threading.Tasks.Task Fetch__Int32(int id, global::TestApp.ILogger? logger); + } + + global::System.Threading.Tasks.Task IDataPortalOperations.Fetch__Int32(int id, global::TestApp.ILogger? logger) => Fetch(id, logger); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + var __c0i0 = (global::TestApp.ILogger?)(serviceProvider.GetService(typeof(global::TestApp.ILogger))); + await __operations.Fetch__Int32(__c0p0, __c0i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + var __c0i0 = (global::TestApp.ILogger?)(serviceProvider.GetService(typeof(global::TestApp.ILogger))); + await __operations.Fetch__Int32(__c0p0, __c0i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..9f7d9f1205 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,72 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + global::System.Threading.Tasks.Task Insert(global::TestApp.IDal dal); + } + + global::System.Threading.Tasks.Task IDataPortalOperations.Insert(global::TestApp.IDal dal) => Insert(dal); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.InsertAttribute)) + { + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Insert"); + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.Insert(__c0i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Insert", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Insert": + if (criteria is null or { Length: 0 }) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Insert"); + try + { + var __c0i0 = (global::TestApp.IDal)(global::Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(serviceProvider, typeof(global::TestApp.IDal)))!; + await __operations.Insert(__c0i0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Insert", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ParamsArrayAndKeywordNames#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ParamsArrayAndKeywordNames#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..3640bcbb8c --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.ParamsArrayAndKeywordNames#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,70 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__ObjectArray(object[] values); + void Create__Int32_String(int @class, string @event); + } + + void IDataPortalOperations.Fetch__ObjectArray(object[] values) => Fetch(values); + void IDataPortalOperations.Create__Int32_String(int @class, string @event) => Create(@class, @event); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is { Length: 2 } && criteria[0] is int __c0p0 && criteria[1] is string __c0p1) + { + try + { + __operations.Create__Int32_String(__c0p0, __c0p1); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Create__Int32_String": + if (criteria is { Length: 2 } && criteria[0] is int __c0p0 && (criteria[1] is null || criteria[1] is string)) + { + try + { + __operations.Create__Int32_String(__c0p0, (string)criteria[1]!); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.PartialClassAcrossFiles#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.PartialClassAcrossFiles#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..fcf69747ae --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.PartialClassAcrossFiles#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,99 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Create(); + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Create() => Create(); + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + try + { + __operations.Fetch__Int32(__c1p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..5b03d2a0df --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,99 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Create(); + void CreateChild(); + } + + void IDataPortalOperations.Create() => Create(); + void IDataPortalOperations.CreateChild() => Create(); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.CreateChildAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.CreateChild(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + case "CreateChild": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.CreateChild(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.RunLocalAndValueTask#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.RunLocalAndValueTask#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..47b41957bd --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.RunLocalAndValueTask#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,131 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Create(); + global::System.Threading.Tasks.ValueTask Fetch__Int32(int id); + global::System.Threading.Tasks.ValueTask Fetch__String(string name); + } + + void IDataPortalOperations.Create() => Create(); + global::System.Threading.Tasks.ValueTask IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + global::System.Threading.Tasks.ValueTask IDataPortalOperations.Fetch__String(string name) => Fetch(name); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + else if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + await __operations.Fetch__Int32(__c1p0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + if (criteria is { Length: 1 } && criteria[0] is string __c2p0) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + await __operations.Fetch__String(__c2p0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c1p0) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + await __operations.Fetch__Int32(__c1p0).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + case "Fetch__String": + if (criteria is { Length: 1 } && (criteria[0] is null || criteria[0] is string)) + { + global::Csla.Server.DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(this, isSync, serviceProvider, "Fetch"); + try + { + await __operations.Fetch__String((string)criteria[0]!).ConfigureAwait(false); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..b534c5a5a4 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,68 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Create(); + } + + void IDataPortalOperations.Create() => Create(); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.CreateAttribute)) + { + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Create": + if (criteria is null or { Length: 0 }) + { + try + { + __operations.Create(); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Create", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs new file mode 100644 index 0000000000..fc9b8a3a20 --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/Snapshots/DataPortalOperationsGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs @@ -0,0 +1,68 @@ +//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs +// +#nullable enable +#pragma warning disable CS1998 // Async method lacks 'await' operators + +namespace TestApp +{ + partial class PersonEdit : global::TestApp.PersonEdit.IDataPortalOperations, global::Csla.Server.IDataPortalOperationMapping, global::Csla.Server.IDataPortalOperationNamedMapping + { + /// + /// Data portal operation methods declared by this type. + /// Generated by CSLA .NET so the methods are statically referenced. + /// + [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + internal interface IDataPortalOperations + { + void Fetch__Int32(int id); + } + + void IDataPortalOperations.Fetch__Int32(int id) => Fetch(id); + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( + global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + if (operationType == typeof(global::Csla.FetchAttribute)) + { + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Fetch__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); + } + + async global::System.Threading.Tasks.Task global::Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( + string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) + { + var __operations = (IDataPortalOperations)this; + switch (operationName) + { + case "Fetch__Int32": + if (criteria is { Length: 1 } && criteria[0] is int __c0p0) + { + try + { + __operations.Fetch__Int32(__c0p0); + } + catch (global::System.Exception __ex) + { + throw global::Csla.Server.DataPortalOperationHelper.CreateCallMethodException(this, "Fetch", __ex); + } + return; + } + break; + } + throw new global::Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/StageCachingTester.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/StageCachingTester.cs new file mode 100644 index 0000000000..2d9761dcbe --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/StageCachingTester.cs @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Verifies incremental generator pipeline stages are cacheable +//----------------------------------------------------------------------- +// Adapted from Csla.DataPortalExtensions by Stefan Ossendorf +// (https://github.com/StefanOssendorf/Csla.DataPortalExtensions), +// licensed under the MIT License. Copyright (c) 2023 Stefan Ossendorf. +//----------------------------------------------------------------------- +using System.Collections; +using System.Collections.Immutable; +using System.Reflection; +using FluentAssertions; +using Microsoft.CodeAnalysis; + +namespace Csla.Generator.AutoImplementProperties.CSharp.Tests.DataPortalOperations +{ + internal static class StageCachingTester + { + public static void VerifyStageCaching(string source, Type trackingNamesType) where T : IIncrementalGenerator, new() + { + var (driver, compilation) = DataPortalOperationsTestHelper.Setup(source, trackSteps: true); + var clonedCompilation = compilation.Clone(); + + driver = driver.RunGenerators(compilation); + var runResult1 = driver.GetRunResult(); + var runResult2 = driver.RunGenerators(clonedCompilation).GetRunResult(); + + AssertRunsEqual(runResult1, runResult2, GetTrackingNames(trackingNamesType)); + + runResult2.Results[0] + .TrackedOutputSteps + .SelectMany(step => step.Value) + .SelectMany(execution => execution.Outputs) + .Should() + .OnlyContain(output => output.Reason == IncrementalStepRunReason.Cached); + } + + private static string[] GetTrackingNames(Type trackingNamesType) + => trackingNamesType + .GetFields() + .Where(field => field.IsLiteral && !field.IsInitOnly && field.FieldType == typeof(string)) + .Select(field => (string?)field.GetRawConstantValue()) + .Where(name => !string.IsNullOrEmpty(name)) + .ToArray()!; + + private static void AssertRunsEqual(GeneratorDriverRunResult runResult1, GeneratorDriverRunResult runResult2, string[] trackingNames) + { + var trackedSteps1 = GetTrackedSteps(runResult1, trackingNames); + var trackedSteps2 = GetTrackedSteps(runResult2, trackingNames); + + trackedSteps1.Should() + .NotBeEmpty() + .And.HaveSameCount(trackedSteps2) + .And.ContainKeys(trackedSteps2.Keys); + + foreach (var (trackingName, runSteps1) in trackedSteps1) + AssertEqual(runSteps1, trackedSteps2[trackingName], trackingName); + + static Dictionary> GetTrackedSteps(GeneratorDriverRunResult runResult, string[] trackingNames) + => runResult + .Results[0] + .TrackedSteps + .Where(step => trackingNames.Contains(step.Key)) + .ToDictionary(step => step.Key, step => step.Value); + } + + private static void AssertEqual(ImmutableArray runSteps1, ImmutableArray runSteps2, string stepName) + { + runSteps1.Should().HaveSameCount(runSteps2); + + for (var i = 0; i < runSteps1.Length; i++) + { + var runStep1 = runSteps1[i]; + var runStep2 = runSteps2[i]; + + var outputs1 = runStep1.Outputs.Select(output => output.Value); + var outputs2 = runStep2.Outputs.Select(output => output.Value); + outputs1.Should().Equal(outputs2, $"because {stepName} should produce cacheable outputs"); + + runStep2.Outputs.Should().OnlyContain( + output => output.Reason == IncrementalStepRunReason.Cached || output.Reason == IncrementalStepRunReason.Unchanged, + $"{stepName} expected to have reason {IncrementalStepRunReason.Cached} or {IncrementalStepRunReason.Unchanged}"); + + AssertObjectGraph(runStep1, stepName); + } + } + + private static void AssertObjectGraph(IncrementalGeneratorRunStep runStep, string stepName) + { + var because = $"{stepName} shouldn't contain banned symbols"; + var visited = new HashSet(ReferenceEqualityComparer.Instance); + + foreach (var (value, _) in runStep.Outputs) + Visit(value); + + void Visit(object? node) + { + if (node is null || !visited.Add(node)) + return; + + node.Should().NotBeAssignableTo(because) + .And.NotBeAssignableTo(because) + .And.NotBeAssignableTo(because) + .And.NotBeAssignableTo(because); + + var type = node.GetType(); + if (type.IsPrimitive || type.IsEnum || type == typeof(string)) + return; + + if (node is IEnumerable collection) + { + foreach (var element in collection) + Visit(element); + return; + } + + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + Visit(field.GetValue(node)); + } + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/TestAnalyzerConfigOptionsProvider.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/TestAnalyzerConfigOptionsProvider.cs new file mode 100644 index 0000000000..301befec4e --- /dev/null +++ b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/DataPortalOperations/TestAnalyzerConfigOptionsProvider.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +//----------------------------------------------------------------------- +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Csla.Generator.AutoImplementProperties.CSharp.Tests.DataPortalOperations +{ + /// + /// Supplies global build properties, such as build_property.* values, + /// to generators and analyzers under test. + /// + internal sealed class TestAnalyzerConfigOptionsProvider(Dictionary globalOptions) : AnalyzerConfigOptionsProvider + { + public static TestAnalyzerConfigOptionsProvider Empty { get; } = new([]); + + public override AnalyzerConfigOptions GlobalOptions { get; } = new Options(globalOptions); + + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) => GlobalOptions; + + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) => GlobalOptions; + + private sealed class Options(Dictionary values) : AnalyzerConfigOptions + { + public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) => values.TryGetValue(key, out value); + } + } +} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 69916fe6bf..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial bool? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index afe38fd635..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial bool[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 764624d2f6..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial bool[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 7bef323b55..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=bool_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial bool Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index b17369f1cc..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial byte? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 6405ac9e21..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial byte[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 60cba132b6..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial byte[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index ce7fbfdeab..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=byte_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial byte Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 7687344adf..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial char? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index c21c684384..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial char[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 5feab6367a..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial char[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index eaef8315dc..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=char_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial char Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 0343cc15fc..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial decimal? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 3bc0060cdc..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial decimal[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 36228611f1..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial decimal[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 215d73734e..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=decimal_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial decimal Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index f1147813cb..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial double? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 9265abf5b0..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial double[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 14d08204ad..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial double[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 5a2c513df2..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=double_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial double Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index f2239a78e6..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial float? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index e32eff6c1f..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial float[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index ccd1eb1b74..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial float[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 0beae55dfa..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=float_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial float Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 74c1e446f3..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial int? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index b9f2266656..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial int[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index dfb91f097e..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial int[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 102621c198..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=int_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial int Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index ad32a64fc1..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial long? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index e83abce87c..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial long[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 50d45d563e..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial long[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index cf8caaf7e5..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=long_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial long Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index a42987b642..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial object? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index c6bf227f0f..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial object[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index ab99437761..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial object[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 76956c1a26..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=object_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial object Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 4cc203e6fd..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial sbyte? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index dc7c035575..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial sbyte[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index e5871c47c3..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial sbyte[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 5113172aba..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=sbyte_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial sbyte Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 8087a40ca5..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial short? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 106072e93d..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial short[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 3ddff9e00d..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial short[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index e447538c8a..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=short_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial short Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 9a9f504268..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial string? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index a890d3f5b6..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial string[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index d6370bef50..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial string[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index e0130eaecc..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=string_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial string Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index abd3dc252e..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial uint? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 8c224d70ed..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial uint[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 125112f9fa..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial uint[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index bec9c9f83c..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=uint_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial uint Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 6ef0db6c01..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ulong? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index b95c2d9872..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ulong[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index e68617a4d6..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ulong[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 4fcc485078..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ulong_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ulong Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index b5e93db5ec..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ushort? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort[]-_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort[]-_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index d272c81fb6..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort[]-_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ushort[]? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort[]_isNullable=True#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort[]_isNullable=True#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 79ed88b00a..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort[]_isNullable=True#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ushort[] Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort_isNullable=False#BusinessBaseTestClass.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort_isNullable=False#BusinessBaseTestClass.g.received.cs deleted file mode 100644 index 6fb309228d..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.Case09_type=ushort_isNullable=False#BusinessBaseTestClass.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BusinessBaseTestClass.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BusinessBaseTestClass -{ - public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); - public partial ushort Name - { - get => GetProperty(NameProperty)!; - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.MyTestMethod#BOTest.g.received.cs b/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.MyTestMethod#BOTest.g.received.cs deleted file mode 100644 index bc22a023d8..0000000000 --- a/Source/tests/Csla.Generator.AutoImplementProperties.CSharp.Tests/Snapshots/AutoImplementPropertiesGeneratorTests.MyTestMethod#BOTest.g.received.cs +++ /dev/null @@ -1,17 +0,0 @@ -//HintName: BOTest.g.cs -// -#nullable enable -using System; -using Csla; - -namespace Test; - -public partial class BOTest -{ - public static readonly PropertyInfo?>?> NameProperty = RegisterProperty?>?>(nameof(Name)); - public partial global::Csla.Core.MobileDictionary?>? Name - { - get => GetProperty(NameProperty); - private set => SetProperty(NameProperty, value); - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj deleted file mode 100644 index a439993e77..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net10.0;net11.0 - preview - false - enable - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/DataPortalInterfaceGeneratorTests.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/DataPortalInterfaceGeneratorTests.cs deleted file mode 100644 index fdcecdb727..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/DataPortalInterfaceGeneratorTests.cs +++ /dev/null @@ -1,312 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -//----------------------------------------------------------------------- -using Csla.Generator.DataPortalInterfaces.CSharp.Tests.Helpers; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Tests -{ - [TestClass] - public class DataPortalInterfaceGeneratorTests : VerifyBase - { - [TestMethod("Single Create operation with no parameters")] - public async Task SingleCreateNoParams() - { - var source = """ - using Csla; - - namespace TestApp - { - public partial class PersonEdit : Csla.BusinessBase - { - [Create] - private void Create() { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Single Fetch operation with one criteria parameter")] - public async Task SingleFetchWithCriteria() - { - var source = """ - using Csla; - - namespace TestApp - { - public partial class PersonEdit : Csla.BusinessBase - { - [Fetch] - private void Fetch(int id) { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Operation method with Inject parameter")] - public async Task OperationWithInjectParam() - { - var source = """ - using Csla; - using System.Threading.Tasks; - - namespace TestApp - { - public interface IDal { } - - public partial class PersonEdit : Csla.BusinessBase - { - [Insert] - private async Task Insert([Inject] IDal dal) { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Operation method with Inject AllowNull parameter")] - public async Task OperationWithInjectAllowNull() - { - var source = """ - using Csla; - using System.Threading.Tasks; - - namespace TestApp - { - public interface ILogger { } - - public partial class PersonEdit : Csla.BusinessBase - { - [Fetch] - private async Task Fetch(int id, [Inject(AllowNull = true)] ILogger? logger) { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Multiple overloads of same Fetch operation")] - public async Task MultipleOverloads() - { - var source = """ - using Csla; - - namespace TestApp - { - public partial class PersonEdit : Csla.BusinessBase - { - [Fetch] - private void Fetch(string name) { } - - [Fetch] - private void Fetch(int id) { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Method with both root and child attributes")] - public async Task RootAndChildAttributes() - { - var source = """ - using Csla; - - namespace TestApp - { - public partial class PersonEdit : Csla.BusinessBase - { - [Create] - [CreateChild] - private void Create() { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Async method returning Task")] - public async Task AsyncMethod() - { - var source = """ - using Csla; - using System.Threading.Tasks; - - namespace TestApp - { - public partial class PersonEdit : Csla.BusinessBase - { - [Fetch] - private async Task Fetch(int id) { await Task.CompletedTask; } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("File-scoped namespace")] - public async Task FileScopedNamespace() - { - var source = """ - using Csla; - - namespace TestApp; - - public partial class PersonEdit : Csla.BusinessBase - { - [Create] - private void Create() { } - - [Fetch] - private void Fetch(int id) { } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Complete CRUD operations")] - public async Task CompleteCrud() - { - var source = """ - using Csla; - using System.Threading.Tasks; - - namespace TestApp - { - public interface IDal { } - - public partial class PersonEdit : Csla.BusinessBase - { - [Create] - private void Create() { } - - [Fetch] - private void Fetch(int id) { } - - [Insert] - private async Task Insert([Inject] IDal dal) { await Task.CompletedTask; } - - [Update] - private async Task Update([Inject] IDal dal) { await Task.CompletedTask; } - - [DeleteSelf] - private async Task DeleteSelf([Inject] IDal dal) { await Task.CompletedTask; } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Delete operation with criteria")] - public async Task DeleteWithCriteria() - { - var source = """ - using Csla; - - namespace TestApp - { - public partial class PersonEdit : Csla.BusinessBase - { - [Delete] - private void Delete(int id) { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Child operations")] - public async Task ChildOperations() - { - var source = """ - using Csla; - using System.Threading.Tasks; - - namespace TestApp - { - public interface IDal { } - - public partial class LineItem : Csla.BusinessBase - { - [CreateChild] - private void CreateChild() { } - - [FetchChild] - private void FetchChild(int id) { } - - [InsertChild] - private async Task InsertChild([Inject] IDal dal) { await Task.CompletedTask; } - - [UpdateChild] - private async Task UpdateChild([Inject] IDal dal) { await Task.CompletedTask; } - - [DeleteSelfChild] - private async Task DeleteSelfChild([Inject] IDal dal) { await Task.CompletedTask; } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Execute operation on command object")] - public async Task ExecuteCommand() - { - var source = """ - using Csla; - - namespace TestApp - { - public partial class MyCommand : Csla.CommandBase - { - [Execute] - private void Execute() { } - } - } - """; - - await TestHelperVerify(source); - } - - [TestMethod("Nested class with data portal operations")] - public async Task NestedClass() - { - var source = """ - using Csla; - - namespace TestApp - { - public partial class Outer - { - public partial class PersonEdit : Csla.BusinessBase - { - [Fetch] - private void Fetch(int id) { } - } - } - } - """; - - await TestHelperVerify(source); - } - - private static async Task TestHelperVerify(string source, params string[]? additionalSources) - { - await TestHelper.Verify(source, additionalSources); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index 825b39f51c..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.AsyncMethod#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,39 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.FetchAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - await Fetch(p0_int).ConfigureAwait(false); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Fetch__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - await Fetch(p0_int).ConfigureAwait(false); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs deleted file mode 100644 index 6dcbb4cab2..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ChildOperations#TestApp.LineItem.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,105 +0,0 @@ -//HintName: TestApp.LineItem.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class LineItem : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.CreateChildAttribute)) - { - if (criteria is null or { Length: 0 }) - { - CreateChild(); - return; - } - } - else if (operationType == typeof(Csla.FetchChildAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - FetchChild(p0_int); - return; - } - } - else if (operationType == typeof(Csla.InsertChildAttribute)) - { - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await InsertChild(dal).ConfigureAwait(false); - return; - } - } - else if (operationType == typeof(Csla.UpdateChildAttribute)) - { - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await UpdateChild(dal).ConfigureAwait(false); - return; - } - } - else if (operationType == typeof(Csla.DeleteSelfChildAttribute)) - { - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await DeleteSelfChild(dal).ConfigureAwait(false); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "CreateChild": - if (criteria is null or { Length: 0 }) - { - CreateChild(); - return; - } - break; - case "FetchChild__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - FetchChild(p0_int); - return; - } - break; - case "InsertChild": - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await InsertChild(dal).ConfigureAwait(false); - return; - } - break; - case "UpdateChild": - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await UpdateChild(dal).ConfigureAwait(false); - return; - } - break; - case "DeleteSelfChild": - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await DeleteSelfChild(dal).ConfigureAwait(false); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index ab81066256..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.CompleteCrud#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,105 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.CreateAttribute)) - { - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - } - else if (operationType == typeof(Csla.FetchAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - } - else if (operationType == typeof(Csla.InsertAttribute)) - { - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await Insert(dal).ConfigureAwait(false); - return; - } - } - else if (operationType == typeof(Csla.UpdateAttribute)) - { - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await Update(dal).ConfigureAwait(false); - return; - } - } - else if (operationType == typeof(Csla.DeleteSelfAttribute)) - { - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await DeleteSelf(dal).ConfigureAwait(false); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Create": - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - break; - case "Fetch__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - break; - case "Insert": - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await Insert(dal).ConfigureAwait(false); - return; - } - break; - case "Update": - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await Update(dal).ConfigureAwait(false); - return; - } - break; - case "DeleteSelf": - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await DeleteSelf(dal).ConfigureAwait(false); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index c604313f49..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.DeleteWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,39 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.DeleteAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Delete(p0_int); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Delete__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Delete(p0_int); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs deleted file mode 100644 index 59e4789567..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.ExecuteCommand#TestApp.MyCommand.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,39 +0,0 @@ -//HintName: TestApp.MyCommand.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class MyCommand : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.ExecuteAttribute)) - { - if (criteria is null or { Length: 0 }) - { - Execute(); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Execute": - if (criteria is null or { Length: 0 }) - { - Execute(); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index a024ca2cff..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.FileScopedNamespace#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,54 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.CreateAttribute)) - { - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - } - else if (operationType == typeof(Csla.FetchAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Create": - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - break; - case "Fetch__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index dd8fac0098..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.MultipleOverloads#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,51 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.FetchAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is string p0_string) - { - Fetch(p0_string); - return; - } - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Fetch__String": - if (criteria is { Length: 1 } && criteria[0] is string p0_string) - { - Fetch(p0_string); - return; - } - break; - case "Fetch__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index bb807b6b31..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.NestedClass#TestApp.Outer.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,42 +0,0 @@ -//HintName: TestApp.Outer.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class Outer - { - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.FetchAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Fetch__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index b8bf49e1d1..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectAllowNull#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,41 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.FetchAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - var logger = (global::TestApp.ILogger?)serviceProvider.GetService(typeof(global::TestApp.ILogger)); - await Fetch(p0_int, logger).ConfigureAwait(false); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Fetch__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - var logger = (global::TestApp.ILogger?)serviceProvider.GetService(typeof(global::TestApp.ILogger)); - await Fetch(p0_int, logger).ConfigureAwait(false); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index af73cdb9a5..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.OperationWithInjectParam#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,41 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.InsertAttribute)) - { - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await Insert(dal).ConfigureAwait(false); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Insert": - if (criteria is null or { Length: 0 }) - { - var dal = (global::TestApp.IDal)(serviceProvider.GetService(typeof(global::TestApp.IDal)) ?? throw new global::System.InvalidOperationException($"No service for type '{typeof(global::TestApp.IDal)}' has been registered.")); - await Insert(dal).ConfigureAwait(false); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index 4826fada4d..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.RootAndChildAttributes#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,54 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.CreateAttribute)) - { - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - } - else if (operationType == typeof(Csla.CreateChildAttribute)) - { - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Create": - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - break; - case "CreateChild": - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index 19785483bf..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleCreateNoParams#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,39 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.CreateAttribute)) - { - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Create": - if (criteria is null or { Length: 0 }) - { - Create(); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs deleted file mode 100644 index 3f44ca0017..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/Snapshots/DataPortalInterfaceGeneratorTests.SingleFetchWithCriteria#TestApp.PersonEdit.DataPortalOperations.g.verified.cs +++ /dev/null @@ -1,39 +0,0 @@ -//HintName: TestApp.PersonEdit.DataPortalOperations.g.cs -// -#nullable enable - -namespace TestApp -{ - public partial class PersonEdit : Csla.Server.IDataPortalOperationMapping, Csla.Server.IDataPortalOperationNamedMapping - { - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationMapping.InvokeOperationAsync( - global::System.Type operationType, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - if (operationType == typeof(Csla.FetchAttribute)) - { - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationType, criteria); - } - - async global::System.Threading.Tasks.Task Csla.Server.IDataPortalOperationNamedMapping.InvokeNamedOperationAsync( - string operationName, bool isSync, object?[]? criteria, global::System.IServiceProvider serviceProvider) - { - switch (operationName) - { - case "Fetch__Int32": - if (criteria is { Length: 1 } && criteria[0] is int p0_int) - { - Fetch(p0_int); - return; - } - break; - } - throw new Csla.Server.DataPortalOperationNotSupportedException(operationName, criteria); - } - } -} diff --git a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/TestHelper.cs b/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/TestHelper.cs deleted file mode 100644 index c076969e10..0000000000 --- a/Source/tests/Csla.Generator.DataPortalInterfaces.CSharp.Tests/Helpers/TestHelper.cs +++ /dev/null @@ -1,91 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Marimer LLC. All rights reserved. -// Website: https://cslanet.com -// -//----------------------------------------------------------------------- -using FluentAssertions; -using FluentAssertions.Execution; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; - -namespace Csla.Generator.DataPortalInterfaces.CSharp.Tests.Helpers -{ - public static class TestHelper where T : IIncrementalGenerator, new() - { - public static Task Verify(string source, IEnumerable? additionalSources = null) - { - var syntaxTrees = new List - { - CSharpSyntaxTree.ParseText(source) - }; - - foreach (var src in additionalSources ?? []) - { - syntaxTrees.Add(CSharpSyntaxTree.ParseText(src)); - } - - var references = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location)) - .Select(a => MetadataReference.CreateFromFile(a.Location)) - .Concat([ - MetadataReference.CreateFromFile(typeof(FetchAttribute).Assembly.Location) - ]); - - var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - .WithNullableContextOptions(NullableContextOptions.Enable) - .WithSpecificDiagnosticOptions(new Dictionary { { "CS8019", ReportDiagnostic.Suppress } }); - - CSharpCompilation compilation = CSharpCompilation.Create( - assemblyName: "Tests", - syntaxTrees: syntaxTrees, - references: references, - options: compilationOptions); - - var generator = new T().AsSourceGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); - - using (new AssertionScope()) - { - outputCompilation.GetDiagnostics() - .Where(d => d.Severity == DiagnosticSeverity.Error) - .Should().BeEmpty(); - diagnostics.Should().BeEmpty(); - } - - return Verifier.Verify(driver).UseDirectory("Snapshots"); - } - - public static Task VerifyWithDiagnostics(string source) - { - var syntaxTrees = new List - { - CSharpSyntaxTree.ParseText(source) - }; - - var references = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.IsDynamic && !string.IsNullOrWhiteSpace(a.Location)) - .Select(a => MetadataReference.CreateFromFile(a.Location)) - .Concat([ - MetadataReference.CreateFromFile(typeof(FetchAttribute).Assembly.Location) - ]); - - var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - .WithNullableContextOptions(NullableContextOptions.Enable) - .WithSpecificDiagnosticOptions(new Dictionary { { "CS8019", ReportDiagnostic.Suppress } }); - - CSharpCompilation compilation = CSharpCompilation.Create( - assemblyName: "Tests", - syntaxTrees: syntaxTrees, - references: references, - options: compilationOptions); - - var generator = new T().AsSourceGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); - - return Verifier.Verify(driver).UseDirectory("Snapshots"); - } - } -} diff --git a/Source/tests/Csla.test/Csla.Tests.csproj b/Source/tests/Csla.test/Csla.Tests.csproj index 2c9236cc84..aa36986bf5 100644 --- a/Source/tests/Csla.test/Csla.Tests.csproj +++ b/Source/tests/Csla.test/Csla.Tests.csproj @@ -68,7 +68,6 @@ - diff --git a/Source/tests/csla.netcore.test/DataPortal/DataPortalExtensionsTests.cs b/Source/tests/csla.netcore.test/DataPortal/DataPortalExtensionsTests.cs new file mode 100644 index 0000000000..c3ac0e16cd --- /dev/null +++ b/Source/tests/csla.netcore.test/DataPortal/DataPortalExtensionsTests.cs @@ -0,0 +1,741 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Integration tests for generated data portal operations and extensions +//----------------------------------------------------------------------- +using System.Collections.Concurrent; +using System.Reflection; +using Csla.Channels.Local; +using Csla.Core; +using Csla.DataPortalClient; +using Csla.Reflection; +using Csla.Server; +using Csla.Testing; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Csla.Test.DataPortal +{ + [TestClass] + public partial class DataPortalExtensionsTests + { + private static CslaTestHost _testHost = default!; + + [ClassInitialize] + public static void ClassSetup(TestContext context) + { + _ = context; + _testHost = CslaTestHost.Create(options => options.ConfigureServices(services => + { + services.AddSingleton(); + services.AddTransient(sp => new CountingRemoteProxy(sp.GetRequiredService())); + })); + } + + [ClassCleanup] + public static void ClassCleanup() + { + _testHost?.Dispose(); + } + + [TestInitialize] + public void TestSetup() + { + CountingRemoteProxy.Reset(); + } + + #region Generated extensions + + [TestMethod] + public async Task CreateAsync_UsesNamedDispatch() + { + var obj = await _testHost.GetDataPortal().PortalNewAsync(); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be("new"); + } + + [TestMethod] + public void Create_Sync_UsesNamedDispatch() + { + var obj = _testHost.GetDataPortal().PortalNewNamed("abc"); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be("abc"); + } + + [TestMethod] + public async Task FetchAsync_UsesNamedDispatchAndInjectsServices() + { + var obj = await _testHost.GetDataPortal().PortalGetByIdAsync(42); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Id.Should().Be(42); + obj.Name.Should().Be(ExtensionTestDal.Marker); + obj.IsNew.Should().BeFalse(); + } + + [TestMethod] + public void Fetch_Sync_UsesNamedDispatch() + { + var obj = _testHost.GetDataPortal().PortalGetById(7); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Id.Should().Be(7); + } + + [TestMethod] + public async Task FetchAsync_StringArrayCriteria_IsNotSplit() + { + var obj = await _testHost.GetDataPortal().PortalGetByNamesAsync(["a", "b", "c"]); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be("a,b,c"); + } + + [TestMethod] + public async Task FetchAsync_NullableCriteria_AcceptsNull() + { + var obj = await _testHost.GetDataPortal().PortalGetByOptionalAsync(null, null); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be("|"); + } + + [TestMethod] + public async Task FetchAsync_AsyncOperationMethod_UsesNamedDispatch() + { + var key = Guid.NewGuid(); + var obj = await _testHost.GetDataPortal().PortalLoadAsync(key); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be(key.ToString()); + } + + [TestMethod] + public void Fetch_Sync_AsyncOperationMethod_ThrowsLikeReflectionDispatch() + { + // a local data portal leaves the execution location as client + using var localHost = CslaTestHost.Create(); + var generated = () => localHost.GetDataPortal().PortalLoad(Guid.NewGuid()); + var reflection = () => localHost.GetDataPortal().Fetch(Guid.NewGuid()); + + var generatedException = generated.Should().Throw().Which; + var reflectionException = reflection.Should().Throw().Which; + + generatedException.GetType().Should().Be(reflectionException.GetType()); + generatedException.GetBaseException().Should().BeOfType(); + reflectionException.GetBaseException().Should().BeOfType(); + } + + [TestMethod] + public async Task FetchAsync_OperationThrows_WrapsExceptionLikeReflectionDispatch() + { + var generated = () => _testHost.GetDataPortal().PortalFailAsync(false); + var reflection = () => _testHost.GetDataPortal().FetchAsync(false); + + var generatedException = (await generated.Should().ThrowAsync()).Which; + var reflectionException = (await reflection.Should().ThrowAsync()).Which; + + ExceptionTypes(generatedException).Should().Equal(ExceptionTypes(reflectionException)); + ExceptionTypes(generatedException).Should().Contain(typeof(CallMethodException)); + generatedException.GetBaseException().Should().BeOfType(); + } + + [TestMethod] + public async Task FetchAsync_MissingRequiredService_WrapsExceptionLikeReflectionDispatch() + { + var generated = () => _testHost.GetDataPortal().PortalGetRequiredAsync("code"); + var reflection = () => _testHost.GetDataPortal().FetchAsync("code"); + + var generatedException = (await generated.Should().ThrowAsync()).Which; + var reflectionException = (await reflection.Should().ThrowAsync()).Which; + + ExceptionTypes(generatedException).Should().Equal(ExceptionTypes(reflectionException)); + ExceptionTypes(generatedException).Should().Contain(typeof(CallMethodException)); + generatedException.GetBaseException().Should().BeOfType(); + } + + [TestMethod] + public async Task FetchAsync_OperationThrowsNotSupported_IsNotInvokedAgain() + { + EdgeCaseRoot.FailCalls = 0; + + var act = () => _testHost.GetDataPortal().PortalFailAsync(true); + + (await act.Should().ThrowAsync()).Which + .GetBaseException().Should().BeOfType(); + EdgeCaseRoot.FailCalls.Should().Be(1); + } + + [TestMethod] + public async Task FetchAsync_ObjectArrayForSingleObjectCriteria_IsNotSplit() + { + var obj = await _testHost.GetDataPortal().PortalGetByValueAsync(new object[] { 1, "two" }); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be("array:2"); + } + + [TestMethod] + public async Task FetchAsync_NullableInjectedService_IsOptional() + { + var obj = await _testHost.GetDataPortal().PortalGetOptionalAsync(5); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be("5:"); + } + + private static List ExceptionTypes(Exception exception) + { + var result = new List(); + for (var current = exception; current is not null; current = current.InnerException) + result.Add(current.GetType()); + return result; + } + + [TestMethod] + public async Task FetchAsync_NonRunLocal_UsesConfiguredProxy() + { + _ = await _testHost.GetDataPortal().PortalGetByIdAsync(1); + + CountingRemoteProxy.Calls.Should().Be(1); + } + + [TestMethod] + public async Task FetchAsync_RunLocal_BypassesConfiguredProxy() + { + var obj = await _testHost.GetDataPortal().PortalGetLocalAsync("local".ToCharArray()); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Name.Should().Be("local"); + CountingRemoteProxy.Calls.Should().Be(0); + } + + [TestMethod] + public async Task DeleteAsync_UsesNamedDispatch() + { + await _testHost.GetDataPortal().PortalRemoveAsync(11); + + ExtensionRoot.Deleted.TryRemove(11, out var dispatch).Should().BeTrue(); + dispatch.Should().Be(DispatchPath.Named); + } + + [TestMethod] + public void Delete_Sync_UsesNamedDispatch() + { + _testHost.GetDataPortal().PortalRemove(12); + + ExtensionRoot.Deleted.TryRemove(12, out var dispatch).Should().BeTrue(); + dispatch.Should().Be(DispatchPath.Named); + } + + [TestMethod] + public async Task ExecuteAsync_UsesNamedDispatch() + { + var cmd = await _testHost.GetDataPortal().PortalRunAsync(3); + + cmd.Dispatch.Should().Be(DispatchPath.Named); + cmd.Result.Should().Be(6); + } + + [TestMethod] + public void Execute_Sync_UsesNamedDispatch() + { + var cmd = _testHost.GetDataPortal().PortalRun(4); + + cmd.Dispatch.Should().Be(DispatchPath.Named); + cmd.Result.Should().Be(8); + } + + [TestMethod] + public async Task CreateChildAsync_UsesNamedDispatch() + { + var child = await _testHost.GetChildDataPortal().PortalNewChildAsync("kid"); + + child.Dispatch.Should().Be(DispatchPath.Named); + child.Name.Should().Be("kid"); + child.IsChild.Should().BeTrue(); + child.IsNew.Should().BeTrue(); + } + + [TestMethod] + public void CreateChild_Sync_UsesNamedDispatch() + { + var child = _testHost.GetChildDataPortal().PortalNewChild("kid"); + + child.Dispatch.Should().Be(DispatchPath.Named); + } + + [TestMethod] + public async Task FetchChildAsync_UsesNamedDispatch() + { + var child = await _testHost.GetChildDataPortal().PortalLoadChildAsync(9, ["x", "y"]); + + child.Dispatch.Should().Be(DispatchPath.Named); + child.Name.Should().Be("9:x,y"); + child.IsNew.Should().BeFalse(); + } + + [TestMethod] + public void FetchChild_Sync_UsesNamedDispatch() + { + var child = _testHost.GetChildDataPortal().PortalLoadChild(10, ["z"]); + + child.Dispatch.Should().Be(DispatchPath.Named); + child.Name.Should().Be("10:z"); + } + + [TestMethod] + public async Task ExistingParamsApi_StillUsesGeneratedDispatch() + { + var obj = await _testHost.GetDataPortal().FetchAsync(3); + + obj.Dispatch.Should().Be(DispatchPath.Named); + obj.Id.Should().Be(3); + } + + [TestMethod] + public async Task ChildParamsApi_UsesTypeBasedDispatch() + { + var child = await _testHost.GetChildDataPortal().CreateChildAsync("kid"); + + child.Dispatch.Should().Be(DispatchPath.Type); + } + + [TestMethod] + public async Task CustomDataPortal_UsesFallback() + { + var fake = new FakeDataPortal(); + + var obj = await fake.PortalGetByIdAsync(99); + + obj.Should().BeSameAs(fake.Result); + fake.FetchCriteria.Should().Equal(99); + } + + [TestMethod] + public async Task CustomDataPortal_UsesFallbackWithoutSplittingArrays() + { + var fake = new FakeDataPortal(); + string[] names = ["a", "b"]; + + _ = await fake.PortalGetByNamesAsync(names); + + fake.FetchCriteria.Should().HaveCount(1); + fake.FetchCriteria![0].Should().BeSameAs(names); + } + + #endregion + + #region Argument validation + + [TestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow(" ")] + public async Task RootInvoker_InvalidOperationName_Throws(string? operationName) + { + var invoker = (IDataPortalOperationInvoker)_testHost.GetDataPortal(); + + await FluentActions.Awaiting(() => invoker.FetchByOperationAsync(operationName!, false, [1])) + .Should().ThrowAsync().WithParameterName("operationName"); + FluentActions.Invoking(() => invoker.CreateByOperation(operationName!, false, [])) + .Should().Throw().WithParameterName("operationName"); + await FluentActions.Awaiting(() => invoker.DeleteByOperationAsync(operationName!, false, [1])) + .Should().ThrowAsync().WithParameterName("operationName"); + } + + [TestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow(" ")] + public async Task ChildInvoker_InvalidOperationName_Throws(string? operationName) + { + var invoker = (IChildDataPortalOperationInvoker)_testHost.GetChildDataPortal(); + + await FluentActions.Awaiting(() => invoker.CreateChildByOperationAsync(operationName!, ["kid"])) + .Should().ThrowAsync().WithParameterName("operationName"); + FluentActions.Invoking(() => invoker.FetchChildByOperation(operationName!, [1, new[] { "x" }])) + .Should().Throw().WithParameterName("operationName"); + } + + [TestMethod] + public void OperationHelper_NullArguments_Throw() + { + var services = _testHost.Services; + var target = new object(); + + FluentActions.Invoking(() => DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(null!, true, services, "Fetch")) + .Should().Throw().WithParameterName("target"); + FluentActions.Invoking(() => DataPortalOperationHelper.ThrowIfAsyncMethodOnSyncClient(target, true, null!, "Fetch")) + .Should().Throw().WithParameterName("serviceProvider"); + } + + #endregion + + #region Name consistency + + [TestMethod] + public void GeneratedInterfaceMemberNames_MatchRuntimeOperationNames() + { + var checkedMembers = 0; + var types = typeof(DataPortalExtensionsTests).Assembly.GetTypes() + .Where(t => !t.ContainsGenericParameters) + .Select(t => (Type: t, Operations: t.GetNestedType("IDataPortalOperations", BindingFlags.NonPublic | BindingFlags.Public))) + .Where(t => t.Operations is not null) + .ToList(); + + types.Should().NotBeEmpty(); + + foreach (var (type, operations) in types) + { + foreach (var member in operations!.GetMethods()) + { + var kind = member.Name.Split('_')[0]; + var attributeType = typeof(FetchAttribute).Assembly.GetType($"Csla.{kind}Attribute", throwOnError: true)!; + var memberParameters = member.GetParameters().Select(p => p.ParameterType).ToArray(); + + var method = type + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) + .Single(m => m.GetCustomAttributes(attributeType, false).Any() + && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(memberParameters)); + + var expected = (string)typeof(DataPortalOperationNameHelper) + .GetMethod(nameof(DataPortalOperationNameHelper.ComputeOperationName), BindingFlags.Static | BindingFlags.NonPublic)! + .MakeGenericMethod(attributeType) + .Invoke(null, [method])!; + + member.Name.Should().Match(name => name == expected || name.StartsWith(expected + "_"), + $"{type.Name}.{method.Name} should map to operation {expected}"); + checkedMembers++; + } + } + + checkedMembers.Should().BeGreaterThan(10); + } + + #endregion + + #region Test types + + public static class DispatchPath + { + public const string Named = "Named"; + public const string Type = "Type"; + public const string Reflection = "Reflection"; + + public static string Current() + { + var stackTrace = Environment.StackTrace; + if (stackTrace.Contains(nameof(IDataPortalOperationNamedMapping))) + return Named; + if (stackTrace.Contains(nameof(IDataPortalOperationMapping))) + return Type; + return Reflection; + } + } + + public class ExtensionTestDal + { + public const string Marker = "from-dal"; + } + + public class CountingRemoteProxy(LocalProxy implementingProxy) : IDataPortalProxy + { + private static int _calls; + + public static int Calls => _calls; + + public static void Reset() => _calls = 0; + + public bool IsServerRemote => true; + + public Task Create(Type objectType, object criteria, DataPortalContext context, bool isSync) + { + Interlocked.Increment(ref _calls); + return implementingProxy.Create(objectType, criteria, context, isSync); + } + + public Task Delete(Type objectType, object criteria, DataPortalContext context, bool isSync) + { + Interlocked.Increment(ref _calls); + return implementingProxy.Delete(objectType, criteria, context, isSync); + } + + public Task Fetch(Type objectType, object criteria, DataPortalContext context, bool isSync) + { + Interlocked.Increment(ref _calls); + return implementingProxy.Fetch(objectType, criteria, context, isSync); + } + + public Task Update(ICslaObject obj, DataPortalContext context, bool isSync) + { + Interlocked.Increment(ref _calls); + return implementingProxy.Update(obj, context, isSync); + } + } + + [DataPortalExtensions(Prefix = "Portal")] + public partial class ExtensionRoot : BusinessBase + { + public static readonly ConcurrentDictionary Deleted = new(); + + public static readonly PropertyInfo DispatchProperty = RegisterProperty(nameof(Dispatch)); + public string Dispatch + { + get => GetProperty(DispatchProperty); + private set => LoadProperty(DispatchProperty, value); + } + + public static readonly PropertyInfo IdProperty = RegisterProperty(nameof(Id)); + public int Id + { + get => GetProperty(IdProperty); + private set => LoadProperty(IdProperty, value); + } + + public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); + public string Name + { + get => GetProperty(NameProperty); + private set => LoadProperty(NameProperty, value); + } + + [Create] + private void New() + { + Dispatch = DispatchPath.Current(); + Name = "new"; + } + + [Create] + private void NewNamed(string name) + { + Dispatch = DispatchPath.Current(); + Name = name; + } + + [Fetch] + private void GetById(int id, [Inject] ExtensionTestDal dal) + { + _ = dal; + Dispatch = DispatchPath.Current(); + Id = id; + Name = ExtensionTestDal.Marker; + } + + [Fetch] + private void GetByNames(string[] names) + { + Dispatch = DispatchPath.Current(); + Name = string.Join(",", names); + } + + [Fetch] + private void GetByOptional(int? number, string? text) + { + Dispatch = DispatchPath.Current(); + Name = $"{number?.ToString() ?? ""}|{text ?? ""}"; + } + + [Fetch] + private async Task LoadAsync(Guid key) + { + Dispatch = DispatchPath.Current(); + await Task.Yield(); + Name = key.ToString(); + } + + [RunLocal] + [Fetch] + private void GetLocal(char[] code) + { + Dispatch = DispatchPath.Current(); + Name = new string(code); + } + + [Delete] + private void Remove(int id) + { + Deleted[id] = DispatchPath.Current(); + } + + [Fetch] + private void GetByKey((int Id, string Name) key, List values, Dictionary[] map) + { + _ = key; + _ = values; + _ = map; + } + } + + /// + /// Not partial, so dispatch uses reflection. + /// + public class ReflectionRoot : BusinessBase + { + [Fetch] + private async Task LoadAsync(Guid key) + { + _ = key; + await Task.Yield(); + } + + [Fetch] + private void Fail(bool notSupported) + { + _ = notSupported; + throw new InvalidOperationException("fail"); + } + + [Fetch] + private void GetRequired(string code, [Inject] IUnregisteredService service) + { + _ = code; + _ = service; + } + } + + public interface IUnregisteredService { } + +#nullable enable + [DataPortalExtensions(Prefix = "Portal")] + public partial class EdgeCaseRoot : BusinessBase + { + public static int FailCalls; + + public static readonly PropertyInfo DispatchProperty = RegisterProperty(nameof(Dispatch)); + public string Dispatch + { + get => GetProperty(DispatchProperty); + private set => LoadProperty(DispatchProperty, value); + } + + public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); + public string Name + { + get => GetProperty(NameProperty); + private set => LoadProperty(NameProperty, value); + } + + [Fetch] + private void Fail(bool notSupported) + { + Interlocked.Increment(ref FailCalls); + if (notSupported) + throw new DataPortalOperationNotSupportedException("Fail", null); + throw new InvalidOperationException("fail"); + } + + [Fetch] + private void GetByValue(object value) + { + Dispatch = DispatchPath.Current(); + Name = value is object[] array ? $"array:{array.Length}" : "single"; + } + + [Fetch] + private void GetRequired(string code, [Inject] IUnregisteredService service) + { + _ = code; + _ = service; + } + + [Fetch] + private void GetOptional(int id, [Inject] IUnregisteredService? service) + { + Dispatch = DispatchPath.Current(); + Name = $"{id}:{(service is null ? "" : "service")}"; + } + } +#nullable restore + + [DataPortalExtensions(Prefix = "Portal")] + public partial class ExtensionCommand : CommandBase + { + public static readonly PropertyInfo DispatchProperty = RegisterProperty(nameof(Dispatch)); + public string Dispatch + { + get => ReadProperty(DispatchProperty); + private set => LoadProperty(DispatchProperty, value); + } + + public static readonly PropertyInfo ResultProperty = RegisterProperty(nameof(Result)); + public int Result + { + get => ReadProperty(ResultProperty); + private set => LoadProperty(ResultProperty, value); + } + + [Execute] + private void Run(int value) + { + Dispatch = DispatchPath.Current(); + Result = value * 2; + } + } + + [DataPortalExtensions(Prefix = "Portal")] + public partial class ExtensionChild : BusinessBase + { + public static readonly PropertyInfo DispatchProperty = RegisterProperty(nameof(Dispatch)); + public string Dispatch + { + get => GetProperty(DispatchProperty); + private set => LoadProperty(DispatchProperty, value); + } + + public static readonly PropertyInfo NameProperty = RegisterProperty(nameof(Name)); + public string Name + { + get => GetProperty(NameProperty); + private set => LoadProperty(NameProperty, value); + } + + [CreateChild] + private void NewChild(string name) + { + Dispatch = DispatchPath.Current(); + Name = name; + } + + [FetchChild] + private void LoadChild(int id, string[] tags) + { + Dispatch = DispatchPath.Current(); + Name = $"{id}:{string.Join(",", tags)}"; + } + } + + private sealed class FakeDataPortal : IDataPortal + { + public ExtensionRoot Result { get; } = new(); + + public object?[]? FetchCriteria { get; private set; } + + public Task FetchAsync(params object?[]? criteria) + { + FetchCriteria = criteria; + return Task.FromResult(Result); + } + + public Task CreateAsync(params object?[]? criteria) => throw new NotSupportedException(); + public Task UpdateAsync(ExtensionRoot obj) => throw new NotSupportedException(); + public Task ExecuteAsync(ExtensionRoot command) => throw new NotSupportedException(); + public Task ExecuteAsync(params object?[]? criteria) => throw new NotSupportedException(); + public Task DeleteAsync(params object?[]? criteria) => throw new NotSupportedException(); + public ExtensionRoot Create(params object?[]? criteria) => throw new NotSupportedException(); + public ExtensionRoot Fetch(params object?[]? criteria) => throw new NotSupportedException(); + public ExtensionRoot Execute(ExtensionRoot obj) => throw new NotSupportedException(); + public ExtensionRoot Execute(params object?[]? criteria) => throw new NotSupportedException(); + public ExtensionRoot Update(ExtensionRoot obj) => throw new NotSupportedException(); + public void Delete(params object?[]? criteria) => throw new NotSupportedException(); + } + + #endregion + } +} diff --git a/Source/tests/csla.netcore.test/csla.netcore.test.csproj b/Source/tests/csla.netcore.test/csla.netcore.test.csproj index 7fb6952145..eb0e6d96c0 100644 --- a/Source/tests/csla.netcore.test/csla.netcore.test.csproj +++ b/Source/tests/csla.netcore.test/csla.netcore.test.csproj @@ -27,6 +27,7 @@ + diff --git a/docs/analyzers/CSLA0024-TypeWithOperationsShouldBePartialAnalyzer.md b/docs/analyzers/CSLA0024-TypeWithOperationsShouldBePartialAnalyzer.md new file mode 100644 index 0000000000..2a0d28a121 --- /dev/null +++ b/docs/analyzers/CSLA0024-TypeWithOperationsShouldBePartialAnalyzer.md @@ -0,0 +1,52 @@ +# Types with data portal operation methods should be partial. + +## Issue + +This analyzer is tripped if a class declares one or more data portal operation methods (methods marked with `[Create]`, `[Fetch]`, `[Insert]`, `[Update]`, `[Execute]`, `[Delete]`, `[DeleteSelf]`, `[CreateChild]`, `[FetchChild]`, `[InsertChild]`, `[UpdateChild]`, `[DeleteSelfChild]`, or `[ExecuteChild]`), and either the class itself or any of its containing types is not declared `partial`. For example: + +``` +using Csla; +using System; + +[Serializable] +public class Customer + : BusinessBase +{ + [Fetch] + private void Fetch(int id) { } +} +``` + +The CSLA source generator adds a `partial` declaration to each class that has data portal operation methods. That declaration implements an internal operations interface so the data portal can invoke the operation methods directly, without reflection. The generator can only do this when the class, and every type that contains it, is `partial`: + +``` +using Csla; +using System; + +[Serializable] +public partial class Customer + : BusinessBase +{ + [Fetch] + private void Fetch(int id) { } +} +``` + +This applies to abstract and generic classes as well. For nested classes, every containing type must also be `partial`: + +``` +public partial class Outer +{ + [Serializable] + public partial class Customer + : BusinessBase + { + [Fetch] + private void Fetch(int id) { } + } +} +``` + +## Code Fix + +A code fix is available for this analyzer. It will add the `partial` modifier to the class and to all of its containing types that are not already `partial`. diff --git a/docs/analyzers/CSLA0025-UseGeneratedDataPortalExtensionAnalyzer.md b/docs/analyzers/CSLA0025-UseGeneratedDataPortalExtensionAnalyzer.md new file mode 100644 index 0000000000..7d5ae208b0 --- /dev/null +++ b/docs/analyzers/CSLA0025-UseGeneratedDataPortalExtensionAnalyzer.md @@ -0,0 +1,51 @@ +# Use the generated data portal extension method. + +## Issue + +When a business class, or its assembly, is marked with `[DataPortalExtensions]`, the CSLA source generator creates strongly typed extension methods on `IDataPortal` and `IChildDataPortal` for each of the class's data portal operation methods. This analyzer is tripped when code calls the untyped, criteria-based data portal methods for such a type instead of the generated extension methods. For example: + +``` +using Csla; +using System; + +[DataPortalExtensions] +[Serializable] +public partial class Customer + : BusinessBase +{ + [Fetch] + private void GetById(int id) { } +} + +public class CustomerService(IDataPortal portal) +{ + public Task GetCustomerAsync(int id) => portal.FetchAsync(id); +} +``` + +The untyped methods accept `params object[]` criteria, so mistakes in the number, order, or types of the arguments are only discovered at runtime. The generated extension methods have parameters that match the operation method, so the compiler verifies each call. + +The following methods are reported when the business type `T`, or its assembly, has the `[DataPortalExtensions]` attribute: + +* On `IDataPortal` and `DataPortal`: `Create`, `CreateAsync`, `Fetch`, `FetchAsync`, `Delete`, `DeleteAsync`, and the criteria overloads of `Execute` and `ExecuteAsync` +* On `IChildDataPortal` and `DataPortal`: `CreateChild`, `CreateChildAsync`, `FetchChild`, and `FetchChildAsync` + +A call is only reported when the generator produces an extension method that can replace it: the business type has an operation method of the matching kind (for example `[Fetch]` for `FetchAsync`) that accepts the number of criteria values passed, and an extension method is generated for that operation method. No diagnostic is reported when no such extension method exists, for example when the operation method has `[NoDataPortalExtension]`, has `ref` or `out` parameters, has a parameter type that is private to the business type, or has a name that the data portal interface's own methods would hide. No diagnostic is reported for a class marked `[NoDataPortalExtension]`. Calls to the synchronous methods are not reported when the `CslaGenerateSyncDataPortalExtensions` MSBuild property is `false`, or the `CslaDataPortalExtensionsAsyncSuffix` property is `none`, because only async extension methods are generated then. + +`Update`, `UpdateAsync`, `UpdateChild`, `UpdateChildAsync`, and `Execute`/`ExecuteAsync` called with a command object are not reported, because they do not take untyped criteria. + +Generated code is not analyzed, because the generated extension methods themselves call the untyped data portal methods. + +## Code Fix + +The code fix replaces the call with the generated extension method, keeping the arguments, and adds a `using` directive for the business class's namespace when needed. When several generated extension methods accept the arguments, a fix is offered for each of them: + +``` +public Task GetCustomerAsync(int id) => portal.FetchAsync(id); +``` + +becomes + +``` +public Task GetCustomerAsync(int id) => portal.GetByIdAsync(id); +``` diff --git a/docs/analyzers/CSLADP002-DuplicateOperationName.md b/docs/analyzers/CSLADP002-DuplicateOperationName.md new file mode 100644 index 0000000000..b47de2b25a --- /dev/null +++ b/docs/analyzers/CSLADP002-DuplicateOperationName.md @@ -0,0 +1,37 @@ +# Data portal operation methods share an operation name + +## Issue + +This diagnostic is reported (as information) when two data portal operation methods on a `partial` class map to the same data portal operation. That happens when the methods have the same operation attribute and the same criteria parameter types, and differ only in their `[Inject]` parameters. For example: + +``` +using Csla; +using System; + +[Serializable] +public partial class Customer + : BusinessBase +{ + [Fetch] + private void Fetch(int id) { } + + [Fetch] + private void Fetch(int id, [Inject] ICustomerDal dal) { } +} +``` + +A call such as `portal.FetchAsync(42)` matches both methods. The data portal picks the method with more injected parameters, which is `Fetch(int, ICustomerDal)` here. This is the same choice that reflection-based dispatch makes, and the code generated for the class makes it too. The diagnostic message names the method that is used. + +The data portal extension methods generator also generates extension methods only for the method that is used, because both methods would produce the same extension method. + +## How to fix + +If the selected method is the one you intend to be called, no change is needed. Otherwise, remove the method that is not used, or change the criteria parameters so that each method maps to a different operation. + +If two methods have the same number of injected parameters, the data portal cannot choose between them, and [CSLADP009](CSLADP009-AmbiguousOperationName.md) is reported instead. + +You can suppress this diagnostic in source or in an `.editorconfig` file: + +``` +dotnet_diagnostic.CSLADP002.severity = none +``` diff --git a/docs/analyzers/CSLADP003-GenericExtensionsNotGenerated.md b/docs/analyzers/CSLADP003-GenericExtensionsNotGenerated.md new file mode 100644 index 0000000000..e3d6a44575 --- /dev/null +++ b/docs/analyzers/CSLADP003-GenericExtensionsNotGenerated.md @@ -0,0 +1,33 @@ +# Data portal extensions are not generated for generic types + +## Issue + +This diagnostic is reported (as information) when a class marked with `[DataPortalExtensions]` is generic, or is nested in a generic type. For example: + +``` +using Csla; +using System; + +[Serializable] +[DataPortalExtensions] +public partial class LookupList + : ReadOnlyListBase, T> +{ + [Fetch] + private void Fetch(int id) { } +} +``` + +Extension methods on `IDataPortal>` would need their own type parameters and constraints, so the data portal extension methods generator does not generate them for generic types. + +The data portal still calls the operation methods of a generic `partial` class directly. Only the extension methods are not generated. + +## How to fix + +Call the `IDataPortal` methods directly, for example `portal.FetchAsync(42)`, or remove `[DataPortalExtensions]` from the class. + +You can suppress this diagnostic in source or in an `.editorconfig` file: + +``` +dotnet_diagnostic.CSLADP003.severity = none +``` diff --git a/docs/analyzers/CSLADP004-InvalidExtensionsTarget.md b/docs/analyzers/CSLADP004-InvalidExtensionsTarget.md new file mode 100644 index 0000000000..8aece967a3 --- /dev/null +++ b/docs/analyzers/CSLADP004-InvalidExtensionsTarget.md @@ -0,0 +1,33 @@ +# Invalid target for DataPortalExtensions + +## Issue + +This diagnostic is reported when `[DataPortalExtensions]` is applied to a class that cannot have data portal extension methods. The message gives the reason, which is one of: + +* The class does not implement `Csla.Core.ICslaObject`, so it is not a CSLA business type. +* The class is abstract, so the data portal cannot create it. +* The class is not accessible outside its containing type (for example, a `private` nested class), so extension methods in the containing namespace cannot refer to it. + +For example: + +``` +using Csla; +using System; + +[Serializable] +[DataPortalExtensions] +public abstract partial class CustomerBase + : BusinessBase +{ + [Fetch] + private void Fetch(int id) { } +} +``` + +No extension methods are generated for the class. + +When the assembly is marked with `[assembly: DataPortalExtensions]`, classes that cannot have extension methods are skipped without a diagnostic. + +## How to fix + +Remove `[DataPortalExtensions]` from the class, or apply it to the concrete business class instead. diff --git a/docs/analyzers/CSLADP005-ExtensionNameHidden.md b/docs/analyzers/CSLADP005-ExtensionNameHidden.md new file mode 100644 index 0000000000..e61e0a6888 --- /dev/null +++ b/docs/analyzers/CSLADP005-ExtensionNameHidden.md @@ -0,0 +1,35 @@ +# Generated data portal extension would be hidden + +## Issue + +This diagnostic is reported when the name of a generated data portal extension method is the same as the name of an instance method on `IDataPortal` or `IChildDataPortal`. For example: + +``` +using Csla; +using System; + +[Serializable] +[DataPortalExtensions] +public partial class Customer + : BusinessBase +{ + [Fetch] + private void Fetch(int id) { } +} +``` + +The generated method names come from the operation method name, so this class would get `FetchAsync(int id)` and `Fetch(int id)` extension methods. `IDataPortal` already has `FetchAsync(params object?[]? criteria)` and `Fetch(params object?[]? criteria)` instance methods. C# always prefers an applicable instance method over an extension method, so a call such as `portal.FetchAsync(42)` would call the untyped instance method, and the extension method could never be called that way. + +The extension method is not generated. + +## How to fix + +Set `Prefix` on the attribute to give the generated methods distinct names: + +``` +[DataPortalExtensions(Prefix = "Portal")] +public partial class Customer + : BusinessBase +``` + +The class now gets `PortalFetchAsync(int id)` and `PortalFetch(int id)`. Alternatively, give the operation method a more specific name, such as `FetchById`, which produces `FetchByIdAsync(int id)`. diff --git a/docs/analyzers/CSLADP006-InaccessibleParameterType.md b/docs/analyzers/CSLADP006-InaccessibleParameterType.md new file mode 100644 index 0000000000..2232e3d212 --- /dev/null +++ b/docs/analyzers/CSLADP006-InaccessibleParameterType.md @@ -0,0 +1,31 @@ +# Operation parameter type is not accessible + +## Issue + +This diagnostic is reported when a criteria parameter of a data portal operation method has a type that is not accessible outside its containing type, such as a `private` nested class. For example: + +``` +using Csla; +using System; + +[Serializable] +[DataPortalExtensions] +public partial class Customer + : BusinessBase +{ + [Serializable] + private class Criteria + { + public int Id { get; set; } + } + + [Fetch] + private void Fetch(Criteria criteria) { } +} +``` + +Data portal extension methods are generated in a separate static class, so their parameters cannot use a type that only `Customer` can see. The extension methods for this operation method are not generated. Extension methods for the other operation methods of the class are still generated. + +## How to fix + +Make the criteria type `internal` or `public`, or pass the criteria values as separate parameters. diff --git a/docs/analyzers/CSLADP007-DuplicateExtensionMethod.md b/docs/analyzers/CSLADP007-DuplicateExtensionMethod.md new file mode 100644 index 0000000000..8bdf0ea15e --- /dev/null +++ b/docs/analyzers/CSLADP007-DuplicateExtensionMethod.md @@ -0,0 +1,28 @@ +# Generated data portal extension would be duplicated + +## Issue + +This diagnostic is reported when two operation methods of a class would produce data portal extension methods with the same name and parameter types. For example: + +``` +using Csla; +using System; + +[Serializable] +[DataPortalExtensions] +public partial class Customer + : BusinessBase +{ + [Create] + private void Load(int id) { } + + [Fetch] + private void Load(int id, [Inject] ICustomerDal dal) { } +} +``` + +Both methods would produce `LoadAsync(int id)` and `Load(int id)` extension methods on `IDataPortal`, because injected parameters are not part of the extension method. The extension methods are generated for only one of the methods; the diagnostic names the method that does not get them. + +## How to fix + +Give the operation methods distinct names, such as `Create` and `Fetch`, so that each produces its own extension methods. Alternatively, mark the method that does not need an extension method with `[NoDataPortalExtension]`. diff --git a/docs/analyzers/CSLADP008-InvalidExtensionPrefix.md b/docs/analyzers/CSLADP008-InvalidExtensionPrefix.md new file mode 100644 index 0000000000..7957bc87a7 --- /dev/null +++ b/docs/analyzers/CSLADP008-InvalidExtensionPrefix.md @@ -0,0 +1,25 @@ +# Invalid DataPortalExtensions prefix + +## Issue + +This diagnostic is reported when the `Prefix` of a `[DataPortalExtensions]` attribute would not produce valid C# method names. For example: + +``` +using Csla; +using System; + +[Serializable] +[DataPortalExtensions(Prefix = "My-")] +public partial class Customer + : BusinessBase +{ + [Fetch] + private void Fetch(int id) { } +} +``` + +No extension methods are generated for the class. When the prefix is set on an assembly attribute, `[assembly: DataPortalExtensions(Prefix = "...")]`, the diagnostic is reported once, on the assembly attribute, and no extension methods are generated for classes that use the assembly prefix. + +## How to fix + +Use a prefix that is a valid C# identifier, such as `Portal` or `My`. diff --git a/docs/analyzers/CSLADP009-AmbiguousOperationName.md b/docs/analyzers/CSLADP009-AmbiguousOperationName.md new file mode 100644 index 0000000000..d11c915453 --- /dev/null +++ b/docs/analyzers/CSLADP009-AmbiguousOperationName.md @@ -0,0 +1,31 @@ +# Data portal operation methods are ambiguous + +## Issue + +This diagnostic is reported when two data portal operation methods on a `partial` class map to the same data portal operation and have the same number of `[Inject]` parameters. For example: + +``` +using Csla; +using System; + +[Serializable] +public partial class Customer + : BusinessBase +{ + [Fetch] + private void Fetch(int id, [Inject] ICustomerDal dal) { } + + [Fetch] + private void Fetch(int id, [Inject] IAuditLog log) { } +} +``` + +A call such as `portal.FetchAsync(42)` matches both methods, and the data portal has no way to choose between them. At run time the data portal throws an `AmbiguousMatchException`. + +The code generated for the class does not dispatch these methods directly, so the data portal falls back to reflection-based dispatch and throws the same exception. Calling the operation through a generated data portal extension method fails the same way. + +## How to fix + +Remove one of the methods, or change the criteria parameters so that each method maps to a different operation. If the methods need different services, combine them into one method that injects every service it needs. + +When the methods have different numbers of injected parameters, the data portal uses the method with more injected parameters, and [CSLADP002](CSLADP002-DuplicateOperationName.md) is reported instead. diff --git a/docs/analyzers/CSLADP010-InvalidAsyncSuffix.md b/docs/analyzers/CSLADP010-InvalidAsyncSuffix.md new file mode 100644 index 0000000000..4679d9755a --- /dev/null +++ b/docs/analyzers/CSLADP010-InvalidAsyncSuffix.md @@ -0,0 +1,25 @@ +# Invalid data portal extensions async suffix + +## Issue + +This diagnostic is reported when the `CslaDataPortalExtensionsAsyncSuffix` MSBuild property is set to a value that cannot be used in a C# method name. For example: + +```xml + + -Async + +``` + +The value is ignored, and the generated async data portal extension methods use the default `Async` suffix. The diagnostic is reported only when the project generates data portal extension methods. + +## How to fix + +Set the property to a valid identifier, such as `Async` or `Task`, or to `none` for no suffix: + +```xml + + none + +``` + +With no suffix, the synchronous extension methods are not generated; see [CSLADP011](CSLADP011-SyncExtensionsNotGenerated.md). diff --git a/docs/analyzers/CSLADP011-SyncExtensionsNotGenerated.md b/docs/analyzers/CSLADP011-SyncExtensionsNotGenerated.md new file mode 100644 index 0000000000..d77255b93b --- /dev/null +++ b/docs/analyzers/CSLADP011-SyncExtensionsNotGenerated.md @@ -0,0 +1,26 @@ +# Synchronous data portal extensions are not generated without an async suffix + +## Issue + +This diagnostic is reported when `CslaDataPortalExtensionsAsyncSuffix` is `none` and `CslaGenerateSyncDataPortalExtensions` is not set to `false`: + +```xml + + none + +``` + +Without an async suffix, the async method for a `Fetch` operation method is named `Fetch`. The synchronous method would have the same name and the same parameters, so it cannot be generated. Only the async extension methods are generated. The diagnostic is reported only when the project generates data portal extension methods. + +## How to fix + +If you only want async extension methods, which is the usual reason for removing the suffix, turn off the synchronous methods: + +```xml + + none + false + +``` + +If you need the synchronous methods, use an async suffix, such as the default `Async`. diff --git a/docs/analyzers/index.md b/docs/analyzers/index.md index f7f26bc995..fd95d0e858 100644 --- a/docs/analyzers/index.md +++ b/docs/analyzers/index.md @@ -21,4 +21,43 @@ While we try to make the analyzers as stable as we can, we may run into coding p Unfortunately, Visual Studio doesn't make it easy to get crash information on an analyzer. The best you can do is launch `devenv.exe` with the `/log` switch, and see if the log file contains any meaningful information. Also, please log an issue on the [CSLA site]((https://github.com/MarimerLLC/csla/issues)) if you do find problems with an analyzer, and we'll do our best to resolve the problem. ## Analyzer Proposals -If you have an idea for an analyzer that would be beneficial for developers using CSLA, please propose it in the [forums](https://github.com/MarimerLLC/cslaforum/issues), and tag it with "feature discussion". \ No newline at end of file +If you have an idea for an analyzer that would be beneficial for developers using CSLA, please propose it in the [forums](https://github.com/MarimerLLC/cslaforum/issues), and tag it with "feature discussion". +## Analyzer Reference +* [CSLA0001](CSLA0001-IsBusinessObjectSerializableAnalyzer.md) - CSLA business objects must be serializable +* [CSLA0002](CSLA0002-IsOperationMethodPublicAnalyzer.md) - CSLA operations should not be public +* [CSLA0003](CSLA0003-CheckConstructorsAnalyzer.md) - CSLA business objects must have a public constructor with no arguments +* [CSLA0004](CSLA0004-CheckConstructorsAnalyzer.md) - CSLA business objects should not have public constructors with parameters +* [CSLA0005](CSLA0005-FindSaveAssignmentIssueAnalyzer.md) - Do not ignore the result of `Save()` +* [CSLA0006](CSLA0006-FindSaveAssignmentIssueAnalyzer.md) - Do not ignore the result of `SaveAsync()` +* [CSLA0007](CSLA0007-EvaluatePropertiesForSimplicityAnalyzer.md) - Properties that use managed backing fields should only use Get/Set/Read/Load methods +* [CSLA0008](CSLA0008-EvaluateManagedBackingFieldsAnalayzer.md) - Managed backing fields must be public, static and read-only +* [CSLA0009](CSLA0009-IsOperationMethodPublicAnalyzer.md) - CSLA operations should not be public on an interface +* [CSLA0010](CSLA0010-FindOperationsWithNonSerializableArgumentsAnalyzer.md) - Operation argument types should be serializable +* [CSLA0011](CSLA0011-FindBusinessObjectCreationAnalyzer.md) - CSLA business objects should not be created outside of an `ObjectFactory` instance +* [CSLA0012](CSLA0012-FindOperationsWithIncorrectReturnTypesAnalyzer.md) - The return type from an operation should be either `void` or `Task` +* [CSLA0013](CSLA0013-DoesChildOperationHaveRunLocalAnalyzer.md) - Child operations should not have `[RunLocal]` +* [CSLA0014](CSLA0014-DoesOperationHaveAttributeAnalyzer.md) - Operations should have the appropriate operation attribute +* [CSLA0015](CSLA0015-EvaluateOperationAttributeUsageAnalyzer.md) - Operation attributes should be used correctly +* [CSLA0016](CSLA0016-AsynchronousBusinessRuleInheritingFromBusinessRuleAnalyzer.md) - Asynchronous business rules should derive from `BusinessRuleAsync` +* [CSLA0017](CSLA0017-BusinessRuleDoesNotUseAddMethodsOnContextAnalyzer.md) - Business rules should use at least one `Add()` method on the context +* [CSLA0018](CSLA0018-IsCompleteCalledInAsynchronousBusinessRuleAnalyzer.md) - `Complete()` should not be called in an asynchronous business rule +* [CSLA0019](CSLA0019-FindRefAndOutParametersInOperationsAnalyzer.md) - Operations should not have `ref` or `out` parameters +* [CSLA0020](CSLA0020-ObjectAuthorizationRulesAttributeMissing.md) - Object authorization rules configuration should be marked with attribute +* [CSLA0021](CSLA0021-ObjectAuthorizationRulesPublic.md) - Object authorization rules configuration should be declared as public +* [CSLA0022](CSLA0022-ObjectAuthorizationRulesStatic.md) - Object authorization rules configuration should be declared as static +* [CSLA0024](CSLA0024-TypeWithOperationsShouldBePartialAnalyzer.md) - Types with data portal operation methods should be partial +* [CSLA0025](CSLA0025-UseGeneratedDataPortalExtensionAnalyzer.md) - Use the generated data portal extension method + +## Data Portal Generator Diagnostics +These diagnostics come from the data portal operations and data portal extensions source generators included in the `Csla` package. They report operation methods that the generated code handles differently from what you might expect, and data portal extension methods that are not generated. They are reported by analyzers, so you can suppress them in source or in an `.editorconfig` file. + +* [CSLADP002](CSLADP002-DuplicateOperationName.md) - Data portal operation methods share an operation name +* [CSLADP003](CSLADP003-GenericExtensionsNotGenerated.md) - Data portal extensions are not generated for generic types +* [CSLADP004](CSLADP004-InvalidExtensionsTarget.md) - Invalid target for `[DataPortalExtensions]` +* [CSLADP005](CSLADP005-ExtensionNameHidden.md) - Generated data portal extension would be hidden +* [CSLADP006](CSLADP006-InaccessibleParameterType.md) - Operation parameter type is not accessible +* [CSLADP007](CSLADP007-DuplicateExtensionMethod.md) - Generated data portal extension would be duplicated +* [CSLADP008](CSLADP008-InvalidExtensionPrefix.md) - Invalid `[DataPortalExtensions]` prefix +* [CSLADP009](CSLADP009-AmbiguousOperationName.md) - Data portal operation methods are ambiguous +* [CSLADP010](CSLADP010-InvalidAsyncSuffix.md) - Invalid data portal extensions async suffix +* [CSLADP011](CSLADP011-SyncExtensionsNotGenerated.md) - Synchronous data portal extensions are not generated without an async suffix diff --git a/docs/code-generators.md b/docs/code-generators.md index e3284151e8..11a8cf20d3 100644 --- a/docs/code-generators.md +++ b/docs/code-generators.md @@ -4,12 +4,14 @@ Starting with CSLA 9, there are code generators available as NuGet packages. These packages are designed to work with the CSLA .NET framework to generate code for your business objects as part of the .NET build process. -* [AutoImplementProperties](https://github.com/MarimerLLC/csla/tree/main/Source/Csla.Generators/cs/AutoImplementProperties) - Uses code generation to radically reduce the amount of code you need to write to declare properties in a business domain class +* [AutoImplementProperties](https://github.com/MarimerLLC/csla/tree/main/Source/Csla.Generators/cs/AutoImplementProperties) - Uses code generation to radically reduce the amount of code you need to write to declare properties in a business domain class. Starting with CSLA 11 this generator is included in the `Csla` package. +* [DataPortalOperations](data-portal-operations-generator.md) - Starting with CSLA 11, included in the `Csla` package. For every `partial` class with data portal operation methods, generates code that lets the data portal call the operation methods directly instead of through reflection. +* [DataPortalExtensions](data-portal-extensions-generator.md) - Starting with CSLA 11, included in the `Csla` package. For classes marked with `[DataPortalExtensions]` (or every eligible class, when the assembly is marked), generates strongly typed extension methods on `IDataPortal` and `IChildDataPortal` for each operation method, so calls such as `portal.GetByIdAsync(42)` are checked by the compiler. Adapted from Stefan Ossendorf's [Csla.DataPortalExtensions](https://github.com/StefanOssendorf/Csla.DataPortalExtensions). * [AutoSerialization](https://github.com/MarimerLLC/csla/tree/main/Source/Csla.Generators/cs/AutoSerialization) - Uses code generation to improve serialization performance Other modern code generator tools also exist: -* [DataPortal Extensions](https://github.com/StefanOssendorf/Csla.DataPortalExtensions) - Generates extension methods for CSLA.NET attributed methods (e.g. Fetch) for type and compile-time safety +* [DataPortal Extensions](https://github.com/StefanOssendorf/Csla.DataPortalExtensions) - Generates extension methods for CSLA.NET attributed methods (e.g. Fetch) for type and compile-time safety. For CSLA 11 and later, use the data portal extensions generator included in the `Csla` package. * [CslaGeneratorSerialization](https://github.com/JasonBock/CslaGeneratorSerialization) - A custom CSLA serialization formatter that uses C# source generators. ## CSLA .NET Code Generators diff --git a/docs/data-portal-extensions-generator.md b/docs/data-portal-extensions-generator.md new file mode 100644 index 0000000000..e3b35ac8b4 --- /dev/null +++ b/docs/data-portal-extensions-generator.md @@ -0,0 +1,101 @@ +# Data Portal Extensions Generator + +Starting with CSLA 11, the `Csla` package includes a source generator that creates strongly typed extension methods on `IDataPortal` and `IChildDataPortal` for the data portal operation methods of your business classes. Instead of passing untyped criteria: + +```csharp +var person = await portal.FetchAsync(42); +``` + +you call a method whose parameters match the operation method, so the compiler checks the criteria: + +```csharp +var person = await portal.PortalFetchAsync(42); +``` + +The generator and the related [CSLA0025](analyzers/CSLA0025-UseGeneratedDataPortalExtensionAnalyzer.md) analyzer are adapted from [Csla.DataPortalExtensions](https://github.com/StefanOssendorf/Csla.DataPortalExtensions) by [Stefan Ossendorf](https://github.com/StefanOssendorf), used under the MIT License. For CSLA 11 and later, use the generator included in the `Csla` package instead of the `Csla.DataPortalExtensions` package. + +## Using the generator + +Add `[DataPortalExtensions]` to a business class: + +```csharp +[DataPortalExtensions(Prefix = "Portal")] +public partial class PersonEdit : BusinessBase +{ + [Create] + private void Create() { } + + [Fetch] + private async Task Fetch(int id, [Inject] IPersonDal dal) { /* ... */ } +} +``` + +The generator creates a `PersonEditDataPortalExtensions` class, in the namespace of `PersonEdit`, with these methods: + +```csharp +Task PortalCreateAsync(this IDataPortal portal); +PersonEdit PortalCreate(this IDataPortal portal); +Task PortalFetchAsync(this IDataPortal portal, int id); +PersonEdit PortalFetch(this IDataPortal portal, int id); +``` + +Extension methods are generated for `[Create]`, `[Fetch]`, `[Execute]` and `[Delete]` methods on `IDataPortal`, and for `[CreateChild]` and `[FetchChild]` methods on `IChildDataPortal`. + +* The method names come from the operation method name, without an `Async` suffix, plus the `Prefix`. +* The methods take only the criteria parameters. `[Inject]` parameters are resolved on the server as usual. Parameter default values are kept. +* The methods pass the operation name to the data portal, so the data portal calls the operation method directly instead of searching for it using reflection. With a custom or mock `IDataPortal` implementation, the methods call the existing `params object[]` methods instead. + +### Choosing a prefix + +`IDataPortal` already has instance methods such as `FetchAsync` and `Fetch`, and C# always prefers an instance method over an extension method. An operation method named `Fetch` would therefore produce extension methods that are hidden by the instance methods. These are not generated, and analyzer [CSLADP005](analyzers/CSLADP005-ExtensionNameHidden.md) reports them. + +Use `Prefix` to give the generated methods distinct names, or give the operation methods more specific names, such as `FetchById`. + +### Enabling extensions for an assembly + +Add the attribute to the assembly to generate extension methods for every business class in the assembly that can have them: + +```csharp +[assembly: DataPortalExtensions(Prefix = "Portal")] +``` + +This applies to non-abstract, non-generic classes that implement `ICslaObject` and are accessible outside their containing type. A `Prefix` on a class attribute takes precedence over the assembly `Prefix`. + +### Excluding classes and methods + +Add `[NoDataPortalExtension]` to a class or an operation method to exclude it. + +## Build properties + +MSBuild properties in the project file control the generated methods: + +```xml + + + false + + none + +``` + +With no async suffix, the sync methods would have the same names as the async methods, so they are not generated; see [CSLADP011](analyzers/CSLADP011-SyncExtensionsNotGenerated.md). + +## Diagnostics + +The analyzers in the `Csla` package report extension methods that are not generated, and why: + +* [CSLADP003](analyzers/CSLADP003-GenericExtensionsNotGenerated.md) - Data portal extensions are not generated for generic types +* [CSLADP004](analyzers/CSLADP004-InvalidExtensionsTarget.md) - Invalid target for `[DataPortalExtensions]` +* [CSLADP005](analyzers/CSLADP005-ExtensionNameHidden.md) - Generated data portal extension would be hidden +* [CSLADP006](analyzers/CSLADP006-InaccessibleParameterType.md) - Operation parameter type is not accessible +* [CSLADP007](analyzers/CSLADP007-DuplicateExtensionMethod.md) - Generated data portal extension would be duplicated +* [CSLADP008](analyzers/CSLADP008-InvalidExtensionPrefix.md) - Invalid `[DataPortalExtensions]` prefix +* [CSLADP010](analyzers/CSLADP010-InvalidAsyncSuffix.md) - Invalid data portal extensions async suffix +* [CSLADP011](analyzers/CSLADP011-SyncExtensionsNotGenerated.md) - Synchronous data portal extensions are not generated without an async suffix + +Analyzer [CSLA0025](analyzers/CSLA0025-UseGeneratedDataPortalExtensionAnalyzer.md) suggests using a generated extension method instead of calling an `IDataPortal` method with untyped criteria, and provides a code fix that replaces the call. + +## Related + +* [Data portal operations generator](data-portal-operations-generator.md) +* [Analyzers](analyzers/index.md) diff --git a/docs/data-portal-operations-generator.md b/docs/data-portal-operations-generator.md new file mode 100644 index 0000000000..ff53f22d0e --- /dev/null +++ b/docs/data-portal-operations-generator.md @@ -0,0 +1,57 @@ +# Data Portal Operations Generator + +Starting with CSLA 11, the `Csla` package includes a source generator that lets the data portal call your data portal operation methods directly, instead of finding and invoking them through reflection. There is nothing to install or configure: the generator runs in every C# project that references `Csla`. + +## Using the generator + +Declare your business class `partial`. Every class that declares data portal operation methods (methods marked with `[Create]`, `[Fetch]`, `[Insert]`, `[Update]`, `[Execute]`, `[Delete]`, `[DeleteSelf]`, `[CreateChild]`, `[FetchChild]`, `[InsertChild]`, `[UpdateChild]`, `[DeleteSelfChild]` or `[ExecuteChild]`) is handled, as long as the class and every type that contains it are `partial`: + +```csharp +[CslaImplementProperties] +public partial class PersonEdit : BusinessBase +{ + public partial int Id { get; private set; } + public partial string Name { get; set; } + + [Create] + private void Create() { } + + [Fetch] + private async Task Fetch(int id, [Inject] IPersonDal dal) + { + var data = await dal.GetAsync(id); + // ... + } + + [Update] + private async Task Update([Inject] IPersonDal dal) { /* ... */ } +} +``` + +Analyzer [CSLA0024](analyzers/CSLA0024-TypeWithOperationsShouldBePartialAnalyzer.md) reports classes with operation methods that are not `partial`, and provides a code fix that adds the `partial` modifier. Classes that are not `partial` continue to work through reflection. + +Your operation methods do not change. They can still be `private`, take criteria and `[Inject]` parameters (including keyed services), and be synchronous or asynchronous. + +## What is generated + +For each `partial` class, the generator adds: + +* An internal nested `IDataPortalOperations` interface with a member for each operation method, implemented explicitly by the class. This makes `private` operation methods visible to IDEs, analyzers and the trimmer as used code. +* For concrete classes, implementations of `Csla.Server.IDataPortalOperationMapping` and `Csla.Server.IDataPortalOperationNamedMapping`. The data portal calls these to invoke the operation methods. + +The data portal dispatches an operation in this order: + +1. **By name.** The client sends the name of the operation (for example `Fetch__Int32`), computed from the operation method's criteria parameter types. The generated code uses the name to call the method directly. +2. **By criteria.** If no name is available, for example because the call comes from an older client, the generated code matches the criteria values against the parameter types of each operation method. +3. **By reflection.** If the generated code cannot handle the call, the data portal falls back to the reflection-based dispatch used by earlier versions of CSLA. + +The generated code resolves `[Inject]` parameters and reports errors the same way as reflection-based dispatch. An exception thrown by an operation method is wrapped in a `CallMethodException`, as before. + +## Operation methods that share a name + +Two operation methods map to the same operation when they have the same attribute and the same criteria parameter types, and differ only in their `[Inject]` parameters. As with reflection-based dispatch, the data portal calls the method with more injected parameters, and analyzer [CSLADP002](analyzers/CSLADP002-DuplicateOperationName.md) reports which method is used. If the methods have the same number of injected parameters, the data portal cannot choose between them, and analyzer [CSLADP009](analyzers/CSLADP009-AmbiguousOperationName.md) reports the ambiguity. + +## Related + +* [Data portal extensions generator](data-portal-extensions-generator.md) - strongly typed extension methods for calling the data portal +* [Analyzers](analyzers/index.md) diff --git a/releasenotes.md b/releasenotes.md index 81fd23bc27..c2d373e960 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -17,8 +17,57 @@ CSLA 11 includes all of the changes released in CSLA 10.2.0. The full list of ch * `netstandard2.0` and .NET Framework 4.6.2 through 4.8 targets are unchanged * `Csla.Maui` targets `net11.0-android`, `net11.0-ios`, `net11.0-maccatalyst`, and `net11.0-windows` alongside the .NET 10 targets +**Source generators bundled in the Csla package** ([#4359](https://github.com/MarimerLLC/csla/issues/4359), [#4925](https://github.com/MarimerLLC/csla/pull/4925)) + +The CSLA source generators now ship inside the `Csla` NuGet package and run automatically in any C# project that references `Csla`. There is no separate generator package to install. + +* The `[CslaImplementProperties]`, `[CslaImplementPropertiesInterface]` and `[CslaIgnoreProperty]` attributes are now part of `Csla.dll`. +* For every `partial` class that declares data portal operation methods (`[Create]`, `[Fetch]`, `[Insert]`, `[Update]`, `[Execute]`, `[Delete]`, `[DeleteSelf]` and the child equivalents), the generator emits an internal nested `IDataPortalOperations` interface that names each operation method, plus implementations of `IDataPortalOperationMapping` and `IDataPortalOperationNamedMapping` for concrete classes. The data portal then calls operation methods directly instead of using reflection, and IDEs, analyzers and the trimmer see private operation methods as used. +* Business classes with data portal operation methods should be declared `partial`. Analyzer **CSLA0024** reports classes (or containing types) that are not partial and provides a code fix that adds the `partial` modifier. Non-partial classes continue to work through reflection. + +**Strongly typed data portal extension methods** ([#4359](https://github.com/MarimerLLC/csla/issues/4359), [#4925](https://github.com/MarimerLLC/csla/pull/4925)) + +Add `[DataPortalExtensions]` to a business class to generate strongly typed extension methods on `IDataPortal` and `IChildDataPortal` for its `[Create]`, `[Fetch]`, `[Execute]`, `[Delete]`, `[CreateChild]` and `[FetchChild]` methods, in both async and sync forms: + +```csharp +[DataPortalExtensions(Prefix = "Portal")] +public partial class PersonEdit : BusinessBase +{ + [Fetch] + private async Task Fetch(int id, [Inject] IPersonDal dal) { ... } +} + +var person = await portal.PortalFetchAsync(42); +``` + +* Generated methods take only the criteria parameters (`[Inject]` parameters are omitted), and pass the pre-computed operation name and `[RunLocal]` setting to the data portal, so the client no longer searches for the operation method using reflection. +* Because `IDataPortal` instance methods such as `FetchAsync` take precedence over extension methods, a generated name that matches one is not generated and is reported as a warning. Use `Prefix` to give the generated methods distinct names. +* Add `[assembly: DataPortalExtensions]` to generate extension methods for every business class in the assembly that can have them (non-abstract, non-generic classes that implement `ICslaObject` and are accessible outside their containing type). A `Prefix` on a class attribute takes precedence over the assembly `Prefix`. +* Use `[NoDataPortalExtension]` to exclude a class or an operation method. +* MSBuild properties control the generated names and forms: + + ```xml + + + false + + none + + ``` + + With no async suffix, the sync methods would have the same names as the async methods, so they are not generated. +* The `CSLADP002` to `CSLADP011` diagnostics, about operation methods that share an operation name and about extension methods that are not generated, are reported by analyzers rather than by the source generators, so they can be suppressed in source. They are described in the [analyzer documentation](docs/analyzers/index.md#data-portal-generator-diagnostics). +* Custom or mock `IDataPortal` implementations continue to work; the generated methods fall back to the existing `params object[]` methods. +* Analyzer **CSLA0025** suggests using the generated extension methods instead of calling `IDataPortal` methods with untyped criteria, and provides a code fix that replaces the call. + +The data portal extension generator and the CSLA0025 analyzer are adapted from [Csla.DataPortalExtensions](https://github.com/StefanOssendorf/Csla.DataPortalExtensions) by [Stefan Ossendorf](https://github.com/StefanOssendorf), used under the MIT License. Thank you, Stefan! + ### Changes +**Data Portal** + +* [#4359](https://github.com/MarimerLLC/csla/issues/4359) Call data portal operation methods explicitly instead of through reflection, using source generators bundled in the `Csla` package ([#4925](https://github.com/MarimerLLC/csla/pull/4925)) + **Build and Release** * [#4917](https://github.com/MarimerLLC/csla/issues/4917) Set the `main` version to 11.0.0 and start the CSLA 11 release notes ([#4918](https://github.com/MarimerLLC/csla/pull/4918)) @@ -27,6 +76,7 @@ CSLA 11 includes all of the changes released in CSLA 10.2.0. The full list of ch * CSLA 11 no longer targets .NET 8 or .NET 9. Applications must target .NET 10 or later (or continue to use .NET Framework 4.6.2 through 4.8, or a `netstandard2.0`-compatible runtime). * On .NET 11, Android apps using `Csla.Maui` require Android API level 24 or later, because .NET 11 for Android requires it. The `net10.0-android` target still supports API level 21. +* The `Csla.Generator.AutoImplementProperties.CSharp` package is retired because its generators are now included in the `Csla` package. Remove any `PackageReference` (and `PackageVersion`) for it; a build error (`CSLABUILD001`) is reported while the reference remains. ### Supported Platforms @@ -46,5 +96,10 @@ Also expected to work on: ### Contributors * [@rockfordlhotka](https://github.com/rockfordlhotka) +* [@StefanOssendorf](https://github.com/StefanOssendorf) + +### Third-party notices + +* Portions of the data portal extension generator and analyzers are adapted from [Csla.DataPortalExtensions](https://github.com/StefanOssendorf/Csla.DataPortalExtensions), Copyright (c) 2023 Stefan Ossendorf, licensed under the [MIT License](https://github.com/StefanOssendorf/Csla.DataPortalExtensions/blob/main/LICENSE). Thank you all for your contributions!