Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: Publish to NuGet
name: Build, Tests and Publish

on:
push:
branches: [master]
pull_request:

jobs:
build-and-publish:
Expand Down Expand Up @@ -32,5 +33,12 @@ jobs:
- name: Pack
run: dotnet pack Collections.Analyzer/Collections.Analyzer.csproj --configuration Release --no-build --output nupkgs

- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
user: Backs

- name: Push to NuGet
run: dotnet nuget push nupkgs/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
run: dotnet nuget push nupkgs/*.nupkg --api-key ${{steps.login.outputs.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json --skip-duplicate
1 change: 1 addition & 0 deletions AI_CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ This file is intended to help AI agents quickly understand the project structure
### 3. Writing Tests
- Tests should be located in the `Tests` project; the folder should match the diagnostic name.
- Place source code for tests in `Tests/Resources/<DiagnosticName>/`.
- **Important**: When adding test data to the `Tests/Resources` folder, do NOT add them to `Tests.csproj` because the entire folder is already included via a wildcard.
- Use `ResourceReader.ReadFromFile()` to load the code.
- Use `{|CIxxxx:code|}` syntax in resource files to mark expected diagnostics.
- Verify both the presence of the diagnostic and the result of applying the fix (if applicable).
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.3.0
- Added [CI0011](https://github.com/Backs/Collections.Analyzer/blob/master/Documentation/CI0011.md): Diagnostic to detect potential N+1 Query Problems in loops and LINQ expressions.
- Added support for `.editorconfig` configuration for CI0011 (data access type substrings, method prefixes, bulk method substrings, and test method analysis).
- Improved analyzer performance by implementing configuration caching.
- Excluded static methods and bulk/batch methods from CI0011 analysis to reduce false positives.
- General documentation updates and refactorings.

## 0.2.15
Added [CI0010](https://github.com/Backs/Collections.Analyzer/blob/master/Documentation/CI0010.md): Diagnostic to suggest using `Dictionary` for lookups in collections inside loops or LINQ chains. This optimizes performance from O(N*M) to O(N+M).

Expand Down
8 changes: 8 additions & 0 deletions Collections.Analyzer/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

## Release 0.3.0

### New Rules

Rule ID | Category | Severity | Notes
---------|-------------|----------|---------------------------------------
CI0011 | Performance | Warning | NPlusOneQueryAnalyzer

## Release 0.2.15

### New Rules
Expand Down
2 changes: 1 addition & 1 deletion Collections.Analyzer/Collections.Analyzer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<DevelopmentDependency>true</DevelopmentDependency>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>0.2.15</PackageVersion>
<PackageVersion>0.3.0</PackageVersion>
<Title>Collections.Analyzer</Title>
<Authors>Rogatnev Sergey</Authors>
<Description>Collections.Analyzer is a set of roslyn-based diagnostics for C#-projects that detect potential problems with operating different collections.</Description>
Expand Down
36 changes: 36 additions & 0 deletions Collections.Analyzer/Diagnostics/AnalyzerConfigHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Collections.Analyzer.Diagnostics;

internal static class AnalyzerConfigHelper
{
private static readonly ConditionalWeakTable<SyntaxTree, object> ConfigCache = new();
private static readonly char[] Separators = { ',' };

public static T GetConfig<T>(AnalyzerOptions options, SyntaxTree syntaxTree, Func<AnalyzerConfigOptions, T> factory)
{
if (ConfigCache.TryGetValue(syntaxTree, out var cached) && cached is T typedConfig)
{
return typedConfig;
}

var analyzerOptions = options.AnalyzerConfigOptionsProvider.GetOptions(syntaxTree);
var config = factory(analyzerOptions);

ConfigCache.Remove(syntaxTree);
ConfigCache.Add(syntaxTree, config!);

return config;
}

public static string[] GetList(AnalyzerConfigOptions options, string optionName, string[] defaultValues)
{
return options.TryGetValue(optionName, out var value) && !string.IsNullOrWhiteSpace(value)
? value.Split(Separators, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray()
: defaultValues;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ public override void Initialize(AnalysisContext context)

private static void AnalyzeLocalDeclaration(SyntaxNodeAnalysisContext context)
{
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

var minLength = GetMinArrayLength(context);
var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

foreach (var variable in localDeclaration.Declaration.Variables)
{
Expand All @@ -50,9 +49,8 @@ private static void AnalyzeLocalDeclaration(SyntaxNodeAnalysisContext context)

private static void AnalyzeFieldDeclaration(SyntaxNodeAnalysisContext context)
{
var fieldDeclaration = (FieldDeclarationSyntax)context.Node;

var minLength = GetMinArrayLength(context);
var fieldDeclaration = (FieldDeclarationSyntax)context.Node;

foreach (var variable in fieldDeclaration.Declaration.Variables)
{
Expand All @@ -62,6 +60,7 @@ private static void AnalyzeFieldDeclaration(SyntaxNodeAnalysisContext context)

private static void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context)
{
var minLength = GetMinArrayLength(context);
var propertyDeclaration = (PropertyDeclarationSyntax)context.Node;

var typeInfo = context.SemanticModel.GetTypeInfo(propertyDeclaration.Type);
Expand All @@ -77,7 +76,6 @@ private static void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context
return;

var arraySize = CountArrayElements(arrayInitializer);
var minLength = GetMinArrayLength(context);

if (arraySize < minLength)
return;
Expand Down Expand Up @@ -245,19 +243,6 @@ argument.Parent is ArgumentListSyntax argumentList &&
return false;
}

private static int GetMinArrayLength(SyntaxNodeAnalysisContext context)
{
var options = context.Options.AnalyzerConfigOptionsProvider.GetOptions(context.Node.SyntaxTree);

if (options.TryGetValue(MinArrayLengthOption, out var valueString) &&
int.TryParse(valueString, out var value) && value >= 0)
{
return value;
}

return DefaultMinArrayLength;
}

private static int CountArrayElements(InitializerExpressionSyntax initializer)
{
return initializer.Expressions.Count;
Expand All @@ -269,4 +254,17 @@ private class UsageAnalysis
public bool HasUnsupportedUsage { get; set; }
public bool ShouldWarn => HasContainsCall && !HasUnsupportedUsage;
}
private static int GetMinArrayLength(SyntaxNodeAnalysisContext context)
{
return AnalyzerConfigHelper.GetConfig(context.Options, context.Node.SyntaxTree, options =>
{
if (options.TryGetValue(MinArrayLengthOption, out var valueString) &&
int.TryParse(valueString, out var value) && value >= 0)
{
return value;
}

return DefaultMinArrayLength;
});
}
}
Loading
Loading