Skip to content

Add dogfood project #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 7, 2024
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
8 changes: 7 additions & 1 deletion effectivecsharpanalyzers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EffectiveCSharp.Analyzers",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EffectiveCSharp.Analyzers.Tests", "tests\EffectiveCSharp.Analyzers.Tests\EffectiveCSharp.Analyzers.Tests.csproj", "{CAEEA9CD-EB19-4071-BD9C-03EF5DB84DF6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EffectiveCSharp.Analyzers.Benchmarks", "tests\EffectiveCSharp.Analyzers.Benchmarks\EffectiveCSharp.Analyzers.Benchmarks.csproj", "{FD778F3D-8187-452E-8680-A5A34B7F7335}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EffectiveCSharp.Analyzers.Benchmarks", "tests\EffectiveCSharp.Analyzers.Benchmarks\EffectiveCSharp.Analyzers.Benchmarks.csproj", "{FD778F3D-8187-452E-8680-A5A34B7F7335}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dogfood", "src\tools\Dogfood\Dogfood.csproj", "{693901D8-ED25-4063-AC66-319B778FE6FF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -27,6 +29,10 @@ Global
{FD778F3D-8187-452E-8680-A5A34B7F7335}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD778F3D-8187-452E-8680-A5A34B7F7335}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD778F3D-8187-452E-8680-A5A34B7F7335}.Release|Any CPU.Build.0 = Release|Any CPU
{693901D8-ED25-4063-AC66-319B778FE6FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{693901D8-ED25-4063-AC66-319B778FE6FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{693901D8-ED25-4063-AC66-319B778FE6FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{693901D8-ED25-4063-AC66-319B778FE6FF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
17 changes: 6 additions & 11 deletions src/EffectiveCSharp.Analyzers/AvoidStringlyTypedApisAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,19 @@
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AvoidStringlyTypedApisAnalyzer : DiagnosticAnalyzer
{
private const string DiagnosticId = DiagnosticIds.AvoidStringlyTypedApis;
private const string Title = "Avoid stringly-typed APIs";
private const string MessageFormat = "Use 'nameof({0})' instead of the string literal \"{0}\"";

private const string Description =
"Replace string literals representing member names with the nameof operator to ensure type safety.";

private const string Category = "Refactoring";
private static readonly string Title = "Avoid stringly-typed APIs";
private static readonly string MessageFormat = "Use 'nameof({0})' instead of the string literal \"{0}\"";
private static readonly string Description = "Replace string literals representing member names with the nameof operator to ensure type safety.";

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId,
DiagnosticIds.AvoidStringlyTypedApis,
Title,
MessageFormat,
Category,
Categories.Refactoring,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: Description,
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers{ThisAssembly.GitCommitId}/docs/{DiagnosticId}.md");
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/rules/{DiagnosticIds.AvoidStringlyTypedApis}.md");

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[Shared]
public class AvoidStringlyTypedApisCodeFixProvider : CodeFixProvider
{
private const string Title = "Use nameof operator";
private static readonly string Title = "Use nameof operator";

/// <inheritdoc/>
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIds.AvoidStringlyTypedApis);
Expand Down
8 changes: 8 additions & 0 deletions src/EffectiveCSharp.Analyzers/Common/Categories.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace EffectiveCSharp.Analyzers.Common;

internal static class Categories
{
internal static readonly string Refactoring = nameof(Refactoring);
internal static readonly string Performance = nameof(Performance);
internal static readonly string Style = nameof(Style);
}
18 changes: 13 additions & 5 deletions src/EffectiveCSharp.Analyzers/Common/DiagnosticExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ internal static Diagnostic CreateDiagnostic(
DiagnosticDescriptor rule,
ImmutableDictionary<string, string?>? properties,
params object?[]? messageArgs)
=> node.CreateDiagnostic(rule, additionalLocations: ImmutableArray<Location>.Empty, properties, messageArgs);
=> node.CreateDiagnostic(
rule,
additionalLocations: null,
properties,
messageArgs);

[DebuggerStepThrough]
internal static Diagnostic CreateDiagnostic(
this SyntaxNode node,
DiagnosticDescriptor rule,
ImmutableArray<Location> additionalLocations,
IEnumerable<Location>? additionalLocations,
ImmutableDictionary<string, string?>? properties,
params object?[]? messageArgs)
=> node
Expand All @@ -40,21 +44,25 @@ internal static Diagnostic CreateDiagnostic(
=> location
.CreateDiagnostic(
rule: rule,
properties: ImmutableDictionary<string, string?>.Empty,
properties: null,
messageArgs: messageArgs);

internal static Diagnostic CreateDiagnostic(
this Location location,
DiagnosticDescriptor rule,
ImmutableDictionary<string, string?>? properties,
params object?[]? messageArgs)
=> location.CreateDiagnostic(rule, ImmutableArray<Location>.Empty, properties, messageArgs);
=> location.CreateDiagnostic(
rule: rule,
additionalLocations: null,
properties: properties,
messageArgs: messageArgs);

[DebuggerStepThrough]
internal static Diagnostic CreateDiagnostic(
this Location location,
DiagnosticDescriptor rule,
ImmutableArray<Location> additionalLocations,
IEnumerable<Location>? additionalLocations,
ImmutableDictionary<string, string?>? properties,
params object?[]? messageArgs)
{
Expand Down
2 changes: 2 additions & 0 deletions src/EffectiveCSharp.Analyzers/Common/WellKnownTypes.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
namespace EffectiveCSharp.Analyzers.Common;

#pragma warning disable ECS0002 // Consider using static readonly instead of const

internal static class WellKnownTypes
{
internal const string Span = nameof(Span);
Expand Down
3 changes: 3 additions & 0 deletions src/EffectiveCSharp.Analyzers/DiagnosticIds.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace EffectiveCSharp.Analyzers;

// Diagnostic IDs must be a non-null constant
#pragma warning disable ECS0002 // Consider using readonly instead of const for better flexibility

internal static class DiagnosticIds
{
internal const string PreferImplicitlyTypedLocalVariables = "ECS0001";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace EffectiveCSharp.Analyzers;
namespace EffectiveCSharp.Analyzers;

/// <summary>
/// A <see cref="DiagnosticAnalyzer"/> for Effective C# Item #7 - Express callbacks with delegates.
Expand All @@ -11,17 +7,15 @@ namespace EffectiveCSharp.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ExpressCallbacksWithDelegatesAnalyzer : DiagnosticAnalyzer
{
private const string Id = DiagnosticIds.ExpressCallbacksWithDelegates;

private static readonly DiagnosticDescriptor Rule = new(
id: Id,
id: DiagnosticIds.ExpressCallbacksWithDelegates,
title: "Express callbacks with delegates",
messageFormat: "Method '{0}' should use a delegate for the callback",
category: "Design",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: "Ensure that callbacks are implemented using delegates.",
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/{Id}.md");
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/{DiagnosticIds.ExpressCallbacksWithDelegates}.md");

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
Expand Down Expand Up @@ -107,7 +101,7 @@ private static bool IsDelegateType(ITypeSymbol? typeSymbol)
return false;
}

// It's true that this CAN be simplified, but the readability is better this way
// It's true that this CAN be simplified, but the readability is better this way
#pragma warning disable IDE0046 // 'if' statement can be simplified
// Handle Func<T>, Action<T>, Predicate<T>
if (namedTypeSymbol.ConstructedFrom.Name.StartsWith("Func", StringComparison.Ordinal) ||
Expand Down
12 changes: 3 additions & 9 deletions src/EffectiveCSharp.Analyzers/MinimizeBoxingUnboxingAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MinimizeBoxingUnboxingAnalyzer : DiagnosticAnalyzer
{
private const string Id = DiagnosticIds.MinimizeBoxingUnboxing;

private static readonly DiagnosticDescriptor Rule = new(
id: Id,
id: DiagnosticIds.MinimizeBoxingUnboxing,
title: "Minimize boxing and unboxing",
messageFormat: "Consider using an alternative implementation to avoid boxing and unboxing",
category: "Performance",
category: Categories.Performance,
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/{Id}.md");
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/rules/{DiagnosticIds.MinimizeBoxingUnboxing}.md");

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
Expand Down Expand Up @@ -76,10 +74,6 @@ private static void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSym
return;
}
}
else
{
Debug.Fail($"Unrecognized constructed from named type '{baseType}'.");
}

return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[Shared]
public class PreferExplicitTypesForNumbersCodeFixProvider : CodeFixProvider
{
private const string Title = "Use explicit type";
private static readonly string Title = "Use explicit type";

/// <inheritdoc />
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIds.PreferImplicitlyTypedLocalVariables);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class PreferExplicitTypesOnNumbersAnalyzer : DiagnosticAnalyzer
{
private const string Id = DiagnosticIds.PreferImplicitlyTypedLocalVariables;

private static readonly DiagnosticDescriptor Rule = new(
id: Id,
id: DiagnosticIds.PreferImplicitlyTypedLocalVariables,
title: "Prefer implicitly typed local variables",
messageFormat: "Use explicit type instead of 'var' for numeric variables",
category: "Style",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: "Use var to declare local variables for better readability and efficiency, except for built-in numeric types where explicit typing prevents potential conversion issues.",
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/{Id}.md");
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/rules/{DiagnosticIds.PreferImplicitlyTypedLocalVariables}.md");

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class PreferReadonlyOverConstAnalyzer : DiagnosticAnalyzer
{
private const string Id = DiagnosticIds.PreferReadonlyOverConst;

private static readonly DiagnosticDescriptor Rule = new(
id: Id,
id: DiagnosticIds.PreferReadonlyOverConst,
title: "Prefer readonly over const",
messageFormat: "Consider using readonly instead of const for better flexibility",
category: "Maintainability",
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/{Id}.md");
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/rules/{DiagnosticIds.PreferReadonlyOverConst}.md");

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[Shared]
public class PreferReadonlyOverConstCodeFixProvider : CodeFixProvider
{
private const string Title = "Use readonly instead of const";
private static readonly string Title = "Use readonly instead of const";

/// <inheritdoc />
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIds.PreferReadonlyOverConst);
Expand Down Expand Up @@ -40,7 +40,16 @@ private static async Task<Solution> ReplaceConstWithReadonlyAsync(Document docum
SyntaxTokenList modifiers = constDeclaration.Modifiers;

// Find the const token within the modifiers
SyntaxToken constToken = modifiers.FirstOrDefault(m => m.IsKind(SyntaxKind.ConstKeyword));
SyntaxToken constToken = default;
for (int i = 0; i < modifiers.Count; i++)
{
SyntaxToken modifier = modifiers[i];
if (modifier.IsKind(SyntaxKind.ConstKeyword))
{
constToken = modifier;
break;
}
}

if (constToken == default)
{
Expand Down
14 changes: 6 additions & 8 deletions src/EffectiveCSharp.Analyzers/ReplaceStringFormatAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ namespace EffectiveCSharp.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ReplaceStringFormatAnalyzer : DiagnosticAnalyzer
{
private const string Category = "Style";
private const string Description = "Replace string.Format with interpolated string.";
private const string DiagnosticId = DiagnosticIds.ReplaceStringFormatWithInterpolatedString;
private const string MessageFormat = "Replace '{0}' with interpolated string";
private const string Title = "Replace string.Format with interpolated string";
private static readonly string Description = "Replace string.Format with interpolated string.";
private static readonly string MessageFormat = "Replace '{0}' with interpolated string";
private static readonly string Title = "Replace string.Format with interpolated string";

// We can't use source generators
private static readonly Regex PlaceholderRegex = new(
Expand All @@ -22,14 +20,14 @@ public class ReplaceStringFormatAnalyzer : DiagnosticAnalyzer
TimeSpan.FromSeconds(1));

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId,
DiagnosticIds.ReplaceStringFormatWithInterpolatedString,
Title,
MessageFormat,
Category,
Categories.Style,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: Description,
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers{ThisAssembly.GitCommitId}/docs/{DiagnosticId}.md");
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/rules/{DiagnosticIds.ReplaceStringFormatWithInterpolatedString}.md");

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace EffectiveCSharp.Analyzers;
[Shared]
public class ReplaceStringFormatCodeFixProvider : CodeFixProvider
{
private const string Title = "Replace with interpolated string";
private static readonly string Title = "Replace with interpolated string";

/// <inheritdoc />
public override sealed ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIds.ReplaceStringFormatWithInterpolatedString);
Expand Down Expand Up @@ -76,7 +76,14 @@ private static async Task<Solution> ReplaceWithInterpolatedStringAsync(Document
return document.Project.Solution;
}

ArgumentSyntax[] arguments = invocationExpr.ArgumentList.Arguments.Skip(1).ToArray();
SeparatedSyntaxList<ArgumentSyntax> allArguments = invocationExpr.ArgumentList.Arguments;
int count = allArguments.Count;
ArgumentSyntax[] arguments = new ArgumentSyntax[count - 1];

for (int i = 1; i < count; i++)
{
arguments[i - 1] = allArguments[i];
}

// Replace format placeholders with corresponding arguments in an interpolated string format
string interpolatedString = CreateInterpolatedString(formatString!, arguments);
Expand Down
7 changes: 3 additions & 4 deletions src/EffectiveCSharp.Analyzers/SpanAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SpanAnalyzer : DiagnosticAnalyzer
{
private const string Id = DiagnosticIds.UseSpanInstead;
private static readonly DiagnosticDescriptor Rule = new(
id: Id,
id: DiagnosticIds.UseSpanInstead,
title: "Use Span<T> for performance",
messageFormat: "Consider using Span<T> instead of array for better performance",
category: "Performance",
category: Categories.Performance,
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/{Id}.md");
helpLinkUri: $"https://github.com/rjmurillo/EffectiveCSharp.Analyzers/blob/{ThisAssembly.GitCommitId}/docs/rules/{DiagnosticIds.UseSpanInstead}.md");

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
Expand Down
2 changes: 1 addition & 1 deletion src/EffectiveCSharp.Analyzers/SpanCodeFixProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[Shared]
public class SpanCodeFixProvider : CodeFixProvider
{
private const string Title = "Use Span<T>";
private static readonly string Title = "Use Span<T>";

/// <inheritdoc />
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(DiagnosticIds.UseSpanInstead);
Expand Down
Loading
Loading