diff --git a/.editorconfig b/.editorconfig index d83256e..4a109a2 100644 --- a/.editorconfig +++ b/.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. ; http://editorconfig.org/ ; @@ -12,11 +11,12 @@ ; then the rule will be silently ignored. ; This is the default for the codeline. +root = true ; For disabling MA0048 and MA0051 in a specific folder, e.g., Migrations [**/Migrations/*.cs] -dotnet_diagnostic.ma0048.severity = none -dotnet_diagnostic.ma0051.severity = none +dotnet_diagnostic.MA0048.severity = none +dotnet_diagnostic.MA0051.severity = none [*] indent_style = space @@ -25,19 +25,14 @@ trim_trailing_whitespace = true insert_final_newline = true file_header_template = Licensed to Timothy Schenk under the Apache 2.0 License. -[*.{yaml,yml}] +[*.(yaml|yml)] indent_size = 2 [*.cs] -# ReSharper properties -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 +dotnet_diagnostic.MA0004.severity = none indent_size = 4 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 # Don't use this. qualifier dotnet_style_qualification_for_field = false:suggestion @@ -58,7 +53,7 @@ csharp_style_var_elsewhere = true:suggestion csharp_style_throw_expression = false:suggestion # 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_catch = true csharp_new_line_before_finally = true @@ -111,367 +106,367 @@ charset = utf-8-bom [*.{cs,vb}] # 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 -dotnet_diagnostic.ca1018.severity = warning +dotnet_diagnostic.CA1018.severity = warning # CA1047: Do not declare protected member in sealed type -dotnet_diagnostic.ca1047.severity = warning +dotnet_diagnostic.CA1047.severity = warning # CA1305: Specify IFormatProvider -dotnet_diagnostic.ca1305.severity = warning +dotnet_diagnostic.CA1305.severity = warning # CA1507: Use nameof to express symbol names -dotnet_diagnostic.ca1507.severity = warning +dotnet_diagnostic.CA1507.severity = warning # CA1510: Use ArgumentNullException throw helper -dotnet_diagnostic.ca1510.severity = warning +dotnet_diagnostic.CA1510.severity = warning # CA1511: Use ArgumentException throw helper -dotnet_diagnostic.ca1511.severity = warning +dotnet_diagnostic.CA1511.severity = warning # CA1512: Use ArgumentOutOfRangeException throw helper -dotnet_diagnostic.ca1512.severity = warning +dotnet_diagnostic.CA1512.severity = warning # CA1513: Use ObjectDisposedException throw helper -dotnet_diagnostic.ca1513.severity = warning +dotnet_diagnostic.CA1513.severity = warning # CA1725: Parameter names should match base declaration -dotnet_diagnostic.ca1725.severity = suggestion +dotnet_diagnostic.CA1725.severity = suggestion # CA1802: Use literals where appropriate -dotnet_diagnostic.ca1802.severity = warning +dotnet_diagnostic.CA1802.severity = warning # CA1805: Do not initialize unnecessarily -dotnet_diagnostic.ca1805.severity = warning +dotnet_diagnostic.CA1805.severity = warning # CA1810: Do not initialize unnecessarily -dotnet_diagnostic.ca1810.severity = warning +dotnet_diagnostic.CA1810.severity = warning # CA1821: Remove empty Finalizers -dotnet_diagnostic.ca1821.severity = warning +dotnet_diagnostic.CA1821.severity = warning # CA1822: Make member static -dotnet_diagnostic.ca1822.severity = warning -dotnet_code_quality.ca1822.api_surface = private, internal +dotnet_diagnostic.CA1822.severity = warning +dotnet_code_quality.CA1822.api_surface = private, internal # CA1823: Avoid unused private fields -dotnet_diagnostic.ca1823.severity = warning +dotnet_diagnostic.CA1823.severity = warning # 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 -dotnet_diagnostic.ca1826.severity = warning +dotnet_diagnostic.CA1826.severity = warning # 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 -dotnet_diagnostic.ca1828.severity = warning +dotnet_diagnostic.CA1828.severity = warning # 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 -dotnet_diagnostic.ca1830.severity = warning +dotnet_diagnostic.CA1830.severity = warning # 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 -dotnet_diagnostic.ca1832.severity = warning +dotnet_diagnostic.CA1832.severity = warning # 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 -dotnet_diagnostic.ca1834.severity = warning +dotnet_diagnostic.CA1834.severity = warning # 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 -dotnet_diagnostic.ca1836.severity = warning +dotnet_diagnostic.CA1836.severity = warning # CA1837: Use 'Environment.ProcessId' -dotnet_diagnostic.ca1837.severity = warning +dotnet_diagnostic.CA1837.severity = warning # CA1838: Avoid 'StringBuilder' parameters for P/Invokes -dotnet_diagnostic.ca1838.severity = warning +dotnet_diagnostic.CA1838.severity = warning # CA1839: Use 'Environment.ProcessPath' -dotnet_diagnostic.ca1839.severity = warning +dotnet_diagnostic.CA1839.severity = warning # CA1840: Use 'Environment.CurrentManagedThreadId' -dotnet_diagnostic.ca1840.severity = warning +dotnet_diagnostic.CA1840.severity = warning # CA1841: Prefer Dictionary.Contains methods -dotnet_diagnostic.ca1841.severity = warning +dotnet_diagnostic.CA1841.severity = warning # 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 -dotnet_diagnostic.ca1843.severity = warning +dotnet_diagnostic.CA1843.severity = warning # 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' -dotnet_diagnostic.ca1845.severity = warning +dotnet_diagnostic.CA1845.severity = warning # 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 -dotnet_diagnostic.ca1847.severity = warning +dotnet_diagnostic.CA1847.severity = warning # CA1852: Seal internal types -dotnet_diagnostic.ca1852.severity = warning +dotnet_diagnostic.CA1852.severity = warning # CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method -dotnet_diagnostic.ca1854.severity = warning +dotnet_diagnostic.CA1854.severity = warning # CA1855: Prefer 'Clear' over 'Fill' -dotnet_diagnostic.ca1855.severity = warning +dotnet_diagnostic.CA1855.severity = warning # CA1856: Incorrect usage of ConstantExpected attribute -dotnet_diagnostic.ca1856.severity = error +dotnet_diagnostic.CA1856.severity = error # CA1857: A constant is expected for the parameter -dotnet_diagnostic.ca1857.severity = warning +dotnet_diagnostic.CA1857.severity = warning # CA1858: Use 'StartsWith' instead of 'IndexOf' -dotnet_diagnostic.ca1858.severity = warning +dotnet_diagnostic.CA1858.severity = warning # 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 -dotnet_diagnostic.ca2008.severity = warning +dotnet_diagnostic.CA2008.severity = warning # CA2009: Do not call ToImmutableCollection on an ImmutableCollection value -dotnet_diagnostic.ca2009.severity = warning +dotnet_diagnostic.CA2009.severity = warning # CA2011: Avoid infinite recursion -dotnet_diagnostic.ca2011.severity = warning +dotnet_diagnostic.CA2011.severity = warning # CA2012: Use ValueTask correctly -dotnet_diagnostic.ca2012.severity = warning +dotnet_diagnostic.CA2012.severity = warning # 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. -dotnet_diagnostic.ca2014.severity = warning +dotnet_diagnostic.CA2014.severity = warning # 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 -dotnet_diagnostic.ca2200.severity = warning +dotnet_diagnostic.CA2200.severity = warning # CA2201: Do not raise reserved exception types -dotnet_diagnostic.ca2201.severity = warning +dotnet_diagnostic.CA2201.severity = warning # CA2208: Instantiate argument exceptions correctly -dotnet_diagnostic.ca2208.severity = warning +dotnet_diagnostic.CA2208.severity = warning # 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 -dotnet_diagnostic.ca2246.severity = warning +dotnet_diagnostic.CA2246.severity = warning # 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 -dotnet_diagnostic.ide0005.severity = warning +dotnet_diagnostic.IDE0005.severity = warning # 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) -dotnet_diagnostic.ide0020.severity = warning +dotnet_diagnostic.IDE0020.severity = warning # IDE0029: Use coalesce expression (non-nullable types) -dotnet_diagnostic.ide0029.severity = warning +dotnet_diagnostic.IDE0029.severity = warning # IDE0030: Use coalesce expression (nullable types) -dotnet_diagnostic.ide0030.severity = warning +dotnet_diagnostic.IDE0030.severity = warning # IDE0031: Use null propagation -dotnet_diagnostic.ide0031.severity = warning +dotnet_diagnostic.IDE0031.severity = warning # IDE0035: Remove unreachable code -dotnet_diagnostic.ide0035.severity = warning +dotnet_diagnostic.IDE0035.severity = warning # IDE0036: Order modifiers 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) -dotnet_diagnostic.ide0038.severity = warning +dotnet_diagnostic.IDE0038.severity = warning # IDE0043: Format string contains invalid placeholder -dotnet_diagnostic.ide0043.severity = warning +dotnet_diagnostic.IDE0043.severity = warning # IDE0044: Make field readonly -dotnet_diagnostic.ide0044.severity = warning +dotnet_diagnostic.IDE0044.severity = warning # IDE0051: Remove unused private members -dotnet_diagnostic.ide0051.severity = warning +dotnet_diagnostic.IDE0051.severity = warning # IDE0055: All formatting rules -dotnet_diagnostic.ide0055.severity = suggestion +dotnet_diagnostic.IDE0055.severity = suggestion # IDE0059: Unnecessary assignment to a value -dotnet_diagnostic.ide0059.severity = warning +dotnet_diagnostic.IDE0059.severity = warning # IDE0060: Remove unused parameter dotnet_code_quality_unused_parameters = non_public -dotnet_diagnostic.ide0060.severity = warning +dotnet_diagnostic.IDE0060.severity = warning # IDE0062: Make local function static -dotnet_diagnostic.ide0062.severity = warning +dotnet_diagnostic.IDE0062.severity = warning # IDE0073: File header -dotnet_diagnostic.ide0073.severity = warning +dotnet_diagnostic.IDE0073.severity = warning # 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. # IDE0161: Convert to file-scoped namespace -dotnet_diagnostic.ide0161.severity = warning +dotnet_diagnostic.IDE0161.severity = warning # IDE0200: Lambda expression can be removed -dotnet_diagnostic.ide0200.severity = warning +dotnet_diagnostic.IDE0200.severity = warning # IDE2000: Disallow multiple blank lines 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}] # CA1018: Mark attributes with AttributeUsageAttribute -dotnet_diagnostic.ca1018.severity = suggestion +dotnet_diagnostic.CA1018.severity = suggestion # CA1507: Use nameof to express symbol names -dotnet_diagnostic.ca1507.severity = suggestion +dotnet_diagnostic.CA1507.severity = suggestion # CA1510: Use ArgumentNullException throw helper -dotnet_diagnostic.ca1510.severity = suggestion +dotnet_diagnostic.CA1510.severity = suggestion # CA1511: Use ArgumentException throw helper -dotnet_diagnostic.ca1511.severity = suggestion +dotnet_diagnostic.CA1511.severity = suggestion # CA1512: Use ArgumentOutOfRangeException throw helper -dotnet_diagnostic.ca1512.severity = suggestion +dotnet_diagnostic.CA1512.severity = suggestion # CA1513: Use ObjectDisposedException throw helper -dotnet_diagnostic.ca1513.severity = suggestion +dotnet_diagnostic.CA1513.severity = suggestion # CA1802: Use literals where appropriate -dotnet_diagnostic.ca1802.severity = suggestion +dotnet_diagnostic.CA1802.severity = suggestion # CA1805: Do not initialize unnecessarily -dotnet_diagnostic.ca1805.severity = suggestion +dotnet_diagnostic.CA1805.severity = suggestion # CA1810: Do not initialize unnecessarily -dotnet_diagnostic.ca1810.severity = suggestion +dotnet_diagnostic.CA1810.severity = suggestion # CA1822: Make member static -dotnet_diagnostic.ca1822.severity = suggestion +dotnet_diagnostic.CA1822.severity = suggestion # 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 -dotnet_diagnostic.ca1826.severity = suggestion +dotnet_diagnostic.CA1826.severity = suggestion # 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 -dotnet_diagnostic.ca1829.severity = suggestion +dotnet_diagnostic.CA1829.severity = suggestion # 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 -dotnet_diagnostic.ca1832.severity = suggestion +dotnet_diagnostic.CA1832.severity = suggestion # 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 -dotnet_diagnostic.ca1834.severity = suggestion +dotnet_diagnostic.CA1834.severity = suggestion # CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' -dotnet_diagnostic.ca1835.severity = suggestion +dotnet_diagnostic.CA1835.severity = suggestion # CA1837: Use 'Environment.ProcessId' -dotnet_diagnostic.ca1837.severity = suggestion +dotnet_diagnostic.CA1837.severity = suggestion # CA1838: Avoid 'StringBuilder' parameters for P/Invokes -dotnet_diagnostic.ca1838.severity = suggestion +dotnet_diagnostic.CA1838.severity = suggestion # 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' -dotnet_diagnostic.ca1844.severity = suggestion +dotnet_diagnostic.CA1844.severity = suggestion # CA1845: Use span-based 'string.Concat' -dotnet_diagnostic.ca1845.severity = suggestion +dotnet_diagnostic.CA1845.severity = suggestion # 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 -dotnet_diagnostic.ca1847.severity = suggestion +dotnet_diagnostic.CA1847.severity = suggestion # CA1852: Seal internal types -dotnet_diagnostic.ca1852.severity = suggestion +dotnet_diagnostic.CA1852.severity = suggestion # CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method -dotnet_diagnostic.ca1854.severity = suggestion +dotnet_diagnostic.CA1854.severity = suggestion # CA1855: Prefer 'Clear' over 'Fill' -dotnet_diagnostic.ca1855.severity = suggestion +dotnet_diagnostic.CA1855.severity = suggestion # CA1856: Incorrect usage of ConstantExpected attribute -dotnet_diagnostic.ca1856.severity = suggestion +dotnet_diagnostic.CA1856.severity = suggestion # CA1857: A constant is expected for the parameter -dotnet_diagnostic.ca1857.severity = suggestion +dotnet_diagnostic.CA1857.severity = suggestion # CA1858: Use 'StartsWith' instead of 'IndexOf' -dotnet_diagnostic.ca1858.severity = suggestion +dotnet_diagnostic.CA1858.severity = suggestion # 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 -dotnet_diagnostic.ca2008.severity = suggestion +dotnet_diagnostic.CA2008.severity = suggestion # CA2012: Use ValueTask correctly -dotnet_diagnostic.ca2012.severity = suggestion +dotnet_diagnostic.CA2012.severity = suggestion # 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. -dotnet_diagnostic.ca2249.severity = suggestion +dotnet_diagnostic.CA2249.severity = suggestion # 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) -dotnet_diagnostic.ide0020.severity = suggestion +dotnet_diagnostic.IDE0020.severity = suggestion # IDE0029: Use coalesce expression (non-nullable types) -dotnet_diagnostic.ide0029.severity = suggestion +dotnet_diagnostic.IDE0029.severity = suggestion # IDE0030: Use coalesce expression (nullable types) -dotnet_diagnostic.ide0030.severity = suggestion +dotnet_diagnostic.IDE0030.severity = suggestion # 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) -dotnet_diagnostic.ide0038.severity = suggestion +dotnet_diagnostic.IDE0038.severity = suggestion # IDE0044: Make field readonly -dotnet_diagnostic.ide0044.severity = suggestion +dotnet_diagnostic.IDE0044.severity = suggestion # IDE0051: Remove unused private members -dotnet_diagnostic.ide0051.severity = suggestion +dotnet_diagnostic.IDE0051.severity = suggestion # IDE0059: Unnecessary assignment to a value -dotnet_diagnostic.ide0059.severity = suggestion +dotnet_diagnostic.IDE0059.severity = suggestion # IDE0060: Remove unused parameters -dotnet_diagnostic.ide0060.severity = suggestion +dotnet_diagnostic.IDE0060.severity = suggestion # IDE0062: Make local function static -dotnet_diagnostic.ide0062.severity = suggestion +dotnet_diagnostic.IDE0062.severity = suggestion # 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 -dotnet_diagnostic.ca2016.severity = suggestion +dotnet_diagnostic.CA2016.severity = suggestion # 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}}] # CA1822: Make member static -dotnet_diagnostic.ca1822.severity = silent +dotnet_diagnostic.CA1822.severity = silent # IDE0011: Use braces -dotnet_diagnostic.ide0011.severity = silent +dotnet_diagnostic.IDE0011.severity = silent # IDE0055: Fix formatting -dotnet_diagnostic.ide0055.severity = silent +dotnet_diagnostic.IDE0055.severity = silent # IDE0060: Remove unused parameters -dotnet_diagnostic.ide0060.severity = silent +dotnet_diagnostic.IDE0060.severity = silent # IDE0062: Make local function static -dotnet_diagnostic.ide0062.severity = silent +dotnet_diagnostic.IDE0062.severity = silent # IDE0161: Convert to file-scoped namespace -dotnet_diagnostic.ide0161.severity = silent +dotnet_diagnostic.IDE0161.severity = silent [{**/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. -dotnet_diagnostic.ide0005.severity = silent +dotnet_diagnostic.IDE0005.severity = silent diff --git a/PacketMediator.Generator/PacketMediator.Generator.Sample/Examples.cs b/PacketMediator.Generator/PacketMediator.Generator.Sample/Examples.cs new file mode 100644 index 0000000..8d77224 --- /dev/null +++ b/PacketMediator.Generator/PacketMediator.Generator.Sample/Examples.cs @@ -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 { + public GamePacketIdAttribute(OperationCode code) : base(code) { + } +} + +public class RandomSession { + +} + +public class HandlerA : IPacketHandler { + public async Task HandleAsync(StructA packet, RandomSession session, CancellationToken cancellationToken) { + await Task.Delay(2000, cancellationToken); + throw new NotImplementedException(); + } +} diff --git a/PacketMediator.Generator/PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj b/PacketMediator.Generator/PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj new file mode 100644 index 0000000..a501867 --- /dev/null +++ b/PacketMediator.Generator/PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj @@ -0,0 +1,16 @@ + + + + true + $(BaseIntermediateOutputPath)Generated + + net9.0 + enable + PacketMediator.Generator.Sample + + + + + + + diff --git a/SourceGenerators1/SourceGenerators1.Tests/SourceGenerators1.Tests.csproj b/PacketMediator.Generator/PacketMediator.Generator.Tests/PacketMediator.Generator.Tests.csproj similarity index 57% rename from SourceGenerators1/SourceGenerators1.Tests/SourceGenerators1.Tests.csproj rename to PacketMediator.Generator/PacketMediator.Generator.Tests/PacketMediator.Generator.Tests.csproj index abb5fc2..183ac8b 100644 --- a/SourceGenerators1/SourceGenerators1.Tests/SourceGenerators1.Tests.csproj +++ b/PacketMediator.Generator/PacketMediator.Generator.Tests/PacketMediator.Generator.Tests.csproj @@ -1,26 +1,27 @@ - net8.0 + net9.0 enable false - SourceGenerators1.Tests + PacketMediator.Generator.Tests - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/SourceGenerators1/SourceGenerators1.Tests/SampleSourceGeneratorTests.cs b/PacketMediator.Generator/PacketMediator.Generator.Tests/SourceGeneratorWithAdditionalFilesTests.cs similarity index 63% rename from SourceGenerators1/SourceGenerators1.Tests/SampleSourceGeneratorTests.cs rename to PacketMediator.Generator/PacketMediator.Generator.Tests/SourceGeneratorWithAdditionalFilesTests.cs index 9d272f6..90953e2 100644 --- a/SourceGenerators1/SourceGenerators1.Tests/SampleSourceGeneratorTests.cs +++ b/PacketMediator.Generator/PacketMediator.Generator.Tests/SourceGeneratorWithAdditionalFilesTests.cs @@ -1,12 +1,14 @@ +using System.Collections.Immutable; using System.IO; using System.Linq; -using SourceGenerators1.Tests.Utils; +using Microsoft.CodeAnalysis; +using PacketMediator.Generator.Tests.Utils; using Microsoft.CodeAnalysis.CSharp; using Xunit; -namespace SourceGenerators1.Tests; +namespace PacketMediator.Generator.Tests; -public class SampleSourceGeneratorTests +public class SourceGeneratorWithAdditionalFilesTests { private const string DddRegistryText = @"User Document @@ -16,19 +18,20 @@ Customer"; public void GenerateClassesBasedOnDDDRegistry() { // Create an instance of the source generator. - var generator = new SampleSourceGenerator(); + var generator = new SourceGeneratorWithAdditionalFiles(); // Source generators should be tested using 'GeneratorDriver'. - var driver = CSharpGeneratorDriver.Create(new[] { generator }, - new[] - { - // Add the additional file separately from the compilation. + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); + + // Add the additional file separately from the compilation. + driver = driver.AddAdditionalTexts( + ImmutableArray.Create( new TestAdditionalFile("./DDD.UbiquitousLanguageRegistry.txt", DddRegistryText) - } + ) ); // 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. driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _); diff --git a/SourceGenerators1/SourceGenerators1.Tests/SampleIncrementalSourceGeneratorTests.cs b/PacketMediator.Generator/PacketMediator.Generator.Tests/SourceGeneratorWithAttributesTests.cs similarity index 88% rename from SourceGenerators1/SourceGenerators1.Tests/SampleIncrementalSourceGeneratorTests.cs rename to PacketMediator.Generator/PacketMediator.Generator.Tests/SourceGeneratorWithAttributesTests.cs index 7ac24dd..c7925b0 100644 --- a/SourceGenerators1/SourceGenerators1.Tests/SampleIncrementalSourceGeneratorTests.cs +++ b/PacketMediator.Generator/PacketMediator.Generator.Tests/SourceGeneratorWithAttributesTests.cs @@ -3,9 +3,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Xunit; -namespace SourceGenerators1.Tests; +namespace PacketMediator.Generator.Tests; -public class SampleIncrementalSourceGeneratorTests +public class SourceGeneratorWithAttributesTests { private const string VectorClassText = @" namespace TestNamespace; @@ -40,13 +40,13 @@ partial class Vector3 public void GenerateReportMethod() { // Create an instance of the source generator. - var generator = new SampleIncrementalSourceGenerator(); + var generator = new SourceGeneratorWithAttributes(); // Source generators should be tested using 'GeneratorDriver'. var driver = CSharpGeneratorDriver.Create(generator); // 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[] { diff --git a/SourceGenerators1/SourceGenerators1.Tests/Utils/TestAdditionalFile.cs b/PacketMediator.Generator/PacketMediator.Generator.Tests/Utils/TestAdditionalFile.cs similarity index 90% rename from SourceGenerators1/SourceGenerators1.Tests/Utils/TestAdditionalFile.cs rename to PacketMediator.Generator/PacketMediator.Generator.Tests/Utils/TestAdditionalFile.cs index 40297c4..1f1fec5 100644 --- a/SourceGenerators1/SourceGenerators1.Tests/Utils/TestAdditionalFile.cs +++ b/PacketMediator.Generator/PacketMediator.Generator.Tests/Utils/TestAdditionalFile.cs @@ -2,7 +2,7 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -namespace SourceGenerators1.Tests.Utils; +namespace PacketMediator.Generator.Tests.Utils; public class TestAdditionalFile : AdditionalText { diff --git a/SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj b/PacketMediator.Generator/PacketMediator.Generator/PacketMediator.Generator.csproj similarity index 59% rename from SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj rename to PacketMediator.Generator/PacketMediator.Generator/PacketMediator.Generator.csproj index 99cd737..3d0d4f6 100644 --- a/SourceGenerators1/SourceGenerators1/SourceGenerators1.csproj +++ b/PacketMediator.Generator/PacketMediator.Generator/PacketMediator.Generator.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + netstandard2.0 false enable latest @@ -9,22 +9,16 @@ true true - SourceGenerators1 - SourceGenerators1 + PacketMediator.Generator + PacketMediator.Generator - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + diff --git a/PacketMediator.Generator/PacketMediator.Generator/PacketMediatorGenerator.cs b/PacketMediator.Generator/PacketMediator.Generator/PacketMediatorGenerator.cs new file mode 100644 index 0000000..5e917ed --- /dev/null +++ b/PacketMediator.Generator/PacketMediator.Generator/PacketMediatorGenerator.cs @@ -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 : 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 : IPacketHandler + where TIncomingPacket : IIncomingPacket +{ + async Task IPacketHandler.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 +{ + Task 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() + .FirstOrDefault(f => f.ConstantValue?.Equals(enumValue) == true); + var enumMaxValue = enumType?.GetMembers() + .OfType().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(); + 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 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)); + }); + } +} diff --git a/SourceGenerators1/SourceGenerators1/Properties/launchSettings.json b/PacketMediator.Generator/PacketMediator.Generator/Properties/launchSettings.json similarity index 64% rename from SourceGenerators1/SourceGenerators1/Properties/launchSettings.json rename to PacketMediator.Generator/PacketMediator.Generator/Properties/launchSettings.json index 7254224..a2105fc 100644 --- a/SourceGenerators1/SourceGenerators1/Properties/launchSettings.json +++ b/PacketMediator.Generator/PacketMediator.Generator/Properties/launchSettings.json @@ -3,7 +3,7 @@ "profiles": { "DebugRoslynSourceGenerator": { "commandName": "DebugRoslynComponent", - "targetProject": "../SourceGenerators1.Sample/SourceGenerators1.Sample.csproj" + "targetProject": "../PacketMediator.Generator.Sample/PacketMediator.Generator.Sample.csproj" } } } \ No newline at end of file diff --git a/SourceGenerators1/SourceGenerators1/Readme.md b/PacketMediator.Generator/PacketMediator.Generator/Readme.md similarity index 83% rename from SourceGenerators1/SourceGenerators1/Readme.md rename to PacketMediator.Generator/PacketMediator.Generator/Readme.md index cc30b4f..a0d1ef8 100644 --- a/SourceGenerators1/SourceGenerators1/Readme.md +++ b/PacketMediator.Generator/PacketMediator.Generator/Readme.md @@ -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. ## Content -### SourceGenerators1 +### PacketMediator.Generator A .NET Standard project with implementations of sample source generators. **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). - [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 -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. +### PacketMediator.Generator.Sample +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. ## How To? diff --git a/PacketMediator.Samples/PacketMediator.Samples.csproj b/PacketMediator.Samples/PacketMediator.Samples.csproj index 7d9bb9b..0d15a37 100644 --- a/PacketMediator.Samples/PacketMediator.Samples.csproj +++ b/PacketMediator.Samples/PacketMediator.Samples.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable diff --git a/PacketMediator.Tests/PacketMediator.Tests.csproj b/PacketMediator.Tests/PacketMediator.Tests.csproj index 0ec6f08..f5c5480 100644 --- a/PacketMediator.Tests/PacketMediator.Tests.csproj +++ b/PacketMediator.Tests/PacketMediator.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -10,14 +10,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/PacketMediator.sln b/PacketMediator.sln deleted file mode 100644 index 68a3d38..0000000 --- a/PacketMediator.sln +++ /dev/null @@ -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 diff --git a/PacketMediator.slnx b/PacketMediator.slnx new file mode 100644 index 0000000..f8c4ba9 --- /dev/null +++ b/PacketMediator.slnx @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/RaiNote.PacketMediator/IBidirectionalPacket.cs b/RaiNote.PacketMediator/IBidirectionalPacket.cs deleted file mode 100644 index 657dc87..0000000 --- a/RaiNote.PacketMediator/IBidirectionalPacket.cs +++ /dev/null @@ -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; diff --git a/RaiNote.PacketMediator/IIncomingPacket.cs b/RaiNote.PacketMediator/IIncomingPacket.cs deleted file mode 100644 index 4384b7c..0000000 --- a/RaiNote.PacketMediator/IIncomingPacket.cs +++ /dev/null @@ -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); -} diff --git a/RaiNote.PacketMediator/IOutgoingPacket.cs b/RaiNote.PacketMediator/IOutgoingPacket.cs deleted file mode 100644 index 1f3b131..0000000 --- a/RaiNote.PacketMediator/IOutgoingPacket.cs +++ /dev/null @@ -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(); -} diff --git a/RaiNote.PacketMediator/IPacket.cs b/RaiNote.PacketMediator/IPacket.cs deleted file mode 100644 index 66b95f1..0000000 --- a/RaiNote.PacketMediator/IPacket.cs +++ /dev/null @@ -1,5 +0,0 @@ -// Licensed to Timothy Schenk under the Apache 2.0 License. - -namespace RaiNote.PacketMediator; - -public interface IPacket; diff --git a/RaiNote.PacketMediator/IPacketHandler.cs b/RaiNote.PacketMediator/IPacketHandler.cs deleted file mode 100644 index 502a4f4..0000000 --- a/RaiNote.PacketMediator/IPacketHandler.cs +++ /dev/null @@ -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 : IPacketHandler - where TIncomingPacket : IIncomingPacket -{ - async Task IPacketHandler.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 -{ - Task TryHandleAsync(IIncomingPacket packet, TSession session, CancellationToken cancellationToken); -} diff --git a/RaiNote.PacketMediator/IntermediatePacketHandlerData.cs b/RaiNote.PacketMediator/IntermediatePacketHandlerData.cs new file mode 100644 index 0000000..bb417a7 --- /dev/null +++ b/RaiNote.PacketMediator/IntermediatePacketHandlerData.cs @@ -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; } +} diff --git a/RaiNote.PacketMediator/IntermediatePacketStructHandlerData.cs b/RaiNote.PacketMediator/IntermediatePacketStructHandlerData.cs new file mode 100644 index 0000000..72e1658 --- /dev/null +++ b/RaiNote.PacketMediator/IntermediatePacketStructHandlerData.cs @@ -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; } +} diff --git a/RaiNote.PacketMediator/PacketDistributor.cs b/RaiNote.PacketMediator/PacketDistributor.cs deleted file mode 100644 index 9c3dd17..0000000 --- a/RaiNote.PacketMediator/PacketDistributor.cs +++ /dev/null @@ -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 where TPacketIdEnum : Enum -{ - private readonly Channel> _channel; - - private readonly ImmutableDictionary> _deserializationMap; - - private readonly ConcurrentDictionary?> _packetHandlersInstantiation; - - public PacketDistributor(IServiceProvider serviceProvider, - IEnumerable sourcesContainingPackets, IEnumerable sourcesContainingPacketHandlers) - { - _channel = Channel.CreateUnbounded>(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>(); - _packetHandlersInstantiation = new ConcurrentDictionary?>(); - packetHandlers.ForEach(packetHandlerPair => - { - var packetHandler = - ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, - packetHandlerPair.Value); - _packetHandlersInstantiation.TryAdd(packetHandlerPair.Key, packetHandler as IPacketHandler); - }); - allIncomingPackets.ForEach(packetsType => - { - var lambda = CodeGenerator.Lambda>(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 PacketIdMap { get; } - - private static IEnumerable> GetAllPackets( - IEnumerable sourcesContainingPackets, Type packetType) - { - var packetsWithId = sourcesContainingPackets.SelectMany(a => a.GetTypes() - .Where(type => type is { IsInterface: false, IsAbstract: false } && - type.GetInterfaces().Contains(packetType) - && type.GetCustomAttributes>().Any() - )) - .Select(type => - new { Type = type, Attribute = type.GetCustomAttribute>() }) - .Select(x => new KeyValuePair(x.Attribute!.Code, x.Type)); - - return packetsWithId; - } - - private static IEnumerable> GetAllPacketHandlersWithId( - IEnumerable 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>() - })) - .Where(x => x.PacketId != null) - .Select(x => new KeyValuePair(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)!; - } -} diff --git a/RaiNote.PacketMediator/PacketDistributorService.cs b/RaiNote.PacketMediator/PacketDistributorService.cs deleted file mode 100644 index 65f4938..0000000 --- a/RaiNote.PacketMediator/PacketDistributorService.cs +++ /dev/null @@ -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 : IHostedService - where TPacketIdEnum : Enum -{ - private readonly PacketDistributor _packetDistributor; - - public PacketDistributorService(IServiceProvider serviceProvider, - IEnumerable sourcesContainingPackets, IEnumerable sourcesContainingPacketHandlers) - { - _packetDistributor = new PacketDistributor(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 GetOperationCodeByPacketType(IPacket packet) - { - var type = packet.GetType(); - _packetDistributor.PacketIdMap.TryGetValue(type, out var value); - return value ?? DotNext.Optional.None; - } -} diff --git a/RaiNote.PacketMediator/PacketHandlerGenerator.cs b/RaiNote.PacketMediator/PacketHandlerGenerator.cs deleted file mode 100644 index f468967..0000000 --- a/RaiNote.PacketMediator/PacketHandlerGenerator.cs +++ /dev/null @@ -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 GetAttributesInheriting(GeneratorSyntaxContext context) - { - var arrayOfInheritingTypes = new List(); - 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> 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)); - } -} diff --git a/RaiNote.PacketMediator/PacketIdAttribute.cs b/RaiNote.PacketMediator/PacketIdAttribute.cs deleted file mode 100644 index 69c3c4d..0000000 --- a/RaiNote.PacketMediator/PacketIdAttribute.cs +++ /dev/null @@ -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 : Attribute where TPacketIdEnum : Enum -{ - protected PacketIdAttribute(TPacketIdEnum code) - { - Code = code; - } - - public TPacketIdEnum Code { get; } -} diff --git a/RaiNote.PacketMediator/PacketMediatorGenerator.cs b/RaiNote.PacketMediator/PacketMediatorGenerator.cs new file mode 100644 index 0000000..9afcfba --- /dev/null +++ b/RaiNote.PacketMediator/PacketMediatorGenerator.cs @@ -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 : 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 : IPacketHandler + where TIncomingPacket : IIncomingPacket +{ + async Task IPacketHandler.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 +{ + Task 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() + .FirstOrDefault(f => f.ConstantValue?.Equals(enumValue) == true); + var enumMaxValue = enumType?.GetMembers() + .OfType().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(); + 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 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)); + }); + } +} diff --git a/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj b/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj index a8b4d55..f0c105f 100644 --- a/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj +++ b/RaiNote.PacketMediator/RaiNote.PacketMediator.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0 enable enable $(PackageVersion) @@ -21,25 +21,24 @@ true snupkg true + true + default - - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SourceGenerators1/SourceGenerators1.Sample/Examples.cs b/SourceGenerators1/SourceGenerators1.Sample/Examples.cs deleted file mode 100644 index 131971e..0000000 --- a/SourceGenerators1/SourceGenerators1.Sample/Examples.cs +++ /dev/null @@ -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 -{ - public GamePacketIdAttribute(OperationCode code) : base(code) - { - } -} - -public enum OperationCode : ushort -{ - LoginRequest = 1 -} - diff --git a/SourceGenerators1/SourceGenerators1.Sample/SourceGenerators1.Sample.csproj b/SourceGenerators1/SourceGenerators1.Sample/SourceGenerators1.Sample.csproj deleted file mode 100644 index 2b388ba..0000000 --- a/SourceGenerators1/SourceGenerators1.Sample/SourceGenerators1.Sample.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net8.0 - enable - SourceGenerators1.Sample - - - - - - - - - - - - - - - diff --git a/SourceGenerators1/SourceGenerators1/SampleIncrementalSourceGenerator.cs b/SourceGenerators1/SourceGenerators1/SampleIncrementalSourceGenerator.cs deleted file mode 100644 index 6e92121..0000000 --- a/SourceGenerators1/SourceGenerators1/SampleIncrementalSourceGenerator.cs +++ /dev/null @@ -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; - -/// -/// 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. -/// -[Generator] -public class SampleIncrementalSourceGenerator : IIncrementalGenerator -{ - private const string Namespace = "Generators"; - private const string AttributeName = "ReportAttribute"; - - private const string AttributeSourceCode = $@"// - -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)) - ); - } - - /// - /// Checks whether the Node is annotated with the [Report] attribute and maps syntax context to the specific node type (ClassDeclarationSyntax). - /// - /// Syntax context, based on CreateSyntaxProvider predicate - /// The specific cast and whether the attribute was found. - 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); - } - - /// - /// Generate code action. - /// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [Report] attribute) changed by the user. - /// - /// Source generation context used to add source files. - /// Compilation used to provide access to the Semantic Model. - /// Nodes annotated with the [Report] attribute that trigger the generate action. - private void GenerateCode(SourceProductionContext context, Compilation compilation, - ImmutableArray 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() - .Select(p => - $@" yield return $""{p.Name}:{{this.{p.Name}}}"";" - ); // e.g. yield return $"Id:{this.Id}"; - - // Build up the source code - var code = $@"// - -using System; -using System.Collections.Generic; - -namespace {namespaceName}; - -partial class {className} -{{ - public IEnumerable Report() - {{ -{string.Join("\n", methodBody)} - }} -}} -"; - - // Add the source code to the compilation. - context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8)); - } - } -} diff --git a/SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs b/SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs deleted file mode 100644 index d70106f..0000000 --- a/SourceGenerators1/SourceGenerators1/SampleSourceGenerator.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System.IO; -using Microsoft.CodeAnalysis; - -namespace SourceGenerators1; - -/// -/// 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. -/// -[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 = $@"// - -namespace Entities -{{ - public partial class {className} - {{ - }} -}} -"; - - // Add the source code to the compilation. - context.AddSource($"{className}.g.cs", source); - } - } - } -} diff --git a/SourceGenerators1/SourceGenerators1/Test.cs b/SourceGenerators1/SourceGenerators1/Test.cs deleted file mode 100644 index 6edc886..0000000 --- a/SourceGenerators1/SourceGenerators1/Test.cs +++ /dev/null @@ -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()) - { - 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()) - { - 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 Right) tuple) - { - var dependencies = new string[] { "System", "System.Threading.Channels", "SourceGenerators1.Sample" }; - /* - var lambda = CodeGenerator.Lambda>(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 = $$""" - // - {{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 : 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; diff --git a/global.json b/global.json index da7a596..e533352 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "8.0.203", + "version": "9.0.0", "rollForward": "latestMinor", - "allowPrerelease": false + "allowPrerelease": true } }