Skip to content

Commit 47150bf

Browse files
Robert HoltRobert Holt
Robert Holt
authored and
Robert Holt
committed
Update SingularNouns rule approach
1 parent c2365ee commit 47150bf

File tree

2 files changed

+89
-41
lines changed

2 files changed

+89
-41
lines changed

Rules/UseSingularNouns.cs

Lines changed: 88 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
using System.Linq;
1616
using System.Management.Automation.Language;
1717
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
18-
#if !CORECLR
19-
using System.ComponentModel.Composition;
20-
#else
18+
#if CORECLR
2119
using Pluralize.NET;
20+
#else
21+
using System.ComponentModel.Composition;
2222
#endif
2323
using System.Globalization;
2424
using System.Text.RegularExpressions;
@@ -55,48 +55,42 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
5555
char[] funcSeperator = { '-' };
5656
string[] funcNamePieces = new string[2];
5757

58-
#if !CORECLR
59-
var usCultureInfo = CultureInfo.GetCultureInfo("en-us");
60-
var pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(usCultureInfo);
61-
#else
62-
var pluralizationService = new Pluralizer();
63-
#endif
58+
var pluralizer = new PluralizerProxy();
6459

6560
foreach (FunctionDefinitionAst funcAst in funcAsts)
6661
{
67-
if (funcAst.Name != null && funcAst.Name.Contains('-'))
62+
if (funcAst.Name == null || !funcAst.Name.Contains('-'))
63+
{
64+
continue;
65+
}
66+
67+
string noun = GetLastWordInCmdlet(funcAst.Name);
68+
69+
if (noun is null)
6870
{
69-
funcNamePieces = funcAst.Name.Split(funcSeperator);
70-
String nounPart = funcNamePieces[1];
71-
72-
// Convert the noun part of the function into a series of space delimited words
73-
// This helps the PluralizationService to provide an accurate determination about the plurality of the string
74-
nounPart = SplitCamelCaseString(nounPart);
75-
var words = nounPart.Split(new char[] { ' ' });
76-
var noun = words.LastOrDefault();
77-
if (noun == null)
71+
continue;
72+
}
73+
74+
if (pluralizer.CanOnlyBePlural(noun))
75+
{
76+
IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst);
77+
78+
if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase))
7879
{
7980
continue;
8081
}
8182

82-
#if !CORECLR
83-
if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun))
84-
#else
85-
if (!pluralizationService.IsSingular(noun) && pluralizationService.IsPlural(noun))
86-
#endif
83+
if (extent is null)
8784
{
88-
IScriptExtent extent = Helper.Instance.GetScriptExtentForFunctionName(funcAst);
89-
if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase))
90-
{
91-
continue;
92-
}
93-
if (null == extent)
94-
{
95-
extent = funcAst.Extent;
96-
}
97-
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsError, funcAst.Name),
98-
extent, GetName(), DiagnosticSeverity.Warning, fileName);
85+
extent = funcAst.Extent;
9986
}
87+
88+
yield return new DiagnosticRecord(
89+
string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsError, funcAst.Name),
90+
extent,
91+
GetName(),
92+
DiagnosticSeverity.Warning,
93+
fileName);
10094
}
10195
}
10296

@@ -155,17 +149,71 @@ public string GetSourceName()
155149
}
156150

157151
/// <summary>
158-
/// SplitCamelCaseString: Splits a Camel Case'd string into individual words with space delimited
152+
/// Gets the last word in a standard syntax, CamelCase cmdlet.
153+
/// If the cmdlet name is non-standard, returns null.
159154
/// </summary>
160-
private string SplitCamelCaseString(string input)
155+
private string GetLastWordInCmdlet(string cmdletName)
156+
{
157+
if (string.IsNullOrEmpty(cmdletName))
158+
{
159+
return null;
160+
}
161+
162+
// Cmdlet doesn't use CamelCase, so assume it's something like an initialism that shouldn't be singularized
163+
if (!char.IsLower(cmdletName[^1]))
164+
{
165+
return null;
166+
}
167+
168+
for (int i = cmdletName.Length - 1; i >= 0; i--)
169+
{
170+
if (cmdletName[i] == '-')
171+
{
172+
// Cmdlet name ends in '-' -- we give up
173+
if (i == cmdletName.Length - 1)
174+
{
175+
return null;
176+
}
177+
178+
// Return everything after the dash
179+
return cmdletName.Substring(i + 1);
180+
}
181+
182+
// We just changed from lower case to upper, so we have the end word
183+
if (char.IsUpper(cmdletName[i]))
184+
{
185+
return cmdletName.Substring(i);
186+
}
187+
}
188+
189+
// We shouldn't ever get here since we should always eventually hit a '-'
190+
// But if we do, assume this isn't supported cmdlet name
191+
return null;
192+
}
193+
194+
#if CoreCLR
195+
private class PluralizerProxy
161196
{
162-
if (String.IsNullOrEmpty(input))
197+
private readonly Pluralizer _pluralizer;
198+
199+
public PluralizerProxy()
163200
{
164-
return String.Empty;
201+
_pluralizer = new Pluralizer();
165202
}
166203

167-
return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim();
204+
public bool CanOnlyBePlural(string noun) =>
205+
!_pluralizer.IsSingular(noun) && _pluralizer.IsPlural(noun);
168206
}
207+
#else
208+
private class PluralizerProxy
209+
{
210+
private static readonly PluralizationService s_pluralizationService = System.Data.Entity.Design.PluralizationServices.PluralizationService.CreateService(
211+
CultureInfo.GetCultureInfo('en-us'));
212+
213+
public bool CanOnlyBePlural(string noun) =>
214+
!s_pluralizationService.IsSingular(noun) && s_pluralizationService.IsPlural(noun);
215+
}
216+
#endif
169217
}
170218

171219
}

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Describe "Test Name parameters" {
6464
It "get Rules with no parameters supplied" {
6565
$defaultRules = Get-ScriptAnalyzerRule
6666
$expectedNumRules = 65
67-
if (($PSVersionTable.PSVersion.Major -eq 3) -or ($PSVersionTable.PSVersion.Major -eq 4))
67+
if ($PSVersionTable.PSVersion.Major -le 4)
6868
{
6969
# for PSv3 PSAvoidGlobalAliases is not shipped because
7070
# it uses StaticParameterBinder.BindCommand which is

0 commit comments

Comments
 (0)