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