├── .editorconfig ├── .gitignore ├── DocumentationAssistant.sln ├── DocumentationAssistant ├── DocumentationAssistant.Test │ ├── ClassUnitTests.cs │ ├── ConstructorUnitTests.cs │ ├── DocumentationAssistant.Test.csproj │ ├── EnumUnitTests.cs │ ├── FieldUnitTests.cs │ ├── Helpers │ │ ├── CodeFixVerifier.Helper.cs │ │ ├── DiagnosticResult.cs │ │ └── DiagnosticVerifier.Helper.cs │ ├── InterfaceUnitTests.cs │ ├── MethodUnitTests.cs │ ├── PropertyUnitTests.cs │ └── Verifiers │ │ ├── CodeFixVerifier.cs │ │ └── DiagnosticVerifier.cs ├── DocumentationAssistant.Vsix │ ├── DocumentationAssistant.Vsix.csproj │ ├── packages.config │ └── source.extension.vsixmanifest └── DocumentationAssistant │ ├── ClassAnalyzer.cs │ ├── ClassCodeFixProvider.cs │ ├── ConstructorAnalyzer.cs │ ├── ConstructorCodeFixProvider.cs │ ├── DocumentationAssistant.csproj │ ├── EnumAnalyzer.cs │ ├── EnumCodeFixProvider.cs │ ├── FieldAnalyzer.cs │ ├── FieldCodeFixProvider.cs │ ├── Helper │ ├── CommentHelper.cs │ ├── Configuration.cs │ ├── DocumentationHeaderHelper.cs │ ├── NameSpliter.cs │ ├── Pluralizer.cs │ ├── PrivateMemberChecker.cs │ └── ReturnCommentConstruction.cs │ ├── InterfaceAnalyzer.cs │ ├── InterfaceCodeFixProvider.cs │ ├── MethodAnalyzer.cs │ ├── MethodCodeFixProvider.cs │ ├── PropertyAnalyzer.cs │ ├── PropertyCodeFixProvider.cs │ └── tools │ ├── install.ps1 │ └── uninstall.ps1 ├── GifInstruction ├── quick action options.gif ├── short cut to quick add.gif └── warning wave line.gif └── Readme.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # Style and naming rules for c# code files. 2 | 3 | root = true 4 | [*.cs] 5 | 6 | ############################### 7 | # Core EditorConfig Options # 8 | ############################### 9 | 10 | indent_style = tab 11 | 12 | ############################### 13 | # .NET Coding Conventions # 14 | ############################### 15 | 16 | # Organize usings 17 | dotnet_sort_system_directives_first = true 18 | 19 | # this. preferences 20 | dotnet_style_qualification_for_field = true:warning 21 | dotnet_style_qualification_for_property = true:warning 22 | dotnet_style_qualification_for_method = true:none 23 | dotnet_style_qualification_for_event = true:warning 24 | 25 | # Language keywords vs BCL types preferences 26 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 27 | dotnet_style_predefined_type_for_member_access = true:warning 28 | 29 | # Parentheses preferences 30 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:none 31 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:none 32 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none 33 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:none 34 | 35 | # Modifier preferences 36 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning 37 | dotnet_style_readonly_field = true:warning 38 | 39 | # Expression-level preferences 40 | dotnet_style_object_initializer = true:none 41 | dotnet_style_collection_initializer = true:none 42 | dotnet_style_explicit_tuple_names = true:none 43 | dotnet_style_null_propagation = true:none 44 | dotnet_style_coalesce_expression = true:none 45 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:none 46 | dotnet_style_prefer_inferred_tuple_names = false:warning 47 | dotnet_style_prefer_inferred_anonymous_type_member_names = false:warning 48 | dotnet_style_prefer_auto_properties = false:none 49 | dotnet_style_prefer_conditional_expression_over_assignment = false:none 50 | dotnet_style_prefer_conditional_expression_over_return = false:none 51 | 52 | ############################### 53 | # Naming Conventions # 54 | ############################### 55 | 56 | # Style Definitions 57 | 58 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 59 | 60 | dotnet_naming_style.field_name_style.capitalization = camel_case 61 | dotnet_naming_style.field_name_style.required_prefix = _ 62 | 63 | dotnet_naming_style.interface_name_style.capitalization = pascal_case 64 | dotnet_naming_style.interface_name_style.required_prefix = I 65 | 66 | # Symbol Definitions 67 | 68 | dotnet_naming_symbols.const_fields.applicable_accessibilities = * 69 | dotnet_naming_symbols.const_fields.applicable_kinds = field 70 | dotnet_naming_symbols.const_fields.required_modifiers = const 71 | 72 | dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private 73 | dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field 74 | dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static 75 | 76 | dotnet_naming_symbols.internal_static_readonly_fields.applicable_accessibilities = internal 77 | dotnet_naming_symbols.internal_static_readonly_fields.applicable_kinds = field 78 | dotnet_naming_symbols.internal_static_readonly_fields.required_modifiers = readonly, static 79 | 80 | dotnet_naming_symbols.private_readonly_fields.applicable_accessibilities = private 81 | dotnet_naming_symbols.private_readonly_fields.applicable_kinds = field 82 | dotnet_naming_symbols.private_readonly_fields.required_modifiers = readonly 83 | 84 | dotnet_naming_symbols.fields.applicable_accessibilities = * 85 | dotnet_naming_symbols.fields.applicable_kinds = field 86 | 87 | dotnet_naming_symbols.interfaces.applicable_accessibilities = * 88 | dotnet_naming_symbols.interfaces.applicable_kinds = interface 89 | 90 | dotnet_naming_symbols.pascal_case_symbols.applicable_accessibilities = * 91 | dotnet_naming_symbols.pascal_case_symbols.applicable_kinds = class, struct, enum, property, method, event 92 | 93 | # Rules 94 | 95 | dotnet_naming_rule.interface.severity = warning 96 | dotnet_naming_rule.interface.style = interface_name_style 97 | dotnet_naming_rule.interface.symbols = interfaces 98 | 99 | dotnet_naming_rule.const_field.severity = warning 100 | dotnet_naming_rule.const_field.style = pascal_case_style 101 | dotnet_naming_rule.const_field.symbols = const_fields 102 | 103 | dotnet_naming_rule.private_static_readonly_field.severity = warning 104 | dotnet_naming_rule.private_static_readonly_field.style = pascal_case_style 105 | dotnet_naming_rule.private_static_readonly_field.symbols = private_static_readonly_fields 106 | 107 | dotnet_naming_rule.internal_static_readonly_field.severity = warning 108 | dotnet_naming_rule.internal_static_readonly_field.style = pascal_case_style 109 | dotnet_naming_rule.internal_static_readonly_field.symbols = internal_static_readonly_fields 110 | 111 | dotnet_naming_rule.private_readonly_field.severity = warning 112 | dotnet_naming_rule.private_readonly_field.style = field_name_style 113 | dotnet_naming_rule.private_readonly_field.symbols = private_readonly_fields 114 | 115 | dotnet_naming_rule.field.severity = warning 116 | dotnet_naming_rule.field.style = field_name_style 117 | dotnet_naming_rule.field.symbols = fields 118 | 119 | dotnet_naming_rule.pascal_case.severity = warning 120 | dotnet_naming_rule.pascal_case.style = pascal_case_style 121 | dotnet_naming_rule.pascal_case.symbols = pascal_case_symbols 122 | 123 | ############################### 124 | # C# Coding Conventions # 125 | ############################### 126 | 127 | # var preferences 128 | csharp_style_var_for_built_in_types = false:none 129 | csharp_style_var_when_type_is_apparent = false:none 130 | csharp_style_var_elsewhere = false:none 131 | 132 | # Expression-bodied members 133 | csharp_style_expression_bodied_methods = false:none 134 | csharp_style_expression_bodied_constructors = false:none 135 | csharp_style_expression_bodied_operators = false:none 136 | csharp_style_expression_bodied_properties = false:none 137 | csharp_style_expression_bodied_indexers = false:none 138 | csharp_style_expression_bodied_accessors = false:none 139 | 140 | # Pattern matching preferences 141 | csharp_style_pattern_matching_over_is_with_cast_check = false:warning 142 | csharp_style_pattern_matching_over_as_with_null_check = false:warning 143 | 144 | # Null-checking preferences 145 | csharp_style_throw_expression = false:warning 146 | csharp_style_conditional_delegate_call = false:warning 147 | 148 | # Modifier preferences 149 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning 150 | 151 | # Expression-level preferences 152 | csharp_prefer_braces = true:warning 153 | csharp_style_deconstructed_variable_declaration = true:none 154 | csharp_prefer_simple_default_expression = true:none 155 | csharp_style_pattern_local_over_anonymous_function = true:none 156 | csharp_style_inlined_variable_declaration = true:none 157 | 158 | ############################### 159 | # C# Formatting Rules # 160 | ############################### 161 | 162 | # New line preferences 163 | csharp_new_line_before_open_brace = all 164 | csharp_new_line_before_else = true 165 | csharp_new_line_before_catch = true 166 | csharp_new_line_before_finally = true 167 | csharp_new_line_before_members_in_object_initializers = true 168 | csharp_new_line_before_members_in_anonymous_types = true 169 | csharp_new_line_between_query_expression_clauses = true 170 | 171 | # Indentation preferences 172 | csharp_indent_case_contents = true 173 | csharp_indent_switch_labels = true 174 | csharp_indent_labels = flush_left 175 | 176 | # Space preferences 177 | csharp_space_after_cast = false 178 | csharp_space_after_keywords_in_control_flow_statements = true 179 | csharp_space_between_method_call_parameter_list_parentheses = false 180 | csharp_space_between_method_declaration_parameter_list_parentheses = false 181 | csharp_space_between_parentheses = false 182 | csharp_space_before_colon_in_inheritance_clause = true 183 | csharp_space_after_colon_in_inheritance_clause = true 184 | csharp_space_around_binary_operators = before_and_after 185 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 186 | csharp_space_between_method_call_name_and_opening_parenthesis = false 187 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 188 | 189 | # Wrapping preferences 190 | csharp_preserve_single_line_statements = false 191 | csharp_preserve_single_line_blocks = true 192 | -------------------------------------------------------------------------------- /.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 | *.sln.docstates 8 | .vs/ 9 | 10 | # Build results 11 | [Dd]ebug/ 12 | [Dd]ebugPublic/ 13 | [Rr]elease/ 14 | x64/ 15 | build/ 16 | bld/ 17 | [Bb]in/ 18 | [Oo]bj/ 19 | 20 | # Roslyn cache directories 21 | *.ide/ 22 | 23 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 24 | !packages/*/build/ 25 | 26 | # MSTest test Results 27 | [Tt]est[Rr]esult*/ 28 | [Bb]uild[Ll]og.* 29 | 30 | #NUNIT 31 | *.VisualState.xml 32 | TestResult.xml 33 | 34 | # Build Results of an ATL Project 35 | [Dd]ebugPS/ 36 | [Rr]eleasePS/ 37 | dlldata.c 38 | 39 | *_i.c 40 | *_p.c 41 | *_i.h 42 | *.ilk 43 | *.meta 44 | *.obj 45 | *.pch 46 | *.pdb 47 | *.pgc 48 | *.pgd 49 | *.rsp 50 | *.sbr 51 | *.tlb 52 | *.tli 53 | *.tlh 54 | *.tmp 55 | *.tmp_proj 56 | *.log 57 | *.vspscc 58 | *.vssscc 59 | .builds 60 | *.pidb 61 | *.svclog 62 | *.scc 63 | 64 | # Visual C++ cache files 65 | ipch/ 66 | *.aps 67 | *.ncb 68 | *.opensdf 69 | *.sdf 70 | *.cachefile 71 | 72 | # Visual Studio profiler 73 | *.psess 74 | *.vsp 75 | *.vspx 76 | 77 | # TFS 2012 Local Workspace 78 | $tf/ 79 | 80 | # Guidance Automation Toolkit 81 | *.gpState 82 | 83 | # ReSharper is a .NET coding add-in 84 | _ReSharper*/ 85 | *.[Rr]e[Ss]harper 86 | *.DotSettings.user 87 | 88 | # JustCode is a .NET coding addin-in 89 | .JustCode 90 | 91 | # TeamCity is a build add-in 92 | _TeamCity* 93 | 94 | # DotCover is a Code Coverage Tool 95 | *.dotCover 96 | 97 | # NCrunch 98 | _NCrunch_* 99 | *.ncrunch* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.Publish.xml 127 | *.azurePubxml 128 | ## TODO: Comment the next line if you want to checkin your 129 | ## web deploy settings but do note that will include unencrypted 130 | ## passwords 131 | *.pubxml 132 | 133 | # NuGet Packages Directory 134 | packages/* 135 | ## TODO: If the tool you use requires repositories.config 136 | ## uncomment the next line 137 | #!packages/repositories.config 138 | 139 | # Enable "build/" folder in the NuGet Packages folder since 140 | # NuGet packages use it for MSBuild targets. 141 | # This line needs to be after the ignore of the build folder 142 | # (and the packages folder if the line above has been uncommented) 143 | !packages/build/ 144 | 145 | # Windows Azure Build Output 146 | csx 147 | *.build.csdef 148 | 149 | # Windows Store app package directory 150 | AppPackages/ 151 | 152 | # Others 153 | sql/ 154 | *.[Cc]ache 155 | ClientBin/ 156 | ~$* 157 | *~ 158 | *.dbmdl 159 | *.dbproj.schemaview 160 | *.pfx 161 | *.publishsettings 162 | node_modules/ 163 | 164 | # RIA/Silverlight projects 165 | Generated_Code/ 166 | 167 | # Backup & report files from converting an old project file to a newer 168 | # Visual Studio version. Backup files are not needed, because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | #LightSwitch generated files 187 | GeneratedArtifacts/ 188 | _Pvt_Extensions/ 189 | ModelManifest.xml 190 | 191 | # ========================= 192 | # Windows detritus 193 | # ========================= 194 | 195 | # Windows image file caches 196 | Thumbs.db 197 | ehthumbs.db 198 | 199 | # Folder config file 200 | Desktop.ini 201 | 202 | # Recycle Bin used on file shares 203 | $RECYCLE.BIN/ 204 | 205 | # Mac desktop service store files 206 | .DS_Store 207 | 208 | # ============================================================== 209 | # StyleCop files 210 | # ============================================================== 211 | [Ss]tyle[Cc]op.* 212 | # Cache settings file 213 | Settings.StyleCop 214 | # Allow stylecop file for StyleCop settings 215 | ![Ss]tyle[Cc]op.json 216 | 217 | # ============================================================== 218 | # Special Folders/Files that have to be in the repository 219 | # ============================================================== 220 | !protobuf/protobuf/*.user -------------------------------------------------------------------------------- /DocumentationAssistant.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31912.275 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentationAssistant", "DocumentationAssistant\DocumentationAssistant\DocumentationAssistant.csproj", "{7E144CCA-9B83-4C1A-83F0-2B8AD87C2CE0}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentationAssistant.Test", "DocumentationAssistant\DocumentationAssistant.Test\DocumentationAssistant.Test.csproj", "{31BA62E4-70F9-448E-B5B4-49C06992C042}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentationAssistant.Vsix", "DocumentationAssistant\DocumentationAssistant.Vsix\DocumentationAssistant.Vsix.csproj", "{213F13FD-8CFB-46A7-87F6-3A4ED53E2C68}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {7E144CCA-9B83-4C1A-83F0-2B8AD87C2CE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {7E144CCA-9B83-4C1A-83F0-2B8AD87C2CE0}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {7E144CCA-9B83-4C1A-83F0-2B8AD87C2CE0}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {7E144CCA-9B83-4C1A-83F0-2B8AD87C2CE0}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {31BA62E4-70F9-448E-B5B4-49C06992C042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {31BA62E4-70F9-448E-B5B4-49C06992C042}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {31BA62E4-70F9-448E-B5B4-49C06992C042}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {31BA62E4-70F9-448E-B5B4-49C06992C042}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {213F13FD-8CFB-46A7-87F6-3A4ED53E2C68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {213F13FD-8CFB-46A7-87F6-3A4ED53E2C68}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {213F13FD-8CFB-46A7-87F6-3A4ED53E2C68}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {213F13FD-8CFB-46A7-87F6-3A4ED53E2C68}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {B1C24040-E2CB-4A07-9065-4F50A8208F89} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/ClassUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using TestHelper; 6 | 7 | namespace DocumentationAssistant.Test 8 | { 9 | /// 10 | /// The class unit test. 11 | /// 12 | [TestClass] 13 | public class ClassUnitTest : CodeFixVerifier 14 | { 15 | /// 16 | /// The inherit doc test code. 17 | /// 18 | private const string InheritDocTestCode = @" 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | 23 | namespace ConsoleApp4 24 | { 25 | /// 26 | class ClassTester 27 | { 28 | } 29 | }"; 30 | 31 | /// 32 | /// The test code. 33 | /// 34 | private const string TestCode = @" 35 | using System; 36 | using System.Collections.Generic; 37 | using System.Text; 38 | 39 | namespace ConsoleApp4 40 | { 41 | class ClassTester 42 | { 43 | } 44 | }"; 45 | 46 | /// 47 | /// The test fix code. 48 | /// 49 | private const string TestFixCode = @" 50 | using System; 51 | using System.Collections.Generic; 52 | using System.Text; 53 | 54 | namespace ConsoleApp4 55 | { 56 | /// 57 | /// The class tester. 58 | /// 59 | class ClassTester 60 | { 61 | } 62 | }"; 63 | 64 | /// 65 | /// Nos diagnostics show. 66 | /// 67 | /// The test code. 68 | [DataTestMethod] 69 | [DataRow("")] 70 | [DataRow(InheritDocTestCode)] 71 | public void NoDiagnosticsShow(string testCode) 72 | { 73 | this.VerifyCSharpDiagnostic(testCode); 74 | } 75 | 76 | /// 77 | /// Shows diagnostic and fix. 78 | /// 79 | /// The test code. 80 | /// The fix code. 81 | /// The line. 82 | /// The column. 83 | [DataTestMethod] 84 | [DataRow(TestCode, TestFixCode, 8, 8)] 85 | public void ShowDiagnosticAndFix(string testCode, string fixCode, int line, int column) 86 | { 87 | var expected = new DiagnosticResult 88 | { 89 | Id = ClassAnalyzer.DiagnosticId, 90 | Message = ClassAnalyzer.MessageFormat, 91 | Severity = DiagnosticSeverity.Warning, 92 | Locations = 93 | new[] { 94 | new DiagnosticResultLocation("Test0.cs", line, column) 95 | } 96 | }; 97 | 98 | this.VerifyCSharpDiagnostic(testCode, expected); 99 | 100 | this.VerifyCSharpFix(testCode, fixCode); 101 | } 102 | 103 | /// 104 | /// Gets c sharp code fix provider. 105 | /// 106 | /// A CodeFixProvider. 107 | protected override CodeFixProvider GetCSharpCodeFixProvider() 108 | { 109 | return new ClassCodeFixProvider(); 110 | } 111 | 112 | /// 113 | /// Gets c sharp diagnostic analyzer. 114 | /// 115 | /// A DiagnosticAnalyzer. 116 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 117 | { 118 | return new ClassAnalyzer(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/ConstructorUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using TestHelper; 6 | 7 | namespace DocumentationAssistant.Test 8 | { 9 | /// 10 | /// The constructor unit test. 11 | /// 12 | [TestClass] 13 | public class ConstrcutorUnitTest : CodeFixVerifier 14 | { 15 | /// 16 | /// The inherit doc test code. 17 | /// 18 | private const string InheritDocTestCode = @" 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | 23 | namespace ConsoleApp4 24 | { 25 | class ConstructorTester 26 | { 27 | /// 28 | public ConstructorTester() 29 | { 30 | } 31 | } 32 | }"; 33 | 34 | /// 35 | /// The public constructor test code. 36 | /// 37 | private const string PublicConstructorTestCode = @" 38 | using System; 39 | using System.Collections.Generic; 40 | using System.Text; 41 | 42 | namespace ConsoleApp4 43 | { 44 | class ConstructorTester 45 | { 46 | public ConstructorTester() 47 | { 48 | } 49 | } 50 | }"; 51 | 52 | /// 53 | /// The public contructor test fix code. 54 | /// 55 | private const string PublicContructorTestFixCode = @" 56 | using System; 57 | using System.Collections.Generic; 58 | using System.Text; 59 | 60 | namespace ConsoleApp4 61 | { 62 | class ConstructorTester 63 | { 64 | /// 65 | /// Initializes a new instance of the class. 66 | /// 67 | public ConstructorTester() 68 | { 69 | } 70 | } 71 | }"; 72 | 73 | /// 74 | /// The private constructor test code. 75 | /// 76 | private const string PrivateConstructorTestCode = @" 77 | using System; 78 | using System.Collections.Generic; 79 | using System.Text; 80 | 81 | namespace ConsoleApp4 82 | { 83 | class ConstructorTester 84 | { 85 | private ConstructorTester() 86 | { 87 | } 88 | } 89 | }"; 90 | 91 | /// 92 | /// The private contructor test fix code. 93 | /// 94 | private const string PrivateContructorTestFixCode = @" 95 | using System; 96 | using System.Collections.Generic; 97 | using System.Text; 98 | 99 | namespace ConsoleApp4 100 | { 101 | class ConstructorTester 102 | { 103 | /// 104 | /// Prevents a default instance of the class from being created. 105 | /// 106 | private ConstructorTester() 107 | { 108 | } 109 | } 110 | }"; 111 | 112 | /// 113 | /// The public constructor test code. 114 | /// 115 | private const string PublicConstructorWithBooleanParameterTestCode = @" 116 | using System; 117 | using System.Collections.Generic; 118 | using System.Text; 119 | 120 | namespace ConsoleApp4 121 | { 122 | class ConstructorTester 123 | { 124 | public ConstructorTester(bool isRed, bool? isAssociatedWithAllProduct) 125 | { 126 | } 127 | } 128 | }"; 129 | 130 | /// 131 | /// The public contructor test fix code. 132 | /// 133 | private const string PublicContructorWithBooleanParameterTestFixCode = @" 134 | using System; 135 | using System.Collections.Generic; 136 | using System.Text; 137 | 138 | namespace ConsoleApp4 139 | { 140 | class ConstructorTester 141 | { 142 | /// 143 | /// Initializes a new instance of the class. 144 | /// 145 | /// If true, is red. 146 | /// If true, is associated with all product. 147 | public ConstructorTester(bool isRed, bool? isAssociatedWithAllProduct) 148 | { 149 | } 150 | } 151 | }"; 152 | /// 153 | /// Nos diagnostics show. 154 | /// 155 | /// The test code. 156 | [DataTestMethod] 157 | [DataRow("")] 158 | [DataRow(InheritDocTestCode)] 159 | public void NoDiagnosticsShow(string testCode) 160 | { 161 | this.VerifyCSharpDiagnostic(testCode); 162 | } 163 | 164 | /// 165 | /// Shows diagnostic and fix. 166 | /// 167 | /// The test code. 168 | /// The fix code. 169 | /// The line. 170 | /// The column. 171 | [DataTestMethod] 172 | [DataRow(PublicConstructorTestCode, PublicContructorTestFixCode, 10, 10)] 173 | [DataRow(PrivateConstructorTestCode, PrivateContructorTestFixCode, 10, 11)] 174 | [DataRow(PublicConstructorWithBooleanParameterTestCode, PublicContructorWithBooleanParameterTestFixCode, 10, 10)] 175 | public void ShowDiagnosticAndFix(string testCode, string fixCode, int line, int column) 176 | { 177 | var expected = new DiagnosticResult 178 | { 179 | Id = ConstructorAnalyzer.DiagnosticId, 180 | Message = ConstructorAnalyzer.MessageFormat, 181 | Severity = DiagnosticSeverity.Warning, 182 | Locations = 183 | new[] { 184 | new DiagnosticResultLocation("Test0.cs", line, column) 185 | } 186 | }; 187 | 188 | this.VerifyCSharpDiagnostic(testCode, expected); 189 | 190 | this.VerifyCSharpFix(testCode, fixCode); 191 | } 192 | 193 | /// 194 | /// Gets c sharp code fix provider. 195 | /// 196 | /// A CodeFixProvider. 197 | protected override CodeFixProvider GetCSharpCodeFixProvider() 198 | { 199 | return new ConstructorCodeFixProvider(); 200 | } 201 | 202 | /// 203 | /// Gets c sharp diagnostic analyzer. 204 | /// 205 | /// A DiagnosticAnalyzer. 206 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 207 | { 208 | return new ConstructorAnalyzer(); 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/DocumentationAssistant.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/EnumUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using TestHelper; 6 | 7 | namespace DocumentationAssistant.Test 8 | { 9 | /// 10 | /// The enum unit test. 11 | /// 12 | [TestClass] 13 | public class EnumUnitTest : CodeFixVerifier 14 | { 15 | /// 16 | /// The test code. 17 | /// 18 | private const string TestCode = @" 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | 23 | namespace Test 24 | { 25 | enum EnumTester 26 | { 27 | } 28 | }"; 29 | 30 | /// 31 | /// The test fix code. 32 | /// 33 | private const string TestFixCode = @" 34 | using System; 35 | using System.Collections.Generic; 36 | using System.Text; 37 | 38 | namespace Test 39 | { 40 | /// 41 | /// The enum tester. 42 | /// 43 | enum EnumTester 44 | { 45 | } 46 | }"; 47 | 48 | /// 49 | /// Nos diagnostics show. 50 | /// 51 | /// The test code. 52 | [DataTestMethod] 53 | [DataRow("")] 54 | public void NoDiagnosticsShow(string testCode) 55 | { 56 | this.VerifyCSharpDiagnostic(testCode); 57 | } 58 | 59 | /// 60 | /// Shows diagnostic and fix. 61 | /// 62 | /// The test code. 63 | /// The fix code. 64 | /// The line. 65 | /// The column. 66 | [DataTestMethod] 67 | [DataRow(TestCode, TestFixCode, 8, 7)] 68 | public void ShowDiagnosticAndFix(string testCode, string fixCode, int line, int column) 69 | { 70 | DiagnosticResult expected = new DiagnosticResult 71 | { 72 | Id = EnumAnalyzer.DiagnosticId, 73 | Message = EnumAnalyzer.MessageFormat, 74 | Severity = DiagnosticSeverity.Warning, 75 | Locations = 76 | new[] { 77 | new DiagnosticResultLocation("Test0.cs", line, column) 78 | } 79 | }; 80 | 81 | this.VerifyCSharpDiagnostic(testCode, expected); 82 | 83 | this.VerifyCSharpFix(testCode, fixCode); 84 | } 85 | 86 | /// 87 | /// Gets c sharp code fix provider. 88 | /// 89 | /// A CodeFixProvider. 90 | protected override CodeFixProvider GetCSharpCodeFixProvider() 91 | { 92 | return new EnumCodeFixProvider(); 93 | } 94 | 95 | /// 96 | /// Gets c sharp diagnostic analyzer. 97 | /// 98 | /// A DiagnosticAnalyzer. 99 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 100 | { 101 | return new EnumAnalyzer(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/FieldUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using TestHelper; 6 | 7 | namespace DocumentationAssistant.Test 8 | { 9 | /// 10 | /// The field unit test. 11 | /// 12 | [TestClass] 13 | public class FieldUnitTest : CodeFixVerifier 14 | { 15 | /// 16 | /// The inherit doc test code. 17 | /// 18 | private const string InheritDocTestCode = @" 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | 23 | namespace ConsoleApp4 24 | { 25 | class FieldTester 26 | { 27 | /// 28 | const int ConstFieldTester = 666; 29 | 30 | public FieldTester() 31 | { 32 | } 33 | } 34 | }"; 35 | 36 | /// 37 | /// The const field test code. 38 | /// 39 | private const string ConstFieldTestCode = @" 40 | using System; 41 | using System.Collections.Generic; 42 | using System.Text; 43 | 44 | namespace ConsoleApp4 45 | { 46 | class FieldTester 47 | { 48 | const int ConstFieldTester = 666; 49 | 50 | public FieldTester() 51 | { 52 | } 53 | } 54 | }"; 55 | 56 | /// 57 | /// The const field test fix code. 58 | /// 59 | private const string ConstFieldTestFixCode = @" 60 | using System; 61 | using System.Collections.Generic; 62 | using System.Text; 63 | 64 | namespace ConsoleApp4 65 | { 66 | class FieldTester 67 | { 68 | /// 69 | /// The const field tester. 70 | /// 71 | const int ConstFieldTester = 666; 72 | 73 | public FieldTester() 74 | { 75 | } 76 | } 77 | }"; 78 | 79 | /// 80 | /// Nos diagnostics show. 81 | /// 82 | /// The test code. 83 | [DataTestMethod] 84 | [DataRow("")] 85 | [DataRow(InheritDocTestCode)] 86 | public void NoDiagnosticsShow(string testCode) 87 | { 88 | this.VerifyCSharpDiagnostic(testCode); 89 | } 90 | 91 | /// 92 | /// Shows diagnostic and fix. 93 | /// 94 | /// The test code. 95 | /// The fix code. 96 | /// The line. 97 | /// The column. 98 | [DataTestMethod] 99 | [DataRow(ConstFieldTestCode, ConstFieldTestFixCode, 10, 13)] 100 | public void ShowDiagnosticAndFix(string testCode, string fixCode, int line, int column) 101 | { 102 | var expected = new DiagnosticResult 103 | { 104 | Id = FieldAnalyzer.DiagnosticId, 105 | Message = FieldAnalyzer.MessageFormat, 106 | Severity = DiagnosticSeverity.Warning, 107 | Locations = 108 | new[] { 109 | new DiagnosticResultLocation("Test0.cs", line, column) 110 | } 111 | }; 112 | 113 | this.VerifyCSharpDiagnostic(testCode, expected); 114 | 115 | this.VerifyCSharpFix(testCode, fixCode); 116 | } 117 | 118 | /// 119 | /// Gets c sharp code fix provider. 120 | /// 121 | /// A CodeFixProvider. 122 | protected override CodeFixProvider GetCSharpCodeFixProvider() 123 | { 124 | return new FieldCodeFixProvider(); 125 | } 126 | 127 | /// 128 | /// Gets c sharp diagnostic analyzer. 129 | /// 130 | /// A DiagnosticAnalyzer. 131 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 132 | { 133 | return new FieldAnalyzer(); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/Helpers/CodeFixVerifier.Helper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.Formatting; 7 | using Microsoft.CodeAnalysis.Simplification; 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 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/Helpers/DiagnosticResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis; 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 | } 88 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/Helpers/DiagnosticVerifier.Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | using Microsoft.CodeAnalysis.Text; 9 | 10 | namespace TestHelper 11 | { 12 | /// 13 | /// Class for turning strings into documents and getting the diagnostics on them 14 | /// All methods are static 15 | /// 16 | public abstract partial class DiagnosticVerifier 17 | { 18 | private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); 19 | private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); 20 | private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); 21 | private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); 22 | 23 | internal const string DefaultFilePathPrefix = "Test"; 24 | internal const string CSharpDefaultFileExt = "cs"; 25 | internal const string VisualBasicDefaultExt = "vb"; 26 | internal const string TestProjectName = "TestProject"; 27 | 28 | #region Get Diagnostics 29 | 30 | /// 31 | /// Given classes in the form of strings, their language, and an IDiagnosticAnalyzer to apply to it, return the diagnostics found in the string after converting it to a document. 32 | /// 33 | /// Classes in the form of strings 34 | /// The language the source classes are in 35 | /// The analyzer to be run on the sources 36 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location 37 | private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) 38 | { 39 | return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); 40 | } 41 | 42 | /// 43 | /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. 44 | /// The returned diagnostics are then ordered by location in the source document. 45 | /// 46 | /// The analyzer to run on the documents 47 | /// The Documents that the analyzer will be run on 48 | /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location 49 | protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) 50 | { 51 | var projects = new HashSet(); 52 | foreach (var document in documents) 53 | { 54 | projects.Add(document.Project); 55 | } 56 | 57 | var diagnostics = new List(); 58 | foreach (var project in projects) 59 | { 60 | var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); 61 | var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; 62 | foreach (var diag in diags) 63 | { 64 | if (diag.Location == Location.None || diag.Location.IsInMetadata) 65 | { 66 | diagnostics.Add(diag); 67 | } 68 | else 69 | { 70 | for (int i = 0; i < documents.Length; i++) 71 | { 72 | var document = documents[i]; 73 | var tree = document.GetSyntaxTreeAsync().Result; 74 | if (tree == diag.Location.SourceTree) 75 | { 76 | diagnostics.Add(diag); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | var results = SortDiagnostics(diagnostics); 84 | diagnostics.Clear(); 85 | return results; 86 | } 87 | 88 | /// 89 | /// Sort diagnostics by location in source document 90 | /// 91 | /// The list of Diagnostics to be sorted 92 | /// An IEnumerable containing the Diagnostics in order of Location 93 | private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) 94 | { 95 | return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); 96 | } 97 | 98 | #endregion 99 | 100 | #region Set up compilation and documents 101 | /// 102 | /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. 103 | /// 104 | /// Classes in the form of strings 105 | /// The language the source code is in 106 | /// A Tuple containing the Documents produced from the sources and their TextSpans if relevant 107 | private static Document[] GetDocuments(string[] sources, string language) 108 | { 109 | if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) 110 | { 111 | throw new ArgumentException("Unsupported Language"); 112 | } 113 | 114 | var project = CreateProject(sources, language); 115 | var documents = project.Documents.ToArray(); 116 | 117 | if (sources.Length != documents.Length) 118 | { 119 | throw new InvalidOperationException("Amount of sources did not match amount of Documents created"); 120 | } 121 | 122 | return documents; 123 | } 124 | 125 | /// 126 | /// Create a Document from a string through creating a project that contains it. 127 | /// 128 | /// Classes in the form of a string 129 | /// The language the source code is in 130 | /// A Document created from the source string 131 | protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) 132 | { 133 | return CreateProject(new[] { source }, language).Documents.First(); 134 | } 135 | 136 | /// 137 | /// Create a project using the inputted strings as sources. 138 | /// 139 | /// Classes in the form of strings 140 | /// The language the source code is in 141 | /// A Project created out of the Documents created from the source strings 142 | private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) 143 | { 144 | string fileNamePrefix = DefaultFilePathPrefix; 145 | string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; 146 | 147 | var projectId = ProjectId.CreateNewId(debugName: TestProjectName); 148 | 149 | var solution = new AdhocWorkspace() 150 | .CurrentSolution 151 | .AddProject(projectId, TestProjectName, TestProjectName, language) 152 | .AddMetadataReference(projectId, CorlibReference) 153 | .AddMetadataReference(projectId, SystemCoreReference) 154 | .AddMetadataReference(projectId, CSharpSymbolsReference) 155 | .AddMetadataReference(projectId, CodeAnalysisReference); 156 | 157 | int count = 0; 158 | foreach (var source in sources) 159 | { 160 | var newFileName = fileNamePrefix + count + "." + fileExt; 161 | var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); 162 | solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); 163 | count++; 164 | } 165 | return solution.GetProject(projectId); 166 | } 167 | #endregion 168 | } 169 | } 170 | 171 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/InterfaceUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using TestHelper; 6 | 7 | namespace DocumentationAssistant.Test 8 | { 9 | /// 10 | /// The interface unit test. 11 | /// 12 | [TestClass] 13 | public class InterfaceUnitTest : CodeFixVerifier 14 | { 15 | /// 16 | /// The test code. 17 | /// 18 | private const string TestCode = @" 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | 23 | namespace ConsoleApp4 24 | { 25 | interface IInterfaceTester 26 | { 27 | } 28 | }"; 29 | 30 | /// 31 | /// The test fix code. 32 | /// 33 | private const string TestFixCode = @" 34 | using System; 35 | using System.Collections.Generic; 36 | using System.Text; 37 | 38 | namespace ConsoleApp4 39 | { 40 | /// 41 | /// The interface tester. 42 | /// 43 | interface IInterfaceTester 44 | { 45 | } 46 | }"; 47 | 48 | /// 49 | /// Nos diagnostics show. 50 | /// 51 | /// The test code. 52 | [DataTestMethod] 53 | [DataRow("")] 54 | public void NoDiagnosticsShow(string testCode) 55 | { 56 | this.VerifyCSharpDiagnostic(testCode); 57 | } 58 | 59 | /// 60 | /// Shows diagnostic and fix. 61 | /// 62 | /// The test code. 63 | /// The fix code. 64 | /// The line. 65 | /// The column. 66 | [DataTestMethod] 67 | [DataRow(TestCode, TestFixCode, 8, 12)] 68 | public void ShowDiagnosticAndFix(string testCode, string fixCode, int line, int column) 69 | { 70 | var expected = new DiagnosticResult 71 | { 72 | Id = InterfaceAnalyzer.DiagnosticId, 73 | Message = InterfaceAnalyzer.MessageFormat, 74 | Severity = DiagnosticSeverity.Warning, 75 | Locations = 76 | new[] { 77 | new DiagnosticResultLocation("Test0.cs", line, column) 78 | } 79 | }; 80 | 81 | this.VerifyCSharpDiagnostic(testCode, expected); 82 | 83 | this.VerifyCSharpFix(testCode, fixCode); 84 | } 85 | 86 | /// 87 | /// Gets c sharp code fix provider. 88 | /// 89 | /// A CodeFixProvider. 90 | protected override CodeFixProvider GetCSharpCodeFixProvider() 91 | { 92 | return new InterfaceCodeFixProvider(); 93 | } 94 | 95 | /// 96 | /// Gets c sharp diagnostic analyzer. 97 | /// 98 | /// A DiagnosticAnalyzer. 99 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 100 | { 101 | return new InterfaceAnalyzer(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/MethodUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using TestHelper; 6 | 7 | namespace DocumentationAssistant.Test 8 | { 9 | /// 10 | /// The method unit test. 11 | /// 12 | [TestClass] 13 | public class MethodUnitTest : CodeFixVerifier 14 | { 15 | /// 16 | /// The inherit doc test code. 17 | /// 18 | private const string InheritDocTestCode = @" 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | 23 | namespace ConsoleApp4 24 | { 25 | public class MethodTester 26 | { 27 | /// 28 | public void ShowBasicMethodTester() 29 | { 30 | } 31 | } 32 | }"; 33 | 34 | /// 35 | /// The basic test code. 36 | /// 37 | private const string BasicTestCode = @" 38 | using System; 39 | using System.Collections.Generic; 40 | using System.Text; 41 | 42 | namespace ConsoleApp4 43 | { 44 | public class MethodTester 45 | { 46 | public void ShowBasicMethodTester() 47 | { 48 | } 49 | } 50 | }"; 51 | 52 | /// 53 | /// The basic test fix code. 54 | /// 55 | private const string BasicTestFixCode = @" 56 | using System; 57 | using System.Collections.Generic; 58 | using System.Text; 59 | 60 | namespace ConsoleApp4 61 | { 62 | public class MethodTester 63 | { 64 | /// 65 | /// Shows the basic method tester. 66 | /// 67 | public void ShowBasicMethodTester() 68 | { 69 | } 70 | } 71 | }"; 72 | 73 | /// 74 | /// The method with parameter test code. 75 | /// 76 | private const string MethodWithParameterTestCode = @" 77 | using System; 78 | using System.Collections.Generic; 79 | using System.Text; 80 | 81 | namespace ConsoleApp4 82 | { 83 | public class MethodTester 84 | { 85 | public void ShowMethodWithParameterTester(string param1, int param2, bool param3) 86 | { 87 | } 88 | } 89 | }"; 90 | /// 91 | /// The method with parameter test fix code. 92 | /// 93 | private const string MethodWithParameterTestFixCode = @" 94 | using System; 95 | using System.Collections.Generic; 96 | using System.Text; 97 | 98 | namespace ConsoleApp4 99 | { 100 | public class MethodTester 101 | { 102 | /// 103 | /// Shows the method with parameter tester. 104 | /// 105 | /// The param1. 106 | /// The param2. 107 | /// If true, param3. 108 | public void ShowMethodWithParameterTester(string param1, int param2, bool param3) 109 | { 110 | } 111 | } 112 | }"; 113 | 114 | /// 115 | /// The method with parameter test code. 116 | /// 117 | private const string MethodWithBooleanParameterTestCode = @" 118 | using System; 119 | using System.Collections.Generic; 120 | using System.Text; 121 | 122 | namespace ConsoleApp4 123 | { 124 | public class MethodTester 125 | { 126 | public void ShowMethodWithBooleanParameterTester(bool isRed, bool? isAssociatedWithAllProduct) 127 | { 128 | } 129 | } 130 | }"; 131 | /// 132 | /// The method with parameter test fix code. 133 | /// 134 | private const string MethodWithBooleanParameterTestFixCode = @" 135 | using System; 136 | using System.Collections.Generic; 137 | using System.Text; 138 | 139 | namespace ConsoleApp4 140 | { 141 | public class MethodTester 142 | { 143 | /// 144 | /// Shows the method with boolean parameter tester. 145 | /// 146 | /// If true, is red. 147 | /// If true, is associated with all product. 148 | public void ShowMethodWithBooleanParameterTester(bool isRed, bool? isAssociatedWithAllProduct) 149 | { 150 | } 151 | } 152 | }"; 153 | 154 | /// 155 | /// The method with parameter test code. 156 | /// 157 | private const string MethodWithNullableStructParameterTestCode = @" 158 | using System; 159 | using System.Collections.Generic; 160 | using System.Text; 161 | 162 | namespace ConsoleApp4 163 | { 164 | public class MethodTester 165 | { 166 | public void Show(DiagnosticResult? param1, int param2, bool param3) 167 | { 168 | } 169 | } 170 | }"; 171 | 172 | /// 173 | /// The method with parameter test fix code. 174 | /// 175 | private const string MethodWithNullableStructParameterTestFixCode = @" 176 | using System; 177 | using System.Collections.Generic; 178 | using System.Text; 179 | 180 | namespace ConsoleApp4 181 | { 182 | public class MethodTester 183 | { 184 | /// 185 | /// Shows the. 186 | /// 187 | /// The param1. 188 | /// The param2. 189 | /// If true, param3. 190 | public void Show(DiagnosticResult? param1, int param2, bool param3) 191 | { 192 | } 193 | } 194 | }"; 195 | 196 | /// 197 | /// The method with return test code. 198 | /// 199 | private const string MethodWithReturnTestCode = @" 200 | using System; 201 | using System.Collections.Generic; 202 | using System.Text; 203 | 204 | namespace ConsoleApp4 205 | { 206 | public class MethodTester 207 | { 208 | public MethodTester ShowMethodWithReturnTester() 209 | { 210 | return null; 211 | } 212 | } 213 | }"; 214 | 215 | /// 216 | /// The method with return test fix code. 217 | /// 218 | private const string MethodWithReturnTestFixCode = @" 219 | using System; 220 | using System.Collections.Generic; 221 | using System.Text; 222 | 223 | namespace ConsoleApp4 224 | { 225 | public class MethodTester 226 | { 227 | /// 228 | /// Shows the method with return tester. 229 | /// 230 | /// A MethodTester. 231 | public MethodTester ShowMethodWithReturnTester() 232 | { 233 | return null; 234 | } 235 | } 236 | }"; 237 | 238 | /// 239 | /// The method with string return test code. 240 | /// 241 | private const string MethodWithStringReturnTestCode = @" 242 | using System; 243 | using System.Collections.Generic; 244 | using System.Text; 245 | 246 | namespace ConsoleApp4 247 | { 248 | public class MethodTester 249 | { 250 | public string ShowMethodWithStringReturnTester() 251 | { 252 | return null; 253 | } 254 | } 255 | }"; 256 | 257 | /// 258 | /// The method with string return test fix code. 259 | /// 260 | private const string MethodWithStringReturnTestFixCode = @" 261 | using System; 262 | using System.Collections.Generic; 263 | using System.Text; 264 | 265 | namespace ConsoleApp4 266 | { 267 | public class MethodTester 268 | { 269 | /// 270 | /// Shows the method with string return tester. 271 | /// 272 | /// A string. 273 | public string ShowMethodWithStringReturnTester() 274 | { 275 | return null; 276 | } 277 | } 278 | }"; 279 | 280 | /// 281 | /// The method with object return test code. 282 | /// 283 | private const string MethodWithObjectReturnTestCode = @" 284 | using System; 285 | using System.Collections.Generic; 286 | using System.Text; 287 | 288 | namespace ConsoleApp4 289 | { 290 | public class MethodTester 291 | { 292 | public object ShowMethodWithObjectReturnTester() 293 | { 294 | return null; 295 | } 296 | } 297 | }"; 298 | 299 | /// 300 | /// The method with object return test fix code. 301 | /// 302 | private const string MethodWithObjectReturnTestFixCode = @" 303 | using System; 304 | using System.Collections.Generic; 305 | using System.Text; 306 | 307 | namespace ConsoleApp4 308 | { 309 | public class MethodTester 310 | { 311 | /// 312 | /// Shows the method with object return tester. 313 | /// 314 | /// An object. 315 | public object ShowMethodWithObjectReturnTester() 316 | { 317 | return null; 318 | } 319 | } 320 | }"; 321 | 322 | /// 323 | /// The method with int return test code. 324 | /// 325 | private const string MethodWithIntReturnTestCode = @" 326 | using System; 327 | using System.Collections.Generic; 328 | using System.Text; 329 | 330 | namespace ConsoleApp4 331 | { 332 | public class MethodTester 333 | { 334 | public int ShowMethodWithIntReturnTester() 335 | { 336 | return null; 337 | } 338 | } 339 | }"; 340 | 341 | /// 342 | /// The method with int return test fix code. 343 | /// 344 | private const string MethodWithIntReturnTestFixCode = @" 345 | using System; 346 | using System.Collections.Generic; 347 | using System.Text; 348 | 349 | namespace ConsoleApp4 350 | { 351 | public class MethodTester 352 | { 353 | /// 354 | /// Shows the method with int return tester. 355 | /// 356 | /// An int. 357 | public int ShowMethodWithIntReturnTester() 358 | { 359 | return null; 360 | } 361 | } 362 | }"; 363 | 364 | /// 365 | /// The method with list int return test code. 366 | /// 367 | private const string MethodWithListIntReturnTestCode = @" 368 | using System; 369 | using System.Collections.Generic; 370 | using System.Text; 371 | 372 | namespace ConsoleApp4 373 | { 374 | public class MethodTester 375 | { 376 | public List ShowMethodWithListIntReturnTester() 377 | { 378 | return null; 379 | } 380 | } 381 | }"; 382 | 383 | /// 384 | /// The method with list int return test fix code. 385 | /// 386 | private const string MethodWithListIntReturnTestFixCode = @" 387 | using System; 388 | using System.Collections.Generic; 389 | using System.Text; 390 | 391 | namespace ConsoleApp4 392 | { 393 | public class MethodTester 394 | { 395 | /// 396 | /// Shows the method with list int return tester. 397 | /// 398 | /// A list of int. 399 | public List ShowMethodWithListIntReturnTester() 400 | { 401 | return null; 402 | } 403 | } 404 | }"; 405 | 406 | /// 407 | /// The method with list list int return test code. 408 | /// 409 | private const string MethodWithListListIntReturnTestCode = @" 410 | using System; 411 | using System.Collections.Generic; 412 | using System.Text; 413 | 414 | namespace ConsoleApp4 415 | { 416 | public class MethodTester 417 | { 418 | public List> ShowMethodWithListListIntReturnTester() 419 | { 420 | return null; 421 | } 422 | } 423 | }"; 424 | 425 | /// 426 | /// The method with list list int return test fix code. 427 | /// 428 | private const string MethodWithListListIntReturnTestFixCode = @" 429 | using System; 430 | using System.Collections.Generic; 431 | using System.Text; 432 | 433 | namespace ConsoleApp4 434 | { 435 | public class MethodTester 436 | { 437 | /// 438 | /// Shows the method with list list int return tester. 439 | /// 440 | /// A list of List. 441 | public List> ShowMethodWithListListIntReturnTester() 442 | { 443 | return null; 444 | } 445 | } 446 | }"; 447 | 448 | /// 449 | /// The method with list qualified name return test code. 450 | /// 451 | private const string MethodWithListQualifiedNameReturnTestCode = @" 452 | using System; 453 | using System.Collections.Generic; 454 | using System.Text; 455 | 456 | namespace ConsoleApp4 457 | { 458 | public class MethodTester 459 | { 460 | public List ShowMethodWithListQualifiedNameReturnTester() 461 | { 462 | return null; 463 | } 464 | } 465 | }"; 466 | 467 | /// 468 | /// The method with list qualified name return test fix code. 469 | /// 470 | private const string MethodWithListQualifiedNameReturnTestFixCode = @" 471 | using System; 472 | using System.Collections.Generic; 473 | using System.Text; 474 | 475 | namespace ConsoleApp4 476 | { 477 | public class MethodTester 478 | { 479 | /// 480 | /// Shows the method with list qualified name return tester. 481 | /// 482 | /// A list of A.B. 483 | public List ShowMethodWithListQualifiedNameReturnTester() 484 | { 485 | return null; 486 | } 487 | } 488 | }"; 489 | 490 | /// 491 | /// Nos diagnostics show. 492 | /// 493 | /// The test code. 494 | [DataTestMethod] 495 | [DataRow("")] 496 | [DataRow(InheritDocTestCode)] 497 | public void NoDiagnosticsShow(string testCode) 498 | { 499 | this.VerifyCSharpDiagnostic(testCode); 500 | } 501 | 502 | /// 503 | /// Shows diagnostic and fix. 504 | /// 505 | /// The test code. 506 | /// The fix code. 507 | /// The line. 508 | /// The column. 509 | [DataTestMethod] 510 | [DataRow(BasicTestCode, BasicTestFixCode, 10, 15)] 511 | [DataRow(MethodWithParameterTestCode, MethodWithParameterTestFixCode, 10, 15)] 512 | [DataRow(MethodWithBooleanParameterTestCode, MethodWithBooleanParameterTestFixCode, 10, 15)] 513 | [DataRow(MethodWithNullableStructParameterTestCode, MethodWithNullableStructParameterTestFixCode, 10, 15)] 514 | [DataRow(MethodWithReturnTestCode, MethodWithReturnTestFixCode, 10, 23)] 515 | [DataRow(MethodWithStringReturnTestCode, MethodWithStringReturnTestFixCode, 10, 17)] 516 | [DataRow(MethodWithObjectReturnTestCode, MethodWithObjectReturnTestFixCode, 10, 17)] 517 | [DataRow(MethodWithIntReturnTestCode, MethodWithIntReturnTestFixCode, 10, 14)] 518 | [DataRow(MethodWithListIntReturnTestCode, MethodWithListIntReturnTestFixCode, 10, 20)] 519 | [DataRow(MethodWithListListIntReturnTestCode, MethodWithListListIntReturnTestFixCode, 10, 26)] 520 | [DataRow(MethodWithListQualifiedNameReturnTestCode, MethodWithListQualifiedNameReturnTestFixCode, 10, 20)] 521 | public void ShowDiagnosticAndFix(string testCode, string fixCode, int line, int column) 522 | { 523 | var expected = new DiagnosticResult 524 | { 525 | Id = MethodAnalyzer.DiagnosticId, 526 | Message = MethodAnalyzer.MessageFormat, 527 | Severity = DiagnosticSeverity.Warning, 528 | Locations = 529 | new[] { 530 | new DiagnosticResultLocation("Test0.cs", line, column) 531 | } 532 | }; 533 | 534 | this.VerifyCSharpDiagnostic(testCode, expected); 535 | 536 | this.VerifyCSharpFix(testCode, fixCode); 537 | } 538 | 539 | /// 540 | /// Gets c sharp code fix provider. 541 | /// 542 | /// A CodeFixProvider. 543 | protected override CodeFixProvider GetCSharpCodeFixProvider() 544 | { 545 | return new MethodCodeFixProvider(); 546 | } 547 | 548 | /// 549 | /// Gets c sharp diagnostic analyzer. 550 | /// 551 | /// A DiagnosticAnalyzer. 552 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 553 | { 554 | return new MethodAnalyzer(); 555 | } 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/PropertyUnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CodeFixes; 3 | using Microsoft.CodeAnalysis.Diagnostics; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using TestHelper; 6 | 7 | namespace DocumentationAssistant.Test 8 | { 9 | /// 10 | /// The property unit test. 11 | /// 12 | [TestClass] 13 | public class PropertyUnitTest : CodeFixVerifier 14 | { 15 | /// 16 | /// The inherit doc test code. 17 | /// 18 | private const string InheritDocTestCode = @" 19 | using System; 20 | using System.Collections.Generic; 21 | using System.Text; 22 | 23 | namespace ConsoleApp4 24 | { 25 | public class PropertyTester 26 | { 27 | /// 28 | public string PersonName { get; set; } 29 | } 30 | }"; 31 | 32 | /// 33 | /// The property with getter setter test code. 34 | /// 35 | private const string PropertyWithGetterSetterTestCode = @" 36 | using System; 37 | using System.Collections.Generic; 38 | using System.Text; 39 | 40 | namespace ConsoleApp4 41 | { 42 | public class PropertyTester 43 | { 44 | public string PersonName { get; set; } 45 | } 46 | }"; 47 | 48 | /// 49 | /// The property with getter setter test fix code. 50 | /// 51 | private const string PropertyWithGetterSetterTestFixCode = @" 52 | using System; 53 | using System.Collections.Generic; 54 | using System.Text; 55 | 56 | namespace ConsoleApp4 57 | { 58 | public class PropertyTester 59 | { 60 | /// 61 | /// Gets or sets the person name. 62 | /// 63 | public string PersonName { get; set; } 64 | } 65 | }"; 66 | 67 | /// 68 | /// The property only getter test code. 69 | /// 70 | private const string PropertyOnlyGetterTestCode = @" 71 | using System; 72 | using System.Collections.Generic; 73 | using System.Text; 74 | 75 | namespace ConsoleApp4 76 | { 77 | public class PropertyTester 78 | { 79 | public string PersonName { get; } 80 | } 81 | }"; 82 | 83 | /// 84 | /// The property only getter test fix code. 85 | /// 86 | private const string PropertyOnlyGetterTestFixCode = @" 87 | using System; 88 | using System.Collections.Generic; 89 | using System.Text; 90 | 91 | namespace ConsoleApp4 92 | { 93 | public class PropertyTester 94 | { 95 | /// 96 | /// Gets the person name. 97 | /// 98 | public string PersonName { get; } 99 | } 100 | }"; 101 | 102 | /// 103 | /// The property private getter test fix code. 104 | /// 105 | private const string PropertyPrivateGetterTestCode = @" 106 | using System; 107 | using System.Collections.Generic; 108 | using System.Text; 109 | 110 | namespace ConsoleApp4 111 | { 112 | public class PropertyTester 113 | { 114 | public string PersonName { get; private set; } 115 | } 116 | }"; 117 | 118 | /// 119 | /// The property private getter test fix code. 120 | /// 121 | private const string PropertyPrivateGetterTestFixCode = @" 122 | using System; 123 | using System.Collections.Generic; 124 | using System.Text; 125 | 126 | namespace ConsoleApp4 127 | { 128 | public class PropertyTester 129 | { 130 | /// 131 | /// Gets the person name. 132 | /// 133 | public string PersonName { get; private set; } 134 | } 135 | }"; 136 | 137 | /// 138 | /// The property internal getter test fix code. 139 | /// 140 | private const string PropertyInternalGetterTestCode = @" 141 | using System; 142 | using System.Collections.Generic; 143 | using System.Text; 144 | 145 | namespace ConsoleApp4 146 | { 147 | public class PropertyTester 148 | { 149 | public string PersonName { get; internal set; } 150 | } 151 | }"; 152 | 153 | /// 154 | /// The property internal getter test fix code. 155 | /// 156 | private const string PropertyInternalGetterTestFixCode = @" 157 | using System; 158 | using System.Collections.Generic; 159 | using System.Text; 160 | 161 | namespace ConsoleApp4 162 | { 163 | public class PropertyTester 164 | { 165 | /// 166 | /// Gets the person name. 167 | /// 168 | public string PersonName { get; internal set; } 169 | } 170 | }"; 171 | 172 | /// 173 | /// The boolean property test code. 174 | /// 175 | private const string BooleanPropertyTestCode = @" 176 | using System; 177 | using System.Collections.Generic; 178 | using System.Text; 179 | 180 | namespace ConsoleApp4 181 | { 182 | public class PropertyTester 183 | { 184 | public bool IsTesterStarted { get; set; } 185 | } 186 | }"; 187 | 188 | /// 189 | /// The boolean property test fix code. 190 | /// 191 | private const string BooleanPropertyTestFixCode = @" 192 | using System; 193 | using System.Collections.Generic; 194 | using System.Text; 195 | 196 | namespace ConsoleApp4 197 | { 198 | public class PropertyTester 199 | { 200 | /// 201 | /// Gets or sets a value indicating whether tester is started. 202 | /// 203 | public bool IsTesterStarted { get; set; } 204 | } 205 | }"; 206 | 207 | /// 208 | /// The nullable boolean property test code. 209 | /// 210 | private const string NullableBooleanPropertyTestCode = @" 211 | using System; 212 | using System.Collections.Generic; 213 | using System.Text; 214 | 215 | namespace ConsoleApp4 216 | { 217 | public class PropertyTester 218 | { 219 | public bool? IsTesterStarted { get; set; } 220 | } 221 | }"; 222 | 223 | /// 224 | /// The nullable boolean property test fix code. 225 | /// 226 | private const string NullableBooleanPropertyTestFixCode = @" 227 | using System; 228 | using System.Collections.Generic; 229 | using System.Text; 230 | 231 | namespace ConsoleApp4 232 | { 233 | public class PropertyTester 234 | { 235 | /// 236 | /// Gets or sets a value indicating whether tester is started. 237 | /// 238 | public bool? IsTesterStarted { get; set; } 239 | } 240 | }"; 241 | 242 | /// 243 | /// The expression body property test code. 244 | /// 245 | private const string ExpressionBodyPropertyTestCode = @" 246 | using System; 247 | using System.Collections.Generic; 248 | using System.Text; 249 | 250 | namespace ConsoleApp4 251 | { 252 | public class PropertyTester 253 | { 254 | public string PersonName => ""Person Name""; 255 | } 256 | }"; 257 | 258 | /// 259 | /// The expression body property test fix code. 260 | /// 261 | private const string ExpressionBodyPropertyTestFixCode = @" 262 | using System; 263 | using System.Collections.Generic; 264 | using System.Text; 265 | 266 | namespace ConsoleApp4 267 | { 268 | public class PropertyTester 269 | { 270 | /// 271 | /// Gets the person name. 272 | /// 273 | public string PersonName => ""Person Name""; 274 | } 275 | }"; 276 | 277 | /// 278 | /// Nos diagnostics show. 279 | /// 280 | /// The test code. 281 | [DataTestMethod] 282 | [DataRow("")] 283 | [DataRow(InheritDocTestCode)] 284 | public void NoDiagnosticsShow(string testCode) 285 | { 286 | this.VerifyCSharpDiagnostic(testCode); 287 | } 288 | 289 | /// 290 | /// Shows diagnostic and fix. 291 | /// 292 | /// The test code. 293 | /// The fix code. 294 | /// The line. 295 | /// The column. 296 | [DataTestMethod] 297 | [DataRow(PropertyWithGetterSetterTestCode, PropertyWithGetterSetterTestFixCode, 10, 17)] 298 | [DataRow(PropertyOnlyGetterTestCode, PropertyOnlyGetterTestFixCode, 10, 17)] 299 | [DataRow(PropertyPrivateGetterTestCode, PropertyPrivateGetterTestFixCode, 10, 23)] 300 | [DataRow(PropertyInternalGetterTestCode, PropertyInternalGetterTestFixCode, 10, 23)] 301 | [DataRow(BooleanPropertyTestCode, BooleanPropertyTestFixCode, 10, 15)] 302 | [DataRow(NullableBooleanPropertyTestCode, NullableBooleanPropertyTestFixCode, 10, 16)] 303 | [DataRow(ExpressionBodyPropertyTestCode, ExpressionBodyPropertyTestFixCode, 10, 17)] 304 | public void ShowDiagnosticAndFix(string testCode, string fixCode, int line, int column) 305 | { 306 | var expected = new DiagnosticResult 307 | { 308 | Id = PropertyAnalyzer.DiagnosticId, 309 | Message = PropertyAnalyzer.MessageFormat, 310 | Severity = DiagnosticSeverity.Warning, 311 | Locations = 312 | new[] { 313 | new DiagnosticResultLocation("Test0.cs", line, column) 314 | } 315 | }; 316 | 317 | this.VerifyCSharpDiagnostic(testCode, expected); 318 | 319 | this.VerifyCSharpFix(testCode, fixCode); 320 | } 321 | 322 | /// 323 | /// Gets c sharp code fix provider. 324 | /// 325 | /// A CodeFixProvider. 326 | protected override CodeFixProvider GetCSharpCodeFixProvider() 327 | { 328 | return new PropertyCodeFixProvider(); 329 | } 330 | 331 | /// 332 | /// Gets c sharp diagnostic analyzer. 333 | /// 334 | /// A DiagnosticAnalyzer. 335 | protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 336 | { 337 | return new PropertyAnalyzer(); 338 | } 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/Verifiers/CodeFixVerifier.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeActions; 6 | using Microsoft.CodeAnalysis.CodeFixes; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | using Microsoft.CodeAnalysis.Formatting; 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 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 | } 129 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Test/Verifiers/DiagnosticVerifier.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.Diagnostics; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace TestHelper 9 | { 10 | /// 11 | /// Superclass of all Unit Tests for DiagnosticAnalyzers 12 | /// 13 | public abstract partial class DiagnosticVerifier 14 | { 15 | #region To be implemented by Test classes 16 | /// 17 | /// Get the CSharp analyzer being tested - to be implemented in non-abstract class 18 | /// 19 | protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() 20 | { 21 | return null; 22 | } 23 | 24 | /// 25 | /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class 26 | /// 27 | protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() 28 | { 29 | return null; 30 | } 31 | #endregion 32 | 33 | #region Verifier wrappers 34 | 35 | /// 36 | /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source 37 | /// Note: input a DiagnosticResult for each Diagnostic expected 38 | /// 39 | /// A class in the form of a string to run the analyzer on 40 | /// DiagnosticResults that should appear after the analyzer is run on the source 41 | protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) 42 | { 43 | VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); 44 | } 45 | 46 | /// 47 | /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source 48 | /// Note: input a DiagnosticResult for each Diagnostic expected 49 | /// 50 | /// A class in the form of a string to run the analyzer on 51 | /// DiagnosticResults that should appear after the analyzer is run on the source 52 | protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) 53 | { 54 | VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); 55 | } 56 | 57 | /// 58 | /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source 59 | /// Note: input a DiagnosticResult for each Diagnostic expected 60 | /// 61 | /// An array of strings to create source documents from to run the analyzers on 62 | /// DiagnosticResults that should appear after the analyzer is run on the sources 63 | protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) 64 | { 65 | VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); 66 | } 67 | 68 | /// 69 | /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source 70 | /// Note: input a DiagnosticResult for each Diagnostic expected 71 | /// 72 | /// An array of strings to create source documents from to run the analyzers on 73 | /// DiagnosticResults that should appear after the analyzer is run on the sources 74 | protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) 75 | { 76 | VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); 77 | } 78 | 79 | /// 80 | /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, 81 | /// then verifies each of them. 82 | /// 83 | /// An array of strings to create source documents from to run the analyzers on 84 | /// The language of the classes represented by the source strings 85 | /// The analyzer to be run on the source code 86 | /// DiagnosticResults that should appear after the analyzer is run on the sources 87 | private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) 88 | { 89 | var diagnostics = GetSortedDiagnostics(sources, language, analyzer); 90 | VerifyDiagnosticResults(diagnostics, analyzer, expected); 91 | } 92 | 93 | #endregion 94 | 95 | #region Actual comparisons and verifications 96 | /// 97 | /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. 98 | /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. 99 | /// 100 | /// The Diagnostics found by the compiler after running the analyzer on the source code 101 | /// The analyzer that was being run on the sources 102 | /// Diagnostic Results that should have appeared in the code 103 | private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) 104 | { 105 | int expectedCount = expectedResults.Count(); 106 | int actualCount = actualResults.Count(); 107 | 108 | if (expectedCount != actualCount) 109 | { 110 | string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; 111 | 112 | Assert.IsTrue(false, 113 | string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); 114 | } 115 | 116 | for (int i = 0; i < expectedResults.Length; i++) 117 | { 118 | var actual = actualResults.ElementAt(i); 119 | var expected = expectedResults[i]; 120 | 121 | if (expected.Line == -1 && expected.Column == -1) 122 | { 123 | if (actual.Location != Location.None) 124 | { 125 | Assert.IsTrue(false, 126 | string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", 127 | FormatDiagnostics(analyzer, actual))); 128 | } 129 | } 130 | else 131 | { 132 | VerifyDiagnosticLocation(analyzer, actual, actual.Location, expected.Locations.First()); 133 | var additionalLocations = actual.AdditionalLocations.ToArray(); 134 | 135 | if (additionalLocations.Length != expected.Locations.Length - 1) 136 | { 137 | Assert.IsTrue(false, 138 | string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", 139 | expected.Locations.Length - 1, additionalLocations.Length, 140 | FormatDiagnostics(analyzer, actual))); 141 | } 142 | 143 | for (int j = 0; j < additionalLocations.Length; ++j) 144 | { 145 | VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); 146 | } 147 | } 148 | 149 | if (actual.Id != expected.Id) 150 | { 151 | Assert.IsTrue(false, 152 | string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 153 | expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); 154 | } 155 | 156 | if (actual.Severity != expected.Severity) 157 | { 158 | Assert.IsTrue(false, 159 | string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 160 | expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); 161 | } 162 | 163 | if (actual.GetMessage() != expected.Message) 164 | { 165 | Assert.IsTrue(false, 166 | string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 167 | expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); 168 | } 169 | } 170 | } 171 | 172 | /// 173 | /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. 174 | /// 175 | /// The analyzer that was being run on the sources 176 | /// The diagnostic that was found in the code 177 | /// The Location of the Diagnostic found in the code 178 | /// The DiagnosticResultLocation that should have been found 179 | private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) 180 | { 181 | var actualSpan = actual.GetLineSpan(); 182 | 183 | Assert.IsTrue(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), 184 | string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 185 | expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); 186 | 187 | var actualLinePosition = actualSpan.StartLinePosition; 188 | 189 | // Only check line position if there is an actual line in the real diagnostic 190 | if (actualLinePosition.Line > 0) 191 | { 192 | if (actualLinePosition.Line + 1 != expected.Line) 193 | { 194 | Assert.IsTrue(false, 195 | string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 196 | expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); 197 | } 198 | } 199 | 200 | // Only check column position if there is an actual column position in the real diagnostic 201 | if (actualLinePosition.Character > 0) 202 | { 203 | if (actualLinePosition.Character + 1 != expected.Column) 204 | { 205 | Assert.IsTrue(false, 206 | string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", 207 | expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); 208 | } 209 | } 210 | } 211 | #endregion 212 | 213 | #region Formatting Diagnostics 214 | /// 215 | /// Helper method to format a Diagnostic into an easily readable string 216 | /// 217 | /// The analyzer that this verifier tests 218 | /// The Diagnostics to be formatted 219 | /// The Diagnostics formatted as a string 220 | private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) 221 | { 222 | var builder = new StringBuilder(); 223 | for (int i = 0; i < diagnostics.Length; ++i) 224 | { 225 | builder.AppendLine("// " + diagnostics[i].ToString()); 226 | 227 | var analyzerType = analyzer.GetType(); 228 | var rules = analyzer.SupportedDiagnostics; 229 | 230 | foreach (var rule in rules) 231 | { 232 | if (rule != null && rule.Id == diagnostics[i].Id) 233 | { 234 | var location = diagnostics[i].Location; 235 | if (location == Location.None) 236 | { 237 | builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); 238 | } 239 | else 240 | { 241 | Assert.IsTrue(location.IsInSource, 242 | $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); 243 | 244 | string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; 245 | var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; 246 | 247 | builder.AppendFormat("{0}({1}, {2}, {3}.{4})", 248 | resultMethodName, 249 | linePosition.Line + 1, 250 | linePosition.Character + 1, 251 | analyzerType.Name, 252 | rule.Id); 253 | } 254 | 255 | if (i != diagnostics.Length - 1) 256 | { 257 | builder.Append(','); 258 | } 259 | 260 | builder.AppendLine(); 261 | break; 262 | } 263 | } 264 | } 265 | return builder.ToString(); 266 | } 267 | #endregion 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Vsix/DocumentationAssistant.Vsix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 15.0 6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 7 | 8 | 9 | 14.0 10 | 11 | 12 | 13 | 14 | Debug 15 | AnyCPU 16 | AnyCPU 17 | 2.0 18 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 19 | {213F13FD-8CFB-46A7-87F6-3A4ED53E2C68} 20 | Library 21 | Properties 22 | DocumentationAssistant.Vsix 23 | DocumentationAssistant 24 | v4.6.1 25 | false 26 | false 27 | false 28 | false 29 | false 30 | false 31 | Roslyn 32 | 33 | 34 | true 35 | full 36 | false 37 | bin\Debug\ 38 | DEBUG;TRACE 39 | prompt 40 | 4 41 | 42 | 43 | pdbonly 44 | true 45 | bin\Release\ 46 | TRACE 47 | prompt 48 | 4 49 | 50 | 51 | Program 52 | $(DevEnvDir)devenv.exe 53 | /rootsuffix Roslyn 54 | 55 | 56 | 57 | 58 | Designer 59 | 60 | 61 | 62 | 63 | {7E144CCA-9B83-4C1A-83F0-2B8AD87C2CE0} 64 | DocumentationAssistant 65 | 66 | 67 | 68 | 69 | ..\..\packages\Pluralize.NET.0.1.84\lib\net46\Pluralize.NET.dll 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Vsix/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentationAssistant 6 | An Extension to generate XML documentation automatically using IntelliSense for interface,class,enum, field, constructor, property and method. 7 | 8 | 9 | 10 | x86 11 | 12 | 13 | x86 14 | 15 | 16 | amd64 17 | 18 | 19 | amd64 20 | 21 | 22 | amd64 23 | 24 | 25 | x86 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/ClassAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using DocumentationAssistant.Helper; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | 9 | namespace DocumentationAssistant 10 | { 11 | /// 12 | /// The class analyzer. 13 | /// 14 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 15 | public class ClassAnalyzer : DiagnosticAnalyzer 16 | { 17 | /// 18 | /// The title. 19 | /// 20 | private const string Title = "The class must have a documentation header."; 21 | 22 | /// 23 | /// The category. 24 | /// 25 | private const string Category = DocumentationHeaderHelper.Category; 26 | 27 | /// 28 | /// The diagnostic id. 29 | /// 30 | public const string DiagnosticId = "ClassDocumentationHeader"; 31 | 32 | /// 33 | /// The message format. 34 | /// 35 | public const string MessageFormat = Title; 36 | 37 | /// 38 | /// The diagnostic descriptor rule. 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); 41 | 42 | /// 43 | /// Gets the supported diagnostics. 44 | /// 45 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 46 | 47 | /// 48 | /// Initializes action. 49 | /// 50 | /// The context. 51 | public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration); 52 | 53 | /// 54 | /// Analyzes node. 55 | /// 56 | /// The context. 57 | private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 58 | { 59 | var node = context.Node as ClassDeclarationSyntax; 60 | 61 | if (Configuration.IsEnabledForPublicMembersOnly && PrivateMemberChecker.IsPrivateMember(node)) 62 | { 63 | return; 64 | } 65 | 66 | DocumentationCommentTriviaSyntax commentTriviaSyntax = node 67 | .GetLeadingTrivia() 68 | .Select(o => o.GetStructure()) 69 | .OfType() 70 | .FirstOrDefault(); 71 | 72 | if (commentTriviaSyntax != null && CommentHelper.HasComment(commentTriviaSyntax)) 73 | { 74 | return; 75 | } 76 | 77 | context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation())); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/ClassCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DocumentationAssistant.Helper; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | namespace DocumentationAssistant 14 | { 15 | /// 16 | /// The class code fix provider. 17 | /// 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ClassCodeFixProvider)), Shared] 19 | public class ClassCodeFixProvider : CodeFixProvider 20 | { 21 | /// 22 | /// The title. 23 | /// 24 | private const string Title = "Add documentation header to this class"; 25 | 26 | /// 27 | /// Gets the fixable diagnostic ids. 28 | /// 29 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ClassAnalyzer.DiagnosticId); 30 | 31 | /// 32 | /// Gets fix all provider. 33 | /// 34 | /// A FixAllProvider. 35 | public sealed override FixAllProvider GetFixAllProvider() 36 | { 37 | return WellKnownFixAllProviders.BatchFixer; 38 | } 39 | 40 | /// 41 | /// Registers code fixes async. 42 | /// 43 | /// The context. 44 | /// A Task. 45 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 46 | { 47 | SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 48 | 49 | Diagnostic diagnostic = context.Diagnostics.First(); 50 | Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 51 | 52 | ClassDeclarationSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 53 | 54 | context.RegisterCodeFix( 55 | CodeAction.Create( 56 | title: Title, 57 | createChangedDocument: c => this.AddDocumentationHeaderAsync(context.Document, root, declaration, c), 58 | equivalenceKey: Title), 59 | diagnostic); 60 | } 61 | 62 | /// 63 | /// Adds documentation header async. 64 | /// 65 | /// The document. 66 | /// The root. 67 | /// The declaration syntax. 68 | /// The cancellation token. 69 | /// A Document. 70 | private async Task AddDocumentationHeaderAsync(Document document, SyntaxNode root, ClassDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) 71 | { 72 | SyntaxTriviaList leadingTrivia = declarationSyntax.GetLeadingTrivia(); 73 | 74 | string comment = CommentHelper.CreateClassComment(declarationSyntax.Identifier.ValueText); 75 | DocumentationCommentTriviaSyntax commentTrivia = await Task.Run(() => DocumentationHeaderHelper.CreateOnlySummaryDocumentationCommentTrivia(comment), cancellationToken); 76 | 77 | SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(leadingTrivia.Count - 1, SyntaxFactory.Trivia(commentTrivia)); 78 | ClassDeclarationSyntax newDeclaration = declarationSyntax.WithLeadingTrivia(newLeadingTrivia); 79 | 80 | SyntaxNode newRoot = root.ReplaceNode(declarationSyntax, newDeclaration); 81 | return document.WithSyntaxRoot(newRoot); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/ConstructorAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using DocumentationAssistant.Helper; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | 9 | namespace DocumentationAssistant 10 | { 11 | /// 12 | /// The constructor analyzer. 13 | /// 14 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 15 | public class ConstructorAnalyzer : DiagnosticAnalyzer 16 | { 17 | /// 18 | /// The title. 19 | /// 20 | private const string Title = "The constructor must have a documentation header."; 21 | 22 | /// 23 | /// The category. 24 | /// 25 | private const string Category = DocumentationHeaderHelper.Category; 26 | 27 | /// 28 | /// The diagnostic id. 29 | /// 30 | public const string DiagnosticId = "ConstructorDocumentationHeader"; 31 | 32 | /// 33 | /// The message format. 34 | /// 35 | public const string MessageFormat = Title; 36 | 37 | /// 38 | /// The diagnostic descriptor rule. 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); 41 | 42 | /// 43 | /// Gets the supported diagnostics. 44 | /// 45 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 46 | 47 | /// 48 | /// Initializes. 49 | /// 50 | /// The context. 51 | public override void Initialize(AnalysisContext context) 52 | { 53 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ConstructorDeclaration); 54 | } 55 | 56 | /// 57 | /// Analyzes node. 58 | /// 59 | /// The context. 60 | private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 61 | { 62 | ConstructorDeclarationSyntax node = context.Node as ConstructorDeclarationSyntax; 63 | 64 | if (Configuration.IsEnabledForPublicMembersOnly && PrivateMemberChecker.IsPrivateMember(node)) 65 | { 66 | return; 67 | } 68 | 69 | DocumentationCommentTriviaSyntax commentTriviaSyntax = node 70 | .GetLeadingTrivia() 71 | .Select(o => o.GetStructure()) 72 | .OfType() 73 | .FirstOrDefault(); 74 | 75 | if (commentTriviaSyntax != null && CommentHelper.HasComment(commentTriviaSyntax)) 76 | { 77 | return; 78 | } 79 | 80 | context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation())); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/ConstructorCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DocumentationAssistant.Helper; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | namespace DocumentationAssistant 14 | { 15 | /// 16 | /// The constructor code fix provider. 17 | /// 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ConstructorCodeFixProvider)), Shared] 19 | public class ConstructorCodeFixProvider : CodeFixProvider 20 | { 21 | /// 22 | /// The title. 23 | /// 24 | private const string Title = "Add documentation header to this constructor"; 25 | 26 | /// 27 | /// Gets the fixable diagnostic ids. 28 | /// 29 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ConstructorAnalyzer.DiagnosticId); 30 | 31 | /// 32 | /// Gets fix all provider. 33 | /// 34 | /// A FixAllProvider. 35 | public sealed override FixAllProvider GetFixAllProvider() 36 | { 37 | return WellKnownFixAllProviders.BatchFixer; 38 | } 39 | 40 | /// 41 | /// Registers code fixes async. 42 | /// 43 | /// The context. 44 | /// A Task. 45 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 46 | { 47 | SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 48 | 49 | Diagnostic diagnostic = context.Diagnostics.First(); 50 | Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 51 | 52 | ConstructorDeclarationSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 53 | 54 | context.RegisterCodeFix( 55 | CodeAction.Create( 56 | title: Title, 57 | createChangedDocument: c => this.AddDocumentationHeaderAsync(context.Document, root, declaration, c), 58 | equivalenceKey: Title), 59 | diagnostic); 60 | } 61 | 62 | /// 63 | /// Adds documentation header async. 64 | /// 65 | /// The document. 66 | /// The root. 67 | /// The declaration syntax. 68 | /// The cancellation token. 69 | /// A Document. 70 | private async Task AddDocumentationHeaderAsync(Document document, SyntaxNode root, ConstructorDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) 71 | { 72 | SyntaxTriviaList leadingTrivia = declarationSyntax.GetLeadingTrivia(); 73 | DocumentationCommentTriviaSyntax commentTrivia = await Task.Run(() => CreateDocumentationCommentTriviaSyntax(declarationSyntax), cancellationToken); 74 | 75 | SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(leadingTrivia.Count - 1, SyntaxFactory.Trivia(commentTrivia)); 76 | ConstructorDeclarationSyntax newDeclaration = declarationSyntax.WithLeadingTrivia(newLeadingTrivia); 77 | 78 | SyntaxNode newRoot = root.ReplaceNode(declarationSyntax, newDeclaration); 79 | return document.WithSyntaxRoot(newRoot); 80 | } 81 | 82 | /// 83 | /// Creates documentation comment trivia syntax. 84 | /// 85 | /// The declaration syntax. 86 | /// A DocumentationCommentTriviaSyntax. 87 | private static DocumentationCommentTriviaSyntax CreateDocumentationCommentTriviaSyntax(ConstructorDeclarationSyntax declarationSyntax) 88 | { 89 | SyntaxList list = SyntaxFactory.List(); 90 | 91 | bool isPrivate = false; 92 | if (declarationSyntax.Modifiers.Any(SyntaxKind.PrivateKeyword)) 93 | { 94 | isPrivate = true; 95 | } 96 | 97 | string comment = CommentHelper.CreateConstructorComment(declarationSyntax.Identifier.ValueText, isPrivate); 98 | list = list.AddRange(DocumentationHeaderHelper.CreateSummaryPartNodes(comment)); 99 | if (declarationSyntax.ParameterList.Parameters.Any()) 100 | { 101 | foreach (ParameterSyntax parameter in declarationSyntax.ParameterList.Parameters) 102 | { 103 | string parameterComment = CommentHelper.CreateParameterComment(parameter); 104 | list = list.AddRange(DocumentationHeaderHelper.CreateParameterPartNodes(parameter.Identifier.ValueText, parameterComment)); 105 | } 106 | } 107 | return SyntaxFactory.DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, list); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/DocumentationAssistant.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | false 6 | True 7 | 8 | 9 | 10 | DocumentationAssistant 11 | 1.0.0.0 12 | jinyafeng 13 | http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE 14 | http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE 15 | http://ICON_URL_HERE_OR_DELETE_THIS_LINE 16 | http://REPOSITORY_URL_HERE_OR_DELETE_THIS_LINE 17 | false 18 | DocumentationAssistant 19 | Summary of changes made in this release of the package. 20 | Copyright 21 | DocumentationAssistant, analyzers 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/EnumAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using DocumentationAssistant.Helper; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | 9 | namespace DocumentationAssistant 10 | { 11 | /// 12 | /// The enum analyzer. 13 | /// 14 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 15 | public class EnumAnalyzer : DiagnosticAnalyzer 16 | { 17 | /// 18 | /// The title. 19 | /// 20 | private const string Title = "The enum must have a documentation header."; 21 | 22 | /// 23 | /// The category. 24 | /// 25 | private const string Category = DocumentationHeaderHelper.Category; 26 | 27 | /// 28 | /// The diagnostic id. 29 | /// 30 | public const string DiagnosticId = "EnumDocumentationHeader"; 31 | 32 | /// 33 | /// The message format. 34 | /// 35 | public const string MessageFormat = Title; 36 | 37 | /// 38 | /// The diagnostic descriptor rule. 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); 41 | 42 | /// 43 | /// Gets the supported diagnostics. 44 | /// 45 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 46 | 47 | /// 48 | /// Initializes action. 49 | /// 50 | /// The context. 51 | public override void Initialize(AnalysisContext context) 52 | { 53 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.EnumDeclaration); 54 | } 55 | 56 | /// 57 | /// Analyzes node. 58 | /// 59 | /// The context. 60 | private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 61 | { 62 | EnumDeclarationSyntax node = context.Node as EnumDeclarationSyntax; 63 | 64 | DocumentationCommentTriviaSyntax commentTriviaSyntax = node 65 | .GetLeadingTrivia() 66 | .Select(o => o.GetStructure()) 67 | .OfType() 68 | .FirstOrDefault(); 69 | 70 | if (commentTriviaSyntax != null && CommentHelper.HasComment(commentTriviaSyntax)) 71 | { 72 | return; 73 | } 74 | 75 | context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation())); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/EnumCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DocumentationAssistant.Helper; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | namespace DocumentationAssistant 14 | { 15 | /// 16 | /// The interface code fix provider. 17 | /// 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(EnumCodeFixProvider)), Shared] 19 | public class EnumCodeFixProvider : CodeFixProvider 20 | { 21 | /// 22 | /// The title. 23 | /// 24 | private const string Title = "Add documentation header to this enum"; 25 | 26 | /// 27 | /// Gets the fixable diagnostic ids. 28 | /// 29 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(EnumAnalyzer.DiagnosticId); 30 | 31 | /// 32 | /// Gets fix all provider. 33 | /// 34 | /// A FixAllProvider. 35 | public sealed override FixAllProvider GetFixAllProvider() 36 | { 37 | return WellKnownFixAllProviders.BatchFixer; 38 | } 39 | 40 | /// 41 | /// Registers code fixes async. 42 | /// 43 | /// The context. 44 | /// A Task. 45 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 46 | { 47 | SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 48 | 49 | Diagnostic diagnostic = context.Diagnostics.First(); 50 | Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 51 | 52 | EnumDeclarationSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 53 | 54 | context.RegisterCodeFix( 55 | CodeAction.Create( 56 | title: Title, 57 | createChangedDocument: c => this.AddDocumentationHeaderAsync(context.Document, root, declaration, c), 58 | equivalenceKey: Title), 59 | diagnostic); 60 | } 61 | 62 | /// 63 | /// Adds documentation header async. 64 | /// 65 | /// The document. 66 | /// The root. 67 | /// The declaration syntax. 68 | /// The cancellation token. 69 | /// A Document. 70 | private async Task AddDocumentationHeaderAsync(Document document, SyntaxNode root, EnumDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) 71 | { 72 | SyntaxTriviaList leadingTrivia = declarationSyntax.GetLeadingTrivia(); 73 | 74 | string comment = CommentHelper.CreateEnumComment(declarationSyntax.Identifier.ValueText); 75 | DocumentationCommentTriviaSyntax commentTrivia = await Task.Run(() => DocumentationHeaderHelper.CreateOnlySummaryDocumentationCommentTrivia(comment), cancellationToken); 76 | 77 | SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(leadingTrivia.Count - 1, SyntaxFactory.Trivia(commentTrivia)); 78 | EnumDeclarationSyntax newDeclaration = declarationSyntax.WithLeadingTrivia(newLeadingTrivia); 79 | 80 | SyntaxNode newRoot = root.ReplaceNode(declarationSyntax, newDeclaration); 81 | return document.WithSyntaxRoot(newRoot); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/FieldAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using DocumentationAssistant.Helper; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | 9 | namespace DocumentationAssistant 10 | { 11 | /// 12 | /// The field analyzer. 13 | /// 14 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 15 | public class FieldAnalyzer : DiagnosticAnalyzer 16 | { 17 | /// 18 | /// The title. 19 | /// 20 | private const string Title = "The field must have a documentation header."; 21 | 22 | /// 23 | /// The category. 24 | /// 25 | private const string Category = DocumentationHeaderHelper.Category; 26 | 27 | /// 28 | /// The diagnostic id. 29 | /// 30 | public const string DiagnosticId = "ConstFieldDocumentationHeader"; 31 | 32 | /// 33 | /// The message format. 34 | /// 35 | public const string MessageFormat = Title; 36 | 37 | /// 38 | /// The diagnostic descriptor rule. 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); 41 | 42 | /// 43 | /// Gets the supported diagnostics. 44 | /// 45 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 46 | 47 | /// 48 | /// Initializes. 49 | /// 50 | /// The context. 51 | public override void Initialize(AnalysisContext context) 52 | { 53 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.FieldDeclaration); 54 | } 55 | 56 | /// 57 | /// Analyzes node. 58 | /// 59 | /// The context. 60 | private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 61 | { 62 | FieldDeclarationSyntax node = context.Node as FieldDeclarationSyntax; 63 | 64 | // Only const field. 65 | if (!node.Modifiers.Any(SyntaxKind.ConstKeyword)) 66 | { 67 | return; 68 | } 69 | 70 | if (Configuration.IsEnabledForPublicMembersOnly && PrivateMemberChecker.IsPrivateMember(node)) 71 | { 72 | return; 73 | } 74 | 75 | DocumentationCommentTriviaSyntax commentTriviaSyntax = node 76 | .GetLeadingTrivia() 77 | .Select(o => o.GetStructure()) 78 | .OfType() 79 | .FirstOrDefault(); 80 | 81 | if (commentTriviaSyntax != null && CommentHelper.HasComment(commentTriviaSyntax)) 82 | { 83 | return; 84 | } 85 | 86 | VariableDeclaratorSyntax field = node.DescendantNodes().OfType().First(); 87 | context.ReportDiagnostic(Diagnostic.Create(Rule, field.GetLocation())); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/FieldCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DocumentationAssistant.Helper; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | namespace DocumentationAssistant 14 | { 15 | /// 16 | /// The field code fix provider. 17 | /// 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FieldCodeFixProvider)), Shared] 19 | public class FieldCodeFixProvider : CodeFixProvider 20 | { 21 | /// 22 | /// The title. 23 | /// 24 | private const string Title = "Add documentation header to this field"; 25 | 26 | /// 27 | /// Gets the fixable diagnostic ids. 28 | /// 29 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(FieldAnalyzer.DiagnosticId); 30 | 31 | /// 32 | /// Gets fix all provider. 33 | /// 34 | /// A FixAllProvider. 35 | public sealed override FixAllProvider GetFixAllProvider() 36 | { 37 | return WellKnownFixAllProviders.BatchFixer; 38 | } 39 | 40 | /// 41 | /// Registers code fixes async. 42 | /// 43 | /// The context. 44 | /// A Task. 45 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 46 | { 47 | SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 48 | 49 | Diagnostic diagnostic = context.Diagnostics.First(); 50 | Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 51 | 52 | FieldDeclarationSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 53 | 54 | context.RegisterCodeFix( 55 | CodeAction.Create( 56 | title: Title, 57 | createChangedDocument: c => this.AddDocumentationHeaderAsync(context.Document, root, declaration, c), 58 | equivalenceKey: Title), 59 | diagnostic); 60 | } 61 | 62 | /// 63 | /// Adds documentation header async. 64 | /// 65 | /// The document. 66 | /// The root. 67 | /// The declaration syntax. 68 | /// The cancellation token. 69 | /// A Document. 70 | private async Task AddDocumentationHeaderAsync(Document document, SyntaxNode root, FieldDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) 71 | { 72 | SyntaxTriviaList leadingTrivia = declarationSyntax.GetLeadingTrivia(); 73 | 74 | VariableDeclaratorSyntax field = declarationSyntax.DescendantNodes().OfType().First(); 75 | string comment = CommentHelper.CreateFieldComment(field.Identifier.ValueText); 76 | DocumentationCommentTriviaSyntax commentTrivia = await Task.Run(() => DocumentationHeaderHelper.CreateOnlySummaryDocumentationCommentTrivia(comment), cancellationToken); 77 | 78 | SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(leadingTrivia.Count - 1, SyntaxFactory.Trivia(commentTrivia)); 79 | FieldDeclarationSyntax newDeclaration = declarationSyntax.WithLeadingTrivia(newLeadingTrivia); 80 | 81 | SyntaxNode newRoot = root.ReplaceNode(declarationSyntax, newDeclaration); 82 | return document.WithSyntaxRoot(newRoot); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/Helper/CommentHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace DocumentationAssistant.Helper 8 | { 9 | /// 10 | /// The comment helper. 11 | /// 12 | public static class CommentHelper 13 | { 14 | /// 15 | /// Creates class comment. 16 | /// 17 | /// The name. 18 | /// The class comment. 19 | public static string CreateClassComment(string name) 20 | { 21 | return CreateCommonComment(name); 22 | } 23 | 24 | /// 25 | /// Creates field comment. 26 | /// 27 | /// The name. 28 | /// The field comment. 29 | public static string CreateFieldComment(string name) 30 | { 31 | return CreateCommonComment(name); 32 | } 33 | 34 | /// 35 | /// Creates constructor comment. 36 | /// 37 | /// The name. 38 | /// If true, the constructor accessibility is private. 39 | /// The contructor comment. 40 | public static string CreateConstructorComment(string name, bool isPrivate) 41 | { 42 | if (isPrivate) 43 | { 44 | return $"Prevents a default instance of the class from being created."; 45 | } 46 | else 47 | { 48 | return $"Initializes a new instance of the class."; 49 | } 50 | } 51 | 52 | /// 53 | /// Creates the interface comment. 54 | /// 55 | /// The name. 56 | /// The class comment. 57 | public static string CreateInterfaceComment(string name) 58 | { 59 | List parts = SpilitNameAndToLower(name, false); 60 | if (parts[0]=="I") 61 | { 62 | parts.RemoveAt(0); 63 | } 64 | 65 | parts.Insert(0,"The"); 66 | return string.Join(" ",parts)+"."; 67 | } 68 | 69 | /// 70 | /// Creates the enum comment. 71 | /// 72 | /// The name. 73 | /// A string. 74 | public static string CreateEnumComment(string name) 75 | { 76 | return CreateCommonComment(name); 77 | } 78 | 79 | /// 80 | /// Creates property comment. 81 | /// 82 | /// The name. 83 | /// If ture, the property type is boolean. 84 | /// If ture, the property has setter. 85 | /// The property comment. 86 | public static string CreatePropertyComment(string name, bool isBoolean, bool hasSetter) 87 | { 88 | string comment = "Gets"; 89 | if (hasSetter) 90 | { 91 | comment += " or sets"; 92 | } 93 | 94 | if (isBoolean) 95 | { 96 | comment += CreatePropertyBooleanPart(name); 97 | } 98 | else 99 | { 100 | comment += " the " + string.Join(" ", SpilitNameAndToLower(name, true)); 101 | } 102 | return comment + "."; 103 | } 104 | 105 | /// 106 | /// Creates method comment. 107 | /// 108 | /// The name. 109 | /// The method comment. 110 | public static string CreateMethodComment(string name) 111 | { 112 | List parts = SpilitNameAndToLower(name, false); 113 | parts[0] = Pluralizer.Pluralize(parts[0]); 114 | parts.Insert(1, "the"); 115 | return string.Join(" ", parts) + "."; 116 | } 117 | 118 | /// 119 | /// Creates parameter comment. 120 | /// 121 | /// The name. 122 | /// The parameter comment. 123 | public static string CreateParameterComment(ParameterSyntax parameter) 124 | { 125 | bool isBoolean = false; 126 | if (parameter.Type.IsKind(SyntaxKind.PredefinedType)) 127 | { 128 | isBoolean = (parameter.Type as PredefinedTypeSyntax).Keyword.IsKind(SyntaxKind.BoolKeyword); 129 | } 130 | else if (parameter.Type.IsKind(SyntaxKind.NullableType)) 131 | { 132 | var type = (parameter.Type as NullableTypeSyntax).ElementType as PredefinedTypeSyntax; 133 | 134 | // If it is not predefined type syntax, it should be IdentifierNameSyntax. 135 | if (type != null) 136 | { 137 | isBoolean = type.Keyword.IsKind(SyntaxKind.BoolKeyword); 138 | } 139 | } 140 | 141 | if (isBoolean) 142 | { 143 | return "If true, " + string.Join(" ", SpilitNameAndToLower(parameter.Identifier.ValueText, true)) + "."; 144 | } 145 | else 146 | { 147 | return CreateCommonComment(parameter.Identifier.ValueText); 148 | } 149 | } 150 | 151 | /// 152 | /// Have the comment. 153 | /// 154 | /// The comment trivia syntax. 155 | /// A bool. 156 | public static bool HasComment(DocumentationCommentTriviaSyntax commentTriviaSyntax) 157 | { 158 | bool hasSummary = commentTriviaSyntax 159 | .ChildNodes() 160 | .OfType() 161 | .Any(o => o.StartTag.Name.ToString().Equals(DocumentationHeaderHelper.Summary)); 162 | 163 | bool hasInheritDoc = commentTriviaSyntax 164 | .ChildNodes() 165 | .OfType() 166 | .Any(o => o.Name.ToString().Equals(DocumentationHeaderHelper.InheritDoc)); 167 | 168 | return hasSummary || hasInheritDoc; 169 | } 170 | 171 | /// 172 | /// Creates property boolean part. 173 | /// 174 | /// The name. 175 | /// The property comment boolean part. 176 | private static string CreatePropertyBooleanPart(string name) 177 | { 178 | string booleanPart = " a value indicating whether "; 179 | 180 | var parts = SpilitNameAndToLower(name, true).ToList(); 181 | 182 | string isWord = parts.FirstOrDefault(o => o == "is"); 183 | if (isWord != null) 184 | { 185 | parts.Remove(isWord); 186 | parts.Insert(parts.Count - 1, isWord); 187 | } 188 | 189 | booleanPart += string.Join(" ", parts); 190 | return booleanPart; 191 | } 192 | 193 | /// 194 | /// Creates common comment. 195 | /// 196 | /// The name. 197 | /// The common comment. 198 | private static string CreateCommonComment(string name) 199 | { 200 | return $"The {string.Join(" ", SpilitNameAndToLower(name, true))}."; 201 | } 202 | 203 | /// 204 | /// Spilits name and make words lower. 205 | /// 206 | /// The name. 207 | /// If true, the first character will be lower. 208 | /// A list of words. 209 | private static List SpilitNameAndToLower(string name, bool isFirstCharacterLower) 210 | { 211 | List parts = NameSpliter.Split(name); 212 | 213 | int i = isFirstCharacterLower ? 0 : 1; 214 | for (; i < parts.Count; i++) 215 | { 216 | parts[i] = parts[i].ToLower(); 217 | } 218 | return parts; 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/Helper/Configuration.cs: -------------------------------------------------------------------------------- 1 | namespace DocumentationAssistant.Helper 2 | { 3 | public static class Configuration 4 | { 5 | /// 6 | /// Gets a value indicating whether the tool is enabled for only public members. 7 | /// 8 | public static bool IsEnabledForPublicMembersOnly => false; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/Helper/DocumentationHeaderHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | 6 | namespace DocumentationAssistant.Helper 7 | { 8 | /// 9 | /// The documentation header helper. 10 | /// 11 | public static class DocumentationHeaderHelper 12 | { 13 | /// 14 | /// The category of the diagnostic. 15 | /// 16 | public const string Category = "DocumentationHeader"; 17 | 18 | /// 19 | /// The summary. 20 | /// 21 | public const string Summary = "summary"; 22 | 23 | /// 24 | /// The inherit doc. 25 | /// 26 | public const string InheritDoc = "inheritdoc"; 27 | 28 | /// 29 | /// Creates only summary documentation comment trivia. 30 | /// 31 | /// The content. 32 | /// A DocumentationCommentTriviaSyntax. 33 | public static DocumentationCommentTriviaSyntax CreateOnlySummaryDocumentationCommentTrivia(string content) 34 | { 35 | SyntaxList list = SyntaxFactory.List(CreateSummaryPartNodes(content)); 36 | return SyntaxFactory.DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, list); 37 | } 38 | 39 | /// 40 | /// Creates summary part nodes. 41 | /// 42 | /// The content. 43 | /// An array of XmlNodeSyntaxes. 44 | public static XmlNodeSyntax[] CreateSummaryPartNodes(string content) 45 | { 46 | /* 47 | ///[0] 48 | /// The code fix provider. 49 | /// [1] [2] 50 | */ 51 | 52 | // [0] " " + leading comment exterior trivia 53 | XmlTextSyntax xmlText0 = CreateLineStartTextSyntax(); 54 | 55 | // [1] Summary 56 | XmlElementSyntax xmlElement = CreateSummaryElementSyntax(content); 57 | 58 | // [2] new line 59 | XmlTextSyntax xmlText1 = CreateLineEndTextSyntax(); 60 | 61 | return new XmlNodeSyntax[] { xmlText0, xmlElement, xmlText1 }; 62 | 63 | } 64 | 65 | /// 66 | /// Creates parameter part nodes. 67 | /// 68 | /// The parameter name. 69 | /// The parameter content. 70 | /// An array of XmlNodeSyntaxes. 71 | public static XmlNodeSyntax[] CreateParameterPartNodes(string parameterName, string parameterContent) 72 | { 73 | ///[0] [1][2] 74 | 75 | // [0] -- line start text 76 | XmlTextSyntax lineStartText = CreateLineStartTextSyntax(); 77 | 78 | // [1] -- parameter text 79 | XmlElementSyntax parameterText = CreateParameterElementSyntax(parameterName, parameterContent); 80 | 81 | // [2] -- line end text 82 | XmlTextSyntax lineEndText = CreateLineEndTextSyntax(); 83 | 84 | return new XmlNodeSyntax[] { lineStartText, parameterText, lineEndText }; 85 | } 86 | 87 | /// 88 | /// Creates return part nodes. 89 | /// 90 | /// The content. 91 | /// An array of XmlNodeSyntaxes. 92 | public static XmlNodeSyntax[] CreateReturnPartNodes(string content) 93 | { 94 | ///[0] [1][2] 95 | 96 | XmlTextSyntax lineStartText = CreateLineStartTextSyntax(); 97 | 98 | XmlElementSyntax returnElement = CreateReturnElementSyntax(content); 99 | 100 | XmlTextSyntax lineEndText = CreateLineEndTextSyntax(); 101 | 102 | return new XmlNodeSyntax[] { lineStartText, returnElement, lineEndText }; 103 | } 104 | 105 | /// 106 | /// Creates summary element syntax. 107 | /// 108 | /// The content. 109 | /// A XmlElementSyntax. 110 | private static XmlElementSyntax CreateSummaryElementSyntax(string content) 111 | { 112 | XmlNameSyntax xmlName = SyntaxFactory.XmlName(SyntaxFactory.Identifier(DocumentationHeaderHelper.Summary)); 113 | XmlElementStartTagSyntax summaryStartTag = SyntaxFactory.XmlElementStartTag(xmlName); 114 | XmlElementEndTagSyntax summaryEndTag = SyntaxFactory.XmlElementEndTag(xmlName); 115 | 116 | return SyntaxFactory.XmlElement( 117 | summaryStartTag, 118 | SyntaxFactory.SingletonList(CreateSummaryTextSyntax(content)), 119 | summaryEndTag); 120 | } 121 | 122 | /// 123 | /// Creates parameter element syntax. 124 | /// 125 | /// The parameter name. 126 | /// The parameter content. 127 | /// A XmlElementSyntax. 128 | private static XmlElementSyntax CreateParameterElementSyntax(string parameterName, string parameterContent) 129 | { 130 | XmlNameSyntax paramName = SyntaxFactory.XmlName("param"); 131 | 132 | /// [0][1][2] 133 | 134 | // [0] -- param start tag with attribute 135 | XmlNameAttributeSyntax paramAttribute = SyntaxFactory.XmlNameAttribute(parameterName); 136 | XmlElementStartTagSyntax startTag = SyntaxFactory.XmlElementStartTag(paramName, SyntaxFactory.SingletonList(paramAttribute)); 137 | 138 | // [1] -- content 139 | XmlTextSyntax content = SyntaxFactory.XmlText(parameterContent); 140 | 141 | // [2] -- end tag 142 | XmlElementEndTagSyntax endTag = SyntaxFactory.XmlElementEndTag(paramName); 143 | return SyntaxFactory.XmlElement(startTag, SyntaxFactory.SingletonList(content), endTag); 144 | } 145 | 146 | /// 147 | /// Creates return element syntax. 148 | /// 149 | /// The content. 150 | /// A XmlElementSyntax. 151 | private static XmlElementSyntax CreateReturnElementSyntax(string content) 152 | { 153 | XmlNameSyntax xmlName = SyntaxFactory.XmlName("returns"); 154 | /// [0]xxx[1][2] 155 | 156 | XmlElementStartTagSyntax startTag = SyntaxFactory.XmlElementStartTag(xmlName); 157 | 158 | XmlTextSyntax contentText = SyntaxFactory.XmlText(content); 159 | 160 | XmlElementEndTagSyntax endTag = SyntaxFactory.XmlElementEndTag(xmlName); 161 | return SyntaxFactory.XmlElement(startTag, SyntaxFactory.SingletonList(contentText), endTag); 162 | } 163 | 164 | /// 165 | /// Creates summary text syntax. 166 | /// 167 | /// The content. 168 | /// A XmlTextSyntax. 169 | private static XmlTextSyntax CreateSummaryTextSyntax(string content) 170 | { 171 | content = " " + content; 172 | /* 173 | /// [0] 174 | /// The code fix provider.[1] [2] 175 | ///[3] 176 | */ 177 | 178 | // [0] -- NewLine token 179 | SyntaxToken newLine0Token = CreateNewLineToken(); 180 | 181 | // [1] -- Content + leading comment exterior trivia 182 | SyntaxTriviaList leadingTrivia = CreateCommentExterior(); 183 | SyntaxToken text1Token = SyntaxFactory.XmlTextLiteral(leadingTrivia, content, content, SyntaxFactory.TriviaList()); 184 | 185 | // [2] -- NewLine token 186 | SyntaxToken newLine2Token = CreateNewLineToken(); 187 | 188 | // [3] -- " " + leading comment exterior 189 | SyntaxTriviaList leadingTrivia2 = CreateCommentExterior(); 190 | SyntaxToken text2Token = SyntaxFactory.XmlTextLiteral(leadingTrivia2, " ", " ", SyntaxFactory.TriviaList()); 191 | 192 | return SyntaxFactory.XmlText(newLine0Token, text1Token, newLine2Token, text2Token); 193 | } 194 | 195 | /// 196 | /// Creates line start text syntax. 197 | /// 198 | /// A XmlTextSyntax. 199 | private static XmlTextSyntax CreateLineStartTextSyntax() 200 | { 201 | /* 202 | ///[0] 203 | /// The code fix provider. 204 | /// 205 | */ 206 | 207 | // [0] " " + leading comment exterior trivia 208 | SyntaxTriviaList xmlText0Leading = CreateCommentExterior(); 209 | SyntaxToken xmlText0LiteralToken = SyntaxFactory.XmlTextLiteral(xmlText0Leading, " ", " ", SyntaxFactory.TriviaList()); 210 | XmlTextSyntax xmlText0 = SyntaxFactory.XmlText(xmlText0LiteralToken); 211 | return xmlText0; 212 | } 213 | 214 | /// 215 | /// Creates line end text syntax. 216 | /// 217 | /// A XmlTextSyntax. 218 | private static XmlTextSyntax CreateLineEndTextSyntax() 219 | { 220 | /* 221 | /// 222 | /// The code fix provider. 223 | /// [0] 224 | */ 225 | 226 | // [0] end line token. 227 | SyntaxToken xmlTextNewLineToken = CreateNewLineToken(); 228 | XmlTextSyntax xmlText = SyntaxFactory.XmlText(xmlTextNewLineToken); 229 | return xmlText; 230 | } 231 | 232 | /// 233 | /// Creates new line token. 234 | /// 235 | /// A SyntaxToken. 236 | private static SyntaxToken CreateNewLineToken() 237 | { 238 | return SyntaxFactory.XmlTextNewLine(Environment.NewLine, false); 239 | } 240 | 241 | /// 242 | /// Creates comment exterior. 243 | /// 244 | /// A SyntaxTriviaList. 245 | private static SyntaxTriviaList CreateCommentExterior() 246 | { 247 | return SyntaxFactory.TriviaList(SyntaxFactory.DocumentationCommentExterior("///")); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/Helper/NameSpliter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DocumentationAssistant.Helper 4 | { 5 | /// 6 | /// The name spliter. 7 | /// 8 | public class NameSpliter 9 | { 10 | /// 11 | /// Splits name by upper character. 12 | /// 13 | /// The name. 14 | /// A list of words. 15 | public static List Split(string name) 16 | { 17 | List words = new List(); 18 | List singleWord = new List(); 19 | 20 | foreach (char c in name) 21 | { 22 | if (char.IsUpper(c) && singleWord.Count > 0) 23 | { 24 | words.Add(new string(singleWord.ToArray())); 25 | singleWord.Clear(); 26 | singleWord.Add(c); 27 | } 28 | else 29 | { 30 | singleWord.Add(c); 31 | } 32 | } 33 | 34 | words.Add(new string(singleWord.ToArray())); 35 | 36 | return words; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/Helper/Pluralizer.cs: -------------------------------------------------------------------------------- 1 | using ThirdPartPluralizer = Pluralize.NET; 2 | 3 | namespace DocumentationAssistant.Helper 4 | { 5 | /// 6 | /// The pluralizer to pluralize word. 7 | /// 8 | public static class Pluralizer 9 | { 10 | /// 11 | /// Pluralizes word. 12 | /// 13 | /// The word. 14 | /// A plural word. 15 | public static string Pluralize(string word) 16 | { 17 | return new ThirdPartPluralizer.Pluralizer().Pluralize(word); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/Helper/PrivateMemberChecker.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | 5 | namespace DocumentationAssistant.Helper 6 | { 7 | /// 8 | /// A checker to check whether a member is private. 9 | /// 10 | public class PrivateMemberChecker 11 | { 12 | public static bool IsPrivateMember(ClassDeclarationSyntax node) 13 | { 14 | if (!node.Modifiers.Any(SyntaxKind.PublicKeyword)) 15 | { 16 | return true; 17 | } 18 | 19 | return false; 20 | } 21 | 22 | public static bool IsPrivateMember(FieldDeclarationSyntax node) 23 | { 24 | if (!node.Modifiers.Any(SyntaxKind.PublicKeyword)) 25 | { 26 | return true; 27 | } 28 | 29 | // If the member is public, we still need to check whether its parent class is a private class. 30 | // Since we don't want show warnings for public members within a private class. 31 | return IsPrivateMember(node.Parent as ClassDeclarationSyntax); 32 | } 33 | 34 | public static bool IsPrivateMember(ConstructorDeclarationSyntax node) 35 | { 36 | if (!node.Modifiers.Any(SyntaxKind.PublicKeyword)) 37 | { 38 | return true; 39 | } 40 | 41 | // If the member is public, we still need to check whether its parent class is a private class. 42 | // Since we don't want show warnings for public members within a private class. 43 | return IsPrivateMember(node.Parent as ClassDeclarationSyntax); 44 | } 45 | 46 | public static bool IsPrivateMember(PropertyDeclarationSyntax node) 47 | { 48 | if (!node.Modifiers.Any(SyntaxKind.PublicKeyword)) 49 | { 50 | return true; 51 | } 52 | 53 | // If the member is public, we still need to check whether its parent class is a private class. 54 | // Since we don't want show warnings for public members within a private class. 55 | return IsPrivateMember(node.Parent as ClassDeclarationSyntax); 56 | } 57 | 58 | public static bool IsPrivateMember(MethodDeclarationSyntax node) 59 | { 60 | if (!node.Modifiers.Any(SyntaxKind.PublicKeyword)) 61 | { 62 | return true; 63 | } 64 | 65 | // If the member is public, we still need to check whether its parent class is a private class. 66 | // Since we don't want show warnings for public members within a private class. 67 | return IsPrivateMember(node.Parent as ClassDeclarationSyntax); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/Helper/ReturnCommentConstruction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp.Syntax; 2 | using System.Collections.Generic; 3 | 4 | namespace DocumentationAssistant.Helper 5 | { 6 | /// 7 | /// The return comment construction. 8 | /// 9 | public class ReturnCommentConstruction 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The return type. 15 | public ReturnCommentConstruction(TypeSyntax returnType) 16 | { 17 | if (returnType is PredefinedTypeSyntax) 18 | { 19 | this.Comment = GeneratePredefinedTypeComment(returnType as PredefinedTypeSyntax); 20 | } 21 | else if (returnType is IdentifierNameSyntax) 22 | { 23 | this.Comment = GenerateIdentifierNameTypeComment(returnType as IdentifierNameSyntax); 24 | } 25 | else if (returnType is QualifiedNameSyntax) 26 | { 27 | this.Comment = GenerateQualifiedNameTypeComment(returnType as QualifiedNameSyntax); 28 | } 29 | else if (returnType is GenericNameSyntax) 30 | { 31 | this.Comment = GenerateGenericTypeComment(returnType as GenericNameSyntax); 32 | } 33 | else if (returnType is ArrayTypeSyntax) 34 | { 35 | this.Comment = this.GenerateArrayTypeComment(returnType as ArrayTypeSyntax); 36 | } 37 | else 38 | { 39 | this.Comment = GenerateGeneralComment(returnType.ToFullString()); 40 | } 41 | } 42 | 43 | /// 44 | /// Generates the comment. 45 | /// 46 | public string Comment { get; } 47 | 48 | /// 49 | /// Generates predefined type comment. 50 | /// 51 | /// The return type. 52 | /// The comment. 53 | private static string GeneratePredefinedTypeComment(PredefinedTypeSyntax returnType) 54 | { 55 | return DetermineStartedWord(returnType.Keyword.ValueText) + " " + returnType.Keyword.ValueText + "."; 56 | } 57 | 58 | /// 59 | /// Generates identifier name type comment. 60 | /// 61 | /// The return type. 62 | /// The comment. 63 | private static string GenerateIdentifierNameTypeComment(IdentifierNameSyntax returnType) 64 | { 65 | return GenerateGeneralComment(returnType.Identifier.ValueText); 66 | } 67 | 68 | /// 69 | /// Generates qualified name type comment. 70 | /// 71 | /// The return type. 72 | /// The comment. 73 | private static string GenerateQualifiedNameTypeComment(QualifiedNameSyntax returnType) 74 | { 75 | return GenerateGeneralComment(returnType.ToString()); 76 | } 77 | 78 | /// 79 | /// Generates array type comment. 80 | /// 81 | /// The array type syntax. 82 | /// The comment. 83 | private string GenerateArrayTypeComment(ArrayTypeSyntax arrayTypeSyntax) 84 | { 85 | return "An array of " + DetermineSpecificObjectName(arrayTypeSyntax.ElementType); 86 | } 87 | 88 | /// 89 | /// Generates generic type comment. 90 | /// 91 | /// The return type. 92 | /// The comment. 93 | private static string GenerateGenericTypeComment(GenericNameSyntax returnType) 94 | { 95 | // ReadOnlyCollection IReadOnlyCollection 96 | string genericTypeStr = returnType.Identifier.ValueText; 97 | if (genericTypeStr.Contains("ReadOnlyCollection")) 98 | { 99 | return "A read only collection of " + DetermineSpecificObjectName(returnType.TypeArgumentList.Arguments.First()); 100 | } 101 | 102 | // IEnumerable IList List 103 | if (genericTypeStr == "IEnumerable" || genericTypeStr.Contains("List")) 104 | { 105 | return "A list of " + DetermineSpecificObjectName(returnType.TypeArgumentList.Arguments.First()); 106 | } 107 | 108 | if (genericTypeStr.Contains("Dictionary")) 109 | { 110 | return GenerateGeneralComment(genericTypeStr); 111 | } 112 | 113 | return GenerateGeneralComment(genericTypeStr); 114 | } 115 | 116 | /// 117 | /// Generates general comment. 118 | /// 119 | /// The return type. 120 | /// The comment. 121 | private static string GenerateGeneralComment(string returnType) 122 | { 123 | return DetermineStartedWord(returnType) + " " + returnType + "."; 124 | } 125 | 126 | /// 127 | /// Determines specific object name. 128 | /// 129 | /// The specific type. 130 | /// The comment. 131 | private static string DetermineSpecificObjectName(TypeSyntax specificType) 132 | { 133 | string result = null; 134 | if (specificType is IdentifierNameSyntax) 135 | { 136 | result = Pluralizer.Pluralize(((IdentifierNameSyntax)specificType).Identifier.ValueText); 137 | } 138 | else if (specificType is PredefinedTypeSyntax) 139 | { 140 | result = (specificType as PredefinedTypeSyntax).Keyword.ValueText; 141 | } 142 | else if (specificType is GenericNameSyntax) 143 | { 144 | result = (specificType as GenericNameSyntax).Identifier.ValueText; 145 | } 146 | else 147 | { 148 | result = specificType.ToFullString(); 149 | } 150 | return result + "."; 151 | } 152 | 153 | /// 154 | /// Determines started word. 155 | /// 156 | /// The return type. 157 | /// The comment. 158 | private static string DetermineStartedWord(string returnType) 159 | { 160 | var vowelChars = new List() { 'a', 'e', 'i', 'o', 'u' }; 161 | if (vowelChars.Contains(char.ToLower(returnType[0]))) 162 | { 163 | return "An"; 164 | } 165 | else 166 | { 167 | return "A"; 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/InterfaceAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using DocumentationAssistant.Helper; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | 9 | namespace DocumentationAssistant 10 | { 11 | /// 12 | /// The interface analyzer. 13 | /// 14 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 15 | public class InterfaceAnalyzer : DiagnosticAnalyzer 16 | { 17 | /// 18 | /// The title. 19 | /// 20 | private const string Title = "The interface must have a documentation header."; 21 | 22 | /// 23 | /// The category. 24 | /// 25 | private const string Category = DocumentationHeaderHelper.Category; 26 | 27 | /// 28 | /// The diagnostic id. 29 | /// 30 | public const string DiagnosticId = "InterfaceDocumentationHeader"; 31 | 32 | /// 33 | /// The message format. 34 | /// 35 | public const string MessageFormat = Title; 36 | 37 | /// 38 | /// The diagnostic descriptor rule. 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); 41 | 42 | /// 43 | /// Gets the supported diagnostics. 44 | /// 45 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 46 | 47 | /// 48 | /// Initializes action. 49 | /// 50 | /// The context. 51 | public override void Initialize(AnalysisContext context) 52 | { 53 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InterfaceDeclaration); 54 | } 55 | 56 | /// 57 | /// Analyzes node. 58 | /// 59 | /// The context. 60 | private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 61 | { 62 | InterfaceDeclarationSyntax node = context.Node as InterfaceDeclarationSyntax; 63 | 64 | DocumentationCommentTriviaSyntax commentTriviaSyntax = node 65 | .GetLeadingTrivia() 66 | .Select(o => o.GetStructure()) 67 | .OfType() 68 | .FirstOrDefault(); 69 | 70 | if (commentTriviaSyntax != null && CommentHelper.HasComment(commentTriviaSyntax)) 71 | { 72 | return; 73 | } 74 | 75 | context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation())); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/InterfaceCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DocumentationAssistant.Helper; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | namespace DocumentationAssistant 14 | { 15 | /// 16 | /// The interface code fix provider. 17 | /// 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(InterfaceCodeFixProvider)), Shared] 19 | public class InterfaceCodeFixProvider : CodeFixProvider 20 | { 21 | /// 22 | /// The title. 23 | /// 24 | private const string Title = "Add documentation header to this interface"; 25 | 26 | /// 27 | /// Gets the fixable diagnostic ids. 28 | /// 29 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(InterfaceAnalyzer.DiagnosticId); 30 | 31 | /// 32 | /// Gets fix all provider. 33 | /// 34 | /// A FixAllProvider. 35 | public sealed override FixAllProvider GetFixAllProvider() 36 | { 37 | return WellKnownFixAllProviders.BatchFixer; 38 | } 39 | 40 | /// 41 | /// Registers code fixes async. 42 | /// 43 | /// The context. 44 | /// A Task. 45 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 46 | { 47 | SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 48 | 49 | Diagnostic diagnostic = context.Diagnostics.First(); 50 | Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 51 | 52 | InterfaceDeclarationSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 53 | 54 | context.RegisterCodeFix( 55 | CodeAction.Create( 56 | title: Title, 57 | createChangedDocument: c => this.AddDocumentationHeaderAsync(context.Document, root, declaration, c), 58 | equivalenceKey: Title), 59 | diagnostic); 60 | } 61 | 62 | /// 63 | /// Adds documentation header async. 64 | /// 65 | /// The document. 66 | /// The root. 67 | /// The declaration syntax. 68 | /// The cancellation token. 69 | /// A Document. 70 | private async Task AddDocumentationHeaderAsync(Document document, SyntaxNode root, InterfaceDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) 71 | { 72 | SyntaxTriviaList leadingTrivia = declarationSyntax.GetLeadingTrivia(); 73 | 74 | string comment = CommentHelper.CreateInterfaceComment(declarationSyntax.Identifier.ValueText); 75 | DocumentationCommentTriviaSyntax commentTrivia = await Task.Run(() => DocumentationHeaderHelper.CreateOnlySummaryDocumentationCommentTrivia(comment), cancellationToken); 76 | 77 | SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(leadingTrivia.Count - 1, SyntaxFactory.Trivia(commentTrivia)); 78 | InterfaceDeclarationSyntax newDeclaration = declarationSyntax.WithLeadingTrivia(newLeadingTrivia); 79 | 80 | SyntaxNode newRoot = root.ReplaceNode(declarationSyntax, newDeclaration); 81 | return document.WithSyntaxRoot(newRoot); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/MethodAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using DocumentationAssistant.Helper; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | 9 | namespace DocumentationAssistant 10 | { 11 | /// 12 | /// The method analyzer. 13 | /// 14 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 15 | public class MethodAnalyzer : DiagnosticAnalyzer 16 | { 17 | /// 18 | /// The title. 19 | /// 20 | private const string Title = "The method must have a documentation header."; 21 | 22 | /// 23 | /// The category. 24 | /// 25 | private const string Category = DocumentationHeaderHelper.Category; 26 | 27 | /// 28 | /// The diagnostic id. 29 | /// 30 | public const string DiagnosticId = "MethodDocumentationHeader"; 31 | 32 | /// 33 | /// The message format. 34 | /// 35 | public const string MessageFormat = Title; 36 | 37 | /// 38 | /// The diagnostic descriptor rule. 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); 41 | 42 | /// 43 | /// Gets the supported diagnostics. 44 | /// 45 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 46 | 47 | /// 48 | /// Initializes. 49 | /// 50 | /// The context. 51 | public override void Initialize(AnalysisContext context) 52 | { 53 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.MethodDeclaration); 54 | } 55 | 56 | /// 57 | /// Analyzes node. 58 | /// 59 | /// The context. 60 | private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 61 | { 62 | MethodDeclarationSyntax node = context.Node as MethodDeclarationSyntax; 63 | 64 | if (Configuration.IsEnabledForPublicMembersOnly && PrivateMemberChecker.IsPrivateMember(node)) 65 | { 66 | return; 67 | } 68 | 69 | DocumentationCommentTriviaSyntax commentTriviaSyntax = node 70 | .GetLeadingTrivia() 71 | .Select(o => o.GetStructure()) 72 | .OfType() 73 | .FirstOrDefault(); 74 | 75 | if (commentTriviaSyntax != null && CommentHelper.HasComment(commentTriviaSyntax)) 76 | { 77 | return; 78 | } 79 | 80 | context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation())); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/MethodCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DocumentationAssistant.Helper; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | namespace DocumentationAssistant 14 | { 15 | /// 16 | /// The method code fix provider. 17 | /// 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MethodCodeFixProvider)), Shared] 19 | public class MethodCodeFixProvider : CodeFixProvider 20 | { 21 | /// 22 | /// The title. 23 | /// 24 | private const string Title = "Add documentation header to this method"; 25 | 26 | /// 27 | /// Gets the fixable diagnostic ids. 28 | /// 29 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(MethodAnalyzer.DiagnosticId); 30 | 31 | /// 32 | /// Gets fix all provider. 33 | /// 34 | /// A FixAllProvider. 35 | public sealed override FixAllProvider GetFixAllProvider() 36 | { 37 | return WellKnownFixAllProviders.BatchFixer; 38 | } 39 | 40 | /// 41 | /// Registers code fixes async. 42 | /// 43 | /// The context. 44 | /// A Task. 45 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 46 | { 47 | SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 48 | 49 | Diagnostic diagnostic = context.Diagnostics.First(); 50 | Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 51 | 52 | MethodDeclarationSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 53 | 54 | context.RegisterCodeFix( 55 | CodeAction.Create( 56 | title: Title, 57 | createChangedDocument: c => this.AddDocumentationHeaderAsync(context.Document, root, declaration, c), 58 | equivalenceKey: Title), 59 | diagnostic); 60 | } 61 | 62 | /// 63 | /// Adds documentation header async. 64 | /// 65 | /// The document. 66 | /// The root. 67 | /// The declaration syntax. 68 | /// The cancellation token. 69 | /// A Task. 70 | private async Task AddDocumentationHeaderAsync(Document document, SyntaxNode root, MethodDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) 71 | { 72 | SyntaxTriviaList leadingTrivia = declarationSyntax.GetLeadingTrivia(); 73 | DocumentationCommentTriviaSyntax commentTrivia = await Task.Run(() => CreateDocumentationCommentTriviaSyntax(declarationSyntax), cancellationToken); 74 | 75 | SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(leadingTrivia.Count - 1, SyntaxFactory.Trivia(commentTrivia)); 76 | MethodDeclarationSyntax newDeclaration = declarationSyntax.WithLeadingTrivia(newLeadingTrivia); 77 | 78 | SyntaxNode newRoot = root.ReplaceNode(declarationSyntax, newDeclaration); 79 | return document.WithSyntaxRoot(newRoot); 80 | } 81 | 82 | /// 83 | /// Creates documentation comment trivia syntax. 84 | /// 85 | /// The declaration syntax. 86 | /// A DocumentationCommentTriviaSyntax. 87 | private static DocumentationCommentTriviaSyntax CreateDocumentationCommentTriviaSyntax(MethodDeclarationSyntax declarationSyntax) 88 | { 89 | SyntaxList list = SyntaxFactory.List(); 90 | 91 | string methodComment = CommentHelper.CreateMethodComment(declarationSyntax.Identifier.ValueText); 92 | list = list.AddRange(DocumentationHeaderHelper.CreateSummaryPartNodes(methodComment)); 93 | 94 | if (declarationSyntax.ParameterList.Parameters.Any()) 95 | { 96 | foreach (ParameterSyntax parameter in declarationSyntax.ParameterList.Parameters) 97 | { 98 | string parameterComment = CommentHelper.CreateParameterComment(parameter); 99 | list = list.AddRange(DocumentationHeaderHelper.CreateParameterPartNodes(parameter.Identifier.ValueText, parameterComment)); 100 | } 101 | } 102 | 103 | string returnType = declarationSyntax.ReturnType.ToString(); 104 | if (returnType != "void") 105 | { 106 | string returnComment = new ReturnCommentConstruction(declarationSyntax.ReturnType).Comment; 107 | list = list.AddRange(DocumentationHeaderHelper.CreateReturnPartNodes(returnComment)); 108 | } 109 | 110 | return SyntaxFactory.DocumentationCommentTrivia(SyntaxKind.SingleLineDocumentationCommentTrivia, list); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/PropertyAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Linq; 3 | using DocumentationAssistant.Helper; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CSharp; 6 | using Microsoft.CodeAnalysis.CSharp.Syntax; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | 9 | namespace DocumentationAssistant 10 | { 11 | /// 12 | /// The property analyzer. 13 | /// 14 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 15 | public class PropertyAnalyzer : DiagnosticAnalyzer 16 | { 17 | /// 18 | /// The title. 19 | /// 20 | private const string Title = "The property must have a documentation header."; 21 | 22 | /// 23 | /// The category. 24 | /// 25 | private const string Category = DocumentationHeaderHelper.Category; 26 | 27 | /// 28 | /// The diagnostic id. 29 | /// 30 | public const string DiagnosticId = "PropertyDocumentationHeader"; 31 | 32 | /// 33 | /// The message format. 34 | /// 35 | public const string MessageFormat = Title; 36 | 37 | /// 38 | /// The diagnostic descriptor rule. 39 | /// 40 | private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true); 41 | 42 | /// 43 | /// Gets the supported diagnostics. 44 | /// 45 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); 46 | 47 | /// 48 | /// Initializes. 49 | /// 50 | /// The context. 51 | public override void Initialize(AnalysisContext context) 52 | { 53 | context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.PropertyDeclaration); 54 | } 55 | 56 | /// 57 | /// Analyzes node. 58 | /// 59 | /// The context. 60 | private static void AnalyzeNode(SyntaxNodeAnalysisContext context) 61 | { 62 | PropertyDeclarationSyntax node = context.Node as PropertyDeclarationSyntax; 63 | 64 | if (Configuration.IsEnabledForPublicMembersOnly && PrivateMemberChecker.IsPrivateMember(node)) 65 | { 66 | return; 67 | } 68 | 69 | DocumentationCommentTriviaSyntax commentTriviaSyntax = node 70 | .GetLeadingTrivia() 71 | .Select(o => o.GetStructure()) 72 | .OfType() 73 | .FirstOrDefault(); 74 | 75 | if (commentTriviaSyntax != null && CommentHelper.HasComment(commentTriviaSyntax)) 76 | { 77 | return; 78 | } 79 | 80 | context.ReportDiagnostic(Diagnostic.Create(Rule, node.Identifier.GetLocation())); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/PropertyCodeFixProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using System.Composition; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using DocumentationAssistant.Helper; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CodeActions; 9 | using Microsoft.CodeAnalysis.CodeFixes; 10 | using Microsoft.CodeAnalysis.CSharp; 11 | using Microsoft.CodeAnalysis.CSharp.Syntax; 12 | 13 | namespace DocumentationAssistant 14 | { 15 | /// 16 | /// The property code fix provider. 17 | /// 18 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PropertyCodeFixProvider)), Shared] 19 | public class PropertyCodeFixProvider : CodeFixProvider 20 | { 21 | /// 22 | /// The title. 23 | /// 24 | private const string Title = "Add documentation header to this property"; 25 | 26 | /// 27 | /// Gets the fixable diagnostic ids. 28 | /// 29 | public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(PropertyAnalyzer.DiagnosticId); 30 | 31 | /// 32 | /// Gets fix all provider. 33 | /// 34 | /// A FixAllProvider. 35 | public sealed override FixAllProvider GetFixAllProvider() 36 | { 37 | return WellKnownFixAllProviders.BatchFixer; 38 | } 39 | 40 | /// 41 | /// Registers code fixes async. 42 | /// 43 | /// The context. 44 | /// A Task. 45 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 46 | { 47 | SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 48 | 49 | Diagnostic diagnostic = context.Diagnostics.First(); 50 | Microsoft.CodeAnalysis.Text.TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; 51 | 52 | PropertyDeclarationSyntax declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); 53 | 54 | context.RegisterCodeFix( 55 | CodeAction.Create( 56 | title: Title, 57 | createChangedDocument: c => this.AddDocumentationHeaderAsync(context.Document, root, declaration, c), 58 | equivalenceKey: Title), 59 | diagnostic); 60 | } 61 | 62 | /// 63 | /// Adds documentation header async. 64 | /// 65 | /// The document. 66 | /// The root. 67 | /// The declaration syntax. 68 | /// The cancellation token. 69 | /// A Document. 70 | private async Task AddDocumentationHeaderAsync(Document document, SyntaxNode root, PropertyDeclarationSyntax declarationSyntax, CancellationToken cancellationToken) 71 | { 72 | SyntaxTriviaList leadingTrivia = declarationSyntax.GetLeadingTrivia(); 73 | 74 | bool isBoolean = false; 75 | if (declarationSyntax.Type.IsKind(SyntaxKind.PredefinedType)) 76 | { 77 | isBoolean = ((PredefinedTypeSyntax)declarationSyntax.Type).Keyword.Kind() == SyntaxKind.BoolKeyword; 78 | } 79 | else if (declarationSyntax.Type.IsKind(SyntaxKind.NullableType)) 80 | { 81 | var retrunType = ((NullableTypeSyntax)declarationSyntax.Type).ElementType as PredefinedTypeSyntax; 82 | isBoolean = retrunType?.Keyword.Kind() == SyntaxKind.BoolKeyword; 83 | } 84 | 85 | bool hasSetter = false; 86 | 87 | if (declarationSyntax.AccessorList != null && declarationSyntax.AccessorList.Accessors.Any(o => o.Kind() == SyntaxKind.SetAccessorDeclaration)) 88 | { 89 | if (declarationSyntax.AccessorList.Accessors.First(o => o.Kind() == SyntaxKind.SetAccessorDeclaration).ChildTokens().Any(o => o.Kind() == SyntaxKind.PrivateKeyword || o.Kind() == SyntaxKind.InternalKeyword)) 90 | { 91 | // private set or internal set should consider as no set. 92 | hasSetter = false; 93 | } 94 | else 95 | { 96 | hasSetter = true; 97 | } 98 | } 99 | 100 | string propertyComment = CommentHelper.CreatePropertyComment(declarationSyntax.Identifier.ValueText, isBoolean, hasSetter); 101 | DocumentationCommentTriviaSyntax commentTrivia = await Task.Run(() => DocumentationHeaderHelper.CreateOnlySummaryDocumentationCommentTrivia(propertyComment), cancellationToken); 102 | 103 | SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(leadingTrivia.Count - 1, SyntaxFactory.Trivia(commentTrivia)); 104 | PropertyDeclarationSyntax newDeclaration = declarationSyntax.WithLeadingTrivia(newLeadingTrivia); 105 | 106 | SyntaxNode newRoot = root.ReplaceNode(declarationSyntax, newDeclaration); 107 | return document.WithSyntaxRoot(newRoot); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not install analyzers via install.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | if (Test-Path $analyzersPath) 17 | { 18 | # Install the language agnostic analyzers. 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Install language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /DocumentationAssistant/DocumentationAssistant/tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | if($project.Object.SupportsPackageDependencyResolution) 4 | { 5 | if($project.Object.SupportsPackageDependencyResolution()) 6 | { 7 | # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. 8 | return 9 | } 10 | } 11 | 12 | $analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve 13 | 14 | foreach($analyzersPath in $analyzersPaths) 15 | { 16 | # Uninstall the language agnostic analyzers. 17 | if (Test-Path $analyzersPath) 18 | { 19 | foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) 20 | { 21 | if($project.Object.AnalyzerReferences) 22 | { 23 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 24 | } 25 | } 26 | } 27 | } 28 | 29 | # $project.Type gives the language name like (C# or VB.NET) 30 | $languageFolder = "" 31 | if($project.Type -eq "C#") 32 | { 33 | $languageFolder = "cs" 34 | } 35 | if($project.Type -eq "VB.NET") 36 | { 37 | $languageFolder = "vb" 38 | } 39 | if($languageFolder -eq "") 40 | { 41 | return 42 | } 43 | 44 | foreach($analyzersPath in $analyzersPaths) 45 | { 46 | # Uninstall language specific analyzers. 47 | $languageAnalyzersPath = join-path $analyzersPath $languageFolder 48 | if (Test-Path $languageAnalyzersPath) 49 | { 50 | foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) 51 | { 52 | if($project.Object.AnalyzerReferences) 53 | { 54 | try 55 | { 56 | $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) 57 | } 58 | catch 59 | { 60 | 61 | } 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /GifInstruction/quick action options.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinyafeng/DocumentationAssistant/c6608d386df4d0538ed00102776459a503240ae9/GifInstruction/quick action options.gif -------------------------------------------------------------------------------- /GifInstruction/short cut to quick add.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinyafeng/DocumentationAssistant/c6608d386df4d0538ed00102776459a503240ae9/GifInstruction/short cut to quick add.gif -------------------------------------------------------------------------------- /GifInstruction/warning wave line.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinyafeng/DocumentationAssistant/c6608d386df4d0538ed00102776459a503240ae9/GifInstruction/warning wave line.gif -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Documentation Assistant 2 | A Visual Studio Extension to generate XML documentation automatically for c# code using IntelliSense for interface,class,enum, field, constructor, property and method. 3 | 4 | **You can see here [VS marketplace](https://marketplace.visualstudio.com/items?itemName=jinyafeng.DocumentationAssistant) for more information.** 5 | --------------------------------------------------------------------------------