diff --git a/.editorconfig b/.editorconfig
index a53d2bc6..8ea80f5d 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,6 +1,9 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
+[*]
+insert_final_newline = true
+
# C# files
[*.cs]
@@ -13,7 +16,6 @@ tab_width = 4
# New line preferences
end_of_line = crlf
-insert_final_newline = true
#### .NET Coding Conventions ####
@@ -221,3 +223,8 @@ dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
+csharp_style_prefer_top_level_statements = true
+csharp_style_prefer_primary_constructors = true
+
+# RCS1123: Add parentheses when necessary
+dotnet_diagnostic.RCS1123.severity = none
diff --git a/Directory.Packages.props b/Directory.Packages.props
index e0e978ce..93c56b3c 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,22 +1,22 @@
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EcoCode.sln b/EcoCode.sln
index 3f343efa..a268a585 100644
--- a/EcoCode.sln
+++ b/EcoCode.sln
@@ -20,13 +20,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EcoCode.Vsix", "src\EcoCode
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{810939C8-2A48-4F56-84C3-E433CDB923EE}"
ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
.github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml
.github\workflows\create-tag-release.yml = .github\workflows\create-tag-release.yml
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
global.json = global.json
icon.jpeg = icon.jpeg
- ProductionCode.ruleset = ProductionCode.ruleset
README.md = README.md
SharedAssemblyInfo.cs = SharedAssemblyInfo.cs
EndProjectSection
diff --git a/README.md b/README.md
index ccdb06b8..60faf01f 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ Both the EcoCode NuGet package and Visual Studio extension target .Net Standard
|[EC69](https://github.com/green-code-initiative/ecoCode/blob/main/ecocode-rules-specifications/src/main/rules/EC69/csharp/EC69.asciidoc)|Don’t call loop invariant functions in loop conditions|⚠️|✔️|❌|
|[EC72](https://github.com/green-code-initiative/ecoCode/blob/main/ecocode-rules-specifications/src/main/rules/EC72/csharp/EC72.asciidoc)|Don’t execute SQL queries in loops|⚠️|✔️|❌|
|[EC75](https://github.com/green-code-initiative/ecoCode/blob/main/ecocode-rules-specifications/src/main/rules/EC75/csharp/EC75.asciidoc)|Don’t concatenate `strings` in loops|⚠️|✔️|❌|
+|[EC81](https://github.com/green-code-initiative/ecoCode/blob/main/ecocode-rules-specifications/src/main/rules/EC81/csharp/EC81.asciidoc)|Specify struct layouts|⚠️|✔️|✔️|
🤝 Contribution
---------------
diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs
index c5597e45..aa6eae15 100644
--- a/SharedAssemblyInfo.cs
+++ b/SharedAssemblyInfo.cs
@@ -2,4 +2,4 @@
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
-[assembly: CLSCompliant(false)]
\ No newline at end of file
+[assembly: CLSCompliant(false)]
diff --git a/src/EcoCode.Analyzers/Analyzers/DontCallFunctionsInLoopConditions.cs b/src/EcoCode.Analyzers/Analyzers/EC69_DontCallFunctionsInLoopConditionsAnalyzer.cs
similarity index 94%
rename from src/EcoCode.Analyzers/Analyzers/DontCallFunctionsInLoopConditions.cs
rename to src/EcoCode.Analyzers/Analyzers/EC69_DontCallFunctionsInLoopConditionsAnalyzer.cs
index f039b154..5beff39c 100644
--- a/src/EcoCode.Analyzers/Analyzers/DontCallFunctionsInLoopConditions.cs
+++ b/src/EcoCode.Analyzers/Analyzers/EC69_DontCallFunctionsInLoopConditionsAnalyzer.cs
@@ -2,7 +2,7 @@
/// Analyzer for don't call loop invariant functions in loop conditions.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class DontCallFunctionsInLoopConditions : DiagnosticAnalyzer
+public sealed class DontCallFunctionsInLoopConditionsAnalyzer : DiagnosticAnalyzer
{
private static readonly ImmutableArray SyntaxKinds = [
SyntaxKind.ForStatement,
@@ -54,11 +54,15 @@ private static void AnalyzeLoopNode(SyntaxNodeAnalysisContext context)
_ = loopInvariantSymbols.Add(symbol);
}
}
+ if (loopInvariantSymbols.Count == 0) return;
// Step 2: Remove the variables that are mutated in the loop body or the for loop incrementors
RemoveMutatedSymbols(expression.DescendantNodes(), loopInvariantSymbols, context.SemanticModel);
+ if (loopInvariantSymbols.Count == 0) return;
+
foreach (var inc in incrementors)
RemoveMutatedSymbols(inc.DescendantNodesAndSelf(), loopInvariantSymbols, context.SemanticModel);
+ if (loopInvariantSymbols.Count == 0) return;
// Step 3: Identify conditions that are loop invariant
foreach (var node in condition.DescendantNodes())
@@ -98,4 +102,4 @@ static void RemoveMutatedSymbols(IEnumerable nodes, HashSet
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/EcoCode.Analyzers/Analyzers/DontExecuteSqlCommandsInLoops.cs b/src/EcoCode.Analyzers/Analyzers/EC72_DontExecuteSqlCommandsInLoopsAnalyzer.cs
similarity index 95%
rename from src/EcoCode.Analyzers/Analyzers/DontExecuteSqlCommandsInLoops.cs
rename to src/EcoCode.Analyzers/Analyzers/EC72_DontExecuteSqlCommandsInLoopsAnalyzer.cs
index ff7f2b6d..eba8f53b 100644
--- a/src/EcoCode.Analyzers/Analyzers/DontExecuteSqlCommandsInLoops.cs
+++ b/src/EcoCode.Analyzers/Analyzers/EC72_DontExecuteSqlCommandsInLoopsAnalyzer.cs
@@ -2,7 +2,7 @@
/// Analyzer for don't execute SQL commands in loops.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class DontExecuteSqlCommandsInLoops : DiagnosticAnalyzer
+public sealed class DontExecuteSqlCommandsInLoopsAnalyzer : DiagnosticAnalyzer
{
/// The diagnostic descriptor.
public static DiagnosticDescriptor Descriptor { get; } = new(
@@ -40,4 +40,4 @@ symbol.Name is "ExecuteNonQuery" or "ExecuteScalar" or "ExecuteReader" &&
}, SyntaxKind.InvocationExpression);
});
}
-}
\ No newline at end of file
+}
diff --git a/src/EcoCode.Analyzers/Analyzers/DontConcatenateStringsInLoops.cs b/src/EcoCode.Analyzers/Analyzers/EC75_DontConcatenateStringsInLoopsAnalyzer.cs
similarity index 96%
rename from src/EcoCode.Analyzers/Analyzers/DontConcatenateStringsInLoops.cs
rename to src/EcoCode.Analyzers/Analyzers/EC75_DontConcatenateStringsInLoopsAnalyzer.cs
index 8fe69675..ada5fce0 100644
--- a/src/EcoCode.Analyzers/Analyzers/DontConcatenateStringsInLoops.cs
+++ b/src/EcoCode.Analyzers/Analyzers/EC75_DontConcatenateStringsInLoopsAnalyzer.cs
@@ -2,7 +2,7 @@
/// Analyzer for don't concatenate strings in loops.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class DontConcatenateStringsInLoops : DiagnosticAnalyzer
+public sealed class DontConcatenateStringsInLoopsAnalyzer : DiagnosticAnalyzer
{
private static readonly ImmutableArray SyntaxKinds = [
SyntaxKind.ForStatement,
@@ -48,4 +48,4 @@ assignment.Left is IdentifierNameSyntax identifierName &&
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/EcoCode.Analyzers/Analyzers/EC81_SpecifyStructLayoutAnalyzer.cs b/src/EcoCode.Analyzers/Analyzers/EC81_SpecifyStructLayoutAnalyzer.cs
new file mode 100644
index 00000000..22f3ddf4
--- /dev/null
+++ b/src/EcoCode.Analyzers/Analyzers/EC81_SpecifyStructLayoutAnalyzer.cs
@@ -0,0 +1,59 @@
+namespace EcoCode.Analyzers;
+
+/// Analyzer for specify struct layout.
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class SpecifyStructLayoutAnalyzer : DiagnosticAnalyzer
+{
+ private static readonly ImmutableArray SymbolKinds = [SymbolKind.NamedType];
+
+ /// The diagnostic descriptor.
+ public static DiagnosticDescriptor Descriptor { get; } = new(
+ Rule.Ids.EC81_UseStructLayout,
+ title: "Use struct layout",
+ messageFormat: "Use struct layout",
+ Rule.Categories.Performance,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: string.Empty,
+ helpLinkUri: Rule.GetHelpUri(Rule.Ids.EC81_UseStructLayout));
+
+ ///
+ public override ImmutableArray SupportedDiagnostics => [Descriptor];
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+ context.RegisterSymbolAction(static context => Analyze(context), SymbolKinds);
+ }
+
+ private static void Analyze(SymbolAnalysisContext context)
+ {
+ if (context.Symbol is not INamedTypeSymbol symbol || !symbol.IsValueType || symbol.EnumUnderlyingType is not null)
+ return;
+
+ var structLayoutAttributeType = context.Compilation.GetBestTypeByMetadataName("System.Runtime.InteropServices.StructLayoutAttribute");
+ if (structLayoutAttributeType is null) return;
+
+ foreach (var attr in symbol.GetAttributes())
+ {
+ if (SymbolEqualityComparer.Default.Equals(structLayoutAttributeType, attr.AttributeClass))
+ return;
+ }
+
+ int memberCount = 0;
+ foreach (var member in symbol.GetMembers())
+ {
+ if (member is not IFieldSymbol fieldSymbol || fieldSymbol.IsConst || fieldSymbol.IsStatic)
+ continue;
+
+ if (fieldSymbol.Type.IsReferenceType) return; // A struct containing a reference type is always in auto layout
+ memberCount++;
+ }
+
+ if (memberCount < 2) return;
+ foreach (var location in symbol.Locations)
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, location));
+ }
+}
diff --git a/src/EcoCode.Analyzers/EcoCode.Analyzers.csproj b/src/EcoCode.Analyzers/EcoCode.Analyzers.csproj
index 3e89d50b..eed48b54 100644
--- a/src/EcoCode.Analyzers/EcoCode.Analyzers.csproj
+++ b/src/EcoCode.Analyzers/EcoCode.Analyzers.csproj
@@ -7,16 +7,9 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
+
+
+
diff --git a/src/EcoCode.Analyzers/Rule.cs b/src/EcoCode.Analyzers/Rule.cs
index a085e1ca..525ea234 100644
--- a/src/EcoCode.Analyzers/Rule.cs
+++ b/src/EcoCode.Analyzers/Rule.cs
@@ -17,5 +17,6 @@ public static class Ids
public const string EC69_DontCallFunctionsInLoopConditions = "EC69";
public const string EC72_DontExecuteSqlCommandsInLoops = "EC72";
public const string EC75_DontConcatenateStringsInLoops = "EC75";
+ public const string EC81_UseStructLayout = "EC81";
}
}
diff --git a/src/EcoCode.CodeFixes/CodeFixes/DontCallFunctionsInLoopConditionsCodeFixProvider.cs b/src/EcoCode.CodeFixes/CodeFixes/EC69_DontCallFunctionsInLoopConditionsCodeFixProvider.cs
similarity index 91%
rename from src/EcoCode.CodeFixes/CodeFixes/DontCallFunctionsInLoopConditionsCodeFixProvider.cs
rename to src/EcoCode.CodeFixes/CodeFixes/EC69_DontCallFunctionsInLoopConditionsCodeFixProvider.cs
index 92435d85..e034878d 100644
--- a/src/EcoCode.CodeFixes/CodeFixes/DontCallFunctionsInLoopConditionsCodeFixProvider.cs
+++ b/src/EcoCode.CodeFixes/CodeFixes/EC69_DontCallFunctionsInLoopConditionsCodeFixProvider.cs
@@ -5,11 +5,11 @@
public sealed class DontCallFunctionsInLoopConditionsCodeFixProvider : CodeFixProvider
{
///
- public override ImmutableArray FixableDiagnosticIds => [DontCallFunctionsInLoopConditions.Descriptor.Id];
+ public override ImmutableArray FixableDiagnosticIds => [DontCallFunctionsInLoopConditionsAnalyzer.Descriptor.Id];
///
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
///
public override Task RegisterCodeFixesAsync(CodeFixContext context) => Task.CompletedTask;
-}
\ No newline at end of file
+}
diff --git a/src/EcoCode.CodeFixes/CodeFixes/DontExecuteSqlCommandsInLoopsCodeFixProvider.cs b/src/EcoCode.CodeFixes/CodeFixes/EC72_DontExecuteSqlCommandsInLoopsCodeFixProvider.cs
similarity index 91%
rename from src/EcoCode.CodeFixes/CodeFixes/DontExecuteSqlCommandsInLoopsCodeFixProvider.cs
rename to src/EcoCode.CodeFixes/CodeFixes/EC72_DontExecuteSqlCommandsInLoopsCodeFixProvider.cs
index cfef66f9..c280cf1a 100644
--- a/src/EcoCode.CodeFixes/CodeFixes/DontExecuteSqlCommandsInLoopsCodeFixProvider.cs
+++ b/src/EcoCode.CodeFixes/CodeFixes/EC72_DontExecuteSqlCommandsInLoopsCodeFixProvider.cs
@@ -5,11 +5,11 @@
public sealed class DontExecuteSqlCommandsInLoopsCodeFixProvider : CodeFixProvider
{
///
- public override ImmutableArray FixableDiagnosticIds => [DontExecuteSqlCommandsInLoops.Descriptor.Id];
+ public override ImmutableArray FixableDiagnosticIds => [DontExecuteSqlCommandsInLoopsAnalyzer.Descriptor.Id];
///
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
///
public override Task RegisterCodeFixesAsync(CodeFixContext context) => Task.CompletedTask;
-}
\ No newline at end of file
+}
diff --git a/src/EcoCode.CodeFixes/CodeFixes/DontConcatenateStringsInLoopsCodeFixProvider.cs b/src/EcoCode.CodeFixes/CodeFixes/EC75_DontConcatenateStringsInLoopsCodeFixProvider.cs
similarity index 92%
rename from src/EcoCode.CodeFixes/CodeFixes/DontConcatenateStringsInLoopsCodeFixProvider.cs
rename to src/EcoCode.CodeFixes/CodeFixes/EC75_DontConcatenateStringsInLoopsCodeFixProvider.cs
index ae4689ca..81fb020e 100644
--- a/src/EcoCode.CodeFixes/CodeFixes/DontConcatenateStringsInLoopsCodeFixProvider.cs
+++ b/src/EcoCode.CodeFixes/CodeFixes/EC75_DontConcatenateStringsInLoopsCodeFixProvider.cs
@@ -6,11 +6,11 @@
public sealed class DontConcatenateStringsInLoopsCodeFixProvider : CodeFixProvider
{
///
- public override ImmutableArray FixableDiagnosticIds => [DontConcatenateStringsInLoops.Descriptor.Id];
+ public override ImmutableArray FixableDiagnosticIds => [DontConcatenateStringsInLoopsAnalyzer.Descriptor.Id];
///
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
///
public override Task RegisterCodeFixesAsync(CodeFixContext context) => Task.CompletedTask;
-}
\ No newline at end of file
+}
diff --git a/src/EcoCode.CodeFixes/CodeFixes/EC81_SpecifyStructLayoutCodeFixProvider.cs b/src/EcoCode.CodeFixes/CodeFixes/EC81_SpecifyStructLayoutCodeFixProvider.cs
new file mode 100644
index 00000000..44d23299
--- /dev/null
+++ b/src/EcoCode.CodeFixes/CodeFixes/EC81_SpecifyStructLayoutCodeFixProvider.cs
@@ -0,0 +1,60 @@
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.Simplification;
+using System.Threading;
+
+namespace EcoCode.CodeFixes;
+
+/// The code fix provider for use struct layout.
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SpecifyStructLayoutCodeFixProvider)), Shared]
+public sealed class SpecifyStructLayoutCodeFixProvider : CodeFixProvider
+{
+ ///
+ public override ImmutableArray FixableDiagnosticIds => [SpecifyStructLayoutAnalyzer.Descriptor.Id];
+
+ ///
+ public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
+
+ ///
+ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
+ var node = root?.FindNode(context.Span, getInnermostNodeForTie: true);
+ if (node is not TypeDeclarationSyntax nodeToFix) return;
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ "Add Auto StructLayout attribute",
+ ct => Refactor(context.Document, nodeToFix, LayoutKind.Auto, ct),
+ equivalenceKey: "Add Auto StructLayout attribute"),
+ context.Diagnostics);
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ "Add Sequential StructLayout attribute",
+ ct => Refactor(context.Document, nodeToFix, LayoutKind.Sequential, ct),
+ equivalenceKey: "Add Sequential StructLayout attribute"),
+ context.Diagnostics);
+ }
+
+ private static async Task Refactor(Document document, SyntaxNode nodeToFix, LayoutKind layoutKind, CancellationToken cancellationToken)
+ {
+ var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
+
+ var structLayoutAttributeType = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.Runtime.InteropServices.StructLayoutAttribute");
+ if (structLayoutAttributeType is null) return document;
+
+ var layoutKindType = editor.SemanticModel.Compilation.GetBestTypeByMetadataName("System.Runtime.InteropServices.LayoutKind");
+ if (layoutKindType is null) return document;
+
+ editor.AddAttribute(nodeToFix, editor.Generator.Attribute(
+ editor.Generator.TypeExpression(structLayoutAttributeType).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
+ [
+ editor.Generator.AttributeArgument(editor.Generator.MemberAccessExpression(
+ editor.Generator.TypeExpression(layoutKindType).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
+ layoutKind.ToString())),
+ ]));
+ return editor.GetChangedDocument();
+ }
+}
diff --git a/src/EcoCode.CodeFixes/EcoCode.CodeFixes.csproj b/src/EcoCode.CodeFixes/EcoCode.CodeFixes.csproj
index d1b1235d..e062bbf3 100644
--- a/src/EcoCode.CodeFixes/EcoCode.CodeFixes.csproj
+++ b/src/EcoCode.CodeFixes/EcoCode.CodeFixes.csproj
@@ -7,15 +7,9 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+
@@ -23,4 +17,4 @@
-
\ No newline at end of file
+
diff --git a/src/EcoCode.CodeFixes/GlobalUsings.cs b/src/EcoCode.CodeFixes/GlobalUsings.cs
index b05d6438..6bf54f4b 100644
--- a/src/EcoCode.CodeFixes/GlobalUsings.cs
+++ b/src/EcoCode.CodeFixes/GlobalUsings.cs
@@ -1,7 +1,8 @@
global using EcoCode.Analyzers;
+global using EcoCode.Shared;
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CodeFixes;
global using System.Collections.Immutable;
global using System.Composition;
global using System.Runtime.InteropServices;
-global using System.Threading.Tasks;
\ No newline at end of file
+global using System.Threading.Tasks;
diff --git a/src/EcoCode.Package/EcoCode.Package.csproj b/src/EcoCode.Package/EcoCode.Package.csproj
index 5637b7d9..a9333921 100644
--- a/src/EcoCode.Package/EcoCode.Package.csproj
+++ b/src/EcoCode.Package/EcoCode.Package.csproj
@@ -45,17 +45,11 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
-
+
\ No newline at end of file
diff --git a/src/EcoCode.Shared/EcoCode.Shared.csproj b/src/EcoCode.Shared/EcoCode.Shared.csproj
index cbbe276d..b021e757 100644
--- a/src/EcoCode.Shared/EcoCode.Shared.csproj
+++ b/src/EcoCode.Shared/EcoCode.Shared.csproj
@@ -6,15 +6,9 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+
\ No newline at end of file
diff --git a/src/EcoCode.Shared/Extensions/CompilationExtensions.cs b/src/EcoCode.Shared/Extensions/CompilationExtensions.cs
new file mode 100644
index 00000000..e187a181
--- /dev/null
+++ b/src/EcoCode.Shared/Extensions/CompilationExtensions.cs
@@ -0,0 +1,52 @@
+namespace EcoCode.Shared;
+
+/// Extension methods for .
+public static class CompilationExtensions
+{
+ ///
+ /// Gets a type by its metadata name to use for code analysis within a . This method
+ /// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
+ /// following rules.
+ ///
+ ///
+ /// -
+ /// If only one type with the given name is found within the compilation and its referenced assemblies, that
+ /// type is returned regardless of accessibility.
+ ///
+ /// -
+ /// If the current defines the symbol, that symbol is returned.
+ ///
+ /// -
+ /// If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
+ /// , that symbol is returned.
+ ///
+ /// -
+ /// Otherwise, this method returns .
+ ///
+ ///
+ ///
+ /// The to consider for analysis.
+ /// The fully-qualified metadata type name to find.
+ /// The symbol to use for code analysis; otherwise, .
+ /// Comes from Roslynator sourc code :
+ /// https://github.com/dotnet/roslyn/blob/d2ff1d83e8fde6165531ad83f0e5b1ae95908289/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs#L11-L68
+ ///
+ public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
+ {
+ var type = default(INamedTypeSymbol);
+ foreach (var currentType in compilation.GetTypesByMetadataName(fullyQualifiedMetadataName))
+ {
+ if (ReferenceEquals(currentType.ContainingAssembly, compilation.Assembly))
+ return currentType;
+
+ var visibility = currentType.GetResultantVisibility();
+ if (visibility is SymbolVisibility.Public ||
+ visibility is SymbolVisibility.Internal && currentType.ContainingAssembly.GivesAccessTo(compilation.Assembly))
+ {
+ if (type is object) return null; // Multiple visible types with the same metadata name are present
+ type = currentType;
+ }
+ }
+ return type;
+ }
+}
diff --git a/src/EcoCode.Shared/Extensions/SymbolExtensions.cs b/src/EcoCode.Shared/Extensions/SymbolExtensions.cs
index 81187c97..87c257b1 100644
--- a/src/EcoCode.Shared/Extensions/SymbolExtensions.cs
+++ b/src/EcoCode.Shared/Extensions/SymbolExtensions.cs
@@ -79,4 +79,29 @@ public static bool IsDeclaredOutsideLoop(this ISymbol symbol, SyntaxNode loopNod
}
return null;
}
-}
\ No newline at end of file
+
+ /// Returns the visibility of a symbol.
+ /// The symbol.
+ /// The visibility of the symbol.
+ public static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
+ {
+ if (symbol.Kind is SymbolKind.Alias or SymbolKind.TypeParameter)
+ return SymbolVisibility.Private;
+
+ if (symbol.Kind is SymbolKind.Parameter) // Parameters are only as visible as their containing symbol
+ return GetResultantVisibility(symbol.ContainingSymbol);
+
+ var visibility = SymbolVisibility.Public;
+ while (symbol is not null && symbol.Kind is not SymbolKind.Namespace)
+ {
+ if (symbol.DeclaredAccessibility is Accessibility.NotApplicable or Accessibility.Private)
+ return SymbolVisibility.Private;
+
+ if (symbol.DeclaredAccessibility is Accessibility.Internal or Accessibility.ProtectedAndInternal)
+ visibility = SymbolVisibility.Internal;
+
+ symbol = symbol.ContainingSymbol;
+ }
+ return visibility;
+ }
+}
diff --git a/src/EcoCode.Shared/GlobalUsings.cs b/src/EcoCode.Shared/GlobalUsings.cs
index 389fac6e..2e7d51b1 100644
--- a/src/EcoCode.Shared/GlobalUsings.cs
+++ b/src/EcoCode.Shared/GlobalUsings.cs
@@ -1,3 +1,3 @@
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
-global using System.Linq;
\ No newline at end of file
+global using System.Linq;
diff --git a/src/EcoCode.Shared/SymbolVisibility.cs b/src/EcoCode.Shared/SymbolVisibility.cs
new file mode 100644
index 00000000..ffaadfac
--- /dev/null
+++ b/src/EcoCode.Shared/SymbolVisibility.cs
@@ -0,0 +1,14 @@
+namespace EcoCode.Shared;
+
+/// The visibility of a symbol.
+public enum SymbolVisibility
+{
+ /// Public visibility.
+ Public,
+
+ /// Internal visibility.
+ Internal,
+
+ /// Private visibility.
+ Private,
+}
diff --git a/src/EcoCode.Tests/EcoCode.Tests.csproj b/src/EcoCode.Tests/EcoCode.Tests.csproj
index 84f41a33..b2f0d0c3 100644
--- a/src/EcoCode.Tests/EcoCode.Tests.csproj
+++ b/src/EcoCode.Tests/EcoCode.Tests.csproj
@@ -14,19 +14,15 @@
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
+
+
+
+
+
diff --git a/src/EcoCode.Tests/LiveWarnings/DontCallFunctionsInLoopConditionsLiveWarnings.cs b/src/EcoCode.Tests/LiveWarnings/EC69_DontCallFunctionsInLoopConditionsLiveWarnings.cs
similarity index 100%
rename from src/EcoCode.Tests/LiveWarnings/DontCallFunctionsInLoopConditionsLiveWarnings.cs
rename to src/EcoCode.Tests/LiveWarnings/EC69_DontCallFunctionsInLoopConditionsLiveWarnings.cs
diff --git a/src/EcoCode.Tests/LiveWarnings/DontExecuteSqlCommandsInLoopsLiveWarnings.cs b/src/EcoCode.Tests/LiveWarnings/EC72_DontExecuteSqlCommandsInLoopsLiveWarnings.cs
similarity index 100%
rename from src/EcoCode.Tests/LiveWarnings/DontExecuteSqlCommandsInLoopsLiveWarnings.cs
rename to src/EcoCode.Tests/LiveWarnings/EC72_DontExecuteSqlCommandsInLoopsLiveWarnings.cs
diff --git a/src/EcoCode.Tests/LiveWarnings/DontConcatenateStringsInLoopsLiveWarnings.cs b/src/EcoCode.Tests/LiveWarnings/EC75_DontConcatenateStringsInLoopsLiveWarnings.cs
similarity index 99%
rename from src/EcoCode.Tests/LiveWarnings/DontConcatenateStringsInLoopsLiveWarnings.cs
rename to src/EcoCode.Tests/LiveWarnings/EC75_DontConcatenateStringsInLoopsLiveWarnings.cs
index b1e1f83a..2aa612af 100644
--- a/src/EcoCode.Tests/LiveWarnings/DontConcatenateStringsInLoopsLiveWarnings.cs
+++ b/src/EcoCode.Tests/LiveWarnings/EC75_DontConcatenateStringsInLoopsLiveWarnings.cs
@@ -32,4 +32,4 @@ public static void Run(string s0)
s3 = i.ToString(provider: null);
Console.WriteLine(s3);
}
-}
\ No newline at end of file
+}
diff --git a/src/EcoCode.Tests/LiveWarnings/EC81_SpecifyStructLayoutLiveWarnings.cs b/src/EcoCode.Tests/LiveWarnings/EC81_SpecifyStructLayoutLiveWarnings.cs
new file mode 100644
index 00000000..d539f624
--- /dev/null
+++ b/src/EcoCode.Tests/LiveWarnings/EC81_SpecifyStructLayoutLiveWarnings.cs
@@ -0,0 +1,29 @@
+using System.Runtime.InteropServices;
+
+namespace EcoCode.Tests;
+
+internal static class SpecifyStructLayoutLiveWarnings
+{
+ private record struct TestStruct1;
+
+ private record struct TestStruct2(int A);
+
+ private record struct TestStruct3(string A);
+
+ private record struct TestStruct4(int A, double B); // EC81 - Use struct layout
+
+ [StructLayout(LayoutKind.Auto)]
+ private record struct TestStruct4_Fixed(int A, double B);
+
+ private record struct TestStruct5(int A, string B);
+
+ private record struct TestStruct6(int A, double B, int C); // EC81 - Use struct layout
+
+ [StructLayout(LayoutKind.Auto)]
+ private record struct TestStruct6_Fixed(int A, double B, int C);
+
+ private record struct TestStruct7(bool A, int B, char C, short D, ulong E, DateTime F); // EC81 - Use struct layout
+
+ [StructLayout(LayoutKind.Auto)]
+ private record struct TestStruct7_Fixed(bool A, int B, char C, short D, ulong E, DateTime F);
+}
diff --git a/src/EcoCode.Tests/Tests/DontCallFunctionsInLoopConditionsUnitTests.cs b/src/EcoCode.Tests/Tests/EC69_DontCallFunctionsInLoopConditionsUnitTests.cs
similarity index 98%
rename from src/EcoCode.Tests/Tests/DontCallFunctionsInLoopConditionsUnitTests.cs
rename to src/EcoCode.Tests/Tests/EC69_DontCallFunctionsInLoopConditionsUnitTests.cs
index 16faad33..c3af233c 100644
--- a/src/EcoCode.Tests/Tests/DontCallFunctionsInLoopConditionsUnitTests.cs
+++ b/src/EcoCode.Tests/Tests/EC69_DontCallFunctionsInLoopConditionsUnitTests.cs
@@ -4,7 +4,7 @@
public class DontCallFunctionsInLoopConditionsUnitTests
{
private static readonly VerifyDlg VerifyAsync = CodeFixVerifier.VerifyAsync<
- DontCallFunctionsInLoopConditions,
+ DontCallFunctionsInLoopConditionsAnalyzer,
DontCallFunctionsInLoopConditionsCodeFixProvider>;
[TestMethod]
diff --git a/src/EcoCode.Tests/Tests/DontExecuteSqlCommandsInLoopsUnitTests.cs b/src/EcoCode.Tests/Tests/EC72_DontExecuteSqlCommandsInLoopsUnitTests.cs
similarity index 96%
rename from src/EcoCode.Tests/Tests/DontExecuteSqlCommandsInLoopsUnitTests.cs
rename to src/EcoCode.Tests/Tests/EC72_DontExecuteSqlCommandsInLoopsUnitTests.cs
index 3fa279d2..b19f1005 100644
--- a/src/EcoCode.Tests/Tests/DontExecuteSqlCommandsInLoopsUnitTests.cs
+++ b/src/EcoCode.Tests/Tests/EC72_DontExecuteSqlCommandsInLoopsUnitTests.cs
@@ -4,7 +4,7 @@
public class DontExecuteSqlCommandsInLoopsUnitTests
{
private static readonly VerifyDlg VerifyAsync = CodeFixVerifier.VerifyAsync<
- DontExecuteSqlCommandsInLoops,
+ DontExecuteSqlCommandsInLoopsAnalyzer,
DontExecuteSqlCommandsInLoopsCodeFixProvider>;
[TestMethod]
diff --git a/src/EcoCode.Tests/Tests/DontConcatenateStringsInLoopsUnitTests.cs b/src/EcoCode.Tests/Tests/EC75_DontConcatenateStringsInLoopsUnitTests.cs
similarity index 97%
rename from src/EcoCode.Tests/Tests/DontConcatenateStringsInLoopsUnitTests.cs
rename to src/EcoCode.Tests/Tests/EC75_DontConcatenateStringsInLoopsUnitTests.cs
index be346eeb..4757fcba 100644
--- a/src/EcoCode.Tests/Tests/DontConcatenateStringsInLoopsUnitTests.cs
+++ b/src/EcoCode.Tests/Tests/EC75_DontConcatenateStringsInLoopsUnitTests.cs
@@ -4,7 +4,7 @@
public class DontConcatenateStringsInLoopsUnitTests
{
private static readonly VerifyDlg VerifyAsync = CodeFixVerifier.VerifyAsync<
- DontConcatenateStringsInLoops,
+ DontConcatenateStringsInLoopsAnalyzer,
DontConcatenateStringsInLoopsCodeFixProvider>;
[TestMethod]
diff --git a/src/EcoCode.Tests/Tests/EC81_SpecifyStructLayoutUnitTests.cs b/src/EcoCode.Tests/Tests/EC81_SpecifyStructLayoutUnitTests.cs
new file mode 100644
index 00000000..2a427694
--- /dev/null
+++ b/src/EcoCode.Tests/Tests/EC81_SpecifyStructLayoutUnitTests.cs
@@ -0,0 +1,92 @@
+namespace EcoCode.Tests;
+
+[TestClass]
+public class SpecifyStructLayoutUnitTests
+{
+ private static readonly VerifyDlg VerifyAsync = CodeFixVerifier.VerifyAsync<
+ SpecifyStructLayoutAnalyzer,
+ SpecifyStructLayoutCodeFixProvider>;
+
+ [TestMethod]
+ public async Task EmptyCodeAsync() => await VerifyAsync("").ConfigureAwait(false);
+
+ [TestMethod]
+ public async Task EmptyStructWithNoLayoutAsync() => await VerifyAsync(
+ "public record struct TestStruct;").ConfigureAwait(false);
+
+ [TestMethod]
+ public async Task ValuePropWithNoLayoutAsync() => await VerifyAsync(
+ "public record struct TestStruct(int A);").ConfigureAwait(false);
+
+ [TestMethod]
+ public async Task ReferencePropWithNoLayoutAsync() => await VerifyAsync(
+ "public record struct TestStruct(string A);").ConfigureAwait(false);
+
+ [TestMethod]
+ // For some reason this test doesn't pass with the 'record' syntax on GitHub, but it passes locally..
+ public async Task ValuePropsWithNoLayoutAsync() => await VerifyAsync("""
+ public struct [|TestStruct|]
+ {
+ public int A { get; set; }
+ public double B { get; set; }
+ };
+ """,
+ fixedSource: """
+ using System.Runtime.InteropServices;
+
+ [StructLayout(LayoutKind.Auto)]
+ public struct TestStruct
+ {
+ public int A { get; set; }
+ public double B { get; set; }
+ };
+ """).ConfigureAwait(false);
+
+ [TestMethod]
+ public async Task ValuePropsWithLayoutAsync() => await VerifyAsync("""
+ using System.Runtime.InteropServices;
+
+ [StructLayout(LayoutKind.Auto)]
+ public record struct TestStruct(int A, double B);
+ """).ConfigureAwait(false);
+
+ [TestMethod]
+ public async Task ValueAndReferencePropsWithNoLayoutAsync() => await VerifyAsync(
+ "public record struct TestStruct(int A, string B);").ConfigureAwait(false);
+
+ [TestMethod]
+ // For some reason this test doesn't pass with the 'record' syntax on GitHub, but it passes locally..
+ public async Task AdditionalValuePropsWithNoLayout1Async() => await VerifyAsync("""
+ public struct [|TestStruct|]
+ {
+ public int A { get; set; }
+ public double B { get; set; }
+ public int C { get; set; }
+ };
+ """,
+ fixedSource: """
+ using System.Runtime.InteropServices;
+
+ [StructLayout(LayoutKind.Auto)]
+ public struct TestStruct
+ {
+ public int A { get; set; }
+ public double B { get; set; }
+ public int C { get; set; }
+ };
+ """).ConfigureAwait(false);
+
+ [TestMethod]
+ public async Task AdditionalValueFieldsWithNoLayout2Async() => await VerifyAsync("""
+ using System;
+
+ public record struct [|TestStruct|](bool A, int B, char C, short D, ulong E, DateTime F);
+ """,
+ fixedSource: """
+ using System;
+ using System.Runtime.InteropServices;
+
+ [StructLayout(LayoutKind.Auto)]
+ public record struct [|TestStruct|](bool A, int B, char C, short D, ulong E, DateTime F);
+ """).ConfigureAwait(false);
+}
diff --git a/src/EcoCode.Vsix/EcoCode.Vsix.csproj b/src/EcoCode.Vsix/EcoCode.Vsix.csproj
index 5d1c5dfb..5743ed22 100644
--- a/src/EcoCode.Vsix/EcoCode.Vsix.csproj
+++ b/src/EcoCode.Vsix/EcoCode.Vsix.csproj
@@ -38,10 +38,12 @@
+
+
-
+