diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml new file mode 100644 index 0000000..cee2015 --- /dev/null +++ b/.github/workflows/build_and_test.yaml @@ -0,0 +1,31 @@ +name: Build and Test + +on: + push: + pull_request: + branches: + - main + - dev + +jobs: + build_and_test: + name: Build and Test + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v1 + with: + dotnet-version: '8.0.x' # You can specify the version you need + + - name: Restore Dependencies + run: dotnet restore CodeJoyRide.sln + + - name: Build Solution + run: dotnet build CodeJoyRide.sln --configuration Release + + - name: Run Tests + run: dotnet test CodeJoyRide.sln --configuration Release --no-restore --verbosity normal diff --git a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/AnalyzerReleases.Shipped.md b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/AnalyzerReleases.Shipped.md index d4b4459..611c4ed 100644 --- a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/AnalyzerReleases.Shipped.md +++ b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/AnalyzerReleases.Shipped.md @@ -4,4 +4,4 @@ | Rule ID | Category | Severity | Notes | |---------|----------|----------|--------------------------------------------------| -| CJR001 | Usage | Warning | The speed must be lower than the Speed of Light. | +| CJR01 | Usage | Warning | The speed must be lower than the Speed of Light. | diff --git a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeCodeFixProvider.cs b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeCodeFixProvider.cs index d7c55fa..c80d836 100644 --- a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeCodeFixProvider.cs +++ b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeCodeFixProvider.cs @@ -46,14 +46,14 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) // Register a code action that will invoke the fix. context.RegisterCodeFix(CodeAction.Create( - title: string.Format(Resources.CJR001CodeFixTitle, "throw", "Maybe.None"), + title: string.Format(Resources.CJR01CodeFixTitle, "throw", "Maybe.None"), token => ReplaceThrowWithReturnStatement(context.Document, throwStatementSyntax, token), - equivalenceKey: nameof(Resources.CJR001CodeFixTitle) + equivalenceKey: nameof(Resources.CJR01CodeFixTitle) ), diagnostic); } - private Task ReplaceThrowWithReturnStatement(Document document, CSharpSyntaxNode throwSyntaxNode, - CancellationToken token) + private Task ReplaceThrowWithReturnStatement( + Document document, CSharpSyntaxNode throwSyntaxNode, CancellationToken token) { throw new NotImplementedException(); } diff --git a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeSemanticAnalyzer.cs b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeSemanticAnalyzer.cs index fd4f79a..b1cce38 100644 --- a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeSemanticAnalyzer.cs +++ b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/MaybeSemanticAnalyzer.cs @@ -1,6 +1,10 @@ +using System; using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; namespace CodeJoyRide.Fx.Analyzer; @@ -12,28 +16,28 @@ namespace CodeJoyRide.Fx.Analyzer; public class MaybeSemanticAnalyzer : DiagnosticAnalyzer { // Preferred format of DiagnosticId is Your Prefix + Number, e.g. CA1234. - public const string DiagnosticId = "CJR001"; + public const string DiagnosticId = "CJR01"; // Feel free to use raw strings if you don't need localization. - private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.CJR001Title), + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.CJR01Title), Resources.ResourceManager, typeof(Resources)); // The message that will be displayed to the user. private static readonly LocalizableString MessageFormat = - new LocalizableResourceString(nameof(Resources.CJR001MessageFormat), Resources.ResourceManager, + new LocalizableResourceString(nameof(Resources.CJR01MessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = - new LocalizableResourceString(nameof(Resources.CJR001Description), Resources.ResourceManager, + new LocalizableResourceString(nameof(Resources.CJR01Description), Resources.ResourceManager, typeof(Resources)); // The category of the diagnostic (Design, Naming etc.). private const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new(DiagnosticId, Title, MessageFormat, Category, - DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description + DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description , customTags: [WellKnownDiagnosticTags.NotConfigurable] - ); + ); // Keep in mind: you have to list your rules here. public override ImmutableArray SupportedDiagnostics { get; } = @@ -45,9 +49,40 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); // You must call this method to enable the Concurrent Execution. - context.EnableConcurrentExecution(); + context.EnableConcurrentExecution(); // Subscribe to semantic (compile time) action invocation, e.g. throw . - // TODO: register operation action + context.RegisterOperationAction(AnalyzeThrowStatements, OperationKind.Throw); + } + + private void AnalyzeThrowStatements(OperationAnalysisContext context) + { + if (context.Operation is not IThrowOperation || context.Operation.Syntax is not ThrowStatementSyntax) + return; + + if (context.Operation.SemanticModel is null) + return; + + var containingMethodSyntax = GetContainingMethodSyntax(context.Operation.Syntax); + var containingMethodSymbol = + context.Operation.SemanticModel.GetDeclaredSymbol(containingMethodSyntax) as IMethodSymbol; + + var returnTypeSymbol = containingMethodSymbol!.ReturnType; + var maybeTypeSymbol = context.Compilation.GetTypeByMetadataName("CodeJoyRide.Fx.Maybe`1"); + + if (!returnTypeSymbol.OriginalDefinition.Equals(maybeTypeSymbol, SymbolEqualityComparer.Default)) + return; + + var diagnostic = Diagnostic.Create(Rule, context.Operation.Syntax.GetLocation()); + context.ReportDiagnostic(diagnostic); + } + + private MethodDeclarationSyntax GetContainingMethodSyntax(SyntaxNode syntaxNode) + { + while (true) + { + if (syntaxNode.Parent is MethodDeclarationSyntax mds) return mds; + syntaxNode = syntaxNode.Parent!; + } } } diff --git a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.Designer.cs b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.Designer.cs index 4e92fac..f859a13 100644 --- a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.Designer.cs +++ b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.Designer.cs @@ -45,27 +45,27 @@ internal static System.Globalization.CultureInfo Culture { } } - internal static string CJR001CodeFixTitle { + internal static string CJR01CodeFixTitle { get { - return ResourceManager.GetString("CJR001CodeFixTitle", resourceCulture); + return ResourceManager.GetString("CJR01CodeFixTitle", resourceCulture); } } - internal static string CJR001Description { + internal static string CJR01Description { get { - return ResourceManager.GetString("CJR001Description", resourceCulture); + return ResourceManager.GetString("CJR01Description", resourceCulture); } } - internal static string CJR001MessageFormat { + internal static string CJR01MessageFormat { get { - return ResourceManager.GetString("CJR001MessageFormat", resourceCulture); + return ResourceManager.GetString("CJR01MessageFormat", resourceCulture); } } - internal static string CJR001Title { + internal static string CJR01Title { get { - return ResourceManager.GetString("CJR001Title", resourceCulture); + return ResourceManager.GetString("CJR01Title", resourceCulture); } } } diff --git a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.resx b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.resx index d475033..c6153ab 100644 --- a/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.resx +++ b/CodeJoyRide.Fx.Analyzer/CodeJoyRide.Fx.Analyzer/Resources.resx @@ -18,16 +18,16 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + Replace '{0}' with '{1}'The title of the code fix. - + It's recommended to return None instead of throw exception.An optional longer localizable description of the diagnostic. - + Use Maybe.None instead of throw exceptionThe format-able message the diagnostic displays. - + No Throw ExceptionThe title of the diagnostic. diff --git a/CodeJoyRide.sln b/CodeJoyRide.sln index 10f20e4..f0d9574 100644 --- a/CodeJoyRide.sln +++ b/CodeJoyRide.sln @@ -17,6 +17,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{D3C4A25F-0 README.md = README.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A6A1F426-676D-4413-93B2-B9DD6A25736A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{F37F6912-F8AE-4B03-8EB0-966358BC757B}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build_and_test.yaml = .github\workflows\build_and_test.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,5 +52,6 @@ Global {3D0FED8B-0509-4407-B66C-AFC7971956C3} = {1B62324C-07F0-45FA-A1F5-C5CAD91A6AC5} {906D80E7-ED31-4D3E-A47D-7ABF8F6153FB} = {1B62324C-07F0-45FA-A1F5-C5CAD91A6AC5} {5508C6E9-856E-4B08-8BFD-D05803BAE144} = {85459589-7B8D-43F5-977D-64D13E685F71} + {F37F6912-F8AE-4B03-8EB0-966358BC757B} = {A6A1F426-676D-4413-93B2-B9DD6A25736A} EndGlobalSection EndGlobal