diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..a271784
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "csharpier": {
+ "version": "0.28.2",
+ "commands": [
+ "dotnet-csharpier"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..1bab644
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,568 @@
+# Version: 2.0.1 (Using https://semver.org/)
+# Updated: 2020-12-11
+# See https://github.com/RehanSaeed/EditorConfig/releases for release notes.
+# See https://github.com/RehanSaeed/EditorConfig for updates to this file.
+# See http://EditorConfig.org for more information about .editorconfig files.
+
+##########################################
+# Common Settings
+##########################################
+
+# This file is the top-most EditorConfig file
+root = true
+
+# All Files
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+##########################################
+# File Extension Settings
+##########################################
+
+# Visual Studio Solution Files
+[*.sln]
+indent_style = tab
+
+# Visual Studio XML Project Files
+[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}]
+indent_size = 2
+
+# XML Configuration Files
+[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}]
+indent_size = 2
+
+# JSON Files
+[*.{json,json5,webmanifest}]
+indent_size = 2
+
+# YAML Files
+[*.{yml,yaml}]
+indent_size = 2
+
+# Markdown Files
+[*.md]
+trim_trailing_whitespace = false
+
+# Web Files
+[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}]
+indent_size = 2
+
+# Batch Files
+[*.{cmd,bat}]
+end_of_line = crlf
+
+# Bash Files
+[*.sh]
+end_of_line = lf
+
+# Makefiles
+[Makefile]
+indent_style = tab
+
+##########################################
+# Default .NET Code Style Severities
+# https://docs.microsoft.com/dotnet/fundamentals/code-analysis/configuration-options#scope
+##########################################
+
+[*.{cs,csx,cake,vb,vbx}]
+# Default Severity for all .NET Code Style rules below
+dotnet_analyzer_diagnostic.severity = silent
+
+##########################################
+# File Header (Uncomment to support file headers)
+# https://docs.microsoft.com/visualstudio/ide/reference/add-file-header
+##########################################
+
+# [*.{cs,csx,cake,vb,vbx}]
+# file_header_template = \n© PROJECT-AUTHOR\n
+
+# SA1636: File header copyright text should match
+# Justification: .editorconfig supports file headers. If this is changed to a value other than "none", a stylecop.json file will need to added to the project.
+# dotnet_diagnostic.SA1636.severity = none
+
+##########################################
+# .NET Language Conventions
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions
+##########################################
+
+# .NET Code Style Settings
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings
+[*.{cs,csx,cake,vb,vbx}]
+# "this." and "Me." qualifiers
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me
+#dotnet_style_qualification_for_field = true:warning
+#dotnet_style_qualification_for_property = true:warning
+#dotnet_style_qualification_for_method = true:warning
+#dotnet_style_qualification_for_event = true:warning
+# Language keywords instead of framework type names for type references
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+# Modifier preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers
+dotnet_style_require_accessibility_modifiers = always:warning
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning
+visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning
+dotnet_style_readonly_field = true:warning
+# Parentheses preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_operators = always_for_clarity:suggestion
+# Expression-level preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences
+dotnet_style_object_initializer = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_explicit_tuple_names = true:warning
+dotnet_style_prefer_inferred_tuple_names = true:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion
+dotnet_diagnostic.IDE0045.severity = suggestion
+dotnet_style_prefer_conditional_expression_over_return = false:suggestion
+dotnet_diagnostic.IDE0046.severity = suggestion
+dotnet_style_prefer_compound_assignment = true:warning
+# Null-checking preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+# Parameter preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences
+dotnet_code_quality_unused_parameters = all:warning
+# More style options (Undocumented)
+# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641
+dotnet_style_operator_placement_when_wrapping = end_of_line
+# https://github.com/dotnet/roslyn/pull/40070
+dotnet_style_prefer_simplified_interpolation = true:warning
+
+# C# Code Style Settings
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings
+[*.{cs,csx,cake}]
+# Implicit and explicit types
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types
+csharp_style_var_for_built_in_types = true:warning
+csharp_style_var_when_type_is_apparent = true:warning
+csharp_style_var_elsewhere = true:warning
+# Expression-bodied members
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members
+csharp_style_expression_bodied_methods = true:warning
+csharp_style_expression_bodied_constructors = true:warning
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_lambdas = true:warning
+csharp_style_expression_bodied_local_functions = true:warning
+# Pattern matching
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+# Inlined variable declarations
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations
+csharp_style_inlined_variable_declaration = true:warning
+# Expression-level preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences
+csharp_prefer_simple_default_expression = true:warning
+# "Null" checking preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences
+csharp_style_throw_expression = true:warning
+csharp_style_conditional_delegate_call = true:warning
+# Code block preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences
+csharp_prefer_braces = true:warning
+# Unused value preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences
+csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
+dotnet_diagnostic.IDE0058.severity = suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+dotnet_diagnostic.IDE0059.severity = suggestion
+# Index and range preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+# Miscellaneous preferences
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences
+csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+csharp_using_directive_placement = outside_namespace:warning
+csharp_prefer_static_local_function = true:warning
+csharp_prefer_simple_using_statement = true:suggestion
+dotnet_diagnostic.IDE0063.severity = suggestion
+
+##########################################
+# .NET Formatting Conventions
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions
+##########################################
+
+# Organize usings
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives
+dotnet_sort_system_directives_first = true
+# Newline options
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+# Indentation options
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = no_change
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents_when_block = false
+# Spacing options
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_after_comma = true
+csharp_space_before_comma = false
+csharp_space_after_dot = false
+csharp_space_before_dot = false
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_around_declaration_statements = false
+csharp_space_before_open_square_brackets = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_square_brackets = false
+# Wrapping options
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options
+csharp_preserve_single_line_statements = false
+csharp_preserve_single_line_blocks = true
+
+csharp_style_namespace_declarations = file_scoped
+
+##########################################
+# .NET Naming Conventions
+# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions
+##########################################
+
+[*.{cs,csx,cake,vb,vbx}]
+dotnet_diagnostic.CA1000.severity = suggestion
+dotnet_diagnostic.CA1001.severity = error
+dotnet_diagnostic.CA1018.severity = error
+dotnet_diagnostic.CA1036.severity = silent
+dotnet_diagnostic.CA1051.severity = suggestion
+dotnet_diagnostic.CA1068.severity = error
+dotnet_diagnostic.CA1069.severity = error
+dotnet_diagnostic.CA1304.severity = error
+dotnet_diagnostic.CA1305.severity = suggestion
+dotnet_diagnostic.CA1307.severity = suggestion
+dotnet_diagnostic.CA1309.severity = suggestion
+dotnet_diagnostic.CA1310.severity = error
+dotnet_diagnostic.CA1507.severity = suggestion
+dotnet_diagnostic.CA1513.severity = suggestion
+dotnet_diagnostic.CA1707.severity = suggestion
+dotnet_diagnostic.CA1708.severity = suggestion
+dotnet_diagnostic.CA1711.severity = suggestion
+dotnet_diagnostic.CA1716.severity = suggestion
+dotnet_diagnostic.CA1720.severity = suggestion
+dotnet_diagnostic.CA1725.severity = suggestion
+dotnet_diagnostic.CA1805.severity = suggestion
+dotnet_diagnostic.CA1816.severity = suggestion
+dotnet_diagnostic.CA1822.severity = suggestion
+dotnet_diagnostic.CA1825.severity = error
+dotnet_diagnostic.CA1826.severity = silent
+dotnet_diagnostic.CA1827.severity = error
+dotnet_diagnostic.CA1829.severity = suggestion
+dotnet_diagnostic.CA1834.severity = error
+dotnet_diagnostic.CA1845.severity = suggestion
+dotnet_diagnostic.CA1848.severity = suggestion
+dotnet_diagnostic.CA1852.severity = suggestion
+dotnet_diagnostic.CA1860.severity = silent
+dotnet_diagnostic.CA2016.severity = suggestion
+dotnet_diagnostic.CA2201.severity = error
+dotnet_diagnostic.CA2206.severity = error
+dotnet_diagnostic.CA2208.severity = error
+dotnet_diagnostic.CA2211.severity = error
+dotnet_diagnostic.CA2249.severity = error
+dotnet_diagnostic.CA2251.severity = error
+dotnet_diagnostic.CA2252.severity = none
+dotnet_diagnostic.CA2254.severity = suggestion
+
+dotnet_diagnostic.CS0169.severity = error
+dotnet_diagnostic.CS0219.severity = error
+dotnet_diagnostic.CS0649.severity = suggestion
+dotnet_diagnostic.CS1998.severity = error
+dotnet_diagnostic.CS8602.severity = error
+dotnet_diagnostic.CS8604.severity = error
+dotnet_diagnostic.CS8618.severity = error
+dotnet_diagnostic.CS0618.severity = suggestion
+dotnet_diagnostic.CS1998.severity = error
+dotnet_diagnostic.CS4014.severity = error
+dotnet_diagnostic.CS8600.severity = error
+dotnet_diagnostic.CS8603.severity = error
+dotnet_diagnostic.CS8625.severity = error
+
+dotnet_diagnostic.BL0005.severity = suggestion
+
+dotnet_diagnostic.MVC1000.severity = suggestion
+
+dotnet_diagnostic.RZ10012.severity = error
+
+dotnet_diagnostic.IDE0004.severity = error # redundant cast
+dotnet_diagnostic.IDE0005.severity = error
+dotnet_diagnostic.IDE0007.severity = error # Use var
+dotnet_diagnostic.IDE0011.severity = error # Use braces on if statements
+dotnet_diagnostic.IDE0010.severity = silent # populate switch
+dotnet_diagnostic.IDE0017.severity = suggestion # initialization can be simplified
+dotnet_diagnostic.IDE0021.severity = silent # expression body for constructors
+dotnet_diagnostic.IDE0022.severity = silent # expression body for methods
+dotnet_diagnostic.IDE0023.severity = suggestion # use expression body for operators
+dotnet_diagnostic.IDE0024.severity = silent # expression body for operators
+dotnet_diagnostic.IDE0025.severity = suggestion # use expression body for properties
+dotnet_diagnostic.IDE0027.severity = suggestion # Use expression body for accessors
+dotnet_diagnostic.IDE0028.severity = silent # expression body for accessors
+dotnet_diagnostic.IDE0032.severity = suggestion # Use auto property
+dotnet_diagnostic.IDE0033.severity = error # prefer tuple name
+dotnet_diagnostic.IDE0037.severity = suggestion # simplify anonymous type
+dotnet_diagnostic.IDE0040.severity = error # modifiers required
+dotnet_diagnostic.IDE0041.severity = error # simplify null
+dotnet_diagnostic.IDE0042.severity = error # deconstruct variable
+dotnet_diagnostic.IDE0044.severity = suggestion # make field only when possible
+dotnet_diagnostic.IDE0047.severity = suggestion # parameter name
+dotnet_diagnostic.IDE0051.severity = error # unused field
+dotnet_diagnostic.IDE0052.severity = error # unused member
+dotnet_diagnostic.IDE0053.severity = suggestion # lambda not needed
+dotnet_diagnostic.IDE0055.severity = suggestion # Fix formatting
+dotnet_diagnostic.IDE0057.severity = suggestion # substring can be simplified
+dotnet_diagnostic.IDE0060.severity = suggestion # unused parameters
+dotnet_diagnostic.IDE0061.severity = suggestion # local expression body
+dotnet_diagnostic.IDE0062.severity = suggestion # local to static
+dotnet_diagnostic.IDE0063.severity = error # simplify using
+dotnet_diagnostic.IDE0066.severity = suggestion # switch expression
+dotnet_diagnostic.IDE0072.severity = suggestion # Populate switch - forces population of all cases even when default specified
+dotnet_diagnostic.IDE0078.severity = suggestion # use pattern matching
+dotnet_diagnostic.IDE0090.severity = suggestion # new can be simplified
+dotnet_diagnostic.IDE0130.severity = suggestion # namespace folder structure
+dotnet_diagnostic.IDE0160.severity = silent # Use block namespaces ARE NOT required
+dotnet_diagnostic.IDE0161.severity = error # Please use file namespaces
+dotnet_diagnostic.IDE0200.severity = suggestion # lambda not needed
+dotnet_diagnostic.IDE1006.severity = suggestion # Naming rule violation: These words cannot contain lower case characters
+dotnet_diagnostic.IDE0260.severity = suggestion # Use pattern matching
+dotnet_diagnostic.IDE0270.severity = suggestion # Null check simplifcation
+dotnet_diagnostic.IDE0290.severity = error # Primary Constructor
+dotnet_diagnostic.IDE0300.severity = suggestion # Collection
+dotnet_diagnostic.IDE0305.severity = suggestion # Collection ToList
+
+dotnet_diagnostic.NX0001.severity = error
+dotnet_diagnostic.NX0002.severity = silent
+dotnet_diagnostic.NX0003.severity = silent
+
+##########################################
+# Styles
+##########################################
+
+# camel_case_style - Define the camelCase style
+dotnet_naming_style.camel_case_style.capitalization = camel_case
+# pascal_case_style - Define the PascalCase style
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+# constant_case - Define the CONSTANT_CASE style
+dotnet_naming_style.constant_case.capitalization = all_upper
+dotnet_naming_style.constant_case.word_separator = _
+# first_upper_style - The first character must start with an upper-case character
+dotnet_naming_style.first_upper_style.capitalization = first_word_upper
+# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I'
+dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case
+dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I
+# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T'
+dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case
+dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T
+# disallowed_style - Anything that has this style applied is marked as disallowed
+dotnet_naming_style.disallowed_style.capitalization = pascal_case
+dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____
+dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____
+# internal_error_style - This style should never occur... if it does, it indicates a bug in file or in the parser using the file
+dotnet_naming_style.internal_error_style.capitalization = pascal_case
+dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____
+dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____
+
+# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I'
+dotnet_naming_style.underscore_camel_case_style.capitalization = camel_case
+dotnet_naming_style.underscore_camel_case_style.required_prefix = _
+
+##########################################
+# .NET Design Guideline Field Naming Rules
+# Naming rules for fields follow the .NET Framework design guidelines
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/index
+##########################################
+
+# All public/protected/protected_internal constant fields must be constant_case
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/field
+dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal
+dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const
+dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field
+dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group
+dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = constant_case
+dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning
+
+# All public/protected/protected_internal static readonly fields must be constant_case
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/field
+dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal
+dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly
+dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field
+dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group
+dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = constant_case
+dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning
+
+# No other public/protected/protected_internal fields are allowed
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/field
+dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal
+dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field
+dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group
+dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style
+dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error
+
+##########################################
+# StyleCop Field Naming Rules
+# Naming rules for fields follow the StyleCop analyzers
+# This does not override any rules using disallowed_style above
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers
+##########################################
+
+# All constant fields must be constant_case
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md
+dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
+dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const
+dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group
+dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = constant_case
+dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning
+
+# All static readonly fields must be constant_case
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md
+dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private
+dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly
+dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group
+dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = constant_case
+dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning
+
+# No non-private instance fields are allowed
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md
+dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected
+dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group
+dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style
+dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error
+
+# Private fields must be camelCase
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md
+dotnet_naming_symbols.stylecop_private_fields_group.applicable_accessibilities = private
+dotnet_naming_symbols.stylecop_private_fields_group.applicable_kinds = field
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.symbols = stylecop_private_fields_group
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.style = underscore_camel_case_style
+dotnet_naming_rule.stylecop_private_fields_must_be_camel_case_rule.severity = warning
+
+# Local variables must be camelCase
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md
+dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local
+dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local
+dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group
+dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style
+dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = warning
+
+# This rule should never fire. However, it's included for at least two purposes:
+# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers.
+# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#).
+dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = *
+dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field
+dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group
+dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style
+dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error
+
+
+##########################################
+# Other Naming Rules
+##########################################
+
+# All of the following must be PascalCase:
+# - Namespaces
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
+# - Classes and Enumerations
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
+# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md
+# - Delegates
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types
+# - Constructors, Properties, Events, Methods
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members
+dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property
+dotnet_naming_rule.element_rule.symbols = element_group
+dotnet_naming_rule.element_rule.style = pascal_case_style
+dotnet_naming_rule.element_rule.severity = warning
+
+# Interfaces use PascalCase and are prefixed with uppercase 'I'
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
+dotnet_naming_symbols.interface_group.applicable_kinds = interface
+dotnet_naming_rule.interface_rule.symbols = interface_group
+dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style
+dotnet_naming_rule.interface_rule.severity = warning
+
+# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T'
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces
+dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter
+dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group
+dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style
+dotnet_naming_rule.type_parameter_rule.severity = warning
+
+# Function parameters use camelCase
+# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters
+dotnet_naming_symbols.parameters_group.applicable_kinds = parameter
+dotnet_naming_rule.parameters_rule.symbols = parameters_group
+dotnet_naming_rule.parameters_rule.style = camel_case_style
+dotnet_naming_rule.parameters_rule.severity = warning
+
+##########################################
+# License
+##########################################
+# The following applies as to the .editorconfig file ONLY, and is
+# included below for reference, per the requirements of the license
+# corresponding to this .editorconfig file.
+# See: https://github.com/RehanSaeed/EditorConfig
+#
+# MIT License
+#
+# Copyright (c) 2017-2019 Muhammad Rehan Saeed
+# Copyright (c) 2019 Henry Gabryjelski
+#
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute,
+# sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject
+# to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+##########################################
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..3413224
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions" # search for actions - there are other options available
+ directory: "/" # search in .github/workflows under root `/`
+ schedule:
+ interval: "weekly" # check for action update every week
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..1da29ee
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,26 @@
+name: "Check Build"
+
+on:
+ pull_request:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Project
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --configuration Debug --no-restore
+
+ - name: Test
+ run: dotnet test --no-build --configuration Debug --verbosity normal
\ No newline at end of file
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
index f18e6f2..3350d1e 100644
--- a/.github/workflows/nuget.yml
+++ b/.github/workflows/nuget.yml
@@ -6,29 +6,24 @@ on:
jobs:
deploy:
runs-on: ubuntu-latest
- env:
- DOTNET_CLI_TELEMETRY_OPTOUT: true
steps:
- name: Checkout Project
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Setup .NET
- uses: actions/setup-dotnet@v1
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: 6.0.x
+ dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
- run: dotnet build --configuration Release --no-restore
-
- - name: Test
- run: dotnet test --no-build --configuration Release --verbosity normal
+ run: dotnet build --configuration Release --no-restore Speckle.InterfaceGenerator/Speckle.InterfaceGenerator.csproj
- name: Pack
- run: dotnet pack --no-build --configuration Release InterfaceGenerator/InterfaceGenerator.csproj --output .
+ run: dotnet pack --no-build --configuration Release Speckle.InterfaceGenerator/Speckle.InterfaceGenerator.csproj --output .
- name: Push to nuget.org
- run: dotnet nuget push *.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.NUGET_API_KEY}} --skip-duplicate
\ No newline at end of file
+ run: dotnet nuget push *.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
\ No newline at end of file
diff --git a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj b/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj
deleted file mode 100644
index db3e2fe..0000000
--- a/InterfaceGenerator.Tests/InterfaceGenerator.Tests.csproj
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- net6.0
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/InterfaceGenerator.Tests/Partial/PartialClass.1.cs b/InterfaceGenerator.Tests/Partial/PartialClass.1.cs
deleted file mode 100644
index 02ff0de..0000000
--- a/InterfaceGenerator.Tests/Partial/PartialClass.1.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace InterfaceGenerator.Tests.Partial;
-
-[GenerateAutoInterface]
-internal partial class PartialClass : IPartialClass
-{
-
-}
\ No newline at end of file
diff --git a/InterfaceGenerator.Tests/Partial/PartialClass.2.cs b/InterfaceGenerator.Tests/Partial/PartialClass.2.cs
deleted file mode 100644
index 9bcf3b9..0000000
--- a/InterfaceGenerator.Tests/Partial/PartialClass.2.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace InterfaceGenerator.Tests.Partial;
-
-internal partial class PartialClass
-{
- public void SomeMethodThatShouldGenerate()
- {
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator.Tests/PartialClassTests.cs b/InterfaceGenerator.Tests/PartialClassTests.cs
deleted file mode 100644
index a290724..0000000
--- a/InterfaceGenerator.Tests/PartialClassTests.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using FluentAssertions;
-using InterfaceGenerator.Tests.Partial;
-using Xunit;
-
-namespace InterfaceGenerator.Tests;
-
-public class PartialClassTests
-{
- [Fact]
- public void GeneratesMethodFromOtherParts()
- {
- var tInterface = typeof(IPartialClass);
- tInterface.GetMethods().Should().Contain(x => x.Name == nameof(PartialClass.SomeMethodThatShouldGenerate));
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator.Tests/SameName/SameNameClass.1.cs b/InterfaceGenerator.Tests/SameName/SameNameClass.1.cs
deleted file mode 100644
index e3906f2..0000000
--- a/InterfaceGenerator.Tests/SameName/SameNameClass.1.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// ReSharper disable CheckNamespace
-namespace InterfaceGenerator.Tests.SameName_1;
-
-///
-/// A class with the same name as . It exists to test if the generated source units have fully
-/// qualified names.
-///
-[GenerateAutoInterface]
-internal class SameNameClass : ISameNameClass
-{
-
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/AttributeDataExtensions.cs b/InterfaceGenerator/AttributeDataExtensions.cs
deleted file mode 100644
index 084842a..0000000
--- a/InterfaceGenerator/AttributeDataExtensions.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Linq;
-using Microsoft.CodeAnalysis;
-
-namespace InterfaceGenerator
-{
- internal static class AttributeDataExtensions
- {
- public static string? GetNamedParamValue(this AttributeData attributeData, string paramName)
- {
- var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == paramName);
- return pair.Value.Value?.ToString();
- }
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/Attributes.cs b/InterfaceGenerator/Attributes.cs
deleted file mode 100644
index 754b8ec..0000000
--- a/InterfaceGenerator/Attributes.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-namespace InterfaceGenerator
-{
-
- internal class Attributes
- {
- public const string AttributesNamespace = nameof(InterfaceGenerator);
-
- public const string GenerateAutoInterfaceClassname = "GenerateAutoInterfaceAttribute";
- public const string AutoInterfaceIgnoreAttributeClassname = "AutoInterfaceIgnoreAttribute";
-
- public const string VisibilityModifierPropName = "VisibilityModifier";
- public const string InterfaceNamePropName = "Name";
-
- public static readonly string AttributesSourceCode = $@"
-
-using System;
-using System.Diagnostics;
-
-#nullable enable
-
-namespace {AttributesNamespace}
-{{
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
- [Conditional(""CodeGeneration"")]
- internal sealed class {GenerateAutoInterfaceClassname} : Attribute
- {{
- public string? {VisibilityModifierPropName} {{ get; init; }}
- public string? {InterfaceNamePropName} {{ get; init; }}
-
- public {GenerateAutoInterfaceClassname}()
- {{
- }}
- }}
-
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false)]
- [Conditional(""CodeGeneration"")]
- internal sealed class {AutoInterfaceIgnoreAttributeClassname} : Attribute
- {{
- }}
-}}
-";
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/AutoInterfaceGenerator.cs b/InterfaceGenerator/AutoInterfaceGenerator.cs
deleted file mode 100644
index e4ec264..0000000
--- a/InterfaceGenerator/AutoInterfaceGenerator.cs
+++ /dev/null
@@ -1,461 +0,0 @@
-using System;
-using System.CodeDom.Compiler;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.Text;
-
-namespace InterfaceGenerator
-{
- [Generator]
- public class AutoInterfaceGenerator : ISourceGenerator
- {
- private INamedTypeSymbol _generateAutoInterfaceAttribute = null!;
- private INamedTypeSymbol _ignoreAttribute = null!;
-
- public void Initialize(GeneratorInitializationContext context)
- {
- context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
-
- #if DEBUG
- if (!Debugger.IsAttached)
- {
- // sadly this is Windows only so as of now :(
- Debugger.Launch();
- }
- #endif
- }
-
- public void Execute(GeneratorExecutionContext context)
- {
- try
- {
- ExecuteCore(context);
- }
- catch (Exception exception)
- {
- RaiseExceptionDiagnostic(context, exception);
- }
- }
-
- private static void RaiseExceptionDiagnostic(GeneratorExecutionContext context, Exception exception)
- {
- var descriptor = new DiagnosticDescriptor(
- "InterfaceGenerator.CriticalError",
- $"Exception thrown in InterfaceGenerator",
- $"{exception.GetType().FullName} {exception.Message} {exception.StackTrace.Trim()}",
- "InterfaceGenerator",
- DiagnosticSeverity.Error,
- true,
- customTags: WellKnownDiagnosticTags.AnalyzerException);
-
- var diagnostic = Diagnostic.Create(descriptor, null);
-
- context.ReportDiagnostic(diagnostic);
- }
-
- private void ExecuteCore(GeneratorExecutionContext context)
- {
- // setting the culture to invariant prevents errors such as emitting a decimal comma (0,1) instead of
- // a decimal point (0.1) in certain cultures
- var prevCulture = Thread.CurrentThread.CurrentCulture;
- Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
-
- GenerateAttributes(context);
- GenerateInterfaces(context);
-
- Thread.CurrentThread.CurrentCulture = prevCulture;
- }
-
- private static void GenerateAttributes(GeneratorExecutionContext context)
- {
- context.AddSource(
- Attributes.GenerateAutoInterfaceClassname,
- SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8));
- }
-
- private void GenerateInterfaces(GeneratorExecutionContext context)
- {
- if (context.SyntaxReceiver is not SyntaxReceiver receiver)
- {
- return;
- }
-
- var compilation = GetCompilation(context);
- InitAttributes(compilation);
-
- var classSymbols = GetImplTypeSymbols(compilation, receiver);
-
- List classSymbolNames = new List();
-
- foreach (var implTypeSymbol in classSymbols)
- {
- if (!implTypeSymbol.TryGetAttribute(_generateAutoInterfaceAttribute, out var attributes))
- {
- continue;
- }
-
- if(classSymbolNames.Contains(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)))
- {
- continue; // partial class, already added
- }
-
- classSymbolNames.Add(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true));
-
- var attribute = attributes.Single();
- var source = SourceText.From(GenerateInterfaceCode(implTypeSymbol, attribute), Encoding.UTF8);
-
- context.AddSource($"{implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)}_AutoInterface.g.cs", source);
- }
- }
-
- private static string InferVisibilityModifier(ISymbol implTypeSymbol, AttributeData attributeData)
- {
- string? result = attributeData.GetNamedParamValue(Attributes.VisibilityModifierPropName);
- if (!string.IsNullOrEmpty(result))
- {
- return result!;
- }
-
- return implTypeSymbol.DeclaredAccessibility switch
- {
- Accessibility.Public => "public",
- _ => "internal",
- };
- }
-
- private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData)
- {
- return attributeData.GetNamedParamValue(Attributes.InterfaceNamePropName) ?? $"I{implTypeSymbol.Name}";
- }
-
- private string GenerateInterfaceCode(INamedTypeSymbol implTypeSymbol, AttributeData attributeData)
- {
- using var stream = new MemoryStream();
- var streamWriter = new StreamWriter(stream, Encoding.UTF8);
- var codeWriter = new IndentedTextWriter(streamWriter, " ");
-
- var namespaceName = implTypeSymbol.ContainingNamespace.ToDisplayString();
- var interfaceName = InferInterfaceName(implTypeSymbol, attributeData);
- var visibilityModifier = InferVisibilityModifier(implTypeSymbol, attributeData);
-
- codeWriter.WriteLine("namespace {0}", namespaceName);
- codeWriter.WriteLine("{");
-
- ++codeWriter.Indent;
- WriteSymbolDocsIfPresent(codeWriter, implTypeSymbol);
- codeWriter.Write("{0} partial interface {1}", visibilityModifier, interfaceName);
- WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol);
- codeWriter.WriteLine();
- codeWriter.WriteLine("{");
-
- ++codeWriter.Indent;
- GenerateInterfaceMemberDefinitions(codeWriter, implTypeSymbol);
- --codeWriter.Indent;
-
- codeWriter.WriteLine("}");
- --codeWriter.Indent;
-
- codeWriter.WriteLine("}");
-
- codeWriter.Flush();
- stream.Seek(0, SeekOrigin.Begin);
- using var reader = new StreamReader(stream, Encoding.UTF8, true);
- return reader.ReadToEnd();
- }
-
- private static void WriteTypeGenericsIfNeeded(TextWriter writer, INamedTypeSymbol implTypeSymbol)
- {
- if (!implTypeSymbol.IsGenericType)
- {
- return;
- }
-
- writer.Write("<");
- writer.WriteJoin(", ", implTypeSymbol.TypeParameters.Select(x => x.Name));
- writer.Write(">");
-
- WriteTypeParameterConstraints(writer, implTypeSymbol.TypeParameters);
- }
-
- private void GenerateInterfaceMemberDefinitions(TextWriter writer, INamespaceOrTypeSymbol implTypeSymbol)
- {
- foreach (var member in implTypeSymbol.GetMembers())
- {
- if (member.DeclaredAccessibility != Accessibility.Public ||
- member.HasAttribute(_ignoreAttribute))
- {
- continue;
- }
-
- GenerateInterfaceMemberDefinition(writer, member);
- }
- }
-
- private static void GenerateInterfaceMemberDefinition(TextWriter writer, ISymbol member)
- {
- switch (member)
- {
- case IPropertySymbol propertySymbol:
- GeneratePropertyDefinition(writer, propertySymbol);
- break;
- case IMethodSymbol methodSymbol:
- GenerateMethodDefinition(writer, methodSymbol);
- break;
- }
- }
-
- private static void WriteSymbolDocsIfPresent(TextWriter writer, ISymbol symbol)
- {
- var xml = symbol.GetDocumentationCommentXml();
- if (string.IsNullOrWhiteSpace(xml))
- {
- return;
- }
-
- // omit the fist and last lines to skip the tag
-
- var reader = new StringReader(xml);
- var lines = new List();
-
- while (true)
- {
- var line = reader.ReadLine();
- if (line is null)
- {
- break;
- }
-
- lines.Add(line);
- }
-
- for (int i = 1; i < lines.Count - 1; i++)
- {
- var line = lines[i].TrimStart(); // for some reason, 4 spaces are inserted to the beginning of the line
- writer.WriteLine("/// {0}", line);
- }
- }
-
- private static bool IsPublicOrInternal(ISymbol symbol)
- {
- return symbol.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal;
- }
-
- private static void GeneratePropertyDefinition(TextWriter writer, IPropertySymbol propertySymbol)
- {
- if (propertySymbol.IsStatic)
- {
- return;
- }
-
- bool hasPublicGetter = propertySymbol.GetMethod is not null &&
- IsPublicOrInternal(propertySymbol.GetMethod);
-
- bool hasPublicSetter = propertySymbol.SetMethod is not null &&
- IsPublicOrInternal(propertySymbol.SetMethod);
-
- if (!hasPublicGetter && !hasPublicSetter)
- {
- return;
- }
-
- WriteSymbolDocsIfPresent(writer, propertySymbol);
-
- if (propertySymbol.IsIndexer)
- {
- writer.Write("{0} this[", propertySymbol.Type);
- writer.WriteJoin(", ", propertySymbol.Parameters, WriteMethodParam);
- writer.Write("] ");
- }
- else
- {
- writer.Write("{0} {1} ", propertySymbol.Type, propertySymbol.Name); // ex. int Foo
- }
-
- writer.Write("{ ");
-
- if (hasPublicGetter)
- {
- writer.Write("get; ");
- }
-
- if (hasPublicSetter)
- {
- if (propertySymbol.SetMethod!.IsInitOnly)
- {
- writer.Write("init; ");
- }
- else
- {
- writer.Write("set; ");
- }
- }
-
- writer.WriteLine("}");
- }
-
- private static void GenerateMethodDefinition(TextWriter writer, IMethodSymbol methodSymbol)
- {
- if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsStatic)
- {
- return;
- }
-
- if (methodSymbol.IsImplicitlyDeclared && methodSymbol.Name != "Deconstruct")
- {
- // omit methods that are auto generated by the compiler (eg. record's methods),
- // except for the record Deconstruct method
- return;
- }
-
- WriteSymbolDocsIfPresent(writer, methodSymbol);
-
- writer.Write("{0} {1}", methodSymbol.ReturnType, methodSymbol.Name); // ex. int Foo
-
- if (methodSymbol.IsGenericMethod)
- {
- writer.Write("<");
- writer.WriteJoin(", ", methodSymbol.TypeParameters.Select(x => x.Name));
- writer.Write(">");
- }
-
- writer.Write("(");
- writer.WriteJoin(", ", methodSymbol.Parameters, WriteMethodParam);
-
- writer.Write(")");
-
- if (methodSymbol.IsGenericMethod)
- {
- WriteTypeParameterConstraints(writer, methodSymbol.TypeParameters);
- }
-
- writer.WriteLine(";");
- }
-
- private static void WriteMethodParam(TextWriter writer, IParameterSymbol param)
- {
- if (param.IsParams)
- {
- writer.Write("params ");
- }
-
- switch (param.RefKind)
- {
- case RefKind.Ref:
- writer.Write("ref ");
- break;
- case RefKind.Out:
- writer.Write("out ");
- break;
- case RefKind.In:
- writer.Write("in ");
- break;
- }
-
- writer.Write(param.Type);
- writer.Write(" ");
-
- if (StringExtensions.IsCSharpKeyword(param.Name))
- {
- writer.Write("@");
- }
-
- writer.Write(param.Name);
-
- if (param.HasExplicitDefaultValue)
- {
- WriteParamExplicitDefaultValue(writer, param);
- }
- }
-
- private static void WriteParamExplicitDefaultValue(TextWriter writer, IParameterSymbol param)
- {
- if (param.ExplicitDefaultValue is null)
- {
- writer.Write(" = default");
- }
- else
- {
- switch (param.Type.Name)
- {
- case nameof(String):
- writer.Write(" = \"{0}\"", param.ExplicitDefaultValue);
- break;
- case nameof(Single):
- writer.Write(" = {0}f", param.ExplicitDefaultValue);
- break;
- case nameof(Double):
- writer.Write(" = {0}d", param.ExplicitDefaultValue);
- break;
- case nameof(Decimal):
- writer.Write(" = {0}m", param.ExplicitDefaultValue);
- break;
- case nameof(Boolean):
- writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower());
- break;
- case nameof(Nullable):
- writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower());
- break;
- default:
- writer.Write(" = {0}", param.ExplicitDefaultValue);
- break;
- }
- }
- }
-
- private static void WriteTypeParameterConstraints(
- TextWriter writer,
- IEnumerable typeParameters)
- {
- foreach (var typeParameter in typeParameters)
- {
- var constraints = typeParameter.EnumGenericConstraints().ToList();
- if (constraints.Count == 0)
- {
- break;
- }
-
- writer.Write(" where {0} : ", typeParameter.Name);
- writer.WriteJoin(", ", constraints);
- }
- }
-
- private void InitAttributes(Compilation compilation)
- {
- _generateAutoInterfaceAttribute = compilation.GetTypeByMetadataName(
- $"{Attributes.AttributesNamespace}.{Attributes.GenerateAutoInterfaceClassname}")!;
-
- _ignoreAttribute = compilation.GetTypeByMetadataName(
- $"{Attributes.AttributesNamespace}.{Attributes.AutoInterfaceIgnoreAttributeClassname}")!;
- }
-
- private static IEnumerable GetImplTypeSymbols(Compilation compilation, SyntaxReceiver receiver)
- {
- return receiver.CandidateTypes.Select(candidate => GetTypeSymbol(compilation, candidate));
- }
-
- private static INamedTypeSymbol GetTypeSymbol(Compilation compilation, SyntaxNode type)
- {
- var model = compilation.GetSemanticModel(type.SyntaxTree);
- var typeSymbol = model.GetDeclaredSymbol(type)!;
- return (INamedTypeSymbol)typeSymbol;
- }
-
- private static Compilation GetCompilation(GeneratorExecutionContext context)
- {
- var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions;
-
- var compilation = context.Compilation.AddSyntaxTrees(
- CSharpSyntaxTree.ParseText(
- SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8), options));
-
- return compilation;
- }
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/InterfaceGenerator.csproj b/InterfaceGenerator/InterfaceGenerator.csproj
deleted file mode 100644
index 9da3776..0000000
--- a/InterfaceGenerator/InterfaceGenerator.csproj
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
- netstandard2
- 9.0
- enable
- 1.0.14
-
- true
- false
- false
- true
-
-
-
- R. David
- InterfaceGenerator
- A source generator that creates interfaces from implementations
- https://github.com/daver32/InterfaceGenerator
- https://github.com/daver32/InterfaceGenerator/blob/master/LICENSE
- https://github.com/daver32/InterfaceGenerator
- git
-
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
diff --git a/InterfaceGenerator/StringExtensions.cs b/InterfaceGenerator/StringExtensions.cs
deleted file mode 100644
index 8859a69..0000000
--- a/InterfaceGenerator/StringExtensions.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-namespace InterfaceGenerator
-{
- internal static class StringExtensions
- {
- public static bool IsCSharpKeyword(string? name)
- {
- switch (name)
- {
- case "abstract":
- case "add":
- case "alias":
- case "as":
- case "ascending":
- case "async":
- case "await":
- case "base":
- case "bool":
- case "break":
- case "by":
- case "byte":
- case "case":
- case "catch":
- case "char":
- case "checked":
- case "class":
- case "const":
- case "continue":
- case "decimal":
- case "default":
- case "delegate":
- case "descending":
- case "do":
- case "double":
- case "dynamic":
- case "else":
- case "enum":
- case "equals":
- case "event":
- case "explicit":
- case "extern":
- case "false":
- case "finally":
- case "fixed":
- case "float":
- case "for":
- case "foreach":
- case "from":
- case "get":
- case "global":
- case "goto":
- // `group` is a contextual to linq queries that we don't generate
- //case "group":
- case "if":
- case "implicit":
- case "in":
- case "int":
- case "interface":
- case "internal":
- case "into":
- case "is":
- case "join":
- case "let":
- case "lock":
- case "long":
- case "nameof":
- case "namespace":
- case "new":
- case "null":
- case "object":
- case "on":
- case "operator":
- // `orderby` is a contextual to linq queries that we don't generate
- //case "orderby":
- case "out":
- case "override":
- case "params":
- case "partial":
- case "private":
- case "protected":
- case "public":
- case "readonly":
- case "ref":
- case "remove":
- case "return":
- case "sbyte":
- case "sealed":
- // `select` is a contextual to linq queries that we don't generate
- // case "select":
- case "set":
- case "short":
- case "sizeof":
- case "stackalloc":
- case "static":
- case "string":
- case "struct":
- case "switch":
- case "this":
- case "throw":
- case "true":
- case "try":
- case "typeof":
- case "uint":
- case "ulong":
- case "unchecked":
- case "unmanaged":
- case "unsafe":
- case "ushort":
- case "using":
- // `value` is a contextual to getters that we don't generate
- // case "value":
- case "var":
- case "virtual":
- case "void":
- case "volatile":
- case "when":
- case "where":
- case "while":
- case "yield":
- return true;
- default:
- return false;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/SymbolExtensions.cs b/InterfaceGenerator/SymbolExtensions.cs
deleted file mode 100644
index 5f309ae..0000000
--- a/InterfaceGenerator/SymbolExtensions.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Microsoft.CodeAnalysis;
-
-namespace InterfaceGenerator
-{
- internal static class SymbolExtensions
- {
- public static bool TryGetAttribute(
- this ISymbol symbol,
- INamedTypeSymbol attributeType,
- out IEnumerable attributes)
- {
- attributes = symbol.GetAttributes()
- .Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
- return attributes.Any();
- }
-
- public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
- {
- return symbol.GetAttributes()
- .Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
- }
-
- //Ref: https://stackoverflow.com/questions/27105909/get-fully-qualified-metadata-name-in-roslyn
- public static string GetFullMetadataName(this ISymbol symbol, bool useNameWhenNotFound = false)
- {
- if (IsRootNamespace(symbol))
- {
- return useNameWhenNotFound ? symbol.Name : string.Empty;
- }
-
- var stringBuilder = new StringBuilder(symbol.MetadataName);
- var last = symbol;
-
- symbol = symbol.ContainingSymbol;
-
- while (!IsRootNamespace(symbol))
- {
- if (symbol is ITypeSymbol && last is ITypeSymbol)
- {
- stringBuilder.Insert(0, '+');
- }
- else
- {
- stringBuilder.Insert(0, '.');
- }
-
- stringBuilder.Insert(0, symbol.OriginalDefinition.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
- symbol = symbol.ContainingSymbol;
- }
-
- var retVal = stringBuilder.ToString();
- if (string.IsNullOrWhiteSpace(retVal) && useNameWhenNotFound)
- {
- return symbol.Name;
- }
-
- return retVal;
- }
-
- private static bool IsRootNamespace(ISymbol symbol)
- {
- return symbol is INamespaceSymbol { IsGlobalNamespace: true };
- }
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/SyntaxReceiver.cs b/InterfaceGenerator/SyntaxReceiver.cs
deleted file mode 100644
index 570b42b..0000000
--- a/InterfaceGenerator/SyntaxReceiver.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.Collections.Generic;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace InterfaceGenerator
-{
- internal class SyntaxReceiver : ISyntaxReceiver
- {
- public IList CandidateTypes { get; } = new List();
-
- public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
- {
- if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax &&
- IsClassOrRecord(typeDeclarationSyntax) &&
- typeDeclarationSyntax.AttributeLists.Count > 0)
- {
- CandidateTypes.Add(typeDeclarationSyntax);
- }
- }
-
- private static bool IsClassOrRecord(TypeDeclarationSyntax typeDeclarationSyntax)
- {
- return typeDeclarationSyntax is ClassDeclarationSyntax || typeDeclarationSyntax is RecordDeclarationSyntax;
- }
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/TextWriterExtensions.cs b/InterfaceGenerator/TextWriterExtensions.cs
deleted file mode 100644
index 2c25a61..0000000
--- a/InterfaceGenerator/TextWriterExtensions.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-
-namespace InterfaceGenerator
-{
- internal static class TextWriterExtensions
- {
-
- public static void WriteJoin(
- this TextWriter writer,
- string separator,
- IEnumerable values)
- {
- writer.WriteJoin(separator, values, (w, x) => w.Write(x));
- }
-
- public static void WriteJoin(
- this TextWriter writer,
- string separator,
- IEnumerable values,
- Action writeAction)
- {
- using var enumerator = values.GetEnumerator();
-
- if (!enumerator.MoveNext())
- {
- return;
- }
-
- writeAction(writer, enumerator.Current);
-
- if (!enumerator.MoveNext())
- {
- return;
- }
-
- do
- {
- writer.Write(separator);
- writeAction(writer, enumerator.Current);
- } while (enumerator.MoveNext());
- }
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator/TypeParameterSymbolExtensions.cs b/InterfaceGenerator/TypeParameterSymbolExtensions.cs
deleted file mode 100644
index bfaa883..0000000
--- a/InterfaceGenerator/TypeParameterSymbolExtensions.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Collections.Generic;
-using Microsoft.CodeAnalysis;
-
-namespace InterfaceGenerator
-{
- internal static class TypeParameterSymbolExtensions
- {
- public static IEnumerable EnumGenericConstraints(this ITypeParameterSymbol symbol)
- {
- // the class/struct/unmanaged/notnull constraint has to be the last
- if (symbol.HasNotNullConstraint)
- {
- yield return "notnull";
- }
-
- if (symbol.HasValueTypeConstraint)
- {
- yield return "struct";
- }
-
- if (symbol.HasUnmanagedTypeConstraint)
- {
- yield return "unmanaged";
- }
-
- if (symbol.HasReferenceTypeConstraint)
- {
- yield return symbol.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated
- ? "class?"
- : "class";
- }
-
-
- // types go in the middle
- foreach (var constraintType in symbol.ConstraintTypes)
- {
- yield return constraintType.ToDisplayString();
- }
-
-
- // the new() constraint has to be the last
- if (symbol.HasConstructorConstraint)
- {
- yield return "new()";
- }
- }
- }
-}
\ No newline at end of file
diff --git a/InterfaceGenerator.Tests/AccessorsGenerationTests.cs b/Speckle.InterfaceGenerator.Tests/AccessorsGenerationTests.cs
similarity index 50%
rename from InterfaceGenerator.Tests/AccessorsGenerationTests.cs
rename to Speckle.InterfaceGenerator.Tests/AccessorsGenerationTests.cs
index 7e61ee2..f3bca43 100644
--- a/InterfaceGenerator.Tests/AccessorsGenerationTests.cs
+++ b/Speckle.InterfaceGenerator.Tests/AccessorsGenerationTests.cs
@@ -1,9 +1,13 @@
-using System.Runtime.CompilerServices;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.SymbolStore;
+using System.Linq;
+using System.Runtime.CompilerServices;
using FluentAssertions;
using FluentAssertions.Common;
using Xunit;
-namespace InterfaceGenerator.Tests;
+namespace Speckle.InterfaceGenerator.Tests;
public class AccessorsGenerationTests
{
@@ -17,7 +21,11 @@ public AccessorsGenerationTests()
[Fact]
public void GetSetIndexer_IsImplemented()
{
- var indexer = typeof(IAccessorsTestsService).GetIndexerByParameterTypes(new[] { typeof(string) });
+ var indexer = typeof(IAccessorsTestsService)
+ .GetProperties()
+ .First(x =>
+ x.GetIndexParameters().Select(x => x.ParameterType).Contains(typeof(string))
+ );
indexer.Should().NotBeNull();
@@ -31,8 +39,10 @@ public void GetSetIndexer_IsImplemented()
[Fact]
public void PublicProperty_IsImplemented()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(IAccessorsTestsService.PublicProperty))!;
+ var prop =
+ typeof(IAccessorsTestsService).GetProperty(
+ nameof(IAccessorsTestsService.PublicProperty)
+ ) ?? throw new InvalidOperationException();
prop.Should().NotBeNull();
@@ -46,15 +56,19 @@ public void PublicProperty_IsImplemented()
[Fact]
public void InitProperty_IsImplemented()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(IAccessorsTestsService.InitOnlyProperty))!;
+ var prop =
+ typeof(IAccessorsTestsService).GetProperty(
+ nameof(IAccessorsTestsService.InitOnlyProperty)
+ ) ?? throw new InvalidOperationException();
prop.Should().NotBeNull();
prop.GetMethod.Should().NotBeNull();
prop.SetMethod.Should().NotBeNull();
- prop.SetMethod!.ReturnParameter!.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit));
+ prop.SetMethod?.ReturnParameter?.GetRequiredCustomModifiers()
+ .Should()
+ .Contain(typeof(IsExternalInit));
var _ = _sut.InitOnlyProperty;
}
@@ -62,8 +76,10 @@ public void InitProperty_IsImplemented()
[Fact]
public void PrivateSetter_IsOmitted()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateSetter))!;
+ var prop =
+ typeof(IAccessorsTestsService).GetProperty(
+ nameof(IAccessorsTestsService.PropertyWithPrivateSetter)
+ ) ?? throw new InvalidOperationException();
prop.Should().NotBeNull();
@@ -76,8 +92,10 @@ public void PrivateSetter_IsOmitted()
[Fact]
public void PrivateGetter_IsOmitted()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(IAccessorsTestsService.PropertyWithPrivateGetter))!;
+ var prop =
+ typeof(IAccessorsTestsService).GetProperty(
+ nameof(IAccessorsTestsService.PropertyWithPrivateGetter)
+ ) ?? throw new InvalidOperationException();
prop.Should().NotBeNull();
@@ -90,8 +108,10 @@ public void PrivateGetter_IsOmitted()
[Fact]
public void ProtectedSetter_IsOmitted()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedSetter))!;
+ var prop =
+ typeof(IAccessorsTestsService).GetProperty(
+ nameof(IAccessorsTestsService.PropertyWithProtectedSetter)
+ ) ?? throw new InvalidOperationException();
prop.Should().NotBeNull();
@@ -104,8 +124,10 @@ public void ProtectedSetter_IsOmitted()
[Fact]
public void ProtectedGetter_IsOmitted()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(IAccessorsTestsService.PropertyWithProtectedGetter))!;
+ var prop =
+ typeof(IAccessorsTestsService).GetProperty(
+ nameof(IAccessorsTestsService.PropertyWithProtectedGetter)
+ ) ?? throw new InvalidOperationException();
prop.Should().NotBeNull();
@@ -118,8 +140,9 @@ public void ProtectedGetter_IsOmitted()
[Fact]
public void IgnoredProperty_IsOmitted()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(AccessorsTestsService.IgnoredProperty));
+ var prop = typeof(IAccessorsTestsService).GetProperty(
+ nameof(AccessorsTestsService.IgnoredProperty)
+ );
prop.Should().BeNull();
}
@@ -127,8 +150,9 @@ public void IgnoredProperty_IsOmitted()
[Fact]
public void StaticProperty_IsOmitted()
{
- var prop = typeof(IAccessorsTestsService)
- .GetProperty(nameof(AccessorsTestsService.StaticProperty));
+ var prop = typeof(IAccessorsTestsService).GetProperty(
+ nameof(AccessorsTestsService.StaticProperty)
+ );
prop.Should().BeNull();
}
@@ -141,25 +165,27 @@ internal class AccessorsTestsService : IAccessorsTestsService
public int this[string x]
{
get => 0;
- set
- {
- }
+ set { }
}
+ public FtpStyleUriParser? SymbolBinder { get; set; }
+ public FtpStyleUriParser SymbolBinder2 { get; set; } = default!;
+ public IEnumerable SymbolBinder3 { get; set; } = default!;
- public string PublicProperty { get; set; }
+ public string PublicProperty { get; set; } = string.Empty;
- public string InitOnlyProperty { get; init; }
+ public string InitOnlyProperty { get; init; } = string.Empty;
- public string PropertyWithPrivateSetter { get; private set; }
+ public string PropertyWithPrivateSetter { get; private set; } = string.Empty;
- public string PropertyWithPrivateGetter { private get; set; }
+ public string PropertyWithPrivateGetter { private get; set; } = string.Empty;
- public string PropertyWithProtectedSetter { get; protected set; }
+ public string PropertyWithProtectedSetter { get; protected set; } = string.Empty;
- public string PropertyWithProtectedGetter { protected get; set; }
+ public string PropertyWithProtectedGetter { protected get; set; } = string.Empty;
- [AutoInterfaceIgnore] public string IgnoredProperty { get; set; }
+ [AutoInterfaceIgnore]
+ public string IgnoredProperty { get; set; } = string.Empty;
- public static string StaticProperty { get; set; }
+ public static string StaticProperty { get; set; } = string.Empty;
}
-// ReSharper enable UnusedMember.Local, ValueParameterNotUsed
\ No newline at end of file
+// ReSharper enable UnusedMember.Local, ValueParameterNotUsed
diff --git a/InterfaceGenerator.Tests/GenericInterfaceTests.cs b/Speckle.InterfaceGenerator.Tests/GenericInterfaceTests.cs
similarity index 78%
rename from InterfaceGenerator.Tests/GenericInterfaceTests.cs
rename to Speckle.InterfaceGenerator.Tests/GenericInterfaceTests.cs
index 8642e3b..ca1255d 100644
--- a/InterfaceGenerator.Tests/GenericInterfaceTests.cs
+++ b/Speckle.InterfaceGenerator.Tests/GenericInterfaceTests.cs
@@ -3,7 +3,7 @@
using FluentAssertions;
using Xunit;
-namespace InterfaceGenerator.Tests;
+namespace Speckle.InterfaceGenerator.Tests;
public class GenericInterfaceTests
{
@@ -18,12 +18,15 @@ public void GenericParametersGeneratedCorrectly()
genericArgs[0].IsClass.Should().BeTrue();
genericArgs[0]
- .GenericParameterAttributes
- .Should()
+ .GenericParameterAttributes.Should()
.HaveFlag(GenericParameterAttributes.DefaultConstructorConstraint);
var iEquatableOfTx = typeof(IEquatable<>).MakeGenericType(genericArgs[0]);
- genericArgs[0].GetGenericParameterConstraints().Should().HaveCount(1).And.Contain(iEquatableOfTx);
+ genericArgs[0]
+ .GetGenericParameterConstraints()
+ .Should()
+ .HaveCount(1)
+ .And.Contain(iEquatableOfTx);
genericArgs[1].IsValueType.Should().BeTrue();
}
@@ -33,6 +36,4 @@ public void GenericParametersGeneratedCorrectly()
// ReSharper disable once UnusedType.Global
internal class GenericInterfaceTestsService : IGenericInterfaceTestsService
where TX : class, IEquatable, new()
- where TY : struct
-{
-}
\ No newline at end of file
+ where TY : struct { }
diff --git a/InterfaceGenerator.Tests/MethodGenerationTests.cs b/Speckle.InterfaceGenerator.Tests/MethodGenerationTests.cs
similarity index 62%
rename from InterfaceGenerator.Tests/MethodGenerationTests.cs
rename to Speckle.InterfaceGenerator.Tests/MethodGenerationTests.cs
index fb9a748..263766b 100644
--- a/InterfaceGenerator.Tests/MethodGenerationTests.cs
+++ b/Speckle.InterfaceGenerator.Tests/MethodGenerationTests.cs
@@ -1,11 +1,12 @@
using System;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Text;
using FluentAssertions;
using Xunit;
-namespace InterfaceGenerator.Tests;
+namespace Speckle.InterfaceGenerator.Tests;
public class MethodGenerationTests
{
@@ -19,8 +20,9 @@ public MethodGenerationTests()
[Fact]
public void VoidMethod_IsImplemented()
{
- var method = typeof(IMethodsTestService).GetMethod(
- nameof(MethodsTestService.VoidMethod))!;
+ var method =
+ typeof(IMethodsTestService).GetMethod(nameof(MethodsTestService.VoidMethod))
+ ?? throw new InvalidOperationException();
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
@@ -34,8 +36,10 @@ public void VoidMethod_IsImplemented()
[Fact]
public void VoidMethodWithKeywordParam_IsImplemented()
{
- var method = typeof(IMethodsTestService).GetMethod(
- nameof(MethodsTestService.VoidMethodWithKeywordParam))!;
+ var method =
+ typeof(IMethodsTestService).GetMethod(
+ nameof(MethodsTestService.VoidMethodWithKeywordParam)
+ ) ?? throw new InvalidOperationException();
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
@@ -52,9 +56,11 @@ public void VoidMethodWithKeywordParam_IsImplemented()
[Fact]
public void VoidMethodWithParams_IsImplemented()
{
- var method = typeof(IMethodsTestService).GetMethod(
- nameof(MethodsTestService.VoidMethodWithParams),
- new[] { typeof(string), typeof(string) })!;
+ var method =
+ typeof(IMethodsTestService).GetMethod(
+ nameof(MethodsTestService.VoidMethodWithParams),
+ [typeof(string), typeof(string)]
+ ) ?? throw new InvalidOperationException();
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
@@ -69,15 +75,20 @@ public void VoidMethodWithParams_IsImplemented()
[Fact]
public void VoidMethodWithOutParam_IsImplemented()
{
- var method = typeof(IMethodsTestService).GetMethod(
- nameof(MethodsTestService.VoidMethodWithOutParam),
- new[] { typeof(string).MakeByRefType() })!;
+ var method =
+ typeof(IMethodsTestService).GetMethod(
+ nameof(MethodsTestService.VoidMethodWithOutParam),
+ [typeof(string).MakeByRefType()]
+ ) ?? throw new InvalidOperationException();
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
var parameters = method.GetParameters();
- parameters.Select(x => x.ParameterType).Should().AllBeEquivalentTo(typeof(string).MakeByRefType());
+ parameters
+ .Select(x => x.ParameterType)
+ .Should()
+ .AllBeEquivalentTo(typeof(string).MakeByRefType());
parameters.Should().HaveCount(1);
parameters[0].IsOut.Should().BeTrue();
@@ -87,15 +98,20 @@ public void VoidMethodWithOutParam_IsImplemented()
[Fact]
public void VoidMethodWithInParam_IsImplemented()
{
- var method = typeof(IMethodsTestService).GetMethod(
- nameof(MethodsTestService.VoidMethodWithInParam),
- new[] { typeof(string).MakeByRefType() })!;
+ var method =
+ typeof(IMethodsTestService).GetMethod(
+ nameof(MethodsTestService.VoidMethodWithInParam),
+ [typeof(string).MakeByRefType()]
+ ) ?? throw new InvalidOperationException();
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
var parameters = method.GetParameters();
- parameters.Select(x => x.ParameterType).Should().AllBeEquivalentTo(typeof(string).MakeByRefType());
+ parameters
+ .Select(x => x.ParameterType)
+ .Should()
+ .AllBeEquivalentTo(typeof(string).MakeByRefType());
parameters.Should().HaveCount(1);
parameters[0].IsIn.Should().BeTrue();
@@ -106,15 +122,20 @@ public void VoidMethodWithInParam_IsImplemented()
[Fact]
public void VoidMethodWithRefParam_IsImplemented()
{
- var method = typeof(IMethodsTestService).GetMethod(
- nameof(MethodsTestService.VoidMethodWithRefParam),
- new[] { typeof(string).MakeByRefType() })!;
+ var method =
+ typeof(IMethodsTestService).GetMethod(
+ nameof(MethodsTestService.VoidMethodWithRefParam),
+ [typeof(string).MakeByRefType()]
+ ) ?? throw new InvalidOperationException();
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
var parameters = method.GetParameters();
- parameters.Select(x => x.ParameterType).Should().AllBeEquivalentTo(typeof(string).MakeByRefType());
+ parameters
+ .Select(x => x.ParameterType)
+ .Should()
+ .AllBeEquivalentTo(typeof(string).MakeByRefType());
parameters.Should().HaveCount(1);
parameters[0].IsIn.Should().BeFalse();
parameters[0].IsOut.Should().BeFalse();
@@ -126,8 +147,9 @@ public void VoidMethodWithRefParam_IsImplemented()
[Fact]
public void StringMethod_IsImplemented()
{
- var method = typeof(IMethodsTestService).GetMethod(
- nameof(MethodsTestService.StringMethod))!;
+ var method =
+ typeof(IMethodsTestService).GetMethod(nameof(MethodsTestService.StringMethod))
+ ?? throw new InvalidOperationException();
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(string));
@@ -138,12 +160,42 @@ public void StringMethod_IsImplemented()
var _ = _sut.StringMethod();
}
+ [Fact]
+ public void StringMethodNullable_IsImplemented()
+ {
+ var method =
+ typeof(IMethodsTestService).GetMethod(nameof(MethodsTestService.StringMethodNullable))
+ ?? throw new InvalidOperationException();
+
+ method.Should().NotBeNull();
+ method.ReturnType.Should().Be(typeof(string));
+ IsNullable(method.ReturnType).Should().BeTrue();
+
+ var parameters = method.GetParameters();
+ parameters.Should().BeEmpty();
+
+ var _ = _sut.StringMethod();
+ }
+
+ private static bool IsNullable(Type type)
+ {
+ var nullableContextAttribute = type.GetCustomAttribute();
+
+ // NullableContextAttribute exists and has a flag indicating nullable annotations
+ if (nullableContextAttribute != null && nullableContextAttribute.Flag == 1)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
[Fact]
public void GenericVoidMethod_IsImplemented()
{
var method = typeof(IMethodsTestService)
- .GetMethods()
- .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethod));
+ .GetMethods()
+ .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethod));
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
@@ -160,8 +212,8 @@ public void GenericVoidMethod_IsImplemented()
public void GenericVoidMethodWithGenericParam_IsImplemented()
{
var method = typeof(IMethodsTestService)
- .GetMethods()
- .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithGenericParam));
+ .GetMethods()
+ .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithGenericParam));
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
@@ -180,8 +232,8 @@ public void GenericVoidMethodWithGenericParam_IsImplemented()
public void GenericVoidMethodWithConstraints_IsImplemented()
{
var method = typeof(IMethodsTestService)
- .GetMethods()
- .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithConstraints));
+ .GetMethods()
+ .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithConstraints));
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
@@ -206,8 +258,8 @@ public void GenericVoidMethodWithConstraints_IsImplemented()
public void VoidMethodWithOptionalParams_IsImplemented()
{
var method = typeof(IMethodsTestService)
- .GetMethods()
- .First(x => x.Name == nameof(MethodsTestService.VoidMethodWithOptionalParams));
+ .GetMethods()
+ .First(x => x.Name == nameof(MethodsTestService.VoidMethodWithOptionalParams));
method.Should().NotBeNull();
method.ReturnType.Should().Be(typeof(void));
@@ -226,7 +278,7 @@ public void VoidMethodWithOptionalParams_IsImplemented()
parameters[7].DefaultValue.Should().Be(true);
parameters[8].DefaultValue.Should().Be(false);
parameters[9].DefaultValue.Should().Be(null);
-
+
_sut.VoidMethodWithOptionalParams();
}
@@ -234,8 +286,8 @@ public void VoidMethodWithOptionalParams_IsImplemented()
public void VoidMethodWithExpandingParam_IsImplemented()
{
var method = typeof(IMethodsTestService)
- .GetMethods()
- .First(x => x.Name == nameof(MethodsTestService.VoidMethodWithExpandingParam));
+ .GetMethods()
+ .First(x => x.Name == nameof(MethodsTestService.VoidMethodWithExpandingParam));
method.ReturnType.Should().Be(typeof(void));
@@ -249,8 +301,8 @@ public void VoidMethodWithExpandingParam_IsImplemented()
public void IgnoreMethod_IsOmitted()
{
var method = typeof(IMethodsTestService)
- .GetMethods()
- .FirstOrDefault(x => x.Name == nameof(MethodsTestService.IgnoredMethod));
+ .GetMethods()
+ .FirstOrDefault(x => x.Name == nameof(MethodsTestService.IgnoredMethod));
method.Should().BeNull();
}
@@ -259,8 +311,8 @@ public void IgnoreMethod_IsOmitted()
public void StaticMethod_IsOmitted()
{
var method = typeof(IMethodsTestService)
- .GetMethods()
- .FirstOrDefault(x => x.Name == nameof(MethodsTestService.StaticMethod));
+ .GetMethods()
+ .FirstOrDefault(x => x.Name == nameof(MethodsTestService.StaticMethod));
method.Should().BeNull();
}
@@ -271,49 +323,38 @@ internal class MethodsTestService : IMethodsTestService
{
public const string StringConstant = "Const";
- public void VoidMethod()
- {
- }
+ public void VoidMethod() { }
- public void VoidMethodWithParams(string a, string b)
- {
- }
+ public void VoidMethodWithParams(string a, string b) { }
- public void VoidMethodWithKeywordParam(string @void)
- {
- }
+ public void VoidMethodWithKeywordParam(string @void) { }
public void VoidMethodWithOutParam(out string a)
{
- a = default;
+ a = string.Empty;
}
- public void VoidMethodWithRefParam(ref string a)
- {
- }
+ public void VoidMethodWithRefParam(ref string a) { }
- public void VoidMethodWithInParam(in string a)
- {
- }
+ public void VoidMethodWithInParam(in string a) { }
public string StringMethod()
{
return string.Empty;
}
- public void GenericVoidMethod()
+ public string? StringMethodNullable()
{
+ return null;
}
- public void GenericVoidMethodWithGenericParam(TX a)
- {
- }
+ public void GenericVoidMethod() { }
+
+ public void GenericVoidMethodWithGenericParam(TX a) { }
public void GenericVoidMethodWithConstraints()
where TX : class
- where TY : class, TX, new()
- {
- }
+ where TY : class, TX, new() { }
public void VoidMethodWithOptionalParams(
string stringLiteral = "cGFyYW0=",
@@ -325,25 +366,17 @@ public void VoidMethodWithOptionalParams(
bool falseLiteral = false,
bool? nullableTrueLiteral = true,
bool? nullableFalseLiteral = false,
- bool? nullableNullBoolLiteral = null)
- {
- }
+ bool? nullableNullBoolLiteral = null
+ ) { }
- public void VoidMethodWithExpandingParam(params string[] strings)
- {
- }
+ public void VoidMethodWithExpandingParam(params string[] strings) { }
[AutoInterfaceIgnore]
- public void IgnoredMethod()
- {
- }
+ public void IgnoredMethod() { }
- public static void StaticMethod()
- {
- }
+ public static void StaticMethod() { }
}
[GenerateAutoInterface]
-internal class MethodsTestServiceGeneric : IMethodsTestServiceGeneric where T : class
-{
-}
\ No newline at end of file
+internal class MethodsTestServiceGeneric : IMethodsTestServiceGeneric
+ where T : class { }
diff --git a/Speckle.InterfaceGenerator.Tests/Partial/PartialClass.1.cs b/Speckle.InterfaceGenerator.Tests/Partial/PartialClass.1.cs
new file mode 100644
index 0000000..e965d62
--- /dev/null
+++ b/Speckle.InterfaceGenerator.Tests/Partial/PartialClass.1.cs
@@ -0,0 +1,4 @@
+namespace Speckle.InterfaceGenerator.Tests.Partial;
+
+[GenerateAutoInterface]
+internal partial class PartialClass : IPartialClass { }
diff --git a/Speckle.InterfaceGenerator.Tests/Partial/PartialClass.2.cs b/Speckle.InterfaceGenerator.Tests/Partial/PartialClass.2.cs
new file mode 100644
index 0000000..caa08b1
--- /dev/null
+++ b/Speckle.InterfaceGenerator.Tests/Partial/PartialClass.2.cs
@@ -0,0 +1,6 @@
+namespace Speckle.InterfaceGenerator.Tests.Partial;
+
+internal partial class PartialClass
+{
+ public void SomeMethodThatShouldGenerate() { }
+}
diff --git a/Speckle.InterfaceGenerator.Tests/PartialClassTests.cs b/Speckle.InterfaceGenerator.Tests/PartialClassTests.cs
new file mode 100644
index 0000000..ab64e34
--- /dev/null
+++ b/Speckle.InterfaceGenerator.Tests/PartialClassTests.cs
@@ -0,0 +1,18 @@
+using FluentAssertions;
+using Speckle.InterfaceGenerator.Tests.Partial;
+using Xunit;
+
+namespace Speckle.InterfaceGenerator.Tests;
+
+public class PartialClassTests
+{
+ [Fact]
+ public void GeneratesMethodFromOtherParts()
+ {
+ var tInterface = typeof(IPartialClass);
+ tInterface
+ .GetMethods()
+ .Should()
+ .Contain(x => x.Name == nameof(PartialClass.SomeMethodThatShouldGenerate));
+ }
+}
diff --git a/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs b/Speckle.InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs
similarity index 57%
rename from InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs
rename to Speckle.InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs
index 8274723..90e74fa 100644
--- a/InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs
+++ b/Speckle.InterfaceGenerator.Tests/RecordInterfaceGenerationTests.cs
@@ -1,8 +1,9 @@
+using System;
using System.Runtime.CompilerServices;
using FluentAssertions;
using Xunit;
-namespace InterfaceGenerator.Tests;
+namespace Speckle.InterfaceGenerator.Tests;
public class RecordInterfaceGenerationTests
{
@@ -16,14 +17,17 @@ public RecordInterfaceGenerationTests()
[Fact]
public void RecordProperty_IsGenerated()
{
- var prop = typeof(ITestRecord)
- .GetProperty(nameof(TestRecord.RecordProperty))!;
+ var prop =
+ typeof(ITestRecord).GetProperty(nameof(TestRecord.RecordProperty))
+ ?? throw new InvalidOperationException();
prop.Should().NotBeNull();
prop.GetMethod.Should().NotBeNull();
prop.SetMethod.Should().NotBeNull();
- prop.SetMethod!.ReturnParameter!.GetRequiredCustomModifiers().Should().Contain(typeof(IsExternalInit));
+ prop.SetMethod?.ReturnParameter?.GetRequiredCustomModifiers()
+ .Should()
+ .Contain(typeof(IsExternalInit));
_sut.RecordProperty.Should().Be(420);
}
@@ -31,13 +35,12 @@ public void RecordProperty_IsGenerated()
[Fact]
public void RecordMethod_IsGenerated()
{
- var method = typeof(ITestRecord).GetMethod(
- nameof(TestRecord.RecordMethod));
+ var method = typeof(ITestRecord).GetMethod(nameof(TestRecord.RecordMethod));
method.Should().NotBeNull();
- method!.ReturnType.Should().Be(typeof(void));
+ method?.ReturnType.Should().Be(typeof(void));
- var parameters = method.GetParameters();
+ var parameters = method?.GetParameters();
parameters.Should().BeEmpty();
_sut.RecordMethod();
@@ -46,13 +49,12 @@ public void RecordMethod_IsGenerated()
[Fact]
public void Deconstruct_IsGenerated()
{
- var method = typeof(ITestRecord).GetMethod(
- nameof(TestRecord.Deconstruct));
+ var method = typeof(ITestRecord).GetMethod(nameof(TestRecord.Deconstruct));
method.Should().NotBeNull();
- method!.ReturnType.Should().Be(typeof(void));
+ method?.ReturnType.Should().Be(typeof(void));
- var parameters = method.GetParameters();
+ var parameters = method?.GetParameters() ?? throw new InvalidOperationException();
parameters.Length.Should().Be(1);
var parameter = parameters[0];
@@ -64,7 +66,5 @@ public void Deconstruct_IsGenerated()
[GenerateAutoInterface]
internal record TestRecord(int RecordProperty) : ITestRecord
{
- public void RecordMethod()
- {
- }
-}
\ No newline at end of file
+ public void RecordMethod() { }
+}
diff --git a/Speckle.InterfaceGenerator.Tests/SameName/SameNameClass.1.cs b/Speckle.InterfaceGenerator.Tests/SameName/SameNameClass.1.cs
new file mode 100644
index 0000000..e134043
--- /dev/null
+++ b/Speckle.InterfaceGenerator.Tests/SameName/SameNameClass.1.cs
@@ -0,0 +1,29 @@
+// ReSharper disable CheckNamespace
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.SymbolStore;
+using Speckle.InterfaceGenerator;
+
+namespace InterfaceGenerator.Tests.SameName_1;
+
+///
+/// A class with the same name as . It exists to test if the generated source units have fully
+/// qualified names.
+///
+[GenerateAutoInterface]
+public class SameNameClass : ISameNameClass { }
+
+[GenerateAutoInterface]
+public class SameNameClass2 : ISameNameClass2
+{
+ public ISameNameClass Return() => throw new InvalidOperationException();
+
+ public SymbolToken Return2() => throw new InvalidOperationException();
+
+ public T GetRequiredService()
+ where T : class => throw new InvalidOperationException();
+
+ public void TestGenericParameter(List x) =>
+ throw new InvalidOperationException();
+}
diff --git a/InterfaceGenerator.Tests/SameName/SameNameClass.2.cs b/Speckle.InterfaceGenerator.Tests/SameName/SameNameClass.2.cs
similarity index 55%
rename from InterfaceGenerator.Tests/SameName/SameNameClass.2.cs
rename to Speckle.InterfaceGenerator.Tests/SameName/SameNameClass.2.cs
index 9147b4f..b1b2764 100644
--- a/InterfaceGenerator.Tests/SameName/SameNameClass.2.cs
+++ b/Speckle.InterfaceGenerator.Tests/SameName/SameNameClass.2.cs
@@ -1,8 +1,8 @@
// ReSharper disable CheckNamespace
+
+using Speckle.InterfaceGenerator;
+
namespace InterfaceGenerator.Tests.SameName_2;
[GenerateAutoInterface]
-internal class SameNameClass : ISameNameClass
-{
-
-}
\ No newline at end of file
+internal class SameNameClass : ISameNameClass { }
diff --git a/Speckle.InterfaceGenerator.Tests/Speckle.InterfaceGenerator.Tests.csproj b/Speckle.InterfaceGenerator.Tests/Speckle.InterfaceGenerator.Tests.csproj
new file mode 100644
index 0000000..1115338
--- /dev/null
+++ b/Speckle.InterfaceGenerator.Tests/Speckle.InterfaceGenerator.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net8.0
+ false
+ Speckle.InterfaceGenerator.Tests
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/InterfaceGenerator.Tests/VisibilityModifierTests.cs b/Speckle.InterfaceGenerator.Tests/VisibilityModifierTests.cs
similarity index 90%
rename from InterfaceGenerator.Tests/VisibilityModifierTests.cs
rename to Speckle.InterfaceGenerator.Tests/VisibilityModifierTests.cs
index 33c9e08..04cc2a2 100644
--- a/InterfaceGenerator.Tests/VisibilityModifierTests.cs
+++ b/Speckle.InterfaceGenerator.Tests/VisibilityModifierTests.cs
@@ -2,7 +2,7 @@
using FluentAssertions;
using Xunit;
-namespace InterfaceGenerator.Tests;
+namespace Speckle.InterfaceGenerator.Tests;
public class VisibilityModifierTests
{
@@ -36,21 +36,13 @@ public void IImplicitlyInternalService_IsInternal()
}
[GenerateAutoInterface(VisibilityModifier = "public")]
-internal class ExplicitlyPublicService : IExplicitlyPublicService
-{
-}
+internal class ExplicitlyPublicService : IExplicitlyPublicService { }
[GenerateAutoInterface(VisibilityModifier = "internal")]
-public class ExplicitlyInternalService : IExplicitlyInternalService
-{
-}
+public class ExplicitlyInternalService : IExplicitlyInternalService { }
[GenerateAutoInterface]
-public class ImplicitlyPublicService : IImplicitlyPublicService
-{
-}
+public class ImplicitlyPublicService : IImplicitlyPublicService { }
[GenerateAutoInterface]
-internal class ImplicitlyInternalService : IImplicitlyInternalService
-{
-}
\ No newline at end of file
+internal class ImplicitlyInternalService : IImplicitlyInternalService { }
diff --git a/InterfaceGenerator.sln b/Speckle.InterfaceGenerator.sln
similarity index 57%
rename from InterfaceGenerator.sln
rename to Speckle.InterfaceGenerator.sln
index 3cae7ca..e4ead48 100644
--- a/InterfaceGenerator.sln
+++ b/Speckle.InterfaceGenerator.sln
@@ -1,8 +1,16 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceGenerator", "InterfaceGenerator\InterfaceGenerator.csproj", "{D40E2D60-5580-42D2-8316-F5AAA42CFBF6}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.InterfaceGenerator", "Speckle.InterfaceGenerator\Speckle.InterfaceGenerator.csproj", "{D40E2D60-5580-42D2-8316-F5AAA42CFBF6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterfaceGenerator.Tests", "InterfaceGenerator.Tests\InterfaceGenerator.Tests.csproj", "{D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.InterfaceGenerator.Tests", "Speckle.InterfaceGenerator.Tests\Speckle.InterfaceGenerator.Tests.csproj", "{D39C541E-9EDC-41E9-BBD8-FAA9DA602CC0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Config", "Config", "{47654DE0-162F-4045-B5EB-151C3678430C}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ .gitignore = .gitignore
+ global.json = global.json
+ .github\workflows\nuget.yml = .github\workflows\nuget.yml
+ EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/InterfaceGenerator/.gitattributes b/Speckle.InterfaceGenerator/.gitattributes
similarity index 100%
rename from InterfaceGenerator/.gitattributes
rename to Speckle.InterfaceGenerator/.gitattributes
diff --git a/Speckle.InterfaceGenerator/AttributeDataExtensions.cs b/Speckle.InterfaceGenerator/AttributeDataExtensions.cs
new file mode 100644
index 0000000..3d7c0ec
--- /dev/null
+++ b/Speckle.InterfaceGenerator/AttributeDataExtensions.cs
@@ -0,0 +1,13 @@
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+namespace Speckle.InterfaceGenerator;
+
+internal static class AttributeDataExtensions
+{
+ public static string? GetNamedParamValue(this AttributeData attributeData, string paramName)
+ {
+ var pair = attributeData.NamedArguments.FirstOrDefault(x => x.Key == paramName);
+ return pair.Value.Value?.ToString();
+ }
+}
diff --git a/Speckle.InterfaceGenerator/Attributes.cs b/Speckle.InterfaceGenerator/Attributes.cs
new file mode 100644
index 0000000..3d70ea1
--- /dev/null
+++ b/Speckle.InterfaceGenerator/Attributes.cs
@@ -0,0 +1,45 @@
+namespace Speckle.InterfaceGenerator;
+
+internal class Attributes
+{
+ public const string AttributesNamespace = "Speckle.InterfaceGenerator";
+
+ public const string GenerateAutoInterfaceClassname = "GenerateAutoInterfaceAttribute";
+ public const string AutoInterfaceIgnoreAttributeClassname = "AutoInterfaceIgnoreAttribute";
+
+ public const string VisibilityModifierPropName = "VisibilityModifier";
+ public const string InterfaceNamePropName = "Name";
+
+ public static readonly string AttributesSourceCode =
+ $@"
+
+#pragma warning disable IDE0005
+using System;
+using System.Diagnostics;
+
+#nullable enable
+
+namespace {AttributesNamespace}
+{{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
+ [Conditional(""CodeGeneration"")]
+ internal sealed class {GenerateAutoInterfaceClassname} : Attribute
+ {{
+ public string? {VisibilityModifierPropName} {{ get; init; }}
+ public string? {InterfaceNamePropName} {{ get; init; }}
+
+ public {GenerateAutoInterfaceClassname}()
+ {{
+ }}
+ }}
+
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false)]
+ [Conditional(""CodeGeneration"")]
+ internal sealed class {AutoInterfaceIgnoreAttributeClassname} : Attribute
+ {{
+ }}
+}}
+
+#pragma warning restore IDE0005
+";
+}
diff --git a/Speckle.InterfaceGenerator/AutoInterfaceGenerator.cs b/Speckle.InterfaceGenerator/AutoInterfaceGenerator.cs
new file mode 100644
index 0000000..2fc3394
--- /dev/null
+++ b/Speckle.InterfaceGenerator/AutoInterfaceGenerator.cs
@@ -0,0 +1,522 @@
+using System;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Speckle.InterfaceGenerator;
+
+[Generator]
+public class AutoInterfaceGenerator : ISourceGenerator
+{
+ private INamedTypeSymbol? _generateAutoInterfaceAttribute;
+ private INamedTypeSymbol? _ignoreAttribute;
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ try
+ {
+ ExecuteCore(context);
+ }
+ catch (Exception exception)
+ {
+ RaiseExceptionDiagnostic(context, exception);
+ }
+ }
+
+ private static void RaiseExceptionDiagnostic(
+ GeneratorExecutionContext context,
+ Exception exception
+ )
+ {
+ var descriptor = new DiagnosticDescriptor(
+ "Speckle.InterfaceGenerator.CriticalError",
+ "Exception thrown in InterfaceGenerator",
+ $"{exception.GetType().FullName} {exception.Message} {exception.StackTrace.Trim()}",
+ "Speckle.InterfaceGenerator",
+ DiagnosticSeverity.Error,
+ true,
+ customTags: WellKnownDiagnosticTags.AnalyzerException
+ );
+
+ var diagnostic = Diagnostic.Create(descriptor, null);
+
+ context.ReportDiagnostic(diagnostic);
+ }
+
+ private void ExecuteCore(GeneratorExecutionContext context)
+ {
+ // setting the culture to invariant prevents errors such as emitting a decimal comma (0,1) instead of
+ // a decimal point (0.1) in certain cultures
+ var prevCulture = Thread.CurrentThread.CurrentCulture;
+ Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
+
+ GenerateAttributes(context);
+ GenerateInterfaces(context);
+
+ Thread.CurrentThread.CurrentCulture = prevCulture;
+ }
+
+ private static void GenerateAttributes(GeneratorExecutionContext context)
+ {
+ context.AddSource(
+ $"{Attributes.GenerateAutoInterfaceClassname}.g.cs",
+ SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8)
+ );
+ }
+
+ private void GenerateInterfaces(GeneratorExecutionContext context)
+ {
+ if (context.SyntaxReceiver is not SyntaxReceiver receiver)
+ {
+ return;
+ }
+
+ var compilation = GetCompilation(context);
+ InitAttributes(compilation);
+
+ var classSymbols = GetImplTypeSymbols(compilation, receiver);
+
+ List classSymbolNames = [];
+
+ foreach (var implTypeSymbol in classSymbols)
+ {
+ if (
+ !implTypeSymbol.TryGetAttribute(
+ _generateAutoInterfaceAttribute
+ ?? throw new NullReferenceException(
+ "_generateAutoInterfaceAttribute is null"
+ ),
+ out var attributes
+ )
+ )
+ {
+ continue;
+ }
+
+ if (
+ classSymbolNames.Contains(
+ implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)
+ )
+ )
+ {
+ continue; // partial class, already added
+ }
+
+ classSymbolNames.Add(implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true));
+
+ var attribute = attributes.Single();
+ var source = SourceText.From(
+ GenerateInterfaceCode(implTypeSymbol, attribute),
+ Encoding.UTF8
+ );
+
+ context.AddSource(
+ $"{implTypeSymbol.GetFullMetadataName(useNameWhenNotFound: true)}_AutoInterface.g.cs",
+ source
+ );
+ }
+ }
+
+ private static string InferVisibilityModifier(
+ ISymbol implTypeSymbol,
+ AttributeData attributeData
+ )
+ {
+ string? result = attributeData.GetNamedParamValue(Attributes.VisibilityModifierPropName);
+ if (!string.IsNullOrEmpty(result))
+ {
+ return result ?? throw new NullReferenceException("result is null");
+ }
+
+ return implTypeSymbol.DeclaredAccessibility switch
+ {
+ Accessibility.Public => "public",
+ _ => "internal",
+ };
+ }
+
+ private static string InferInterfaceName(ISymbol implTypeSymbol, AttributeData attributeData)
+ {
+ return attributeData.GetNamedParamValue(Attributes.InterfaceNamePropName)
+ ?? $"I{implTypeSymbol.Name}";
+ }
+
+ private string GenerateInterfaceCode(
+ INamedTypeSymbol implTypeSymbol,
+ AttributeData attributeData
+ )
+ {
+ using var stream = new MemoryStream();
+ var streamWriter = new StreamWriter(stream, Encoding.UTF8);
+ var codeWriter = new IndentedTextWriter(streamWriter, " ");
+
+ var namespaceName = implTypeSymbol.ContainingNamespace.ToDisplayString();
+ var interfaceName = InferInterfaceName(implTypeSymbol, attributeData);
+ var visibilityModifier = InferVisibilityModifier(implTypeSymbol, attributeData);
+
+ //https://stackoverflow.com/questions/55492214/the-annotation-for-nullable-reference-types-should-only-be-used-in-code-within-a fix for nullable
+
+ codeWriter.WriteLine("#nullable enable");
+ codeWriter.WriteLine("namespace {0}", namespaceName);
+ codeWriter.WriteLine("{");
+
+ ++codeWriter.Indent;
+ WriteSymbolDocsIfPresent(codeWriter, implTypeSymbol);
+ codeWriter.Write("{0} partial interface {1}", visibilityModifier, interfaceName);
+ WriteTypeGenericsIfNeeded(codeWriter, implTypeSymbol);
+ codeWriter.WriteLine();
+ codeWriter.WriteLine("{");
+
+ ++codeWriter.Indent;
+ GenerateInterfaceMemberDefinitions(codeWriter, implTypeSymbol);
+ --codeWriter.Indent;
+
+ codeWriter.WriteLine("}");
+ --codeWriter.Indent;
+
+ codeWriter.WriteLine("}");
+ codeWriter.WriteLine("#nullable restore");
+
+ codeWriter.Flush();
+ stream.Seek(0, SeekOrigin.Begin);
+ using var reader = new StreamReader(stream, Encoding.UTF8, true);
+ return reader.ReadToEnd();
+ }
+
+ private static void WriteTypeGenericsIfNeeded(
+ TextWriter writer,
+ INamedTypeSymbol implTypeSymbol
+ )
+ {
+ if (!implTypeSymbol.IsGenericType)
+ {
+ return;
+ }
+
+ writer.Write("<");
+ writer.WriteJoin(", ", implTypeSymbol.TypeParameters.Select(x => x.Name));
+ writer.Write(">");
+
+ WriteTypeParameterConstraints(writer, implTypeSymbol.TypeParameters);
+ }
+
+ private void GenerateInterfaceMemberDefinitions(
+ TextWriter writer,
+ INamedTypeSymbol implTypeSymbol
+ )
+ {
+ foreach (var member in implTypeSymbol.GetMembers())
+ {
+ if (
+ member.DeclaredAccessibility != Accessibility.Public
+ || member.HasAttribute(
+ _ignoreAttribute ?? throw new NullReferenceException("_ignoreAttribute is null")
+ )
+ )
+ {
+ continue;
+ }
+
+ GenerateInterfaceMemberDefinition(writer, implTypeSymbol, member);
+ }
+ }
+
+ private static void GenerateInterfaceMemberDefinition(
+ TextWriter writer,
+ INamedTypeSymbol owner,
+ ISymbol member
+ )
+ {
+ switch (member)
+ {
+ case IPropertySymbol propertySymbol:
+ GeneratePropertyDefinition(writer, propertySymbol);
+ break;
+ case IMethodSymbol methodSymbol:
+ GenerateMethodDefinition(writer, owner, methodSymbol);
+ break;
+ }
+ }
+
+ private static void WriteSymbolDocsIfPresent(TextWriter writer, ISymbol symbol)
+ {
+ var xml = symbol.GetDocumentationCommentXml();
+ if (string.IsNullOrWhiteSpace(xml))
+ {
+ return;
+ }
+
+ // omit the fist and last lines to skip the tag
+
+ var reader = new StringReader(xml);
+ var lines = new List();
+
+ while (true)
+ {
+ var line = reader.ReadLine();
+ if (line is null)
+ {
+ break;
+ }
+
+ lines.Add(line);
+ }
+
+ for (int i = 1; i < lines.Count - 1; i++)
+ {
+ var line = lines[i].TrimStart(); // for some reason, 4 spaces are inserted to the beginning of the line
+ writer.WriteLine("/// {0}", line);
+ }
+ }
+
+ private static bool IsPublicOrInternal(ISymbol symbol)
+ {
+ return symbol.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal;
+ }
+
+ private static void GeneratePropertyDefinition(
+ TextWriter writer,
+ IPropertySymbol propertySymbol
+ )
+ {
+ if (propertySymbol.IsStatic)
+ {
+ return;
+ }
+
+ bool hasPublicGetter =
+ propertySymbol.GetMethod is not null && IsPublicOrInternal(propertySymbol.GetMethod);
+
+ bool hasPublicSetter =
+ propertySymbol.SetMethod is not null && IsPublicOrInternal(propertySymbol.SetMethod);
+
+ if (!hasPublicGetter && !hasPublicSetter)
+ {
+ return;
+ }
+
+ WriteSymbolDocsIfPresent(writer, propertySymbol);
+
+ if (propertySymbol.IsIndexer)
+ {
+ writer.Write("{0} this[", propertySymbol.Type.GetNamespaceAndType());
+ writer.WriteJoin(", ", propertySymbol.Parameters, WriteMethodParam);
+ writer.Write("] ");
+ }
+ else
+ {
+ writer.Write("{0} {1} ", propertySymbol.Type, propertySymbol.Name); // ex. int Foo
+ }
+
+ writer.Write("{ ");
+
+ if (hasPublicGetter)
+ {
+ writer.Write("get; ");
+ }
+
+ if (hasPublicSetter)
+ {
+ if (propertySymbol.SetMethod?.IsInitOnly ?? false)
+ {
+ writer.Write("init; ");
+ }
+ else
+ {
+ writer.Write("set; ");
+ }
+ }
+
+ writer.WriteLine("}");
+ }
+
+ private static void GenerateMethodDefinition(
+ TextWriter writer,
+ INamedTypeSymbol owner,
+ IMethodSymbol methodSymbol
+ )
+ {
+ if (methodSymbol.MethodKind != MethodKind.Ordinary || methodSymbol.IsStatic)
+ {
+ return;
+ }
+
+ if (methodSymbol.IsImplicitlyDeclared && methodSymbol.Name != "Deconstruct")
+ {
+ // omit methods that are auto generated by the compiler (eg. record's methods),
+ // except for the record Deconstruct method
+ return;
+ }
+
+ WriteSymbolDocsIfPresent(writer, methodSymbol);
+
+ writer.Write("{0} {1}", methodSymbol.ReturnType.GetNamespaceAndType(), methodSymbol.Name); // ex. int Foo
+
+ if (methodSymbol.IsGenericMethod)
+ {
+ writer.Write("<");
+ writer.WriteJoin(
+ ", ",
+ methodSymbol.TypeParameters.Select(x => x.GetNamespaceAndType())
+ );
+ writer.Write(">");
+ }
+
+ writer.Write("(");
+ writer.WriteJoin(", ", methodSymbol.Parameters, WriteMethodParam);
+
+ writer.Write(")");
+
+ if (methodSymbol.IsGenericMethod)
+ {
+ WriteTypeParameterConstraints(writer, methodSymbol.TypeParameters);
+ }
+
+ writer.WriteLine(";");
+ }
+
+ private static void WriteMethodParam(TextWriter writer, IParameterSymbol param)
+ {
+ if (param.IsParams)
+ {
+ writer.Write("params ");
+ }
+
+ switch (param.RefKind)
+ {
+ case RefKind.Ref:
+ writer.Write("ref ");
+ break;
+ case RefKind.Out:
+ writer.Write("out ");
+ break;
+ case RefKind.In:
+ writer.Write("in ");
+ break;
+ }
+
+ writer.Write(param.Type.GetNamespaceAndType());
+ writer.Write(" ");
+
+ if (StringExtensions.IsCSharpKeyword(param.Name))
+ {
+ writer.Write("@");
+ }
+
+ writer.Write(param.Name);
+
+ if (param.HasExplicitDefaultValue)
+ {
+ WriteParamExplicitDefaultValue(writer, param);
+ }
+ }
+
+ private static void WriteParamExplicitDefaultValue(TextWriter writer, IParameterSymbol param)
+ {
+ if (param.ExplicitDefaultValue is null)
+ {
+ writer.Write(" = default");
+ }
+ else
+ {
+ switch (param.Type.Name)
+ {
+ case nameof(String):
+ writer.Write(" = \"{0}\"", param.ExplicitDefaultValue);
+ break;
+ case nameof(Single):
+ writer.Write(" = {0}f", param.ExplicitDefaultValue);
+ break;
+ case nameof(Double):
+ writer.Write(" = {0}d", param.ExplicitDefaultValue);
+ break;
+ case nameof(Decimal):
+ writer.Write(" = {0}m", param.ExplicitDefaultValue);
+ break;
+ case nameof(Boolean):
+ writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower());
+ break;
+ case nameof(Nullable):
+ writer.Write(" = {0}", param.ExplicitDefaultValue.ToString().ToLower());
+ break;
+ default:
+ writer.Write(" = {0}", param.ExplicitDefaultValue);
+ break;
+ }
+ }
+ }
+
+ private static void WriteTypeParameterConstraints(
+ TextWriter writer,
+ IEnumerable typeParameters
+ )
+ {
+ foreach (var typeParameter in typeParameters)
+ {
+ var constraints = typeParameter.EnumGenericConstraints().ToList();
+ if (constraints.Count == 0)
+ {
+ break;
+ }
+
+ writer.Write(" where {0} : ", typeParameter.Name);
+ writer.WriteJoin(", ", constraints);
+ }
+ }
+
+ private void InitAttributes(Compilation compilation)
+ {
+ _generateAutoInterfaceAttribute = compilation.GetTypeByMetadataName(
+ $"{Attributes.AttributesNamespace}.{Attributes.GenerateAutoInterfaceClassname}"
+ );
+
+ _ignoreAttribute = compilation.GetTypeByMetadataName(
+ $"{Attributes.AttributesNamespace}.{Attributes.AutoInterfaceIgnoreAttributeClassname}"
+ );
+ }
+
+ private static IEnumerable GetImplTypeSymbols(
+ Compilation compilation,
+ SyntaxReceiver receiver
+ )
+ {
+ return receiver
+ .CandidateTypes.Select(candidate => GetTypeSymbol(compilation, candidate))
+ .Where(x => x != null)
+ .Cast();
+ }
+
+ private static INamedTypeSymbol? GetTypeSymbol(Compilation compilation, SyntaxNode type)
+ {
+ var model = compilation.GetSemanticModel(type.SyntaxTree);
+ var typeSymbol = model.GetDeclaredSymbol(type);
+ return typeSymbol as INamedTypeSymbol;
+ }
+
+ private static Compilation GetCompilation(GeneratorExecutionContext context)
+ {
+ var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions;
+
+ var compilation = context.Compilation.AddSyntaxTrees(
+ CSharpSyntaxTree.ParseText(
+ SourceText.From(Attributes.AttributesSourceCode, Encoding.UTF8),
+ options
+ )
+ );
+
+ return compilation;
+ }
+}
diff --git a/Speckle.InterfaceGenerator/Speckle.InterfaceGenerator.csproj b/Speckle.InterfaceGenerator/Speckle.InterfaceGenerator.csproj
new file mode 100644
index 0000000..9577c28
--- /dev/null
+++ b/Speckle.InterfaceGenerator/Speckle.InterfaceGenerator.csproj
@@ -0,0 +1,39 @@
+
+
+ netstandard2.0
+ Latest
+ enable
+ 0.9.8
+ true
+ false
+ false
+ true
+
+ Speckle.InterfaceGenerator
+ true
+ true
+
+
+
+ Speckle.InterfaceGenerator
+ A source generator that creates interfaces from implementations
+ https://github.com/specklesystems/InterfaceGenerator
+ https://github.com/specklesystems/InterfaceGenerator/blob/master/LICENSE
+ https://github.com/specklesystems/InterfaceGenerator
+ git
+ readme.md
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/Speckle.InterfaceGenerator/StringExtensions.cs b/Speckle.InterfaceGenerator/StringExtensions.cs
new file mode 100644
index 0000000..02a2522
--- /dev/null
+++ b/Speckle.InterfaceGenerator/StringExtensions.cs
@@ -0,0 +1,124 @@
+namespace Speckle.InterfaceGenerator;
+
+internal static class StringExtensions
+{
+ public static bool IsCSharpKeyword(string? name)
+ {
+ switch (name)
+ {
+ case "abstract":
+ case "add":
+ case "alias":
+ case "as":
+ case "ascending":
+ case "async":
+ case "await":
+ case "base":
+ case "bool":
+ case "break":
+ case "by":
+ case "byte":
+ case "case":
+ case "catch":
+ case "char":
+ case "checked":
+ case "class":
+ case "const":
+ case "continue":
+ case "decimal":
+ case "default":
+ case "delegate":
+ case "descending":
+ case "do":
+ case "double":
+ case "dynamic":
+ case "else":
+ case "enum":
+ case "equals":
+ case "event":
+ case "explicit":
+ case "extern":
+ case "false":
+ case "finally":
+ case "fixed":
+ case "float":
+ case "for":
+ case "foreach":
+ case "from":
+ case "get":
+ case "global":
+ case "goto":
+ // `group` is a contextual to linq queries that we don't generate
+ //case "group":
+ case "if":
+ case "implicit":
+ case "in":
+ case "int":
+ case "interface":
+ case "internal":
+ case "into":
+ case "is":
+ case "join":
+ case "let":
+ case "lock":
+ case "long":
+ case "nameof":
+ case "namespace":
+ case "new":
+ case "null":
+ case "object":
+ case "on":
+ case "operator":
+ // `orderby` is a contextual to linq queries that we don't generate
+ //case "orderby":
+ case "out":
+ case "override":
+ case "params":
+ case "partial":
+ case "private":
+ case "protected":
+ case "public":
+ case "readonly":
+ case "ref":
+ case "remove":
+ case "return":
+ case "sbyte":
+ case "sealed":
+ // `select` is a contextual to linq queries that we don't generate
+ // case "select":
+ case "set":
+ case "short":
+ case "sizeof":
+ case "stackalloc":
+ case "static":
+ case "string":
+ case "struct":
+ case "switch":
+ case "this":
+ case "throw":
+ case "true":
+ case "try":
+ case "typeof":
+ case "uint":
+ case "ulong":
+ case "unchecked":
+ case "unmanaged":
+ case "unsafe":
+ case "ushort":
+ case "using":
+ // `value` is a contextual to getters that we don't generate
+ // case "value":
+ case "var":
+ case "virtual":
+ case "void":
+ case "volatile":
+ case "when":
+ case "where":
+ case "while":
+ case "yield":
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/Speckle.InterfaceGenerator/SymbolExtensions.cs b/Speckle.InterfaceGenerator/SymbolExtensions.cs
new file mode 100644
index 0000000..dc733ea
--- /dev/null
+++ b/Speckle.InterfaceGenerator/SymbolExtensions.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+
+namespace Speckle.InterfaceGenerator;
+
+internal static class SymbolExtensions
+{
+ public static string GetNamespaceAndType(this ITypeSymbol typeSymbol)
+ {
+ if (typeSymbol is ITypeParameterSymbol t)
+ {
+ return t.Name;
+ }
+ return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ }
+
+ public static bool TryGetAttribute(
+ this ISymbol symbol,
+ INamedTypeSymbol attributeType,
+ out IEnumerable attributes
+ )
+ {
+ attributes = symbol
+ .GetAttributes()
+ .Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
+ return attributes.Any();
+ }
+
+ public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
+ {
+ return symbol
+ .GetAttributes()
+ .Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
+ }
+
+ //Ref: https://stackoverflow.com/questions/27105909/get-fully-qualified-metadata-name-in-roslyn
+ public static string GetFullMetadataName(this ISymbol symbol, bool useNameWhenNotFound = false)
+ {
+ if (IsRootNamespace(symbol))
+ {
+ return useNameWhenNotFound ? symbol.Name : string.Empty;
+ }
+
+ var stringBuilder = new StringBuilder(symbol.MetadataName);
+ var last = symbol;
+
+ symbol = symbol.ContainingSymbol;
+
+ while (!IsRootNamespace(symbol))
+ {
+ if (symbol is ITypeSymbol && last is ITypeSymbol)
+ {
+ stringBuilder.Insert(0, '+');
+ }
+ else
+ {
+ stringBuilder.Insert(0, '.');
+ }
+
+ stringBuilder.Insert(
+ 0,
+ symbol.OriginalDefinition.ToDisplayString(
+ SymbolDisplayFormat.MinimallyQualifiedFormat
+ )
+ );
+ symbol = symbol.ContainingSymbol;
+ }
+
+ var retVal = stringBuilder.ToString();
+ if (string.IsNullOrWhiteSpace(retVal) && useNameWhenNotFound)
+ {
+ return symbol.Name;
+ }
+
+ return retVal;
+ }
+
+ private static bool IsRootNamespace(ISymbol symbol)
+ {
+ return symbol is INamespaceSymbol { IsGlobalNamespace: true };
+ }
+}
diff --git a/Speckle.InterfaceGenerator/SyntaxReceiver.cs b/Speckle.InterfaceGenerator/SyntaxReceiver.cs
new file mode 100644
index 0000000..7651dee
--- /dev/null
+++ b/Speckle.InterfaceGenerator/SyntaxReceiver.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Speckle.InterfaceGenerator;
+
+internal class SyntaxReceiver : ISyntaxReceiver
+{
+ public IList CandidateTypes { get; } = new List();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (
+ syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax
+ && IsClassOrRecord(typeDeclarationSyntax)
+ && typeDeclarationSyntax.AttributeLists.Count > 0
+ )
+ {
+ CandidateTypes.Add(typeDeclarationSyntax);
+ }
+ }
+
+ private static bool IsClassOrRecord(TypeDeclarationSyntax typeDeclarationSyntax)
+ {
+ return typeDeclarationSyntax is ClassDeclarationSyntax
+ || typeDeclarationSyntax is RecordDeclarationSyntax;
+ }
+}
diff --git a/Speckle.InterfaceGenerator/TextWriterExtensions.cs b/Speckle.InterfaceGenerator/TextWriterExtensions.cs
new file mode 100644
index 0000000..c2c5d94
--- /dev/null
+++ b/Speckle.InterfaceGenerator/TextWriterExtensions.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Speckle.InterfaceGenerator;
+
+internal static class TextWriterExtensions
+{
+ public static void WriteJoin(this TextWriter writer, string separator, IEnumerable values)
+ {
+ writer.WriteJoin(separator, values, (w, x) => w.Write(x));
+ }
+
+ public static void WriteJoin(
+ this TextWriter writer,
+ string separator,
+ IEnumerable values,
+ Action writeAction
+ )
+ {
+ using var enumerator = values.GetEnumerator();
+
+ if (!enumerator.MoveNext())
+ {
+ return;
+ }
+
+ writeAction(writer, enumerator.Current);
+
+ if (!enumerator.MoveNext())
+ {
+ return;
+ }
+
+ do
+ {
+ writer.Write(separator);
+ writeAction(writer, enumerator.Current);
+ } while (enumerator.MoveNext());
+ }
+}
diff --git a/Speckle.InterfaceGenerator/TypeParameterSymbolExtensions.cs b/Speckle.InterfaceGenerator/TypeParameterSymbolExtensions.cs
new file mode 100644
index 0000000..d33a8ba
--- /dev/null
+++ b/Speckle.InterfaceGenerator/TypeParameterSymbolExtensions.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+
+namespace Speckle.InterfaceGenerator;
+
+internal static class TypeParameterSymbolExtensions
+{
+ public static IEnumerable EnumGenericConstraints(this ITypeParameterSymbol symbol)
+ {
+ // the class/struct/unmanaged/notnull constraint has to be the last
+ if (symbol.HasNotNullConstraint)
+ {
+ yield return "notnull";
+ }
+
+ if (symbol.HasValueTypeConstraint)
+ {
+ yield return "struct";
+ }
+
+ if (symbol.HasUnmanagedTypeConstraint)
+ {
+ yield return "unmanaged";
+ }
+
+ if (symbol.HasReferenceTypeConstraint)
+ {
+ yield return symbol.ReferenceTypeConstraintNullableAnnotation
+ == NullableAnnotation.Annotated
+ ? "class?"
+ : "class";
+ }
+
+ // types go in the middle
+ foreach (var constraintType in symbol.ConstraintTypes)
+ {
+ yield return constraintType.ToDisplayString();
+ }
+
+ // the new() constraint has to be the last
+ if (symbol.HasConstructorConstraint)
+ {
+ yield return "new()";
+ }
+ }
+}
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..18b689d
--- /dev/null
+++ b/global.json
@@ -0,0 +1,7 @@
+{
+ "sdk": {
+ "version": "8.0.0",
+ "rollForward": "latestMinor",
+ "allowPrerelease": false
+ }
+}