rough outline
This commit is contained in:
parent
be0d5c08bf
commit
00c19845dd
34 changed files with 677 additions and 1074 deletions
295
.editorconfig
295
.editorconfig
|
@ -1,5 +1,4 @@
|
||||||
root = true
|
; EditorConfig to support per-solution formatting.
|
||||||
; EditorConfig to support per-solution formatting.
|
|
||||||
; Use the EditorConfig VS add-in to make this work.
|
; Use the EditorConfig VS add-in to make this work.
|
||||||
; http://editorconfig.org/
|
; http://editorconfig.org/
|
||||||
;
|
;
|
||||||
|
@ -12,11 +11,12 @@
|
||||||
; then the rule will be silently ignored.
|
; then the rule will be silently ignored.
|
||||||
|
|
||||||
; This is the default for the codeline.
|
; This is the default for the codeline.
|
||||||
|
root = true
|
||||||
|
|
||||||
; For disabling MA0048 and MA0051 in a specific folder, e.g., Migrations
|
; For disabling MA0048 and MA0051 in a specific folder, e.g., Migrations
|
||||||
[**/Migrations/*.cs]
|
[**/Migrations/*.cs]
|
||||||
dotnet_diagnostic.ma0048.severity = none
|
dotnet_diagnostic.MA0048.severity = none
|
||||||
dotnet_diagnostic.ma0051.severity = none
|
dotnet_diagnostic.MA0051.severity = none
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
@ -25,19 +25,14 @@ trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
file_header_template = Licensed to Timothy Schenk under the Apache 2.0 License.
|
file_header_template = Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||||
|
|
||||||
[*.{yaml,yml}]
|
[*.(yaml|yml)]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
# ReSharper properties
|
dotnet_diagnostic.MA0004.severity = none
|
||||||
resharper_csharp_wrap_arguments_style = chop_if_long
|
|
||||||
resharper_csharp_wrap_before_invocation_rpar = true
|
|
||||||
resharper_wrap_chained_binary_expressions = chop_if_long
|
|
||||||
resharper_wrap_chained_method_calls = chop_if_long
|
|
||||||
dotnet_diagnostic.ma0004.severity = none
|
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
dotnet_sort_system_directives_first = true
|
dotnet_sort_system_directives_first = true
|
||||||
dotnet_diagnostic.ma0007.severity = none
|
dotnet_diagnostic.MA0007.severity = none
|
||||||
#MA0004.report = DetectContext # (default) Try to detect the current context and report only if it considers ConfigureAwait is needed
|
#MA0004.report = DetectContext # (default) Try to detect the current context and report only if it considers ConfigureAwait is needed
|
||||||
# Don't use this. qualifier
|
# Don't use this. qualifier
|
||||||
dotnet_style_qualification_for_field = false:suggestion
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
|
@ -58,7 +53,7 @@ csharp_style_var_elsewhere = true:suggestion
|
||||||
csharp_style_throw_expression = false:suggestion
|
csharp_style_throw_expression = false:suggestion
|
||||||
|
|
||||||
# Newline settings
|
# Newline settings
|
||||||
csharp_new_line_before_open_brace = all
|
csharp_new_line_before_open_brace = none
|
||||||
csharp_new_line_before_else = true
|
csharp_new_line_before_else = true
|
||||||
csharp_new_line_before_catch = true
|
csharp_new_line_before_catch = true
|
||||||
csharp_new_line_before_finally = true
|
csharp_new_line_before_finally = true
|
||||||
|
@ -111,367 +106,367 @@ charset = utf-8-bom
|
||||||
[*.{cs,vb}]
|
[*.{cs,vb}]
|
||||||
|
|
||||||
# SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
|
# SYSLIB1054: Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
|
||||||
dotnet_diagnostic.syslib1054.severity = warning
|
dotnet_diagnostic.SYSLIB1054.severity = warning
|
||||||
|
|
||||||
# CA1018: Mark attributes with AttributeUsageAttribute
|
# CA1018: Mark attributes with AttributeUsageAttribute
|
||||||
dotnet_diagnostic.ca1018.severity = warning
|
dotnet_diagnostic.CA1018.severity = warning
|
||||||
|
|
||||||
# CA1047: Do not declare protected member in sealed type
|
# CA1047: Do not declare protected member in sealed type
|
||||||
dotnet_diagnostic.ca1047.severity = warning
|
dotnet_diagnostic.CA1047.severity = warning
|
||||||
|
|
||||||
# CA1305: Specify IFormatProvider
|
# CA1305: Specify IFormatProvider
|
||||||
dotnet_diagnostic.ca1305.severity = warning
|
dotnet_diagnostic.CA1305.severity = warning
|
||||||
|
|
||||||
# CA1507: Use nameof to express symbol names
|
# CA1507: Use nameof to express symbol names
|
||||||
dotnet_diagnostic.ca1507.severity = warning
|
dotnet_diagnostic.CA1507.severity = warning
|
||||||
|
|
||||||
# CA1510: Use ArgumentNullException throw helper
|
# CA1510: Use ArgumentNullException throw helper
|
||||||
dotnet_diagnostic.ca1510.severity = warning
|
dotnet_diagnostic.CA1510.severity = warning
|
||||||
|
|
||||||
# CA1511: Use ArgumentException throw helper
|
# CA1511: Use ArgumentException throw helper
|
||||||
dotnet_diagnostic.ca1511.severity = warning
|
dotnet_diagnostic.CA1511.severity = warning
|
||||||
|
|
||||||
# CA1512: Use ArgumentOutOfRangeException throw helper
|
# CA1512: Use ArgumentOutOfRangeException throw helper
|
||||||
dotnet_diagnostic.ca1512.severity = warning
|
dotnet_diagnostic.CA1512.severity = warning
|
||||||
|
|
||||||
# CA1513: Use ObjectDisposedException throw helper
|
# CA1513: Use ObjectDisposedException throw helper
|
||||||
dotnet_diagnostic.ca1513.severity = warning
|
dotnet_diagnostic.CA1513.severity = warning
|
||||||
|
|
||||||
# CA1725: Parameter names should match base declaration
|
# CA1725: Parameter names should match base declaration
|
||||||
dotnet_diagnostic.ca1725.severity = suggestion
|
dotnet_diagnostic.CA1725.severity = suggestion
|
||||||
|
|
||||||
# CA1802: Use literals where appropriate
|
# CA1802: Use literals where appropriate
|
||||||
dotnet_diagnostic.ca1802.severity = warning
|
dotnet_diagnostic.CA1802.severity = warning
|
||||||
|
|
||||||
# CA1805: Do not initialize unnecessarily
|
# CA1805: Do not initialize unnecessarily
|
||||||
dotnet_diagnostic.ca1805.severity = warning
|
dotnet_diagnostic.CA1805.severity = warning
|
||||||
|
|
||||||
# CA1810: Do not initialize unnecessarily
|
# CA1810: Do not initialize unnecessarily
|
||||||
dotnet_diagnostic.ca1810.severity = warning
|
dotnet_diagnostic.CA1810.severity = warning
|
||||||
|
|
||||||
# CA1821: Remove empty Finalizers
|
# CA1821: Remove empty Finalizers
|
||||||
dotnet_diagnostic.ca1821.severity = warning
|
dotnet_diagnostic.CA1821.severity = warning
|
||||||
|
|
||||||
# CA1822: Make member static
|
# CA1822: Make member static
|
||||||
dotnet_diagnostic.ca1822.severity = warning
|
dotnet_diagnostic.CA1822.severity = warning
|
||||||
dotnet_code_quality.ca1822.api_surface = private, internal
|
dotnet_code_quality.CA1822.api_surface = private, internal
|
||||||
|
|
||||||
# CA1823: Avoid unused private fields
|
# CA1823: Avoid unused private fields
|
||||||
dotnet_diagnostic.ca1823.severity = warning
|
dotnet_diagnostic.CA1823.severity = warning
|
||||||
|
|
||||||
# CA1825: Avoid zero-length array allocations
|
# CA1825: Avoid zero-length array allocations
|
||||||
dotnet_diagnostic.ca1825.severity = warning
|
dotnet_diagnostic.CA1825.severity = warning
|
||||||
|
|
||||||
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly
|
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly
|
||||||
dotnet_diagnostic.ca1826.severity = warning
|
dotnet_diagnostic.CA1826.severity = warning
|
||||||
|
|
||||||
# CA1827: Do not use Count() or LongCount() when Any() can be used
|
# CA1827: Do not use Count() or LongCount() when Any() can be used
|
||||||
dotnet_diagnostic.ca1827.severity = warning
|
dotnet_diagnostic.CA1827.severity = warning
|
||||||
|
|
||||||
# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used
|
# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used
|
||||||
dotnet_diagnostic.ca1828.severity = warning
|
dotnet_diagnostic.CA1828.severity = warning
|
||||||
|
|
||||||
# CA1829: Use Length/Count property instead of Count() when available
|
# CA1829: Use Length/Count property instead of Count() when available
|
||||||
dotnet_diagnostic.ca1829.severity = warning
|
dotnet_diagnostic.CA1829.severity = warning
|
||||||
|
|
||||||
# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder
|
# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder
|
||||||
dotnet_diagnostic.ca1830.severity = warning
|
dotnet_diagnostic.CA1830.severity = warning
|
||||||
|
|
||||||
# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
dotnet_diagnostic.ca1831.severity = warning
|
dotnet_diagnostic.CA1831.severity = warning
|
||||||
|
|
||||||
# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
dotnet_diagnostic.ca1832.severity = warning
|
dotnet_diagnostic.CA1832.severity = warning
|
||||||
|
|
||||||
# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
dotnet_diagnostic.ca1833.severity = warning
|
dotnet_diagnostic.CA1833.severity = warning
|
||||||
|
|
||||||
# CA1834: Consider using 'StringBuilder.Append(char)' when applicable
|
# CA1834: Consider using 'StringBuilder.Append(char)' when applicable
|
||||||
dotnet_diagnostic.ca1834.severity = warning
|
dotnet_diagnostic.CA1834.severity = warning
|
||||||
|
|
||||||
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
||||||
dotnet_diagnostic.ca1835.severity = warning
|
dotnet_diagnostic.CA1835.severity = warning
|
||||||
|
|
||||||
# CA1836: Prefer IsEmpty over Count
|
# CA1836: Prefer IsEmpty over Count
|
||||||
dotnet_diagnostic.ca1836.severity = warning
|
dotnet_diagnostic.CA1836.severity = warning
|
||||||
|
|
||||||
# CA1837: Use 'Environment.ProcessId'
|
# CA1837: Use 'Environment.ProcessId'
|
||||||
dotnet_diagnostic.ca1837.severity = warning
|
dotnet_diagnostic.CA1837.severity = warning
|
||||||
|
|
||||||
# CA1838: Avoid 'StringBuilder' parameters for P/Invokes
|
# CA1838: Avoid 'StringBuilder' parameters for P/Invokes
|
||||||
dotnet_diagnostic.ca1838.severity = warning
|
dotnet_diagnostic.CA1838.severity = warning
|
||||||
|
|
||||||
# CA1839: Use 'Environment.ProcessPath'
|
# CA1839: Use 'Environment.ProcessPath'
|
||||||
dotnet_diagnostic.ca1839.severity = warning
|
dotnet_diagnostic.CA1839.severity = warning
|
||||||
|
|
||||||
# CA1840: Use 'Environment.CurrentManagedThreadId'
|
# CA1840: Use 'Environment.CurrentManagedThreadId'
|
||||||
dotnet_diagnostic.ca1840.severity = warning
|
dotnet_diagnostic.CA1840.severity = warning
|
||||||
|
|
||||||
# CA1841: Prefer Dictionary.Contains methods
|
# CA1841: Prefer Dictionary.Contains methods
|
||||||
dotnet_diagnostic.ca1841.severity = warning
|
dotnet_diagnostic.CA1841.severity = warning
|
||||||
|
|
||||||
# CA1842: Do not use 'WhenAll' with a single task
|
# CA1842: Do not use 'WhenAll' with a single task
|
||||||
dotnet_diagnostic.ca1842.severity = warning
|
dotnet_diagnostic.CA1842.severity = warning
|
||||||
|
|
||||||
# CA1843: Do not use 'WaitAll' with a single task
|
# CA1843: Do not use 'WaitAll' with a single task
|
||||||
dotnet_diagnostic.ca1843.severity = warning
|
dotnet_diagnostic.CA1843.severity = warning
|
||||||
|
|
||||||
# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream'
|
# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream'
|
||||||
dotnet_diagnostic.ca1844.severity = warning
|
dotnet_diagnostic.CA1844.severity = warning
|
||||||
|
|
||||||
# CA1845: Use span-based 'string.Concat'
|
# CA1845: Use span-based 'string.Concat'
|
||||||
dotnet_diagnostic.ca1845.severity = warning
|
dotnet_diagnostic.CA1845.severity = warning
|
||||||
|
|
||||||
# CA1846: Prefer AsSpan over Substring
|
# CA1846: Prefer AsSpan over Substring
|
||||||
dotnet_diagnostic.ca1846.severity = warning
|
dotnet_diagnostic.CA1846.severity = warning
|
||||||
|
|
||||||
# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
|
# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
|
||||||
dotnet_diagnostic.ca1847.severity = warning
|
dotnet_diagnostic.CA1847.severity = warning
|
||||||
|
|
||||||
# CA1852: Seal internal types
|
# CA1852: Seal internal types
|
||||||
dotnet_diagnostic.ca1852.severity = warning
|
dotnet_diagnostic.CA1852.severity = warning
|
||||||
|
|
||||||
# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method
|
# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method
|
||||||
dotnet_diagnostic.ca1854.severity = warning
|
dotnet_diagnostic.CA1854.severity = warning
|
||||||
|
|
||||||
# CA1855: Prefer 'Clear' over 'Fill'
|
# CA1855: Prefer 'Clear' over 'Fill'
|
||||||
dotnet_diagnostic.ca1855.severity = warning
|
dotnet_diagnostic.CA1855.severity = warning
|
||||||
|
|
||||||
# CA1856: Incorrect usage of ConstantExpected attribute
|
# CA1856: Incorrect usage of ConstantExpected attribute
|
||||||
dotnet_diagnostic.ca1856.severity = error
|
dotnet_diagnostic.CA1856.severity = error
|
||||||
|
|
||||||
# CA1857: A constant is expected for the parameter
|
# CA1857: A constant is expected for the parameter
|
||||||
dotnet_diagnostic.ca1857.severity = warning
|
dotnet_diagnostic.CA1857.severity = warning
|
||||||
|
|
||||||
# CA1858: Use 'StartsWith' instead of 'IndexOf'
|
# CA1858: Use 'StartsWith' instead of 'IndexOf'
|
||||||
dotnet_diagnostic.ca1858.severity = warning
|
dotnet_diagnostic.CA1858.severity = warning
|
||||||
|
|
||||||
# CA2007: Consider calling ConfigureAwait on the awaited task
|
# CA2007: Consider calling ConfigureAwait on the awaited task
|
||||||
dotnet_diagnostic.ca2007.severity = none
|
dotnet_diagnostic.CA2007.severity = none
|
||||||
|
|
||||||
# CA2008: Do not create tasks without passing a TaskScheduler
|
# CA2008: Do not create tasks without passing a TaskScheduler
|
||||||
dotnet_diagnostic.ca2008.severity = warning
|
dotnet_diagnostic.CA2008.severity = warning
|
||||||
|
|
||||||
# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value
|
# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value
|
||||||
dotnet_diagnostic.ca2009.severity = warning
|
dotnet_diagnostic.CA2009.severity = warning
|
||||||
|
|
||||||
# CA2011: Avoid infinite recursion
|
# CA2011: Avoid infinite recursion
|
||||||
dotnet_diagnostic.ca2011.severity = warning
|
dotnet_diagnostic.CA2011.severity = warning
|
||||||
|
|
||||||
# CA2012: Use ValueTask correctly
|
# CA2012: Use ValueTask correctly
|
||||||
dotnet_diagnostic.ca2012.severity = warning
|
dotnet_diagnostic.CA2012.severity = warning
|
||||||
|
|
||||||
# CA2013: Do not use ReferenceEquals with value types
|
# CA2013: Do not use ReferenceEquals with value types
|
||||||
dotnet_diagnostic.ca2013.severity = warning
|
dotnet_diagnostic.CA2013.severity = warning
|
||||||
|
|
||||||
# CA2014: Do not use stackalloc in loops.
|
# CA2014: Do not use stackalloc in loops.
|
||||||
dotnet_diagnostic.ca2014.severity = warning
|
dotnet_diagnostic.CA2014.severity = warning
|
||||||
|
|
||||||
# CA2016: Forward the 'CancellationToken' parameter to methods that take one
|
# CA2016: Forward the 'CancellationToken' parameter to methods that take one
|
||||||
dotnet_diagnostic.ca2016.severity = warning
|
dotnet_diagnostic.CA2016.severity = warning
|
||||||
|
|
||||||
# CA2200: Rethrow to preserve stack details
|
# CA2200: Rethrow to preserve stack details
|
||||||
dotnet_diagnostic.ca2200.severity = warning
|
dotnet_diagnostic.CA2200.severity = warning
|
||||||
|
|
||||||
# CA2201: Do not raise reserved exception types
|
# CA2201: Do not raise reserved exception types
|
||||||
dotnet_diagnostic.ca2201.severity = warning
|
dotnet_diagnostic.CA2201.severity = warning
|
||||||
|
|
||||||
# CA2208: Instantiate argument exceptions correctly
|
# CA2208: Instantiate argument exceptions correctly
|
||||||
dotnet_diagnostic.ca2208.severity = warning
|
dotnet_diagnostic.CA2208.severity = warning
|
||||||
|
|
||||||
# CA2245: Do not assign a property to itself
|
# CA2245: Do not assign a property to itself
|
||||||
dotnet_diagnostic.ca2245.severity = warning
|
dotnet_diagnostic.CA2245.severity = warning
|
||||||
|
|
||||||
# CA2246: Assigning symbol and its member in the same statement
|
# CA2246: Assigning symbol and its member in the same statement
|
||||||
dotnet_diagnostic.ca2246.severity = warning
|
dotnet_diagnostic.CA2246.severity = warning
|
||||||
|
|
||||||
# CA2249: Use string.Contains instead of string.IndexOf to improve readability.
|
# CA2249: Use string.Contains instead of string.IndexOf to improve readability.
|
||||||
dotnet_diagnostic.ca2249.severity = warning
|
dotnet_diagnostic.CA2249.severity = warning
|
||||||
|
|
||||||
# IDE0005: Remove unnecessary usings
|
# IDE0005: Remove unnecessary usings
|
||||||
dotnet_diagnostic.ide0005.severity = warning
|
dotnet_diagnostic.IDE0005.severity = warning
|
||||||
|
|
||||||
# IDE0011: Curly braces to surround blocks of code
|
# IDE0011: Curly braces to surround blocks of code
|
||||||
dotnet_diagnostic.ide0011.severity = warning
|
dotnet_diagnostic.IDE0011.severity = warning
|
||||||
|
|
||||||
# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable)
|
# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable)
|
||||||
dotnet_diagnostic.ide0020.severity = warning
|
dotnet_diagnostic.IDE0020.severity = warning
|
||||||
|
|
||||||
# IDE0029: Use coalesce expression (non-nullable types)
|
# IDE0029: Use coalesce expression (non-nullable types)
|
||||||
dotnet_diagnostic.ide0029.severity = warning
|
dotnet_diagnostic.IDE0029.severity = warning
|
||||||
|
|
||||||
# IDE0030: Use coalesce expression (nullable types)
|
# IDE0030: Use coalesce expression (nullable types)
|
||||||
dotnet_diagnostic.ide0030.severity = warning
|
dotnet_diagnostic.IDE0030.severity = warning
|
||||||
|
|
||||||
# IDE0031: Use null propagation
|
# IDE0031: Use null propagation
|
||||||
dotnet_diagnostic.ide0031.severity = warning
|
dotnet_diagnostic.IDE0031.severity = warning
|
||||||
|
|
||||||
# IDE0035: Remove unreachable code
|
# IDE0035: Remove unreachable code
|
||||||
dotnet_diagnostic.ide0035.severity = warning
|
dotnet_diagnostic.IDE0035.severity = warning
|
||||||
|
|
||||||
# IDE0036: Order modifiers
|
# IDE0036: Order modifiers
|
||||||
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion
|
csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async:suggestion
|
||||||
dotnet_diagnostic.ide0036.severity = warning
|
dotnet_diagnostic.IDE0036.severity = warning
|
||||||
|
|
||||||
# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable)
|
# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable)
|
||||||
dotnet_diagnostic.ide0038.severity = warning
|
dotnet_diagnostic.IDE0038.severity = warning
|
||||||
|
|
||||||
# IDE0043: Format string contains invalid placeholder
|
# IDE0043: Format string contains invalid placeholder
|
||||||
dotnet_diagnostic.ide0043.severity = warning
|
dotnet_diagnostic.IDE0043.severity = warning
|
||||||
|
|
||||||
# IDE0044: Make field readonly
|
# IDE0044: Make field readonly
|
||||||
dotnet_diagnostic.ide0044.severity = warning
|
dotnet_diagnostic.IDE0044.severity = warning
|
||||||
|
|
||||||
# IDE0051: Remove unused private members
|
# IDE0051: Remove unused private members
|
||||||
dotnet_diagnostic.ide0051.severity = warning
|
dotnet_diagnostic.IDE0051.severity = warning
|
||||||
|
|
||||||
# IDE0055: All formatting rules
|
# IDE0055: All formatting rules
|
||||||
dotnet_diagnostic.ide0055.severity = suggestion
|
dotnet_diagnostic.IDE0055.severity = suggestion
|
||||||
|
|
||||||
# IDE0059: Unnecessary assignment to a value
|
# IDE0059: Unnecessary assignment to a value
|
||||||
dotnet_diagnostic.ide0059.severity = warning
|
dotnet_diagnostic.IDE0059.severity = warning
|
||||||
|
|
||||||
# IDE0060: Remove unused parameter
|
# IDE0060: Remove unused parameter
|
||||||
dotnet_code_quality_unused_parameters = non_public
|
dotnet_code_quality_unused_parameters = non_public
|
||||||
dotnet_diagnostic.ide0060.severity = warning
|
dotnet_diagnostic.IDE0060.severity = warning
|
||||||
|
|
||||||
# IDE0062: Make local function static
|
# IDE0062: Make local function static
|
||||||
dotnet_diagnostic.ide0062.severity = warning
|
dotnet_diagnostic.IDE0062.severity = warning
|
||||||
|
|
||||||
# IDE0073: File header
|
# IDE0073: File header
|
||||||
dotnet_diagnostic.ide0073.severity = warning
|
dotnet_diagnostic.IDE0073.severity = warning
|
||||||
# Not a dotnet foundation project
|
# Not a dotnet foundation project
|
||||||
#file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
|
#file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
# IDE0161: Convert to file-scoped namespace
|
# IDE0161: Convert to file-scoped namespace
|
||||||
dotnet_diagnostic.ide0161.severity = warning
|
dotnet_diagnostic.IDE0161.severity = warning
|
||||||
|
|
||||||
# IDE0200: Lambda expression can be removed
|
# IDE0200: Lambda expression can be removed
|
||||||
dotnet_diagnostic.ide0200.severity = warning
|
dotnet_diagnostic.IDE0200.severity = warning
|
||||||
|
|
||||||
# IDE2000: Disallow multiple blank lines
|
# IDE2000: Disallow multiple blank lines
|
||||||
dotnet_style_allow_multiple_blank_lines_experimental = false
|
dotnet_style_allow_multiple_blank_lines_experimental = false
|
||||||
dotnet_diagnostic.ide2000.severity = warning
|
dotnet_diagnostic.IDE2000.severity = warning
|
||||||
|
|
||||||
[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,benchmarkapps,scripts,stress}/**.cs,src/Hosting/Server.IntegrationTesting/**.cs,src/Servers/IIS/IntegrationTesting.IIS/**.cs,src/Shared/Http2cat/**.cs,src/Testing/**.cs}]
|
[{eng/tools/**.cs,**/{test,testassets,samples,Samples,perf,benchmarkapps,scripts,stress}/**.cs,src/Hosting/Server.IntegrationTesting/**.cs,src/Servers/IIS/IntegrationTesting.IIS/**.cs,src/Shared/Http2cat/**.cs,src/Testing/**.cs}]
|
||||||
# CA1018: Mark attributes with AttributeUsageAttribute
|
# CA1018: Mark attributes with AttributeUsageAttribute
|
||||||
dotnet_diagnostic.ca1018.severity = suggestion
|
dotnet_diagnostic.CA1018.severity = suggestion
|
||||||
# CA1507: Use nameof to express symbol names
|
# CA1507: Use nameof to express symbol names
|
||||||
dotnet_diagnostic.ca1507.severity = suggestion
|
dotnet_diagnostic.CA1507.severity = suggestion
|
||||||
# CA1510: Use ArgumentNullException throw helper
|
# CA1510: Use ArgumentNullException throw helper
|
||||||
dotnet_diagnostic.ca1510.severity = suggestion
|
dotnet_diagnostic.CA1510.severity = suggestion
|
||||||
# CA1511: Use ArgumentException throw helper
|
# CA1511: Use ArgumentException throw helper
|
||||||
dotnet_diagnostic.ca1511.severity = suggestion
|
dotnet_diagnostic.CA1511.severity = suggestion
|
||||||
# CA1512: Use ArgumentOutOfRangeException throw helper
|
# CA1512: Use ArgumentOutOfRangeException throw helper
|
||||||
dotnet_diagnostic.ca1512.severity = suggestion
|
dotnet_diagnostic.CA1512.severity = suggestion
|
||||||
# CA1513: Use ObjectDisposedException throw helper
|
# CA1513: Use ObjectDisposedException throw helper
|
||||||
dotnet_diagnostic.ca1513.severity = suggestion
|
dotnet_diagnostic.CA1513.severity = suggestion
|
||||||
# CA1802: Use literals where appropriate
|
# CA1802: Use literals where appropriate
|
||||||
dotnet_diagnostic.ca1802.severity = suggestion
|
dotnet_diagnostic.CA1802.severity = suggestion
|
||||||
# CA1805: Do not initialize unnecessarily
|
# CA1805: Do not initialize unnecessarily
|
||||||
dotnet_diagnostic.ca1805.severity = suggestion
|
dotnet_diagnostic.CA1805.severity = suggestion
|
||||||
# CA1810: Do not initialize unnecessarily
|
# CA1810: Do not initialize unnecessarily
|
||||||
dotnet_diagnostic.ca1810.severity = suggestion
|
dotnet_diagnostic.CA1810.severity = suggestion
|
||||||
# CA1822: Make member static
|
# CA1822: Make member static
|
||||||
dotnet_diagnostic.ca1822.severity = suggestion
|
dotnet_diagnostic.CA1822.severity = suggestion
|
||||||
# CA1823: Avoid zero-length array allocations
|
# CA1823: Avoid zero-length array allocations
|
||||||
dotnet_diagnostic.ca1825.severity = suggestion
|
dotnet_diagnostic.CA1825.severity = suggestion
|
||||||
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly
|
# CA1826: Do not use Enumerable methods on indexable collections. Instead use the collection directly
|
||||||
dotnet_diagnostic.ca1826.severity = suggestion
|
dotnet_diagnostic.CA1826.severity = suggestion
|
||||||
# CA1827: Do not use Count() or LongCount() when Any() can be used
|
# CA1827: Do not use Count() or LongCount() when Any() can be used
|
||||||
dotnet_diagnostic.ca1827.severity = suggestion
|
dotnet_diagnostic.CA1827.severity = suggestion
|
||||||
# CA1829: Use Length/Count property instead of Count() when available
|
# CA1829: Use Length/Count property instead of Count() when available
|
||||||
dotnet_diagnostic.ca1829.severity = suggestion
|
dotnet_diagnostic.CA1829.severity = suggestion
|
||||||
# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
dotnet_diagnostic.ca1831.severity = suggestion
|
dotnet_diagnostic.CA1831.severity = suggestion
|
||||||
# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
dotnet_diagnostic.ca1832.severity = suggestion
|
dotnet_diagnostic.CA1832.severity = suggestion
|
||||||
# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate
|
||||||
dotnet_diagnostic.ca1833.severity = suggestion
|
dotnet_diagnostic.CA1833.severity = suggestion
|
||||||
# CA1834: Consider using 'StringBuilder.Append(char)' when applicable
|
# CA1834: Consider using 'StringBuilder.Append(char)' when applicable
|
||||||
dotnet_diagnostic.ca1834.severity = suggestion
|
dotnet_diagnostic.CA1834.severity = suggestion
|
||||||
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
||||||
dotnet_diagnostic.ca1835.severity = suggestion
|
dotnet_diagnostic.CA1835.severity = suggestion
|
||||||
# CA1837: Use 'Environment.ProcessId'
|
# CA1837: Use 'Environment.ProcessId'
|
||||||
dotnet_diagnostic.ca1837.severity = suggestion
|
dotnet_diagnostic.CA1837.severity = suggestion
|
||||||
# CA1838: Avoid 'StringBuilder' parameters for P/Invokes
|
# CA1838: Avoid 'StringBuilder' parameters for P/Invokes
|
||||||
dotnet_diagnostic.ca1838.severity = suggestion
|
dotnet_diagnostic.CA1838.severity = suggestion
|
||||||
# CA1841: Prefer Dictionary.Contains methods
|
# CA1841: Prefer Dictionary.Contains methods
|
||||||
dotnet_diagnostic.ca1841.severity = suggestion
|
dotnet_diagnostic.CA1841.severity = suggestion
|
||||||
# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream'
|
# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream'
|
||||||
dotnet_diagnostic.ca1844.severity = suggestion
|
dotnet_diagnostic.CA1844.severity = suggestion
|
||||||
# CA1845: Use span-based 'string.Concat'
|
# CA1845: Use span-based 'string.Concat'
|
||||||
dotnet_diagnostic.ca1845.severity = suggestion
|
dotnet_diagnostic.CA1845.severity = suggestion
|
||||||
# CA1846: Prefer AsSpan over Substring
|
# CA1846: Prefer AsSpan over Substring
|
||||||
dotnet_diagnostic.ca1846.severity = suggestion
|
dotnet_diagnostic.CA1846.severity = suggestion
|
||||||
# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
|
# CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters
|
||||||
dotnet_diagnostic.ca1847.severity = suggestion
|
dotnet_diagnostic.CA1847.severity = suggestion
|
||||||
# CA1852: Seal internal types
|
# CA1852: Seal internal types
|
||||||
dotnet_diagnostic.ca1852.severity = suggestion
|
dotnet_diagnostic.CA1852.severity = suggestion
|
||||||
# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method
|
# CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method
|
||||||
dotnet_diagnostic.ca1854.severity = suggestion
|
dotnet_diagnostic.CA1854.severity = suggestion
|
||||||
# CA1855: Prefer 'Clear' over 'Fill'
|
# CA1855: Prefer 'Clear' over 'Fill'
|
||||||
dotnet_diagnostic.ca1855.severity = suggestion
|
dotnet_diagnostic.CA1855.severity = suggestion
|
||||||
# CA1856: Incorrect usage of ConstantExpected attribute
|
# CA1856: Incorrect usage of ConstantExpected attribute
|
||||||
dotnet_diagnostic.ca1856.severity = suggestion
|
dotnet_diagnostic.CA1856.severity = suggestion
|
||||||
# CA1857: A constant is expected for the parameter
|
# CA1857: A constant is expected for the parameter
|
||||||
dotnet_diagnostic.ca1857.severity = suggestion
|
dotnet_diagnostic.CA1857.severity = suggestion
|
||||||
# CA1858: Use 'StartsWith' instead of 'IndexOf'
|
# CA1858: Use 'StartsWith' instead of 'IndexOf'
|
||||||
dotnet_diagnostic.ca1858.severity = suggestion
|
dotnet_diagnostic.CA1858.severity = suggestion
|
||||||
# CA2007: Consider calling ConfigureAwait on the awaited task
|
# CA2007: Consider calling ConfigureAwait on the awaited task
|
||||||
dotnet_diagnostic.ca2007.severity = suggestion
|
dotnet_diagnostic.CA2007.severity = suggestion
|
||||||
# CA2008: Do not create tasks without passing a TaskScheduler
|
# CA2008: Do not create tasks without passing a TaskScheduler
|
||||||
dotnet_diagnostic.ca2008.severity = suggestion
|
dotnet_diagnostic.CA2008.severity = suggestion
|
||||||
# CA2012: Use ValueTask correctly
|
# CA2012: Use ValueTask correctly
|
||||||
dotnet_diagnostic.ca2012.severity = suggestion
|
dotnet_diagnostic.CA2012.severity = suggestion
|
||||||
# CA2201: Do not raise reserved exception types
|
# CA2201: Do not raise reserved exception types
|
||||||
dotnet_diagnostic.ca2201.severity = suggestion
|
dotnet_diagnostic.CA2201.severity = suggestion
|
||||||
# CA2249: Use string.Contains instead of string.IndexOf to improve readability.
|
# CA2249: Use string.Contains instead of string.IndexOf to improve readability.
|
||||||
dotnet_diagnostic.ca2249.severity = suggestion
|
dotnet_diagnostic.CA2249.severity = suggestion
|
||||||
# IDE0005: Remove unnecessary usings
|
# IDE0005: Remove unnecessary usings
|
||||||
dotnet_diagnostic.ide0005.severity = suggestion
|
dotnet_diagnostic.IDE0005.severity = suggestion
|
||||||
# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable)
|
# IDE0020: Use pattern matching to avoid is check followed by a cast (with variable)
|
||||||
dotnet_diagnostic.ide0020.severity = suggestion
|
dotnet_diagnostic.IDE0020.severity = suggestion
|
||||||
# IDE0029: Use coalesce expression (non-nullable types)
|
# IDE0029: Use coalesce expression (non-nullable types)
|
||||||
dotnet_diagnostic.ide0029.severity = suggestion
|
dotnet_diagnostic.IDE0029.severity = suggestion
|
||||||
# IDE0030: Use coalesce expression (nullable types)
|
# IDE0030: Use coalesce expression (nullable types)
|
||||||
dotnet_diagnostic.ide0030.severity = suggestion
|
dotnet_diagnostic.IDE0030.severity = suggestion
|
||||||
# IDE0031: Use null propagation
|
# IDE0031: Use null propagation
|
||||||
dotnet_diagnostic.ide0031.severity = suggestion
|
dotnet_diagnostic.IDE0031.severity = suggestion
|
||||||
# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable)
|
# IDE0038: Use pattern matching to avoid is check followed by a cast (without variable)
|
||||||
dotnet_diagnostic.ide0038.severity = suggestion
|
dotnet_diagnostic.IDE0038.severity = suggestion
|
||||||
# IDE0044: Make field readonly
|
# IDE0044: Make field readonly
|
||||||
dotnet_diagnostic.ide0044.severity = suggestion
|
dotnet_diagnostic.IDE0044.severity = suggestion
|
||||||
# IDE0051: Remove unused private members
|
# IDE0051: Remove unused private members
|
||||||
dotnet_diagnostic.ide0051.severity = suggestion
|
dotnet_diagnostic.IDE0051.severity = suggestion
|
||||||
# IDE0059: Unnecessary assignment to a value
|
# IDE0059: Unnecessary assignment to a value
|
||||||
dotnet_diagnostic.ide0059.severity = suggestion
|
dotnet_diagnostic.IDE0059.severity = suggestion
|
||||||
# IDE0060: Remove unused parameters
|
# IDE0060: Remove unused parameters
|
||||||
dotnet_diagnostic.ide0060.severity = suggestion
|
dotnet_diagnostic.IDE0060.severity = suggestion
|
||||||
# IDE0062: Make local function static
|
# IDE0062: Make local function static
|
||||||
dotnet_diagnostic.ide0062.severity = suggestion
|
dotnet_diagnostic.IDE0062.severity = suggestion
|
||||||
# IDE0200: Lambda expression can be removed
|
# IDE0200: Lambda expression can be removed
|
||||||
dotnet_diagnostic.ide0200.severity = suggestion
|
dotnet_diagnostic.IDE0200.severity = suggestion
|
||||||
|
|
||||||
# CA2016: Forward the 'CancellationToken' parameter to methods that take one
|
# CA2016: Forward the 'CancellationToken' parameter to methods that take one
|
||||||
dotnet_diagnostic.ca2016.severity = suggestion
|
dotnet_diagnostic.CA2016.severity = suggestion
|
||||||
|
|
||||||
# Defaults for content in the shared src/ and shared runtime dir
|
# Defaults for content in the shared src/ and shared runtime dir
|
||||||
|
|
||||||
[{**/Shared/runtime/**.{cs,vb},src/Shared/test/Shared.Tests/runtime/**.{cs,vb},**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}]
|
[{**/Shared/runtime/**.{cs,vb},src/Shared/test/Shared.Tests/runtime/**.{cs,vb},**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}]
|
||||||
# CA1822: Make member static
|
# CA1822: Make member static
|
||||||
dotnet_diagnostic.ca1822.severity = silent
|
dotnet_diagnostic.CA1822.severity = silent
|
||||||
# IDE0011: Use braces
|
# IDE0011: Use braces
|
||||||
dotnet_diagnostic.ide0011.severity = silent
|
dotnet_diagnostic.IDE0011.severity = silent
|
||||||
# IDE0055: Fix formatting
|
# IDE0055: Fix formatting
|
||||||
dotnet_diagnostic.ide0055.severity = silent
|
dotnet_diagnostic.IDE0055.severity = silent
|
||||||
# IDE0060: Remove unused parameters
|
# IDE0060: Remove unused parameters
|
||||||
dotnet_diagnostic.ide0060.severity = silent
|
dotnet_diagnostic.IDE0060.severity = silent
|
||||||
# IDE0062: Make local function static
|
# IDE0062: Make local function static
|
||||||
dotnet_diagnostic.ide0062.severity = silent
|
dotnet_diagnostic.IDE0062.severity = silent
|
||||||
# IDE0161: Convert to file-scoped namespace
|
# IDE0161: Convert to file-scoped namespace
|
||||||
dotnet_diagnostic.ide0161.severity = silent
|
dotnet_diagnostic.IDE0161.severity = silent
|
||||||
|
|
||||||
[{**/Shared/**.cs,**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}]
|
[{**/Shared/**.cs,**/microsoft.extensions.hostfactoryresolver.sources/**.{cs,vb}}]
|
||||||
# IDE0005: Remove unused usings. Ignore for shared src files since imports for those depend on the projects in which they are included.
|
# IDE0005: Remove unused usings. Ignore for shared src files since imports for those depend on the projects in which they are included.
|
||||||
dotnet_diagnostic.ide0005.severity = silent
|
dotnet_diagnostic.IDE0005.severity = silent
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PacketMediator.Generator.Sample;
|
||||||
|
|
||||||
|
public enum OperationCode {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
E = 10,
|
||||||
|
F,
|
||||||
|
G,
|
||||||
|
H = 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
[GamePacketId(OperationCode.A)]
|
||||||
|
public struct StructA : IIncomingPacket {
|
||||||
|
public void Deserialize(byte[] data) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GamePacketId(OperationCode.B)]
|
||||||
|
public struct StructB : IPacket {
|
||||||
|
}
|
||||||
|
[GamePacketId(OperationCode.E)]
|
||||||
|
public struct StructC : IPacket {
|
||||||
|
}
|
||||||
|
[GamePacketId(OperationCode.F)]
|
||||||
|
public struct StructD : IPacket {
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
|
||||||
|
public class GamePacketIdAttribute : PacketIdAttribute<OperationCode> {
|
||||||
|
public GamePacketIdAttribute(OperationCode code) : base(code) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RandomSession {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HandlerA : IPacketHandler<StructA, RandomSession> {
|
||||||
|
public async Task HandleAsync(StructA packet, RandomSession session, CancellationToken cancellationToken) {
|
||||||
|
await Task.Delay(2000, cancellationToken);
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||||
|
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
|
||||||
|
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>PacketMediator.Generator.Sample</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\RaiNote.PacketMediator\RaiNote.PacketMediator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -1,26 +1,27 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
<RootNamespace>SourceGenerators1.Tests</RootNamespace>
|
<RootNamespace>PacketMediator.Generator.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.1"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2"/>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\SourceGenerators1\SourceGenerators1.csproj"/>
|
<ProjectReference Include="..\PacketMediator.Generator\PacketMediator.Generator.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using SourceGenerators1.Tests.Utils;
|
using Microsoft.CodeAnalysis;
|
||||||
|
using PacketMediator.Generator.Tests.Utils;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace SourceGenerators1.Tests;
|
namespace PacketMediator.Generator.Tests;
|
||||||
|
|
||||||
public class SampleSourceGeneratorTests
|
public class SourceGeneratorWithAdditionalFilesTests
|
||||||
{
|
{
|
||||||
private const string DddRegistryText = @"User
|
private const string DddRegistryText = @"User
|
||||||
Document
|
Document
|
||||||
|
@ -16,19 +18,20 @@ Customer";
|
||||||
public void GenerateClassesBasedOnDDDRegistry()
|
public void GenerateClassesBasedOnDDDRegistry()
|
||||||
{
|
{
|
||||||
// Create an instance of the source generator.
|
// Create an instance of the source generator.
|
||||||
var generator = new SampleSourceGenerator();
|
var generator = new SourceGeneratorWithAdditionalFiles();
|
||||||
|
|
||||||
// Source generators should be tested using 'GeneratorDriver'.
|
// Source generators should be tested using 'GeneratorDriver'.
|
||||||
var driver = CSharpGeneratorDriver.Create(new[] { generator },
|
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
|
||||||
new[]
|
|
||||||
{
|
// Add the additional file separately from the compilation.
|
||||||
// Add the additional file separately from the compilation.
|
driver = driver.AddAdditionalTexts(
|
||||||
|
ImmutableArray.Create<AdditionalText>(
|
||||||
new TestAdditionalFile("./DDD.UbiquitousLanguageRegistry.txt", DddRegistryText)
|
new TestAdditionalFile("./DDD.UbiquitousLanguageRegistry.txt", DddRegistryText)
|
||||||
}
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// To run generators, we can use an empty compilation.
|
// To run generators, we can use an empty compilation.
|
||||||
var compilation = CSharpCompilation.Create(nameof(SampleSourceGeneratorTests));
|
var compilation = CSharpCompilation.Create(nameof(SourceGeneratorWithAdditionalFilesTests));
|
||||||
|
|
||||||
// Run generators. Don't forget to use the new compilation rather than the previous one.
|
// Run generators. Don't forget to use the new compilation rather than the previous one.
|
||||||
driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
|
driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
|
|
@ -3,9 +3,9 @@ using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace SourceGenerators1.Tests;
|
namespace PacketMediator.Generator.Tests;
|
||||||
|
|
||||||
public class SampleIncrementalSourceGeneratorTests
|
public class SourceGeneratorWithAttributesTests
|
||||||
{
|
{
|
||||||
private const string VectorClassText = @"
|
private const string VectorClassText = @"
|
||||||
namespace TestNamespace;
|
namespace TestNamespace;
|
||||||
|
@ -40,13 +40,13 @@ partial class Vector3
|
||||||
public void GenerateReportMethod()
|
public void GenerateReportMethod()
|
||||||
{
|
{
|
||||||
// Create an instance of the source generator.
|
// Create an instance of the source generator.
|
||||||
var generator = new SampleIncrementalSourceGenerator();
|
var generator = new SourceGeneratorWithAttributes();
|
||||||
|
|
||||||
// Source generators should be tested using 'GeneratorDriver'.
|
// Source generators should be tested using 'GeneratorDriver'.
|
||||||
var driver = CSharpGeneratorDriver.Create(generator);
|
var driver = CSharpGeneratorDriver.Create(generator);
|
||||||
|
|
||||||
// We need to create a compilation with the required source code.
|
// We need to create a compilation with the required source code.
|
||||||
var compilation = CSharpCompilation.Create(nameof(SampleSourceGeneratorTests),
|
var compilation = CSharpCompilation.Create(nameof(SourceGeneratorWithAdditionalFilesTests),
|
||||||
new[] { CSharpSyntaxTree.ParseText(VectorClassText) },
|
new[] { CSharpSyntaxTree.ParseText(VectorClassText) },
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
|
@ -2,7 +2,7 @@ using System.Threading;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.Text;
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
namespace SourceGenerators1.Tests.Utils;
|
namespace PacketMediator.Generator.Tests.Utils;
|
||||||
|
|
||||||
public class TestAdditionalFile : AdditionalText
|
public class TestAdditionalFile : AdditionalText
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
@ -9,22 +9,16 @@
|
||||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
<IsRoslynComponent>true</IsRoslynComponent>
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
|
||||||
<RootNamespace>SourceGenerators1</RootNamespace>
|
<RootNamespace>PacketMediator.Generator</RootNamespace>
|
||||||
<PackageId>SourceGenerators1</PackageId>
|
<PackageId>PacketMediator.Generator</PackageId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.0"/>
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.3.0"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="test.snippet.cs" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace PacketMediator.Generator;
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
public class PacketMediatorGenerator : IIncrementalGenerator {
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context) {
|
||||||
|
context.RegisterPostInitializationOutput(ctx => {
|
||||||
|
ctx.AddSource("PacketMediatorStatic.g.cs", SourceText.From(@"
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace RaiNote.PacketMediator;
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
|
||||||
|
public abstract class PacketIdAttribute<TPacketIdEnum> : Attribute where TPacketIdEnum : Enum
|
||||||
|
{
|
||||||
|
protected PacketIdAttribute(TPacketIdEnum code)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TPacketIdEnum Code { get; }
|
||||||
|
}
|
||||||
|
public interface IPacket;
|
||||||
|
public interface IIncomingPacket : IPacket
|
||||||
|
{
|
||||||
|
public void Deserialize(byte[] data);
|
||||||
|
}
|
||||||
|
public interface IOutgoingPacket : IPacket
|
||||||
|
{
|
||||||
|
public byte[] Serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IBidirectionalPacket : IOutgoingPacket, IIncomingPacket;
|
||||||
|
|
||||||
|
public interface IPacketHandler<in TIncomingPacket, in TSession> : IPacketHandler<TSession>
|
||||||
|
where TIncomingPacket : IIncomingPacket
|
||||||
|
{
|
||||||
|
async Task<bool> IPacketHandler<TSession>.TryHandleAsync(IIncomingPacket packet, TSession session,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (packet is not TIncomingPacket tPacket)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var activity = new ActivitySource(nameof(PacketMediator)).StartActivity(nameof(HandleAsync));
|
||||||
|
activity?.AddTag(""Handler"", ToString());
|
||||||
|
activity?.AddTag(""Packet"", packet.ToString());
|
||||||
|
await HandleAsync(tPacket, session, cancellationToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||||
|
public Task HandleAsync(TIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPacketHandler<in TSession>
|
||||||
|
{
|
||||||
|
Task<bool> TryHandleAsync(IIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
", Encoding.UTF8));
|
||||||
|
});
|
||||||
|
// Find all struct declarations
|
||||||
|
var structsWithAttributes = context.SyntaxProvider
|
||||||
|
.CreateSyntaxProvider(
|
||||||
|
predicate: (node, _) => node is StructDeclarationSyntax structSyntax &&
|
||||||
|
structSyntax.AttributeLists.Count > 0,
|
||||||
|
transform: (syntaxContext, _) => {
|
||||||
|
var structDeclaration = (StructDeclarationSyntax)syntaxContext.Node;
|
||||||
|
var model = syntaxContext.SemanticModel;
|
||||||
|
var symbol = model.GetDeclaredSymbol(structDeclaration) as INamedTypeSymbol;
|
||||||
|
var requiredInterfaces = new[] { "IPacket", "IIncomingPacket", "IOutgoingPacket", "IBidirectionalPacket" };
|
||||||
|
var implementsInterface = symbol != null && symbol.AllInterfaces
|
||||||
|
.Any(i => requiredInterfaces.Contains(i.Name));
|
||||||
|
// Check for the marker attribute
|
||||||
|
var attribute = symbol?.GetAttributes()
|
||||||
|
.FirstOrDefault(attr =>
|
||||||
|
{
|
||||||
|
var attrClass = attr.AttributeClass;
|
||||||
|
while (attrClass != null)
|
||||||
|
{
|
||||||
|
if (attrClass.Name == "PacketIdAttribute" && attrClass.ContainingNamespace.ToDisplayString() == "PacketMediator.Generator")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
attrClass = attrClass.BaseType;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (attribute == null) {
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeConstructorArgument = attribute.ConstructorArguments[0];
|
||||||
|
var enumType = attributeConstructorArgument.Type;
|
||||||
|
var enumValue = attributeConstructorArgument.Value;
|
||||||
|
var enumMember = enumType?.GetMembers()
|
||||||
|
.OfType<IFieldSymbol>()
|
||||||
|
.FirstOrDefault(f => f.ConstantValue?.Equals(enumValue) == true);
|
||||||
|
var enumMaxValue = enumType?.GetMembers()
|
||||||
|
.OfType<IFieldSymbol>().Max(x => x.ConstantValue);
|
||||||
|
return (symbol, structDeclaration.Identifier.Text, value: enumValue,enumType,enumMember,enumMaxValue, implementsInterface);
|
||||||
|
|
||||||
|
})
|
||||||
|
.Where(result => result != default);
|
||||||
|
|
||||||
|
// Collect and generate the dictionary
|
||||||
|
context.RegisterSourceOutput(structsWithAttributes.Collect(), (ctx, result) => {
|
||||||
|
var usedValues = new List<long>();
|
||||||
|
var highestValue = long.Parse(result.FirstOrDefault().enumMaxValue?.ToString() ?? throw new InvalidOperationException());
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine("using System.Collections.Generic;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("public static class StructDictionary");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
var enumTypeString = result.FirstOrDefault().enumType?.ToDisplayString();
|
||||||
|
sb.AppendLine($" public static readonly Dictionary<string, {enumTypeString}> Values = new()");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
|
||||||
|
foreach (var (symbol, structName, value, _,enumMember,_, implementsInterface) in result) {
|
||||||
|
if (!implementsInterface) {
|
||||||
|
var diagnostic = Diagnostic.Create(
|
||||||
|
new DiagnosticDescriptor(
|
||||||
|
id: "MYGEN001",
|
||||||
|
title: "Struct does not implement required interface",
|
||||||
|
messageFormat: $"The struct '{{0}}' must implement at least one of: \"IPacket\", \"IIncomingPacket\", \"IOutgoingPacket\", \"IBidirectionalPacket\" ",
|
||||||
|
category: "SourceGenerator",
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true),
|
||||||
|
symbol?.Locations.FirstOrDefault(), structName);
|
||||||
|
|
||||||
|
ctx.ReportDiagnostic(diagnostic);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var tempVal = long.Parse(value?.ToString() ?? throw new InvalidOperationException());
|
||||||
|
usedValues.Add(tempVal);
|
||||||
|
sb.AppendLine($" {{ \"{structName}{highestValue}\", {enumMember} }},");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (long i = 0; i <= highestValue; i++) {
|
||||||
|
if(!usedValues.Contains(i))
|
||||||
|
sb.AppendLine($" {{ \"Dead\", (({enumTypeString}){i}) }},");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine(" };");
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
ctx.AddSource("StructDictionary.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"DebugRoslynSourceGenerator": {
|
"DebugRoslynSourceGenerator": {
|
||||||
"commandName": "DebugRoslynComponent",
|
"commandName": "DebugRoslynComponent",
|
||||||
"targetProject": "../SourceGenerators1.Sample/SourceGenerators1.Sample.csproj"
|
"targetProject": "../PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,17 +3,17 @@
|
||||||
A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs.
|
A set of three projects that illustrates Roslyn source generators. Enjoy this template to learn from and modify source generators for your own needs.
|
||||||
|
|
||||||
## Content
|
## Content
|
||||||
### SourceGenerators1
|
### PacketMediator.Generator
|
||||||
A .NET Standard project with implementations of sample source generators.
|
A .NET Standard project with implementations of sample source generators.
|
||||||
**You must build this project to see the result (generated code) in the IDE.**
|
**You must build this project to see the result (generated code) in the IDE.**
|
||||||
|
|
||||||
- [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text file (in this case, Domain Driven Design ubiquitous language registry).
|
- [SampleSourceGenerator.cs](SampleSourceGenerator.cs): A source generator that creates C# classes based on a text file (in this case, Domain Driven Design ubiquitous language registry).
|
||||||
- [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` attribute.
|
- [SampleIncrementalSourceGenerator.cs](SampleIncrementalSourceGenerator.cs): A source generator that creates a custom report based on class properties. The target class should be annotated with the `Generators.ReportAttribute` attribute.
|
||||||
|
|
||||||
### SourceGenerators1.Sample
|
### PacketMediator.Generator.Sample
|
||||||
A project that references source generators. Note the parameters of `ProjectReference` in [SourceGenerators1.Sample.csproj](../SourceGenerators1.Sample/SourceGenerators1.Sample.csproj), they make sure that the project is referenced as a set of source generators.
|
A project that references source generators. Note the parameters of `ProjectReference` in [PacketMediator.Generator.Sample.csproj](../PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj), they make sure that the project is referenced as a set of source generators.
|
||||||
|
|
||||||
### SourceGenerators1.Tests
|
### PacketMediator.Generator.Tests
|
||||||
Unit tests for source generators. The easiest way to develop language-related features is to start with unit tests.
|
Unit tests for source generators. The easiest way to develop language-related features is to start with unit tests.
|
||||||
|
|
||||||
## How To?
|
## How To?
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
@ -10,14 +10,14 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
<PackageReference Include="coverlet.collector" Version="6.0.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
<PackageReference Include="Shouldly" Version="4.2.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.0" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.0.31903.59
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RaiNote.PacketMediator", "RaiNote.PacketMediator\RaiNote.PacketMediator.csproj", "{13243A92-DC1E-4DBF-8E2C-13CE9ABFEAD3}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketMediator.Samples", "PacketMediator.Samples\PacketMediator.Samples.csproj", "{C20254E6-0C15-43A4-9C4A-1D0B547F7983}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PacketMediator.Tests", "PacketMediator.Tests\PacketMediator.Tests.csproj", "{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerators1", "SourceGenerators1\SourceGenerators1\SourceGenerators1.csproj", "{268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerators1.Sample", "SourceGenerators1\SourceGenerators1.Sample\SourceGenerators1.Sample.csproj", "{AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerators1.Tests", "SourceGenerators1\SourceGenerators1.Tests\SourceGenerators1.Tests.csproj", "{60921557-6F31-43D7-A0CE-ED5C6E7DCB57}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{13243A92-DC1E-4DBF-8E2C-13CE9ABFEAD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{13243A92-DC1E-4DBF-8E2C-13CE9ABFEAD3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{13243A92-DC1E-4DBF-8E2C-13CE9ABFEAD3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{13243A92-DC1E-4DBF-8E2C-13CE9ABFEAD3}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{C20254E6-0C15-43A4-9C4A-1D0B547F7983}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{C20254E6-0C15-43A4-9C4A-1D0B547F7983}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{C20254E6-0C15-43A4-9C4A-1D0B547F7983}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{C20254E6-0C15-43A4-9C4A-1D0B547F7983}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{C0508314-2B9C-4285-9EE5-6DED3DD8C2DA}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{268AA6B4-0EBD-483B-A147-6B8D0E5B9F92}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{AA60BE32-4D65-4657-8ACF-ACF1437E2D1A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{60921557-6F31-43D7-A0CE-ED5C6E7DCB57}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
8
PacketMediator.slnx
Normal file
8
PacketMediator.slnx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<Solution>
|
||||||
|
<Project Path="PacketMediator.Generator\PacketMediator.Generator.Sample\PacketMediator.Generator.Sample.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="PacketMediator.Generator\PacketMediator.Generator.Tests\PacketMediator.Generator.Tests.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="PacketMediator.Generator\PacketMediator.Generator\PacketMediator.Generator.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="PacketMediator.Samples\PacketMediator.Samples.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="PacketMediator.Tests\PacketMediator.Tests.csproj" Type="Classic C#" />
|
||||||
|
<Project Path="RaiNote.PacketMediator\RaiNote.PacketMediator.csproj" Type="Classic C#" />
|
||||||
|
</Solution>
|
|
@ -1,8 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public interface IBidirectionalPacket : IOutgoingPacket, IIncomingPacket;
|
|
|
@ -1,12 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public interface IIncomingPacket : IPacket
|
|
||||||
{
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public void Deserialize(byte[] data);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public interface IOutgoingPacket : IPacket
|
|
||||||
{
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public byte[] Serialize();
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
public interface IPacket;
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public interface IPacketHandler<in TIncomingPacket, in TSession> : IPacketHandler<TSession>
|
|
||||||
where TIncomingPacket : IIncomingPacket
|
|
||||||
{
|
|
||||||
async Task<bool> IPacketHandler<TSession>.TryHandleAsync(IIncomingPacket packet, TSession session,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (packet is not TIncomingPacket tPacket)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var activity = new ActivitySource(nameof(PacketMediator)).StartActivity(nameof(HandleAsync));
|
|
||||||
activity?.AddTag("Handler", ToString());
|
|
||||||
activity?.AddTag("Packet", packet.ToString());
|
|
||||||
await HandleAsync(tPacket, session, cancellationToken);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public Task HandleAsync(TIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPacketHandler<in TSession>
|
|
||||||
{
|
|
||||||
Task<bool> TryHandleAsync(IIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
|
||||||
}
|
|
17
RaiNote.PacketMediator/IntermediatePacketHandlerData.cs
Normal file
17
RaiNote.PacketMediator/IntermediatePacketHandlerData.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace RaiNote.PacketMediator;
|
||||||
|
|
||||||
|
class IntermediatePacketHandlerData {
|
||||||
|
public IntermediatePacketHandlerData(INamedTypeSymbol? symbol, string packetHandlerIdentifier, IntermediatePacketStructHandlerData? packetStructHandlerData) {
|
||||||
|
Symbol = symbol;
|
||||||
|
PacketHandlerIdentifier = packetHandlerIdentifier;
|
||||||
|
PacketStructHandlerData = packetStructHandlerData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public INamedTypeSymbol? Symbol { get; set; }
|
||||||
|
public string PacketHandlerIdentifier { get; set; }
|
||||||
|
public IntermediatePacketStructHandlerData? PacketStructHandlerData { get; set; }
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace RaiNote.PacketMediator;
|
||||||
|
|
||||||
|
class IntermediatePacketStructHandlerData {
|
||||||
|
public IntermediatePacketStructHandlerData(ITypeSymbol packetStructSymbol, ITypeSymbol sessionSymbol) {
|
||||||
|
PacketStructSymbol = packetStructSymbol;
|
||||||
|
SessionSymbol = sessionSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITypeSymbol PacketStructSymbol { get; set; }
|
||||||
|
public ITypeSymbol SessionSymbol { get; set; }
|
||||||
|
}
|
|
@ -1,141 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Channels;
|
|
||||||
using DotNext.Collections.Generic;
|
|
||||||
using DotNext.Linq.Expressions;
|
|
||||||
using DotNext.Metaprogramming;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
public class PacketDistributor<TPacketIdEnum, TSession> where TPacketIdEnum : Enum
|
|
||||||
{
|
|
||||||
private readonly Channel<ValueTuple<byte[], TPacketIdEnum, TSession>> _channel;
|
|
||||||
|
|
||||||
private readonly ImmutableDictionary<TPacketIdEnum,
|
|
||||||
Func<byte[], IIncomingPacket>> _deserializationMap;
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<TPacketIdEnum, IPacketHandler<TSession>?> _packetHandlersInstantiation;
|
|
||||||
|
|
||||||
public PacketDistributor(IServiceProvider serviceProvider,
|
|
||||||
IEnumerable<Assembly> sourcesContainingPackets, IEnumerable<Assembly> sourcesContainingPacketHandlers)
|
|
||||||
{
|
|
||||||
_channel = Channel.CreateUnbounded<ValueTuple<byte[], TPacketIdEnum, TSession>>(new UnboundedChannelOptions
|
|
||||||
{
|
|
||||||
AllowSynchronousContinuations = false,
|
|
||||||
SingleReader = false,
|
|
||||||
SingleWriter = false
|
|
||||||
});
|
|
||||||
var containingPackets = sourcesContainingPackets as Assembly[] ?? sourcesContainingPackets.ToArray();
|
|
||||||
var allIncomingPackets = GetAllPackets(containingPackets, typeof(IIncomingPacket));
|
|
||||||
var allOutgoingPackets = GetAllPackets(containingPackets, typeof(IOutgoingPacket));
|
|
||||||
|
|
||||||
var packetHandlers = GetAllPacketHandlersWithId(sourcesContainingPacketHandlers);
|
|
||||||
|
|
||||||
PacketIdMap = allOutgoingPackets.Select(x => new { PacketId = x.Key, Type = x.Value })
|
|
||||||
.ToImmutableDictionary(x => x.Type, x => x.PacketId);
|
|
||||||
|
|
||||||
var tempDeserializationMap =
|
|
||||||
new ConcurrentDictionary<TPacketIdEnum, Func<byte[], IIncomingPacket>>();
|
|
||||||
_packetHandlersInstantiation = new ConcurrentDictionary<TPacketIdEnum, IPacketHandler<TSession>?>();
|
|
||||||
packetHandlers.ForEach(packetHandlerPair =>
|
|
||||||
{
|
|
||||||
var packetHandler =
|
|
||||||
ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider,
|
|
||||||
packetHandlerPair.Value);
|
|
||||||
_packetHandlersInstantiation.TryAdd(packetHandlerPair.Key, packetHandler as IPacketHandler<TSession>);
|
|
||||||
});
|
|
||||||
allIncomingPackets.ForEach(packetsType =>
|
|
||||||
{
|
|
||||||
var lambda = CodeGenerator.Lambda<Func<byte[], IIncomingPacket>>(fun =>
|
|
||||||
{
|
|
||||||
var argPacketData = fun[0];
|
|
||||||
var newPacket = packetsType.Value.New();
|
|
||||||
|
|
||||||
var packetVariable = CodeGenerator.DeclareVariable(packetsType.Value, "packet");
|
|
||||||
CodeGenerator.Assign(packetVariable, newPacket);
|
|
||||||
CodeGenerator.Call(packetVariable, nameof(IIncomingPacket.Deserialize), argPacketData);
|
|
||||||
|
|
||||||
CodeGenerator.Return(packetVariable);
|
|
||||||
}).Compile();
|
|
||||||
tempDeserializationMap.TryAdd(packetsType.Key, lambda);
|
|
||||||
});
|
|
||||||
|
|
||||||
_deserializationMap = tempDeserializationMap.ToImmutableDictionary();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImmutableDictionary<Type, TPacketIdEnum> PacketIdMap { get; }
|
|
||||||
|
|
||||||
private static IEnumerable<KeyValuePair<TPacketIdEnum, Type>> GetAllPackets(
|
|
||||||
IEnumerable<Assembly> sourcesContainingPackets, Type packetType)
|
|
||||||
{
|
|
||||||
var packetsWithId = sourcesContainingPackets.SelectMany(a => a.GetTypes()
|
|
||||||
.Where(type => type is { IsInterface: false, IsAbstract: false } &&
|
|
||||||
type.GetInterfaces().Contains(packetType)
|
|
||||||
&& type.GetCustomAttributes<PacketIdAttribute<TPacketIdEnum>>().Any()
|
|
||||||
))
|
|
||||||
.Select(type =>
|
|
||||||
new { Type = type, Attribute = type.GetCustomAttribute<PacketIdAttribute<TPacketIdEnum>>() })
|
|
||||||
.Select(x => new KeyValuePair<TPacketIdEnum, Type>(x.Attribute!.Code, x.Type));
|
|
||||||
|
|
||||||
return packetsWithId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<KeyValuePair<TPacketIdEnum, Type>> GetAllPacketHandlersWithId(
|
|
||||||
IEnumerable<Assembly> sourcesContainingPacketHandlers)
|
|
||||||
{
|
|
||||||
var packetHandlersWithId = sourcesContainingPacketHandlers.SelectMany(assembly => assembly.GetTypes()
|
|
||||||
.Where(t =>
|
|
||||||
t is { IsClass: true, IsAbstract: false } && Array.Exists(t
|
|
||||||
.GetInterfaces(), i =>
|
|
||||||
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IPacketHandler<,>)))
|
|
||||||
.Select(packetHandlerType => new
|
|
||||||
{
|
|
||||||
Type = packetHandlerType,
|
|
||||||
PacketId = packetHandlerType
|
|
||||||
.GetInterfaces().First(t1 =>
|
|
||||||
t1 is { IsGenericType: true } &&
|
|
||||||
t1.GetGenericTypeDefinition() == typeof(IPacketHandler<,>)).GetGenericArguments()
|
|
||||||
.First(genericType => genericType.GetInterfaces().Any(packetType =>
|
|
||||||
packetType == typeof(IPacket)))
|
|
||||||
.GetCustomAttribute<PacketIdAttribute<TPacketIdEnum>>()
|
|
||||||
}))
|
|
||||||
.Where(x => x.PacketId != null)
|
|
||||||
.Select(x => new KeyValuePair<TPacketIdEnum, Type>(x.PacketId!.Code, x.Type));
|
|
||||||
|
|
||||||
return packetHandlersWithId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task AddPacketAsync(byte[] packetData, TPacketIdEnum operationCode, TSession session)
|
|
||||||
{
|
|
||||||
await _channel.Writer.WriteAsync((packetData, operationCode, session));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DequeuePacketAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
while (await _channel.Reader.WaitToReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
while (_channel.Reader.TryRead(out var item))
|
|
||||||
{
|
|
||||||
await InvokePacketHandlerAsync(item, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InvokePacketHandlerAsync((byte[], TPacketIdEnum, TSession) valueTuple,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var (packetData, operationCode, session) = valueTuple;
|
|
||||||
if (!_deserializationMap.TryGetValue(operationCode, out var func))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var packet = func(packetData);
|
|
||||||
|
|
||||||
await _packetHandlersInstantiation[operationCode]?.TryHandleAsync(packet, session, cancellationToken)!;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
public class PacketDistributorService<TPacketIdEnum, TSession> : IHostedService
|
|
||||||
where TPacketIdEnum : Enum
|
|
||||||
{
|
|
||||||
private readonly PacketDistributor<TPacketIdEnum, TSession> _packetDistributor;
|
|
||||||
|
|
||||||
public PacketDistributorService(IServiceProvider serviceProvider,
|
|
||||||
IEnumerable<Assembly> sourcesContainingPackets, IEnumerable<Assembly> sourcesContainingPacketHandlers)
|
|
||||||
{
|
|
||||||
_packetDistributor = new PacketDistributor<TPacketIdEnum, TSession>(serviceProvider,
|
|
||||||
sourcesContainingPackets,
|
|
||||||
sourcesContainingPacketHandlers
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
await _packetDistributor.DequeuePacketAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task AddPacketAsync(byte[] packetData, TPacketIdEnum operationCode, TSession session)
|
|
||||||
{
|
|
||||||
return _packetDistributor.AddPacketAsync(packetData, operationCode, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DotNext.Optional<TPacketIdEnum> GetOperationCodeByPacketType(IPacket packet)
|
|
||||||
{
|
|
||||||
var type = packet.GetType();
|
|
||||||
_packetDistributor.PacketIdMap.TryGetValue(type, out var value);
|
|
||||||
return value ?? DotNext.Optional<TPacketIdEnum>.None;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
using System.CodeDom.Compiler;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Resources;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using Microsoft.CodeAnalysis.Text;
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
[Generator]
|
|
||||||
public class PacketHandlerGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
var structProvider = context.SyntaxProvider.CreateSyntaxProvider(
|
|
||||||
predicate: static (node, _) => node is StructDeclarationSyntax,
|
|
||||||
transform: static (ctx, _) => GetAttributesInheriting(ctx)
|
|
||||||
)
|
|
||||||
.Where(m => m is not null);
|
|
||||||
var classProvider = context.SyntaxProvider.CreateSyntaxProvider(
|
|
||||||
predicate: static (node, _) => node is ClassDeclarationSyntax,
|
|
||||||
transform: static (ctx, _) => ctx.Node as ClassDeclarationSyntax
|
|
||||||
)
|
|
||||||
.Where(m => m is not null);
|
|
||||||
var compilation = context.CompilationProvider.Combine(structProvider.Collect());
|
|
||||||
context.RegisterSourceOutput(compilation, Execute);
|
|
||||||
var writer = new IndentedTextWriter(new StringWriter());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ICollection<string> GetAttributesInheriting(GeneratorSyntaxContext context)
|
|
||||||
{
|
|
||||||
var arrayOfInheritingTypes = new List<string>();
|
|
||||||
var structDeclarationSyntax = (StructDeclarationSyntax)context.Node;
|
|
||||||
foreach (var attributeList in structDeclarationSyntax.AttributeLists)
|
|
||||||
{
|
|
||||||
foreach (var attribute in attributeList.Attributes)
|
|
||||||
{
|
|
||||||
if (context.SemanticModel.GetSymbolInfo(attribute).Symbol is not IMethodSymbol attributeSymbol)
|
|
||||||
continue; // if we can't get the symbol, ignore it
|
|
||||||
if (context.SemanticModel.GetSymbolInfo(attribute).Symbol is not ITypeSymbol attributeTypeSymbol)
|
|
||||||
continue;
|
|
||||||
if (!attributeTypeSymbol.AllInterfaces
|
|
||||||
.Select(x => x.Name.Equals(typeof(PacketIdAttribute<>).ToString(), StringComparison.Ordinal))
|
|
||||||
.Any())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
arrayOfInheritingTypes.Add(attributeTypeSymbol.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return arrayOfInheritingTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Execute(SourceProductionContext context,
|
|
||||||
(Compilation Left, ImmutableArray<ICollection<string>> Right) valueTuple)
|
|
||||||
{
|
|
||||||
var (compilation, attributeNames) = valueTuple;
|
|
||||||
var code = $@"{string.Join('\n', attributeNames)}";
|
|
||||||
code += "\r\n";
|
|
||||||
|
|
||||||
context.AddSource("Test.g.cs", SourceText.From(code, Encoding.UTF8));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
|
||||||
|
|
||||||
namespace RaiNote.PacketMediator;
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
|
||||||
public abstract class PacketIdAttribute<TPacketIdEnum> : Attribute where TPacketIdEnum : Enum
|
|
||||||
{
|
|
||||||
protected PacketIdAttribute(TPacketIdEnum code)
|
|
||||||
{
|
|
||||||
Code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TPacketIdEnum Code { get; }
|
|
||||||
}
|
|
208
RaiNote.PacketMediator/PacketMediatorGenerator.cs
Normal file
208
RaiNote.PacketMediator/PacketMediatorGenerator.cs
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
// Licensed to Timothy Schenk under the Apache 2.0 License.
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace RaiNote.PacketMediator;
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
public class PacketMediatorGenerator : IIncrementalGenerator {
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context) {
|
||||||
|
context.RegisterPostInitializationOutput(ctx => {
|
||||||
|
ctx.AddSource("PacketMediatorStatic.g.cs", SourceText.From(@"
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PacketMediator.Generator;
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
|
||||||
|
public abstract class PacketIdAttribute<TPacketIdEnum> : Attribute where TPacketIdEnum : Enum
|
||||||
|
{
|
||||||
|
protected PacketIdAttribute(TPacketIdEnum code)
|
||||||
|
{
|
||||||
|
Code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TPacketIdEnum Code { get; }
|
||||||
|
}
|
||||||
|
public interface IPacket;
|
||||||
|
public interface IIncomingPacket : IPacket
|
||||||
|
{
|
||||||
|
public void Deserialize(byte[] data);
|
||||||
|
}
|
||||||
|
public interface IOutgoingPacket : IPacket
|
||||||
|
{
|
||||||
|
public byte[] Serialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IBidirectionalPacket : IOutgoingPacket, IIncomingPacket;
|
||||||
|
|
||||||
|
public interface IPacketHandler<in TIncomingPacket, in TSession> : IPacketHandler<TSession>
|
||||||
|
where TIncomingPacket : IIncomingPacket
|
||||||
|
{
|
||||||
|
async Task<bool> IPacketHandler<TSession>.TryHandleAsync(IIncomingPacket packet, TSession session,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (packet is not TIncomingPacket tPacket)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var activity = new ActivitySource(nameof(PacketMediator)).StartActivity(nameof(HandleAsync));
|
||||||
|
activity?.AddTag(""Handler"", ToString());
|
||||||
|
activity?.AddTag(""Packet"", packet.ToString());
|
||||||
|
await HandleAsync(tPacket, session, cancellationToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleAsync(TIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IPacketHandler<in TSession>
|
||||||
|
{
|
||||||
|
Task<bool> TryHandleAsync(IIncomingPacket packet, TSession session, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
", Encoding.UTF8));
|
||||||
|
});
|
||||||
|
// Find all struct declarations
|
||||||
|
var structsWithAttributes = context.SyntaxProvider
|
||||||
|
.CreateSyntaxProvider(
|
||||||
|
predicate: (node, _) => node is StructDeclarationSyntax structSyntax &&
|
||||||
|
structSyntax.AttributeLists.Count > 0,
|
||||||
|
transform: (syntaxContext, _) => {
|
||||||
|
var structDeclaration = (StructDeclarationSyntax)syntaxContext.Node;
|
||||||
|
var model = syntaxContext.SemanticModel;
|
||||||
|
var symbol = model.GetDeclaredSymbol(structDeclaration, cancellationToken: _) as INamedTypeSymbol;
|
||||||
|
var requiredInterfaces = new[] {
|
||||||
|
"IPacket", "IIncomingPacket", "IOutgoingPacket", "IBidirectionalPacket"
|
||||||
|
};
|
||||||
|
var implementsInterface = symbol != null && symbol.AllInterfaces
|
||||||
|
.Any(i => requiredInterfaces.Contains(i.Name, StringComparer.Ordinal));
|
||||||
|
// Check for the marker attribute
|
||||||
|
var attribute = symbol?.GetAttributes()
|
||||||
|
.FirstOrDefault(attr => {
|
||||||
|
var attrClass = attr.AttributeClass;
|
||||||
|
while (attrClass != null) {
|
||||||
|
if (string.Equals(attrClass.Name, "PacketIdAttribute", StringComparison.Ordinal) &&
|
||||||
|
string.Equals(attrClass.ContainingNamespace.ToDisplayString(),
|
||||||
|
"PacketMediator.Generator", StringComparison.Ordinal)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
attrClass = attrClass.BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (attribute == null) {
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributeConstructorArgument = attribute.ConstructorArguments[0];
|
||||||
|
var enumType = attributeConstructorArgument.Type;
|
||||||
|
var enumValue = attributeConstructorArgument.Value;
|
||||||
|
var enumMember = enumType?.GetMembers()
|
||||||
|
.OfType<IFieldSymbol>()
|
||||||
|
.FirstOrDefault(f => f.ConstantValue?.Equals(enumValue) == true);
|
||||||
|
var enumMaxValue = enumType?.GetMembers()
|
||||||
|
.OfType<IFieldSymbol>().Max(x => x.ConstantValue);
|
||||||
|
return (symbol, structName: structDeclaration.Identifier.Text, value: enumValue, enumType, enumMember,
|
||||||
|
enumMaxValue, implementsInterface);
|
||||||
|
})
|
||||||
|
.Where(result => result != default);
|
||||||
|
var packetHandlers = context.SyntaxProvider.CreateSyntaxProvider(
|
||||||
|
predicate: (node, _) => node is ClassDeclarationSyntax classDeclarationSyntax,
|
||||||
|
(syntaxContext, _) => {
|
||||||
|
var classDeclaration = (ClassDeclarationSyntax)syntaxContext.Node;
|
||||||
|
var model = syntaxContext.SemanticModel;
|
||||||
|
var symbol = model.GetDeclaredSymbol(classDeclaration, cancellationToken: _) as INamedTypeSymbol;
|
||||||
|
|
||||||
|
var implementsInterface = symbol != null && symbol.AllInterfaces
|
||||||
|
.Any(i => i.Name.SequenceEqual("IPacketHandler"));
|
||||||
|
var packetStruct = (symbol?.Interfaces.Select(interfaceSyntax => {
|
||||||
|
if (!interfaceSyntax.Name.SequenceEqual("IPacketHandler")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfaceSyntax.TypeArguments.Length < 2)
|
||||||
|
return null;
|
||||||
|
var genericStructArgument = interfaceSyntax.TypeArguments[0];
|
||||||
|
var genericSessionArgument = interfaceSyntax.TypeArguments[1];
|
||||||
|
return new IntermediatePacketStructHandlerData(genericStructArgument, genericSessionArgument);
|
||||||
|
|
||||||
|
}) ?? throw new InvalidOperationException("1")).FirstOrDefault(x => x != null);
|
||||||
|
|
||||||
|
if (!implementsInterface)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new IntermediatePacketHandlerData(symbol, classDeclaration.Identifier.Text, packetStruct);
|
||||||
|
}).Where(result => result != null);
|
||||||
|
|
||||||
|
var combinedResults = structsWithAttributes.Collect().Combine(packetHandlers.Collect());
|
||||||
|
|
||||||
|
// Collect and generate the dictionary
|
||||||
|
context.RegisterSourceOutput(combinedResults, (ctx, result) => {
|
||||||
|
var (packetStructs, packetHandlerDatas) = result;
|
||||||
|
//throw new InvalidOperationException(packetStructs.Length.ToString());
|
||||||
|
var combinedInfo = packetHandlerDatas.Select(handler => {
|
||||||
|
var respectiveStruct = packetStructs.FirstOrDefault(pStruct => pStruct.structName.SequenceEqual(handler?.PacketStructHandlerData?.PacketStructSymbol.Name));
|
||||||
|
return (handler, respectiveStruct);
|
||||||
|
});
|
||||||
|
var usedValues = new List<long>();
|
||||||
|
var highestValue = long.Parse(packetStructs.FirstOrDefault().enumMaxValue?.ToString());
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var @struct in packetStructs) {
|
||||||
|
sb.Append($"// {@struct} a {@struct.enumMaxValue}");
|
||||||
|
}
|
||||||
|
sb.AppendLine("using System.Collections.Generic;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("public static class PacketHandlerMediator");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
var enumTypeString = packetStructs.FirstOrDefault().enumType?.ToDisplayString();
|
||||||
|
sb.AppendLine($" public static bool Handle()");
|
||||||
|
sb.AppendLine($" var x= Dictionary<string, {enumTypeString}> Values = new()");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
|
||||||
|
foreach (var (handler, (symbol, structName, value, _, enumMember, _, implementsInterface)) in combinedInfo) {
|
||||||
|
if (!implementsInterface) {
|
||||||
|
var diagnostic = Diagnostic.Create(
|
||||||
|
new DiagnosticDescriptor(
|
||||||
|
id: "MYGEN001",
|
||||||
|
title: "Struct does not implement required interface",
|
||||||
|
messageFormat:
|
||||||
|
$"The struct '{{0}}' must implement at least one of: \"IPacket\", \"IIncomingPacket\", \"IOutgoingPacket\", \"IBidirectionalPacket\" ",
|
||||||
|
category: "SourceGenerator",
|
||||||
|
DiagnosticSeverity.Error,
|
||||||
|
isEnabledByDefault: true),
|
||||||
|
symbol?.Locations.FirstOrDefault(), structName);
|
||||||
|
|
||||||
|
ctx.ReportDiagnostic(diagnostic);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempVal = long.Parse(value?.ToString() ?? throw new InvalidOperationException("3"),
|
||||||
|
new NumberFormatInfo());
|
||||||
|
usedValues.Add(tempVal);
|
||||||
|
sb.AppendLine($" {{ \"{handler.PacketHandlerIdentifier}-{structName}{highestValue}\", {enumMember} }},");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (long i = 0; i <= highestValue; i++) {
|
||||||
|
if (!usedValues.Contains(i))
|
||||||
|
sb.AppendLine($" {{ \"Dead\", (({enumTypeString}){i}) }},");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine(" };");
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
ctx.AddSource("StructDictionary.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<PackageVersion>$(PackageVersion)</PackageVersion>
|
<PackageVersion>$(PackageVersion)</PackageVersion>
|
||||||
|
@ -21,25 +21,24 @@
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
<LangVersion>default</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="../README.md" Pack="true" PackagePath="\"/>
|
<None Include="../README.md" Pack="true" PackagePath="\"/>
|
||||||
<PackageReference Include="DotNext" Version="5.11.0" />
|
<PackageReference Include="Meziantou.Analyzer" Version="2.0.186">
|
||||||
<PackageReference Include="DotNext.Metaprogramming" Version="5.7.0" />
|
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
|
||||||
<PackageReference Include="Meziantou.Analyzer" Version="2.0.163">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.12.19">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
namespace SourceGenerators1.Sample;
|
|
||||||
|
|
||||||
[GamePacketIdAttribute(OperationCode.LoginRequest)]
|
|
||||||
public class LoginRequestPacket : IIncomingPacket
|
|
||||||
{
|
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
public string Password { get; set; }
|
|
||||||
|
|
||||||
public void Deserialize(byte[] data)
|
|
||||||
{
|
|
||||||
Username = Encoding.ASCII.GetString(data, 0, 32);
|
|
||||||
Password = Encoding.ASCII.GetString(data, 32, 64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GamePacketIdAttribute.cs
|
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
|
||||||
public class GamePacketIdAttribute : PacketIdAttribute<OperationCode>
|
|
||||||
{
|
|
||||||
public GamePacketIdAttribute(OperationCode code) : base(code)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum OperationCode : ushort
|
|
||||||
{
|
|
||||||
LoginRequest = 1
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<RootNamespace>SourceGenerators1.Sample</RootNamespace>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SourceGenerators1\SourceGenerators1.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="true"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="DDD.UbiquitousLanguageRegistry.txt"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
|
@ -1,135 +0,0 @@
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using Microsoft.CodeAnalysis.Text;
|
|
||||||
|
|
||||||
|
|
||||||
namespace SourceGenerators1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A sample source generator that creates a custom report based on class properties. The target class should be annotated with the 'Generators.ReportAttribute' attribute.
|
|
||||||
/// When using the source code as a baseline, an incremental source generator is preferable because it reduces the performance overhead.
|
|
||||||
/// </summary>
|
|
||||||
[Generator]
|
|
||||||
public class SampleIncrementalSourceGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
private const string Namespace = "Generators";
|
|
||||||
private const string AttributeName = "ReportAttribute";
|
|
||||||
|
|
||||||
private const string AttributeSourceCode = $@"// <auto-generated/>
|
|
||||||
|
|
||||||
namespace {Namespace}
|
|
||||||
{{
|
|
||||||
[System.AttributeUsage(System.AttributeTargets.Class)]
|
|
||||||
public class {AttributeName} : System.Attribute
|
|
||||||
{{
|
|
||||||
}}
|
|
||||||
}}";
|
|
||||||
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
// Add the marker attribute to the compilation.
|
|
||||||
context.RegisterPostInitializationOutput(ctx => ctx.AddSource(
|
|
||||||
"ReportAttribute.g.cs",
|
|
||||||
SourceText.From(AttributeSourceCode, Encoding.UTF8)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Filter classes annotated with the [Report] attribute. Only filtered Syntax Nodes can trigger code generation.
|
|
||||||
var provider = context.SyntaxProvider
|
|
||||||
.CreateSyntaxProvider(
|
|
||||||
(s, _) => s is ClassDeclarationSyntax,
|
|
||||||
(ctx, _) => GetClassDeclarationForSourceGen(ctx)
|
|
||||||
)
|
|
||||||
.Where(t => t.reportAttributeFound)
|
|
||||||
.Select((t, _) => t.Item1);
|
|
||||||
|
|
||||||
// Generate the source code.
|
|
||||||
context.RegisterSourceOutput(context.CompilationProvider.Combine(provider.Collect()),
|
|
||||||
((ctx, t) => GenerateCode(ctx, t.Left, t.Right))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether the Node is annotated with the [Report] attribute and maps syntax context to the specific node type (ClassDeclarationSyntax).
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">Syntax context, based on CreateSyntaxProvider predicate</param>
|
|
||||||
/// <returns>The specific cast and whether the attribute was found.</returns>
|
|
||||||
private static (ClassDeclarationSyntax, bool reportAttributeFound) GetClassDeclarationForSourceGen(
|
|
||||||
GeneratorSyntaxContext context)
|
|
||||||
{
|
|
||||||
var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
|
|
||||||
|
|
||||||
// Go through all attributes of the class.
|
|
||||||
foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
|
|
||||||
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
|
|
||||||
{
|
|
||||||
if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
|
|
||||||
continue; // if we can't get the symbol, ignore it
|
|
||||||
|
|
||||||
string attributeName = attributeSymbol.ContainingType.ToDisplayString();
|
|
||||||
|
|
||||||
// Check the full name of the [Report] attribute.
|
|
||||||
if (attributeName == $"{Namespace}.{AttributeName}")
|
|
||||||
return (classDeclarationSyntax, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (classDeclarationSyntax, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate code action.
|
|
||||||
/// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [Report] attribute) changed by the user.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">Source generation context used to add source files.</param>
|
|
||||||
/// <param name="compilation">Compilation used to provide access to the Semantic Model.</param>
|
|
||||||
/// <param name="classDeclarations">Nodes annotated with the [Report] attribute that trigger the generate action.</param>
|
|
||||||
private void GenerateCode(SourceProductionContext context, Compilation compilation,
|
|
||||||
ImmutableArray<ClassDeclarationSyntax> classDeclarations)
|
|
||||||
{
|
|
||||||
// Go through all filtered class declarations.
|
|
||||||
foreach (var classDeclarationSyntax in classDeclarations)
|
|
||||||
{
|
|
||||||
// We need to get semantic model of the class to retrieve metadata.
|
|
||||||
var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree);
|
|
||||||
|
|
||||||
// Symbols allow us to get the compile-time information.
|
|
||||||
if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
|
|
||||||
|
|
||||||
// 'Identifier' means the token of the node. Get class name from the syntax node.
|
|
||||||
var className = classDeclarationSyntax.Identifier.Text;
|
|
||||||
|
|
||||||
// Go through all class members with a particular type (property) to generate method lines.
|
|
||||||
var methodBody = classSymbol.GetMembers()
|
|
||||||
.OfType<IPropertySymbol>()
|
|
||||||
.Select(p =>
|
|
||||||
$@" yield return $""{p.Name}:{{this.{p.Name}}}"";"
|
|
||||||
); // e.g. yield return $"Id:{this.Id}";
|
|
||||||
|
|
||||||
// Build up the source code
|
|
||||||
var code = $@"// <auto-generated/>
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace {namespaceName};
|
|
||||||
|
|
||||||
partial class {className}
|
|
||||||
{{
|
|
||||||
public IEnumerable<string> Report()
|
|
||||||
{{
|
|
||||||
{string.Join("\n", methodBody)}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Add the source code to the compilation.
|
|
||||||
context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
using System.IO;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
|
|
||||||
namespace SourceGenerators1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A sample source generator that creates C# classes based on the text file (in this case, Domain Driven Design ubiquitous language registry).
|
|
||||||
/// When using a simple text file as a baseline, we can create a non-incremental source generator.
|
|
||||||
/// </summary>
|
|
||||||
[Generator]
|
|
||||||
public class SampleSourceGenerator : ISourceGenerator
|
|
||||||
{
|
|
||||||
public void Initialize(GeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
// No initialization required for this generator.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute(GeneratorExecutionContext context)
|
|
||||||
{
|
|
||||||
// If you would like to put some data to non-compilable file (e.g. a .txt file), mark it as an Additional File.
|
|
||||||
|
|
||||||
// Go through all files marked as an Additional File in file properties.
|
|
||||||
foreach (var additionalFile in context.AdditionalFiles)
|
|
||||||
{
|
|
||||||
if (additionalFile == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Check if the file name is the specific file that we expect.
|
|
||||||
if (Path.GetFileName(additionalFile.Path) != "DDD.UbiquitousLanguageRegistry.txt")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var text = additionalFile.GetText();
|
|
||||||
if (text == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (var line in text.Lines)
|
|
||||||
{
|
|
||||||
var className = line.ToString().Trim();
|
|
||||||
|
|
||||||
// Build up the source code.
|
|
||||||
string source = $@"// <auto-generated/>
|
|
||||||
|
|
||||||
namespace Entities
|
|
||||||
{{
|
|
||||||
public partial class {className}
|
|
||||||
{{
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
";
|
|
||||||
|
|
||||||
// Add the source code to the compilation.
|
|
||||||
context.AddSource($"{className}.g.cs", source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,232 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.CodeDom.Compiler;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Microsoft.CodeAnalysis;
|
|
||||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
||||||
using Microsoft.CodeAnalysis.Text;
|
|
||||||
|
|
||||||
namespace SourceGenerators1;
|
|
||||||
|
|
||||||
[Generator]
|
|
||||||
public class PacketHandlerGenerator : IIncrementalGenerator
|
|
||||||
{
|
|
||||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
||||||
{
|
|
||||||
var attributeProvider = context.SyntaxProvider.CreateSyntaxProvider(
|
|
||||||
predicate: static (node, _) => node is ClassDeclarationSyntax,
|
|
||||||
transform: static (ctx, _) => GetAttributesInheriting(ctx)
|
|
||||||
)
|
|
||||||
.Where(m => m is not null);
|
|
||||||
|
|
||||||
var attribute = attributeProvider.Select((data, _) => data);
|
|
||||||
|
|
||||||
/*
|
|
||||||
var classProvider = context.SyntaxProvider.CreateSyntaxProvider(
|
|
||||||
predicate: static (node, _) => node is ClassDeclarationSyntax,
|
|
||||||
transform: (ctx, _) => GetIncomingPacketTypes(ctx, attribute)
|
|
||||||
)
|
|
||||||
.Where(m => m is not null);
|
|
||||||
|
|
||||||
var test = attributeProvider.Combine(classProvider.Collect());*/
|
|
||||||
|
|
||||||
context.RegisterSourceOutput(context.CompilationProvider.Combine(attributeProvider.Collect()),
|
|
||||||
Execute
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? GetIncomingPacketTypes(GeneratorSyntaxContext context, string? desiredAttributeName)
|
|
||||||
{
|
|
||||||
var structDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
|
|
||||||
|
|
||||||
var isValidBaseType = false;
|
|
||||||
foreach (var baseTypeSyntax in structDeclarationSyntax.BaseList?.Types ?? Enumerable.Empty<BaseTypeSyntax>())
|
|
||||||
{
|
|
||||||
var simpleBaseTypeSyntax = baseTypeSyntax as SimpleBaseTypeSyntax;
|
|
||||||
var genericName = simpleBaseTypeSyntax?.Type as GenericNameSyntax;
|
|
||||||
if (genericName?.Identifier.ToString() !=
|
|
||||||
GetAttributeName(typeof(IIncomingPacket)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidBaseType = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidBaseType)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var attributeList in structDeclarationSyntax.AttributeLists)
|
|
||||||
{
|
|
||||||
foreach (var attributeSyntax in attributeList.Attributes)
|
|
||||||
{
|
|
||||||
if (attributeSyntax.Name.ToString() != desiredAttributeName)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return structDeclarationSyntax.Identifier.Text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AttributeClassMapping? GetAttributesInheriting(GeneratorSyntaxContext context)
|
|
||||||
{
|
|
||||||
var structDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
|
|
||||||
var targetName = structDeclarationSyntax.Identifier.Text;
|
|
||||||
string? attributeName = null;
|
|
||||||
var isValidAttribute = false;
|
|
||||||
var isValidPacketType = false;
|
|
||||||
foreach (var attributeList in structDeclarationSyntax.AttributeLists)
|
|
||||||
{
|
|
||||||
foreach (var attributeSyntax in attributeList.Attributes)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach (var baseTypeSyntax in structDeclarationSyntax.BaseList?.Types ?? Enumerable.Empty<BaseTypeSyntax>())
|
|
||||||
{
|
|
||||||
var simpleBaseTypeSyntax = baseTypeSyntax as SimpleBaseTypeSyntax;
|
|
||||||
var genericName = simpleBaseTypeSyntax?.Type as GenericNameSyntax;
|
|
||||||
if (genericName?.Identifier.ToString() ==
|
|
||||||
GetAttributeName(typeof(PacketIdAttribute<>)))
|
|
||||||
{
|
|
||||||
isValidAttribute = true;
|
|
||||||
attributeName = simpleBaseTypeSyntax?.ToFullString();
|
|
||||||
}
|
|
||||||
var interfaceName = simpleBaseTypeSyntax?.Type as IdentifierNameSyntax;
|
|
||||||
|
|
||||||
if (interfaceName?.Identifier.ToString() == nameof(IIncomingPacket))
|
|
||||||
isValidPacketType = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidPacketType)
|
|
||||||
return new AttributeClassMapping(targetName, attributeName);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct AttributeClassMapping
|
|
||||||
{
|
|
||||||
public AttributeClassMapping(string? className, string? attributeName)
|
|
||||||
{
|
|
||||||
this.ClassName = className;
|
|
||||||
this.AttributeName = attributeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly string? ClassName;
|
|
||||||
public readonly string? AttributeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetAttributeName(Type type)
|
|
||||||
{
|
|
||||||
return type.IsGenericType ? type.Name.Split('`')[0] : type.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Execute(SourceProductionContext context,
|
|
||||||
(Compilation Left, ImmutableArray<AttributeClassMapping?> Right) tuple)
|
|
||||||
{
|
|
||||||
var dependencies = new string[] { "System", "System.Threading.Channels", "SourceGenerators1.Sample" };
|
|
||||||
/*
|
|
||||||
var lambda = CodeGenerator.Lambda<Func<byte[], IIncomingPacket>>(fun =>
|
|
||||||
{
|
|
||||||
var argPacketData = fun[0];
|
|
||||||
var newPacket = packetsType.Value.New();
|
|
||||||
|
|
||||||
var packetVariable = CodeGenerator.DeclareVariable(packetsType.Value, "packet");
|
|
||||||
CodeGenerator.Assign(packetVariable, newPacket);
|
|
||||||
CodeGenerator.Call(packetVariable, nameof(IIncomingPacket.Deserialize), argPacketData);
|
|
||||||
|
|
||||||
CodeGenerator.Return(packetVariable);
|
|
||||||
}).Compile();
|
|
||||||
*/
|
|
||||||
var allparsingFunctions = "";
|
|
||||||
context.ReportDiagnostic(Diagnostic.Create(
|
|
||||||
new DiagnosticDescriptor("fuckyou0001",
|
|
||||||
"FuckYou",
|
|
||||||
"Message: {0}",
|
|
||||||
"FUCK YOU",
|
|
||||||
DiagnosticSeverity.Error,
|
|
||||||
true
|
|
||||||
),
|
|
||||||
Location.None,
|
|
||||||
string.Join(", ",
|
|
||||||
tuple.Right.Select(x =>
|
|
||||||
{
|
|
||||||
if (!x.HasValue)
|
|
||||||
return string.Empty;
|
|
||||||
return x.Value.AttributeName + ":" + x.Value.ClassName;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (tuple.Right.Length == 0)
|
|
||||||
{
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var packetName in tuple.Right)
|
|
||||||
{
|
|
||||||
var parsingFunctionTemplate = $$"""
|
|
||||||
public {{packetName?.ClassName}} Parse(byte[] data) {
|
|
||||||
var packet = new {{packetName?.ClassName}}();
|
|
||||||
packet.Deserialize(data);
|
|
||||||
return packet;
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
allparsingFunctions += parsingFunctionTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
var codeTemplate = $$"""
|
|
||||||
//<auto-generated/>
|
|
||||||
{{string.Join('\n', dependencies.Select(dep => $"using {dep};"))}}
|
|
||||||
|
|
||||||
public class PacketDistributorService{
|
|
||||||
|
|
||||||
{{allparsingFunctions}}
|
|
||||||
|
|
||||||
private async Task InvokePacketHandlerAsync((byte[], TPacketIdEnum, TSession) valueTuple,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var (packetData, operationCode, session) = valueTuple;
|
|
||||||
if (!_deserializationMap.TryGetValue(operationCode, out var func))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var packet = func(packetData);
|
|
||||||
|
|
||||||
await _packetHandlersInstantiation[operationCode]?.TryHandleAsync(packet, session, cancellationToken)!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""";
|
|
||||||
context.AddSource("Test.g.cs", SourceText.From(codeTemplate, Encoding.UTF8));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
|
||||||
public abstract class PacketIdAttribute<TPacketIdEnum> : Attribute where TPacketIdEnum : Enum
|
|
||||||
{
|
|
||||||
protected PacketIdAttribute(TPacketIdEnum code)
|
|
||||||
{
|
|
||||||
Code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TPacketIdEnum Code { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public interface IIncomingPacket : IPacket
|
|
||||||
{
|
|
||||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
|
||||||
public void Deserialize(byte[] data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IPacket;
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.203",
|
"version": "9.0.0",
|
||||||
"rollForward": "latestMinor",
|
"rollForward": "latestMinor",
|
||||||
"allowPrerelease": false
|
"allowPrerelease": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue