├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE ├── QuickConstructor.sln ├── README.md ├── src ├── QuickConstructor.Attributes │ ├── Accessibility.cs │ ├── IncludeFields.cs │ ├── IncludeProperties.cs │ ├── NullChecks.cs │ ├── QuickConstructor.Attributes.csproj │ ├── QuickConstructorAttribute.cs │ └── QuickConstructorParameterAttribute.cs └── QuickConstructor.Generator │ ├── ClassMembersAnalyzer.cs │ ├── ClassSymbolProcessor.cs │ ├── ConstructorDescriptor.cs │ ├── ConstructorParameter.cs │ ├── DiagnosticDescriptors.cs │ ├── DiagnosticException.cs │ ├── QuickConstructor.Generator.csproj │ ├── QuickConstructorGenerator.cs │ ├── SourceRenderer.cs │ └── SymbolExtensions.cs └── test └── QuickConstructor.Tests ├── BaseClassTests.cs ├── CSharpIncrementalGeneratorTest.cs ├── DiagnosticsTests.cs ├── GeneratedCodeTests.cs └── QuickConstructor.Tests.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | # Top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.{xml,json,html,cshtml,csproj}] 11 | 12 | # use soft tabs (spaces) for indentation 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.sh] 17 | end_of_line = lf 18 | 19 | [*.cs] 20 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference 21 | 22 | # New line preferences 23 | csharp_new_line_before_open_brace = all 24 | csharp_new_line_before_else = true 25 | csharp_new_line_before_catch = true 26 | csharp_new_line_before_finally = true 27 | csharp_new_line_before_members_in_object_initializers = true 28 | csharp_new_line_before_members_in_anonymous_types = true 29 | csharp_new_line_between_query_expression_clauses = true 30 | 31 | # Indentation preferences 32 | csharp_indent_block_contents = true 33 | csharp_indent_braces = false 34 | csharp_indent_case_contents = true 35 | csharp_indent_switch_labels = true 36 | csharp_indent_labels = one_less_than_current 37 | 38 | # avoid this. unless absolutely necessary 39 | dotnet_style_qualification_for_field = false:warning 40 | dotnet_style_qualification_for_property = false:warning 41 | dotnet_style_qualification_for_method = false:warning 42 | dotnet_style_qualification_for_event = false:warning 43 | 44 | # only use var when it's obvious what the variable type is 45 | csharp_style_var_for_built_in_types = false:warning 46 | csharp_style_var_when_type_is_apparent = false:warning 47 | csharp_style_var_elsewhere = false:warning 48 | 49 | # use language keywords instead of BCL types 50 | dotnet_style_predefined_type_for_locals_parameters_members = true:warning 51 | dotnet_style_predefined_type_for_member_access = true:warning 52 | 53 | # name all constant fields using PascalCase 54 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = warning 55 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 56 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 57 | 58 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 59 | dotnet_naming_symbols.constant_fields.required_modifiers = const 60 | 61 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 62 | 63 | # internal and private fields should be _camelCase 64 | dotnet_naming_rule.camel_case_for_private_internal_fields.severity = warning 65 | dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields 66 | dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style 67 | 68 | dotnet_naming_symbols.private_internal_fields.applicable_kinds = field 69 | dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal 70 | 71 | dotnet_naming_style.camel_case_underscore_style.required_prefix = _ 72 | dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case 73 | 74 | # Locals and parameters are camelCase 75 | dotnet_naming_rule.locals_should_be_camel_case.severity = warning 76 | dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters 77 | dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style 78 | 79 | dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local 80 | 81 | dotnet_naming_style.camel_case_style.capitalization = camel_case 82 | 83 | # By default, name items with PascalCase 84 | dotnet_naming_rule.members_should_be_pascal_case.severity = warning 85 | dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members 86 | dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style 87 | 88 | dotnet_naming_symbols.all_members.applicable_kinds = * 89 | 90 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 91 | 92 | # Code style defaults 93 | dotnet_sort_system_directives_first = true 94 | csharp_style_namespace_declarations = file_scoped:warning 95 | csharp_using_directive_placement = inside_namespace:warning 96 | csharp_preserve_single_line_blocks = true 97 | csharp_preserve_single_line_statements = false 98 | csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:warning 99 | csharp_style_implicit_object_creation_when_type_is_apparent = true:warning 100 | 101 | # Expression-bodied members 102 | csharp_style_expression_bodied_methods = false:none 103 | csharp_style_expression_bodied_constructors = false:none 104 | csharp_style_expression_bodied_operators = false:none 105 | csharp_style_expression_bodied_properties = true:none 106 | csharp_style_expression_bodied_indexers = true:none 107 | csharp_style_expression_bodied_accessors = true:none 108 | 109 | # Expression-level preferences 110 | dotnet_style_object_initializer = true:suggestion 111 | dotnet_style_collection_initializer = true:suggestion 112 | dotnet_style_explicit_tuple_names = true:suggestion 113 | dotnet_style_coalesce_expression = true:suggestion 114 | dotnet_style_null_propagation = true:suggestion 115 | 116 | # Pattern matching 117 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 118 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 119 | csharp_style_inlined_variable_declaration = true:suggestion 120 | 121 | # Null checking preferences 122 | csharp_style_throw_expression = true:suggestion 123 | csharp_style_conditional_delegate_call = true:suggestion 124 | 125 | # Space preferences 126 | csharp_space_after_cast = false 127 | csharp_space_after_colon_in_inheritance_clause = true 128 | csharp_space_after_comma = true 129 | csharp_space_after_dot = false 130 | csharp_space_after_keywords_in_control_flow_statements = true 131 | csharp_space_after_semicolon_in_for_statement = true 132 | csharp_space_around_binary_operators = before_and_after 133 | csharp_space_around_declaration_statements = do_not_ignore 134 | csharp_space_before_colon_in_inheritance_clause = true 135 | csharp_space_before_comma = false 136 | csharp_space_before_dot = false 137 | csharp_space_before_open_square_brackets = false 138 | csharp_space_before_semicolon_in_for_statement = false 139 | csharp_space_between_empty_square_brackets = false 140 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 141 | csharp_space_between_method_call_name_and_opening_parenthesis = false 142 | csharp_space_between_method_call_parameter_list_parentheses = false 143 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 144 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 145 | csharp_space_between_method_declaration_parameter_list_parentheses = false 146 | csharp_space_between_parentheses = false 147 | csharp_space_between_square_brackets = false 148 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | *.cs text diff=csharp 7 | *.*proj text 8 | *.sln text 9 | *.html text diff=html 10 | *.cshtml text diff=html 11 | *.css text 12 | *.less text 13 | *.js text 14 | *.config text 15 | *.targets text 16 | *.aspx text 17 | *.asax text 18 | *.xml text 19 | *.xaml text 20 | *.txt text 21 | *.sql text 22 | *.cmd text 23 | *.md text 24 | *.tt text 25 | *.ps1 text 26 | *.py text 27 | *.sh eol=lf 28 | *.conf eol=lf 29 | 30 | ############################################################################### 31 | # Set default behavior for command prompt diff. 32 | # 33 | # This is need for earlier builds of msysgit that does not have it on by 34 | # default for csharp files. 35 | # Note: This is only used by command line 36 | ############################################################################### 37 | #*.cs diff=csharp 38 | 39 | ############################################################################### 40 | # Set the merge driver for project and solution files 41 | # 42 | # Merging from the command prompt will add diff markers to the files if there 43 | # are conflicts (Merging from VS is not affected by the settings below, in VS 44 | # the diff markers are never inserted). Diff markers may cause the following 45 | # file extensions to fail to load in VS. An alternative would be to treat 46 | # these files as binary and thus will always conflict and require user 47 | # intervention with every merge. To do so, just uncomment the entries below 48 | ############################################################################### 49 | #*.sln merge=binary 50 | #*.csproj merge=binary 51 | #*.vbproj merge=binary 52 | #*.vcxproj merge=binary 53 | #*.vcproj merge=binary 54 | #*.dbproj merge=binary 55 | #*.fsproj merge=binary 56 | #*.lsproj merge=binary 57 | #*.wixproj merge=binary 58 | #*.modelproj merge=binary 59 | #*.sqlproj merge=binary 60 | #*.wwaproj merge=binary 61 | 62 | ############################################################################### 63 | # behavior for image files 64 | # 65 | # image files are treated as binary by default. 66 | ############################################################################### 67 | #*.jpg binary 68 | #*.png binary 69 | #*.gif binary 70 | 71 | ############################################################################### 72 | # diff behavior for common document formats 73 | # 74 | # Convert binary document formats to text before diffing them. This feature 75 | # is only available from the command line. Turn it on by uncommenting the 76 | # entries below. 77 | ############################################################################### 78 | #*.doc diff=astextplain 79 | #*.DOC diff=astextplain 80 | #*.docx diff=astextplain 81 | #*.DOCX diff=astextplain 82 | #*.dot diff=astextplain 83 | #*.DOT diff=astextplain 84 | #*.pdf diff=astextplain 85 | #*.PDF diff=astextplain 86 | #*.rtf diff=astextplain 87 | #*.RTF diff=astextplain 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | # NuGet v3's project.json files produces more ignoreable files 155 | *.nuget.props 156 | *.nuget.targets 157 | 158 | # Microsoft Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Microsoft Azure Emulator 163 | ecf/ 164 | rcf/ 165 | 166 | # Microsoft Azure ApplicationInsights config file 167 | ApplicationInsights.config 168 | 169 | # Windows Store app package directories and files 170 | AppPackages/ 171 | BundleArtifacts/ 172 | Package.StoreAssociation.xml 173 | _pkginfo.txt 174 | 175 | # Visual Studio cache files 176 | # files ending in .cache can be ignored 177 | *.[Cc]ache 178 | # but keep track of directories ending in .cache 179 | !*.[Cc]ache/ 180 | 181 | # Others 182 | ClientBin/ 183 | ~$* 184 | *~ 185 | *.dbmdl 186 | *.dbproj.schemaview 187 | *.pfx 188 | *.publishsettings 189 | node_modules/ 190 | orleans.codegen.cs 191 | 192 | # Since there are multiple workflows, uncomment next line to ignore bower_components 193 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 194 | #bower_components/ 195 | 196 | # RIA/Silverlight projects 197 | Generated_Code/ 198 | 199 | # Backup & report files from converting an old project file 200 | # to a newer Visual Studio version. Backup files are not needed, 201 | # because we have git ;-) 202 | _UpgradeReport_Files/ 203 | Backup*/ 204 | UpgradeLog*.XML 205 | UpgradeLog*.htm 206 | 207 | # SQL Server files 208 | *.mdf 209 | *.ldf 210 | 211 | # Business Intelligence projects 212 | *.rdl.data 213 | *.bim.layout 214 | *.bim_*.settings 215 | 216 | # Microsoft Fakes 217 | FakesAssemblies/ 218 | 219 | # GhostDoc plugin setting file 220 | *.GhostDoc.xml 221 | 222 | # Node.js Tools for Visual Studio 223 | .ntvs_analysis.dat 224 | 225 | # Visual Studio 6 build log 226 | *.plg 227 | 228 | # Visual Studio 6 workspace options file 229 | *.opt 230 | 231 | # Visual Studio LightSwitch build output 232 | **/*.HTMLClient/GeneratedArtifacts 233 | **/*.DesktopClient/GeneratedArtifacts 234 | **/*.DesktopClient/ModelManifest.xml 235 | **/*.Server/GeneratedArtifacts 236 | **/*.Server/ModelManifest.xml 237 | _Pvt_Extensions 238 | 239 | # Paket dependency manager 240 | .paket/paket.exe 241 | 242 | # FAKE - F# Make 243 | .fake/ 244 | 245 | # JetBrains Rider 246 | .idea/ 247 | *.sln.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2022 Flavien Charlon 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /QuickConstructor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32328.378 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickConstructor.Generator", "src\QuickConstructor.Generator\QuickConstructor.Generator.csproj", "{16A18D69-90EF-4EAD-9C76-0BA009BF1D6D}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F2287042-4465-4036-9662-5BD2097E6351}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DD76DE8E-1708-4620-81BB-7378DBB4B55F}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickConstructor.Tests", "test\QuickConstructor.Tests\QuickConstructor.Tests.csproj", "{9A8B0C16-88E7-44C8-8801-3C438B1CF5A0}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickConstructor.Attributes", "src\QuickConstructor.Attributes\QuickConstructor.Attributes.csproj", "{BE42C923-3A8D-4D4E-8812-C373AF96ED89}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {16A18D69-90EF-4EAD-9C76-0BA009BF1D6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {16A18D69-90EF-4EAD-9C76-0BA009BF1D6D}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {16A18D69-90EF-4EAD-9C76-0BA009BF1D6D}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {16A18D69-90EF-4EAD-9C76-0BA009BF1D6D}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {9A8B0C16-88E7-44C8-8801-3C438B1CF5A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {9A8B0C16-88E7-44C8-8801-3C438B1CF5A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {9A8B0C16-88E7-44C8-8801-3C438B1CF5A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {9A8B0C16-88E7-44C8-8801-3C438B1CF5A0}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {BE42C923-3A8D-4D4E-8812-C373AF96ED89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {BE42C923-3A8D-4D4E-8812-C373AF96ED89}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {BE42C923-3A8D-4D4E-8812-C373AF96ED89}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {BE42C923-3A8D-4D4E-8812-C373AF96ED89}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(NestedProjects) = preSolution 39 | {16A18D69-90EF-4EAD-9C76-0BA009BF1D6D} = {F2287042-4465-4036-9662-5BD2097E6351} 40 | {9A8B0C16-88E7-44C8-8801-3C438B1CF5A0} = {DD76DE8E-1708-4620-81BB-7378DBB4B55F} 41 | {BE42C923-3A8D-4D4E-8812-C373AF96ED89} = {F2287042-4465-4036-9662-5BD2097E6351} 42 | EndGlobalSection 43 | GlobalSection(ExtensibilityGlobals) = postSolution 44 | SolutionGuid = {7000C4EC-E258-4440-8B51-DCB4CBBE3D75} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickConstructor 2 | [![QuickConstructor](https://img.shields.io/nuget/v/QuickConstructor.svg?style=flat-square&color=blue&logo=nuget)](https://www.nuget.org/packages/QuickConstructor/) 3 | 4 | QuickConstructor is a reliable and feature-rich source generator that can automatically emit a constructor from the fields and properties of a class. 5 | 6 | ## Features 7 | 8 | - Decorate any class with the `[QuickConstructor]` attribute to automatically generate a constructor for that class. 9 | - The constructor updates in real-time as the class is modified. 10 | - Customize which fields and properties are initialized in the constructor. 11 | - Generate null checks automatically based on nullable annotations. 12 | - Works with nested classes and generic classes. 13 | - Supports derived classes. 14 | - Supports classes, records and structs. 15 | - Ability to place attributes on the parameters of the generated constructor. 16 | - No traces left after compilation, no runtime reference necessary. 17 | - Generate XML documentation automatically for the constructor. 18 | - Lightning fast thanks to the .NET 6.0 incremental source generator system. 19 | 20 | ## Example 21 | 22 | Code without QuickConstructor: 23 | 24 | ```csharp 25 | public class Car 26 | { 27 | private readonly string _registration; 28 | private readonly string _model; 29 | private readonly string _make; 30 | private readonly string _color; 31 | private readonly int _year; 32 | 33 | public Car(string registration, string model, string make, string color, int year) 34 | { 35 | _registration = registration; 36 | _model = model; 37 | _make = make; 38 | _color = color; 39 | _year = year; 40 | } 41 | } 42 | ``` 43 | 44 | With QuickConstructor, this becomes: 45 | 46 | ```csharp 47 | [QuickConstructor] 48 | public partial class Car 49 | { 50 | private readonly string _registration; 51 | private readonly string _model; 52 | private readonly string _make; 53 | private readonly string _color; 54 | private readonly int _year; 55 | } 56 | ``` 57 | 58 | The constructor is automatically generated from the field definitions. 59 | 60 | ## Installation 61 | 62 | The requirements to use the QuickConstructor package are the following: 63 | 64 | - Visual Studio 17.0+ 65 | - .NET SDK 6.0.100+ 66 | 67 | Install the NuGet package: 68 | 69 | ``` 70 | dotnet add package QuickConstructor 71 | ``` 72 | 73 | ## Usage 74 | 75 | QuickConstructor is very easy to use. By simply decorating a class with the `[QuickConstructor]` attribute and making the class `partial`, the source generator will automatically create a constructor based on fields and properties declared in the class. The constructor will automatically update to reflect any change made to the class. 76 | 77 | QuickConstructor offers options to customize various aspects of the constructors being generated. 78 | 79 | ### Fields selection 80 | 81 | Quick constructors will always initialize read-only fields as the constructor would otherwise cause a compilation error. However mutable fields can either be included or excluded from the constructor. This is controlled via the `Fields` property of the `[QuickConstructor]` attribute. The possible values are: 82 | 83 | | Value | Description | 84 | | ------------------------------ | ----------- | 85 | | `IncludeFields.ReadOnlyFields` | **(default)** Only read-only fields are initialized in the constructor. | 86 | | `IncludeFields.AllFields` | All fields are initialized in the constructor. | 87 | 88 | Fields with an initializer are never included as part of the constructor. 89 | 90 | ### Properties selection 91 | 92 | It is possible to control which property is initialized in the constructor via the `Properties` property of the `[QuickConstructor]` attribute. The possible values are: 93 | 94 | | Value | Description | 95 | | ------------------------ | ----------- | 96 | | `IncludeProperties.None` | No property is initialized in the constructor. | 97 | | `IncludeProperties.ReadOnlyProperties` | **(default)** Only read-only auto-implemented properties are initialized in the constructor. | 98 | | `IncludeProperties.AllProperties` | All settable properties are initialized in the constructor. | 99 | 100 | Properties with an initializer are never included as part of the constructor. 101 | 102 | ### Null checks 103 | 104 | QuickConstructor has the ability to generate null checks for reference parameters. This is controlled via the `NullCheck` property of the `[QuickConstructor]` attribute. The possible values are: 105 | 106 | | Value | Description | 107 | | ------------------- | ----------- | 108 | | `NullChecks.Always` | Null checks are generated for any field or property whose type is a reference type. | 109 | | `NullChecks.Never` | Null checks are not generated for this constructor. | 110 | | `NullChecks.NonNullableReferencesOnly` | **(default)** When null-state analysis is enabled (C# 8.0 and later), a null check will be generated only if a type is marked as non-nullable. When null-state analysis is disabled, no null check is generated. | 111 | 112 | For example, with null-state analysis enabled: 113 | 114 | ```csharp 115 | [QuickConstructor] 116 | public partial class Name 117 | { 118 | private readonly string _firstName; 119 | private readonly string? _middleName; 120 | private readonly string _lastName; 121 | } 122 | ``` 123 | 124 | This code will result in the following constructor being generated: 125 | 126 | ```csharp 127 | public Name(string firstName, string? middleName, string lastName) 128 | { 129 | if (firstName == null) 130 | throw new ArgumentNullException(nameof(firstName)); 131 | 132 | if (lastName == null) 133 | throw new ArgumentNullException(nameof(lastName)); 134 | 135 | this._firstName = firstName; 136 | this._middleName = middleName; 137 | this._lastName = lastName; 138 | } 139 | ``` 140 | 141 | ### Explicitely include a field or property 142 | 143 | It is possible to explicitely include a field or property by decorating it with the `[QuickConstructorParameter]`. 144 | 145 | For example: 146 | 147 | ```csharp 148 | [QuickConstructor] 149 | public partial class Vehicle 150 | { 151 | [QuickConstructorParameter] 152 | private int _mileage; 153 | 154 | private int _speed; 155 | } 156 | ``` 157 | 158 | will result in this constructor: 159 | 160 | ```csharp 161 | public Vehicle(int mileage) 162 | { 163 | this._mileage = mileage; 164 | } 165 | ``` 166 | 167 | While both `_mileage` and `_speed` are mutable fields, and therefore are exluded by default, `_mileage` does get initialized in the constructor because it is decorated with `[QuickConstructorParameter]`. 168 | 169 | ### Overriding the name of a parameter 170 | 171 | It is possible to override the name of a parameter in the constructor using the `Name` property of the `[QuickConstructorParameter]` attribute. 172 | 173 | This class: 174 | 175 | ```csharp 176 | [QuickConstructor] 177 | public partial class Vehicle 178 | { 179 | [QuickConstructorParameter(Name = "startingMileage")] 180 | private int _mileage; 181 | 182 | private int _speed; 183 | } 184 | ``` 185 | 186 | will result in this constructor: 187 | 188 | ```csharp 189 | public Vehicle(int startingMileage) 190 | { 191 | this._mileage = startingMileage; 192 | } 193 | ``` 194 | 195 | ### Derived classes 196 | 197 | It is possible to generate a constructor for a class inheriting from a base class, however the base class must either itself be decorated with `[QuickConstructor]`, or it must have a parameterless constructor. 198 | 199 | For example: 200 | 201 | ```csharp 202 | [QuickConstructor(Fields = IncludeFields.AllFields)] 203 | public partial class Vehicle 204 | { 205 | private int _mileage; 206 | private int _speed; 207 | } 208 | 209 | [QuickConstructor] 210 | public partial class Bus : Vehicle 211 | { 212 | private readonly int _capacity; 213 | } 214 | ``` 215 | 216 | In that situation, a constructor will be generated for the `Bus` class, with the following implementation: 217 | 218 | ```csharp 219 | public Bus(int mileage, int speed, int capacity) 220 | : base(mileage, speed) 221 | { 222 | this._capacity = capacity; 223 | } 224 | ``` 225 | 226 | ### Constructor accessibility 227 | 228 | It is possible to customize the accessibility level of the auto-generated constructor. This is controlled via the `ConstructorAccessibility` property of the `[QuickConstructor]` attribute. 229 | 230 | ## License 231 | 232 | Copyright 2022 Flavien Charlon 233 | 234 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 235 | 236 | http://www.apache.org/licenses/LICENSE-2.0 237 | 238 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 239 | See the License for the specific language governing permissions and limitations under the License. 240 | -------------------------------------------------------------------------------- /src/QuickConstructor.Attributes/Accessibility.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Attributes; 16 | 17 | /// 18 | /// Represents the accessibility level of a constructor. 19 | /// 20 | public enum Accessibility 21 | { 22 | /// 23 | /// The constructor is public. 24 | /// 25 | Public, 26 | /// 27 | /// The constructor is protected. 28 | /// 29 | Protected, 30 | /// 31 | /// The constructor is internal. 32 | /// 33 | Internal, 34 | /// 35 | /// The constructor is private. 36 | /// 37 | Private 38 | } 39 | -------------------------------------------------------------------------------- /src/QuickConstructor.Attributes/IncludeFields.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Attributes; 16 | 17 | /// 18 | /// Represents a strategy for selecting which fields are initialized in an automatically generated constructor. 19 | /// 20 | public enum IncludeFields 21 | { 22 | /// 23 | /// Only read-only fields are initialized in the constructor. 24 | /// 25 | ReadOnlyFields, 26 | /// 27 | /// All fields are initialized in the constructor. 28 | /// 29 | AllFields 30 | } 31 | -------------------------------------------------------------------------------- /src/QuickConstructor.Attributes/IncludeProperties.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Attributes; 16 | 17 | /// 18 | /// Represents a strategy for selecting which properties are initialized in an automatically generated constructor. 19 | /// 20 | public enum IncludeProperties 21 | { 22 | /// 23 | /// No property is initialized in the constructor. 24 | /// 25 | None, 26 | /// 27 | /// Only read-only auto-implemented properties are initialized in the constructor. 28 | /// 29 | ReadOnlyProperties, 30 | /// 31 | /// All settable properties are initialized in the constructor. 32 | /// 33 | AllProperties 34 | } 35 | -------------------------------------------------------------------------------- /src/QuickConstructor.Attributes/NullChecks.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Attributes; 16 | 17 | /// 18 | /// Represents a strategy for emitting null checks in an automatically generated constructor. 19 | /// 20 | public enum NullChecks 21 | { 22 | /// 23 | /// Null checks are generated for any field or property whose type is a reference type. 24 | /// 25 | Always, 26 | /// 27 | /// Null checks are not generated for this constructor. 28 | /// 29 | Never, 30 | /// 31 | /// When null-state analysis is enabled (C# 8.0 and later), a null check will be generated only if a type is 32 | /// marked as non-nullable. When null-state analysis is disabled, no null check is generated. 33 | /// 34 | NonNullableReferencesOnly 35 | } 36 | -------------------------------------------------------------------------------- /src/QuickConstructor.Attributes/QuickConstructor.Attributes.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 10 6 | enable 7 | true 8 | CS1591 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/QuickConstructor.Attributes/QuickConstructorAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Attributes; 16 | 17 | using System; 18 | using System.Diagnostics; 19 | 20 | /// 21 | /// Specifies that a constructor should be automatically generated. 22 | /// 23 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 24 | [Conditional("INCLUDE_AUTO_CONSTRUCTOR_ATTRIBUTES")] 25 | public sealed class QuickConstructorAttribute : Attribute 26 | { 27 | /// 28 | /// Gets or sets a value indicating which fields should be initialized in the constructor. 29 | /// 30 | public IncludeFields Fields { get; set; } = IncludeFields.ReadOnlyFields; 31 | 32 | /// 33 | /// Gets or sets a value indicating which properties should be initialized in the constructor. 34 | /// 35 | public IncludeProperties Properties { get; set; } = IncludeProperties.ReadOnlyProperties; 36 | 37 | /// 38 | /// Gets or sets a value indicating how null checks should be emitted for the constructor parameters. 39 | /// 40 | public NullChecks NullChecks { get; set; } = NullChecks.NonNullableReferencesOnly; 41 | 42 | /// 43 | /// Gets or sets a value indicating which accessibility the constructor should have. 44 | /// 45 | public Accessibility ConstructorAccessibility { get; set; } = Accessibility.Public; 46 | 47 | /// 48 | /// Gets or sets the summary text used when emitting XML documentation for the constructor. 49 | /// 50 | public string? Documentation { get; set; } = "Initializes a new instance of the {0} class."; 51 | } 52 | -------------------------------------------------------------------------------- /src/QuickConstructor.Attributes/QuickConstructorParameterAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Attributes; 16 | 17 | using System; 18 | using System.Diagnostics; 19 | 20 | /// 21 | /// Specifies that a field or property should be initialized through the generated constructor. 22 | /// 23 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] 24 | [Conditional("INCLUDE_AUTO_CONSTRUCTOR_ATTRIBUTES")] 25 | public class QuickConstructorParameterAttribute : Attribute 26 | { 27 | /// 28 | /// Gets or sets the name to give to the constructor parameter from which the field or property is initialized. 29 | /// If null, the name will be derived from the field or property name by turning it to camel case. 30 | /// 31 | public string? Name { get; set; } = null; 32 | 33 | /// 34 | /// Gets or sets a boolean value that indicates whether to copy attributes applied to the field or property to the 35 | /// constructor parameter. 36 | /// 37 | public bool IncludeAttributes { get; set; } = true; 38 | } 39 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/ClassMembersAnalyzer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Collections.Immutable; 20 | using System.Linq; 21 | using System.Text.RegularExpressions; 22 | using Microsoft.CodeAnalysis; 23 | using Microsoft.CodeAnalysis.CSharp; 24 | using Microsoft.CodeAnalysis.CSharp.Syntax; 25 | using QuickConstructor.Attributes; 26 | 27 | public class ClassMembersAnalyzer 28 | { 29 | private static readonly Regex _identifierTrim = new( 30 | @"^[^\p{L}]+", 31 | RegexOptions.Compiled | RegexOptions.CultureInvariant); 32 | 33 | private readonly INamedTypeSymbol _classSymbol; 34 | private readonly TypeDeclarationSyntax _declarationSyntax; 35 | private readonly QuickConstructorAttribute _attribute; 36 | 37 | public ClassMembersAnalyzer( 38 | INamedTypeSymbol classSymbol, 39 | TypeDeclarationSyntax declarationSyntax, 40 | QuickConstructorAttribute attribute) 41 | { 42 | _classSymbol = classSymbol; 43 | _declarationSyntax = declarationSyntax; 44 | _attribute = attribute; 45 | } 46 | 47 | public ImmutableArray GetConstructorParameters() 48 | { 49 | return ImmutableArray.CreateRange(GetFields().Concat(GetProperties())); 50 | } 51 | 52 | private IEnumerable GetFields() 53 | { 54 | foreach (IFieldSymbol field in _classSymbol.GetMembers().OfType()) 55 | { 56 | if (ExcludeMember(field)) 57 | continue; 58 | 59 | if (HasFieldInitializer(field)) 60 | continue; 61 | 62 | QuickConstructorParameterAttribute? attribute = field.GetAttribute(); 63 | 64 | bool include = attribute != null || _attribute.Fields switch 65 | { 66 | IncludeFields.AllFields => true, 67 | _ => field.IsReadOnly 68 | }; 69 | 70 | if (include) 71 | yield return CreateParameter(field, field.Type, attribute); 72 | } 73 | } 74 | 75 | private IEnumerable GetProperties() 76 | { 77 | foreach (IPropertySymbol property in _classSymbol.GetMembers().OfType()) 78 | { 79 | if (ExcludeMember(property)) 80 | continue; 81 | 82 | if (HasPropertyInitializer(property)) 83 | continue; 84 | 85 | if (property.IsReadOnly && !IsAutoProperty(property)) 86 | continue; 87 | 88 | QuickConstructorParameterAttribute? attribute = property.GetAttribute(); 89 | 90 | bool include = attribute != null || _attribute.Properties switch 91 | { 92 | IncludeProperties.None => false, 93 | IncludeProperties.AllProperties => true, 94 | _ => property.IsReadOnly 95 | }; 96 | 97 | if (include) 98 | yield return CreateParameter(property, property.Type, attribute); 99 | } 100 | } 101 | 102 | public static bool ExcludeMember(ISymbol member) 103 | { 104 | return !member.CanBeReferencedByName || member.IsStatic; 105 | } 106 | 107 | private static bool HasFieldInitializer(IFieldSymbol symbol) 108 | { 109 | SyntaxNode? syntaxNode = symbol.DeclaringSyntaxReferences.ElementAtOrDefault(0)?.GetSyntax(); 110 | VariableDeclaratorSyntax? field = syntaxNode as VariableDeclaratorSyntax; 111 | 112 | return field?.Initializer != null; 113 | } 114 | 115 | private static bool IsAutoProperty(IPropertySymbol propertySymbol) 116 | { 117 | return propertySymbol.ContainingType.GetMembers() 118 | .OfType() 119 | .Any(field => !field.CanBeReferencedByName 120 | && SymbolEqualityComparer.Default.Equals(field.AssociatedSymbol, propertySymbol)); 121 | } 122 | 123 | private static bool HasPropertyInitializer(IPropertySymbol symbol) 124 | { 125 | SyntaxNode? syntaxNode = symbol.DeclaringSyntaxReferences.ElementAtOrDefault(0)?.GetSyntax(); 126 | PropertyDeclarationSyntax? property = syntaxNode as PropertyDeclarationSyntax; 127 | 128 | return property?.Initializer != null; 129 | } 130 | 131 | private ConstructorParameter CreateParameter( 132 | ISymbol member, 133 | ITypeSymbol type, 134 | QuickConstructorParameterAttribute? parameterAttribute) 135 | { 136 | string parameterName; 137 | if (parameterAttribute?.Name == null) 138 | parameterName = GetParameterName(member.Name); 139 | else 140 | parameterName = parameterAttribute.Name.TrimStart('@'); 141 | 142 | if (!SyntaxFacts.IsValidIdentifier(parameterName)) 143 | { 144 | throw new DiagnosticException(Diagnostic.Create( 145 | DiagnosticDescriptors.InvalidParameterName, 146 | _declarationSyntax.Identifier.GetLocation(), 147 | parameterName, 148 | _classSymbol.Name)); 149 | } 150 | 151 | List attributeData = new(); 152 | if (parameterAttribute?.IncludeAttributes != false) 153 | { 154 | foreach (AttributeData attribute in member.GetAttributes()) 155 | { 156 | if (attribute.AttributeClass == null) 157 | continue; 158 | 159 | AttributeData? attributeUsage = attribute.AttributeClass 160 | .GetAttributes() 161 | .FirstOrDefault(x => x.AttributeClass?.Name == nameof(AttributeUsageAttribute)); 162 | 163 | if (attributeUsage == null) 164 | continue; 165 | 166 | TypedConstant validOn = attributeUsage.ConstructorArguments[0]; 167 | if (validOn.Value is not int targets) 168 | continue; 169 | 170 | if (((AttributeTargets)targets).HasFlag(AttributeTargets.Parameter)) 171 | { 172 | attributeData.Add(attribute); 173 | } 174 | } 175 | } 176 | 177 | bool nullCheck; 178 | if (_attribute.NullChecks == NullChecks.NonNullableReferencesOnly) 179 | nullCheck = !type.IsValueType && type.NullableAnnotation == NullableAnnotation.NotAnnotated; 180 | else if (_attribute.NullChecks == NullChecks.Always) 181 | nullCheck = !type.IsValueType; 182 | else 183 | nullCheck = false; 184 | 185 | return new ConstructorParameter( 186 | symbol: member, 187 | type: type, 188 | parameterName: parameterName, 189 | nullCheck: nullCheck, 190 | attributes: ImmutableArray.CreateRange(attributeData)); 191 | } 192 | 193 | private static string GetParameterName(string symbolName) 194 | { 195 | string trimmedParameterName = _identifierTrim.Replace(symbolName, ""); 196 | if (trimmedParameterName == string.Empty) 197 | return symbolName; 198 | else 199 | return char.ToLowerInvariant(trimmedParameterName[0]) + trimmedParameterName.Substring(1); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/ClassSymbolProcessor.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Collections.Immutable; 20 | using System.Linq; 21 | using Microsoft.CodeAnalysis; 22 | using Microsoft.CodeAnalysis.CSharp; 23 | using Microsoft.CodeAnalysis.CSharp.Syntax; 24 | using QuickConstructor.Attributes; 25 | 26 | public class ClassSymbolProcessor 27 | { 28 | private readonly INamedTypeSymbol _classSymbol; 29 | private readonly TypeDeclarationSyntax _declarationSyntax; 30 | private readonly QuickConstructorAttribute _attribute; 31 | 32 | public ClassSymbolProcessor( 33 | INamedTypeSymbol classSymbol, 34 | TypeDeclarationSyntax declarationSyntax, 35 | QuickConstructorAttribute attribute) 36 | { 37 | _classSymbol = classSymbol; 38 | _declarationSyntax = declarationSyntax; 39 | _attribute = attribute; 40 | } 41 | 42 | public INamedTypeSymbol ClassSymbol { get => _classSymbol; } 43 | 44 | public ConstructorDescriptor GetConstructorDescriptor() 45 | { 46 | ClassMembersAnalyzer classMembersAnalyzer = new(_classSymbol, _declarationSyntax, _attribute); 47 | ImmutableArray members = classMembersAnalyzer.GetConstructorParameters(); 48 | 49 | ImmutableArray baseClassMembers = ImmutableArray 50 | .CreateRange(GetRecursiveClassMembers(_classSymbol.BaseType, _declarationSyntax)); 51 | 52 | ILookup lookup = members 53 | .ToLookup(member => member.ParameterName, StringComparer.Ordinal); 54 | 55 | IList duplicates = lookup 56 | .Where(nameGroup => nameGroup.Count() > 1) 57 | .Select(nameGroup => nameGroup.Last()) 58 | .ToList(); 59 | 60 | if (duplicates.Count > 0) 61 | { 62 | throw new DiagnosticException(Diagnostic.Create( 63 | DiagnosticDescriptors.DuplicateConstructorParameter, 64 | _declarationSyntax.Identifier.GetLocation(), 65 | duplicates[0].ParameterName, 66 | _classSymbol.Name)); 67 | } 68 | 69 | return new ConstructorDescriptor( 70 | classSymbol: _classSymbol, 71 | accessibility: _attribute.ConstructorAccessibility, 72 | constructorParameters: members, 73 | baseClassConstructorParameters: baseClassMembers, 74 | documentation: _attribute.Documentation); 75 | } 76 | 77 | private static IEnumerable GetRecursiveClassMembers( 78 | INamedTypeSymbol? classSymbol, 79 | TypeDeclarationSyntax declarationSyntax) 80 | { 81 | if (classSymbol != null) 82 | { 83 | QuickConstructorAttribute? attribute = classSymbol.GetAttribute(); 84 | if (attribute != null) 85 | { 86 | ClassMembersAnalyzer analyzer = new(classSymbol, declarationSyntax, attribute); 87 | IReadOnlyList parameters = analyzer.GetConstructorParameters(); 88 | 89 | return GetRecursiveClassMembers(classSymbol.BaseType, declarationSyntax).Concat(parameters); 90 | } 91 | } 92 | 93 | return ImmutableArray.Empty; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/ConstructorDescriptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System.Collections.Immutable; 18 | using Microsoft.CodeAnalysis; 19 | using Accessibility = Attributes.Accessibility; 20 | 21 | public record ConstructorDescriptor 22 | { 23 | public ConstructorDescriptor( 24 | INamedTypeSymbol classSymbol, 25 | Accessibility accessibility, 26 | ImmutableArray constructorParameters, 27 | ImmutableArray baseClassConstructorParameters, 28 | string? documentation) 29 | { 30 | ClassSymbol = classSymbol; 31 | Accessibility = accessibility; 32 | ConstructorParameters = constructorParameters; 33 | BaseClassConstructorParameters = baseClassConstructorParameters; 34 | Documentation = documentation; 35 | } 36 | 37 | public INamedTypeSymbol ClassSymbol { get; } 38 | 39 | public Accessibility Accessibility { get; } 40 | 41 | public bool IsReadOnly { get; } 42 | 43 | public ImmutableArray ConstructorParameters { get; } 44 | 45 | public ImmutableArray BaseClassConstructorParameters { get; } 46 | 47 | public string? Documentation { get; } 48 | } 49 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/ConstructorParameter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System.Collections.Immutable; 18 | using Microsoft.CodeAnalysis; 19 | 20 | public record ConstructorParameter 21 | { 22 | public ConstructorParameter( 23 | ISymbol symbol, 24 | ITypeSymbol type, 25 | string parameterName, 26 | bool nullCheck, 27 | ImmutableArray attributes) 28 | { 29 | Symbol = symbol; 30 | Type = type; 31 | ParameterName = parameterName; 32 | NullCheck = nullCheck; 33 | Attributes = attributes; 34 | } 35 | 36 | public ISymbol Symbol { get; } 37 | 38 | public ITypeSymbol Type { get; } 39 | 40 | public string ParameterName { get; } 41 | 42 | public bool NullCheck { get; } 43 | 44 | public ImmutableArray Attributes { get; } 45 | } 46 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/DiagnosticDescriptors.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using Microsoft.CodeAnalysis; 18 | 19 | public class DiagnosticDescriptors 20 | { 21 | public static DiagnosticDescriptor DuplicateConstructorParameter { get; } = new( 22 | id: "AC0001", 23 | title: "Duplicate parameter name for auto-generated constructor", 24 | messageFormat: "The parameter '{0}' is duplicated in the auto-generated constructor for '{1}'.", 25 | category: "QuickConstructor", 26 | DiagnosticSeverity.Error, 27 | isEnabledByDefault: true); 28 | 29 | public static DiagnosticDescriptor InvalidParameterName { get; } = new( 30 | id: "AC0002", 31 | title: "Invalid parameter name", 32 | messageFormat: "The parameter name '{0}' in class '{1}' is not a valid identifier.", 33 | category: "QuickConstructor", 34 | DiagnosticSeverity.Error, 35 | isEnabledByDefault: true); 36 | } 37 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/DiagnosticException.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System; 18 | using Microsoft.CodeAnalysis; 19 | 20 | public class DiagnosticException : Exception 21 | { 22 | public DiagnosticException(Diagnostic diagnostic) 23 | : base(diagnostic.Descriptor.Title.ToString()) 24 | { 25 | Diagnostic = diagnostic; 26 | } 27 | 28 | public Diagnostic Diagnostic { get; } 29 | } 30 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/QuickConstructor.Generator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | QuickConstructor 5 | 1.0.5 6 | QuickConstructor 7 | Source generator that automatically emits a constructor from the fields and properties of a class. 8 | Flavien Charlon 9 | README.md 10 | https://github.com/flavien/QuickConstructor 11 | Apache-2.0 12 | Copyright © 2022 Flavien Charlon 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | netstandard2.0 21 | false 22 | 10 23 | enable 24 | true 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | $(GetTargetPathDependsOn);GetDependencyTargetPaths 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/QuickConstructorGenerator.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System.Collections.Generic; 18 | using System.Linq; 19 | using System.Text; 20 | using System.Text.RegularExpressions; 21 | using System.Threading; 22 | using QuickConstructor.Attributes; 23 | using Microsoft.CodeAnalysis; 24 | using Microsoft.CodeAnalysis.CSharp; 25 | using Microsoft.CodeAnalysis.CSharp.Syntax; 26 | using Microsoft.CodeAnalysis.Text; 27 | 28 | [Generator(LanguageNames.CSharp)] 29 | public class QuickConstructorGenerator : IIncrementalGenerator 30 | { 31 | private static readonly Regex _attributeSyntaxRegex = new("QuickConstructor(Attribute)?$", RegexOptions.Compiled); 32 | 33 | private readonly SourceRenderer _sourceRenderer = new(); 34 | 35 | public void Initialize(IncrementalGeneratorInitializationContext context) 36 | { 37 | IncrementalValuesProvider syntaxProvider = 38 | context.SyntaxProvider.CreateSyntaxProvider(IsCandidate, ProcessSyntaxNode) 39 | .WithComparer(ClassSymbolProcessorComparer.Default); 40 | 41 | IncrementalValuesProvider<(ConstructorDescriptor?, Diagnostic?)> model = 42 | syntaxProvider.Select(ProcessSymbol); 43 | 44 | context.RegisterSourceOutput(model, (context, result) => 45 | { 46 | (ConstructorDescriptor? constructorDescriptor, Diagnostic? diagnostic) = result; 47 | 48 | if (diagnostic != null) 49 | context.ReportDiagnostic(diagnostic); 50 | 51 | if (constructorDescriptor != null) 52 | { 53 | context.AddSource( 54 | constructorDescriptor.ClassSymbol.Name, 55 | SourceText.From(_sourceRenderer.Render(constructorDescriptor), Encoding.UTF8)); 56 | } 57 | }); 58 | } 59 | 60 | public bool IsCandidate(SyntaxNode syntaxNode, CancellationToken cancel) 61 | { 62 | if (syntaxNode is not AttributeSyntax attribute) 63 | return false; 64 | 65 | if (attribute?.Parent?.Parent is not TypeDeclarationSyntax typeDeclaration) 66 | return false; 67 | 68 | if (!IsValidDeclaration(typeDeclaration)) 69 | return false; 70 | 71 | if (!_attributeSyntaxRegex.IsMatch(attribute.Name.ToString())) 72 | return false; 73 | 74 | return true; 75 | } 76 | 77 | private ClassSymbolProcessor? ProcessSyntaxNode( 78 | GeneratorSyntaxContext syntaxContext, 79 | CancellationToken cancel) 80 | { 81 | if (syntaxContext.Node is not AttributeSyntax attributeSyntax) 82 | return null; 83 | 84 | if (attributeSyntax?.Parent?.Parent is not TypeDeclarationSyntax typeDeclaration) 85 | return null; 86 | 87 | if (!IsValidDeclaration(typeDeclaration)) 88 | return null; 89 | 90 | ISymbol? symbol = syntaxContext.SemanticModel.GetDeclaredSymbol(typeDeclaration, cancel); 91 | 92 | if (symbol is not INamedTypeSymbol classSymbol) 93 | return null; 94 | 95 | QuickConstructorAttribute? attribute = symbol.GetAttribute(); 96 | 97 | if (attribute == null) 98 | return null; 99 | 100 | return new ClassSymbolProcessor(classSymbol, typeDeclaration, attribute); 101 | } 102 | 103 | private (ConstructorDescriptor?, Diagnostic?) ProcessSymbol( 104 | ClassSymbolProcessor? processor, 105 | CancellationToken cancel) 106 | { 107 | if (processor == null) 108 | return default; 109 | 110 | try 111 | { 112 | return (processor.GetConstructorDescriptor(), null); 113 | } 114 | catch (DiagnosticException exception) 115 | { 116 | return (null, exception.Diagnostic); 117 | } 118 | } 119 | 120 | private static bool IsValidDeclaration(TypeDeclarationSyntax typeDeclaration) 121 | { 122 | return typeDeclaration is ClassDeclarationSyntax 123 | || typeDeclaration is RecordDeclarationSyntax 124 | || typeDeclaration is StructDeclarationSyntax; 125 | } 126 | 127 | private class ClassSymbolProcessorComparer : IEqualityComparer 128 | { 129 | public static ClassSymbolProcessorComparer Default { get; } = new ClassSymbolProcessorComparer(); 130 | 131 | public bool Equals(ClassSymbolProcessor? x, ClassSymbolProcessor? y) 132 | { 133 | return SymbolEqualityComparer.Default.Equals(x?.ClassSymbol, y?.ClassSymbol); 134 | } 135 | 136 | public int GetHashCode(ClassSymbolProcessor? obj) 137 | { 138 | return SymbolEqualityComparer.Default.GetHashCode(obj?.ClassSymbol); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/SourceRenderer.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Linq; 20 | using System.Text; 21 | using CSharpier; 22 | using Microsoft.CodeAnalysis; 23 | 24 | public class SourceRenderer 25 | { 26 | private static readonly SymbolDisplayFormat _parameterFormat = SymbolDisplayFormat.FullyQualifiedFormat 27 | .WithMiscellaneousOptions( 28 | SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions 29 | | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); 30 | 31 | public string Render(ConstructorDescriptor constructorDescriptor) 32 | { 33 | INamedTypeSymbol classSymbol = constructorDescriptor.ClassSymbol; 34 | IReadOnlyList parameters = constructorDescriptor.ConstructorParameters; 35 | IReadOnlyList baseClassParameters = constructorDescriptor.BaseClassConstructorParameters; 36 | 37 | IEnumerable allParameters = baseClassParameters.Concat(parameters); 38 | string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); 39 | string documentation = RenderDocumentation(constructorDescriptor); 40 | string accessibility = GetAccessModifier(constructorDescriptor.Accessibility); 41 | string readonlyKeyword = constructorDescriptor.IsReadOnly ? "readonly" : ""; 42 | string declarationKeywords = classSymbol.GetDeclarationKeywords(); 43 | string classDeclaration = classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); 44 | string parameterDeclarations = string.Join(",", allParameters.Select(parameter => RenderParameter(parameter))); 45 | string baseClassConstructor = CreateBaseClassConstructor(baseClassParameters); 46 | string nullChecks = string.Concat(parameters.Select(parameter => RenderNullCheck(parameter))); 47 | string assignments = string.Concat(parameters.Select(parameter => RenderAssignment(parameter))); 48 | 49 | string namespaceContents = $@" 50 | {readonlyKeyword} partial {declarationKeywords} {classDeclaration} 51 | {{ 52 | {documentation} 53 | {accessibility} {classSymbol.Name}({parameterDeclarations}) 54 | {baseClassConstructor} 55 | {{ 56 | {nullChecks} 57 | 58 | {assignments} 59 | }} 60 | }}"; 61 | 62 | INamedTypeSymbol currentSymbol = classSymbol; 63 | while (currentSymbol.ContainingType != null) 64 | { 65 | currentSymbol = currentSymbol.ContainingType; 66 | string symbolDeclaration = currentSymbol.GetDeclarationKeywords(); 67 | string symbolName = currentSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); 68 | namespaceContents = $@" 69 | partial {symbolDeclaration} {symbolName} 70 | {{ 71 | {namespaceContents} 72 | }}"; 73 | } 74 | 75 | string source = $@" 76 | /// 77 | /// This code was generated by the {nameof(QuickConstructor)} source generator. 78 | /// 79 | 80 | #nullable enable 81 | 82 | namespace {namespaceName} 83 | {{ 84 | {namespaceContents} 85 | }}"; 86 | 87 | return CodeFormatter.Format(source); 88 | } 89 | 90 | public string RenderParameter(ConstructorParameter parameter) 91 | { 92 | StringBuilder stringBuilder = new(); 93 | 94 | stringBuilder.AppendLine(); 95 | 96 | foreach (AttributeData attribute in parameter.Attributes) 97 | stringBuilder.Append($"[global::{attribute}] "); 98 | 99 | stringBuilder.Append(parameter.Type.ToDisplayString(_parameterFormat)); 100 | stringBuilder.Append(" @"); 101 | stringBuilder.Append(parameter.ParameterName); 102 | 103 | return stringBuilder.ToString(); 104 | } 105 | 106 | private string RenderNullCheck(ConstructorParameter parameter) 107 | { 108 | if (!parameter.NullCheck) 109 | return string.Empty; 110 | 111 | return @$" 112 | if (@{ parameter.ParameterName } == null) 113 | throw new global::System.ArgumentNullException(nameof(@{ parameter.ParameterName })); 114 | "; 115 | } 116 | 117 | private static string RenderAssignment(ConstructorParameter parameter) 118 | { 119 | return $@" 120 | this.@{parameter.Symbol.Name} = @{parameter.ParameterName};"; 121 | } 122 | 123 | private static string CreateBaseClassConstructor(IReadOnlyList baseClassParameters) 124 | { 125 | if (baseClassParameters.Count == 0) 126 | return string.Empty; 127 | 128 | IEnumerable argumentList = baseClassParameters.Select(argument => "@" + argument.ParameterName); 129 | 130 | return $": base({string.Join(", ", argumentList)})"; 131 | } 132 | 133 | private static string RenderDocumentation(ConstructorDescriptor constructor) 134 | { 135 | if (constructor.Documentation == null) 136 | { 137 | return string.Empty; 138 | } 139 | else 140 | { 141 | string symbolName = constructor.ClassSymbol 142 | .ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat) 143 | .Replace('<', '{') 144 | .Replace('>', '}'); 145 | string seeTag = $""; 146 | 147 | return $@" 148 | /// 149 | /// { string.Format(constructor.Documentation, seeTag) } 150 | /// "; 151 | } 152 | } 153 | 154 | private static string GetAccessModifier(Attributes.Accessibility accessibility) 155 | { 156 | return accessibility switch 157 | { 158 | Attributes.Accessibility.Private => "private", 159 | Attributes.Accessibility.Internal => "internal", 160 | Attributes.Accessibility.Protected => "protected", 161 | _ => "public" 162 | }; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/QuickConstructor.Generator/SymbolExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Generator; 16 | 17 | using System; 18 | using System.Collections.Generic; 19 | using System.Linq; 20 | using System.Text; 21 | using Microsoft.CodeAnalysis; 22 | using Microsoft.CodeAnalysis.CSharp; 23 | 24 | internal static class QuickConstructorExtensions 25 | { 26 | public static T? GetAttribute(this ISymbol symbol) 27 | where T : Attribute 28 | { 29 | string name = typeof(T).Name; 30 | AttributeData? attribute = symbol.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == name); 31 | 32 | if (attribute == null) 33 | return null; 34 | 35 | return CreateAttribute(attribute); 36 | } 37 | 38 | public static T CreateAttribute(this AttributeData attributeData) 39 | { 40 | T attribute = Activator.CreateInstance(); 41 | foreach (KeyValuePair pair in attributeData.NamedArguments) 42 | { 43 | typeof(T).GetProperty(pair.Key).SetValue(attribute, pair.Value.Value); 44 | } 45 | 46 | return attribute; 47 | } 48 | 49 | public static string GetDeclarationKeywords(this INamedTypeSymbol symbol) 50 | { 51 | StringBuilder stringBuilder = new(); 52 | 53 | if (symbol.IsRecord) 54 | { 55 | stringBuilder.Append(SyntaxFactory.Token(SyntaxKind.RecordKeyword).ToFullString()); 56 | stringBuilder.Append(' '); 57 | } 58 | 59 | if (symbol.TypeKind == TypeKind.Struct) 60 | stringBuilder.Append(SyntaxFactory.Token(SyntaxKind.StructKeyword).ToFullString()); 61 | else 62 | stringBuilder.Append(SyntaxFactory.Token(SyntaxKind.ClassKeyword).ToFullString()); 63 | 64 | return stringBuilder.ToString(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/QuickConstructor.Tests/BaseClassTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Tests; 16 | 17 | using System.Text; 18 | using System.Threading.Tasks; 19 | using CSharpier; 20 | using Microsoft.CodeAnalysis.Testing.Verifiers; 21 | using Microsoft.CodeAnalysis.Text; 22 | using QuickConstructor.Attributes; 23 | using QuickConstructor.Generator; 24 | using Xunit; 25 | 26 | public class BaseClassTests 27 | { 28 | [Fact] 29 | public async Task BaseClass_ParentWithAttribute() 30 | { 31 | string sourceCode = @" 32 | [QuickConstructor(Documentation = null)] 33 | partial class TestClass : Parent 34 | { 35 | private readonly int fieldOne; 36 | } 37 | 38 | [QuickConstructor(Documentation = null)] 39 | partial class Parent 40 | { 41 | private readonly int parentClassField; 42 | }"; 43 | 44 | string generatedCode = @" 45 | partial class TestClass 46 | { 47 | public TestClass( 48 | int @parentClassField, 49 | int @fieldOne) 50 | : base(@parentClassField) 51 | { 52 | this.@fieldOne = @fieldOne; 53 | } 54 | }"; 55 | 56 | string parentClassGeneratedCode = @" 57 | partial class Parent 58 | { 59 | public Parent( 60 | int @parentClassField) 61 | { 62 | this.@parentClassField = @parentClassField; 63 | } 64 | }"; 65 | 66 | await AssertGeneratedCode(sourceCode, ("TestClass", generatedCode), ("Parent", parentClassGeneratedCode)); 67 | } 68 | 69 | [Fact] 70 | public async Task BaseClass_ParentWithDefaultConstructor() 71 | { 72 | string sourceCode = @" 73 | [QuickConstructor(Documentation = null)] 74 | partial class TestClass : System.Exception 75 | { 76 | private readonly int fieldOne; 77 | }"; 78 | 79 | string generatedCode = @" 80 | partial class TestClass 81 | { 82 | public TestClass( 83 | int @fieldOne) 84 | { 85 | this.@fieldOne = @fieldOne; 86 | } 87 | }"; 88 | 89 | await AssertGeneratedCode(sourceCode, ("TestClass", generatedCode)); 90 | } 91 | 92 | [Fact] 93 | public async Task BaseClass_Grandparent() 94 | { 95 | string sourceCode = @" 96 | [QuickConstructor(Documentation = null)] 97 | partial class Grandparent 98 | { 99 | private readonly int grandparentClassField; 100 | } 101 | 102 | [QuickConstructor(Documentation = null)] 103 | partial class TestClass : Parent 104 | { 105 | private readonly int fieldOne; 106 | } 107 | 108 | [QuickConstructor(Documentation = null)] 109 | partial class Parent : Grandparent 110 | { 111 | private readonly int parentClassField; 112 | }"; 113 | 114 | string generatedCode = @" 115 | partial class TestClass 116 | { 117 | public TestClass( 118 | int @grandparentClassField, 119 | int @parentClassField, 120 | int @fieldOne) 121 | : base(@grandparentClassField, @parentClassField) 122 | { 123 | this.@fieldOne = @fieldOne; 124 | } 125 | }"; 126 | 127 | string parentClassGeneratedCode = @" 128 | partial class Parent 129 | { 130 | public Parent( 131 | int @grandparentClassField, 132 | int @parentClassField) 133 | : base(@grandparentClassField) 134 | { 135 | this.@parentClassField = @parentClassField; 136 | } 137 | }"; 138 | 139 | string grandparentClassGeneratedCode = @" 140 | partial class Grandparent 141 | { 142 | public Grandparent( 143 | int @grandparentClassField) 144 | { 145 | this.@grandparentClassField = @grandparentClassField; 146 | } 147 | }"; 148 | 149 | await AssertGeneratedCode( 150 | sourceCode, 151 | ("Grandparent", grandparentClassGeneratedCode), 152 | ("TestClass", generatedCode), 153 | ("Parent", parentClassGeneratedCode)); 154 | } 155 | 156 | private static async Task AssertGeneratedCode(string sourceCode, params (string name, string code)[] expected) 157 | { 158 | CSharpIncrementalGeneratorTest tester = new() 159 | { 160 | TestState = 161 | { 162 | Sources = 163 | { 164 | $@" 165 | #nullable enable 166 | using QuickConstructor.Attributes; 167 | namespace TestNamespace 168 | {{ 169 | {sourceCode} 170 | }}" 171 | } 172 | }, 173 | }; 174 | 175 | tester.TestState.AdditionalReferences.Add(typeof(QuickConstructorGenerator).Assembly); 176 | tester.TestState.AdditionalReferences.Add(typeof(QuickConstructorAttribute).Assembly); 177 | 178 | foreach ((string name, string code) in expected) 179 | { 180 | tester.TestState.GeneratedSources.Add(( 181 | typeof(QuickConstructorGenerator), 182 | $"{name}.cs", 183 | SourceText.From(CreateExpectedFile(code), Encoding.UTF8))); 184 | } 185 | 186 | await tester.RunAsync(); 187 | } 188 | 189 | private static string CreateExpectedFile(string generatedCode) 190 | { 191 | string fullGeneratedCode = $@" 192 | /// 193 | /// This code was generated by the {nameof(QuickConstructor)} source generator. 194 | /// 195 | 196 | #nullable enable 197 | 198 | namespace TestNamespace 199 | {{ 200 | {generatedCode} 201 | }}"; 202 | 203 | return CodeFormatter.Format(fullGeneratedCode); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /test/QuickConstructor.Tests/CSharpIncrementalGeneratorTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Tests; 16 | 17 | using System.Collections.Generic; 18 | using System.Collections.Immutable; 19 | using Microsoft.CodeAnalysis; 20 | using Microsoft.CodeAnalysis.CSharp; 21 | using Microsoft.CodeAnalysis.Testing; 22 | 23 | public class CSharpIncrementalGeneratorTest : SourceGeneratorTest 24 | where TSourceGenerator : IIncrementalGenerator, new() 25 | where TVerifier : IVerifier, new() 26 | { 27 | protected override IEnumerable GetSourceGenerators() 28 | => new[] { new TSourceGenerator().AsSourceGenerator() }; 29 | 30 | protected override string DefaultFileExt => "cs"; 31 | 32 | public override string Language => LanguageNames.CSharp; 33 | 34 | protected override GeneratorDriver CreateGeneratorDriver(Project project, ImmutableArray sourceGenerators) 35 | { 36 | return CSharpGeneratorDriver.Create( 37 | sourceGenerators, 38 | project.AnalyzerOptions.AdditionalFiles, 39 | (CSharpParseOptions)project.ParseOptions!, 40 | project.AnalyzerOptions.AnalyzerConfigOptionsProvider); 41 | } 42 | 43 | protected override CompilationOptions CreateCompilationOptions() 44 | => new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true); 45 | 46 | protected override ParseOptions CreateParseOptions() 47 | => new CSharpParseOptions(LanguageVersion.Default, DocumentationMode.Diagnose); 48 | } 49 | -------------------------------------------------------------------------------- /test/QuickConstructor.Tests/DiagnosticsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Tests; 16 | 17 | using System.Threading.Tasks; 18 | using Microsoft.CodeAnalysis.Testing; 19 | using Microsoft.CodeAnalysis.Testing.Verifiers; 20 | using QuickConstructor.Attributes; 21 | using QuickConstructor.Generator; 22 | using Xunit; 23 | 24 | public class DiagnosticsTests 25 | { 26 | [Fact] 27 | public async Task Diagnostics_DuplicateConstructorParameter() 28 | { 29 | string sourceCode = @" 30 | [QuickConstructor] 31 | partial class TestClass 32 | { 33 | private readonly int value; 34 | public int Value { get; } 35 | }"; 36 | 37 | await AssertDiagnostic( 38 | sourceCode, 39 | new DiagnosticResult(DiagnosticDescriptors.DuplicateConstructorParameter) 40 | .WithSpan(8, 27, 8, 36) 41 | .WithArguments("value", "TestClass")); 42 | } 43 | 44 | [Theory] 45 | [InlineData("1value")] 46 | [InlineData("a$")] 47 | [InlineData("!a")] 48 | [InlineData("")] 49 | [InlineData("with space")] 50 | public async Task Diagnostics_InvalidParameterName(string name) 51 | { 52 | string sourceCode = $@" 53 | [QuickConstructor] 54 | partial class TestClass 55 | {{ 56 | [QuickConstructorParameter(Name = ""{name}"")] 57 | private readonly int value; 58 | }}"; 59 | 60 | await AssertDiagnostic( 61 | sourceCode, 62 | new DiagnosticResult(DiagnosticDescriptors.InvalidParameterName) 63 | .WithSpan(8, 27, 8, 36) 64 | .WithArguments(name, "TestClass")); 65 | } 66 | 67 | private static async Task AssertDiagnostic(string sourceCode, DiagnosticResult diagnostic) 68 | { 69 | CSharpIncrementalGeneratorTest tester = new() 70 | { 71 | TestState = 72 | { 73 | Sources = 74 | { 75 | $@" 76 | #nullable enable 77 | using QuickConstructor.Attributes; 78 | namespace TestNamespace 79 | {{ 80 | {sourceCode} 81 | }}" 82 | }, 83 | GeneratedSources = { }, 84 | ExpectedDiagnostics = 85 | { 86 | diagnostic 87 | } 88 | }, 89 | }; 90 | 91 | tester.TestState.AdditionalReferences.Add(typeof(QuickConstructorGenerator).Assembly); 92 | tester.TestState.AdditionalReferences.Add(typeof(QuickConstructorAttribute).Assembly); 93 | 94 | await tester.RunAsync(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/QuickConstructor.Tests/GeneratedCodeTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Flavien Charlon 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace QuickConstructor.Tests; 16 | 17 | using System.Text; 18 | using System.Threading.Tasks; 19 | using CSharpier; 20 | using Microsoft.CodeAnalysis.Testing.Verifiers; 21 | using Microsoft.CodeAnalysis.Text; 22 | using QuickConstructor.Attributes; 23 | using QuickConstructor.Generator; 24 | using Xunit; 25 | using Accessibility = QuickConstructor.Attributes.Accessibility; 26 | 27 | public class GeneratedCodeTests 28 | { 29 | [Fact] 30 | public async Task IncludeFields_ReadOnlyFields() 31 | { 32 | string sourceCode = @" 33 | [QuickConstructor(Fields = IncludeFields.ReadOnlyFields, Documentation = null)] 34 | partial class TestClass 35 | { 36 | private readonly int fieldOne; 37 | private static readonly int fieldTwo; 38 | private int fieldThree; 39 | private readonly int fieldFour = 10; 40 | }"; 41 | 42 | string generatedCode = @" 43 | partial class TestClass 44 | { 45 | public TestClass( 46 | int @fieldOne) 47 | { 48 | this.@fieldOne = @fieldOne; 49 | } 50 | }"; 51 | 52 | await AssertGeneratedCode(sourceCode, generatedCode); 53 | } 54 | 55 | [Fact] 56 | public async Task IncludeFields_AllFields() 57 | { 58 | string sourceCode = @" 59 | [QuickConstructor(Fields = IncludeFields.AllFields, Documentation = null)] 60 | partial class TestClass 61 | { 62 | private readonly int fieldOne; 63 | private static readonly int fieldTwo; 64 | private int fieldThree; 65 | private readonly int fieldFour = 10; 66 | }"; 67 | 68 | string generatedCode = @" 69 | partial class TestClass 70 | { 71 | public TestClass( 72 | int @fieldOne, 73 | int @fieldThree) 74 | { 75 | this.@fieldOne = @fieldOne; 76 | this.@fieldThree = @fieldThree; 77 | } 78 | }"; 79 | 80 | await AssertGeneratedCode(sourceCode, generatedCode); 81 | } 82 | 83 | [Fact] 84 | public async Task IncludeFields_QuickConstructorParameterAttribute() 85 | { 86 | string sourceCode = @" 87 | [QuickConstructor(Fields = IncludeFields.ReadOnlyFields, Documentation = null)] 88 | partial class TestClass 89 | { 90 | [QuickConstructorParameter] 91 | private readonly int fieldOne; 92 | [QuickConstructorParameter] 93 | private static readonly int fieldTwo; 94 | [QuickConstructorParameter] 95 | private int fieldThree; 96 | [QuickConstructorParameter] 97 | private readonly int fieldFour = 10; 98 | }"; 99 | 100 | string generatedCode = @" 101 | partial class TestClass 102 | { 103 | public TestClass( 104 | int @fieldOne, 105 | int @fieldThree) 106 | { 107 | this.@fieldOne = @fieldOne; 108 | this.@fieldThree = @fieldThree; 109 | } 110 | }"; 111 | 112 | await AssertGeneratedCode(sourceCode, generatedCode); 113 | } 114 | 115 | [Fact] 116 | public async Task IncludeProperties_None() 117 | { 118 | string sourceCode = @" 119 | [QuickConstructor(Properties = IncludeProperties.None, Documentation = null)] 120 | partial class TestClass 121 | { 122 | public int PropertyOne { get; } 123 | public static int PropertyTwo { get; } 124 | public int PropertyThree { get; set; } 125 | public int PropertyFour { get; private set; } 126 | public int PropertyFive { get => 10; } 127 | public int PropertySix { get; } = 10; 128 | public int PropertySeven 129 | { 130 | get => 0; 131 | set { } 132 | } 133 | }"; 134 | 135 | string generatedCode = @" 136 | partial class TestClass 137 | { 138 | public TestClass() 139 | { 140 | } 141 | }"; 142 | 143 | await AssertGeneratedCode(sourceCode, generatedCode); 144 | } 145 | 146 | [Fact] 147 | public async Task IncludeProperties_ReadOnlyProperties() 148 | { 149 | string sourceCode = @" 150 | [QuickConstructor(Properties = IncludeProperties.ReadOnlyProperties, Documentation = null)] 151 | partial class TestClass 152 | { 153 | public int PropertyOne { get; } 154 | public static int PropertyTwo { get; } 155 | public int PropertyThree { get; set; } 156 | public int PropertyFour { get; private set; } 157 | public int PropertyFive { get => 10; } 158 | public int PropertySix { get; } = 10; 159 | public int PropertySeven 160 | { 161 | get => 0; 162 | set { } 163 | } 164 | }"; 165 | 166 | string generatedCode = @" 167 | partial class TestClass 168 | { 169 | public TestClass( 170 | int @propertyOne) 171 | { 172 | this.@PropertyOne = @propertyOne; 173 | } 174 | }"; 175 | 176 | await AssertGeneratedCode(sourceCode, generatedCode); 177 | } 178 | 179 | [Fact] 180 | public async Task IncludeProperties_AllProperties() 181 | { 182 | string sourceCode = @" 183 | [QuickConstructor(Properties = IncludeProperties.AllProperties, Documentation = null)] 184 | partial class TestClass 185 | { 186 | public int PropertyOne { get; } 187 | public static int PropertyTwo { get; } 188 | public int PropertyThree { get; set; } 189 | public int PropertyFour { get; private set; } 190 | public int PropertyFive { get => 10; } 191 | public int PropertySix { get; } = 10; 192 | public int PropertySeven 193 | { 194 | get => 0; 195 | set { } 196 | } 197 | }"; 198 | 199 | string generatedCode = @" 200 | partial class TestClass 201 | { 202 | public TestClass( 203 | int @propertyOne, 204 | int @propertyThree, 205 | int @propertyFour, 206 | int @propertySeven) 207 | { 208 | this.@PropertyOne = @propertyOne; 209 | this.@PropertyThree = @propertyThree; 210 | this.@PropertyFour = @propertyFour; 211 | this.@PropertySeven = @propertySeven; 212 | } 213 | }"; 214 | 215 | await AssertGeneratedCode(sourceCode, generatedCode); 216 | } 217 | 218 | [Fact] 219 | public async Task IncludeProperties_QuickConstructorParameterAttribute() 220 | { 221 | string sourceCode = @" 222 | [QuickConstructor(Properties = IncludeProperties.None, Documentation = null)] 223 | partial class TestClass 224 | { 225 | [QuickConstructorParameter] 226 | public int PropertyOne { get; } 227 | [QuickConstructorParameter] 228 | public static int PropertyTwo { get; } 229 | [QuickConstructorParameter] 230 | public int PropertyThree { get; set; } 231 | [QuickConstructorParameter] 232 | public int PropertyFour { get; private set; } 233 | [QuickConstructorParameter] 234 | public int PropertyFive { get => 10; } 235 | [QuickConstructorParameter] 236 | public int PropertySix { get; } = 10; 237 | [QuickConstructorParameter] 238 | public int PropertySeven 239 | { 240 | get => 0; 241 | set { } 242 | } 243 | }"; 244 | 245 | string generatedCode = @" 246 | partial class TestClass 247 | { 248 | public TestClass( 249 | int @propertyOne, 250 | int @propertyThree, 251 | int @propertyFour, 252 | int @propertySeven) 253 | { 254 | this.@PropertyOne = @propertyOne; 255 | this.@PropertyThree = @propertyThree; 256 | this.@PropertyFour = @propertyFour; 257 | this.@PropertySeven = @propertySeven; 258 | } 259 | }"; 260 | 261 | await AssertGeneratedCode(sourceCode, generatedCode); 262 | } 263 | 264 | [Fact] 265 | public async Task ParameterName_OverrideName() 266 | { 267 | string sourceCode = @" 268 | [QuickConstructor(Documentation = null)] 269 | partial class TestClass 270 | { 271 | [QuickConstructorParameter(Name = ""modifiedField"")] 272 | private readonly int fieldOne; 273 | 274 | [QuickConstructorParameter(Name = ""@modifiedProperty"")] 275 | public int PropertyOne { get; } 276 | }"; 277 | 278 | string generatedCode = @" 279 | partial class TestClass 280 | { 281 | public TestClass( 282 | int @modifiedField, 283 | int @modifiedProperty) 284 | { 285 | this.@fieldOne = @modifiedField; 286 | this.@PropertyOne = @modifiedProperty; 287 | } 288 | }"; 289 | 290 | await AssertGeneratedCode(sourceCode, generatedCode); 291 | } 292 | 293 | [Fact] 294 | public async Task ParameterName_ToCamelCase() 295 | { 296 | string sourceCode = @" 297 | [QuickConstructor(Documentation = null)] 298 | partial class TestClass 299 | { 300 | private readonly string _underscoreField; 301 | private readonly string number1; 302 | private readonly string É; 303 | private readonly string 你好; 304 | }"; 305 | 306 | string generatedCode = @" 307 | partial class TestClass 308 | { 309 | public TestClass( 310 | string @underscoreField, 311 | string @number1, 312 | string @é, 313 | string @你好) 314 | { 315 | if (@underscoreField == null) 316 | throw new global::System.ArgumentNullException(nameof(@underscoreField)); 317 | 318 | if (@number1 == null) 319 | throw new global::System.ArgumentNullException(nameof(@number1)); 320 | 321 | if (@é == null) 322 | throw new global::System.ArgumentNullException(nameof(@é)); 323 | 324 | if (@你好 == null) 325 | throw new global::System.ArgumentNullException(nameof(@你好)); 326 | 327 | this.@_underscoreField = @underscoreField; 328 | this.@number1 = @number1; 329 | this.@É = @é; 330 | this.@你好 = @你好; 331 | } 332 | }"; 333 | 334 | await AssertGeneratedCode(sourceCode, generatedCode); 335 | } 336 | 337 | [Fact] 338 | public async Task ParameterName_ReservedKeyword() 339 | { 340 | string sourceCode = @" 341 | [QuickConstructor(Documentation = null)] 342 | partial class TestClass 343 | { 344 | private readonly string @class; 345 | public string Return { get; } 346 | }"; 347 | 348 | string generatedCode = @" 349 | partial class TestClass 350 | { 351 | public TestClass( 352 | string @class, 353 | string @return) 354 | { 355 | if (@class == null) 356 | throw new global::System.ArgumentNullException(nameof(@class)); 357 | 358 | if (@return == null) 359 | throw new global::System.ArgumentNullException(nameof(@return)); 360 | 361 | this.@class = @class; 362 | this.@Return = @return; 363 | } 364 | }"; 365 | 366 | await AssertGeneratedCode(sourceCode, generatedCode); 367 | } 368 | 369 | [Fact] 370 | public async Task ParameterName_Unchanged() 371 | { 372 | string sourceCode = @" 373 | [QuickConstructor(Documentation = null)] 374 | partial class TestClass 375 | { 376 | private readonly string @_; 377 | private readonly string _1; 378 | }"; 379 | 380 | string generatedCode = @" 381 | partial class TestClass 382 | { 383 | public TestClass( 384 | string @_, 385 | string @_1) 386 | { 387 | if (@_ == null) 388 | throw new global::System.ArgumentNullException(nameof(@_)); 389 | 390 | if (@_1 == null) 391 | throw new global::System.ArgumentNullException(nameof(@_1)); 392 | 393 | this.@_ = @_; 394 | this.@_1 = @_1; 395 | } 396 | }"; 397 | 398 | await AssertGeneratedCode(sourceCode, generatedCode); 399 | } 400 | 401 | [Fact] 402 | public async Task Rendering_EmptyClass() 403 | { 404 | string sourceCode = @" 405 | [QuickConstructor(Documentation = null)] 406 | partial class TestClass 407 | { 408 | }"; 409 | 410 | string generatedCode = @" 411 | partial class TestClass 412 | { 413 | public TestClass() 414 | { 415 | } 416 | }"; 417 | 418 | await AssertGeneratedCode(sourceCode, generatedCode); 419 | } 420 | 421 | [Fact] 422 | public async Task Rendering_ArgumentTypes() 423 | { 424 | string sourceCode = @" 425 | using System.Collections.Generic; 426 | using L = System.Collections.Generic.LinkedList; 427 | 428 | [QuickConstructor(NullChecks = NullChecks.Never, Documentation = null)] 429 | partial class TestClass where T : class 430 | { 431 | private readonly T fieldOne; 432 | private readonly System.IO.Stream fieldTwo; 433 | private readonly System.Collections.Generic.List fieldThree; 434 | private readonly List fieldFour; 435 | private readonly L fieldFive; 436 | }"; 437 | 438 | string generatedCode = @" 439 | partial class TestClass 440 | { 441 | public TestClass( 442 | T @fieldOne, 443 | global::System.IO.Stream @fieldTwo, 444 | global::System.Collections.Generic.List @fieldThree, 445 | global::System.Collections.Generic.List @fieldFour, 446 | global::System.Collections.Generic.LinkedList @fieldFive) 447 | { 448 | this.@fieldOne = @fieldOne; 449 | this.@fieldTwo = @fieldTwo; 450 | this.@fieldThree = @fieldThree; 451 | this.@fieldFour = @fieldFour; 452 | this.@fieldFive = @fieldFive; 453 | } 454 | }"; 455 | 456 | await AssertGeneratedCode(sourceCode, generatedCode); 457 | } 458 | 459 | [Fact] 460 | public async Task Rendering_Attributes() 461 | { 462 | string sourceCode = @" 463 | using System.ComponentModel.DataAnnotations; 464 | using System.Collections.Generic; 465 | 466 | [QuickConstructor(Documentation = null)] 467 | partial class TestClass 468 | { 469 | [DataType(""Applicable"", ErrorMessageResourceType = typeof(List))] 470 | [DisplayFormat(DataFormatString = ""Not applicable"")] 471 | private readonly int fieldOne; 472 | 473 | [QuickConstructorParameter] 474 | [DataType(""Applicable"", ErrorMessageResourceType = typeof(List))] 475 | [DisplayFormat(DataFormatString = ""Not applicable"")] 476 | private readonly int fieldTwo; 477 | 478 | [QuickConstructorParameter(IncludeAttributes = false)] 479 | [DataType(""Applicable"", ErrorMessageResourceType = typeof(List))] 480 | [DisplayFormat(DataFormatString = ""Not applicable"")] 481 | private readonly int fieldThree; 482 | }"; 483 | 484 | string generatedCode = @" 485 | partial class TestClass 486 | { 487 | public TestClass( 488 | [global::System.ComponentModel.DataAnnotations.DataTypeAttribute(""Applicable"", ErrorMessageResourceType = typeof(System.Collections.Generic.List))] int @fieldOne, 489 | [global::System.ComponentModel.DataAnnotations.DataTypeAttribute(""Applicable"", ErrorMessageResourceType = typeof(System.Collections.Generic.List))] int @fieldTwo, 490 | int @fieldThree) 491 | { 492 | this.@fieldOne = @fieldOne; 493 | this.@fieldTwo = @fieldTwo; 494 | this.@fieldThree = @fieldThree; 495 | } 496 | }"; 497 | 498 | await AssertGeneratedCode(sourceCode, generatedCode); 499 | } 500 | 501 | [Theory] 502 | [InlineData("class", "class")] 503 | [InlineData("struct", "struct")] 504 | [InlineData("record", "record class")] 505 | [InlineData("record class", "record class")] 506 | [InlineData("record struct", "record struct")] 507 | public async Task Rendering_DeclarationTypes(string declarationKeywords, string expected) 508 | { 509 | string sourceCode = $@" 510 | [QuickConstructor(Documentation = null)] 511 | partial {declarationKeywords} TestClass 512 | {{ 513 | private readonly int fieldOne; 514 | }}"; 515 | 516 | string generatedCode = $@" 517 | partial {expected} TestClass 518 | {{ 519 | public TestClass( 520 | int @fieldOne) 521 | {{ 522 | this.@fieldOne = @fieldOne; 523 | }} 524 | }}"; 525 | 526 | await AssertGeneratedCode(sourceCode, generatedCode); 527 | } 528 | 529 | [Theory] 530 | [InlineData("class", "class")] 531 | [InlineData("struct", "struct")] 532 | [InlineData("record", "record class")] 533 | [InlineData("record class", "record class")] 534 | [InlineData("record struct", "record struct")] 535 | public async Task Rendering_NestedClass(string declarationKeywords, string expected) 536 | { 537 | string sourceCode = $@" 538 | partial {declarationKeywords} Parent 539 | {{ 540 | [QuickConstructor] 541 | partial class TestClass 542 | {{ 543 | private readonly int fieldOne; 544 | }} 545 | }}"; 546 | 547 | string generatedCode = $@" 548 | partial {expected} Parent 549 | {{ 550 | partial class TestClass 551 | {{ 552 | /// 553 | /// Initializes a new instance of the class. 554 | /// 555 | public TestClass( 556 | int @fieldOne) 557 | {{ 558 | this.@fieldOne = @fieldOne; 559 | }} 560 | }} 561 | }}"; 562 | 563 | await AssertGeneratedCode(sourceCode, generatedCode); 564 | } 565 | 566 | [Fact] 567 | public async Task Rendering_Documentation() 568 | { 569 | string sourceCode = @" 570 | [QuickConstructor(Documentation = ""This is a constructor for {0}."")] 571 | partial class TestClass where T : struct 572 | { 573 | private readonly T fieldOne; 574 | }"; 575 | 576 | string generatedCode = @" 577 | partial class TestClass 578 | { 579 | /// 580 | /// This is a constructor for . 581 | /// 582 | public TestClass( 583 | T @fieldOne) 584 | { 585 | this.@fieldOne = @fieldOne; 586 | } 587 | }"; 588 | 589 | await AssertGeneratedCode(sourceCode, generatedCode); 590 | } 591 | 592 | [Theory] 593 | [InlineData(Accessibility.Public, "public")] 594 | [InlineData(Accessibility.Protected, "protected")] 595 | [InlineData(Accessibility.Private, "private")] 596 | [InlineData(Accessibility.Internal, "internal")] 597 | public async Task Rendering_Accessibility(Accessibility accessibility, string keyword) 598 | { 599 | string sourceCode = $@" 600 | [QuickConstructor(ConstructorAccessibility = Accessibility.{accessibility}, Documentation = null)] 601 | partial class TestClass 602 | {{ 603 | private readonly int fieldOne; 604 | }}"; 605 | 606 | string generatedCode = $@" 607 | partial class TestClass 608 | {{ 609 | {keyword} TestClass( 610 | int @fieldOne) 611 | {{ 612 | this.@fieldOne = @fieldOne; 613 | }} 614 | }}"; 615 | 616 | await AssertGeneratedCode(sourceCode, generatedCode); 617 | } 618 | 619 | [Fact] 620 | public async Task SyntaxTree_Partial() 621 | { 622 | string sourceCode = @" 623 | [QuickConstructor(Documentation = null)] 624 | partial class TestClass 625 | { 626 | private readonly int fieldOne; 627 | } 628 | 629 | partial class TestClass 630 | { 631 | private readonly int fieldTwo; 632 | }"; 633 | 634 | string generatedCode = @" 635 | partial class TestClass 636 | { 637 | public TestClass( 638 | int @fieldOne, 639 | int @fieldTwo) 640 | { 641 | this.@fieldOne = @fieldOne; 642 | this.@fieldTwo = @fieldTwo; 643 | } 644 | }"; 645 | 646 | await AssertGeneratedCode(sourceCode, generatedCode); 647 | } 648 | 649 | [Theory] 650 | [InlineData("QuickConstructor")] 651 | [InlineData("QuickConstructor()")] 652 | [InlineData("QuickConstructorAttribute")] 653 | [InlineData("QuickConstructor.Attributes.QuickConstructor")] 654 | [InlineData("QuickConstructor.Attributes.QuickConstructorAttribute")] 655 | [InlineData("global::QuickConstructor.Attributes.QuickConstructor")] 656 | [InlineData("global::QuickConstructor.Attributes.QuickConstructorAttribute")] 657 | [InlineData("global::QuickConstructor.Attributes.QuickConstructorAttribute()")] 658 | public async Task SyntaxTree_AttributeSyntax(string attributeSyntax) 659 | { 660 | string sourceCode = $@" 661 | [{attributeSyntax}] 662 | partial class TestClass 663 | {{ 664 | private readonly int fieldOne; 665 | }}"; 666 | 667 | string generatedCode = @" 668 | partial class TestClass 669 | { 670 | /// 671 | /// Initializes a new instance of the class. 672 | /// 673 | public TestClass( 674 | int @fieldOne) 675 | { 676 | this.@fieldOne = @fieldOne; 677 | } 678 | }"; 679 | 680 | await AssertGeneratedCode(sourceCode, generatedCode); 681 | } 682 | 683 | [Fact] 684 | public async Task NullChecks_NonNullableReferencesOnly() 685 | { 686 | string sourceCode = @" 687 | [QuickConstructor(NullChecks = NullChecks.NonNullableReferencesOnly, Documentation = null)] 688 | partial class TestClass 689 | { 690 | private readonly int fieldOne; 691 | private readonly int? fieldTwo; 692 | private readonly string fieldThree; 693 | private readonly string? fieldFour; 694 | private readonly System.Collections.Generic.List fieldFive; 695 | }"; 696 | 697 | string generatedCode = @" 698 | partial class TestClass 699 | { 700 | public TestClass( 701 | int @fieldOne, 702 | int? @fieldTwo, 703 | string @fieldThree, 704 | string? @fieldFour, 705 | global::System.Collections.Generic.List @fieldFive) 706 | { 707 | if (@fieldThree == null) 708 | throw new global::System.ArgumentNullException(nameof(@fieldThree)); 709 | 710 | if (@fieldFive == null) 711 | throw new global::System.ArgumentNullException(nameof(@fieldFive)); 712 | 713 | this.@fieldOne = @fieldOne; 714 | this.@fieldTwo = @fieldTwo; 715 | this.@fieldThree = @fieldThree; 716 | this.@fieldFour = @fieldFour; 717 | this.@fieldFive = @fieldFive; 718 | } 719 | }"; 720 | 721 | await AssertGeneratedCode(sourceCode, generatedCode); 722 | } 723 | 724 | [Fact] 725 | public async Task NullChecks_Always() 726 | { 727 | string sourceCode = @" 728 | [QuickConstructor(NullChecks = NullChecks.Always, Documentation = null)] 729 | partial class TestClass 730 | { 731 | private readonly int fieldOne; 732 | private readonly int? fieldTwo; 733 | private readonly string fieldThree; 734 | private readonly string? fieldFour; 735 | private readonly System.Collections.Generic.List fieldFive; 736 | }"; 737 | 738 | string generatedCode = @" 739 | partial class TestClass 740 | { 741 | public TestClass( 742 | int @fieldOne, 743 | int? @fieldTwo, 744 | string @fieldThree, 745 | string? @fieldFour, 746 | global::System.Collections.Generic.List @fieldFive) 747 | { 748 | if (@fieldThree == null) 749 | throw new global::System.ArgumentNullException(nameof(@fieldThree)); 750 | 751 | if (@fieldFour == null) 752 | throw new global::System.ArgumentNullException(nameof(@fieldFour)); 753 | 754 | if (@fieldFive == null) 755 | throw new global::System.ArgumentNullException(nameof(@fieldFive)); 756 | 757 | this.@fieldOne = @fieldOne; 758 | this.@fieldTwo = @fieldTwo; 759 | this.@fieldThree = @fieldThree; 760 | this.@fieldFour = @fieldFour; 761 | this.@fieldFive = @fieldFive; 762 | } 763 | }"; 764 | 765 | await AssertGeneratedCode(sourceCode, generatedCode); 766 | } 767 | 768 | [Fact] 769 | public async Task NullChecks_Never() 770 | { 771 | string sourceCode = @" 772 | [QuickConstructor(NullChecks = NullChecks.Never, Documentation = null)] 773 | partial class TestClass 774 | { 775 | private readonly int fieldOne; 776 | private readonly int? fieldTwo; 777 | private readonly string fieldThree; 778 | private readonly string? fieldFour; 779 | private readonly System.Collections.Generic.List fieldFive; 780 | }"; 781 | 782 | string generatedCode = @" 783 | partial class TestClass 784 | { 785 | public TestClass( 786 | int @fieldOne, 787 | int? @fieldTwo, 788 | string @fieldThree, 789 | string? @fieldFour, 790 | global::System.Collections.Generic.List @fieldFive) 791 | { 792 | this.@fieldOne = @fieldOne; 793 | this.@fieldTwo = @fieldTwo; 794 | this.@fieldThree = @fieldThree; 795 | this.@fieldFour = @fieldFour; 796 | this.@fieldFive = @fieldFive; 797 | } 798 | }"; 799 | 800 | await AssertGeneratedCode(sourceCode, generatedCode); 801 | } 802 | 803 | [Fact] 804 | public async Task NullChecks_NullableReferenceTypesDisabled() 805 | { 806 | string sourceCode = @" 807 | #nullable disable 808 | [QuickConstructor(NullChecks = NullChecks.NonNullableReferencesOnly, Documentation = null)] 809 | partial class TestClass 810 | { 811 | private readonly int fieldOne; 812 | private readonly int? fieldTwo; 813 | private readonly string fieldThree; 814 | private readonly string? fieldFour; 815 | private readonly System.Collections.Generic.List fieldFive; 816 | }"; 817 | 818 | string generatedCode = @" 819 | partial class TestClass 820 | { 821 | public TestClass( 822 | int @fieldOne, 823 | int? @fieldTwo, 824 | string @fieldThree, 825 | string? @fieldFour, 826 | global::System.Collections.Generic.List @fieldFive) 827 | { 828 | this.@fieldOne = @fieldOne; 829 | this.@fieldTwo = @fieldTwo; 830 | this.@fieldThree = @fieldThree; 831 | this.@fieldFour = @fieldFour; 832 | this.@fieldFive = @fieldFive; 833 | } 834 | }"; 835 | 836 | await AssertGeneratedCode(sourceCode, generatedCode); 837 | } 838 | 839 | [Theory] 840 | [InlineData("class")] 841 | [InlineData("System.Uri")] 842 | [InlineData("class?")] 843 | [InlineData("System.Uri?")] 844 | [InlineData("notnull")] 845 | public async Task NullChecks_Generics(string constraint) 846 | { 847 | string sourceCode = $@" 848 | [QuickConstructor(NullChecks = NullChecks.NonNullableReferencesOnly, Documentation = null)] 849 | partial class TestClass where T : {constraint} 850 | {{ 851 | private readonly T fieldOne; 852 | }}"; 853 | 854 | string generatedCode = @" 855 | partial class TestClass 856 | { 857 | public TestClass( 858 | T @fieldOne) 859 | { 860 | if (@fieldOne == null) 861 | throw new global::System.ArgumentNullException(nameof(@fieldOne)); 862 | 863 | this.@fieldOne = @fieldOne; 864 | } 865 | }"; 866 | 867 | await AssertGeneratedCode(sourceCode, generatedCode); 868 | } 869 | 870 | private static async Task AssertGeneratedCode(string sourceCode, string generatedCode) 871 | { 872 | string fullGeneratedCode = CreateExpectedFile(generatedCode); 873 | 874 | CSharpIncrementalGeneratorTest tester = new() 875 | { 876 | TestState = 877 | { 878 | Sources = 879 | { 880 | $@" 881 | #nullable enable 882 | using QuickConstructor.Attributes; 883 | namespace TestNamespace 884 | {{ 885 | {sourceCode} 886 | }}" 887 | }, 888 | GeneratedSources = 889 | { 890 | ( 891 | typeof(QuickConstructorGenerator), 892 | $"TestClass.cs", 893 | SourceText.From(fullGeneratedCode, Encoding.UTF8) 894 | ), 895 | } 896 | }, 897 | }; 898 | 899 | tester.TestState.AdditionalReferences.Add(typeof(QuickConstructorGenerator).Assembly); 900 | tester.TestState.AdditionalReferences.Add(typeof(QuickConstructorAttribute).Assembly); 901 | 902 | await tester.RunAsync(); 903 | } 904 | 905 | private static string CreateExpectedFile(string generatedCode) 906 | { 907 | string fullGeneratedCode = $@" 908 | /// 909 | /// This code was generated by the {nameof(QuickConstructor)} source generator. 910 | /// 911 | 912 | #nullable enable 913 | 914 | namespace TestNamespace 915 | {{ 916 | {generatedCode} 917 | }}"; 918 | 919 | return CodeFormatter.Format(fullGeneratedCode); 920 | } 921 | } 922 | -------------------------------------------------------------------------------- /test/QuickConstructor.Tests/QuickConstructor.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | --------------------------------------------------------------------------------