diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..fe1152bd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/MintPlayer.Dotnet.Tools.sln b/MintPlayer.Dotnet.Tools.sln index 9e0dd67a..2a23b933 100644 --- a/MintPlayer.Dotnet.Tools.sln +++ b/MintPlayer.Dotnet.Tools.sln @@ -176,6 +176,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MintPlayer.AdminHelper", "A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdminTest", "AdminHelper\AdminTest\AdminTest.csproj", "{CC34556F-529A-4186-AD6C-5F986808D8A5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CommandLineApp", "CommandLineApp", "{752F83A4-B5A2-431F-B7A9-871FD0BD1617}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MintPlayer.CommandLineApp", "SourceGenerators\CommandLineApp\MintPlayer.CommandLineApp\MintPlayer.CommandLineApp.csproj", "{83309DAC-700C-4A03-B7CD-EC1C4F77CC85}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MintPlayer.CommandLineApp.Attributes", "SourceGenerators\CommandLineApp\MintPlayer.CommandLineApp.Attributes\MintPlayer.CommandLineApp.Attributes.csproj", "{53451C47-4933-47BE-8B44-ED911D9163AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLineAppDebugging", "SourceGenerators\TestProjects\CommandLineAppDebugging\CommandLineAppDebugging.csproj", "{4CA948F7-9118-4442-885F-CE8CFFE5E0A2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -390,6 +398,18 @@ Global {9BCC691D-02F6-4F52-BB14-BB99825959B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {9BCC691D-02F6-4F52-BB14-BB99825959B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {9BCC691D-02F6-4F52-BB14-BB99825959B9}.Release|Any CPU.Build.0 = Release|Any CPU + {83309DAC-700C-4A03-B7CD-EC1C4F77CC85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83309DAC-700C-4A03-B7CD-EC1C4F77CC85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83309DAC-700C-4A03-B7CD-EC1C4F77CC85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83309DAC-700C-4A03-B7CD-EC1C4F77CC85}.Release|Any CPU.Build.0 = Release|Any CPU + {53451C47-4933-47BE-8B44-ED911D9163AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53451C47-4933-47BE-8B44-ED911D9163AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53451C47-4933-47BE-8B44-ED911D9163AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53451C47-4933-47BE-8B44-ED911D9163AB}.Release|Any CPU.Build.0 = Release|Any CPU + {4CA948F7-9118-4442-885F-CE8CFFE5E0A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4CA948F7-9118-4442-885F-CE8CFFE5E0A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4CA948F7-9118-4442-885F-CE8CFFE5E0A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4CA948F7-9118-4442-885F-CE8CFFE5E0A2}.Release|Any CPU.Build.0 = Release|Any CPU {447355EB-ED1F-4701-955F-598D00E1E207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {447355EB-ED1F-4701-955F-598D00E1E207}.Debug|Any CPU.Build.0 = Debug|Any CPU {447355EB-ED1F-4701-955F-598D00E1E207}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -466,6 +486,10 @@ Global {9BCC691D-02F6-4F52-BB14-BB99825959B9} = {BE967E23-E7A7-4A55-912D-38FD21068E1D} {447355EB-ED1F-4701-955F-598D00E1E207} = {41AC5E0E-BCB6-4F0F-A991-C5D19C66828C} {CC34556F-529A-4186-AD6C-5F986808D8A5} = {41AC5E0E-BCB6-4F0F-A991-C5D19C66828C} + {752F83A4-B5A2-431F-B7A9-871FD0BD1617} = {C65054D9-CAD1-4124-B474-D03C178D9D78} + {83309DAC-700C-4A03-B7CD-EC1C4F77CC85} = {752F83A4-B5A2-431F-B7A9-871FD0BD1617} + {53451C47-4933-47BE-8B44-ED911D9163AB} = {752F83A4-B5A2-431F-B7A9-871FD0BD1617} + {4CA948F7-9118-4442-885F-CE8CFFE5E0A2} = {1614A8B2-CA9A-4F94-B68A-BCC8ED244648} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D5378D43-9246-4355-8C4B-880DB15B07DA} diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp.Attributes/ConsoleAppAttribute.cs b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp.Attributes/ConsoleAppAttribute.cs new file mode 100644 index 00000000..6381cd7b --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp.Attributes/ConsoleAppAttribute.cs @@ -0,0 +1,9 @@ +namespace MintPlayer.CommandLineApp.Attributes; + +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public class ConsoleAppAttribute : Attribute +{ + public ConsoleAppAttribute(string description) + { + } +} diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp.Attributes/MintPlayer.CommandLineApp.Attributes.csproj b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp.Attributes/MintPlayer.CommandLineApp.Attributes.csproj new file mode 100644 index 00000000..d9f9f7e9 --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp.Attributes/MintPlayer.CommandLineApp.Attributes.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + 13 + enable + enable + + + + + + + diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/AnalyzerReleases.Shipped.md b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/AnalyzerReleases.Shipped.md new file mode 100644 index 00000000..60b59dd9 --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/AnalyzerReleases.Shipped.md @@ -0,0 +1,3 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/AnalyzerReleases.Unshipped.md b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/AnalyzerReleases.Unshipped.md new file mode 100644 index 00000000..dfb7955a --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/AnalyzerReleases.Unshipped.md @@ -0,0 +1,8 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +COMLAPP001 | ConsoleAppGenerator | Error | DiagnosticRules \ No newline at end of file diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.Producer.cs b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.Producer.cs new file mode 100644 index 00000000..0ca65dcc --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.Producer.cs @@ -0,0 +1,78 @@ +using Microsoft.CodeAnalysis; +using MintPlayer.CommandLineApp.Models; +using MintPlayer.SourceGenerators.Tools; +using MintPlayer.SourceGenerators.Tools.Extensions; +using System.CodeDom.Compiler; + +namespace MintPlayer.CommandLineApp.Generators; + +public class LaunchSettingsSummaryProducer : Producer, IDiagnosticReporter +{ + private readonly IEnumerable consoleApps; + private readonly IEnumerable filesWithTopLevelStatements; + public LaunchSettingsSummaryProducer(IEnumerable consoleApps, IEnumerable filesWithTopLevelStatements, string rootNamespace) : base(rootNamespace, "LaunchSettingsSummary.g.cs") + { + this.consoleApps = consoleApps; + this.filesWithTopLevelStatements = filesWithTopLevelStatements; + } + + public IEnumerable GetDiagnostics() + { + if (consoleApps.Count() > 1) + return consoleApps.Select(ca => DiagnosticRules.OnlyOneConsoleAppAllowed.Create(ca.ClassSymbolLocation)); + else + return Enumerable.Empty(); + + //return filesWithTopLevelStatements.Select(f => DiagnosticRules.CannotHaveTopLevelStatements.Create(f)); + } + + protected override void ProduceSource(IndentedTextWriter writer, CancellationToken cancellationToken) + { + foreach (var consoleApp in consoleApps) + { + if (!string.IsNullOrEmpty(consoleApp.Namespace)) + { + writer.WriteLine($"namespace {consoleApp.Namespace}"); + writer.WriteLine("{"); + writer.Indent++; + } + + writer.WriteLine($"public static class {consoleApp.ClassName}App"); + writer.WriteLine("{"); + writer.Indent++; + + writer.WriteLine("public static async global::System.Threading.Tasks.Task Run(string[] args)"); + writer.WriteLine("{"); + writer.Indent++; + + ProduceMainMethod(writer, consoleApp); + + writer.Indent--; + writer.WriteLine("}"); + + writer.Indent--; + writer.WriteLine("}"); + + + if (!string.IsNullOrEmpty(consoleApp.Namespace)) + { + writer.Indent--; + writer.WriteLine("}"); + } + } + } + + private void ProduceMainMethod(IndentedTextWriter writer, ConsoleApp consoleApp) + { + var description = consoleApp.Description ?? string.Empty; + writer.WriteLine($"""" + var rootCommand = new System.CommandLine.RootCommand("{description.Replace("\"", "\\\"")}"); + """"); + writer.WriteLine($"""" + var parsed = rootCommand.Parse(args); + """"); + writer.WriteLine($"""" + await parsed.InvokeAsync(); + """"); + } +} diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.Rules.cs b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.Rules.cs new file mode 100644 index 00000000..d7fcf42b --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.Rules.cs @@ -0,0 +1,22 @@ +using Microsoft.CodeAnalysis; + +namespace MintPlayer.CommandLineApp.Generators; + +public static partial class DiagnosticRules +{ + public static readonly DiagnosticDescriptor CannotHaveTopLevelStatements = new( + id: "COMLAPP001", + title: "A console app that uses the [ConsoleApp] attribute cannot have top-level statements", + messageFormat: "A console app that uses the [ConsoleApp] attribute cannot have top-level statements", + category: "ConsoleAppGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor OnlyOneConsoleAppAllowed = new( + id: "COMLAPP002", + title: "Only one console app with the [ConsoleApp] attribute is allowed", + messageFormat: "Only one console app with the [ConsoleApp] attribute is allowed", + category: "ConsoleAppGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); +} \ No newline at end of file diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.cs b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.cs new file mode 100644 index 00000000..5a5261ad --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Generators/LaunchSettingsSummaryGenerator.cs @@ -0,0 +1,65 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using MintPlayer.SourceGenerators.Tools; +using MintPlayer.SourceGenerators.Tools.ValueComparers; + +namespace MintPlayer.CommandLineApp.Generators +{ + [Generator(LanguageNames.CSharp)] + public sealed class LaunchSettingsSummaryGenerator : IncrementalGenerator + { + const string consoleAppAttribute = "MintPlayer.CommandLineApp.Attributes.ConsoleAppAttribute"; + + public override void Initialize(IncrementalGeneratorInitializationContext context, IncrementalValueProvider settingsProvider) + { + var filesWithTopLevelStatements = context.CompilationProvider + .SelectMany(static (compilation, ct) => compilation.SyntaxTrees + .Where(st => st.GetRoot(ct).DescendantNodesAndSelf().OfType().Any()) + .Select(f => f.GetLocation(TextSpan.FromBounds(0, f.Length - 1)))) + .WithComparer(ValueComparer.Instance) + .Collect(); + + var consoleAppsProvider = context.SyntaxProvider.ForAttributeWithMetadataName( + consoleAppAttribute, + static (node, ct) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }, + static (context, ct) => + { + if (context.TargetNode is ClassDeclarationSyntax classDeclaration && + context.SemanticModel.GetDeclaredSymbol(classDeclaration, ct) is INamedTypeSymbol classSymbol) + { + return new Models.ConsoleApp + { + Description = context.Attributes.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == consoleAppAttribute) + ?.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? string.Empty, + ClassName = classSymbol.Name, + Namespace = classSymbol.ContainingNamespace.IsGlobalNamespace ? string.Empty : classSymbol.ContainingNamespace?.ToDisplayString(), + ClassSymbolLocation = classSymbol.Locations.FirstOrDefault(), + }; + } + + return default; + }) + .WithNullableComparer() + .Collect(); + + var consoleAppsSourceProvider = consoleAppsProvider + .Join(filesWithTopLevelStatements) + .Join(settingsProvider) + .Select(static Producer (prov, ct) => new LaunchSettingsSummaryProducer(prov.Item1, prov.Item2, prov.Item3.RootNamespace!)); + + var consoleAppsDiagnosticProvider = consoleAppsProvider + .Join(filesWithTopLevelStatements) + .Join(settingsProvider) + .Select(static IDiagnosticReporter (prov, ct) => new LaunchSettingsSummaryProducer(prov.Item1, prov.Item2, prov.Item3.RootNamespace!)); + + context.ProduceCode(consoleAppsSourceProvider); + context.ReportDiagnostics(consoleAppsDiagnosticProvider); + } + + public override void RegisterComparers() + { + } + } +} diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/MintPlayer.CommandLineApp.csproj b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/MintPlayer.CommandLineApp.csproj new file mode 100644 index 00000000..33eea4be --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/MintPlayer.CommandLineApp.csproj @@ -0,0 +1,24 @@ + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + + + + + + + + + + + diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Models/ConsoleApp.cs b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Models/ConsoleApp.cs new file mode 100644 index 00000000..60270fb1 --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Models/ConsoleApp.cs @@ -0,0 +1,12 @@ +using MintPlayer.ValueComparerGenerator.Attributes; + +namespace MintPlayer.CommandLineApp.Models; + +[AutoValueComparer] +public partial class ConsoleApp +{ + public Microsoft.CodeAnalysis.Location? ClassSymbolLocation { get; set; } + public string? Namespace { get; set; } + public string? ClassName { get; set; } + public string? Description { get; set; } +} diff --git a/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Properties/launchSettings.json b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Properties/launchSettings.json new file mode 100644 index 00000000..271fde0e --- /dev/null +++ b/SourceGenerators/CommandLineApp/MintPlayer.CommandLineApp/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Debug CommandLineApp Generators on CommandLineAppDebugging": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\..\\TestProjects\\CommandLineAppDebugging\\CommandLineAppDebugging.csproj" + } + } +} \ No newline at end of file diff --git a/SourceGenerators/MintPlayer.SourceGenerators.Tools/Extensions/ComparerExtensions.cs b/SourceGenerators/MintPlayer.SourceGenerators.Tools/Extensions/ComparerExtensions.cs new file mode 100644 index 00000000..50f8cf16 --- /dev/null +++ b/SourceGenerators/MintPlayer.SourceGenerators.Tools/Extensions/ComparerExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.CodeAnalysis; +using MintPlayer.SourceGenerators.Tools.ValueComparers; + +namespace MintPlayer.SourceGenerators.Tools; + +public static class ComparerExtensions +{ + /// + /// Only use this method on simple types, like bool + /// + public static IncrementalValueProvider WithDefaultComparer(this IncrementalValueProvider provider) where T : struct + { + return provider.WithComparer(DefaultValueComparer.Instance); + } +} diff --git a/SourceGenerators/TestProjects/CommandLineAppDebugging/CommandLineAppDebugging.csproj b/SourceGenerators/TestProjects/CommandLineAppDebugging/CommandLineAppDebugging.csproj new file mode 100644 index 00000000..5be0e189 --- /dev/null +++ b/SourceGenerators/TestProjects/CommandLineAppDebugging/CommandLineAppDebugging.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + enable + enable + false + true + true + + + + + + + + diff --git a/SourceGenerators/TestProjects/CommandLineAppDebugging/Program.cs b/SourceGenerators/TestProjects/CommandLineAppDebugging/Program.cs new file mode 100644 index 00000000..04e1ac8c --- /dev/null +++ b/SourceGenerators/TestProjects/CommandLineAppDebugging/Program.cs @@ -0,0 +1,9 @@ +// See https://aka.ms/new-console-template for more information +using MintPlayer.CommandLineApp.Attributes; + +await ListAspnetcoreAppsApp.Run(args); + +[ConsoleApp("Lists all ASP.NET Core apps under the current folder")] +public class ListAspnetcoreApps +{ +} diff --git a/SourceGenerators/TestProjects/CommandLineAppDebugging/Properties/launchSettings.json b/SourceGenerators/TestProjects/CommandLineAppDebugging/Properties/launchSettings.json new file mode 100644 index 00000000..71e133b2 --- /dev/null +++ b/SourceGenerators/TestProjects/CommandLineAppDebugging/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "CommandLineAppDebugging": { + "commandName": "Project" + }, + "Container (Dockerfile)": { + "commandName": "Docker" + } + } +} \ No newline at end of file