From 97e7341c7fc0124aeb0ecd3168a430dee552277f Mon Sep 17 00:00:00 2001 From: Jason Bock Date: Thu, 5 Jan 2017 09:30:43 -0600 Subject: [PATCH 1/2] #680 Fixes trivia issue Analyzer no longer removes leading and trailing trivia from new field node. --- .../Csla.Analyzers.IntegrationTests/Cmd.cs | 2 +- .../Csla.Analyzers.IntegrationTests.csproj | 1 + .../PropertiesAndRegions.cs | 14 ++++++++ ...rsAnalyzerPublicConstructorCodeFixTests.cs | 4 +-- .../Csla.Analyzers.Tests.csproj | 3 ++ ...valuateManagedBackingFieldsCodeFixTests.cs | 32 ++++++++++++++++--- .../VerifyGetFixesWithTrivia.cs | 12 +++++++ .../EvaluateManagedBackingFieldsCodeFix.cs | 8 ++--- 8 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/PropertiesAndRegions.cs create mode 100644 Source/Csla.Analyzers/Csla.Analyzers.Tests/Targets/EvaluateManagedBackingFieldsCodeFixTests/VerifyGetFixesWithTrivia.cs diff --git a/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Cmd.cs b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Cmd.cs index a8f44ca50b..3cb3d9d98d 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Cmd.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Cmd.cs @@ -45,4 +45,4 @@ public string MyText set { this.SetProperty(MyTextProperty, value); } } } -} +} \ No newline at end of file diff --git a/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests.csproj b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests.csproj index a4f8a4be24..9a88546ca8 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests.csproj +++ b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests.csproj @@ -45,6 +45,7 @@ + diff --git a/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/PropertiesAndRegions.cs b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/PropertiesAndRegions.cs new file mode 100644 index 0000000000..040eec48d7 --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/PropertiesAndRegions.cs @@ -0,0 +1,14 @@ +using System; + +namespace Csla.Analyzers.IntegrationTests +{ + [Serializable] + public sealed class PropertiesAndRegions + : BusinessBase + { + #region Properties + private static readonly PropertyInfo MyGuidProperty = RegisterProperty(c => c.MyGuid); + public Guid MyGuid => this.GetProperty(PropertiesAndRegions.MyGuidProperty); + #endregion + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers.Tests/CheckConstructorsAnalyzerPublicConstructorCodeFixTests.cs b/Source/Csla.Analyzers/Csla.Analyzers.Tests/CheckConstructorsAnalyzerPublicConstructorCodeFixTests.cs index cc6567adb2..2388ec1a32 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers.Tests/CheckConstructorsAnalyzerPublicConstructorCodeFixTests.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers.Tests/CheckConstructorsAnalyzerPublicConstructorCodeFixTests.cs @@ -1,6 +1,4 @@ -using Csla.Analyzers; -using Csla.Analyzers.Tests; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/Source/Csla.Analyzers/Csla.Analyzers.Tests/Csla.Analyzers.Tests.csproj b/Source/Csla.Analyzers/Csla.Analyzers.Tests/Csla.Analyzers.Tests.csproj index 3f90bb5554..14bb1978d7 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers.Tests/Csla.Analyzers.Tests.csproj +++ b/Source/Csla.Analyzers/Csla.Analyzers.Tests/Csla.Analyzers.Tests.csproj @@ -192,6 +192,9 @@ Always + + Always + Always diff --git a/Source/Csla.Analyzers/Csla.Analyzers.Tests/EvaluateManagedBackingFieldsCodeFixTests.cs b/Source/Csla.Analyzers/Csla.Analyzers.Tests/EvaluateManagedBackingFieldsCodeFixTests.cs index acd3d255d8..1d5cb74652 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers.Tests/EvaluateManagedBackingFieldsCodeFixTests.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers.Tests/EvaluateManagedBackingFieldsCodeFixTests.cs @@ -1,6 +1,4 @@ -using Csla.Analyzers; -using Csla.Analyzers.Tests; -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -51,7 +49,33 @@ public async Task VerifyGetFixes() await TestHelpers.VerifyActionAsync(actions, EvaluateManagedBackingFieldsCodeFixConstants.FixManagedBackingFieldDescription, document, - tree, new[] { $@" public static readonly " }); + tree, new[] { " public static readonly " }); + } + + [TestMethod] + public async Task VerifyGetFixesWithTrivia() + { + var code = File.ReadAllText( + $@"Targets\{nameof(EvaluateManagedBackingFieldsCodeFixTests)}\{(nameof(this.VerifyGetFixesWithTrivia))}.cs"); + var document = TestHelpers.Create(code); + var tree = await document.GetSyntaxTreeAsync(); + var diagnostics = await TestHelpers.GetDiagnosticsAsync(code, new EvaluateManagedBackingFieldsAnalayzer()); + var sourceSpan = diagnostics[0].Location.SourceSpan; + + var actions = new List(); + var codeActionRegistration = new Action>( + (a, _) => { actions.Add(a); }); + + var fix = new EvaluateManagedBackingFieldsCodeFix(); + var codeFixContext = new CodeFixContext(document, diagnostics[0], + codeActionRegistration, new CancellationToken(false)); + await fix.RegisterCodeFixesAsync(codeFixContext); + + Assert.AreEqual(1, actions.Count, nameof(actions.Count)); + + await TestHelpers.VerifyActionAsync(actions, + EvaluateManagedBackingFieldsCodeFixConstants.FixManagedBackingFieldDescription, document, + tree, new[] { " #region Properties\r\n public" }); } } } diff --git a/Source/Csla.Analyzers/Csla.Analyzers.Tests/Targets/EvaluateManagedBackingFieldsCodeFixTests/VerifyGetFixesWithTrivia.cs b/Source/Csla.Analyzers/Csla.Analyzers.Tests/Targets/EvaluateManagedBackingFieldsCodeFixTests/VerifyGetFixesWithTrivia.cs new file mode 100644 index 0000000000..def3232333 --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers.Tests/Targets/EvaluateManagedBackingFieldsCodeFixTests/VerifyGetFixesWithTrivia.cs @@ -0,0 +1,12 @@ +namespace Csla.Analyzers.Tests.Targets.EvaluateManagedBackingFieldsCodeFixTests +{ + public class VerifyGetFixesWithTrivia + : BusinessBase + { + #region Properties + private static readonly PropertyInfo DataProperty = RegisterProperty(_ => _.Data); + #endregion + + public string Data => GetProperty(DataProperty); + } +} \ No newline at end of file diff --git a/Source/Csla.Analyzers/Csla.Analyzers/EvaluateManagedBackingFieldsCodeFix.cs b/Source/Csla.Analyzers/Csla.Analyzers/EvaluateManagedBackingFieldsCodeFix.cs index 3f6a001793..75f01f74cb 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/EvaluateManagedBackingFieldsCodeFix.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/EvaluateManagedBackingFieldsCodeFix.cs @@ -39,12 +39,12 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.CancellationToken.ThrowIfCancellationRequested(); - var newFieldNode = fieldNode; - - newFieldNode = newFieldNode.WithModifiers(SyntaxFactory.TokenList( + var newFieldNode = fieldNode + .WithModifiers(SyntaxFactory.TokenList( SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), - SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword))); + SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword))) + .WithTriviaFrom(fieldNode); var newRoot = root.ReplaceNode(fieldNode, newFieldNode); From fa4e21af7001cb72f2bd56197f85ee933d260fdf Mon Sep 17 00:00:00 2001 From: Jason Bock Date: Thu, 28 Mar 2019 12:06:17 -0500 Subject: [PATCH 2/2] #925 Added new analyzer --- .../CallingNewTests.cs | 27 ++++++ .../Extensions/ITypeSymbolExtensionsTests.cs | 17 ++++ ...FindBusinessObjectCreationAnalyzerTests.cs | 89 +++++++++++++++++++ .../CheckConstructorsAnalyzerConstants.cs | 7 ++ .../Csla.Analyzers/Constants.cs | 1 + .../Csla.Analyzers/CslaMemberConstants.cs | 1 + .../Extensions/ITypeSymbolExtensions.cs | 8 ++ .../FindBusinessObjectCreationAnalyzer.cs | 50 +++++++++++ 8 files changed, 200 insertions(+) create mode 100644 Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/CallingNewTests.cs create mode 100644 Source/Csla.Analyzers/Csla.Analyzers.Tests/FindBusinessObjectCreationAnalyzerTests.cs create mode 100644 Source/Csla.Analyzers/Csla.Analyzers/FindBusinessObjectCreationAnalyzer.cs diff --git a/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/CallingNewTests.cs b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/CallingNewTests.cs new file mode 100644 index 0000000000..0dfd8925e1 --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers.IntegrationTests/Csla.Analyzers.IntegrationTests/CallingNewTests.cs @@ -0,0 +1,27 @@ +using System; +using Csla.Server; + +namespace Csla.Analyzers.IntegrationTests +{ + [Serializable] + public class A : BusinessBase { } + + public class B : ObjectFactory + { + public void Foo() + { + var a = new A(); + } + } + + public class C + { + public void Foo() + { + // This should be an error + // because you can't create a new business object + // outside of an ObjectFactory. + var a = new A(); + } + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers.Tests/Extensions/ITypeSymbolExtensionsTests.cs b/Source/Csla.Analyzers/Csla.Analyzers.Tests/Extensions/ITypeSymbolExtensionsTests.cs index 7c15d37116..55ab452ac0 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers.Tests/Extensions/ITypeSymbolExtensionsTests.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers.Tests/Extensions/ITypeSymbolExtensionsTests.cs @@ -113,6 +113,23 @@ public async Task IsPrimitiveForDouble() [TestMethod] public void IsBusinessBaseWhenSymbolIsNull() => Assert.IsFalse((null as ITypeSymbol).IsBusinessBase()); + [TestMethod] + public async Task IsObjectFactoryForNotObjectFactoryType() + { + var code = "public class A { }"; + Assert.IsFalse((await GetTypeSymbolAsync(code, "A")).IsObjectFactory()); + } + + [TestMethod] + public async Task IsObjectFactoryForObjectFactoryType() + { + var code = +@"using Csla.Server; + +public class A : ObjectFactory { }"; + Assert.IsTrue((await GetTypeSymbolAsync(code, "A")).IsObjectFactory()); + } + [TestMethod] public async Task IsIPropertyInfoWhenSymbolDoesNotDeriveFromIPropertyInfo() { diff --git a/Source/Csla.Analyzers/Csla.Analyzers.Tests/FindBusinessObjectCreationAnalyzerTests.cs b/Source/Csla.Analyzers/Csla.Analyzers.Tests/FindBusinessObjectCreationAnalyzerTests.cs new file mode 100644 index 0000000000..66c3611a3e --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers.Tests/FindBusinessObjectCreationAnalyzerTests.cs @@ -0,0 +1,89 @@ +using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading.Tasks; + +namespace Csla.Analyzers.Tests +{ + [TestClass] + + public sealed class FindBusinessObjectCreationAnalyzerTests + { + [TestMethod] + public void VerifySupportedDiagnostics() + { + var analyzer = new FindBusinessObjectCreationAnalyzer(); + var diagnostics = analyzer.SupportedDiagnostics; + Assert.AreEqual(1, diagnostics.Length); + + var diagnostic = diagnostics[0]; + Assert.AreEqual(Constants.AnalyzerIdentifiers.FindBusinessObjectCreation, diagnostic.Id, + nameof(DiagnosticDescriptor.Id)); + Assert.AreEqual(FindBusinessObjectCreationConstants.Title, diagnostic.Title.ToString(), + nameof(DiagnosticDescriptor.Title)); + Assert.AreEqual(FindBusinessObjectCreationConstants.Message, diagnostic.MessageFormat.ToString(), + nameof(DiagnosticDescriptor.MessageFormat)); + Assert.AreEqual(Constants.Categories.Usage, diagnostic.Category, + nameof(DiagnosticDescriptor.Category)); + Assert.AreEqual(DiagnosticSeverity.Error, diagnostic.DefaultSeverity, + nameof(DiagnosticDescriptor.DefaultSeverity)); + } + + [TestMethod] + public async Task AnalyzeWhenConstructorIsNotOnBusinessObject() + { + var code = +@"public class A { } + + public class B + { + void Foo() + { + var a = new A(); + } + }"; + await TestHelpers.RunAnalysisAsync( + code, Array.Empty()); + } + + [TestMethod] + public async Task AnalyzeWhenConstructorIsOnBusinessObjectWithinObjectFactory() + { + var code = +@"using Csla; +using Csla.Server; + +public class A : BusinessBase { } + +public class B : ObjectFactory +{ + void Foo() + { + var a = new A(); + } +}"; + await TestHelpers.RunAnalysisAsync( + code, Array.Empty()); + } + + [TestMethod] + public async Task AnalyzeWhenConstructorIsOnBusinessObjectOutsideOfObjectFactory() + { + var code = +@"using Csla; +using Csla.Server; + +public class A : BusinessBase { } + +public class B +{ + void Foo() + { + var a = new A(); + } +}"; + await TestHelpers.RunAnalysisAsync( + code, new[] { Constants.AnalyzerIdentifiers.FindBusinessObjectCreation }); + } + } +} diff --git a/Source/Csla.Analyzers/Csla.Analyzers/CheckConstructorsAnalyzerConstants.cs b/Source/Csla.Analyzers/Csla.Analyzers/CheckConstructorsAnalyzerConstants.cs index f3bd51a234..d6d9225c51 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/CheckConstructorsAnalyzerConstants.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/CheckConstructorsAnalyzerConstants.cs @@ -15,6 +15,13 @@ public static class ConstructorHasParametersConstants public const string Message = "CSLA business objects should not have public constructors with parameters."; } + public static class FindBusinessObjectCreationConstants + { + public const string Title = "Find CSLA Business Objects That Are Created Outside of a ObjectFactory"; + public const string IdentifierText = "BusinessObjectCreated"; + public const string Message = "CSLA business objects should not be created outside of a ObjectFactory instance."; + } + public static class CheckConstructorsAnalyzerPublicConstructorCodeFixConstants { public const string AddPublicConstructorDescription = "Add public constructor with no arguments"; diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs b/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs index 71b72b26b1..752179d2c9 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/Constants.cs @@ -8,6 +8,7 @@ public static class AnalyzerIdentifiers public const string IsOperationMethodPublic = "CSLA0002"; public const string PublicNoArgumentConstructorIsMissing = "CSLA0003"; public const string ConstructorHasParameters = "CSLA0004"; + public const string FindBusinessObjectCreation = "CSLA0011"; public const string FindSaveAssignmentIssue = "CSLA0005"; public const string FindSaveAsyncAssignmentIssue = "CSLA0006"; public const string OnlyUseCslaPropertyMethodsInGetSetRule = "CSLA0007"; diff --git a/Source/Csla.Analyzers/Csla.Analyzers/CslaMemberConstants.cs b/Source/Csla.Analyzers/Csla.Analyzers/CslaMemberConstants.cs index b3f52a8444..0e23dc8fe7 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/CslaMemberConstants.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/CslaMemberConstants.cs @@ -46,6 +46,7 @@ public static class Types public const string IMobileObject = "IMobileObject"; public const string IPropertyInfo = "IPropertyInfo"; public const string ManagedObjectBase = "ManagedObjectBase"; + public const string ObjectFactoryBase = "ObjectFactory"; public const string ReadOnlyBase = "ReadOnlyBase"; } diff --git a/Source/Csla.Analyzers/Csla.Analyzers/Extensions/ITypeSymbolExtensions.cs b/Source/Csla.Analyzers/Csla.Analyzers/Extensions/ITypeSymbolExtensions.cs index 8fa70851f1..267428dbce 100644 --- a/Source/Csla.Analyzers/Csla.Analyzers/Extensions/ITypeSymbolExtensions.cs +++ b/Source/Csla.Analyzers/Csla.Analyzers/Extensions/ITypeSymbolExtensions.cs @@ -9,6 +9,14 @@ namespace Csla.Analyzers.Extensions { internal static class ITypeSymbolExtensions { + internal static bool IsObjectFactory(this ITypeSymbol @this) + { + return @this != null && + ((@this.Name == CslaMemberConstants.Types.ObjectFactoryBase && + @this.ContainingAssembly.Name == CslaMemberConstants.AssemblyName) || + @this.BaseType.IsObjectFactory()); + } + internal static bool IsBusinessBase(this ITypeSymbol @this) { return @this != null && diff --git a/Source/Csla.Analyzers/Csla.Analyzers/FindBusinessObjectCreationAnalyzer.cs b/Source/Csla.Analyzers/Csla.Analyzers/FindBusinessObjectCreationAnalyzer.cs new file mode 100644 index 0000000000..296860c4c1 --- /dev/null +++ b/Source/Csla.Analyzers/Csla.Analyzers/FindBusinessObjectCreationAnalyzer.cs @@ -0,0 +1,50 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using static Csla.Analyzers.Extensions.ITypeSymbolExtensions; +using static Csla.Analyzers.Extensions.SyntaxNodeExtensions; + +namespace Csla.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class FindBusinessObjectCreationAnalyzer + : DiagnosticAnalyzer + { + private static readonly DiagnosticDescriptor objectCreatedRule = + new DiagnosticDescriptor( + Constants.AnalyzerIdentifiers.FindBusinessObjectCreation, FindBusinessObjectCreationConstants.Title, + FindBusinessObjectCreationConstants.Message, Constants.Categories.Usage, + DiagnosticSeverity.Error, true); + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(objectCreatedRule); + + public override void Initialize(AnalysisContext context) => + context.RegisterSyntaxNodeAction(AnalyzeObjectCreationExpression, SyntaxKind.ObjectCreationExpression); + + private static void AnalyzeObjectCreationExpression(SyntaxNodeAnalysisContext context) + { + var constructorNode = (ObjectCreationExpressionSyntax)context.Node; + var constructorSymbol = context.SemanticModel.GetSymbolInfo(constructorNode).Symbol as IMethodSymbol; + var containingSymbol = constructorSymbol?.ContainingType; + + if(containingSymbol.IsStereotype()) + { + context.CancellationToken.ThrowIfCancellationRequested(); + var callerClassNode = constructorNode.FindParent(); + + if(callerClassNode != null) + { + var callerClassSymbol = context.SemanticModel.GetDeclaredSymbol(callerClassNode) as ITypeSymbol; + + if(!callerClassSymbol.IsObjectFactory()) + { + context.ReportDiagnostic(Diagnostic.Create(objectCreatedRule, constructorNode.GetLocation())); + } + } + } + } + } +} \ No newline at end of file