Skip to content

Commit

Permalink
Add MEN016 Avoid Top-Level Statements rule
Browse files Browse the repository at this point in the history
  • Loading branch information
menees committed May 30, 2023
1 parent 5a75373 commit a1254de
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ This software is CharityWare. If you use it, I ask that you donate something to
| MEN012 | Flags should be powers of two | Flags enum members should be powers of two or bitwise-or combinations of named members. This rule is a complement to [CA2217](https://msdn.microsoft.com/en-us/library/ms182335.aspx). |
| MEN013 | Use UTC time | Recommends UTC times because they're unambiguous and always increasing. This rule includes a code fix provider. |
| MEN014 | Prefer TryGetValue | Recommends calling TryGetValue (for a single lookup and retrieval) instead of ContainsKey and this[key] with duplicate lookups. |
| MEN015 | Use Preferred Terms | Similar to the old FxCop [CA1726 rule](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1726?view=vs-2019#rule-description) except this rule only checks single terms (not double terms). So it uses a slightly different set of [default preferred terms](https://github.com/menees/Analyzers/blob/172bf0b6820e143de28f2f43e712908179ca1073/src/Menees.Analyzers/Settings.cs#L53-L77) (omitting double terms like LogOn), and it includes Id as a preferred term over ID (per FxCop's [CA1709 rule](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1709?view=vs-2019#rule-description)). |
| MEN015 | Use Preferred Terms | Similar to the old FxCop [CA1726 rule](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1726?view=vs-2019#rule-description) except this rule only checks single terms (not double terms). So it uses a slightly different set of [default preferred terms](https://github.com/menees/Analyzers/blob/172bf0b6820e143de28f2f43e712908179ca1073/src/Menees.Analyzers/Settings.cs#L53-L77) (omitting double terms like LogOn), and it includes Id as a preferred term over ID (per FxCop's [CA1709 rule](https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1709?view=vs-2019#rule-description)). |
| MEN016 | Avoid Top-Level Statements | C# [top-level statements](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements) are only for toy/example programs and should be avoided in long-term code for [consistency and maintainability](https://github.com/dotnet/docs/issues/27420#issuecomment-988776134). |
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

<!-- Make the assembly, file, and NuGet package versions the same. -->
<!-- NOTE: Change the version in Vsix\source.extension.vsixmanifest to match this! -->
<Version>3.0.10</Version>
<Version>3.0.11</Version>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Debug'">
Expand Down
2 changes: 1 addition & 1 deletion src/Menees.Analyzers.Vsix/source.extension.vsixmanifest
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="Menees.Analyzers.Vsix" Version="3.0.4" Language="en-US" Publisher="Bill Menees"/>
<Identity Id="Menees.Analyzers.Vsix" Version="3.0.11" Language="en-US" Publisher="Bill Menees"/>
<DisplayName>Menees.Analyzers.Vsix</DisplayName>
<Description xml:space="preserve">Provides analyzers for validating that tabs are used for indentation, that the lengths of lines, methods, properties, and files are acceptable, and that #regions are used within long files and files that contain multiple types.</Description>
<License>License.txt</License>
Expand Down
57 changes: 57 additions & 0 deletions src/Menees.Analyzers/Men016AvoidTopLevelStatements.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace Menees.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class Men016AvoidTopLevelStatements : Analyzer
{
#region Public Constants

public const string DiagnosticId = "MEN016";

#endregion

#region Private Data Members

private static readonly LocalizableString Title =
new LocalizableResourceString(nameof(Resources.Men016Title), Resources.ResourceManager, typeof(Resources));

private static readonly LocalizableString MessageFormat =
new LocalizableResourceString(nameof(Resources.Men016MessageFormat), Resources.ResourceManager, typeof(Resources));

private static readonly LocalizableString Description =
new LocalizableResourceString(nameof(Resources.Men016Description), Resources.ResourceManager, typeof(Resources));

private static readonly DiagnosticDescriptor Rule =
new(DiagnosticId, Title, MessageFormat, Rules.Usage, DiagnosticSeverity.Warning, Rules.EnabledByDefault, Description);

#endregion

#region Public Properties

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

#endregion

#region Public Methods

public override void Initialize(AnalysisContext context)
{
base.Initialize(context);
context.RegisterSyntaxNodeActionHonorExclusions(this, HandleGlobalStatement, SyntaxKind.GlobalStatement);
}

#endregion

#region Private Methods

private static void HandleGlobalStatement(SyntaxNodeAnalysisContext context)
{
if (context.Node is GlobalStatementSyntax statement)
{
Location location = statement.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(Rule, location));
}
}

#endregion
}
}
27 changes: 27 additions & 0 deletions src/Menees.Analyzers/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/Menees.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,13 @@
<data name="Men015Title" xml:space="preserve">
<value>Use preferred terms</value>
</data>
<data name="Men016Description" xml:space="preserve">
<value>Top-level statements should be avoided in long-term code for consistency and maintainability with other object-oriented modules.</value>
</data>
<data name="Men016MessageFormat" xml:space="preserve">
<value>Use object-oriented methods instead of top-level statements.</value>
</data>
<data name="Men016Title" xml:space="preserve">
<value>Avoid top-level statements</value>
</data>
</root>
151 changes: 151 additions & 0 deletions tests/Menees.Analyzers.Test/Men016UnitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
namespace Menees.Analyzers.Test
{
[TestClass]
public class Men016UnitTests : CodeFixVerifier
{
#region Private Data Members

private const string ExpectedMessage = "Use object-oriented methods instead of top-level statements.";

#endregion

#region Protected Properties

protected override DiagnosticAnalyzer CSharpDiagnosticAnalyzer => new Men016AvoidTopLevelStatements();

#endregion

#region ValidCodeTest

[TestMethod]
public void ValidCodeTest()
{
this.VerifyCSharpDiagnostic(string.Empty);

const string test = @"
using System.Collections.Generic;
using System.Diagnostics;
class Testing
{
Dictionary<string, string> _entries = new();
public Testing()
{
Dictionary<string, int> test = new();
if (test.ContainsKey(""a""))
{
Debug.WriteLine(""Contains a"");
}
}
public void Mask()
{
if (_entries.ContainsKey(""Password""))
{
_entries[""Password""] = ""********"";
}
}
public bool ContainsKey(string key) => _entries.ContainsKey(key);
}";
this.VerifyCSharpDiagnostic(test);
}

#endregion

#region InvalidCodeTests

[TestMethod]
public void InvalidCodeSimpleTest()
{
const string test = @"
using System;
Console.WriteLine(""Test"");
";

var analyzer = this.CSharpDiagnosticAnalyzer;
DiagnosticResult[] expected = new[]
{
new DiagnosticResult(analyzer)
{
Message = ExpectedMessage,
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 3, 1) },
},
};

this.VerifyCSharpDiagnostic(test, expected);
}

[TestMethod]
public void InvalidCodeWithClassTest()
{
const string test = @"
using System;
using static System.Console;
WriteLine(""Test"");
MyClass.TestMethod();
public class MyClass
{
public static void TestMethod()
{
Console.WriteLine(""Hello World!"");
}
}
";

var analyzer = this.CSharpDiagnosticAnalyzer;
DiagnosticResult[] expected = new[]
{
new DiagnosticResult(analyzer)
{
Message = ExpectedMessage,
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 5, 1) },
},
new DiagnosticResult(analyzer)
{
Message = ExpectedMessage,
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 6, 1) },
},
};

this.VerifyCSharpDiagnostic(test, expected);
}

[TestMethod]
public void InvalidCodeWithNamespaceTest()
{
const string test = @"
using System;
MyNamespace.MyClass.MyMethod();
namespace MyNamespace
{
class MyClass
{
public static void MyMethod()
{
Console.WriteLine(""Hello World from MyNamespace.MyClass.MyMethod!"");
}
}
}";

var analyzer = this.CSharpDiagnosticAnalyzer;
DiagnosticResult[] expected = new[]
{
new DiagnosticResult(analyzer)
{
Message = ExpectedMessage,
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 4, 1) },
},
};

this.VerifyCSharpDiagnostic(test, expected);
}


#endregion
}
}

0 comments on commit a1254de

Please sign in to comment.