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
14 changes: 14 additions & 0 deletions SourceKit.sln
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceKit.Analyzers.Propert
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceKit.Reflect.Samples", "samples\SourceKit.Reflect.Samples\SourceKit.Reflect.Samples.csproj", "{B3EEEF25-A90E-404F-9874-93A2E6990468}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceKit.Analyzers.MemberAccessibility.Samples", "samples\analyzers\SourceKit.Analyzers.MemberAccessibility.Samples\SourceKit.Analyzers.MemberAccessibility.Samples.csproj", "{6D49F53F-4765-4108-9AA7-1470D5FB8FD7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceKit.Analyzers.MemberAccessibility.Tests", "tests\SourceKit.Analyzers.MemberAccessibility.Tests\SourceKit.Analyzers.MemberAccessibility.Tests.csproj", "{EACEDBCD-081E-4F95-A81E-B62CD18D3028}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -131,6 +135,8 @@ Global
{35F679FA-4F45-4870-B7C6-7B31A6CD014F} = {365210F4-5F33-49FD-9E14-552154E26285}
{4B54BDA0-D16F-428F-97D2-03654311CE22} = {CB9AFB88-6DC1-436D-8F6F-398E065A07DE}
{B3EEEF25-A90E-404F-9874-93A2E6990468} = {68973D47-37E1-492E-8F62-E94B002349BB}
{6D49F53F-4765-4108-9AA7-1470D5FB8FD7} = {365210F4-5F33-49FD-9E14-552154E26285}
{EACEDBCD-081E-4F95-A81E-B62CD18D3028} = {CB9AFB88-6DC1-436D-8F6F-398E065A07DE}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{637C01C1-3A3C-4FC6-9874-6CFBA4319A79}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -249,5 +255,13 @@ Global
{B3EEEF25-A90E-404F-9874-93A2E6990468}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3EEEF25-A90E-404F-9874-93A2E6990468}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3EEEF25-A90E-404F-9874-93A2E6990468}.Release|Any CPU.Build.0 = Release|Any CPU
{6D49F53F-4765-4108-9AA7-1470D5FB8FD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D49F53F-4765-4108-9AA7-1470D5FB8FD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D49F53F-4765-4108-9AA7-1470D5FB8FD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D49F53F-4765-4108-9AA7-1470D5FB8FD7}.Release|Any CPU.Build.0 = Release|Any CPU
{EACEDBCD-081E-4F95-A81E-B62CD18D3028}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EACEDBCD-081E-4F95-A81E-B62CD18D3028}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EACEDBCD-081E-4F95-A81E-B62CD18D3028}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EACEDBCD-081E-4F95-A81E-B62CD18D3028}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[*.cs]

dotnet_diagnostic.SK1100.severity = warning
dotnet_diagnostic.SK1101.severity = warning
dotnet_diagnostic.SK1102.severity = warning
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace SourceKit.Analyzers.MemberAccessibility.Samples;

public class MultipleFieldsCaseFixed
{
private object _first;
private object _second;
private object _third;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SourceKit.Analyzers.MemberAccessibility.Samples;

public class MultipleFieldsCase
{
private object _first, _second, _third;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SourceKit.Analyzers.MemberAccessibility.Samples;

public class PrivatePropertyCaseFixed
{
public object PrivateProperty { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SourceKit.Analyzers.MemberAccessibility.Samples;

public class PrivatePropertyCase
{
private object PrivateProperty { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SourceKit.Analyzers.MemberAccessibility.Samples;

public class PublicFieldTestCaseFixed
{
private object _publicField;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace SourceKit.Analyzers.MemberAccessibility.Samples;

public class PublicFieldTestCase
{
public object _publicField;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<ImplicitUsings>disable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SourceKit.Analyzers.MemberAccessibility"/>
</ItemGroup>

<ItemGroup>
<Compile Update="MultipleFieldsCase.Fixed.cs">
<DependentUpon>MultipleFieldsCase.cs</DependentUpon>
</Compile>
<Compile Update="PrivatePropertyCase.Fixed.cs">
<DependentUpon>PrivatePropertyCase.cs</DependentUpon>
</Compile>
<Compile Update="PublicFieldTestCase.Fixed.cs">
<DependentUpon>PublicFieldTestCase.cs</DependentUpon>
</Compile>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private void AnalyzeGeneric(SyntaxNodeAnalysisContext context)
{
var node = (GenericNameSyntax)context.Node;

if (context.SemanticModel.GetDeclaredSymbol(node) is not INamedTypeSymbol symbol)
if (context.SemanticModel.GetSymbolInfo(node).Symbol is not INamedTypeSymbol symbol)
return;

if (TryGetDictionaryKeySymbol(symbol, typeof(Dictionary<,>), context, out INamedTypeSymbol? keySymbol) is false
Expand All @@ -54,17 +54,14 @@ private void AnalyzeGeneric(SyntaxNodeAnalysisContext context)

INamedTypeSymbol equatableSymbol = context.Compilation.GetTypeSymbol(typeof(IEquatable<>));

INamedTypeSymbol madeEquatableSymbol = equatableSymbol
.Construct(keySymbol.WithNullableAnnotation(NullableAnnotation.None));

IEnumerable<INamedTypeSymbol> foundEquatableSymbols = keySymbol
.FindAssignableTypesConstructedFrom(equatableSymbol);

bool hasCorrectEquatableImplementation = foundEquatableSymbols
.Select(x => x.TypeArguments.First())
.Any(x => madeEquatableSymbol.Equals(x, SymbolEqualityComparer.Default));
.Any(x => keySymbol.Equals(x, SymbolEqualityComparer.Default) || keySymbol.IsAssignableTo(x));

if (hasCorrectEquatableImplementation is false)
if (hasCorrectEquatableImplementation)
return;

var diag = Diagnostic.Create(Descriptor, node.GetLocation());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)

context.RegisterCodeFix(
CodeAction.Create(
equivalenceKey: ListForEachNotAllowedAnalyzer.DiagnosticId,
title: Title,
priority: CodeActionPriority.High,
equivalenceKey: ListForEachNotAllowedAnalyzer.DiagnosticId,
createChangedDocument: cancellationToken =>
ConvertListForEachIntoLoop(context.Document, invocationExpression, cancellationToken)),
diagnostic);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)

var codeFixAction = CodeAction.Create(
title: title,
priority: CodeActionPriority.High,
createChangedSolution: c => RemoveToList(context.Document, node, c),
equivalenceKey: title);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
SK1100 | Design | Error | Do not use private properties
SK1101 | Design | Error | Do not use public fields
SK1101 | Design | Error | Do not use public fields
SK1102 | Design | Error | Do not use multiple fields
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace SourceKit.Analyzers.MemberAccessibility.Analyzers;
Expand All @@ -8,9 +10,9 @@ namespace SourceKit.Analyzers.MemberAccessibility.Analyzers;
public class FieldCannotBePublicAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "SK1101";
public const string Title = nameof(PropertyCannotBePrivateAnalyzer);
public const string Title = nameof(FieldCannotBePublicAnalyzer);

public const string Format = """Field {0} {1} cannot be public""";
public const string Format = """Field '{0} {1}' cannot be public""";

public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
DiagnosticId,
Expand All @@ -27,5 +29,24 @@ public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSyntaxNodeAction(AnalyzeField, SyntaxKind.FieldDeclaration);
}
}

private void AnalyzeField(SyntaxNodeAnalysisContext context)
{
var fieldSyntax = (FieldDeclarationSyntax)context.Node;

if (fieldSyntax.Modifiers.All(x => x.IsKind(SyntaxKind.PublicKeyword) is false))
{
return;
}

foreach (VariableDeclaratorSyntax variable in fieldSyntax.Declaration.Variables)
{
Location location = variable.GetLocation();
var diagnostic = Diagnostic.Create(Descriptor, location, fieldSyntax.Declaration.Type, variable.Identifier.Text);

context.ReportDiagnostic(diagnostic);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace SourceKit.Analyzers.MemberAccessibility.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public class FieldCannotHaveMultipleVariablesAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "SK1102";
public const string Title = nameof(FieldCannotHaveMultipleVariablesAnalyzer);

public const string Format = """Each field must have separate declaration""";

public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(DiagnosticId,
Title,
Format,
"Design",
DiagnosticSeverity.Error,
true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
ImmutableArray.Create(Descriptor);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSyntaxNodeAction(AnalyzeField, SyntaxKind.FieldDeclaration);
}

private void AnalyzeField(SyntaxNodeAnalysisContext context)
{
var fieldSyntax = (FieldDeclarationSyntax)context.Node;

if (fieldSyntax.Declaration.Variables.Count <= 1)
{
return;
}

Location location = fieldSyntax.GetLocation();
var diagnostic = Diagnostic.Create(Descriptor, location);

context.ReportDiagnostic(diagnostic);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace SourceKit.Analyzers.MemberAccessibility.Analyzers;
Expand All @@ -10,10 +12,9 @@ public class PropertyCannotBePrivateAnalyzer : DiagnosticAnalyzer
public const string DiagnosticId = "SK1100";
public const string Title = nameof(PropertyCannotBePrivateAnalyzer);

public const string Format = """Property {0} {1} cannot be private""";
public const string Format = """Property '{0} {1}' cannot be private""";

public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(
DiagnosticId,
public static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor(DiagnosticId,
Title,
Format,
"Design",
Expand All @@ -27,5 +28,21 @@ public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterSyntaxNodeAction(AnalyzeProperty, SyntaxKind.PropertyDeclaration);
}
}

private void AnalyzeProperty(SyntaxNodeAnalysisContext context)
{
var propertySyntax = (PropertyDeclarationSyntax)context.Node;

if (propertySyntax.Modifiers.All(x => x.IsKind(SyntaxKind.PrivateKeyword) is false))
{
return;
}

Location location = propertySyntax.GetLocation();
var diagnostic = Diagnostic.Create(Descriptor, location, propertySyntax.Type, propertySyntax.Identifier.Text);

context.ReportDiagnostic(diagnostic);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SourceKit.Analyzers.MemberAccessibility.Analyzers;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace SourceKit.Analyzers.MemberAccessibility.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(FieldCannotBePublicCodeFixProvider))]
public class FieldCannotBePublicCodeFixProvider : CodeFixProvider
{
public const string Title = "Make field private";

public override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(FieldCannotBePublicAnalyzer.DiagnosticId);

public override FixAllProvider GetFixAllProvider()
=> WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.CancellationToken.ThrowIfCancellationRequested();

IEnumerable<Task> derivativesMustBePartialDiagnostics = context.Diagnostics
.Select(x => ProvideDerivativesMustBePartial(context, x));

await Task.WhenAll(derivativesMustBePartialDiagnostics);
}

private static async Task ProvideDerivativesMustBePartial(CodeFixContext context, Diagnostic diagnostic)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);

if (root?.FindNode(diagnostic.Location.SourceSpan) is not { Parent.Parent: FieldDeclarationSyntax fieldSyntax })
return;

var action = CodeAction.Create(
title: Title,
priority: CodeActionPriority.High,
equivalenceKey: nameof(FieldCannotBePublicCodeFixProvider),
createChangedDocument: _ =>
{
SyntaxToken privateModifier = fieldSyntax.Modifiers.First(x => x.IsKind(SyntaxKind.PublicKeyword));

SyntaxTokenList fixedModifiers = fieldSyntax.Modifiers
.Replace(privateModifier, Token(SyntaxKind.PrivateKeyword));

FieldDeclarationSyntax fixedSyntax = fieldSyntax.WithModifiers(fixedModifiers);

SyntaxNode newRoot = root.ReplaceNode(fieldSyntax, fixedSyntax);

Document document = context.Document.WithSyntaxRoot(newRoot);

return Task.FromResult(document);
});

context.RegisterCodeFix(action, diagnostic);
}
}
Loading
Loading