├── .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 | [](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 |
--------------------------------------------------------------------------------