diff --git a/DependencyInjection.slnx b/DependencyInjection.slnx index d44f4d2..bf0c5a5 100644 --- a/DependencyInjection.slnx +++ b/DependencyInjection.slnx @@ -1,7 +1,9 @@ + - + + diff --git a/src/CodeAnalysis.Tests/CodeAnalysis.Tests.csproj b/src/CodeAnalysis.Tests/CodeAnalysis.Tests.csproj index 3c1dd88..aa039c3 100644 --- a/src/CodeAnalysis.Tests/CodeAnalysis.Tests.csproj +++ b/src/CodeAnalysis.Tests/CodeAnalysis.Tests.csproj @@ -14,15 +14,17 @@ + - + + - + diff --git a/src/CodeAnalysis.Tests/LegacyServiceAttributeTests.cs b/src/CodeAnalysis.Tests/LegacyServiceAttributeTests.cs new file mode 100644 index 0000000..c10ecb5 --- /dev/null +++ b/src/CodeAnalysis.Tests/LegacyServiceAttributeTests.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Devlooped.Extensions.DependencyInjection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using Xunit.Abstractions; +using AnalyzerTest = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerTest; +using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier; + +namespace Tests.CodeAnalysis; + +public class LegacyServiceAttributeTests(ITestOutputHelper Output) +{ + [Fact] + public async Task ErrorIfTKeyMatchesStringConstant() + { + var test = new AnalyzerTest + { + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck, + TestCode = + """ + using System; + using Microsoft.Extensions.DependencyInjection; + + [{|#0:Service("my")|}] + public class MyService { } + """ + }.WithTestState(); + + var expected = Verifier.Diagnostic(LegacyServiceAttributeAnalyzer.ServiceTypeNotKeyType).WithLocation(0); + test.ExpectedDiagnostics.Add(expected); + + await test.RunAsync(); + } + + [Fact] + public async Task ExecuteSyncWithAsyncCommand() + { + var test = new CSharpCodeFixTest + { + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck, + TestCode = + """ + using System; + using Microsoft.Extensions.DependencyInjection; + + [{|#0:Service("my")|}] + public class MyService { } + """, + FixedCode = + """ + using System; + using Microsoft.Extensions.DependencyInjection; + + [Service("my")] + public class MyService { } + """, + }.WithTestState(); + + test.ExpectedDiagnostics.Add(new DiagnosticResult(LegacyServiceAttributeAnalyzer.ServiceTypeNotKeyType).WithLocation(0)); + test.FixedState.Sources.AddStaticFiles(); + + await test.RunAsync(); + } +} \ No newline at end of file diff --git a/src/CodeAnalysis.Tests/TestExtensions.cs b/src/CodeAnalysis.Tests/TestExtensions.cs index e050e70..b06c760 100644 --- a/src/CodeAnalysis.Tests/TestExtensions.cs +++ b/src/CodeAnalysis.Tests/TestExtensions.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources; namespace Tests.CodeAnalysis; @@ -31,4 +34,26 @@ public static TAnalyzerTest WithPreprocessorSymbols(this TAnalyze return test; } + + public static TAnalyzerTest WithTestState(this TAnalyzerTest test) + where TAnalyzerTest : AnalyzerTest + { + test.TestState.Sources.AddStaticFiles(); + test.TestState.ReferenceAssemblies = new ReferenceAssemblies( + "net8.0", + new PackageIdentity( + "Microsoft.NETCore.App.Ref", "8.0.0"), + Path.Combine("ref", "net8.0")) + .AddPackages(ImmutableArray.Create( + new PackageIdentity("Microsoft.Extensions.DependencyInjection", "8.0.0"))); + + return test; + } + + public static void AddStaticFiles(this SourceFileList sources) + { + sources.Add(ThisAssembly.Resources.AddServicesNoReflectionExtension.Text); + sources.Add(ThisAssembly.Resources.ServiceAttribute.Text); + sources.Add(ThisAssembly.Resources.ServiceAttribute_1.Text); + } } diff --git a/src/DependencyInjection/AddServicesAnalyzer.cs b/src/DependencyInjection.CodeAnalysis/AddServicesAnalyzer.cs similarity index 100% rename from src/DependencyInjection/AddServicesAnalyzer.cs rename to src/DependencyInjection.CodeAnalysis/AddServicesAnalyzer.cs diff --git a/src/DependencyInjection/CodeAnalysisExtensions.cs b/src/DependencyInjection.CodeAnalysis/CodeAnalysisExtensions.cs similarity index 100% rename from src/DependencyInjection/CodeAnalysisExtensions.cs rename to src/DependencyInjection.CodeAnalysis/CodeAnalysisExtensions.cs diff --git a/src/DependencyInjection/ConstraintsChecker.cs b/src/DependencyInjection.CodeAnalysis/ConstraintsChecker.cs similarity index 100% rename from src/DependencyInjection/ConstraintsChecker.cs rename to src/DependencyInjection.CodeAnalysis/ConstraintsChecker.cs diff --git a/src/DependencyInjection/ConventionsAnalyzer.cs b/src/DependencyInjection.CodeAnalysis/ConventionsAnalyzer.cs similarity index 100% rename from src/DependencyInjection/ConventionsAnalyzer.cs rename to src/DependencyInjection.CodeAnalysis/ConventionsAnalyzer.cs diff --git a/src/DependencyInjection/DependencyInjection.csproj b/src/DependencyInjection.CodeAnalysis/DependencyInjection.CodeAnalysis.csproj similarity index 86% rename from src/DependencyInjection/DependencyInjection.csproj rename to src/DependencyInjection.CodeAnalysis/DependencyInjection.CodeAnalysis.csproj index 2bcaf62..3cd2803 100644 --- a/src/DependencyInjection/DependencyInjection.csproj +++ b/src/DependencyInjection.CodeAnalysis/DependencyInjection.CodeAnalysis.csproj @@ -3,20 +3,12 @@ Preview netstandard2.0 - Devlooped.Extensions.DependencyInjection - Devlooped.Extensions.DependencyInjection - - Automatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies, from conventions or attributes. - - $(Title) + Devlooped.Extensions.DependencyInjection.CodeAnalysis analyzers/dotnet true true $(DefineConstants);DDI_ADDSERVICE;DDI_ADDSERVICES false - - OSMFEULA.txt - true true @@ -30,7 +22,6 @@ - diff --git a/src/DependencyInjection/Devlooped.Extensions.DependencyInjection.props b/src/DependencyInjection.CodeAnalysis/Devlooped.Extensions.DependencyInjection.props similarity index 100% rename from src/DependencyInjection/Devlooped.Extensions.DependencyInjection.props rename to src/DependencyInjection.CodeAnalysis/Devlooped.Extensions.DependencyInjection.props diff --git a/src/DependencyInjection/Devlooped.Extensions.DependencyInjection.targets b/src/DependencyInjection.CodeAnalysis/Devlooped.Extensions.DependencyInjection.targets similarity index 100% rename from src/DependencyInjection/Devlooped.Extensions.DependencyInjection.targets rename to src/DependencyInjection.CodeAnalysis/Devlooped.Extensions.DependencyInjection.targets diff --git a/src/DependencyInjection/IncrementalGenerator.cs b/src/DependencyInjection.CodeAnalysis/IncrementalGenerator.cs similarity index 100% rename from src/DependencyInjection/IncrementalGenerator.cs rename to src/DependencyInjection.CodeAnalysis/IncrementalGenerator.cs diff --git a/src/DependencyInjection/LegacyServiceAttributeAnalyzer.cs b/src/DependencyInjection.CodeAnalysis/LegacyServiceAttributeAnalyzer.cs similarity index 100% rename from src/DependencyInjection/LegacyServiceAttributeAnalyzer.cs rename to src/DependencyInjection.CodeAnalysis/LegacyServiceAttributeAnalyzer.cs diff --git a/src/DependencyInjection/Properties/launchSettings.json b/src/DependencyInjection.CodeAnalysis/Properties/launchSettings.json similarity index 100% rename from src/DependencyInjection/Properties/launchSettings.json rename to src/DependencyInjection.CodeAnalysis/Properties/launchSettings.json diff --git a/src/DependencyInjection/SymbolExtensions.cs b/src/DependencyInjection.CodeAnalysis/SymbolExtensions.cs similarity index 100% rename from src/DependencyInjection/SymbolExtensions.cs rename to src/DependencyInjection.CodeAnalysis/SymbolExtensions.cs diff --git a/src/DependencyInjection/compile/AddServicesNoReflectionExtension.cs b/src/DependencyInjection.CodeAnalysis/compile/AddServicesNoReflectionExtension.cs similarity index 100% rename from src/DependencyInjection/compile/AddServicesNoReflectionExtension.cs rename to src/DependencyInjection.CodeAnalysis/compile/AddServicesNoReflectionExtension.cs diff --git a/src/DependencyInjection/compile/ServiceAttribute.cs b/src/DependencyInjection.CodeAnalysis/compile/ServiceAttribute.cs similarity index 100% rename from src/DependencyInjection/compile/ServiceAttribute.cs rename to src/DependencyInjection.CodeAnalysis/compile/ServiceAttribute.cs diff --git a/src/DependencyInjection/compile/ServiceAttribute`1.cs b/src/DependencyInjection.CodeAnalysis/compile/ServiceAttribute`1.cs similarity index 100% rename from src/DependencyInjection/compile/ServiceAttribute`1.cs rename to src/DependencyInjection.CodeAnalysis/compile/ServiceAttribute`1.cs diff --git a/src/DependencyInjection.CodeFixes/DependencyInjection.CodeFixes.csproj b/src/DependencyInjection.CodeFixes/DependencyInjection.CodeFixes.csproj new file mode 100644 index 0000000..725ce2f --- /dev/null +++ b/src/DependencyInjection.CodeFixes/DependencyInjection.CodeFixes.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + Devlooped.Extensions.DependencyInjection.CodeFixes + analyzers/dotnet + false + + + + + + + + + + + + + diff --git a/src/DependencyInjection.CodeFixes/LegacyServiceAttributeFixer.cs b/src/DependencyInjection.CodeFixes/LegacyServiceAttributeFixer.cs new file mode 100644 index 0000000..eaced2d --- /dev/null +++ b/src/DependencyInjection.CodeFixes/LegacyServiceAttributeFixer.cs @@ -0,0 +1,55 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Devlooped.Extensions.DependencyInjection; + +[Shared] +[ExportCodeFixProvider(LanguageNames.CSharp)] +public class LegacyServiceAttributeFixer : CodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create( + LegacyServiceAttributeAnalyzer.ServiceTypeNotKeyType.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root == null) + return; + + var attribute = root.FindNode(context.Span).FirstAncestorOrSelf(); + if (attribute == null) + return; + + context.RegisterCodeFix(new RemoveGenericParameterCodeAction(context.Document, attribute), context.Diagnostics); + } + + class RemoveGenericParameterCodeAction(Document document, AttributeSyntax syntax) : CodeAction + { + public override string Title => "Remove generic parameter"; + public override string? EquivalenceKey => Title; + + protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken); + if (root == null) + return document; + + var isGeneric = syntax.Name is GenericNameSyntax; + if (!isGeneric) + return document; + + var newName = IdentifierName("Service"); + + return document.WithSyntaxRoot(root.ReplaceNode(syntax, syntax.WithName(newName))); + } + } +} diff --git a/src/DependencyInjection.Tests/DependencyInjection.Tests.csproj b/src/DependencyInjection.Tests/DependencyInjection.Tests.csproj index 50f76e5..f61889d 100644 --- a/src/DependencyInjection.Tests/DependencyInjection.Tests.csproj +++ b/src/DependencyInjection.Tests/DependencyInjection.Tests.csproj @@ -1,6 +1,6 @@  - + net10.0 @@ -24,7 +24,8 @@ - + + @@ -32,7 +33,7 @@ - + diff --git a/src/NoAddServices/NoAddServices.csproj b/src/NoAddServices/NoAddServices.csproj index e251aa3..337464a 100644 --- a/src/NoAddServices/NoAddServices.csproj +++ b/src/NoAddServices/NoAddServices.csproj @@ -1,6 +1,6 @@  - + net10.0 @@ -14,9 +14,9 @@ - + - + diff --git a/src/Package/DependencyInjection.Package.msbuildproj b/src/Package/DependencyInjection.Package.msbuildproj new file mode 100644 index 0000000..111874b --- /dev/null +++ b/src/Package/DependencyInjection.Package.msbuildproj @@ -0,0 +1,24 @@ + + + netstandard2.0 + Devlooped.Extensions.DependencyInjection + + Automatic compile-time service registrations for Microsoft.Extensions.DependencyInjection with no run-time dependencies, from conventions or attributes. + + $(Title) + + + OSMFEULA.txt + true + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DependencyInjection/readme.md b/src/Package/readme.md similarity index 100% rename from src/DependencyInjection/readme.md rename to src/Package/readme.md