From 5efd32bca774fb57035871a16bf307ad707369be Mon Sep 17 00:00:00 2001 From: Antao Almada Date: Sun, 17 Sep 2023 20:37:12 +0100 Subject: [PATCH] Refactoring --- ... HLQ010_UseForLoop.ValueTypeEnumerator.cs} | 12 +-- ...abric.Hyperlinq.Analyzer.Benchmarks.csproj | 2 +- .../Program.cs | 30 ++------ .../HLQ010_UseForLoopAnalyzerTests.cs | 4 +- .../HLQ013_UseForEachLoopAnalyzerTests.cs | 10 ++- .../Helpers/DiagnosticVerifier.Helper.cs | 74 +++++++++++++------ ...Fabric.Hyperlinq.Analyzer.UnitTests.csproj | 7 +- .../HLQ010/Diagnostic/ArraySegment.cs | 14 ++++ .../HLQ010/NoDiagnostic/ImmutableArray.cs | 14 ++++ .../{Diagnostic => NoDiagnostic}/List.cs | 2 +- .../TestData/HLQ013/Diagnostic/Array.cs | 4 +- .../Diagnostic/CompoundAssignmentOne.cs | 17 +++++ .../HLQ013/Diagnostic/PostfixAssignPlusOne.cs | 17 +++++ .../HLQ013/Diagnostic/PrefixAssignPlusOne.cs | 17 +++++ .../HLQ013/Diagnostic/PrefixIncrement.cs | 17 +++++ .../HLQ013/Diagnostic/ReadOnlySpan.cs | 2 +- .../TestData/HLQ013/Diagnostic/Span.cs | 2 +- .../NoDiagnostic/CompoundAssignmentNotOne.cs | 18 +++++ .../HLQ013/NoDiagnostic/ImmutableArray.cs | 18 +++++ .../HLQ013/NoDiagnostic/IndexNotUsed.cs | 18 +++++ .../TestData/HLQ013/NoDiagnostic/List.cs | 2 +- .../HLQ013/NoDiagnostic/MathOnIndex.cs | 18 +++++ .../{Array.cs => MultipleIndexing.cs} | 15 +--- .../NetFabric.Hyperlinq.Analyzer.Vsix.csproj | 2 +- .../HLQ006_GetEnumeratorReturnTypeAnalyzer.cs | 9 +-- .../HLQ007_NonDisposableEnumeratorAnalyzer.cs | 6 +- .../Analyzers/HLQ010_UseForLoopAnalyzer.cs | 19 ++--- ...012_UseCollectionsMarshalAsSpanAnalyzer.cs | 7 +- .../HLQ013_UseForEachLoopAnalyzer.cs | 33 +++++---- .../NetFabric.Hyperlinq.Analyzer.csproj | 2 +- .../Utils/ExpressionSyntaxExtensions.cs | 70 ++++++++++++++++++ .../Utils/FieldDeclarationSyntaxExtensions.cs | 6 +- .../Utils/ForStatementSyntaxExtensions.cs | 39 ++++++++++ .../MethodDeclarationSyntaxExtensions.cs | 10 +-- .../PropertyDeclarationSyntaxExtensions.cs | 2 +- .../Utils/TypeDeclarationSyntaxExtensions.cs | 16 ++-- ...TypeParameterConstraintSyntaxExtensions.cs | 6 +- .../Utils/TypeSymbolExtensions.cs | 41 +++++++++- 38 files changed, 459 insertions(+), 143 deletions(-) rename NetFabric.Hyperlinq.Analyzer.Benchmarks/{HLQ010_UseForLoop.cs => HLQ010_UseForLoop.ValueTypeEnumerator.cs} (62%) create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/Diagnostic/ArraySegment.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/NoDiagnostic/ImmutableArray.cs rename NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/{Diagnostic => NoDiagnostic}/List.cs (87%) create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/CompoundAssignmentOne.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PostfixAssignPlusOne.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixAssignPlusOne.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixIncrement.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/CompoundAssignmentNotOne.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/ImmutableArray.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/IndexNotUsed.cs create mode 100644 NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/MathOnIndex.cs rename NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/{Array.cs => MultipleIndexing.cs} (50%) create mode 100644 NetFabric.Hyperlinq.Analyzer/Utils/ExpressionSyntaxExtensions.cs create mode 100644 NetFabric.Hyperlinq.Analyzer/Utils/ForStatementSyntaxExtensions.cs diff --git a/NetFabric.Hyperlinq.Analyzer.Benchmarks/HLQ010_UseForLoop.cs b/NetFabric.Hyperlinq.Analyzer.Benchmarks/HLQ010_UseForLoop.ValueTypeEnumerator.cs similarity index 62% rename from NetFabric.Hyperlinq.Analyzer.Benchmarks/HLQ010_UseForLoop.cs rename to NetFabric.Hyperlinq.Analyzer.Benchmarks/HLQ010_UseForLoop.ValueTypeEnumerator.cs index 8e60c69..cce3e65 100644 --- a/NetFabric.Hyperlinq.Analyzer.Benchmarks/HLQ010_UseForLoop.cs +++ b/NetFabric.Hyperlinq.Analyzer.Benchmarks/HLQ010_UseForLoop.ValueTypeEnumerator.cs @@ -2,9 +2,9 @@ namespace NetFabric.Hyperlinq.Analyzer.Benchmarks; -public class HLQ010_UseForLoop +public class HLQ010_UseForLoop_ValueTypeEnumerator { - List? list; + ArraySegment source; [Params(100, 10_000)] public int Count { get; set; } @@ -12,14 +12,14 @@ public class HLQ010_UseForLoop [GlobalSetup] public void GlobalSetup() { - list = System.Linq.Enumerable.Range(0, Count).ToList(); + source = new ArraySegment(Enumerable.Range(0, Count).ToArray()); } [Benchmark(Baseline = true)] public int Foreach() { var sum = 0; - foreach (var item in list!) + foreach (var item in source!) sum += item; return sum; } @@ -28,9 +28,9 @@ public int Foreach() public int For() { var sum = 0; - for (var index = 0; index < list!.Count; index++) + for (var index = 0; index < source!.Count; index++) { - var item = list![index]; + var item = source![index]; sum += item; } return sum; diff --git a/NetFabric.Hyperlinq.Analyzer.Benchmarks/NetFabric.Hyperlinq.Analyzer.Benchmarks.csproj b/NetFabric.Hyperlinq.Analyzer.Benchmarks/NetFabric.Hyperlinq.Analyzer.Benchmarks.csproj index 4b09b73..e3c71d9 100644 --- a/NetFabric.Hyperlinq.Analyzer.Benchmarks/NetFabric.Hyperlinq.Analyzer.Benchmarks.csproj +++ b/NetFabric.Hyperlinq.Analyzer.Benchmarks/NetFabric.Hyperlinq.Analyzer.Benchmarks.csproj @@ -10,6 +10,6 @@ - + diff --git a/NetFabric.Hyperlinq.Analyzer.Benchmarks/Program.cs b/NetFabric.Hyperlinq.Analyzer.Benchmarks/Program.cs index 03e24c1..ca5773c 100644 --- a/NetFabric.Hyperlinq.Analyzer.Benchmarks/Program.cs +++ b/NetFabric.Hyperlinq.Analyzer.Benchmarks/Program.cs @@ -7,29 +7,15 @@ using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; -namespace NetFabric.Hyperlinq.Analyzer.Benchmarks; - -class Program -{ - static void Main(string[] args) - { - var config = DefaultConfig.Instance +var config = DefaultConfig.Instance .WithSummaryStyle(SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend)) .AddDiagnoser(MemoryDiagnoser.Default) - .AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig( - printSource: true, - exportGithubMarkdown: true))) + //.AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig( + // printSource: true, + // exportGithubMarkdown: true))) .AddExporter(MarkdownExporter.GitHub) - .AddJob(Job.Default - .WithRuntime(CoreRuntime.Core60) - .WithId(".NET 6")) - .AddJob(Job.Default - .WithRuntime(CoreRuntime.Core70) - .WithId(".NET 7")) - .AddJob(Job.Default - .WithRuntime(CoreRuntime.Core80) - .WithId(".NET 8")); + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core60)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core70)) + .AddJob(Job.Default.WithRuntime(CoreRuntime.Core80)); - BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); - } -} +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ010_UseForLoopAnalyzerTests.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ010_UseForLoopAnalyzerTests.cs index ab21126..51e4ea2 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ010_UseForLoopAnalyzerTests.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ010_UseForLoopAnalyzerTests.cs @@ -18,8 +18,10 @@ public class UseForLoopAnalyzerTests : DiagnosticVerifier [Theory] [InlineData("TestData/HLQ010/NoDiagnostic/Array.cs")] [InlineData("TestData/HLQ010/NoDiagnostic/Span.cs")] + [InlineData("TestData/HLQ010/NoDiagnostic/List.cs")] [InlineData("TestData/HLQ010/NoDiagnostic/ReadOnlySpan.cs")] [InlineData("TestData/HLQ010/NoDiagnostic/Dictionary.cs")] + [InlineData("TestData/HLQ010/NoDiagnostic/ImmutableArray.cs")] public void Verify_NoDiagnostics(string path) { var paths = new[] @@ -30,7 +32,7 @@ public void Verify_NoDiagnostics(string path) } [Theory] - [InlineData("TestData/HLQ010/Diagnostic/List.cs", 11, 34)] + [InlineData("TestData/HLQ010/Diagnostic/ArraySegment.cs", 10, 34)] public void Verify_Diagnostic(string path, int line, int column) { var paths = new[] diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ013_UseForEachLoopAnalyzerTests.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ013_UseForEachLoopAnalyzerTests.cs index ef54901..f7bc2a6 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ013_UseForEachLoopAnalyzerTests.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/HLQ013_UseForEachLoopAnalyzerTests.cs @@ -17,7 +17,11 @@ public class UseForEachLoopAnalyzerTests : DiagnosticVerifier [Theory] [InlineData("TestData/HLQ013/NoDiagnostic/List.cs")] - [InlineData("TestData/HLQ013/NoDiagnostic/Array.cs")] + [InlineData("TestData/HLQ013/NoDiagnostic/ImmutableArray.cs")] + [InlineData("TestData/HLQ013/NoDiagnostic/IndexNotUsed.cs")] + [InlineData("TestData/HLQ013/NoDiagnostic/MathOnIndex.cs")] + [InlineData("TestData/HLQ013/NoDiagnostic/MultipleIndexing.cs")] + [InlineData("TestData/HLQ013/NoDiagnostic/CompoundAssignmentNotOne.cs")] public void Verify_NoDiagnostics(string path) { var paths = new[] @@ -31,6 +35,10 @@ public void Verify_NoDiagnostics(string path) [InlineData("TestData/HLQ013/Diagnostic/Array.cs", 10, 13, "int[]")] [InlineData("TestData/HLQ013/Diagnostic/Span.cs", 10, 13, "System.Span")] [InlineData("TestData/HLQ013/Diagnostic/ReadOnlySpan.cs", 10, 13, "System.ReadOnlySpan")] + [InlineData("TestData/HLQ013/Diagnostic/PrefixIncrement.cs", 10, 13, "int[]")] + [InlineData("TestData/HLQ013/Diagnostic/CompoundAssignmentOne.cs", 10, 13, "int[]")] + [InlineData("TestData/HLQ013/Diagnostic/PrefixAssignPlusOne.cs", 10, 13, "int[]")] + [InlineData("TestData/HLQ013/Diagnostic/PostfixAssignPlusOne.cs", 10, 13, "int[]")] public void Verify_Diagnostic(string path, int line, int column, string collectionType) { var paths = new[] diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/Helpers/DiagnosticVerifier.Helper.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/Helpers/DiagnosticVerifier.Helper.cs index 5c03729..0172025 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/Helpers/DiagnosticVerifier.Helper.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/Helpers/DiagnosticVerifier.Helper.cs @@ -15,10 +15,13 @@ namespace TestHelper /// public abstract partial class DiagnosticVerifier { - private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); - private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); - private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); - private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); + private static readonly MetadataReference SystemPrivateCorLibReference = MetadataReference.CreateFromFile(typeof(Object).Assembly.Location); + private static readonly MetadataReference SystemRuntimeReference = MetadataReference.CreateFromFile(typeof(ValueType).Assembly.Location); + private static readonly MetadataReference SystemLinqReference = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location); + private static readonly MetadataReference MicrosoftCodeAnalysisCSharpReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location); + private static readonly MetadataReference MicrosoftCodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location); + private static readonly MetadataReference SystemCollectionsImmutableReference = MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location); + private static readonly MetadataReference SystemRuntimeCompilerServicesUnsafeReference = MetadataReference.CreateFromFile(typeof(System.Runtime.CompilerServices.Unsafe).Assembly.Location); internal static string DefaultFilePathPrefix = "Test"; internal static string CSharpDefaultFileExt = "cs"; @@ -138,27 +141,56 @@ protected static Document CreateDocument(string source, string language = Langua private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) { string fileNamePrefix = DefaultFilePathPrefix; - string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; - - var projectId = ProjectId.CreateNewId(debugName: TestProjectName); - - var solution = new AdhocWorkspace() - .CurrentSolution - .AddProject(projectId, TestProjectName, TestProjectName, language) - .AddMetadataReference(projectId, CorlibReference) - .AddMetadataReference(projectId, SystemCoreReference) - .AddMetadataReference(projectId, CSharpSymbolsReference) - .AddMetadataReference(projectId, CodeAnalysisReference); - + string fileExt = language == LanguageNames.CSharp + ? CSharpDefaultFileExt + : VisualBasicDefaultExt; + + // Create the workspace + var workspace = new AdhocWorkspace(); + + // Create the project + var projectId = ProjectId.CreateNewId(); + var versionStamp = VersionStamp.Create(); + var projectName = TestProjectName; + var assemblyName = TestProjectName; + var projectInfo = ProjectInfo.Create( + projectId, + versionStamp, + projectName, + assemblyName, + language); + + // Set the target framework by adding metadata references + var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + projectInfo = projectInfo.WithCompilationOptions(compilationOptions); + + var metadataReferences = new[] + { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), + MetadataReference.CreateFromFile(typeof(CSharpCompilation).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Compilation).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Runtime.CompilerServices.Unsafe).Assembly.Location), + MetadataReference.CreateFromFile(typeof(ImmutableArray).Assembly.Location), + // TODO: Add other framework assemblies as needed + }; + projectInfo = projectInfo.WithMetadataReferences(metadataReferences); + + // Add the project to the workspace + var project = workspace.AddProject(projectInfo); + + // Add documents to the project int count = 0; - foreach (var source in sources) + foreach (var sourceCode in sources) { - var newFileName = fileNamePrefix + count + "." + fileExt; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); + // Add the document to the project + var newFileName = $"{fileNamePrefix}{count}.{fileExt}"; + var sourceText = SourceText.From(sourceCode); + project = project.AddDocument(newFileName, sourceText).Project; + count++; } - return solution.GetProject(projectId); + return project; } #endregion } diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/NetFabric.Hyperlinq.Analyzer.UnitTests.csproj b/NetFabric.Hyperlinq.Analyzer.UnitTests/NetFabric.Hyperlinq.Analyzer.UnitTests.csproj index 3d7aedc..acbfde9 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/NetFabric.Hyperlinq.Analyzer.UnitTests.csproj +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/NetFabric.Hyperlinq.Analyzer.UnitTests.csproj @@ -6,10 +6,11 @@ - + + - - + + all runtime; build; native; contentfiles; analyzers diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/Diagnostic/ArraySegment.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/Diagnostic/ArraySegment.cs new file mode 100644 index 0000000..cacaaa6 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/Diagnostic/ArraySegment.cs @@ -0,0 +1,14 @@ +using System; + +namespace HLQ010.NoDiagnostic.ArraySegment +{ + partial class C + { + void Method() + { + var source = new ArraySegment(); + foreach (var item in source) + Console.WriteLine(item); + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/NoDiagnostic/ImmutableArray.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/NoDiagnostic/ImmutableArray.cs new file mode 100644 index 0000000..6d6c14d --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/NoDiagnostic/ImmutableArray.cs @@ -0,0 +1,14 @@ +using System; + +namespace HLQ010.NoDiagnostic.ImmutableArray +{ + partial class C + { + void Method() + { + var source = System.Collections.Immutable.ImmutableArray.Create(System.Array.Empty()); + foreach (var item in source) + Console.WriteLine(item); + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/Diagnostic/List.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/NoDiagnostic/List.cs similarity index 87% rename from NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/Diagnostic/List.cs rename to NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/NoDiagnostic/List.cs index d609739..5e3eb59 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/Diagnostic/List.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ010/NoDiagnostic/List.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace HLQ010.NoDiagnostic.List +namespace HLQ010.Diagnostic.List { partial class C { diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Array.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Array.cs index ec03bd3..04987d4 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Array.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Array.cs @@ -1,13 +1,13 @@ using System; -namespace HLQ013.Diagnostic.Array +namespace HLQ013.Diagnostic.ArrayTest { partial class C { void Method() { var source = new[] { 1, 2, 3 }; - for (var index = 0; index < source.Length; index++) + for (var index = 0; index < source.Length; index++) { var item = source[index]; Console.WriteLine(item); diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/CompoundAssignmentOne.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/CompoundAssignmentOne.cs new file mode 100644 index 0000000..81dd572 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/CompoundAssignmentOne.cs @@ -0,0 +1,17 @@ +using System; + +namespace HLQ013.Diagnostic.CompoundAssignmentOneTest +{ + partial class C + { + void Method() + { + var source = new[] { 1, 2, 3 }; + for (var index = 0; index < source.Length; index += 1) + { + var item = source[index]; + Console.WriteLine(item); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PostfixAssignPlusOne.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PostfixAssignPlusOne.cs new file mode 100644 index 0000000..a8c32a1 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PostfixAssignPlusOne.cs @@ -0,0 +1,17 @@ +using System; + +namespace HLQ013.Diagnostic.PostfixAssignPlusOneTest +{ + partial class C + { + void Method() + { + var source = new[] { 1, 2, 3 }; + for (var index = 0; index < source.Length; index = 1 + index) + { + var item = source[index]; + Console.WriteLine(item); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixAssignPlusOne.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixAssignPlusOne.cs new file mode 100644 index 0000000..984ea36 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixAssignPlusOne.cs @@ -0,0 +1,17 @@ +using System; + +namespace HLQ013.Diagnostic.PrefixAssignPlusOneTest +{ + partial class C + { + void Method() + { + var source = new[] { 1, 2, 3 }; + for (var index = 0; index < source.Length; index = index + 1) + { + var item = source[index]; + Console.WriteLine(item); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixIncrement.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixIncrement.cs new file mode 100644 index 0000000..42f78fd --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/PrefixIncrement.cs @@ -0,0 +1,17 @@ +using System; + +namespace HLQ013.Diagnostic.PrefixIncrementTest +{ + partial class C + { + void Method() + { + var source = new[] { 1, 2, 3 }; + for (var index = 0; index < source.Length; ++index) + { + var item = source[index]; + Console.WriteLine(item); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/ReadOnlySpan.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/ReadOnlySpan.cs index 699081b..2def57b 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/ReadOnlySpan.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/ReadOnlySpan.cs @@ -1,6 +1,6 @@ using System; -namespace HLQ013.Diagnostic.ReadOnlySpan +namespace HLQ013.Diagnostic.ReadOnlySpanTest { partial class C { diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Span.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Span.cs index 7aba7e6..60f2cfd 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Span.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/Diagnostic/Span.cs @@ -1,6 +1,6 @@ using System; -namespace HLQ013.Diagnostic.Span +namespace HLQ013.Diagnostic.SpanTest { partial class C { diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/CompoundAssignmentNotOne.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/CompoundAssignmentNotOne.cs new file mode 100644 index 0000000..d57e9a4 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/CompoundAssignmentNotOne.cs @@ -0,0 +1,18 @@ +using System; + +namespace HLQ013.NoDiagnostic.CompoundAssignmentNotOneTest +{ + partial class C + { + void Method() + { + var source = new[] { 1, 2, 3 }; + + for (var index = 0; index < source.Length; index += 2) // not incrementing by 1 + { + var item = source[index]; + Console.WriteLine(item); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/ImmutableArray.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/ImmutableArray.cs new file mode 100644 index 0000000..47e6ce8 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/ImmutableArray.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Immutable; + +namespace HLQ013.NoDiagnostic.ImmutableArrayTest +{ + partial class C + { + void Method() + { + var source = ImmutableArray.Create(new[] { 1, 2, 3 }); + for (var index = 0; index < source.Length; index++) + { + var item = source[index]; + Console.WriteLine(item); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/IndexNotUsed.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/IndexNotUsed.cs new file mode 100644 index 0000000..5130553 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/IndexNotUsed.cs @@ -0,0 +1,18 @@ +using System; + +namespace HLQ013.NoDiagnostic.IndexNotUsedTest +{ + partial class C + { + void Method() + { + var source = new[] { 1, 2, 3 }; + + for (var index = 0; index < source.Length; index++) + { + // variable not used for indexing + Console.WriteLine(index); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/List.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/List.cs index e43a0b7..c3fdda3 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/List.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/List.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace HLQ013.NoDiagnostic.List +namespace HLQ013.NoDiagnostic.ListTest { partial class C { diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/MathOnIndex.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/MathOnIndex.cs new file mode 100644 index 0000000..7b9b53b --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/MathOnIndex.cs @@ -0,0 +1,18 @@ +using System; + +namespace HLQ013.NoDiagnostic.MathOnIndexTest +{ + partial class C + { + void Method() + { + var source = new[] { 1, 2, 3 }; + + for (var index = 0; index < source.Length; index++) + { + var item = source[index + 1]; // using value other than index + Console.WriteLine(item); + } + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/Array.cs b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/MultipleIndexing.cs similarity index 50% rename from NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/Array.cs rename to NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/MultipleIndexing.cs index 0cafb00..8449de1 100644 --- a/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/Array.cs +++ b/NetFabric.Hyperlinq.Analyzer.UnitTests/TestData/HLQ013/NoDiagnostic/MultipleIndexing.cs @@ -1,6 +1,6 @@ using System; -namespace HLQ013.NoDiagnostic.Array +namespace HLQ013.NoDiagnostic.MultipleIndexingTest { partial class C { @@ -8,19 +8,6 @@ void Method() { var source = new[] { 1, 2, 3 }; - for (var index = 0; index < source.Length; index++) - { - // variable not used for indexing - Console.WriteLine(index); - } - - for (var index = 0; index < source.Length; index++) - { - var item = source[index]; // using variable for indexing - Console.WriteLine(item); - Console.WriteLine(index); // using variable for something else - } - var source2 = source; for (var index = 0; index < source.Length; index++) { diff --git a/NetFabric.Hyperlinq.Analyzer.Vsix/NetFabric.Hyperlinq.Analyzer.Vsix.csproj b/NetFabric.Hyperlinq.Analyzer.Vsix/NetFabric.Hyperlinq.Analyzer.Vsix.csproj index ce12441..5494593 100644 --- a/NetFabric.Hyperlinq.Analyzer.Vsix/NetFabric.Hyperlinq.Analyzer.Vsix.csproj +++ b/NetFabric.Hyperlinq.Analyzer.Vsix/NetFabric.Hyperlinq.Analyzer.Vsix.csproj @@ -30,7 +30,7 @@ - + diff --git a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ006_GetEnumeratorReturnTypeAnalyzer.cs b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ006_GetEnumeratorReturnTypeAnalyzer.cs index da4a59d..10ecd13 100644 --- a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ006_GetEnumeratorReturnTypeAnalyzer.cs +++ b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ006_GetEnumeratorReturnTypeAnalyzer.cs @@ -2,10 +2,9 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using NetFabric.CodeAnalysis; using System.Collections.Generic; using System.Collections.Immutable; -using System.Reflection.Metadata; +using NetFabric.CodeAnalysis; namespace NetFabric.Hyperlinq.Analyzer { @@ -39,7 +38,7 @@ public override void Initialize(AnalysisContext context) static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) { - if (!(context.Node is MethodDeclarationSyntax methodDeclarationSyntax)) + if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax) return; var semanticModel = context.SemanticModel; @@ -60,14 +59,14 @@ static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) { // check if it returns an enumerator var type = semanticModel.GetTypeInfo(returnType).Type; - if (type is null || !type.IsEnumerator(semanticModel.Compilation, out _)) + if (!type.IsEnumerator()) return; } else if (identifier == "GetAsyncEnumerator" && methodDeclarationSyntax.ParameterList.Parameters.Count < 2) { // check if it returns an async enumerator var type = semanticModel.GetTypeInfo(returnType).Type; - if (type is null || !type.IsAsyncEnumerator(semanticModel.Compilation, out _)) + if (!type.IsAsyncEnumerator(context.Compilation)) return; } else diff --git a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ007_NonDisposableEnumeratorAnalyzer.cs b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ007_NonDisposableEnumeratorAnalyzer.cs index c6ca161..f0d8ee3 100644 --- a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ007_NonDisposableEnumeratorAnalyzer.cs +++ b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ007_NonDisposableEnumeratorAnalyzer.cs @@ -3,8 +3,6 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using NetFabric.CodeAnalysis; -using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -76,7 +74,7 @@ static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) IMethodSymbol getEnumerator; // check if disposable type is an enumerator - if (declaredTypeSymbol.IsEnumerator(compilation, out _)) + if (declaredTypeSymbol.IsEnumerator()) { // check if there's an outer type typeDeclaration = typeDeclaration.Ancestors().OfType().FirstOrDefault(); @@ -94,7 +92,7 @@ static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) getEnumerator = enumerableSymbols.GetEnumerator; } - else if (declaredTypeSymbol.IsAsyncEnumerator(compilation, out _)) + else if (declaredTypeSymbol.IsAsyncEnumerator(compilation)) { // check if there's an outer type typeDeclaration = typeDeclaration.Ancestors().OfType().FirstOrDefault(); diff --git a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ010_UseForLoopAnalyzer.cs b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ010_UseForLoopAnalyzer.cs index 7d184df..99d4af8 100644 --- a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ010_UseForLoopAnalyzer.cs +++ b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ010_UseForLoopAnalyzer.cs @@ -2,8 +2,8 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using NetFabric.CodeAnalysis; using System.Collections.Immutable; -using System.Linq; namespace NetFabric.Hyperlinq.Analyzer { @@ -43,20 +43,15 @@ static void AnalyzeForEachStatement(SyntaxNodeAnalysisContext context) if (expressionType is null) return; - if (expressionType.IsArrayType() || - expressionType.IsSpanType()) + // if foreach uses indexer for this type then let it be + if (expressionType.IsForEachOptimized()) return; - var properties = expressionType.GetMembers().OfType(); - var hasIndexer = properties.Any(property => - property.IsIndexer && - property.Parameters.Length == 1 && - property.Parameters[0].Type.SpecialType == SpecialType.System_Int32); - bool hasCountOrLength = properties.Any(property => - property.IsReadOnly && - (property.Name == "Length" || property.Name == "Count")); + // if it's List then should be using foreach with CollectionsMarshal.AsSpan() + if (expressionType.IsList(out _)) + return; - if (hasIndexer && hasCountOrLength) + if (expressionType.IsIndexable(out _)) { var diagnostic = Diagnostic.Create(Rule, foreachStatement.Expression.GetLocation()); context.ReportDiagnostic(diagnostic); diff --git a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ012_UseCollectionsMarshalAsSpanAnalyzer.cs b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ012_UseCollectionsMarshalAsSpanAnalyzer.cs index 975d3e9..12e0944 100644 --- a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ012_UseCollectionsMarshalAsSpanAnalyzer.cs +++ b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ012_UseCollectionsMarshalAsSpanAnalyzer.cs @@ -39,13 +39,10 @@ static void AnalyzeForEachStatement(SyntaxNodeAnalysisContext context) var foreachStatement = (ForEachStatementSyntax)context.Node; var collectionType = context.SemanticModel.GetTypeInfo(foreachStatement.Expression).Type; - if (collectionType is INamedTypeSymbol namedTypeSymbol && - namedTypeSymbol.IsGenericType && - namedTypeSymbol.OriginalDefinition.ToString() == "System.Collections.Generic.List") + if (collectionType.IsList(out var itemType)) { - var listType = namedTypeSymbol.TypeArguments[0]; var diagnostic = Diagnostic.Create(Rule, foreachStatement.Expression.GetLocation(), - listType.ToDisplayString()); + itemType.ToDisplayString()); context.ReportDiagnostic(diagnostic); } } diff --git a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ013_UseForEachLoopAnalyzer.cs b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ013_UseForEachLoopAnalyzer.cs index 8bfc66a..b8ef137 100644 --- a/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ013_UseForEachLoopAnalyzer.cs +++ b/NetFabric.Hyperlinq.Analyzer/Analyzers/HLQ013_UseForEachLoopAnalyzer.cs @@ -40,27 +40,20 @@ private static void AnalyzeForLoop(SyntaxNodeAnalysisContext context) { var forStatement = (ForStatementSyntax)context.Node; - var declaration = forStatement.Declaration; - if (declaration is null || declaration.Variables.Count != 1) - return; - var statement = forStatement.Statement; if (statement is null) return; - // check if declaration declares a variable of type int - var variableType = context.SemanticModel.GetTypeInfo(declaration.Type).Type; - if (variableType is null || variableType.SpecialType != SpecialType.System_Int32) + if (!forStatement.IsIncrementalStep(context, out var forIdentifier)) return; // check if the variable is used in the statement only for accessing indexer of one single array or span - var variableIdentifier = declaration.Variables[0].Identifier.ToString(); var indexedCollection = default(string); var indexedCollectionType = default(ITypeSymbol); foreach (var identifierNameSyntax in statement.DescendantNodes().OfType()) { // check if the identifier is using the variable - if (identifierNameSyntax.Identifier.ToString() != variableIdentifier) + if (identifierNameSyntax.Identifier.ToString() != forIdentifier) continue; // check if the identifier is used for something other than to access indexer @@ -68,14 +61,16 @@ private static void AnalyzeForLoop(SyntaxNodeAnalysisContext context) if (elementAccessExpressionSyntax is null) return; - if (elementAccessExpressionSyntax.ArgumentList.Arguments.Count != 1) + var arguments = elementAccessExpressionSyntax.ArgumentList.Arguments; + if (arguments.Count != 1) continue; - var expression = elementAccessExpressionSyntax.Expression; + // check if the indexer variable is the same as the one used in the for loop + if (arguments[0].ToString() != forIdentifier) + return; - // check if the indexed collection is an array or a span - var expressionType = context.SemanticModel.GetTypeInfo(expression).Type; - if (!(expressionType.IsArrayType() || expressionType.IsSpanType())) + var expression = elementAccessExpressionSyntax.Expression; + if (expression is not IdentifierNameSyntax elementAccessIdentifierNameSyntax) return; // check if the indexed collection is the same for all accesses @@ -83,14 +78,20 @@ private static void AnalyzeForLoop(SyntaxNodeAnalysisContext context) if (!string.IsNullOrEmpty(indexedCollection) && indexedCollection != expressionString) return; + // check if foreach is optimized for this type + var typeInfo = context.SemanticModel.GetTypeInfo(elementAccessIdentifierNameSyntax); + var expressionType = typeInfo.ConvertedType; + if (!expressionType.IsForEachOptimized()) + return; + indexedCollection = expressionString; indexedCollectionType = expressionType; } if (indexedCollection is null) return; - var diagnostic = Diagnostic.Create(Rule, forStatement.ForKeyword.GetLocation(), indexedCollectionType!.ToDisplayString()); - context.ReportDiagnostic(diagnostic); + var diagnostic2 = Diagnostic.Create(Rule, forStatement.ForKeyword.GetLocation(), indexedCollectionType!.ToString()); + context.ReportDiagnostic(diagnostic2); } } } \ No newline at end of file diff --git a/NetFabric.Hyperlinq.Analyzer/NetFabric.Hyperlinq.Analyzer.csproj b/NetFabric.Hyperlinq.Analyzer/NetFabric.Hyperlinq.Analyzer.csproj index afcda54..23ef050 100644 --- a/NetFabric.Hyperlinq.Analyzer/NetFabric.Hyperlinq.Analyzer.csproj +++ b/NetFabric.Hyperlinq.Analyzer/NetFabric.Hyperlinq.Analyzer.csproj @@ -10,7 +10,7 @@ - + all diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/ExpressionSyntaxExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/ExpressionSyntaxExtensions.cs new file mode 100644 index 0000000..6382c0b --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer/Utils/ExpressionSyntaxExtensions.cs @@ -0,0 +1,70 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using System.Diagnostics.CodeAnalysis; + +namespace NetFabric.Hyperlinq.Analyzer +{ + static class ExpressionSyntaxExtensions + { + public static bool IsIncrementAssignment(this ExpressionSyntax? expression, [NotNullWhen(true)] out string? identifier) + { + // check if it's something like `index++` + if (expression is PostfixUnaryExpressionSyntax { OperatorToken.ValueText: "++" } postfixIncrementExpression) + { + identifier = postfixIncrementExpression.Operand.ToString(); + return true; + } + + // check if it's something like `++index` + if (expression is PrefixUnaryExpressionSyntax { OperatorToken.ValueText: "++" } prefixIncrementExpression) + { + identifier = prefixIncrementExpression.Operand.ToString(); + return true; + } + + // check if it's something like `index += 1` + if (expression is AssignmentExpressionSyntax assignmentExpression && + assignmentExpression.Kind() == SyntaxKind.AddAssignmentExpression && + assignmentExpression.Left is IdentifierNameSyntax identifierName && + assignmentExpression.Right.IsOne()) + { + identifier = identifierName.Identifier.ValueText; + return true; + } + + // check if it's something like `index = index + 1` or `index = 1 + index` + if (expression is AssignmentExpressionSyntax { Left: IdentifierNameSyntax assignmentIdentifier, Right: BinaryExpressionSyntax binaryExpression } && + binaryExpression.Kind() == SyntaxKind.AddExpression && + IsIdentifierAndOneOrViceVersa(binaryExpression.Left, binaryExpression.Right, out var binaryExpressionIdentifier) + ) + { + identifier = assignmentIdentifier.Identifier.ValueText; + return binaryExpressionIdentifier == identifier; + } + + identifier = default; + return false; + + static bool IsIdentifierAndOneOrViceVersa(ExpressionSyntax first, ExpressionSyntax second, [NotNullWhen(true)] out string? identifier) + => IsIdentifierAndOne(first, second, out identifier) || IsIdentifierAndOne(second, first, out identifier); + + static bool IsIdentifierAndOne(ExpressionSyntax first, ExpressionSyntax second, [NotNullWhen(true)] out string? identifier) + { + if(first is IdentifierNameSyntax identifierName && second.IsOne()) + { + identifier = identifierName.Identifier.ValueText; + return true; + } + + identifier = default; + return false; + } + } + + public static bool IsOne(this ExpressionSyntax? expression) + => expression is LiteralExpressionSyntax literalExpression && + literalExpression.Kind() == SyntaxKind.NumericLiteralExpression && + literalExpression.Token.Value is int value && + value == 1; + } +} diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/FieldDeclarationSyntaxExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/FieldDeclarationSyntaxExtensions.cs index 4e01b92..4e47ff1 100644 --- a/NetFabric.Hyperlinq.Analyzer/Utils/FieldDeclarationSyntaxExtensions.cs +++ b/NetFabric.Hyperlinq.Analyzer/Utils/FieldDeclarationSyntaxExtensions.cs @@ -15,7 +15,7 @@ public static bool IsPublic(this FieldDeclarationSyntax fieldDeclarationSyntax) if (!fieldDeclarationSyntax.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PublicKeyword))) return false; - for (var node = fieldDeclarationSyntax.Parent; node is object; node = node.Parent) + for (var node = fieldDeclarationSyntax.Parent; node is not null; node = node.Parent) { if (node is TypeDeclarationSyntax type) { @@ -46,8 +46,8 @@ public static bool IsEnumerableValueType(this FieldDeclarationSyntax fieldDeclar else { if (!typeSymbol.IsValueType - || !(typeSymbol.IsEnumerator(context.Compilation, out _) - || typeSymbol.IsAsyncEnumerator(context.Compilation, out _))) + || !(typeSymbol.IsEnumerator() + || typeSymbol.IsAsyncEnumerator(context.Compilation))) return false; } diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/ForStatementSyntaxExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/ForStatementSyntaxExtensions.cs new file mode 100644 index 0000000..21438f0 --- /dev/null +++ b/NetFabric.Hyperlinq.Analyzer/Utils/ForStatementSyntaxExtensions.cs @@ -0,0 +1,39 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace NetFabric.Hyperlinq.Analyzer +{ + static class ForStatementSyntaxExtensions + { + public static bool IsIncrementalStep(this ForStatementSyntax forStatement, SyntaxNodeAnalysisContext context, [NotNullWhen(true)] out string? identifier) + { + identifier = default; + + var incrementors = forStatement.Incrementors; + if (incrementors.Count != 1) + return false; + + // check if incrementor is incrementing the variable by one + var incrementor = incrementors[0]; + if (!incrementor.IsIncrementAssignment(out identifier)) + return false; + + var declaration = forStatement.Declaration; + if (declaration is null || declaration.Variables.Count != 1) + return false; + + // check if the variable is the same used in the incrementor + if (identifier != declaration.Variables[0].Identifier.ToString()) + return false; + + // check if declaration declares a variable of type int + var variableType = context.SemanticModel.GetTypeInfo(declaration.Type).Type; + if (variableType is null || variableType.SpecialType != SpecialType.System_Int32) + return false; + + return true; + } + } +} diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/MethodDeclarationSyntaxExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/MethodDeclarationSyntaxExtensions.cs index 3482530..2ad4455 100644 --- a/NetFabric.Hyperlinq.Analyzer/Utils/MethodDeclarationSyntaxExtensions.cs +++ b/NetFabric.Hyperlinq.Analyzer/Utils/MethodDeclarationSyntaxExtensions.cs @@ -52,17 +52,17 @@ public static bool IsExtensionMethod(this MethodDeclarationSyntax methodDeclarat public static bool IsEnumerableInstanceMethod(this MethodDeclarationSyntax methodDeclarationSyntax, SyntaxNodeAnalysisContext context) { var typeDeclaration = methodDeclarationSyntax.Ancestors().OfType().FirstOrDefault(); - return typeDeclaration is object && typeDeclaration.IsEnumerable(context); + return typeDeclaration is not null && typeDeclaration.IsEnumerable(context); } public static bool IsEnumerableExtensionMethod(this MethodDeclarationSyntax methodDeclarationSyntax, SyntaxNodeAnalysisContext context) { if (methodDeclarationSyntax.IsExtensionMethod(out var parameterSyntax)) { - if (parameterSyntax.Type is object) + if (parameterSyntax.Type is not null) { var typeSymbol = context.SemanticModel.GetTypeInfo(parameterSyntax.Type).Type; - if (typeSymbol is object) + if (typeSymbol is not null) { return typeSymbol.IsEnumerable(context.Compilation, out _) || typeSymbol.IsAsyncEnumerable(context.Compilation, out _); } @@ -151,7 +151,7 @@ public static bool IsEmptyAsyncMethod(this MethodDeclarationSyntax methodDeclara public static bool IsPublic(this MethodDeclarationSyntax methodDeclarationSyntax) { - for (SyntaxNode? node = methodDeclarationSyntax; node is object; node = node.Parent) + for (SyntaxNode? node = methodDeclarationSyntax; node is not null; node = node.Parent) { if (node is TypeDeclarationSyntax type) { @@ -186,7 +186,7 @@ public static bool HasAttribute(this MethodDeclarationSyntax methodDeclarationSy => methodDeclarationSyntax.AttributeLists .Select(attributeList => attributeList.Target) .Any(target => - target is object && + target is not null && (target.Identifier.ValueText == name || target.Identifier.ValueText == $"{name}Attribute")); } } diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/PropertyDeclarationSyntaxExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/PropertyDeclarationSyntaxExtensions.cs index f1252c3..de2776f 100644 --- a/NetFabric.Hyperlinq.Analyzer/Utils/PropertyDeclarationSyntaxExtensions.cs +++ b/NetFabric.Hyperlinq.Analyzer/Utils/PropertyDeclarationSyntaxExtensions.cs @@ -11,7 +11,7 @@ public static bool IsPublic(this PropertyDeclarationSyntax propertyDeclarationSy if (!propertyDeclarationSyntax.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PublicKeyword))) return false; - for (var node = propertyDeclarationSyntax.Parent; node is object; node = node.Parent) + for (var node = propertyDeclarationSyntax.Parent; node is not null; node = node.Parent) { if (node is TypeDeclarationSyntax type) { diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/TypeDeclarationSyntaxExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/TypeDeclarationSyntaxExtensions.cs index b31725b..ccae6f8 100644 --- a/NetFabric.Hyperlinq.Analyzer/Utils/TypeDeclarationSyntaxExtensions.cs +++ b/NetFabric.Hyperlinq.Analyzer/Utils/TypeDeclarationSyntaxExtensions.cs @@ -23,7 +23,7 @@ public static bool IsEnumerable(this TypeDeclarationSyntax typeDeclarationSyntax { var name = typeDeclarationSyntax.GetMetadataName(); var declaredTypeSymbol = context.Compilation.GetTypeByMetadataName(name); - return declaredTypeSymbol is object + return declaredTypeSymbol is not null && (declaredTypeSymbol.IsEnumerable(context.Compilation, out _) || declaredTypeSymbol.IsAsyncEnumerable(context.Compilation, out _)); } @@ -31,12 +31,12 @@ public static bool IsEnumerable(this TypeDeclarationSyntax typeDeclarationSyntax public static bool ImplementsInterface(this TypeDeclarationSyntax typeDeclarationSyntax, SpecialType interfaceType, SyntaxNodeAnalysisContext context) { var baseList = typeDeclarationSyntax.BaseList; - if (baseList is object) + if (baseList is not null) { foreach (var type in baseList.Types.Select(baseType => baseType.Type)) { var typeSymbol = context.SemanticModel.GetTypeInfo(type).Type; - if (typeSymbol is object + if (typeSymbol is not null && (typeSymbol.OriginalDefinition.SpecialType == interfaceType || typeSymbol.ImplementsInterface(interfaceType, out _))) return true; @@ -49,12 +49,12 @@ public static bool ImplementsInterface(this TypeDeclarationSyntax typeDeclaratio public static bool ImplementsInterface(this TypeDeclarationSyntax typeDeclarationSyntax, INamedTypeSymbol interfaceType, SyntaxNodeAnalysisContext context) { var baseList = typeDeclarationSyntax.BaseList; - if (baseList is object) + if (baseList is not null) { foreach (var type in baseList.Types.Select(baseType => baseType.Type)) { var typeSymbol = context.SemanticModel.GetTypeInfo(type).Type; - if (typeSymbol is object + if (typeSymbol is not null && (SymbolEqualityComparer.Default.Equals(typeSymbol.OriginalDefinition, interfaceType) || typeSymbol.ImplementsInterface(interfaceType, out _))) return true; @@ -72,7 +72,7 @@ public static string GetMetadataName(this TypeDeclarationSyntax typeDeclarationS var namespaces = new LinkedList(); var types = new LinkedList(); - for (var parent = typeDeclarationSyntax.Parent; parent is object; parent = parent.Parent) + for (var parent = typeDeclarationSyntax.Parent; parent is not null; parent = parent.Parent) { if (parent is NamespaceDeclarationSyntax @namespace) { @@ -85,11 +85,11 @@ public static string GetMetadataName(this TypeDeclarationSyntax typeDeclarationS } var result = new StringBuilder(); - for (var item = namespaces.First; item is object; item = item.Next) + for (var item = namespaces.First; item is not null; item = item.Next) { _ = result.Append(item.Value.Name).Append(NAMESPACE_CLASS_DELIMITER); } - for (var item = types.First; item is object; item = item.Next) + for (var item = types.First; item is not null; item = item.Next) { var type = item.Value; AppendName(result, type); diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/TypeParameterConstraintSyntaxExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/TypeParameterConstraintSyntaxExtensions.cs index bdace7a..5b6abe0 100644 --- a/NetFabric.Hyperlinq.Analyzer/Utils/TypeParameterConstraintSyntaxExtensions.cs +++ b/NetFabric.Hyperlinq.Analyzer/Utils/TypeParameterConstraintSyntaxExtensions.cs @@ -13,9 +13,9 @@ public static bool IsEnumerator(this TypeParameterConstraintSyntax typeParameter if (typeParameterConstraintSyntax is TypeConstraintSyntax typeConstraintSyntax) { var typeSymbol = context.SemanticModel.GetTypeInfo(typeConstraintSyntax.Type).Type; - if (typeSymbol is object - && (typeSymbol.IsEnumerator(context.Compilation, out _) - || typeSymbol.IsAsyncEnumerator(context.Compilation, out _))) + if (typeSymbol is not null + && (typeSymbol.IsEnumerator() + || typeSymbol.IsAsyncEnumerator(context.Compilation))) return true; } return false; diff --git a/NetFabric.Hyperlinq.Analyzer/Utils/TypeSymbolExtensions.cs b/NetFabric.Hyperlinq.Analyzer/Utils/TypeSymbolExtensions.cs index 7670a78..5e53d91 100644 --- a/NetFabric.Hyperlinq.Analyzer/Utils/TypeSymbolExtensions.cs +++ b/NetFabric.Hyperlinq.Analyzer/Utils/TypeSymbolExtensions.cs @@ -1,12 +1,45 @@ using Microsoft.CodeAnalysis; +using NetFabric.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace NetFabric.Hyperlinq.Analyzer; static class TypeSymbolExtensions { - public static bool IsArrayType(this ITypeSymbol typeSymbol) - => typeSymbol is IArrayTypeSymbol; + public static bool IsForEachOptimized(this ITypeSymbol typeSymbol) + { + if (typeSymbol is IArrayTypeSymbol) + return true; - public static bool IsSpanType(this ITypeSymbol typeSymbol) - => typeSymbol.ToString().StartsWith("System.Span<") || typeSymbol.ToString().StartsWith("System.ReadOnlySpan<"); + var namedTypeSymbol = typeSymbol.OriginalDefinition.ToString(); + return namedTypeSymbol + is "System.Span" + or "System.ReadOnlySpan" + or "System.Collections.Immutable.ImmutableArray"; + } + + public static bool IsList(this ITypeSymbol typeSymbol, [NotNullWhen(true)] out ITypeSymbol? itemType) + { + if (typeSymbol is INamedTypeSymbol namedTypeSymbol && + namedTypeSymbol.IsGenericType && + namedTypeSymbol.OriginalDefinition.ToString() == "System.Collections.Generic.List") + { + itemType = namedTypeSymbol.TypeArguments[0]; + return true; + } + + itemType = default; + return false; + } + + public static bool IsEnumerator(this ITypeSymbol type) + => type.ImplementsInterface(SpecialType.System_Collections_IEnumerator, out _) || + (type.GetPublicReadProperty("Current") is not null && type.GetPublicMethod("MoveNext") is not null); + + public static bool IsAsyncEnumerator(this ITypeSymbol type, Compilation compilation) + { + var asyncEnumeratorType = compilation.GetTypeByMetadataName("System.Collections.Generic.IAsyncEnumerator`1"); + return (asyncEnumeratorType is not null && type.ImplementsInterface(asyncEnumeratorType, out _)) || + (type.GetPublicReadProperty("Current") is not null && type.GetPublicMethod("MoveNextAsync") is not null); + } }