From 48d92a18ed2392ed234879f81ebb92177ae06f15 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 5 Aug 2021 16:08:48 -0700 Subject: [PATCH 1/6] Extract shared project for InterfaceStubGenerator implementation --- .../InterfaceStubGenerator.Core.csproj | 2 ++ .../ITypeSymbolExtensions.cs | 0 .../InterfaceStubGenerator.Shared.projitems | 15 +++++++++++++++ .../InterfaceStubGenerator.Shared.shproj | 13 +++++++++++++ .../InterfaceStubGenerator.cs | 0 Refit.sln | 6 ++++++ 6 files changed, 36 insertions(+) rename {InterfaceStubGenerator.Core => InterfaceStubGenerator.Shared}/ITypeSymbolExtensions.cs (100%) create mode 100644 InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems create mode 100644 InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.shproj rename {InterfaceStubGenerator.Core => InterfaceStubGenerator.Shared}/InterfaceStubGenerator.cs (100%) diff --git a/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj b/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj index 68c931c4f..95a2281f1 100644 --- a/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj +++ b/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj @@ -21,4 +21,6 @@ + + diff --git a/InterfaceStubGenerator.Core/ITypeSymbolExtensions.cs b/InterfaceStubGenerator.Shared/ITypeSymbolExtensions.cs similarity index 100% rename from InterfaceStubGenerator.Core/ITypeSymbolExtensions.cs rename to InterfaceStubGenerator.Shared/ITypeSymbolExtensions.cs diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems new file mode 100644 index 000000000..5589f69c2 --- /dev/null +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.projitems @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + b591423d-f92d-4e00-b0eb-615c9853506c + + + InterfaceStubGenerator.Shared + + + + + + \ No newline at end of file diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.shproj b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.shproj new file mode 100644 index 000000000..fa970afbf --- /dev/null +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.Shared.shproj @@ -0,0 +1,13 @@ + + + + b591423d-f92d-4e00-b0eb-615c9853506c + 14.0 + + + + + + + + diff --git a/InterfaceStubGenerator.Core/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs similarity index 100% rename from InterfaceStubGenerator.Core/InterfaceStubGenerator.cs rename to InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs diff --git a/Refit.sln b/Refit.sln index 7776f0ce8..39689b78a 100644 --- a/Refit.sln +++ b/Refit.sln @@ -25,7 +25,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit.Newtonsoft.Json", "Re EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Core", "InterfaceStubGenerator.Core\InterfaceStubGenerator.Core.csproj", "{72869789-0310-4916-9A41-20D16A01C1B8}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InterfaceStubGenerator.Shared", "InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.shproj", "{B591423D-F92D-4E00-B0EB-615C9853506C}" +EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{72869789-0310-4916-9a41-20d16a01c1b8}*SharedItemsImports = 5 + InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{b591423d-f92d-4e00-b0eb-615c9853506c}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM From 38b510f5a230f2370a3688da1a96bbcd9b88e949 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 5 Aug 2021 16:14:00 -0700 Subject: [PATCH 2/6] Add a second implementation assembly for Roslyn 4.0+ --- .../InterfaceStubGenerator.Core.csproj | 1 + .../InterfaceStubGenerator.Roslyn40.csproj | 27 +++++++++++++++++++ .../InterfaceStubGenerator.cs | 8 +++--- Refit.sln | 19 +++++++++++++ Refit/Refit.csproj | 12 ++++++--- Refit/targets/refit.props | 13 +++++++++ 6 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj diff --git a/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj b/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj index 95a2281f1..6e9ca887a 100644 --- a/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj +++ b/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj @@ -2,6 +2,7 @@ netstandard2.0 + InterfaceStubGeneratorV1 Refit.Generator false ..\buildtask.snk diff --git a/InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj b/InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj new file mode 100644 index 000000000..60d8e3ebf --- /dev/null +++ b/InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj @@ -0,0 +1,27 @@ + + + + netstandard2.0 + InterfaceStubGeneratorV2 + Refit.Generator + false + ..\buildtask.snk + true + true + enable + + + + + + + + + $(BuildVersion) + $(BuildVersionSimple) + + + + + + diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs index 1ca673cdb..1131f562f 100644 --- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs @@ -23,7 +23,7 @@ public class InterfaceStubGenerator : ISourceGenerator static readonly DiagnosticDescriptor InvalidRefitMember = new( "RF001", "Refit types must have Refit HTTP method attributes", - "Method {0}.{1} either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument.", + "Method {0}.{1} either has no Refit HTTP method attribute or you've used something other than a string literal for the 'path' argument", "Refit", DiagnosticSeverity.Warning, true); @@ -68,7 +68,9 @@ public void GenerateInterfaceStubs(GeneratorExecutionContext context) // Check the candidates and keep the ones we're actually interested in - var interfaceToNullableEnabledMap = new Dictionary(); +#pragma warning disable RS1024 // Compare symbols correctly + var interfaceToNullableEnabledMap = new Dictionary(SymbolEqualityComparer.Default); +#pragma warning restore RS1024 // Compare symbols correctly var methodSymbols = new List(); foreach (var group in receiver.CandidateMethods.GroupBy(m => m.SyntaxTree)) { @@ -88,7 +90,7 @@ public void GenerateInterfaceStubs(GeneratorExecutionContext context) } } - var interfaces = methodSymbols.GroupBy(m => m.ContainingType) + var interfaces = methodSymbols.GroupBy(m => m.ContainingType, SymbolEqualityComparer.Default) .ToDictionary(g => g.Key, v => v.ToList()); // Look through the candidate interfaces diff --git a/Refit.sln b/Refit.sln index 39689b78a..2660e9a1b 100644 --- a/Refit.sln +++ b/Refit.sln @@ -27,9 +27,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Core EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InterfaceStubGenerator.Shared", "InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.shproj", "{B591423D-F92D-4E00-B0EB-615C9853506C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Roslyn40", "InterfaceStubGenerator.Roslyn40\InterfaceStubGenerator.Roslyn40.csproj", "{A4B61169-3314-41DB-8156-BE9677C90C9F}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{72869789-0310-4916-9a41-20d16a01c1b8}*SharedItemsImports = 5 + InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{a4b61169-3314-41db-8156-be9677c90c9f}*SharedItemsImports = 5 InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.projitems*{b591423d-f92d-4e00-b0eb-615c9853506c}*SharedItemsImports = 13 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -123,6 +126,22 @@ Global {72869789-0310-4916-9A41-20D16A01C1B8}.Release|x64.Build.0 = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|x86.ActiveCfg = Release|Any CPU {72869789-0310-4916-9A41-20D16A01C1B8}.Release|x86.Build.0 = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|ARM.ActiveCfg = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|ARM.Build.0 = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x64.Build.0 = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x86.ActiveCfg = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Debug|x86.Build.0 = Debug|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|Any CPU.Build.0 = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|ARM.ActiveCfg = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|ARM.Build.0 = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x64.ActiveCfg = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x64.Build.0 = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x86.ActiveCfg = Release|Any CPU + {A4B61169-3314-41DB-8156-BE9677C90C9F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Refit/Refit.csproj b/Refit/Refit.csproj index a1f7efaa7..779ea5639 100644 --- a/Refit/Refit.csproj +++ b/Refit/Refit.csproj @@ -17,6 +17,7 @@ + @@ -25,9 +26,14 @@ - - + + diff --git a/Refit/targets/refit.props b/Refit/targets/refit.props index c5eee5805..33c3801b7 100644 --- a/Refit/targets/refit.props +++ b/Refit/targets/refit.props @@ -4,4 +4,17 @@ + + + + + + + + + + + + + From 59cedd7fcc0d3e0037300b340e38f5b18565099a Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 6 Aug 2021 08:13:41 -0700 Subject: [PATCH 3/6] Rename project for clarity --- .../InterfaceStubGenerator.Roslyn38.csproj | 0 Refit.Tests/Refit.Tests.csproj | 2 +- Refit.sln | 2 +- Refit/Refit.csproj | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj => InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj (100%) diff --git a/InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj b/InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj similarity index 100% rename from InterfaceStubGenerator.Core/InterfaceStubGenerator.Core.csproj rename to InterfaceStubGenerator.Roslyn38/InterfaceStubGenerator.Roslyn38.csproj diff --git a/Refit.Tests/Refit.Tests.csproj b/Refit.Tests/Refit.Tests.csproj index dd6e29c49..1958287d8 100644 --- a/Refit.Tests/Refit.Tests.csproj +++ b/Refit.Tests/Refit.Tests.csproj @@ -23,7 +23,7 @@ - diff --git a/Refit.sln b/Refit.sln index 2660e9a1b..b275022a3 100644 --- a/Refit.sln +++ b/Refit.sln @@ -23,7 +23,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit.HttpClientFactory", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Refit.Newtonsoft.Json", "Refit.Newtonsoft.Json\Refit.Newtonsoft.Json.csproj", "{2210E606-1C91-4EA0-8876-3B2F501F2669}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Core", "InterfaceStubGenerator.Core\InterfaceStubGenerator.Core.csproj", "{72869789-0310-4916-9A41-20D16A01C1B8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterfaceStubGenerator.Roslyn38", "InterfaceStubGenerator.Roslyn38\InterfaceStubGenerator.Roslyn38.csproj", "{72869789-0310-4916-9A41-20D16A01C1B8}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "InterfaceStubGenerator.Shared", "InterfaceStubGenerator.Shared\InterfaceStubGenerator.Shared.shproj", "{B591423D-F92D-4E00-B0EB-615C9853506C}" EndProject diff --git a/Refit/Refit.csproj b/Refit/Refit.csproj index 779ea5639..f55fd7131 100644 --- a/Refit/Refit.csproj +++ b/Refit/Refit.csproj @@ -16,7 +16,7 @@ - + @@ -27,7 +27,7 @@ - From 3f1a59934e442c2b0f6a74779b785612ecffc8dd Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 6 Aug 2021 22:03:27 -0700 Subject: [PATCH 4/6] Add tests for the new incremental syntax generator --- .../InterfaceStubGenerator.Roslyn40.csproj | 1 + .../InterfaceStubGenerator.cs | 4 ++ Refit.Tests/InterfaceStubGenerator.cs | 46 +++++++++++++++++++ Refit.Tests/Refit.Tests.csproj | 3 +- ...crementalSourceGeneratorVerifier`1+Test.cs | 41 +++++++++++++++++ ...arpIncrementalSourceGeneratorVerifier`1.cs | 9 ++++ 6 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1+Test.cs create mode 100644 Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1.cs diff --git a/InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj b/InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj index 60d8e3ebf..4a8cded8d 100644 --- a/InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj +++ b/InterfaceStubGenerator.Roslyn40/InterfaceStubGenerator.Roslyn40.csproj @@ -9,6 +9,7 @@ true true enable + $(DefineConstants);ROSLYN_4 diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs index 1131f562f..9c7b1143e 100644 --- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs @@ -17,7 +17,11 @@ namespace Refit.Generator // defn's [Generator] +#if ROSLYN_4 + public class InterfaceStubGeneratorV2 : ISourceGenerator +#else public class InterfaceStubGenerator : ISourceGenerator +#endif { #pragma warning disable RS2008 // Enable analyzer release tracking static readonly DiagnosticDescriptor InvalidRefitMember = new( diff --git a/Refit.Tests/InterfaceStubGenerator.cs b/Refit.Tests/InterfaceStubGenerator.cs index 09df22413..4b7c7bbd1 100644 --- a/Refit.Tests/InterfaceStubGenerator.cs +++ b/Refit.Tests/InterfaceStubGenerator.cs @@ -16,6 +16,7 @@ using Task = System.Threading.Tasks.Task; using VerifyCS = Refit.Tests.CSharpSourceGeneratorVerifier; +using VerifyCSV2 = Refit.Tests.CSharpSourceGeneratorVerifier; namespace Refit.Tests { @@ -102,6 +103,7 @@ static Compilation CreateCompilation(params string[] sourceFiles) public async Task NoRefitInterfacesSmokeTest() { var input = File.ReadAllText(IntegrationTestHelper.GetPath("IInterfaceWithoutRefit.cs")); + await new VerifyCS.Test { ReferenceAssemblies = ReferenceAssemblies, @@ -111,6 +113,16 @@ public async Task NoRefitInterfacesSmokeTest() Sources = { input }, }, }.RunAsync(); + + await new VerifyCSV2.Test + { + ReferenceAssemblies = ReferenceAssemblies, + TestState = + { + AdditionalReferences = { RefitAssembly }, + Sources = { input }, + }, + }.RunAsync(); } [Fact] @@ -640,6 +652,24 @@ public RefitTestsTestNestedINestedGitHubApi(global::System.Net.Http.HttpClient c }, }, }.RunAsync(); + + await new VerifyCSV2.Test + { + ReferenceAssemblies = ReferenceAssemblies, + TestState = + { + AdditionalReferences = { RefitAssembly }, + Sources = { input }, + GeneratedSources = + { + (typeof(InterfaceStubGeneratorV2), "PreserveAttribute.g.cs", output1), + (typeof(InterfaceStubGeneratorV2), "Generated.g.cs", output1_5), + (typeof(InterfaceStubGeneratorV2), "IGitHubApi.g.cs", output2), + (typeof(InterfaceStubGeneratorV2), "IGitHubApiDisposable.g.cs", output3), + (typeof(InterfaceStubGeneratorV2), "INestedGitHubApi.g.cs", output4), + }, + }, + }.RunAsync(); } @@ -768,6 +798,22 @@ public IServiceWithoutNamespace(global::System.Net.Http.HttpClient client, globa }, }, }.RunAsync(); + + await new VerifyCSV2.Test + { + ReferenceAssemblies = ReferenceAssemblies, + TestState = + { + AdditionalReferences = { RefitAssembly }, + Sources = { input }, + GeneratedSources = + { + (typeof(InterfaceStubGeneratorV2), "PreserveAttribute.g.cs", output1), + (typeof(InterfaceStubGeneratorV2), "Generated.g.cs", output1_5), + (typeof(InterfaceStubGeneratorV2), "IServiceWithoutNamespace.g.cs", output2), + }, + }, + }.RunAsync(); } } diff --git a/Refit.Tests/Refit.Tests.csproj b/Refit.Tests/Refit.Tests.csproj index 1958287d8..e5a9df7e9 100644 --- a/Refit.Tests/Refit.Tests.csproj +++ b/Refit.Tests/Refit.Tests.csproj @@ -18,13 +18,14 @@ - + + diff --git a/Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1+Test.cs b/Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1+Test.cs new file mode 100644 index 000000000..c310c1295 --- /dev/null +++ b/Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1+Test.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Refit.Tests +{ + public static partial class CSharpIncrementalSourceGeneratorVerifier + where TIncrementalGenerator : IIncrementalGenerator, new() + { + public class Test : CSharpSourceGeneratorTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var compilationOptions = solution.GetProject(projectId).CompilationOptions; + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + + protected override IEnumerable GetSourceGenerators() + { + yield return new TIncrementalGenerator().AsSourceGenerator(); + } + + protected override ParseOptions CreateParseOptions() + { + var parseOptions = (CSharpParseOptions)base.CreateParseOptions(); + return parseOptions.WithLanguageVersion(LanguageVersion.Preview); + } + } + } +} diff --git a/Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1.cs b/Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1.cs new file mode 100644 index 000000000..9aa951242 --- /dev/null +++ b/Refit.Tests/Verifiers/CSharpIncrementalSourceGeneratorVerifier`1.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis; + +namespace Refit.Tests +{ + public static partial class CSharpIncrementalSourceGeneratorVerifier + where TIncrementalGenerator : IIncrementalGenerator, new() + { + } +} From 35cbeba3f04cac451c3cebd782d42627d7c1f2fd Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 6 Aug 2021 22:06:18 -0700 Subject: [PATCH 5/6] Avoid SyntaxReceiver usage in Visual Studio 2022 --- .../InterfaceStubGenerator.cs | 115 ++++++++++++++---- Refit.Tests/InterfaceStubGenerator.cs | 2 +- 2 files changed, 89 insertions(+), 28 deletions(-) diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs index 9c7b1143e..7273d8e6d 100644 --- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace Refit.Generator @@ -18,7 +19,7 @@ namespace Refit.Generator [Generator] #if ROSLYN_4 - public class InterfaceStubGeneratorV2 : ISourceGenerator + public class InterfaceStubGeneratorV2 : IIncrementalGenerator #else public class InterfaceStubGenerator : ISourceGenerator #endif @@ -41,31 +42,48 @@ public class InterfaceStubGenerator : ISourceGenerator true); #pragma warning restore RS2008 // Enable analyzer release tracking - public void Execute(GeneratorExecutionContext context) - { - GenerateInterfaceStubs(context); - } +#if !ROSLYN_4 - public void GenerateInterfaceStubs(GeneratorExecutionContext context) + public void Execute(GeneratorExecutionContext context) { if (context.SyntaxReceiver is not SyntaxReceiver receiver) return; - context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.RefitInternalNamespace", out var refitInternalNamespace); + GenerateInterfaceStubs( + context, + static (context, diagnostic) => context.ReportDiagnostic(diagnostic), + static (context, hintName, sourceText) => context.AddSource(hintName, sourceText), + (CSharpCompilation)context.Compilation, + context.AnalyzerConfigOptions, + receiver.CandidateMethods.ToImmutableArray(), + receiver.CandidateInterfaces.ToImmutableArray()); + } + +#endif + + public void GenerateInterfaceStubs( + TContext context, + Action reportDiagnostic, + Action addSource, + CSharpCompilation compilation, + AnalyzerConfigOptionsProvider analyzerConfigOptions, + ImmutableArray candidateMethods, + ImmutableArray candidateInterfaces) + { + analyzerConfigOptions.GlobalOptions.TryGetValue("build_property.RefitInternalNamespace", out var refitInternalNamespace); refitInternalNamespace = $"{refitInternalNamespace ?? string.Empty}RefitInternalGenerated"; // we're going to create a new compilation that contains the attribute. // TODO: we should allow source generators to provide source during initialize, so that this step isn't required. - var options = (context.Compilation as CSharpCompilation)!.SyntaxTrees[0].Options as CSharpParseOptions; - var compilation = context.Compilation; + var options = (CSharpParseOptions)compilation.SyntaxTrees[0].Options; var disposableInterfaceSymbol = compilation.GetTypeByMetadataName("System.IDisposable")!; var httpMethodBaseAttributeSymbol = compilation.GetTypeByMetadataName("Refit.HttpMethodAttribute"); if(httpMethodBaseAttributeSymbol == null) { - context.ReportDiagnostic(Diagnostic.Create(RefitNotReferenced, null)); + reportDiagnostic(context, Diagnostic.Create(RefitNotReferenced, null)); return; } @@ -76,7 +94,7 @@ public void GenerateInterfaceStubs(GeneratorExecutionContext context) var interfaceToNullableEnabledMap = new Dictionary(SymbolEqualityComparer.Default); #pragma warning restore RS1024 // Compare symbols correctly var methodSymbols = new List(); - foreach (var group in receiver.CandidateMethods.GroupBy(m => m.SyntaxTree)) + foreach (var group in candidateMethods.GroupBy(m => m.SyntaxTree)) { var model = compilation.GetSemanticModel(group.Key); foreach (var method in group) @@ -85,7 +103,7 @@ public void GenerateInterfaceStubs(GeneratorExecutionContext context) var methodSymbol = model.GetDeclaredSymbol(method); if (IsRefitMethod(methodSymbol, httpMethodBaseAttributeSymbol)) { - var isAnnotated = context.Compilation.Options.NullableContextOptions == NullableContextOptions.Enable || + var isAnnotated = compilation.Options.NullableContextOptions == NullableContextOptions.Enable || model.GetNullableContext(method.SpanStart) == NullableContext.Enabled; interfaceToNullableEnabledMap[methodSymbol!.ContainingType] = isAnnotated; @@ -99,7 +117,7 @@ public void GenerateInterfaceStubs(GeneratorExecutionContext context) // Look through the candidate interfaces var interfaceSymbols = new List(); - foreach(var group in receiver.CandidateInterfaces.GroupBy(i => i.SyntaxTree)) + foreach(var group in candidateInterfaces.GroupBy(i => i.SyntaxTree)) { var model = compilation.GetSemanticModel(group.Key); foreach (var iface in group) @@ -133,7 +151,7 @@ public void GenerateInterfaceStubs(GeneratorExecutionContext context) if(!interfaces.Any()) return; - var supportsNullable = ((CSharpParseOptions)context.ParseOptions).LanguageVersion >= LanguageVersion.CSharp8; + var supportsNullable = options.LanguageVersion >= LanguageVersion.CSharp8; var keyCount = new Dictionary(); @@ -158,10 +176,10 @@ sealed class PreserveAttribute : global::System.Attribute "; - compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options)); + compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options)); // add the attribute text - context.AddSource("PreserveAttribute.g.cs", SourceText.From(attributeText, Encoding.UTF8)); + addSource(context, "PreserveAttribute.g.cs", SourceText.From(attributeText, Encoding.UTF8)); // get the newly bound attribute var preserveAttributeSymbol = compilation.GetTypeByMetadataName($"{refitInternalNamespace}.PreserveAttribute")!; @@ -183,7 +201,7 @@ internal static partial class Generated }} #pragma warning restore "; - context.AddSource("Generated.g.cs", SourceText.From(generatedClassText, Encoding.UTF8)); + addSource(context, "Generated.g.cs", SourceText.From(generatedClassText, Encoding.UTF8)); compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(generatedClassText, Encoding.UTF8), options)); @@ -196,14 +214,15 @@ internal static partial class Generated // with a refit attribute on them. Types may contain other members, without the attribute, which we'll // need to check for and error out on - var classSource = ProcessInterface(group.Key, + var classSource = ProcessInterface(context, + reportDiagnostic, + group.Key, group.Value, preserveAttributeSymbol, disposableInterfaceSymbol, httpMethodBaseAttributeSymbol, supportsNullable, - interfaceToNullableEnabledMap[group.Key], - context); + interfaceToNullableEnabledMap[group.Key]); var keyName = group.Key.Name; if(keyCount.TryGetValue(keyName, out var value)) @@ -212,19 +231,20 @@ internal static partial class Generated } keyCount[keyName] = value; - context.AddSource($"{keyName}.g.cs", SourceText.From(classSource, Encoding.UTF8)); + addSource(context, $"{keyName}.g.cs", SourceText.From(classSource, Encoding.UTF8)); } } - string ProcessInterface(INamedTypeSymbol interfaceSymbol, + string ProcessInterface(TContext context, + Action reportDiagnostic, + INamedTypeSymbol interfaceSymbol, List refitMethods, ISymbol preserveAttributeSymbol, ISymbol disposableInterfaceSymbol, INamedTypeSymbol httpMethodBaseAttributeSymbol, bool supportsNullable, - bool nullableEnabled, - GeneratorExecutionContext context) + bool nullableEnabled) { // Get the class name with the type parameters, then remove the namespace @@ -337,7 +357,7 @@ partial class {ns}{classDeclaration} !method.IsAbstract) // If an interface method has a body, it won't be abstract continue; - ProcessNonRefitMethod(source, method, context); + ProcessNonRefitMethod(context, reportDiagnostic, source, method); } // Handle Dispose @@ -464,7 +484,7 @@ void WriteConstraitsForTypeParameter(StringBuilder source, ITypeParameterSymbol } - void ProcessNonRefitMethod(StringBuilder source, IMethodSymbol methodSymbol, GeneratorExecutionContext context) + void ProcessNonRefitMethod(TContext context, Action reportDiagnostic, StringBuilder source, IMethodSymbol methodSymbol) { WriteMethodOpening(source, methodSymbol, true); @@ -477,7 +497,7 @@ void ProcessNonRefitMethod(StringBuilder source, IMethodSymbol methodSymbol, Gen foreach(var location in methodSymbol.Locations) { var diagnostic = Diagnostic.Create(InvalidRefitMember, location, methodSymbol.ContainingType.Name, methodSymbol.Name); - context.ReportDiagnostic(diagnostic); + reportDiagnostic(context, diagnostic); } } @@ -521,6 +541,45 @@ bool IsRefitMethod(IMethodSymbol? methodSymbol, INamedTypeSymbol httpMethodAttib return methodSymbol?.GetAttributes().Any(ad => ad.AttributeClass?.InheritsFromOrEquals(httpMethodAttibute) == true) == true; } +#if ROSLYN_4 + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // We're looking for methods with an attribute that are in an interface + var candidateMethodsProvider = context.SyntaxProvider.CreateSyntaxProvider( + (syntax, cancellationToken) => syntax is MethodDeclarationSyntax { Parent: InterfaceDeclarationSyntax, AttributeLists: { Count: > 0 } }, + (context, cancellationToken) => (MethodDeclarationSyntax)context.Node); + + // We also look for interfaces that derive from others, so we can see if any base methods contain + // Refit methods + var candidateInterfacesProvider = context.SyntaxProvider.CreateSyntaxProvider( + (syntax, cancellationToken) => syntax is InterfaceDeclarationSyntax { BaseList: not null }, + (context, cancellationToken) => (InterfaceDeclarationSyntax)context.Node); + + var inputs = candidateMethodsProvider.Collect() + .Combine(candidateInterfacesProvider.Collect()) + .Select((combined, cancellationToken) => (candidateMethods: combined.Left, candidateInterfaces: combined.Right)) + .Combine(context.AnalyzerConfigOptionsProvider) + .Combine(context.CompilationProvider) + .Select((combined, cancellationToken) => (combined.Left.Left.candidateMethods, combined.Left.Left.candidateInterfaces, analyzerConfigOptions: combined.Left.Right, compilation: combined.Right)); + + context.RegisterSourceOutput( + inputs, + (context, collectedValues) => + { + GenerateInterfaceStubs( + context, + static (context, diagnostic) => context.ReportDiagnostic(diagnostic), + static (context, hintName, sourceText) => context.AddSource(hintName, sourceText), + (CSharpCompilation)collectedValues.compilation, + collectedValues.analyzerConfigOptions, + collectedValues.candidateMethods, + collectedValues.candidateInterfaces); + }); + } + +#else + public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); @@ -550,5 +609,7 @@ methodDeclarationSyntax.Parent is InterfaceDeclarationSyntax && } } } + +#endif } } diff --git a/Refit.Tests/InterfaceStubGenerator.cs b/Refit.Tests/InterfaceStubGenerator.cs index 4b7c7bbd1..adfc35bfb 100644 --- a/Refit.Tests/InterfaceStubGenerator.cs +++ b/Refit.Tests/InterfaceStubGenerator.cs @@ -16,7 +16,7 @@ using Task = System.Threading.Tasks.Task; using VerifyCS = Refit.Tests.CSharpSourceGeneratorVerifier; -using VerifyCSV2 = Refit.Tests.CSharpSourceGeneratorVerifier; +using VerifyCSV2 = Refit.Tests.CSharpIncrementalSourceGeneratorVerifier; namespace Refit.Tests { From 482cf420369b5fa4dd1168129af39bf0ed09d3ab Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 6 Aug 2021 22:08:46 -0700 Subject: [PATCH 6/6] Extract RefitInternalNamespace as an incremental value --- .../InterfaceStubGenerator.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs index 7273d8e6d..e59526513 100644 --- a/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs +++ b/InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; namespace Refit.Generator @@ -49,12 +48,14 @@ public void Execute(GeneratorExecutionContext context) if (context.SyntaxReceiver is not SyntaxReceiver receiver) return; + context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.RefitInternalNamespace", out var refitInternalNamespace); + GenerateInterfaceStubs( context, static (context, diagnostic) => context.ReportDiagnostic(diagnostic), static (context, hintName, sourceText) => context.AddSource(hintName, sourceText), (CSharpCompilation)context.Compilation, - context.AnalyzerConfigOptions, + refitInternalNamespace, receiver.CandidateMethods.ToImmutableArray(), receiver.CandidateInterfaces.ToImmutableArray()); } @@ -66,12 +67,10 @@ public void GenerateInterfaceStubs( Action reportDiagnostic, Action addSource, CSharpCompilation compilation, - AnalyzerConfigOptionsProvider analyzerConfigOptions, + string? refitInternalNamespace, ImmutableArray candidateMethods, ImmutableArray candidateInterfaces) { - analyzerConfigOptions.GlobalOptions.TryGetValue("build_property.RefitInternalNamespace", out var refitInternalNamespace); - refitInternalNamespace = $"{refitInternalNamespace ?? string.Empty}RefitInternalGenerated"; // we're going to create a new compilation that contains the attribute. @@ -556,12 +555,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context) (syntax, cancellationToken) => syntax is InterfaceDeclarationSyntax { BaseList: not null }, (context, cancellationToken) => (InterfaceDeclarationSyntax)context.Node); + var refitInternalNamespace = context.AnalyzerConfigOptionsProvider.Select( + (analyzerConfigOptionsProvider, cancellationToken) => analyzerConfigOptionsProvider.GlobalOptions.TryGetValue("build_property.RefitInternalNamespace", out var refitInternalNamespace) ? refitInternalNamespace : null); + var inputs = candidateMethodsProvider.Collect() .Combine(candidateInterfacesProvider.Collect()) .Select((combined, cancellationToken) => (candidateMethods: combined.Left, candidateInterfaces: combined.Right)) - .Combine(context.AnalyzerConfigOptionsProvider) + .Combine(refitInternalNamespace) .Combine(context.CompilationProvider) - .Select((combined, cancellationToken) => (combined.Left.Left.candidateMethods, combined.Left.Left.candidateInterfaces, analyzerConfigOptions: combined.Left.Right, compilation: combined.Right)); + .Select((combined, cancellationToken) => (combined.Left.Left.candidateMethods, combined.Left.Left.candidateInterfaces, refitInternalNamespace: combined.Left.Right, compilation: combined.Right)); context.RegisterSourceOutput( inputs, @@ -572,7 +574,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (context, diagnostic) => context.ReportDiagnostic(diagnostic), static (context, hintName, sourceText) => context.AddSource(hintName, sourceText), (CSharpCompilation)collectedValues.compilation, - collectedValues.analyzerConfigOptions, + collectedValues.refitInternalNamespace, collectedValues.candidateMethods, collectedValues.candidateInterfaces); });