diff --git a/src/EffectiveCSharp.Analyzers/AnalyzerReleases.Unshipped.md b/src/EffectiveCSharp.Analyzers/AnalyzerReleases.Unshipped.md index 3d45752..ffcfe32 100644 --- a/src/EffectiveCSharp.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/EffectiveCSharp.Analyzers/AnalyzerReleases.Unshipped.md @@ -11,4 +11,3 @@ ECS0006 | Refactoring | Info | AvoidStringlyTypedApisAnalyzer, [Documentation](h ECS0007 | Design | Info | ExpressCallbacksWithDelegatesAnalyzer, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/dc194bbde030e9c40d8d9cdb1e0b5ff8919fe5a8/docs/rules/ECS0007.md) ECS0008 | Usage | Info | EventInvocationAnalyzer, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/cc7d91eb81f6781851c09732db1268be7dab402b/docs/rules/ECS0008.md) ECS0009 | Performance | Info | MinimizeBoxingUnboxingAnalyzer, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/6213cba8473dac61d6132e205550884eae1c94bf/docs/rules/ECS0009.md) -ECS1000 | Performance | Info | SpanAnalyzer, [Documentation](https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/d00a4cc9f61e7d5b392894aad859e46c43a5611c/docs/rules/ECS1000.md) \ No newline at end of file diff --git a/src/EffectiveCSharp.Analyzers/SpanAnalyzer.cs b/src/EffectiveCSharp.Analyzers/SpanAnalyzer.cs deleted file mode 100644 index 51b00b0..0000000 --- a/src/EffectiveCSharp.Analyzers/SpanAnalyzer.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace EffectiveCSharp.Analyzers; - -/// -/// Analyzer that suggests using instead of arrays for better performance. -/// -/// -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class SpanAnalyzer : DiagnosticAnalyzer -{ - private static readonly DiagnosticDescriptor Rule = new( - id: DiagnosticIds.UseSpanInstead, - title: "Use Span for performance", - messageFormat: "Consider using Span instead of array for better performance", - category: Categories.Performance, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: true, - helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/rules/{DiagnosticIds.UseSpanInstead}.md"); - - /// - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - /// - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction(AnalyzeArrayCreation, SyntaxKind.ArrayCreationExpression); - context.RegisterSyntaxNodeAction(AnalyzeArrayAccess, SyntaxKind.ElementAccessExpression); - } - - private static void AnalyzeArrayCreation(SyntaxNodeAnalysisContext context) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - ArrayCreationExpressionSyntax arrayCreation = (ArrayCreationExpressionSyntax)context.Node; - - if (IsInsideSpanInitialization(arrayCreation)) - { - return; - } - - if (context.SemanticModel.GetTypeInfo(arrayCreation.Type, context.CancellationToken).Type is IArrayTypeSymbol) - { - Diagnostic diagnostic = arrayCreation.GetLocation().CreateDiagnostic(Rule); - context.ReportDiagnostic(diagnostic); - } - } - - private static bool IsInsideSpanInitialization(ArrayCreationExpressionSyntax arrayCreation) - { -#pragma warning disable S125 // Remove this commented out code - // Check if the parent is a Span or ReadOnlySpan creation - // example: new Span(new int[10]); -#pragma warning restore S125 // Remove this commented out code - if (arrayCreation.Parent?.Parent?.Parent is not ObjectCreationExpressionSyntax objectCreation) - { - return false; - } - - if (objectCreation.Type is not GenericNameSyntax type) - { - return false; - } - - string typeName = type.Identifier.Text; - return typeName is WellKnownTypes.Span or WellKnownTypes.ReadOnlySpan; - } - - private static void AnalyzeArrayAccess(SyntaxNodeAnalysisContext context) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - ElementAccessExpressionSyntax elementAccess = (ElementAccessExpressionSyntax)context.Node; - - if (context.SemanticModel.GetTypeInfo(elementAccess.Expression, context.CancellationToken).Type is IArrayTypeSymbol) - { - Diagnostic diagnostic = elementAccess.GetLocation().CreateDiagnostic(Rule); - context.ReportDiagnostic(diagnostic); - } - } -} diff --git a/src/EffectiveCSharp.Analyzers/SpanCodeFixProvider.cs b/src/EffectiveCSharp.Analyzers/SpanCodeFixProvider.cs deleted file mode 100644 index 2c5b7e3..0000000 --- a/src/EffectiveCSharp.Analyzers/SpanCodeFixProvider.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace EffectiveCSharp.Analyzers; - -/// -/// A that provides a code fix for the . -/// -/// -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SpanCodeFixProvider))] -[Shared] -public class SpanCodeFixProvider : CodeFixProvider -{ - private static readonly string Title = "Use Span"; - - /// - public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIds.UseSpanInstead); - - /// - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - /// - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - Diagnostic diagnostic = context.Diagnostics[0]; - TextSpan diagnosticSpan = diagnostic.Location.SourceSpan; - SyntaxNode? declaration = root?.FindNode(diagnosticSpan); - - switch (declaration) - { - case ArrayCreationExpressionSyntax arrayCreation: - context.RegisterCodeFix( - CodeAction.Create( - title: Title, - createChangedSolution: c => UseSpanAsync(context.Document, arrayCreation, c), - equivalenceKey: Title), - diagnostic); - break; - case ElementAccessExpressionSyntax elementAccess: - context.RegisterCodeFix( - CodeAction.Create( - title: Title, - createChangedSolution: c => UseSpanElementAccessAsync(context.Document, elementAccess, c), - equivalenceKey: Title), - diagnostic); - break; - } - } - - private async Task UseSpanAsync(Document document, ArrayCreationExpressionSyntax arrayCreation, CancellationToken cancellationToken) - { - string elementType = arrayCreation.Type.ElementType.ToString(); - InitializerExpressionSyntax? arrayInitializer = arrayCreation.Initializer; - string spanText = $"new Span<{elementType}>({arrayInitializer})"; - - SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - SyntaxNode? newRoot = root?.ReplaceNode(arrayCreation, SyntaxFactory.ParseExpression(spanText)); - - return newRoot != null - ? document.WithSyntaxRoot(newRoot).Project.Solution - : document.Project.Solution; - } - - private async Task UseSpanElementAccessAsync(Document document, ElementAccessExpressionSyntax elementAccess, CancellationToken cancellationToken) - { - string spanExpression = elementAccess.Expression.ToString(); - string indexArgument = elementAccess.ArgumentList.Arguments.First().ToString(); - string spanAccessText = $"{spanExpression}.Slice({indexArgument}, 1)[0]"; - - SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - SyntaxNode? newRoot = root?.ReplaceNode(elementAccess, SyntaxFactory.ParseExpression(spanAccessText)); - - return newRoot != null - ? document.WithSyntaxRoot(newRoot).Project.Solution - : document.Project.Solution; - } -} diff --git a/src/EffectiveCSharp.Analyzers/SquiggleCop.Baseline.yaml b/src/EffectiveCSharp.Analyzers/SquiggleCop.Baseline.yaml index 6635398..6dd906e 100644 --- a/src/EffectiveCSharp.Analyzers/SquiggleCop.Baseline.yaml +++ b/src/EffectiveCSharp.Analyzers/SquiggleCop.Baseline.yaml @@ -689,8 +689,8 @@ - {Id: RCS1114FadeOut, Title: Remove redundant delegate creation, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1118, Title: Mark local variable as const, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1123, Title: Add parentheses when necessary, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} -- {Id: RCS1124, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true} -- {Id: RCS1124FadeOut, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true} +- {Id: RCS1124, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} +- {Id: RCS1124FadeOut, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1126, Title: Add braces to if-else, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: false, EffectiveSeverities: [None], IsEverSuppressed: true} - {Id: RCS1128, Title: Use coalesce expression, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1129, Title: Remove redundant field initialization, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true} @@ -1371,7 +1371,7 @@ - {Id: SA1027, Title: Use tabs correctly, Category: StyleCop.CSharp.SpacingRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1028, Title: Code should not contain trailing whitespace, Category: StyleCop.CSharp.SpacingRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1100, Title: Do not prefix calls with base unless local implementation exists, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} -- {Id: SA1101, Title: Prefix local calls with this, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error, Note], IsEverSuppressed: true} +- {Id: SA1101, Title: Prefix local calls with this, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error, Note], IsEverSuppressed: false} - {Id: SA1102, Title: Query clause should follow previous clause, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1103, Title: Query clauses should be on separate lines or all on one line, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1104, Title: Query clause should begin on new line when previous clause spans multiple lines, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} diff --git a/src/tools/Dogfood/SquiggleCop.Baseline.yaml b/src/tools/Dogfood/SquiggleCop.Baseline.yaml index 0af4a27..87019e0 100644 --- a/src/tools/Dogfood/SquiggleCop.Baseline.yaml +++ b/src/tools/Dogfood/SquiggleCop.Baseline.yaml @@ -305,7 +305,6 @@ - {Id: ECS0007, Title: Express callbacks with delegates, Category: Design, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: ECS0008, Title: Use the Null Conditional Operator for Event Invocations, Category: Usage, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: ECS0009, Title: Minimize boxing and unboxing, Category: Performance, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} -- {Id: ECS1000, Title: Use Span for performance, Category: Performance, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: EM0001, Title: Switch on Enum Not Exhaustive, Category: Logic, DefaultSeverity: Error, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: EM0002, Title: Switch on Nullable Enum Not Exhaustive, Category: Logic, DefaultSeverity: Error, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: EM0003, Title: Switch on Closed Type Not Exhaustive, Category: Logic, DefaultSeverity: Error, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} @@ -697,8 +696,8 @@ - {Id: RCS1114FadeOut, Title: Remove redundant delegate creation, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1118, Title: Mark local variable as const, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1123, Title: Add parentheses when necessary, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} -- {Id: RCS1124, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true} -- {Id: RCS1124FadeOut, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true} +- {Id: RCS1124, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} +- {Id: RCS1124FadeOut, Title: Inline local variable, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1126, Title: Add braces to if-else, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: false, EffectiveSeverities: [None], IsEverSuppressed: true} - {Id: RCS1128, Title: Use coalesce expression, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: false} - {Id: RCS1129, Title: Remove redundant field initialization, Category: Roslynator, DefaultSeverity: Note, IsEnabledByDefault: true, EffectiveSeverities: [Note], IsEverSuppressed: true} @@ -1379,7 +1378,7 @@ - {Id: SA1027, Title: Use tabs correctly, Category: StyleCop.CSharp.SpacingRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1028, Title: Code should not contain trailing whitespace, Category: StyleCop.CSharp.SpacingRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1100, Title: Do not prefix calls with base unless local implementation exists, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} -- {Id: SA1101, Title: Prefix local calls with this, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error, Note], IsEverSuppressed: true} +- {Id: SA1101, Title: Prefix local calls with this, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error, Note], IsEverSuppressed: false} - {Id: SA1102, Title: Query clause should follow previous clause, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1103, Title: Query clauses should be on separate lines or all on one line, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} - {Id: SA1104, Title: Query clause should begin on new line when previous clause spans multiple lines, Category: StyleCop.CSharp.ReadabilityRules, DefaultSeverity: Warning, IsEnabledByDefault: true, EffectiveSeverities: [Error], IsEverSuppressed: false} diff --git a/tests/EffectiveCSharp.Analyzers.Benchmarks/Ecs1000Benchmarks.cs b/tests/EffectiveCSharp.Analyzers.Benchmarks/Ecs1000Benchmarks.cs deleted file mode 100644 index 78524c1..0000000 --- a/tests/EffectiveCSharp.Analyzers.Benchmarks/Ecs1000Benchmarks.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace EffectiveCSharp.Analyzers.Benchmarks; - -[InProcess] -[MemoryDiagnoser] -public class Ecs1000Benchmarks -{ - private static CompilationWithAnalyzers? BaselineCompilation { get; set; } - - private static CompilationWithAnalyzers? TestCompilation { get; set; } - - [IterationSetup] - [SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Async setup not supported in BenchmarkDotNet.See https://github.com/dotnet/BenchmarkDotNet/issues/2442.")] - public static void SetupCompilation() - { - List<(string Name, string Content)> sources = []; - for (int index = 0; index < Constants.NumberOfCodeFiles; index++) - { - string name = "TypeName" + index; - sources.Add((name, @$" -using System; - -internal class {name} -{{ - private void Test() - {{ - _ = new int[10]; - _ = new Span(new int[10]); - }} -}} -")); - } - - (BaselineCompilation, TestCompilation) = - BenchmarkCSharpCompilationFactory - .CreateAsync(sources.ToArray()) - .GetAwaiter() - .GetResult(); - } - - [Benchmark] - public async Task Ecs1000WithDiagnostics() - { - ImmutableArray diagnostics = - (await TestCompilation! - .GetAnalysisResultAsync(CancellationToken.None) - .ConfigureAwait(false)) - .AssertValidAnalysisResult() - .GetAllDiagnostics(); - - if (diagnostics.Length != Constants.NumberOfCodeFiles) - { - throw new InvalidOperationException($"Expected '{Constants.NumberOfCodeFiles:N0}' analyzer diagnostics but found '{diagnostics.Length}'"); - } - } - - [Benchmark(Baseline = true)] - public async Task Ecs1000Baseline() - { - ImmutableArray diagnostics = - (await BaselineCompilation! - .GetAnalysisResultAsync(CancellationToken.None) - .ConfigureAwait(false)) - .AssertValidAnalysisResult() - .GetAllDiagnostics(); - - if (diagnostics.Length != 0) - { - throw new InvalidOperationException($"Expected no analyzer diagnostics but found '{diagnostics.Length}'"); - } - } -} diff --git a/tests/EffectiveCSharp.Analyzers.Tests/SpanAnalyzerTests.cs b/tests/EffectiveCSharp.Analyzers.Tests/SpanAnalyzerTests.cs deleted file mode 100644 index aa9379c..0000000 --- a/tests/EffectiveCSharp.Analyzers.Tests/SpanAnalyzerTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using CodeFixVerifier = EffectiveCSharp.Analyzers.Tests.Helpers.AnalyzerAndCodeFixVerifier; -using Verifier = EffectiveCSharp.Analyzers.Tests.Helpers.AnalyzerVerifier; - -namespace EffectiveCSharp.Analyzers.Tests; - -#pragma warning disable IDE0028 // We cannot simply object creation on TheoryData because we need to convert from object[] to string, the way it is now is cleaner - -public class SpanAnalyzerTests -{ - public static TheoryData TestData() - { - TheoryData data = new() - { - // This should fire - "var arr = {|ECS1000:new int[10]|};", - - // This should not fire because it's wrapped by a Span - """ - #if NET6_0_OR_GREATER - var arr = new Span(new int[10]); - #endif - """, - - // This should not fire because it's wrapped by a ReadOnlySpan - """ - #if NET6_0_OR_GREATER - var arr = new ReadOnlySpan(new int[10]); - #endif - """, - - // This should not fire because it's suppressed - """ - #pragma warning disable ECS1000 // Use Span for performance - var arr = new int[10]; - #pragma warning restore ECS1000 // Use Span for performance - """, - }; - - return data.WithReferenceAssemblyGroups(); - } - - [Theory] - [MemberData(nameof(TestData))] - public async Task Analyzer(string referenceAssemblyGroup, string source) - { - await Verifier.VerifyAnalyzerAsync( - $$""" - internal class MyClass - { - void Method() - { - {{source}} - } - } - """, - referenceAssemblyGroup); - } - - [Fact(Skip = "Reporting an analyzer failure when the unit test code above shows it is correct")] - public async Task CodeFix() - { - const string testCode = """ - class Program - { - void Method() - { - var arr = {|ECS1000:new int[10]|}; - var val = arr[5]; - } - } - """; - - const string fixedCode = """ - class Program - { - void Method() - { - var arr = new Span(new int[10]); - var val = arr.Slice(5, 1)[0]; - } - } - """; - - await CodeFixVerifier.VerifyCodeFixAsync(testCode, fixedCode, ReferenceAssemblyCatalog.Net80); - } -}