Skip to content

Commit 8c7e2ff

Browse files
authored
Merge pull request Azure#3349 from cormacpayne/plural-noun-check
Add StaticAnalysis rule for plural nouns in cmdlet and parameter names
2 parents d9e29e4 + 0f7da15 commit 8c7e2ff

File tree

6 files changed

+703
-0
lines changed

6 files changed

+703
-0
lines changed

tools/StaticAnalysis/Exceptions/SignatureIssues.csv

Lines changed: 433 additions & 0 deletions
Large diffs are not rendered by default.

tools/StaticAnalysis/ProblemIDs.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public static class SignatureProblemId
88
public const int ConfirmLevelChange = 8200;
99
public const int CmdletWithDestructiveVerbNoForce = 8210;
1010
public const int CmdletWithUnapprovedVerb = 8300;
11+
public const int CmdletWithPluralNoun = 8400;
12+
public const int ParameterWithPluralNoun = 8410;
1113
public const int CmdletWithDestructiveVerb = 98300;
1214
public const int CmdletWithForceParameter = 98310;
1315
}

tools/StaticAnalysis/SignatureVerifier/CmdletSignatureMetadata.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,48 @@ private static List<string> GetApprovedVerbs()
141141
}
142142
#endregion
143143

144+
#region SingularNouns
145+
private static readonly List<string> SingularNouns = new List<string>
146+
{
147+
"Access",
148+
"Address",
149+
"Anonymous",
150+
"Diagnostics",
151+
"Express",
152+
"Https",
153+
"InBytes",
154+
"InDays",
155+
"InHours",
156+
"InMinutes",
157+
"InMonths",
158+
"InSeconds",
159+
"Loss",
160+
"Mbps",
161+
"Process",
162+
"Progress",
163+
"SaveAs",
164+
"Statistics",
165+
"Status",
166+
"Success",
167+
"Vmss"
168+
};
169+
170+
public List<ParameterMetadata> GetParametersWithPluralNoun()
171+
{
172+
List<ParameterMetadata> pluralParameters = new List<ParameterMetadata>();
173+
foreach (var parameter in _parameters)
174+
{
175+
if (parameter.Name.EndsWith("s") && SingularNouns.Find(n => parameter.Name.EndsWith(n)) == null)
176+
{
177+
pluralParameters.Add(parameter);
178+
}
179+
}
180+
181+
return pluralParameters;
182+
}
183+
184+
#endregion
185+
144186
/// <summary>
145187
/// The name of the assembly containing cmdlet
146188
/// </summary>
@@ -185,6 +227,14 @@ public bool IsApprovedVerb
185227
get { return VerbName != null && GetApprovedVerbs().Contains(VerbName); }
186228
}
187229

230+
/// <summary>
231+
/// True if the cmdlet has a singular noun
232+
/// </summary>
233+
public bool HasSingularNoun
234+
{
235+
get { return !NounName.EndsWith("s") || SingularNouns.Find(n => NounName.EndsWith(n)) != null; }
236+
}
237+
188238
/// <summary>
189239
/// The name of the class that implements the cmdlet
190240
/// </summary>

tools/StaticAnalysis/SignatureVerifier/SignatureVerifier.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,34 @@ public void Analyze(IEnumerable<string> cmdletProbingDirs,
170170
cmdlet.Name, cmdlet.VerbName),
171171
remediation: "Consider renaming the cmdlet to use an approved verb for PowerShell.");
172172
}
173+
174+
if (!cmdlet.HasSingularNoun)
175+
{
176+
issueLogger.LogSignatureIssue(
177+
cmdlet: cmdlet,
178+
severity: 1,
179+
problemId: SignatureProblemId.CmdletWithPluralNoun,
180+
description:
181+
string.Format(
182+
"{0} uses the noun '{1}', which does not follow the enforced " +
183+
"naming convention of using a singular noun for a cmdlet name.",
184+
cmdlet.Name, cmdlet.NounName),
185+
remediation: "Consider using a singular noun for the cmdlet name.");
186+
}
187+
188+
foreach (var parameter in cmdlet.GetParametersWithPluralNoun())
189+
{
190+
issueLogger.LogSignatureIssue(
191+
cmdlet: cmdlet,
192+
severity: 1,
193+
problemId: SignatureProblemId.ParameterWithPluralNoun,
194+
description:
195+
string.Format(
196+
"Parameter {0} of cmdlet {1} does not follow the enforced " +
197+
"naming convention of using a singular noun for a parameter name.",
198+
parameter.Name, cmdlet.Name),
199+
remediation: "Consider using a singular noun for the parameter name.");
200+
}
173201
}
174202

175203
AppDomain.Unload(_appDomain);

tools/StaticAnalysis/StaticAnalysis.Test/SignatureVerifierTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,5 +191,76 @@ public void CmdletWithUnapprovedVerb()
191191
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.CmdletWithUnapprovedVerb)).SingleOrDefault<int>().Equals(SignatureProblemId.CmdletWithUnapprovedVerb));
192192
}
193193
#endregion
194+
195+
#region CmdletWithPluralNoun
196+
[Fact]
197+
[Trait(Category.AcceptanceType, Category.CheckIn)]
198+
public void CmdletWithSingularNoun()
199+
{
200+
cmdletSignatureVerifier.Analyze(
201+
new List<string> { _testCmdletDirPath },
202+
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
203+
(cmdletName) => cmdletName.Equals("Get-SampleKey", StringComparison.OrdinalIgnoreCase));
204+
205+
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
206+
Assert.Equal(0, testReport.ProblemIdList.Count);
207+
}
208+
209+
[Fact]
210+
[Trait(Category.AcceptanceType, Category.CheckIn)]
211+
public void CmdletWithPluralNoun()
212+
{
213+
cmdletSignatureVerifier.Analyze(
214+
new List<string> { _testCmdletDirPath },
215+
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
216+
(cmdletName) => cmdletName.Equals("Get-SampleKeys", StringComparison.OrdinalIgnoreCase));
217+
218+
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
219+
Assert.Equal(1, testReport.ProblemIdList.Count);
220+
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.CmdletWithPluralNoun)).SingleOrDefault<int>().Equals(SignatureProblemId.CmdletWithPluralNoun));
221+
}
222+
#endregion
223+
224+
#region ParameterWithPluralNoun
225+
[Fact]
226+
[Trait(Category.AcceptanceType, Category.CheckIn)]
227+
public void ParameterWithSingularNoun()
228+
{
229+
cmdletSignatureVerifier.Analyze(
230+
new List<string> { _testCmdletDirPath },
231+
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
232+
(cmdletName) => cmdletName.Equals("Get-SampleFoo", StringComparison.OrdinalIgnoreCase));
233+
234+
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
235+
Assert.Equal(0, testReport.ProblemIdList.Count);
236+
}
237+
238+
[Fact]
239+
[Trait(Category.AcceptanceType, Category.CheckIn)]
240+
public void ParameterWithPluralNoun()
241+
{
242+
cmdletSignatureVerifier.Analyze(
243+
new List<string> { _testCmdletDirPath },
244+
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
245+
(cmdletName) => cmdletName.Equals("Get-SampleBar", StringComparison.OrdinalIgnoreCase));
246+
247+
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
248+
Assert.Equal(1, testReport.ProblemIdList.Count);
249+
Assert.True(testReport.ProblemIdList.Where<int>((problemId) => problemId.Equals(SignatureProblemId.ParameterWithPluralNoun)).SingleOrDefault<int>().Equals(SignatureProblemId.ParameterWithPluralNoun));
250+
}
251+
252+
[Fact]
253+
[Trait(Category.AcceptanceType, Category.CheckIn)]
254+
public void CmdletAndParameterWithSingularNounInList()
255+
{
256+
cmdletSignatureVerifier.Analyze(
257+
new List<string> { _testCmdletDirPath },
258+
((dirList) => { return new List<string> { _testCmdletDirPath }; }),
259+
(cmdletName) => cmdletName.Equals("Get-SampleValue", StringComparison.OrdinalIgnoreCase));
260+
261+
AnalysisReport testReport = cmdletSignatureVerifier.GetAnalysisReport();
262+
Assert.Equal(0, testReport.ProblemIdList.Count);
263+
}
264+
#endregion
194265
}
195266
}

tools/StaticAnalysis/StaticAnalysis.Test/TestCmdlets/SignatureVerifier_Cmdlet.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,123 @@ protected override void BeginProcessing()
270270
}
271271
}
272272
}
273+
#endregion
274+
275+
#region CmdletWithPluralNoun
276+
namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletWithSingularNoun
277+
{
278+
using System.Management.Automation;
279+
280+
/// <summary>
281+
/// Verify if a cmdlet has a singular noun in its name.
282+
/// </summary>
283+
[Cmdlet(VerbsCommon.Get, "SampleKey")]
284+
public class CmdletGetSampleKey : Cmdlet
285+
{
286+
/// <summary>
287+
/// Begin processing the cmdlet.
288+
/// </summary>
289+
protected override void BeginProcessing()
290+
{
291+
WriteObject("Get-SampleKey BeginProcessing()");
292+
WriteInformation("Info", null);
293+
}
294+
}
295+
}
296+
297+
namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletWithPluralNoun
298+
{
299+
using System.Management.Automation;
300+
301+
/// <summary>
302+
/// Verify if a cmdlet has a plural noun in its name.
303+
/// </summary>
304+
[Cmdlet("Get", "SampleKeys")]
305+
public class CmdletGetSampleKeys : Cmdlet
306+
{
307+
/// <summary>
308+
/// Begin processing the cmdlet.
309+
/// </summary>
310+
protected override void BeginProcessing()
311+
{
312+
WriteObject("Get-SampleKeys BeginProcessing()");
313+
WriteInformation("Info", null);
314+
}
315+
}
316+
}
317+
#endregion
318+
319+
#region ParameterWithPluralNoun
320+
namespace StaticAnalysis.Test.CmdletTest.Signature.ParameterWithSingularNoun
321+
{
322+
using System.Management.Automation;
323+
324+
/// <summary>
325+
/// Verify if a parameter has a singular noun in its name.
326+
/// </summary>
327+
[Cmdlet(VerbsCommon.Get, "SampleFoo")]
328+
public class CmdletGetSampleFoo : Cmdlet
329+
{
330+
[Parameter(Mandatory = false)]
331+
public string Foo { get; set; }
332+
333+
/// <summary>
334+
/// Begin processing the cmdlet.
335+
/// </summary>
336+
protected override void BeginProcessing()
337+
{
338+
WriteObject("Get-SampleFoo BeginProcessing()");
339+
WriteInformation("Info", null);
340+
}
341+
}
342+
}
343+
344+
namespace StaticAnalysis.Test.CmdletTest.Signature.ParameterWithPluralNoun
345+
{
346+
using System.Management.Automation;
347+
348+
/// <summary>
349+
/// Verify if a parameter has a plural noun in its name.
350+
/// </summary>
351+
[Cmdlet("Get", "SampleBar")]
352+
public class CmdletGetSampleBar : Cmdlet
353+
{
354+
[Parameter(Mandatory = false)]
355+
public string Bars { get; set; }
356+
357+
/// <summary>
358+
/// Begin processing the cmdlet.
359+
/// </summary>
360+
protected override void BeginProcessing()
361+
{
362+
WriteObject("Get-SampleBar BeginProcessing()");
363+
WriteInformation("Info", null);
364+
}
365+
}
366+
}
367+
368+
namespace StaticAnalysis.Test.CmdletTest.Signature.CmdletAndParameterWithSingularNounInList
369+
{
370+
using System.Management.Automation;
371+
372+
/// <summary>
373+
/// Verify if a cmdlet and parameter have a singular noun in the list of
374+
/// accepted nouns ending with "s".
375+
/// </summary>
376+
[Cmdlet("Get", "SampleAddress")]
377+
public class CmdletGetSampleAddress : Cmdlet
378+
{
379+
[Parameter(Mandatory = false)]
380+
public string Address { get; set; }
381+
382+
/// <summary>
383+
/// Begin processing the cmdlet.
384+
/// </summary>
385+
protected override void BeginProcessing()
386+
{
387+
WriteObject("Get-SampleAddress BeginProcessing()");
388+
WriteInformation("Info", null);
389+
}
390+
}
391+
}
273392
#endregion

0 commit comments

Comments
 (0)