├── .gitattributes ├── .gitignore ├── LICENSE ├── ObjectMapper ├── ObjectMapper.Framework │ ├── Extensions │ │ ├── CollectionExtensions.cs │ │ └── ObjectMapperExtensions.cs │ ├── IObjectMapper.cs │ ├── IObjectMapperAdapter.cs │ ├── ObjectMapper.Framework.csproj │ ├── ObjectMapperMethodAttribute.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── ObjectMapper.GenerateImplementationAnalyzer │ ├── ObjectMapper.GenerateImplementationAnalyzer.Test │ │ ├── AttributeUsageUnitTests.cs │ │ ├── GenerateImplementationUnitTests.cs │ │ ├── Helpers │ │ │ ├── CodeFixVerifier.Helper.cs │ │ │ ├── DiagnosticResult.cs │ │ │ └── DiagnosticVerifier.Helper.cs │ │ ├── ObjectMapper.GenerateImplementationAnalyzer.Test.csproj │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ ├── Verifiers │ │ │ ├── CodeFixVerifier.cs │ │ │ └── DiagnosticVerifier.cs │ │ └── packages.config │ ├── ObjectMapper.GenerateImplementationAnalyzer.Vsix │ │ ├── ObjectMapper.GenerateImplementationAnalyzer.Vsix.csproj │ │ └── source.extension.vsixmanifest │ └── ObjectMapper.GenerateImplementationAnalyzer │ │ ├── AttributeUsageCodeAnalyzer.cs │ │ ├── Diagnostic.nuspec │ │ ├── GenerateImplementationCodeAnalyzer.cs │ │ ├── GenerateImplementationCodeGenerator.cs │ │ ├── ObjectMapper.GenerateImplementationAnalyzer.csproj │ │ ├── Properties │ │ └── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Utilities │ │ └── FrameworkHelpers.cs │ │ ├── lib │ │ └── ObjectMapper.Framework.dll │ │ ├── packages.config │ │ └── tools │ │ ├── install.ps1 │ │ └── uninstall.ps1 └── ObjectMapper.sln └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nejc Skofic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.Framework/Extensions/CollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ObjectMapper.Framework 8 | { 9 | public static class CollectionExtensions 10 | { 11 | /// 12 | /// Copies elements from source enumerable to list. List is cleared before new elements are copied over. 13 | /// 14 | /// Type of objects in collections. 15 | /// The target. 16 | /// The source. 17 | public static void CopyFrom(this List target, IEnumerable source) 18 | { 19 | if (target == null || source == null) return; 20 | target.Clear(); 21 | target.AddRange(source); 22 | } 23 | 24 | /// 25 | /// Copies elements from source enumerable to collection. Collection is cleared before new elements are copied over. 26 | /// 27 | /// Type of objects in collections. 28 | /// The target. 29 | /// The source. 30 | public static void CopyFrom(this ICollection target, IEnumerable source) 31 | { 32 | if (target == null || source == null || target.IsReadOnly) return; 33 | target.Clear(); 34 | foreach (var element in source) 35 | { 36 | target.Add(element); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.Framework/Extensions/ObjectMapperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ObjectMapper.Framework 8 | { 9 | public static class ObjectMapperExtensions 10 | { 11 | /// 12 | /// Creates new object of type T from mapper object. 13 | /// 14 | /// Type of target 15 | /// The mapper. 16 | /// New object with properties mapped from mapper. 17 | public static T CreateMappedObject(this IObjectMapper mapper) where T : new() 18 | { 19 | if (mapper == null) return default(T); 20 | 21 | T newObject = new T(); 22 | mapper.MapObject(newObject); 23 | return newObject; 24 | } 25 | 26 | /// 27 | /// Creates new object of type T from mapper adapter object. 28 | /// 29 | /// Type of source object 30 | /// Type of target object 31 | /// The adapter. 32 | /// The source. 33 | /// New object with properties mapped from mapper adapter. 34 | public static U CreateMappedObject(this IObjectMapperAdapter adapter, T source) where U : new() 35 | { 36 | if (adapter == null) return default(U); 37 | 38 | U newObject = new U(); 39 | adapter.MapObject(source, newObject); 40 | return newObject; 41 | } 42 | 43 | /// 44 | /// Creates new object of type U from mapper adapter object. 45 | /// 46 | /// Type of target object 47 | /// Type of source object 48 | /// The adapter. 49 | /// The source. 50 | /// New object with properties mapped from mapper adapter. 51 | public static T CreateMappedObject(this IObjectMapperAdapter adapter, U source) where T : new() 52 | { 53 | if (adapter == null) return default(T); 54 | 55 | T newObject = new T(); 56 | adapter.MapObject(source, newObject); 57 | return newObject; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.Framework/IObjectMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ObjectMapper.Framework 8 | { 9 | /// 10 | /// Implementing object can be mapped to object of class T. 11 | /// 12 | /// Type of target object 13 | public interface IObjectMapper 14 | { 15 | /// 16 | /// Maps this object to target. 17 | /// 18 | /// The target. 19 | void MapObject(T target); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.Framework/IObjectMapperAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ObjectMapper.Framework 8 | { 9 | /// 10 | /// Provides mapping between objects of type T and U. 11 | /// 12 | /// Type of first object. 13 | /// Type of second object. 14 | public interface IObjectMapperAdapter 15 | { 16 | /// 17 | /// Maps source object to target object. 18 | /// 19 | /// The source. 20 | /// The target. 21 | void MapObject(T source, U target); 22 | 23 | /// 24 | /// Maps source object to target object. 25 | /// 26 | /// The source. 27 | /// The target. 28 | void MapObject(U source, T target); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.Framework/ObjectMapper.Framework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 11.0 6 | Debug 7 | AnyCPU 8 | {8220C878-68E1-4307-A908-66FBEA9D8B56} 9 | Library 10 | Properties 11 | ObjectMapper.Framework 12 | ObjectMapper.Framework 13 | en-US 14 | 512 15 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | Profile7 17 | v4.5 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | true 28 | AllRules.ruleset 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | true 38 | AllRules.ruleset 39 | 40 | 41 | true 42 | 43 | 44 | objectmapper.pfx 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 66 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.Framework/ObjectMapperMethodAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace ObjectMapper.Framework 8 | { 9 | /// 10 | /// Marks method as mapping method. Method should accept two parameters (source and target) and returns void. 11 | /// 12 | /// 13 | [System.AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] 14 | public sealed class ObjectMapperMethodAttribute : Attribute 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.Framework/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("ObjectMapper.Framework")] 11 | [assembly: AssemblyDescription("Contains interfaces for mapping one object to another.")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("ObjectMapper.Framework")] 15 | [assembly: AssemblyCopyright("Copyright © 2016")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | [assembly: NeutralResourcesLanguage("en")] 19 | 20 | [assembly: CLSCompliant(true)] 21 | [assembly: ComVisible(false)] 22 | 23 | [assembly: AssemblyVersion("1.2.2.0")] 24 | [assembly: AssemblyFileVersion("1.2.2.0")] 25 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/AttributeUsageUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using TestHelper; 8 | using Microsoft.CodeAnalysis.Diagnostics; 9 | using Microsoft.CodeAnalysis; 10 | 11 | namespace ObjectMapper.GenerateImplementationAnalyzer.Test 12 | { 13 | [TestClass] 14 | public class AttributeUsageUnitTests : DiagnosticVerifier 15 | { 16 | #region TestEmptyCompilationUnit 17 | /// 18 | /// Tests the empty compilation unit. 19 | /// 20 | [TestMethod] 21 | public void TestEmptyCompilationUnit() 22 | { 23 | var test = @""; 24 | 25 | VerifyCSharpDiagnostic(test); 26 | } 27 | #endregion 28 | 29 | #region TestValidUse 30 | /// 31 | /// Tests the valid use. 32 | /// 33 | [TestMethod] 34 | public void TestValidUse() 35 | { 36 | var test = @" 37 | using ObjectMapper.Framework; 38 | 39 | namespace TestClassLibrary 40 | { 41 | public class ClassA 42 | { 43 | } 44 | 45 | public class ClassB 46 | { 47 | } 48 | 49 | public static class MappingClass 50 | { 51 | [ObjectMapperMethod] 52 | public void MapObject(ClassA source, ClassB target) 53 | { 54 | } 55 | } 56 | }"; 57 | 58 | VerifyCSharpDiagnostic(test); 59 | } 60 | #endregion 61 | 62 | #region TestUseWithInvalidReturnType 63 | /// 64 | /// Tests use with invalid return type. 65 | /// 66 | [TestMethod] 67 | public void TestUseWithInvalidReturnType() 68 | { 69 | var test = @" 70 | using ObjectMapper.Framework; 71 | 72 | namespace TestClassLibrary 73 | { 74 | public class ClassA 75 | { 76 | } 77 | 78 | public class ClassB 79 | { 80 | } 81 | 82 | public static class MappingClass 83 | { 84 | [ObjectMapperMethod] 85 | public ClassA MapObject(ClassA source, ClassB target) 86 | { 87 | return source; 88 | } 89 | } 90 | }"; 91 | 92 | var expected = new DiagnosticResult 93 | { 94 | Id = "OMAU01", 95 | Message = "Attribute cannot be applied to this method.", 96 | Severity = DiagnosticSeverity.Error, 97 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 10) } 98 | }; 99 | 100 | VerifyCSharpDiagnostic(test, expected); 101 | } 102 | #endregion 103 | 104 | #region TestUseWithTooLittleArguments 105 | /// 106 | /// Tests the use with too little arguments. 107 | /// 108 | [TestMethod] 109 | public void TestUseWithTooLittleArguments() 110 | { 111 | var test = @" 112 | using ObjectMapper.Framework; 113 | 114 | namespace TestClassLibrary 115 | { 116 | public class ClassA 117 | { 118 | } 119 | 120 | public class ClassB 121 | { 122 | } 123 | 124 | public static class MappingClass 125 | { 126 | [ObjectMapperMethod] 127 | public void MapObject(ClassA source) 128 | { 129 | } 130 | } 131 | }"; 132 | 133 | var expected = new DiagnosticResult 134 | { 135 | Id = "OMAU01", 136 | Message = "Attribute cannot be applied to this method.", 137 | Severity = DiagnosticSeverity.Error, 138 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 10) } 139 | }; 140 | 141 | VerifyCSharpDiagnostic(test, expected); 142 | } 143 | #endregion 144 | 145 | #region TestUseWithTooManyArguments 146 | /// 147 | /// Tests the use with too many arguments. 148 | /// 149 | [TestMethod] 150 | public void TestUseWithTooManyArguments() 151 | { 152 | var test = @" 153 | using ObjectMapper.Framework; 154 | 155 | namespace TestClassLibrary 156 | { 157 | public class ClassA 158 | { 159 | } 160 | 161 | public class ClassB 162 | { 163 | } 164 | 165 | public static class MappingClass 166 | { 167 | [ObjectMapperMethod] 168 | public void MapObject(ClassA source, ClassB target, ClassB tooMany) 169 | { 170 | } 171 | } 172 | }"; 173 | 174 | var expected = new DiagnosticResult 175 | { 176 | Id = "OMAU01", 177 | Message = "Attribute cannot be applied to this method.", 178 | Severity = DiagnosticSeverity.Error, 179 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 16, 10) } 180 | }; 181 | 182 | VerifyCSharpDiagnostic(test, expected); 183 | } 184 | #endregion 185 | 186 | #region Setup 187 | /// 188 | /// Get the CSharp analyzer being tested - to be implemented in non-abstract class 189 | /// 190 | /// 191 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 192 | { 193 | return new AttributeUsageCodeAnalyzer(); 194 | } 195 | #endregion 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/GenerateImplementationUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System; 6 | using TestHelper; 7 | 8 | namespace ObjectMapper.GenerateImplementationAnalyzer.Test 9 | { 10 | [TestClass] 11 | public class GenerateImplementationUnitTest : CodeFixVerifier 12 | { 13 | #region TestEmptyCompilationUnit 14 | /// 15 | /// Tests the empty compilation unit. 16 | /// 17 | [TestMethod] 18 | public void TestEmptyCompilationUnit() 19 | { 20 | var test = @""; 21 | 22 | VerifyCSharpDiagnostic(test); 23 | } 24 | #endregion 25 | 26 | #region TestIObjectMapperInterfaceWithoutDefinedMethods 27 | /// 28 | /// Tests the IObjectMapper interface without interface methods defined. 29 | /// 30 | [TestMethod] 31 | public void TestIObjectMapperInterfaceWithoutDefinedMethods() 32 | { 33 | var test = @" 34 | using ObjectMapper.Framework; 35 | using System.Collections.Generic; 36 | using System.Collections.ObjectModel; 37 | 38 | namespace TestClassLibrary 39 | { 40 | public class ClassA : IObjectMapper 41 | { 42 | public int? Prop1 { get; set; } 43 | public string Prop2 { get; set; } 44 | public decimal Prop3 { get; set; } 45 | 46 | private List _prop4; 47 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 48 | 49 | private LinkedList _prop5; 50 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 51 | 52 | private Collection _prop6; 53 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 54 | } 55 | 56 | public class ClassB 57 | { 58 | public int Prop1 { get; set; } 59 | public string Prop2 { get; set; } 60 | public decimal Prop3 { get; set; } 61 | 62 | private List _prop4; 63 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 64 | 65 | private LinkedList _prop5; 66 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 67 | 68 | private Collection _prop6; 69 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 70 | } 71 | }"; 72 | var expected = new DiagnosticResult 73 | { 74 | Id = "OMCG01", 75 | Message = "Implementation of mapping method(s) can be generated.", 76 | Severity = DiagnosticSeverity.Hidden, 77 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 27) } 78 | }; 79 | 80 | VerifyCSharpDiagnostic(test, expected); 81 | 82 | var fixtest = @" 83 | using ObjectMapper.Framework; 84 | using System.Collections.Generic; 85 | using System.Collections.ObjectModel; 86 | 87 | namespace TestClassLibrary 88 | { 89 | public class ClassA : IObjectMapper 90 | { 91 | public int? Prop1 { get; set; } 92 | public string Prop2 { get; set; } 93 | public decimal Prop3 { get; set; } 94 | 95 | private List _prop4; 96 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 97 | 98 | private LinkedList _prop5; 99 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 100 | 101 | private Collection _prop6; 102 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 103 | 104 | public void MapObject(ClassB target) 105 | { 106 | target.Prop1 = this.Prop1 ?? default(int); 107 | target.Prop2 = this.Prop2; 108 | target.Prop3 = this.Prop3; 109 | target.Prop4.CopyFrom(this.Prop4); 110 | target.Prop5.CopyFrom(this.Prop5); 111 | target.Prop6.CopyFrom(this.Prop6); 112 | } 113 | } 114 | 115 | public class ClassB 116 | { 117 | public int Prop1 { get; set; } 118 | public string Prop2 { get; set; } 119 | public decimal Prop3 { get; set; } 120 | 121 | private List _prop4; 122 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 123 | 124 | private LinkedList _prop5; 125 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 126 | 127 | private Collection _prop6; 128 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 129 | } 130 | }"; 131 | VerifyCSharpFix(test, fixtest); 132 | } 133 | #endregion 134 | 135 | #region TestIObjectMapperInterfaceWithDefinedMethods 136 | /// 137 | /// Tests the IObjectMapper interface with interface methods defined. 138 | /// 139 | [TestMethod] 140 | public void TestIObjectMapperInterfaceWithDefinedMethods() 141 | { 142 | var test = @" 143 | using ObjectMapper.Framework; 144 | using System.Collections.Generic; 145 | using System.Collections.ObjectModel; 146 | 147 | namespace TestClassLibrary 148 | { 149 | public class ClassA : IObjectMapper 150 | { 151 | public int? Prop1 { get; set; } 152 | public string Prop2 { get; set; } 153 | public decimal Prop3 { get; set; } 154 | 155 | private List _prop4; 156 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 157 | 158 | private LinkedList _prop5; 159 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 160 | 161 | private Collection _prop6; 162 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 163 | 164 | public void MapObject(ClassB target) 165 | { 166 | } 167 | } 168 | 169 | public class ClassB 170 | { 171 | public int Prop1 { get; set; } 172 | public string Prop2 { get; set; } 173 | public decimal Prop3 { get; set; } 174 | 175 | private List _prop4; 176 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 177 | 178 | private LinkedList _prop5; 179 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 180 | 181 | private Collection _prop6; 182 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 183 | } 184 | }"; 185 | var expectedOnInterface = new DiagnosticResult 186 | { 187 | Id = "OMCG01", 188 | Message = "Implementation of mapping method(s) can be generated.", 189 | Severity = DiagnosticSeverity.Hidden, 190 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 8, 27) } 191 | }; 192 | 193 | var expectedOnMethod = new DiagnosticResult 194 | { 195 | Id = "OMCG01", 196 | Message = "Implementation of mapping method(s) can be generated.", 197 | Severity = DiagnosticSeverity.Hidden, 198 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 23, 21) } 199 | }; 200 | 201 | VerifyCSharpDiagnostic(test, expectedOnInterface, expectedOnMethod); 202 | 203 | var fixtest = @" 204 | using ObjectMapper.Framework; 205 | using System.Collections.Generic; 206 | using System.Collections.ObjectModel; 207 | 208 | namespace TestClassLibrary 209 | { 210 | public class ClassA : IObjectMapper 211 | { 212 | public int? Prop1 { get; set; } 213 | public string Prop2 { get; set; } 214 | public decimal Prop3 { get; set; } 215 | 216 | private List _prop4; 217 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 218 | 219 | private LinkedList _prop5; 220 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 221 | 222 | private Collection _prop6; 223 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 224 | 225 | public void MapObject(ClassB target) 226 | { 227 | target.Prop1 = this.Prop1 ?? default(int); 228 | target.Prop2 = this.Prop2; 229 | target.Prop3 = this.Prop3; 230 | target.Prop4.CopyFrom(this.Prop4); 231 | target.Prop5.CopyFrom(this.Prop5); 232 | target.Prop6.CopyFrom(this.Prop6); 233 | } 234 | } 235 | 236 | public class ClassB 237 | { 238 | public int Prop1 { get; set; } 239 | public string Prop2 { get; set; } 240 | public decimal Prop3 { get; set; } 241 | 242 | private List _prop4; 243 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 244 | 245 | private LinkedList _prop5; 246 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 247 | 248 | private Collection _prop6; 249 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 250 | } 251 | }"; 252 | VerifyCSharpFix(test, fixtest); 253 | } 254 | #endregion 255 | 256 | #region TestIObjectMapperAdapterInterfaceWithoutDefinedMethods 257 | /// 258 | /// Tests the IObjectMapperAdapter interface without interface methods defined. 259 | /// 260 | [TestMethod] 261 | public void TestIObjectMapperAdapterInterfaceWithoutDefinedMethods() 262 | { 263 | var test = @" 264 | using ObjectMapper.Framework; 265 | using System.Collections.Generic; 266 | using System.Collections.ObjectModel; 267 | 268 | namespace TestClassLibrary 269 | { 270 | public class ClassA 271 | { 272 | public int? Prop1 { get; set; } 273 | public string Prop2 { get; set; } 274 | public decimal Prop3 { get; set; } 275 | 276 | private List _prop4; 277 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 278 | 279 | private LinkedList _prop5; 280 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 281 | 282 | private Collection _prop6; 283 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 284 | } 285 | 286 | public class ClassB 287 | { 288 | public int Prop1 { get; set; } 289 | public string Prop2 { get; set; } 290 | public decimal Prop3 { get; set; } 291 | 292 | private List _prop4; 293 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 294 | 295 | private LinkedList _prop5; 296 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 297 | 298 | private Collection _prop6; 299 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 300 | } 301 | 302 | public class MapperAdapter : IObjectMapperAdapter 303 | { 304 | 305 | } 306 | }"; 307 | var expected = new DiagnosticResult 308 | { 309 | Id = "OMCG01", 310 | Message = "Implementation of mapping method(s) can be generated.", 311 | Severity = DiagnosticSeverity.Hidden, 312 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 40, 34) } 313 | }; 314 | 315 | VerifyCSharpDiagnostic(test, expected); 316 | 317 | var fixtest = @" 318 | using ObjectMapper.Framework; 319 | using System.Collections.Generic; 320 | using System.Collections.ObjectModel; 321 | 322 | namespace TestClassLibrary 323 | { 324 | public class ClassA 325 | { 326 | public int? Prop1 { get; set; } 327 | public string Prop2 { get; set; } 328 | public decimal Prop3 { get; set; } 329 | 330 | private List _prop4; 331 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 332 | 333 | private LinkedList _prop5; 334 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 335 | 336 | private Collection _prop6; 337 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 338 | } 339 | 340 | public class ClassB 341 | { 342 | public int Prop1 { get; set; } 343 | public string Prop2 { get; set; } 344 | public decimal Prop3 { get; set; } 345 | 346 | private List _prop4; 347 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 348 | 349 | private LinkedList _prop5; 350 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 351 | 352 | private Collection _prop6; 353 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 354 | } 355 | 356 | public class MapperAdapter : IObjectMapperAdapter 357 | { 358 | public void MapObject(ClassA source, ClassB target) 359 | { 360 | target.Prop1 = source.Prop1 ?? default(int); 361 | target.Prop2 = source.Prop2; 362 | target.Prop3 = source.Prop3; 363 | target.Prop4.CopyFrom(source.Prop4); 364 | target.Prop5.CopyFrom(source.Prop5); 365 | target.Prop6.CopyFrom(source.Prop6); 366 | } 367 | 368 | public void MapObject(ClassB source, ClassA target) 369 | { 370 | target.Prop1 = source.Prop1; 371 | target.Prop2 = source.Prop2; 372 | target.Prop3 = source.Prop3; 373 | target.Prop4.CopyFrom(source.Prop4); 374 | target.Prop5.CopyFrom(source.Prop5); 375 | target.Prop6.CopyFrom(source.Prop6); 376 | } 377 | } 378 | }"; 379 | VerifyCSharpFix(test, fixtest); 380 | } 381 | #endregion 382 | 383 | #region TestIObjectMapperAdapterInterfaceWithDefinedMethods 384 | /// 385 | /// Tests the IObjectMapperAdapter interface with interface methods defined. 386 | /// 387 | [TestMethod] 388 | public void TestIObjectMapperAdapterInterfaceWithDefinedMethods() 389 | { 390 | var test = @" 391 | using ObjectMapper.Framework; 392 | using System.Collections.Generic; 393 | using System.Collections.ObjectModel; 394 | 395 | namespace TestClassLibrary 396 | { 397 | public class ClassA 398 | { 399 | public int? Prop1 { get; set; } 400 | public string Prop2 { get; set; } 401 | public decimal Prop3 { get; set; } 402 | 403 | private List _prop4; 404 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 405 | 406 | private LinkedList _prop5; 407 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 408 | 409 | private Collection _prop6; 410 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 411 | } 412 | 413 | public class ClassB 414 | { 415 | public int Prop1 { get; set; } 416 | public string Prop2 { get; set; } 417 | public decimal Prop3 { get; set; } 418 | 419 | private List _prop4; 420 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 421 | 422 | private LinkedList _prop5; 423 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 424 | 425 | private Collection _prop6; 426 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 427 | } 428 | 429 | public class MapperAdapter : IObjectMapperAdapter 430 | { 431 | public void MapObject(ClassA source, ClassB target) 432 | { 433 | } 434 | 435 | public void MapObject(ClassB source, ClassA target) 436 | { 437 | } 438 | } 439 | }"; 440 | var expectedOnInterface = new DiagnosticResult 441 | { 442 | Id = "OMCG01", 443 | Message = "Implementation of mapping method(s) can be generated.", 444 | Severity = DiagnosticSeverity.Hidden, 445 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 40, 34) } 446 | }; 447 | 448 | var expectedOnFirstMethod = new DiagnosticResult 449 | { 450 | Id = "OMCG01", 451 | Message = "Implementation of mapping method(s) can be generated.", 452 | Severity = DiagnosticSeverity.Hidden, 453 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 42, 21) } 454 | }; 455 | 456 | var expectedOnSecondMethod = new DiagnosticResult 457 | { 458 | Id = "OMCG01", 459 | Message = "Implementation of mapping method(s) can be generated.", 460 | Severity = DiagnosticSeverity.Hidden, 461 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 46, 21) } 462 | }; 463 | 464 | VerifyCSharpDiagnostic(test, expectedOnInterface, expectedOnFirstMethod, expectedOnSecondMethod); 465 | 466 | var fixtest = @" 467 | using ObjectMapper.Framework; 468 | using System.Collections.Generic; 469 | using System.Collections.ObjectModel; 470 | 471 | namespace TestClassLibrary 472 | { 473 | public class ClassA 474 | { 475 | public int? Prop1 { get; set; } 476 | public string Prop2 { get; set; } 477 | public decimal Prop3 { get; set; } 478 | 479 | private List _prop4; 480 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 481 | 482 | private LinkedList _prop5; 483 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 484 | 485 | private Collection _prop6; 486 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 487 | } 488 | 489 | public class ClassB 490 | { 491 | public int Prop1 { get; set; } 492 | public string Prop2 { get; set; } 493 | public decimal Prop3 { get; set; } 494 | 495 | private List _prop4; 496 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 497 | 498 | private LinkedList _prop5; 499 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 500 | 501 | private Collection _prop6; 502 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 503 | } 504 | 505 | public class MapperAdapter : IObjectMapperAdapter 506 | { 507 | public void MapObject(ClassA source, ClassB target) 508 | { 509 | target.Prop1 = source.Prop1 ?? default(int); 510 | target.Prop2 = source.Prop2; 511 | target.Prop3 = source.Prop3; 512 | target.Prop4.CopyFrom(source.Prop4); 513 | target.Prop5.CopyFrom(source.Prop5); 514 | target.Prop6.CopyFrom(source.Prop6); 515 | } 516 | 517 | public void MapObject(ClassB source, ClassA target) 518 | { 519 | target.Prop1 = source.Prop1; 520 | target.Prop2 = source.Prop2; 521 | target.Prop3 = source.Prop3; 522 | target.Prop4.CopyFrom(source.Prop4); 523 | target.Prop5.CopyFrom(source.Prop5); 524 | target.Prop6.CopyFrom(source.Prop6); 525 | } 526 | } 527 | }"; 528 | VerifyCSharpFix(test, fixtest); 529 | } 530 | #endregion 531 | 532 | #region TestObjectMapperMethodAttribute 533 | /// 534 | /// Tests ObjectMapperMethodAttribute. 535 | /// 536 | [TestMethod] 537 | public void TestObjectMapperMethodAttribute() 538 | { 539 | var test = @" 540 | using ObjectMapper.Framework; 541 | using System.Collections.Generic; 542 | using System.Collections.ObjectModel; 543 | 544 | namespace TestClassLibrary 545 | { 546 | public class ClassA 547 | { 548 | public int? Prop1 { get; set; } 549 | public string Prop2 { get; set; } 550 | public decimal Prop3 { get; set; } 551 | 552 | private List _prop4; 553 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 554 | 555 | private LinkedList _prop5; 556 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 557 | 558 | private Collection _prop6; 559 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 560 | } 561 | 562 | public class ClassB 563 | { 564 | public int Prop1 { get; set; } 565 | public string Prop2 { get; set; } 566 | public decimal Prop3 { get; set; } 567 | 568 | private List _prop4; 569 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 570 | 571 | private LinkedList _prop5; 572 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 573 | 574 | private Collection _prop6; 575 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 576 | } 577 | 578 | public static class MappingClass 579 | { 580 | [ObjectMapperMethod] 581 | public void MapObject(ClassA source, ClassB target) 582 | { 583 | } 584 | } 585 | }"; 586 | var expected = new DiagnosticResult 587 | { 588 | Id = "OMCG01", 589 | Message = "Implementation of mapping method(s) can be generated.", 590 | Severity = DiagnosticSeverity.Hidden, 591 | Locations = new[] { new DiagnosticResultLocation("Test0.cs", 43, 21) } 592 | }; 593 | 594 | VerifyCSharpDiagnostic(test, expected); 595 | 596 | var fixtest = @" 597 | using ObjectMapper.Framework; 598 | using System.Collections.Generic; 599 | using System.Collections.ObjectModel; 600 | 601 | namespace TestClassLibrary 602 | { 603 | public class ClassA 604 | { 605 | public int? Prop1 { get; set; } 606 | public string Prop2 { get; set; } 607 | public decimal Prop3 { get; set; } 608 | 609 | private List _prop4; 610 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 611 | 612 | private LinkedList _prop5; 613 | public LinkedList Prop5 { get { if (_prop5 == null) _prop5 = new LinkedList(); return _prop5; } } 614 | 615 | private Collection _prop6; 616 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 617 | } 618 | 619 | public class ClassB 620 | { 621 | public int Prop1 { get; set; } 622 | public string Prop2 { get; set; } 623 | public decimal Prop3 { get; set; } 624 | 625 | private List _prop4; 626 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 627 | 628 | private LinkedList _prop5; 629 | public LinkedList Prop5 { get { if (_prop4 == null) _prop5 = new LinkedList(); return _prop5; } } 630 | 631 | private Collection _prop6; 632 | public Collection Prop6 { get { if (_prop6 == null) _prop6 = new Collection(); return _prop6; } } 633 | } 634 | 635 | public static class MappingClass 636 | { 637 | [ObjectMapperMethod] 638 | public void MapObject(ClassA source, ClassB target) 639 | { 640 | target.Prop1 = source.Prop1 ?? default(int); 641 | target.Prop2 = source.Prop2; 642 | target.Prop3 = source.Prop3; 643 | target.Prop4.CopyFrom(source.Prop4); 644 | target.Prop5.CopyFrom(source.Prop5); 645 | target.Prop6.CopyFrom(source.Prop6); 646 | } 647 | } 648 | }"; 649 | VerifyCSharpFix(test, fixtest); 650 | } 651 | #endregion 652 | 653 | #region Setup 654 | protected override CodeFixProvider GetCSharpCodeFixProvider() 655 | { 656 | return new GenerateImplementationCodeGenerator(); 657 | } 658 | 659 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 660 | { 661 | return new GenerateImplementationCodeAnalyzer(); 662 | } 663 | #endregion 664 | } 665 | } -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/Helpers/CodeFixVerifier.Helper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeActions; 3 | using Microsoft.CodeAnalysis.Formatting; 4 | using Microsoft.CodeAnalysis.Simplification; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | 9 | namespace TestHelper 10 | { 11 | /// 12 | /// Diagnostic Producer class with extra methods dealing with applying codefixes 13 | /// All methods are static 14 | /// 15 | public abstract partial class CodeFixVerifier : DiagnosticVerifier 16 | { 17 | /// 18 | /// Apply the inputted CodeAction to the inputted document. 19 | /// Meant to be used to apply codefixes. 20 | /// 21 | /// The Document to apply the fix on 22 | /// A CodeAction that will be applied to the Document. 23 | /// A Document with the changes from the CodeAction 24 | private static Document ApplyFix(Document document, CodeAction codeAction) 25 | { 26 | var operations = codeAction.GetOperationsAsync(CancellationToken.None).Result; 27 | var solution = operations.OfType().Single().ChangedSolution; 28 | return solution.GetDocument(document.Id); 29 | } 30 | 31 | /// 32 | /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection. 33 | /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row, 34 | /// this method may not necessarily return the new one. 35 | /// 36 | /// The Diagnostics that existed in the code before the CodeFix was applied 37 | /// The Diagnostics that exist in the code after the CodeFix was applied 38 | /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied 39 | private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) 40 | { 41 | var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); 42 | var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); 43 | 44 | int oldIndex = 0; 45 | int newIndex = 0; 46 | 47 | while (newIndex < newArray.Length) 48 | { 49 | if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id) 50 | { 51 | ++oldIndex; 52 | ++newIndex; 53 | } 54 | else 55 | { 56 | yield return newArray[newIndex++]; 57 | } 58 | } 59 | } 60 | 61 | /// 62 | /// Get the existing compiler diagnostics on the inputted document. 63 | /// 64 | /// The Document to run the compiler diagnostic analyzers on 65 | /// The compiler diagnostics that were found in the code 66 | private static IEnumerable GetCompilerDiagnostics(Document document) 67 | { 68 | return document.GetSemanticModelAsync().Result.GetDiagnostics(); 69 | } 70 | 71 | /// 72 | /// Given a document, turn it into a string based on the syntax root 73 | /// 74 | /// The Document to be converted to a string 75 | /// A string containing the syntax of the Document after formatting 76 | private static string GetStringFromDocument(Document document) 77 | { 78 | var simplifiedDoc = Simplifier.ReduceAsync(document, Simplifier.Annotation).Result; 79 | var root = simplifiedDoc.GetSyntaxRootAsync().Result; 80 | root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace); 81 | return root.GetText().ToString(); 82 | } 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/Helpers/DiagnosticResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System; 3 | 4 | namespace TestHelper 5 | { 6 | /// 7 | /// Location where the diagnostic appears, as determined by path, line number, and column number. 8 | /// 9 | public struct DiagnosticResultLocation 10 | { 11 | public DiagnosticResultLocation(string path, int line, int column) 12 | { 13 | if (line < -1) 14 | { 15 | throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); 16 | } 17 | 18 | if (column < -1) 19 | { 20 | throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); 21 | } 22 | 23 | this.Path = path; 24 | this.Line = line; 25 | this.Column = column; 26 | } 27 | 28 | public string Path { get; } 29 | public int Line { get; } 30 | public int Column { get; } 31 | } 32 | 33 | /// 34 | /// Struct that stores information about a Diagnostic appearing in a source 35 | /// 36 | public struct DiagnosticResult 37 | { 38 | private DiagnosticResultLocation[] locations; 39 | 40 | public DiagnosticResultLocation[] Locations 41 | { 42 | get 43 | { 44 | if (this.locations == null) 45 | { 46 | this.locations = new DiagnosticResultLocation[] { }; 47 | } 48 | return this.locations; 49 | } 50 | 51 | set 52 | { 53 | this.locations = value; 54 | } 55 | } 56 | 57 | public DiagnosticSeverity Severity { get; set; } 58 | 59 | public string Id { get; set; } 60 | 61 | public string Message { get; set; } 62 | 63 | public string Path 64 | { 65 | get 66 | { 67 | return this.Locations.Length > 0 ? this.Locations[0].Path : ""; 68 | } 69 | } 70 | 71 | public int Line 72 | { 73 | get 74 | { 75 | return this.Locations.Length > 0 ? this.Locations[0].Line : -1; 76 | } 77 | } 78 | 79 | public int Column 80 | { 81 | get 82 | { 83 | return this.Locations.Length > 0 ? this.Locations[0].Column : -1; 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/Helpers/DiagnosticVerifier.Helper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.CodeAnalysis.Text; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Collections.Immutable; 8 | using System.IO; 9 | using System.Linq; 10 | 11 | namespace TestHelper 12 | { 13 | /// 14 | /// Class for turning strings into documents and getting the diagnostics on them 15 | /// All methods are static 16 | /// 17 | public abstract partial class DiagnosticVerifier 18 | { 19 | private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); 20 | private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); 21 | private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); 22 | private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); 23 | private static readonly MetadataReference ObjectMapperFrameworkReference = MetadataReference.CreateFromFile(typeof(ObjectMapper.Framework.IObjectMapper<>).Assembly.Location); 24 | private static readonly MetadataReference SystemReference = MetadataReference.CreateFromFile(typeof(LinkedList<>).Assembly.Location); 25 | private static readonly MetadataReference SystemRuntimeFacadeReference = MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.dll")); 26 | private static readonly MetadataReference SystemCollectionsFacadeReference = MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Collections.dll")); 27 | 28 | internal static string DefaultFilePathPrefix = "Test"; 29 | internal static string CSharpDefaultFileExt = "cs"; 30 | internal static string VisualBasicDefaultExt = "vb"; 31 | internal static string TestProjectName = "TestProject"; 32 | 33 | #region Get Diagnostics 34 | 35 | /// 36 | /// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string after converting it to a document. 37 | /// 38 | /// Classes in the form of strings 39 | /// The language the source classes are in 40 | /// The analyzer to be run on the sources 41 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location 42 | private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) 43 | { 44 | return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); 45 | } 46 | 47 | /// 48 | /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. 49 | /// The returned diagnostics are then ordered by location in the source document. 50 | /// 51 | /// The analyzer to run on the documents 52 | /// The Documents that the analyzer will be run on 53 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location 54 | protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) 55 | { 56 | var projects = new HashSet(); 57 | foreach (var document in documents) 58 | { 59 | projects.Add(document.Project); 60 | } 61 | 62 | var diagnostics = new List(); 63 | foreach (var project in projects) 64 | { 65 | var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); 66 | var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; 67 | foreach (var diag in diags) 68 | { 69 | if (diag.Location == Location.None || diag.Location.IsInMetadata) 70 | { 71 | diagnostics.Add(diag); 72 | } 73 | else 74 | { 75 | for (int i = 0; i < documents.Length; i++) 76 | { 77 | var document = documents[i]; 78 | var tree = document.GetSyntaxTreeAsync().Result; 79 | if (tree == diag.Location.SourceTree) 80 | { 81 | diagnostics.Add(diag); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | var results = SortDiagnostics(diagnostics); 89 | diagnostics.Clear(); 90 | return results; 91 | } 92 | 93 | /// 94 | /// Sort diagnostics by location in source document 95 | /// 96 | /// The list of Diagnostics to be sorted 97 | /// An IEnumerable containing the Diagnostics in order of Location 98 | private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) 99 | { 100 | return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); 101 | } 102 | 103 | #endregion 104 | 105 | #region Set up compilation and documents 106 | /// 107 | /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. 108 | /// 109 | /// Classes in the form of strings 110 | /// The language the source code is in 111 | /// A Tuple containing the Documents produced from the sources and their TextSpans if relevant 112 | private static Document[] GetDocuments(string[] sources, string language) 113 | { 114 | if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) 115 | { 116 | throw new ArgumentException("Unsupported Language"); 117 | } 118 | 119 | var project = CreateProject(sources, language); 120 | var documents = project.Documents.ToArray(); 121 | 122 | if (sources.Length != documents.Length) 123 | { 124 | throw new SystemException("Amount of sources did not match amount of Documents created"); 125 | } 126 | 127 | return documents; 128 | } 129 | 130 | /// 131 | /// Create a Document from a string through creating a project that contains it. 132 | /// 133 | /// Classes in the form of a string 134 | /// The language the source code is in 135 | /// A Document created from the source string 136 | protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) 137 | { 138 | return CreateProject(new[] { source }, language).Documents.First(); 139 | } 140 | 141 | /// 142 | /// Create a project using the inputted strings as sources. 143 | /// 144 | /// Classes in the form of strings 145 | /// The language the source code is in 146 | /// A Project created out of the Documents created from the source strings 147 | private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) 148 | { 149 | string fileNamePrefix = DefaultFilePathPrefix; 150 | string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; 151 | 152 | var projectId = ProjectId.CreateNewId(debugName: TestProjectName); 153 | 154 | var solution = new AdhocWorkspace() 155 | .CurrentSolution 156 | .AddProject(projectId, TestProjectName, TestProjectName, language) 157 | .AddMetadataReference(projectId, CorlibReference) 158 | .AddMetadataReference(projectId, SystemCoreReference) 159 | .AddMetadataReference(projectId, CSharpSymbolsReference) 160 | .AddMetadataReference(projectId, CodeAnalysisReference) 161 | .AddMetadataReference(projectId, SystemReference) 162 | .AddMetadataReference(projectId, ObjectMapperFrameworkReference) 163 | .AddMetadataReference(projectId, SystemRuntimeFacadeReference) 164 | .AddMetadataReference(projectId, SystemCollectionsFacadeReference); 165 | 166 | int count = 0; 167 | foreach (var source in sources) 168 | { 169 | var newFileName = fileNamePrefix + count + "." + fileExt; 170 | var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); 171 | solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); 172 | count++; 173 | } 174 | return solution.GetProject(projectId); 175 | } 176 | #endregion 177 | } 178 | } 179 | 180 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/ObjectMapper.GenerateImplementationAnalyzer.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {FCC88640-43A1-4FEE-AE9B-44674C6376F7} 9 | Library 10 | Properties 11 | ObjectMapper.GenerateImplementationAnalyzer.Test 12 | ObjectMapper.GenerateImplementationAnalyzer.Test 13 | v4.5.2 14 | 512 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | 38 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.dll 39 | True 40 | 41 | 42 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.dll 43 | True 44 | 45 | 46 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll 47 | True 48 | 49 | 50 | ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.dll 51 | True 52 | 53 | 54 | ..\..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.0.0\lib\net45\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll 55 | True 56 | 57 | 58 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll 59 | True 60 | 61 | 62 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll 63 | True 64 | 65 | 66 | 67 | ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 68 | True 69 | 70 | 71 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll 72 | True 73 | 74 | 75 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll 76 | True 77 | 78 | 79 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll 80 | True 81 | 82 | 83 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll 84 | True 85 | 86 | 87 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll 88 | True 89 | 90 | 91 | 92 | ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll 93 | True 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | false 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | {8220c878-68e1-4307-a908-66fbea9d8b56} 120 | ObjectMapper.Framework 121 | 122 | 123 | {492F3804-A43F-4E82-B649-8AD0D967A830} 124 | ObjectMapper.GenerateImplementationAnalyzer 125 | 126 | 127 | 128 | 129 | 130 | 131 | 138 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("ObjectMapper.GenerateImplementationAnalyzer.Test")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("ObjectMapper.GenerateImplementationAnalyzer.Test")] 12 | [assembly: AssemblyCopyright("Copyright © 2016")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | [assembly: ComVisible(false)] 17 | 18 | [assembly: AssemblyVersion("1.2.2.0")] 19 | [assembly: AssemblyFileVersion("1.2.2.0")] -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/Verifiers/CodeFixVerifier.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeActions; 3 | using Microsoft.CodeAnalysis.CodeFixes; 4 | using Microsoft.CodeAnalysis.Diagnostics; 5 | using Microsoft.CodeAnalysis.Formatting; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading; 10 | 11 | namespace TestHelper 12 | { 13 | /// 14 | /// Superclass of all Unit tests made for diagnostics with codefixes. 15 | /// Contains methods used to verify correctness of codefixes 16 | /// 17 | public abstract partial class CodeFixVerifier : DiagnosticVerifier 18 | { 19 | /// 20 | /// Returns the codefix being tested (C#) - to be implemented in non-abstract class 21 | /// 22 | /// The CodeFixProvider to be used for CSharp code 23 | protected virtual CodeFixProvider GetCSharpCodeFixProvider() 24 | { 25 | return null; 26 | } 27 | 28 | /// 29 | /// Returns the codefix being tested (VB) - to be implemented in non-abstract class 30 | /// 31 | /// The CodeFixProvider to be used for VisualBasic code 32 | protected virtual CodeFixProvider GetBasicCodeFixProvider() 33 | { 34 | return null; 35 | } 36 | 37 | /// 38 | /// Called to test a C# codefix when applied on the inputted string as a source 39 | /// 40 | /// A class in the form of a string before the CodeFix was applied to it 41 | /// A class in the form of a string after the CodeFix was applied to it 42 | /// Index determining which codefix to apply if there are multiple 43 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied 44 | protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) 45 | { 46 | VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); 47 | } 48 | 49 | /// 50 | /// Called to test a VB codefix when applied on the inputted string as a source 51 | /// 52 | /// A class in the form of a string before the CodeFix was applied to it 53 | /// A class in the form of a string after the CodeFix was applied to it 54 | /// Index determining which codefix to apply if there are multiple 55 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied 56 | protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) 57 | { 58 | VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); 59 | } 60 | 61 | /// 62 | /// General verifier for codefixes. 63 | /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. 64 | /// Then gets the string after the codefix is applied and compares it with the expected result. 65 | /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. 66 | /// 67 | /// The language the source code is in 68 | /// The analyzer to be applied to the source code 69 | /// The codefix to be applied to the code wherever the relevant Diagnostic is found 70 | /// A class in the form of a string before the CodeFix was applied to it 71 | /// A class in the form of a string after the CodeFix was applied to it 72 | /// Index determining which codefix to apply if there are multiple 73 | /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied 74 | private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) 75 | { 76 | var document = CreateDocument(oldSource, language); 77 | var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); 78 | var compilerDiagnostics = GetCompilerDiagnostics(document); 79 | var attempts = analyzerDiagnostics.Length; 80 | 81 | for (int i = 0; i < attempts; ++i) 82 | { 83 | var actions = new List(); 84 | var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); 85 | codeFixProvider.RegisterCodeFixesAsync(context).Wait(); 86 | 87 | if (!actions.Any()) 88 | { 89 | break; 90 | } 91 | 92 | if (codeFixIndex != null) 93 | { 94 | document = ApplyFix(document, actions.ElementAt((int)codeFixIndex)); 95 | break; 96 | } 97 | 98 | document = ApplyFix(document, actions.ElementAt(0)); 99 | analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); 100 | 101 | var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); 102 | 103 | //check if applying the code fix introduced any new compiler diagnostics 104 | if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) 105 | { 106 | // Format and get the compiler diagnostics again so that the locations make sense in the output 107 | document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); 108 | newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); 109 | 110 | Assert.IsTrue(false, 111 | string.Format("Fix introduced new compiler diagnostics:\r\n{0}\r\n\r\nNew document:\r\n{1}\r\n", 112 | string.Join("\r\n", newCompilerDiagnostics.Select(d => d.ToString())), 113 | document.GetSyntaxRootAsync().Result.ToFullString())); 114 | } 115 | 116 | //check if there are analyzer diagnostics left after the code fix 117 | if (!analyzerDiagnostics.Any()) 118 | { 119 | break; 120 | } 121 | } 122 | 123 | //after applying all of the code fixes, compare the resulting string to the inputted one 124 | var actual = GetStringFromDocument(document); 125 | Assert.AreEqual(newSource, actual); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/Verifiers/DiagnosticVerifier.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace TestHelper 10 | { 11 | /// 12 | /// Superclass of all Unit Tests for DiagnosticAnalyzers 13 | /// 14 | public abstract partial class DiagnosticVerifier 15 | { 16 | #region To be implemented by Test classes 17 | /// 18 | /// Get the CSharp analyzer being tested - to be implemented in non-abstract class 19 | /// 20 | protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 21 | { 22 | return null; 23 | } 24 | 25 | /// 26 | /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class 27 | /// 28 | protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() 29 | { 30 | return null; 31 | } 32 | #endregion 33 | 34 | #region Verifier wrappers 35 | 36 | /// 37 | /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source 38 | /// Note: input a DiagnosticResult for each Diagnostic expected 39 | /// 40 | /// A class in the form of a string to run the analyzer on 41 | /// DiagnosticResults that should appear after the analyzer is run on the source 42 | protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) 43 | { 44 | VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); 45 | } 46 | 47 | /// 48 | /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source 49 | /// Note: input a DiagnosticResult for each Diagnostic expected 50 | /// 51 | /// A class in the form of a string to run the analyzer on 52 | /// DiagnosticResults that should appear after the analyzer is run on the source 53 | protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) 54 | { 55 | VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); 56 | } 57 | 58 | /// 59 | /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source 60 | /// Note: input a DiagnosticResult for each Diagnostic expected 61 | /// 62 | /// An array of strings to create source documents from to run the analyzers on 63 | /// DiagnosticResults that should appear after the analyzer is run on the sources 64 | protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) 65 | { 66 | VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); 67 | } 68 | 69 | /// 70 | /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source 71 | /// Note: input a DiagnosticResult for each Diagnostic expected 72 | /// 73 | /// An array of strings to create source documents from to run the analyzers on 74 | /// DiagnosticResults that should appear after the analyzer is run on the sources 75 | protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) 76 | { 77 | VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); 78 | } 79 | 80 | /// 81 | /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, 82 | /// then verifies each of them. 83 | /// 84 | /// An array of strings to create source documents from to run the analyzers on 85 | /// The language of the classes represented by the source strings 86 | /// The analyzer to be run on the source code 87 | /// DiagnosticResults that should appear after the analyzer is run on the sources 88 | private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) 89 | { 90 | var diagnostics = GetSortedDiagnostics(sources, language, analyzer); 91 | VerifyDiagnosticResults(diagnostics, analyzer, expected); 92 | } 93 | 94 | #endregion 95 | 96 | #region Actual comparisons and verifications 97 | /// 98 | /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. 99 | /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. 100 | /// 101 | /// The Diagnostics found by the compiler after running the analyzer on the source code 102 | /// The analyzer that was being run on the sources 103 | /// Diagnostic Results that should have appeared in the code 104 | private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) 105 | { 106 | int expectedCount = expectedResults.Count(); 107 | int actualCount = actualResults.Count(); 108 | 109 | if (expectedCount != actualCount) 110 | { 111 | string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; 112 | 113 | Assert.IsTrue(false, 114 | string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); 115 | } 116 | 117 | for (int i = 0; i < expectedResults.Length; i++) 118 | { 119 | var actual = actualResults.ElementAt(i); 120 | var expected = expectedResults[i]; 121 | 122 | if (expected.Line == -1 && expected.Column == -1) 123 | { 124 | if (actual.Location != Location.None) 125 | { 126 | Assert.IsTrue(false, 127 | string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", 128 | FormatDiagnostics(analyzer, actual))); 129 | } 130 | } 131 | else 132 | { 133 | VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); 134 | var additionalLocations = actual.AdditionalLocations.ToArray(); 135 | 136 | if (additionalLocations.Length != expected.Locations.Length - 1) 137 | { 138 | Assert.IsTrue(false, 139 | string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", 140 | expected.Locations.Length - 1, additionalLocations.Length, 141 | FormatDiagnostics(analyzer, actual))); 142 | } 143 | 144 | for (int j = 0; j < additionalLocations.Length; ++j) 145 | { 146 | VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); 147 | } 148 | } 149 | 150 | if (actual.Id != expected.Id) 151 | { 152 | Assert.IsTrue(false, 153 | string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 154 | expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); 155 | } 156 | 157 | if (actual.Severity != expected.Severity) 158 | { 159 | Assert.IsTrue(false, 160 | string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 161 | expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); 162 | } 163 | 164 | if (actual.GetMessage() != expected.Message) 165 | { 166 | Assert.IsTrue(false, 167 | string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 168 | expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); 169 | } 170 | } 171 | } 172 | 173 | /// 174 | /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. 175 | /// 176 | /// The analyzer that was being run on the sources 177 | /// The diagnostic that was found in the code 178 | /// The Location of the Diagnostic found in the code 179 | /// The DiagnosticResultLocation that should have been found 180 | private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) 181 | { 182 | var actualSpan = actual.GetLineSpan(); 183 | 184 | Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), 185 | string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 186 | expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); 187 | 188 | var actualLinePosition = actualSpan.StartLinePosition; 189 | 190 | // Only check line position if there is an actual line in the real diagnostic 191 | if (actualLinePosition.Line > 0) 192 | { 193 | if (actualLinePosition.Line + 1 != expected.Line) 194 | { 195 | Assert.IsTrue(false, 196 | string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 197 | expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); 198 | } 199 | } 200 | 201 | // Only check column position if there is an actual column position in the real diagnostic 202 | if (actualLinePosition.Character > 0) 203 | { 204 | if (actualLinePosition.Character + 1 != expected.Column) 205 | { 206 | Assert.IsTrue(false, 207 | string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 208 | expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); 209 | } 210 | } 211 | } 212 | #endregion 213 | 214 | #region Formatting Diagnostics 215 | /// 216 | /// Helper method to format a Diagnostic into an easily readable string 217 | /// 218 | /// The analyzer that this verifier tests 219 | /// The Diagnostics to be formatted 220 | /// The Diagnostics formatted as a string 221 | private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) 222 | { 223 | var builder = new StringBuilder(); 224 | for (int i = 0; i < diagnostics.Length; ++i) 225 | { 226 | builder.AppendLine("// " + diagnostics[i].ToString()); 227 | 228 | var analyzerType = analyzer.GetType(); 229 | var rules = analyzer.SupportedDiagnostics; 230 | 231 | foreach (var rule in rules) 232 | { 233 | if (rule != null && rule.Id == diagnostics[i].Id) 234 | { 235 | var location = diagnostics[i].Location; 236 | if (location == Location.None) 237 | { 238 | builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); 239 | } 240 | else 241 | { 242 | Assert.IsTrue(location.IsInSource, 243 | $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); 244 | 245 | string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; 246 | var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; 247 | 248 | builder.AppendFormat("{0}({1}, {2}, {3}.{4})", 249 | resultMethodName, 250 | linePosition.Line + 1, 251 | linePosition.Character + 1, 252 | analyzerType.Name, 253 | rule.Id); 254 | } 255 | 256 | if (i != diagnostics.Length - 1) 257 | { 258 | builder.Append(','); 259 | } 260 | 261 | builder.AppendLine(); 262 | break; 263 | } 264 | } 265 | } 266 | return builder.ToString(); 267 | } 268 | #endregion 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Vsix/ObjectMapper.GenerateImplementationAnalyzer.Vsix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | Debug 10 | AnyCPU 11 | 2.0 12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 13 | {2AF839D8-C2E5-45CC-BA8E-04C20DD75B96} 14 | Library 15 | Properties 16 | ObjectMapper.GenerateImplementationAnalyzer.Vsix 17 | ObjectMapper.GenerateImplementationAnalyzer.Vsix 18 | v4.5.2 19 | false 20 | false 21 | false 22 | false 23 | false 24 | false 25 | Roslyn 26 | 27 | 28 | true 29 | full 30 | false 31 | bin\Debug\ 32 | DEBUG;TRACE 33 | prompt 34 | 4 35 | 36 | 37 | pdbonly 38 | true 39 | bin\Release\ 40 | TRACE 41 | prompt 42 | 4 43 | 44 | 45 | Program 46 | $(DevEnvDir)devenv.exe 47 | /rootsuffix Roslyn 48 | 49 | 50 | 51 | Designer 52 | 53 | 54 | 55 | 56 | {492F3804-A43F-4E82-B649-8AD0D967A830} 57 | ObjectMapper.GenerateImplementationAnalyzer 58 | BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b 59 | DebugSymbolsProjectOutputGroup%3b 60 | 61 | 62 | 63 | 64 | 71 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | ObjectMapper.GenerateImplementationAnalyzer 6 | This is a sample diagnostic extension for the .NET Compiler Platform ("Roslyn"). 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/AttributeUsageCodeAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.CodeAnalysis.Diagnostics; 5 | using ObjectMapper.GenerateImplementationAnalyzer.Utilities; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Collections.Immutable; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace ObjectMapper.GenerateImplementationAnalyzer 14 | { 15 | /// 16 | /// Analyzer class for analyzing and reporting mapping attribute for mapping methods missuse. 17 | /// 18 | /// 19 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 20 | public class AttributeUsageCodeAnalyzer : DiagnosticAnalyzer 21 | { 22 | public const string DiagnosticId = "OMAU01"; 23 | 24 | private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AttributeUsageAnalyzerTitle), Resources.ResourceManager, typeof(Resources)); 25 | private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AttributeUsageAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); 26 | private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AttributeUsageAnalyzerDescription), Resources.ResourceManager, typeof(Resources)); 27 | private const string Category = "Attribute usage"; 28 | 29 | private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); 30 | 31 | /// 32 | /// Returns a set of descriptors for the diagnostics that this analyzer is capable of producing. 33 | /// 34 | public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } 35 | 36 | /// 37 | /// Called once at session start to register actions in the analysis context. 38 | /// 39 | /// 40 | public override void Initialize(AnalysisContext context) 41 | { 42 | if (context == null) return; 43 | 44 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.Attribute); 45 | } 46 | 47 | /// 48 | /// Analyzes the node. 49 | /// 50 | /// The context. 51 | private void AnalyzeNode(SyntaxNodeAnalysisContext context) 52 | { 53 | var attributeNode = context.Node as AttributeSyntax; 54 | SimpleNameSyntax sns = (attributeNode.Name as SimpleNameSyntax) ?? (attributeNode.Name as QualifiedNameSyntax).Right; 55 | var className = sns?.Identifier.Text; 56 | if (className != "ObjectMapperMethod" && className != "ObjectMapperMethodAttribute") 57 | { 58 | return; 59 | } 60 | 61 | // symbol is ctor call 62 | var symbol = context.SemanticModel.GetSymbolInfo(attributeNode).Symbol.ContainingType; 63 | if (symbol == null) 64 | { 65 | return; 66 | } 67 | 68 | var fullSymbolName = symbol.ToDisplayString(); 69 | if (fullSymbolName != "ObjectMapper.Framework.ObjectMapperMethodAttribute") 70 | { 71 | return; 72 | } 73 | 74 | if (!FrameworkHelpers.IsObjectMapperFrameworkAssembly(symbol.ContainingAssembly)) 75 | { 76 | return; 77 | } 78 | 79 | var methodSyntax = attributeNode.Ancestors().OfType().FirstOrDefault(); 80 | if (methodSyntax == null) 81 | { 82 | // missused attribute - compiler will take care of that 83 | return; 84 | } 85 | 86 | var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax); 87 | if (methodSymbol.ReturnsVoid && methodSymbol.Parameters.Length == 2) 88 | { 89 | // correct use 90 | return; 91 | } 92 | 93 | var diagnostic = Diagnostic.Create(Rule, attributeNode.GetLocation()); 94 | context.ReportDiagnostic(diagnostic); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/Diagnostic.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | SimpleObjectMapper 5 | 1.2.1 6 | Object Mapper 7 | Nejc Skofic 8 | Nejc Skofic 9 | https://github.com/nejcskofic/ObjectMapper/blob/master/LICENSE 10 | https://github.com/nejcskofic/ObjectMapper 11 | 12 | false 13 | Simple object mapping library with included analyser and code generator for generating mapping implementation. 14 | Added mapping attribute used by generator to generate mapping code. 15 | Copyright 2016 16 | object, mapper, analyzers, code, generator 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/GenerateImplementationCodeAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Threading; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CSharp; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | using Microsoft.CodeAnalysis.Diagnostics; 10 | using ObjectMapper.GenerateImplementationAnalyzer.Utilities; 11 | 12 | namespace ObjectMapper.GenerateImplementationAnalyzer 13 | { 14 | /// 15 | /// Analyzer class for registering code generator for object mapper interfaces. 16 | /// 17 | /// 18 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 19 | public class GenerateImplementationCodeAnalyzer : DiagnosticAnalyzer 20 | { 21 | public const string DiagnosticId = "OMCG01"; 22 | 23 | private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.GenerateImplementationAnalyzerTitle), Resources.ResourceManager, typeof(Resources)); 24 | private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.GenerateImplementationAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); 25 | private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.GenerateImplementationAnalyzerDescription), Resources.ResourceManager, typeof(Resources)); 26 | private const string Category = "Code generation"; 27 | 28 | private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Hidden, isEnabledByDefault: true, description: Description); 29 | 30 | /// 31 | /// Returns a set of descriptors for the diagnostics that this analyzer is capable of producing. 32 | /// 33 | public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } 34 | 35 | /// 36 | /// Called once at session start to register actions in the analysis context. 37 | /// 38 | /// 39 | public override void Initialize(AnalysisContext context) 40 | { 41 | if (context == null) return; 42 | 43 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.SimpleBaseType, SyntaxKind.MethodDeclaration); 44 | } 45 | 46 | /// 47 | /// Analyzes the node. 48 | /// 49 | /// The context. 50 | private void AnalyzeNode(SyntaxNodeAnalysisContext context) 51 | { 52 | var baseNode = context.Node as SimpleBaseTypeSyntax; 53 | if (baseNode != null) 54 | { 55 | CheckForObjectMapperBaseInterface(baseNode, context); 56 | return; 57 | } 58 | 59 | var methodNode = context.Node as MethodDeclarationSyntax; 60 | if (methodNode != null && CheckForObjectMapperMethod(methodNode, context)) 61 | { 62 | return; 63 | } 64 | if (methodNode != null && CheckForMethodWithObjectMapperAttribute(methodNode, context)) 65 | { 66 | return; 67 | } 68 | } 69 | 70 | /// 71 | /// Checks if symbol under carret is object mapper interface. 72 | /// 73 | /// The node. 74 | /// The context. 75 | private static void CheckForObjectMapperBaseInterface(SimpleBaseTypeSyntax node, SyntaxNodeAnalysisContext context) 76 | { 77 | SimpleNameSyntax sns = (node.Type as SimpleNameSyntax) ?? (node.Type as QualifiedNameSyntax).Right; 78 | var className = sns?.Identifier.Text; 79 | if (className != "IObjectMapper" && className != "IObjectMapperAdapter") 80 | { 81 | return; 82 | } 83 | var symbol = context.SemanticModel.GetSymbolInfo(sns).Symbol as INamedTypeSymbol; 84 | if (symbol == null || symbol.TypeKind != TypeKind.Interface || !symbol.IsGenericType) 85 | { 86 | return; 87 | } 88 | 89 | var fullSymbolName = symbol.OriginalDefinition.ToDisplayString(); 90 | if (fullSymbolName != "ObjectMapper.Framework.IObjectMapper" && fullSymbolName != "ObjectMapper.Framework.IObjectMapperAdapter") 91 | { 92 | return; 93 | } 94 | 95 | if (!FrameworkHelpers.IsObjectMapperFrameworkAssembly(symbol.OriginalDefinition.ContainingAssembly)) 96 | { 97 | return; 98 | } 99 | 100 | var diagnostic = Diagnostic.Create(Rule, node.GetLocation()); 101 | context.ReportDiagnostic(diagnostic); 102 | } 103 | 104 | /// 105 | /// Checks if method is implementation of object mapper interface. 106 | /// 107 | /// The node. 108 | /// The context. 109 | private static bool CheckForObjectMapperMethod(MethodDeclarationSyntax node, SyntaxNodeAnalysisContext context) 110 | { 111 | if (node?.Identifier.Text != "MapObject") 112 | { 113 | return false; 114 | } 115 | var symbol = context.SemanticModel.GetDeclaredSymbol(node); 116 | if (symbol == null || symbol.Kind != SymbolKind.Method || 117 | (symbol.MethodKind != MethodKind.Ordinary && symbol.MethodKind != MethodKind.ExplicitInterfaceImplementation) || 118 | symbol.DeclaredAccessibility != Accessibility.Public || !symbol.ReturnsVoid || 119 | (symbol.Parameters.Length != 1 && symbol.Parameters.Length != 2)) 120 | { 121 | return false; 122 | } 123 | 124 | // find out if we have implementation of framework interfaces 125 | INamedTypeSymbol mapperInterface = null; 126 | if (symbol.Parameters.Length == 1) 127 | { 128 | mapperInterface = symbol.ContainingType.AllInterfaces.FirstOrDefault(x => 129 | x.OriginalDefinition.ToDisplayString() == "ObjectMapper.Framework.IObjectMapper" && 130 | FrameworkHelpers.IsObjectMapperFrameworkAssembly(x.OriginalDefinition.ContainingAssembly) && 131 | x.TypeArguments[0].Equals(symbol.Parameters[0].Type)); 132 | } 133 | else if (symbol.Parameters.Length == 2) 134 | { 135 | mapperInterface = symbol.ContainingType.AllInterfaces.FirstOrDefault(x => 136 | x.OriginalDefinition.ToDisplayString() == "ObjectMapper.Framework.IObjectMapperAdapter" && 137 | FrameworkHelpers.IsObjectMapperFrameworkAssembly(x.OriginalDefinition.ContainingAssembly) && 138 | (x.TypeArguments[0].Equals(symbol.Parameters[0].Type) && x.TypeArguments[1].Equals(symbol.Parameters[1].Type) 139 | || x.TypeArguments[0].Equals(symbol.Parameters[1].Type) && x.TypeArguments[1].Equals(symbol.Parameters[0].Type))); 140 | } 141 | if (mapperInterface == null) 142 | { 143 | return false; 144 | } 145 | 146 | // final check 147 | bool implementsInterfaceMethod = false; 148 | foreach (IMethodSymbol member in mapperInterface.GetMembers().Where(x => x.Kind == SymbolKind.Method)) 149 | { 150 | if (symbol.Equals(symbol.ContainingType.FindImplementationForInterfaceMember(member))) 151 | { 152 | implementsInterfaceMethod = true; 153 | break; 154 | } 155 | } 156 | if (!implementsInterfaceMethod) 157 | { 158 | return false; 159 | } 160 | 161 | var diagnostic = Diagnostic.Create(Rule, node.Identifier.GetLocation()); 162 | context.ReportDiagnostic(diagnostic); 163 | return true; 164 | } 165 | 166 | /// 167 | /// Checks for method with object mapper attribute. 168 | /// 169 | /// The node. 170 | /// The context. 171 | /// 172 | private static bool CheckForMethodWithObjectMapperAttribute(MethodDeclarationSyntax node, SyntaxNodeAnalysisContext context) 173 | { 174 | if (node.AttributeLists.Count == 0) 175 | { 176 | return false; 177 | } 178 | 179 | var candidateAttributes = node.AttributeLists.SelectMany(x => x.Attributes).Where(x => 180 | { 181 | SimpleNameSyntax sns = (x.Name as SimpleNameSyntax) ?? (x.Name as QualifiedNameSyntax).Right; 182 | var className = sns?.Identifier.Text; 183 | return className == "ObjectMapperMethod" || className == "ObjectMapperMethodAttribute"; 184 | }).ToList(); 185 | if (candidateAttributes.Count == 0) 186 | { 187 | return false; 188 | } 189 | 190 | var methodSymbol = context.SemanticModel.GetDeclaredSymbol(node); 191 | if (!methodSymbol.ReturnsVoid || methodSymbol.Parameters.Length != 2) 192 | { 193 | return false; 194 | } 195 | 196 | if (!candidateAttributes.Any(x => 197 | { 198 | var symbol = context.SemanticModel.GetSymbolInfo(x).Symbol?.ContainingType; 199 | if (symbol == null) 200 | { 201 | return false; 202 | } 203 | 204 | var fullSymbolName = symbol.ToDisplayString(); 205 | if (fullSymbolName != "ObjectMapper.Framework.ObjectMapperMethodAttribute") 206 | { 207 | return false; 208 | } 209 | 210 | if (!FrameworkHelpers.IsObjectMapperFrameworkAssembly(symbol.ContainingAssembly)) 211 | { 212 | return false; 213 | } 214 | 215 | return true; 216 | })) 217 | { 218 | return false; 219 | } 220 | 221 | var diagnostic = Diagnostic.Create(Rule, node.Identifier.GetLocation()); 222 | context.ReportDiagnostic(diagnostic); 223 | return true; 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/GenerateImplementationCodeGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Composition; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.CodeAnalysis; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CodeActions; 11 | using Microsoft.CodeAnalysis.CSharp; 12 | using Microsoft.CodeAnalysis.CSharp.Syntax; 13 | using Microsoft.CodeAnalysis.Rename; 14 | using Microsoft.CodeAnalysis.Text; 15 | using Microsoft.CodeAnalysis.Formatting; 16 | 17 | namespace ObjectMapper.GenerateImplementationAnalyzer 18 | { 19 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(GenerateImplementationCodeGenerator)), Shared] 20 | public class GenerateImplementationCodeGenerator : CodeFixProvider 21 | { 22 | private const string title = "Generate implementation"; 23 | 24 | public sealed override ImmutableArray FixableDiagnosticIds 25 | { 26 | get { return ImmutableArray.Create(GenerateImplementationCodeAnalyzer.DiagnosticId); } 27 | } 28 | 29 | public sealed override FixAllProvider GetFixAllProvider() 30 | { 31 | // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers 32 | return WellKnownFixAllProviders.BatchFixer; 33 | } 34 | 35 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 36 | { 37 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 38 | 39 | var diagnostic = context.Diagnostics.First(); 40 | var diagnosticSpan = diagnostic.Location.SourceSpan; 41 | 42 | // Find the type declaration identified by the diagnostic. 43 | var baseTypeDeclaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().FirstOrDefault(); 44 | if (baseTypeDeclaration != null) 45 | { 46 | context.RegisterCodeFix( 47 | CodeAction.Create( 48 | title: title, 49 | createChangedDocument: c => GenerateInterfaceImplementationAsync(context.Document, baseTypeDeclaration, c), 50 | equivalenceKey: title), 51 | diagnostic); 52 | return; 53 | } 54 | 55 | var methodDeclaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().FirstOrDefault(); 56 | if (methodDeclaration != null) 57 | { 58 | context.RegisterCodeFix( 59 | CodeAction.Create( 60 | title: title, 61 | createChangedDocument: c => GenerateMethodImplementationAsync(context.Document, methodDeclaration, c), 62 | equivalenceKey: title), 63 | diagnostic); 64 | return; 65 | } 66 | } 67 | 68 | private static async Task GenerateInterfaceImplementationAsync(Document document, SimpleBaseTypeSyntax baseTypeSyntax, CancellationToken cancellationToken) 69 | { 70 | var semanticModel = await document.GetSemanticModelAsync(cancellationToken); 71 | 72 | SimpleNameSyntax sns = (baseTypeSyntax.Type as SimpleNameSyntax) ?? (baseTypeSyntax.Type as QualifiedNameSyntax).Right; 73 | var interfaceSymbol = semanticModel.GetSymbolInfo(sns).Symbol as INamedTypeSymbol; 74 | if (interfaceSymbol == null || interfaceSymbol.TypeKind != TypeKind.Interface) return document; 75 | 76 | var originalClassDefinitionSyntax = (ClassDeclarationSyntax)baseTypeSyntax.Parent.Parent; 77 | ClassDeclarationSyntax modifiedClassDefinitionSyntax = null; 78 | if (interfaceSymbol.Name == "IObjectMapper" && interfaceSymbol.TypeArguments.Length == 1) 79 | { 80 | var sourceClassSymbol = semanticModel.GetDeclaredSymbol(originalClassDefinitionSyntax); 81 | var targetClassSymbol = interfaceSymbol.TypeArguments[0].OriginalDefinition as INamedTypeSymbol; 82 | if (sourceClassSymbol == null || targetClassSymbol == null) return document; 83 | 84 | var matchedProperties = RetrieveMatchedProperties(sourceClassSymbol, targetClassSymbol); 85 | 86 | var updatedMethods = new Dictionary(); 87 | var addedMethods = new List(); 88 | 89 | foreach (IMethodSymbol member in interfaceSymbol.GetMembers().Where(x => x.Kind == SymbolKind.Method)) 90 | { 91 | var method = sourceClassSymbol.FindImplementationForInterfaceMember(member) as IMethodSymbol; 92 | MethodDeclarationSyntax methodSyntax = null; 93 | if (method != null) 94 | { 95 | methodSyntax = await method.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken) as MethodDeclarationSyntax; 96 | var newMethodSyntax = methodSyntax.WithBody(GenerateMethodBody(member, matchedProperties, semanticModel, originalClassDefinitionSyntax.Span.End - 1)); 97 | updatedMethods.Add(methodSyntax, newMethodSyntax); 98 | } 99 | else 100 | { 101 | methodSyntax = GenerateMethodImplementation(member, semanticModel, originalClassDefinitionSyntax.Span.End - 1). 102 | WithBody(GenerateMethodBody(member, matchedProperties, semanticModel, originalClassDefinitionSyntax.Span.End - 1)); 103 | addedMethods.Add(methodSyntax); 104 | } 105 | } 106 | 107 | modifiedClassDefinitionSyntax = originalClassDefinitionSyntax.ReplaceNodes(updatedMethods.Keys.AsEnumerable(), (n1, n2) => updatedMethods[n1]).AddMembers(addedMethods.ToArray()); 108 | } 109 | else if (interfaceSymbol.Name == "IObjectMapperAdapter" && interfaceSymbol.TypeArguments.Length == 2) 110 | { 111 | var adapterClassSymbol = semanticModel.GetDeclaredSymbol(originalClassDefinitionSyntax); 112 | var sourceClassSymbol = interfaceSymbol.TypeArguments[0].OriginalDefinition as INamedTypeSymbol; 113 | var targetClassSymbol = interfaceSymbol.TypeArguments[1].OriginalDefinition as INamedTypeSymbol; 114 | if (sourceClassSymbol == null || targetClassSymbol == null) return document; 115 | 116 | var matchedProperties = RetrieveMatchedProperties(sourceClassSymbol, targetClassSymbol); 117 | 118 | var updatedMethods = new Dictionary(); 119 | var addedMethods = new List(); 120 | 121 | foreach (IMethodSymbol member in interfaceSymbol.GetMembers().Where(x => x.Kind == SymbolKind.Method)) 122 | { 123 | var matchingPropertyList = matchedProperties; 124 | // check if we have to switch matched properties 125 | if (member.Parameters.Length == 2 && !interfaceSymbol.TypeArguments[0].Equals(member.Parameters[0].Type)) 126 | { 127 | matchingPropertyList = matchingPropertyList.Select(x => new MatchedPropertySymbols { Source = x.Target, Target = x.Source }); 128 | } 129 | 130 | var method = adapterClassSymbol.FindImplementationForInterfaceMember(member) as IMethodSymbol; 131 | MethodDeclarationSyntax methodSyntax = null; 132 | if (method != null) 133 | { 134 | methodSyntax = await method.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken) as MethodDeclarationSyntax; 135 | var newMethodSyntax = methodSyntax.WithBody(GenerateMethodBody(member, matchingPropertyList, semanticModel, originalClassDefinitionSyntax.Span.End - 1)); 136 | updatedMethods.Add(methodSyntax, newMethodSyntax); 137 | } 138 | else 139 | { 140 | methodSyntax = GenerateMethodImplementation(member, semanticModel, originalClassDefinitionSyntax.Span.End - 1). 141 | WithBody(GenerateMethodBody(member, matchingPropertyList, semanticModel, originalClassDefinitionSyntax.Span.End - 1)); 142 | addedMethods.Add(methodSyntax); 143 | } 144 | } 145 | 146 | modifiedClassDefinitionSyntax = originalClassDefinitionSyntax.ReplaceNodes(updatedMethods.Keys.AsEnumerable(), (n1, n2) => updatedMethods[n1]).AddMembers(addedMethods.ToArray()); 147 | } 148 | 149 | if (modifiedClassDefinitionSyntax == null) 150 | { 151 | return document; 152 | } 153 | 154 | // replace root and return modified document 155 | var root = await document.GetSyntaxRootAsync(cancellationToken); 156 | var newRoot = root.ReplaceNode(originalClassDefinitionSyntax, modifiedClassDefinitionSyntax); 157 | var newDocument = document.WithSyntaxRoot(newRoot); 158 | return newDocument; 159 | } 160 | 161 | private static async Task GenerateMethodImplementationAsync(Document document, MethodDeclarationSyntax methodSyntax, CancellationToken cancellationToken) 162 | { 163 | var semanticModel = await document.GetSemanticModelAsync(cancellationToken); 164 | IMethodSymbol methodSymbol = semanticModel.GetDeclaredSymbol(methodSyntax); 165 | 166 | MethodDeclarationSyntax modifiedMethodSyntax = methodSyntax; 167 | if (methodSymbol.Parameters.Length == 1) 168 | { 169 | var sourceClassSymbol = methodSymbol.ContainingType; 170 | var targetClassSymbol = methodSymbol.Parameters[0].Type as INamedTypeSymbol; 171 | if (targetClassSymbol == null) return document; 172 | 173 | var matchedProperties = RetrieveMatchedProperties(sourceClassSymbol, targetClassSymbol); 174 | modifiedMethodSyntax = methodSyntax.WithBody(GenerateMethodBody(methodSymbol, matchedProperties, semanticModel, methodSyntax.Body.Span.End - 1)); 175 | } 176 | else if (methodSymbol.Parameters.Length == 2) 177 | { 178 | var sourceClassSymbol = methodSymbol.Parameters[0].Type as INamedTypeSymbol; 179 | var targetClassSymbol = methodSymbol.Parameters[1].Type as INamedTypeSymbol; 180 | if (sourceClassSymbol == null || targetClassSymbol == null) return document; 181 | 182 | var matchedProperties = RetrieveMatchedProperties(sourceClassSymbol, targetClassSymbol); 183 | modifiedMethodSyntax = methodSyntax.WithBody(GenerateMethodBody(methodSymbol, matchedProperties, semanticModel, methodSyntax.Body.Span.End - 1)); 184 | } 185 | 186 | // replace root and return modified document 187 | var root = await document.GetSyntaxRootAsync(cancellationToken); 188 | var newRoot = root.ReplaceNode(methodSyntax, modifiedMethodSyntax); 189 | var newDocument = document.WithSyntaxRoot(newRoot); 190 | return newDocument; 191 | } 192 | 193 | private static IEnumerable RetrieveMatchedProperties(INamedTypeSymbol source, INamedTypeSymbol target) 194 | { 195 | SortedDictionary propertiesMap = new SortedDictionary(); 196 | 197 | foreach (IPropertySymbol mSymbol in source.GetMembers().Where(x => x.Kind == SymbolKind.Property)) 198 | { 199 | if (mSymbol.IsStatic || mSymbol.IsIndexer || mSymbol.DeclaredAccessibility != Accessibility.Public) 200 | { 201 | continue; 202 | } 203 | if (!propertiesMap.ContainsKey(mSymbol.Name)) 204 | { 205 | // If class definition is invalid, it may happen that we get multiple properties with the same name 206 | // Ignore all but first 207 | propertiesMap.Add(mSymbol.Name, new MatchedPropertySymbols() { Source = mSymbol }); 208 | } 209 | } 210 | 211 | foreach (IPropertySymbol mSymbol in target.GetMembers().Where(x => x.Kind == SymbolKind.Property)) 212 | { 213 | if (mSymbol.IsStatic || mSymbol.IsIndexer || mSymbol.DeclaredAccessibility != Accessibility.Public) 214 | { 215 | continue; 216 | } 217 | MatchedPropertySymbols sourceProperty = null; 218 | if (!propertiesMap.TryGetValue(mSymbol.Name, out sourceProperty)) 219 | { 220 | propertiesMap.Add(mSymbol.Name, new MatchedPropertySymbols { Target = mSymbol }); 221 | } 222 | else if (sourceProperty.Target == null) 223 | { 224 | // If class definition is invalid, it may happen that we get multiple properties with the same name 225 | // Ignore all but first 226 | sourceProperty.Target = mSymbol; 227 | } 228 | } 229 | 230 | return propertiesMap.Values; 231 | } 232 | 233 | private static BlockSyntax GenerateMethodBody(IMethodSymbol method, IEnumerable matchedProperties, SemanticModel model, int position) 234 | { 235 | if (method.ReturnsVoid && method.Parameters.Length == 1) 236 | { 237 | return SyntaxFactory.Block( 238 | SyntaxFactory.Token(SyntaxKind.OpenBraceToken), 239 | SyntaxFactory.List(GenerateAssignmentSyntax(SyntaxFactory.ThisExpression(), SyntaxFactory.IdentifierName(method.Parameters[0].Name), matchedProperties, model, position)), 240 | SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); 241 | } 242 | else if (method.ReturnsVoid && method.Parameters.Length == 2) 243 | { 244 | return SyntaxFactory.Block( 245 | SyntaxFactory.Token(SyntaxKind.OpenBraceToken), 246 | SyntaxFactory.List(GenerateAssignmentSyntax(SyntaxFactory.IdentifierName(method.Parameters[0].Name), SyntaxFactory.IdentifierName(method.Parameters[1].Name), matchedProperties, model, position)), 247 | SyntaxFactory.Token(SyntaxKind.CloseBraceToken)); 248 | } 249 | else 250 | { 251 | return SyntaxFactory.Block(); 252 | } 253 | } 254 | 255 | private static IEnumerable GenerateAssignmentSyntax(ExpressionSyntax source, ExpressionSyntax target, IEnumerable matchedPropertySymbols, SemanticModel model, int position) 256 | { 257 | foreach (var matchedProperty in matchedPropertySymbols) 258 | { 259 | if (matchedProperty.Source == null || matchedProperty.Target == null) continue; 260 | if (matchedProperty.Source.GetMethod == null || matchedProperty.Source.GetMethod.DeclaredAccessibility != Accessibility.Public) continue; 261 | 262 | if (matchedProperty.Target.SetMethod != null && matchedProperty.Source.SetMethod.DeclaredAccessibility == Accessibility.Public && 263 | (matchedProperty.Source.Type.Equals(matchedProperty.Target.Type) || matchedProperty.Target.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T && (matchedProperty.Target.Type as INamedTypeSymbol).TypeArguments[0].Equals(matchedProperty.Source.Type))) 264 | { 265 | yield return SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( 266 | SyntaxKind.SimpleAssignmentExpression, 267 | SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, target, SyntaxFactory.IdentifierName(matchedProperty.Target.Name)), 268 | SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, source, SyntaxFactory.IdentifierName(matchedProperty.Source.Name)))); 269 | } 270 | else if (matchedProperty.Target.SetMethod != null && matchedProperty.Source.SetMethod.DeclaredAccessibility == Accessibility.Public && 271 | (matchedProperty.Source.Type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T && (matchedProperty.Source.Type as INamedTypeSymbol).TypeArguments[0].Equals(matchedProperty.Target.Type))) 272 | { 273 | yield return SyntaxFactory.ExpressionStatement(SyntaxFactory.AssignmentExpression( 274 | SyntaxKind.SimpleAssignmentExpression, 275 | SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, target, SyntaxFactory.IdentifierName(matchedProperty.Target.Name)), 276 | SyntaxFactory.BinaryExpression( 277 | SyntaxKind.CoalesceExpression, 278 | SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, source, SyntaxFactory.IdentifierName(matchedProperty.Source.Name)), 279 | SyntaxFactory.DefaultExpression(SyntaxFactory.ParseTypeName(matchedProperty.Target.Type.ToMinimalDisplayString(model, position)))))); 280 | } 281 | else if (matchedProperty.Target.Type.OriginalDefinition.AllInterfaces.Any(x => x.ToDisplayString() == "System.Collections.Generic.ICollection") && 282 | matchedProperty.Target.GetMethod != null && matchedProperty.Target.GetMethod.DeclaredAccessibility == Accessibility.Public && 283 | matchedProperty.Source.Type.OriginalDefinition.AllInterfaces.Any(x => x.ToDisplayString() == "System.Collections.Generic.IEnumerable" && 284 | (matchedProperty.Target.Type as INamedTypeSymbol).TypeArguments[0].Equals((matchedProperty.Source.Type as INamedTypeSymbol).TypeArguments[0]))) 285 | { 286 | yield return SyntaxFactory.ExpressionStatement(SyntaxFactory.InvocationExpression( 287 | SyntaxFactory.MemberAccessExpression( 288 | SyntaxKind.SimpleMemberAccessExpression, 289 | SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, target, SyntaxFactory.IdentifierName(matchedProperty.Target.Name)), 290 | SyntaxFactory.IdentifierName("CopyFrom"))). 291 | WithArgumentList(SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument( 292 | SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, source, SyntaxFactory.IdentifierName(matchedProperty.Source.Name))))))); 293 | } 294 | } 295 | } 296 | 297 | private static MethodDeclarationSyntax GenerateMethodImplementation(IMethodSymbol fromMethod, SemanticModel model, int position) 298 | { 299 | MethodDeclarationSyntax syntax = SyntaxFactory.MethodDeclaration( 300 | fromMethod.ReturnsVoid ? SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)) : SyntaxFactory.ParseTypeName(fromMethod.ReturnType.ToMinimalDisplayString(model, position)), 301 | fromMethod.Name) 302 | .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) 303 | .WithParameterList(SyntaxFactory.ParameterList( 304 | SyntaxFactory.Token(SyntaxKind.OpenParenToken), 305 | SyntaxFactory.SeparatedList(fromMethod.Parameters.Select(x => SyntaxFactory.Parameter(SyntaxFactory.Identifier(x.Name)).WithType(SyntaxFactory.ParseTypeName(x.Type.ToMinimalDisplayString(model, position))))), 306 | SyntaxFactory.Token(SyntaxKind.CloseParenToken))) 307 | .WithBody(SyntaxFactory.Block()) 308 | .WithAdditionalAnnotations(Formatter.Annotation); 309 | 310 | return syntax; 311 | } 312 | 313 | private sealed class MatchedPropertySymbols 314 | { 315 | public IPropertySymbol Source { get; set; } 316 | public IPropertySymbol Target { get; set; } 317 | } 318 | } 319 | } -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 11.0 6 | Debug 7 | AnyCPU 8 | {492F3804-A43F-4E82-B649-8AD0D967A830} 9 | Library 10 | Properties 11 | ObjectMapper.GenerateImplementationAnalyzer 12 | ObjectMapper.GenerateImplementationAnalyzer 13 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | Profile7 15 | v4.5 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | true 26 | AllRules.ruleset 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | true 36 | AllRules.ruleset 37 | 38 | 39 | true 40 | 41 | 42 | objectmapper.pfx 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | True 51 | True 52 | Resources.resx 53 | 54 | 55 | 56 | 57 | 58 | ResXFileCodeGenerator 59 | Resources.Designer.cs 60 | 61 | 62 | 63 | 64 | Designer 65 | PreserveNewest 66 | 67 | 68 | 69 | 70 | PreserveNewest 71 | 72 | 73 | PreserveNewest 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | ..\..\packages\Microsoft.CodeAnalysis.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.dll 83 | False 84 | 85 | 86 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.dll 87 | False 88 | 89 | 90 | ..\..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.CSharp.Workspaces.dll 91 | False 92 | 93 | 94 | ..\..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.0.0\lib\portable-net45+win8\Microsoft.CodeAnalysis.Workspaces.dll 95 | False 96 | 97 | 98 | ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll 99 | False 100 | 101 | 102 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll 103 | False 104 | 105 | 106 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll 107 | False 108 | 109 | 110 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll 111 | False 112 | 113 | 114 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll 115 | False 116 | 117 | 118 | ..\..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll 119 | False 120 | 121 | 122 | ..\..\packages\System.Reflection.Metadata.1.0.21\lib\portable-net45+win8\System.Reflection.Metadata.dll 123 | False 124 | 125 | 126 | 127 | 128 | 129 | PreserveNewest 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 148 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ObjectMapper.GenerateImplementationAnalyzer")] 9 | [assembly: AssemblyDescription("Contains analyzer and code fix provider for generating mapping boilerplate code.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ObjectMapper.GenerateImplementationAnalyzer")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | [assembly: CLSCompliant(false)] 18 | [assembly: ComVisible(false)] 19 | 20 | [assembly: AssemblyVersion("1.2.2.0")] 21 | [assembly: AssemblyFileVersion("1.2.2.0")] 22 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ObjectMapper.GenerateImplementationAnalyzer { 12 | using System; 13 | using System.Reflection; 14 | 15 | 16 | /// 17 | /// A strongly-typed resource class, for looking up localized strings, etc. 18 | /// 19 | // This class was auto-generated by the StronglyTypedResourceBuilder 20 | // class via a tool like ResGen or Visual Studio. 21 | // To add or remove a member, edit your .ResX file then rerun ResGen 22 | // with the /str option, or rebuild your VS project. 23 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 24 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 25 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 26 | internal class Resources { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() { 34 | } 35 | 36 | /// 37 | /// Returns the cached ResourceManager instance used by this class. 38 | /// 39 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 40 | internal static global::System.Resources.ResourceManager ResourceManager { 41 | get { 42 | if (object.ReferenceEquals(resourceMan, null)) { 43 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ObjectMapper.GenerateImplementationAnalyzer.Resources", typeof(Resources).GetTypeInfo().Assembly); 44 | resourceMan = temp; 45 | } 46 | return resourceMan; 47 | } 48 | } 49 | 50 | /// 51 | /// Overrides the current thread's CurrentUICulture property for all 52 | /// resource lookups using this strongly typed resource class. 53 | /// 54 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 55 | internal static global::System.Globalization.CultureInfo Culture { 56 | get { 57 | return resourceCulture; 58 | } 59 | set { 60 | resourceCulture = value; 61 | } 62 | } 63 | 64 | /// 65 | /// Looks up a localized string similar to Attribute cannot be applied to methods that do not accept exactly two parameters and return void.. 66 | /// 67 | internal static string AttributeUsageAnalyzerDescription { 68 | get { 69 | return ResourceManager.GetString("AttributeUsageAnalyzerDescription", resourceCulture); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized string similar to Attribute cannot be applied to this method.. 75 | /// 76 | internal static string AttributeUsageAnalyzerMessageFormat { 77 | get { 78 | return ResourceManager.GetString("AttributeUsageAnalyzerMessageFormat", resourceCulture); 79 | } 80 | } 81 | 82 | /// 83 | /// Looks up a localized string similar to Invalid use of object mapper attribute.. 84 | /// 85 | internal static string AttributeUsageAnalyzerTitle { 86 | get { 87 | return ResourceManager.GetString("AttributeUsageAnalyzerTitle", resourceCulture); 88 | } 89 | } 90 | 91 | /// 92 | /// Looks up a localized string similar to Interface implementation can be generated.. 93 | /// 94 | internal static string GenerateImplementationAnalyzerDescription { 95 | get { 96 | return ResourceManager.GetString("GenerateImplementationAnalyzerDescription", resourceCulture); 97 | } 98 | } 99 | 100 | /// 101 | /// Looks up a localized string similar to Implementation of mapping method(s) can be generated.. 102 | /// 103 | internal static string GenerateImplementationAnalyzerMessageFormat { 104 | get { 105 | return ResourceManager.GetString("GenerateImplementationAnalyzerMessageFormat", resourceCulture); 106 | } 107 | } 108 | 109 | /// 110 | /// Looks up a localized string similar to Type implements object mapper interface.. 111 | /// 112 | internal static string GenerateImplementationAnalyzerTitle { 113 | get { 114 | return ResourceManager.GetString("GenerateImplementationAnalyzerTitle", resourceCulture); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | Attribute cannot be applied to methods that do not accept exactly two parameters and return void. 122 | 123 | 124 | Attribute cannot be applied to this method. 125 | 126 | 127 | Invalid use of object mapper attribute. 128 | 129 | 130 | Interface implementation can be generated. 131 | An optional longer localizable description of the diagnostic. 132 | 133 | 134 | Implementation of mapping method(s) can be generated. 135 | The format-able message the diagnostic displays. 136 | 137 | 138 | Type implements object mapper interface. 139 | The title of the diagnostic. 140 | 141 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/Utilities/FrameworkHelpers.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.Immutable; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace ObjectMapper.GenerateImplementationAnalyzer.Utilities 10 | { 11 | internal static class FrameworkHelpers 12 | { 13 | private static readonly ImmutableArray _publicKeyToken = ImmutableArray.Create(38, 23, 68, 249, 236, 31, 118, 87); 14 | public static bool IsObjectMapperFrameworkAssembly(IAssemblySymbol assemblySymbol) 15 | { 16 | if (assemblySymbol.Name != "ObjectMapper.Framework") 17 | { 18 | return false; 19 | } 20 | if (!assemblySymbol.Identity.IsStrongName || !_publicKeyToken.SequenceEqual(assemblySymbol.Identity.PublicKeyToken)) 21 | { 22 | return false; 23 | } 24 | return true; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/lib/ObjectMapper.Framework.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nejcskofic/ObjectMapper/8108d3e309d670d82a17cc219805536bc435cf3e/ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/lib/ObjectMapper.Framework.dll -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve 4 | 5 | foreach($analyzersPath in $analyzersPaths) 6 | { 7 | # Install the language agnostic analyzers. 8 | if (Test-Path $analyzersPath) 9 | { 10 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 11 | { 12 | if($project.Object.AnalyzerReferences) 13 | { 14 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 15 | } 16 | } 17 | } 18 | } 19 | 20 | # $project.Type gives the language name like (C# or VB.NET) 21 | $languageFolder = "" 22 | if($project.Type -eq "C#") 23 | { 24 | $languageFolder = "cs" 25 | } 26 | if($project.Type -eq "VB.NET") 27 | { 28 | $languageFolder = "vb" 29 | } 30 | if($languageFolder -eq "") 31 | { 32 | return 33 | } 34 | 35 | foreach($analyzersPath in $analyzersPaths) 36 | { 37 | # Install language specific analyzers. 38 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 39 | if (Test-Path $languageAnalyzersPath) 40 | { 41 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 42 | { 43 | if($project.Object.AnalyzerReferences) 44 | { 45 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.GenerateImplementationAnalyzer/ObjectMapper.GenerateImplementationAnalyzer/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers" ) * -Resolve 4 | 5 | foreach($analyzersPath in $analyzersPaths) 6 | { 7 | # Uninstall the language agnostic analyzers. 8 | if (Test-Path $analyzersPath) 9 | { 10 | foreach ($analyzerFilePath in Get-ChildItem $analyzersPath -Filter *.dll) 11 | { 12 | if($project.Object.AnalyzerReferences) 13 | { 14 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 15 | } 16 | } 17 | } 18 | } 19 | 20 | # $project.Type gives the language name like (C# or VB.NET) 21 | $languageFolder = "" 22 | if($project.Type -eq "C#") 23 | { 24 | $languageFolder = "cs" 25 | } 26 | if($project.Type -eq "VB.NET") 27 | { 28 | $languageFolder = "vb" 29 | } 30 | if($languageFolder -eq "") 31 | { 32 | return 33 | } 34 | 35 | foreach($analyzersPath in $analyzersPaths) 36 | { 37 | # Uninstall language specific analyzers. 38 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 39 | if (Test-Path $languageAnalyzersPath) 40 | { 41 | foreach ($analyzerFilePath in Get-ChildItem $languageAnalyzersPath -Filter *.dll) 42 | { 43 | if($project.Object.AnalyzerReferences) 44 | { 45 | try 46 | { 47 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 48 | } 49 | catch 50 | { 51 | 52 | } 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /ObjectMapper/ObjectMapper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectMapper.GenerateImplementationAnalyzer", "ObjectMapper.GenerateImplementationAnalyzer\ObjectMapper.GenerateImplementationAnalyzer\ObjectMapper.GenerateImplementationAnalyzer.csproj", "{492F3804-A43F-4E82-B649-8AD0D967A830}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectMapper.GenerateImplementationAnalyzer.Test", "ObjectMapper.GenerateImplementationAnalyzer\ObjectMapper.GenerateImplementationAnalyzer.Test\ObjectMapper.GenerateImplementationAnalyzer.Test.csproj", "{FCC88640-43A1-4FEE-AE9B-44674C6376F7}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectMapper.GenerateImplementationAnalyzer.Vsix", "ObjectMapper.GenerateImplementationAnalyzer\ObjectMapper.GenerateImplementationAnalyzer.Vsix\ObjectMapper.GenerateImplementationAnalyzer.Vsix.csproj", "{2AF839D8-C2E5-45CC-BA8E-04C20DD75B96}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectMapper.Framework", "ObjectMapper.Framework\ObjectMapper.Framework.csproj", "{8220C878-68E1-4307-A908-66FBEA9D8B56}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {492F3804-A43F-4E82-B649-8AD0D967A830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {492F3804-A43F-4E82-B649-8AD0D967A830}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {492F3804-A43F-4E82-B649-8AD0D967A830}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {492F3804-A43F-4E82-B649-8AD0D967A830}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {FCC88640-43A1-4FEE-AE9B-44674C6376F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {FCC88640-43A1-4FEE-AE9B-44674C6376F7}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {FCC88640-43A1-4FEE-AE9B-44674C6376F7}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {FCC88640-43A1-4FEE-AE9B-44674C6376F7}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {2AF839D8-C2E5-45CC-BA8E-04C20DD75B96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {2AF839D8-C2E5-45CC-BA8E-04C20DD75B96}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {2AF839D8-C2E5-45CC-BA8E-04C20DD75B96}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {2AF839D8-C2E5-45CC-BA8E-04C20DD75B96}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {8220C878-68E1-4307-A908-66FBEA9D8B56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {8220C878-68E1-4307-A908-66FBEA9D8B56}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {8220C878-68E1-4307-A908-66FBEA9D8B56}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {8220C878-68E1-4307-A908-66FBEA9D8B56}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Object Mapper 2 | This is simple library with interfaces for mapping one C# object to another with code analyser feature of .NET compiler platform (aka Roslyn) to provide code generation. 3 | 4 | ## Why another mapper 5 | All other mappers operate in runtime using some kind of reflection to generate code which maps properties between objects. While this removes the need to write boilerplate code and speeds up development, it has number of disadvantages such as: 6 | - Refactoring unfriendly - if someone renames one property but not the other, mapping does not work on that property anymore 7 | - No way to see what will actually get copied - code is generated at the runtime 8 | - Runtime code generation and reflection has performance overhead 9 | 10 | This is why this library consists of simple interfaces for object mapping contract only. Provided code analyser and code fix provider are responsible for generating mapping boilerplate code for developer. Generated code can be modified if desired, you actually see what gets mapped and there is no performance overhead since mapping code is like any other hand written code. 11 | 12 | ## Usage 13 | 14 | Install NuGet package to your project - you can find current release [here](https://www.nuget.org/packages/SimpleObjectMapper/). This will add ObjectMapper.Framework to your references and diagnostic analyser under analyzers node. You can then use mapper interfaces or attribute from ObjectMapper.Framework assembly to define mapping. 15 | 16 | ### IObjectMapper interface 17 | 18 | Let's define two classes with some matching properties: 19 | 20 | ```C# 21 | using ObjectMapper.Framework; 22 | using System.Collections.Generic; 23 | 24 | namespace TestClassLibrary 25 | { 26 | public class ClassA 27 | { 28 | public int? Prop1 { get; set; } 29 | public string Prop2 { get; set; } 30 | public decimal Prop3 { get; set; } 31 | 32 | private List _prop4; 33 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 34 | } 35 | 36 | public class ClassB 37 | { 38 | public int Prop1 { get; set; } 39 | public string Prop2 { get; set; } 40 | public decimal Prop3 { get; set; } 41 | 42 | private List _prop4; 43 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 44 | } 45 | } 46 | ``` 47 | 48 | Let's say that we want to define mapping from class ClassA to ClassB. We specify that ClassA implements IObjectMapper interface. If we have caret on IObjectMapper symbol, Visual Studio will display lightbulb on the right side. By clicking on lightbulb (or using left control + . shortcut), you will get menu of options. If you click 'Generate implementation' interface implementation with mapping code will be generated for you. For the above example result is as follows: 49 | 50 | ```C# 51 | using ObjectMapper.Framework; 52 | using System.Collections.Generic; 53 | 54 | namespace TestClassLibrary 55 | { 56 | public class ClassA : IObjectMapper 57 | { 58 | public int? Prop1 { get; set; } 59 | public string Prop2 { get; set; } 60 | public decimal Prop3 { get; set; } 61 | 62 | private List _prop4; 63 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 64 | 65 | public void MapObject(ClassB target) 66 | { 67 | target.Prop1 = this.Prop1 ?? default(int); 68 | target.Prop2 = this.Prop2; 69 | target.Prop3 = this.Prop3; 70 | target.Prop4.CopyFrom(this.Prop4); 71 | } 72 | } 73 | 74 | public class ClassB 75 | { 76 | public int Prop1 { get; set; } 77 | public string Prop2 { get; set; } 78 | public decimal Prop3 { get; set; } 79 | 80 | private List _prop4; 81 | public List Prop4 { get { if (_prop4 == null) _prop4 = new List(); return _prop4; } } 82 | } 83 | } 84 | ``` 85 | 86 | If you have method already defined (or generated), you can set caret on method name and 'Generate implementation' action will be available. 87 | 88 | ### IObjectMapperAdapter interface 89 | 90 | If you do not want to have mapping code inside your POCO classes you can use IObjectMapperAdapter. By having cursor on IObjectMapperAdapter symbol, you can invoke 'Generate implementation' and generator will generate mapping code for mapping from class T to class U and from class U to class T. 91 | 92 | If we take previous example of ClassA and ClassB and we define new class Adapter which implements IObjectMapperAdapter generated code for Adapter class is as follows: 93 | 94 | ```C# 95 | public class Adapter : IObjectMapperAdapter 96 | { 97 | public void MapObject(ClassA source, ClassB target) 98 | { 99 | target.Prop1 = source.Prop1 ?? default(int); 100 | target.Prop2 = source.Prop2; 101 | target.Prop3 = source.Prop3; 102 | target.Prop4.CopyFrom(source.Prop4); 103 | } 104 | 105 | public void MapObject(ClassB source, ClassA target) 106 | { 107 | target.Prop1 = source.Prop1; 108 | target.Prop2 = source.Prop2; 109 | target.Prop3 = source.Prop3; 110 | target.Prop4.CopyFrom(source.Prop4); 111 | } 112 | } 113 | ``` 114 | 115 | ### ObjectMapperMethodAttribute attribute 116 | 117 | If you want to have ad hoc method for mapping without actually implementing one of the above interfaces, you can annotate mapping method with ObjectMapperMethodAttribute attribute. This attribute can be applied only to methods with following signatures: 118 | - Method accepts exactly two parameters 119 | - Method return type is void 120 | 121 | Compile time error will be raised otherwise. If you then place caret on method name, lightbulb will apear and 'Generate implementation' action will be available. 122 | 123 | Example of method with applied attribute: 124 | 125 | ```C# 126 | [ObjectMapperMethod] 127 | private static void MapFromAToB(ClassA source, ClassB target) 128 | { 129 | target.Prop1 = source.Prop1 ?? default(int); 130 | target.Prop2 = source.Prop2; 131 | target.Prop3 = source.Prop3; 132 | target.Prop4.CopyFrom(source.Prop4); 133 | } 134 | ``` 135 | 136 | ## Code generation rules 137 | 138 | 1. Property names must match in both classes. 139 | 2. Only public properties are considered. Also source getter and target setter must be public. 140 | 3. Type must match exactly. Currently there is no check if source object/value is assignable to target. 141 | 4. If source and target types are collections, mapping code will copy objects from one collection to another if generic type is the same. Non generic collections are not supported. 142 | 143 | --------------------------------------------------------------------------------