Skip to content

Add StaticAnalysis rule for plural nouns in cmdlet and parameter names #3349

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 4 commits into from
Feb 1, 2017
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
433 changes: 433 additions & 0 deletions tools/StaticAnalysis/Exceptions/SignatureIssues.csv

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions tools/StaticAnalysis/ProblemIDs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public static class SignatureProblemId
public const int ConfirmLevelChange = 8200;
public const int CmdletWithDestructiveVerbNoForce = 8210;
public const int CmdletWithUnapprovedVerb = 8300;
public const int CmdletWithPluralNoun = 8400;
public const int ParameterWithPluralNoun = 8410;
public const int CmdletWithDestructiveVerb = 98300;
public const int CmdletWithForceParameter = 98310;
}
Expand Down
50 changes: 50 additions & 0 deletions tools/StaticAnalysis/SignatureVerifier/CmdletSignatureMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,48 @@ private static List<string> GetApprovedVerbs()
}
#endregion

#region SingularNouns
private static readonly List<string> SingularNouns = new List<string>
{
"Access",
"Address",
"Anonymous",
"Diagnostics",
"Express",
"Https",
"InBytes",
"InDays",
"InHours",
"InMinutes",
"InMonths",
"InSeconds",
"Loss",
"Mbps",
"Process",
"Progress",
"SaveAs",
"Statistics",
"Status",
"Success",
"Vmss"
};

public List<ParameterMetadata> GetParametersWithPluralNoun()
{
List<ParameterMetadata> pluralParameters = new List<ParameterMetadata>();
foreach (var parameter in _parameters)
{
if (parameter.Name.EndsWith("s") && SingularNouns.Find(n => parameter.Name.EndsWith(n)) == null)
{
pluralParameters.Add(parameter);
}
}

return pluralParameters;
}

#endregion

/// <summary>
/// The name of the assembly containing cmdlet
/// </summary>
Expand Down Expand Up @@ -185,6 +227,14 @@ public bool IsApprovedVerb
get { return VerbName != null && GetApprovedVerbs().Contains(VerbName); }
}

/// <summary>
/// True if the cmdlet has a singular noun
/// </summary>
public bool HasSingularNoun
{
get { return !NounName.EndsWith("s") || SingularNouns.Find(n => NounName.EndsWith(n)) != null; }
}

/// <summary>
/// The name of the class that implements the cmdlet
/// </summary>
Expand Down
28 changes: 28 additions & 0 deletions tools/StaticAnalysis/SignatureVerifier/SignatureVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,34 @@ public void Analyze(IEnumerable<string> cmdletProbingDirs,
cmdlet.Name, cmdlet.VerbName),
remediation: "Consider renaming the cmdlet to use an approved verb for PowerShell.");
}

if (!cmdlet.HasSingularNoun)
{
issueLogger.LogSignatureIssue(
cmdlet: cmdlet,
severity: 1,
problemId: SignatureProblemId.CmdletWithPluralNoun,
description:
string.Format(
"{0} uses the noun '{1}', which does not follow the enforced " +
"naming convention of using a singular noun for a cmdlet name.",
cmdlet.Name, cmdlet.NounName),
remediation: "Consider using a singular noun for the cmdlet name.");
}

foreach (var parameter in cmdlet.GetParametersWithPluralNoun())
{
issueLogger.LogSignatureIssue(
cmdlet: cmdlet,
severity: 1,
problemId: SignatureProblemId.ParameterWithPluralNoun,
description:
string.Format(
"Parameter {0} of cmdlet {1} does not follow the enforced " +
"naming convention of using a singular noun for a parameter name.",
parameter.Name, cmdlet.Name),
remediation: "Consider using a singular noun for the parameter name.");
}
}

AppDomain.Unload(_appDomain);
Expand Down
71 changes: 71 additions & 0 deletions tools/StaticAnalysis/StaticAnalysis.Test/SignatureVerifierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,76 @@ public void CmdletWithUnapprovedVerb()
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.CmdletWithUnapprovedVerb)).SingleOrDefault<int>().Equals(SignatureProblemId.CmdletWithUnapprovedVerb));
}
#endregion

#region CmdletWithPluralNoun
[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void CmdletWithSingularNoun()
{
cmdletSignatureVerifier.Analyze(
new List<string> { _testCmdletDirPath },
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
(cmdletName) => cmdletName.Equals("Get-SampleKey", StringComparison.OrdinalIgnoreCase));

AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
Assert.Equal(0, testReport.ProblemIdList.Count);
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void CmdletWithPluralNoun()
{
cmdletSignatureVerifier.Analyze(
new List<string> { _testCmdletDirPath },
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
(cmdletName) => cmdletName.Equals("Get-SampleKeys", StringComparison.OrdinalIgnoreCase));

AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
Assert.Equal(1, testReport.ProblemIdList.Count);
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.CmdletWithPluralNoun)).SingleOrDefault<int>().Equals(SignatureProblemId.CmdletWithPluralNoun));
}
#endregion

#region ParameterWithPluralNoun
[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void ParameterWithSingularNoun()
{
cmdletSignatureVerifier.Analyze(
new List<string> { _testCmdletDirPath },
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
(cmdletName) => cmdletName.Equals("Get-SampleFoo", StringComparison.OrdinalIgnoreCase));

AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
Assert.Equal(0, testReport.ProblemIdList.Count);
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void ParameterWithPluralNoun()
{
cmdletSignatureVerifier.Analyze(
new List<string> { _testCmdletDirPath },
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
(cmdletName) => cmdletName.Equals("Get-SampleBar", StringComparison.OrdinalIgnoreCase));

AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
Assert.Equal(1, testReport.ProblemIdList.Count);
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ParameterWithPluralNoun)).SingleOrDefault<int>().Equals(SignatureProblemId.ParameterWithPluralNoun));
}

[Fact]
[Trait(Category.AcceptanceType, Category.CheckIn)]
public void CmdletAndParameterWithSingularNounInList()
{
cmdletSignatureVerifier.Analyze(
new List<string> { _testCmdletDirPath },
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
(cmdletName) => cmdletName.Equals("Get-SampleValue", StringComparison.OrdinalIgnoreCase));

AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
Assert.Equal(0, testReport.ProblemIdList.Count);
}
#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,123 @@ protected override void BeginProcessing()
}
}
}
#endregion

#region CmdletWithPluralNoun
namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletWithSingularNoun
{
using System.Management.Automation;

/// <summary>
/// Verify if a cmdlet has a singular noun in its name.
/// </summary>
[Cmdlet(VerbsCommon.Get, "SampleKey")]
public class CmdletGetSampleKey : Cmdlet
{
/// <summary>
/// Begin processing the cmdlet.
/// </summary>
protected override void BeginProcessing()
{
WriteObject("Get-SampleKey BeginProcessing()");
WriteInformation("Info", null);
}
}
}

namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletWithPluralNoun
{
using System.Management.Automation;

/// <summary>
/// Verify if a cmdlet has a plural noun in its name.
/// </summary>
[Cmdlet("Get", "SampleKeys")]
public class CmdletGetSampleKeys : Cmdlet
{
/// <summary>
/// Begin processing the cmdlet.
/// </summary>
protected override void BeginProcessing()
{
WriteObject("Get-SampleKeys BeginProcessing()");
WriteInformation("Info", null);
}
}
}
#endregion

#region ParameterWithPluralNoun
namespace StaticAnalysis.Test.CmdletTest.Signature.ParameterWithSingularNoun
{
using System.Management.Automation;

/// <summary>
/// Verify if a parameter has a singular noun in its name.
/// </summary>
[Cmdlet(VerbsCommon.Get, "SampleFoo")]
public class CmdletGetSampleFoo : Cmdlet
{
[Parameter(Mandatory = false)]
public string Foo { get; set; }

/// <summary>
/// Begin processing the cmdlet.
/// </summary>
protected override void BeginProcessing()
{
WriteObject("Get-SampleFoo BeginProcessing()");
WriteInformation("Info", null);
}
}
}

namespace StaticAnalysis.Test.CmdletTest.Signature.ParameterWithPluralNoun
{
using System.Management.Automation;

/// <summary>
/// Verify if a parameter has a plural noun in its name.
/// </summary>
[Cmdlet("Get", "SampleBar")]
public class CmdletGetSampleBar : Cmdlet
{
[Parameter(Mandatory = false)]
public string Bars { get; set; }

/// <summary>
/// Begin processing the cmdlet.
/// </summary>
protected override void BeginProcessing()
{
WriteObject("Get-SampleBar BeginProcessing()");
WriteInformation("Info", null);
}
}
}

namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletAndParameterWithSingularNounInList
{
using System.Management.Automation;

/// <summary>
/// Verify if a cmdlet and parameter have a singular noun in the list of
/// accepted nouns ending with "s".
/// </summary>
[Cmdlet("Get", "SampleAddress")]
public class CmdletGetSampleAddress : Cmdlet
{
[Parameter(Mandatory = false)]
public string Address { get; set; }

/// <summary>
/// Begin processing the cmdlet.
/// </summary>
protected override void BeginProcessing()
{
WriteObject("Get-SampleAddress BeginProcessing()");
WriteInformation("Info", null);
}
}
}
#endregion