15
15
using System . Linq ;
16
16
using System . Management . Automation . Language ;
17
17
using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
18
+ #if CORECLR
19
+ using Pluralize . NET ;
20
+ #else
18
21
using System . ComponentModel . Composition ;
22
+ using System . Data . Entity . Design . PluralizationServices ;
23
+ #endif
19
24
using System . Globalization ;
20
- using System . Text . RegularExpressions ;
21
25
22
26
namespace Microsoft . Windows . PowerShell . ScriptAnalyzer . BuiltinRules
23
27
{
24
28
/// <summary>
25
29
/// CmdletSingularNoun: Analyzes scripts to check that all defined cmdlets use singular nouns.
26
30
///
27
31
/// </summary>
28
- [ Export ( typeof ( IScriptRule ) ) ]
29
- public class CmdletSingularNoun : IScriptRule {
32
+ #if ! CORECLR
33
+ [ Export ( typeof ( IScriptRule ) ) ]
34
+ #endif
35
+ public class CmdletSingularNoun : IScriptRule
36
+ {
30
37
31
38
private readonly string [ ] nounAllowList =
32
39
{
@@ -39,46 +46,49 @@ public class CmdletSingularNoun : IScriptRule {
39
46
/// <param name="ast"></param>
40
47
/// <param name="fileName"></param>
41
48
/// <returns></returns>
42
- public IEnumerable < DiagnosticRecord > AnalyzeScript ( Ast ast , string fileName ) {
49
+ public IEnumerable < DiagnosticRecord > AnalyzeScript ( Ast ast , string fileName )
50
+ {
43
51
if ( ast == null ) throw new ArgumentNullException ( Strings . NullCommandInfoError ) ;
44
52
45
53
IEnumerable < Ast > funcAsts = ast . FindAll ( item => item is FunctionDefinitionAst , true ) ;
46
54
47
- char [ ] funcSeperator = { '-' } ;
48
- string [ ] funcNamePieces = new string [ 2 ] ;
55
+ var pluralizer = new PluralizerProxy ( ) ;
49
56
50
57
foreach ( FunctionDefinitionAst funcAst in funcAsts )
51
58
{
52
- if ( funcAst . Name != null && funcAst . Name . Contains ( '-' ) )
59
+ if ( funcAst . Name == null || ! funcAst . Name . Contains ( '-' ) )
60
+ {
61
+ continue ;
62
+ }
63
+
64
+ string noun = GetLastWordInCmdlet ( funcAst . Name ) ;
65
+
66
+ if ( noun is null )
53
67
{
54
- funcNamePieces = funcAst . Name . Split ( funcSeperator ) ;
55
- String nounPart = funcNamePieces [ 1 ] ;
56
-
57
- // Convert the noun part of the function into a series of space delimited words
58
- // This helps the PluralizationService to provide an accurate determination about the plurality of the string
59
- nounPart = SplitCamelCaseString ( nounPart ) ;
60
- var words = nounPart . Split ( new char [ ] { ' ' } ) ;
61
- var noun = words . LastOrDefault ( ) ;
62
- if ( noun == null )
68
+ continue ;
69
+ }
70
+
71
+ if ( pluralizer . CanOnlyBePlural ( noun ) )
72
+ {
73
+ if ( nounAllowList . Contains ( noun , StringComparer . OrdinalIgnoreCase ) )
63
74
{
64
75
continue ;
65
76
}
66
- var ps = System . Data . Entity . Design . PluralizationServices . PluralizationService . CreateService ( CultureInfo . GetCultureInfo ( "en-us" ) ) ;
67
77
68
- if ( ! ps . IsSingular ( noun ) && ps . IsPlural ( noun ) )
78
+ IScriptExtent extent = Helper . Instance . GetScriptExtentForFunctionName ( funcAst ) ;
79
+
80
+ if ( extent is null )
69
81
{
70
- IScriptExtent extent = Helper . Instance . GetScriptExtentForFunctionName ( funcAst ) ;
71
- if ( nounAllowList . Contains ( noun , StringComparer . OrdinalIgnoreCase ) )
72
- {
73
- continue ;
74
- }
75
- if ( null == extent )
76
- {
77
- extent = funcAst . Extent ;
78
- }
79
- yield return new DiagnosticRecord ( string . Format ( CultureInfo . CurrentCulture , Strings . UseSingularNounsError , funcAst . Name ) ,
80
- extent , GetName ( ) , DiagnosticSeverity . Warning , fileName ) ;
82
+ extent = funcAst . Extent ;
81
83
}
84
+
85
+ yield return new DiagnosticRecord (
86
+ string . Format ( CultureInfo . CurrentCulture , Strings . UseSingularNounsError , funcAst . Name ) ,
87
+ extent ,
88
+ GetName ( ) ,
89
+ DiagnosticSeverity . Warning ,
90
+ fileName ,
91
+ suggestedCorrections : new CorrectionExtent [ ] { GetCorrection ( pluralizer , extent , funcAst . Name , noun ) } ) ;
82
92
}
83
93
}
84
94
@@ -106,7 +116,8 @@ public string GetCommonName()
106
116
/// GetDescription: Retrieves the description of this rule.
107
117
/// </summary>
108
118
/// <returns>The description of this rule</returns>
109
- public string GetDescription ( ) {
119
+ public string GetDescription ( )
120
+ {
110
121
return string . Format ( CultureInfo . CurrentCulture , Strings . UseSingularNounsDescription ) ;
111
122
}
112
123
@@ -135,18 +146,77 @@ public string GetSourceName()
135
146
return string . Format ( CultureInfo . CurrentCulture , Strings . SourceName ) ;
136
147
}
137
148
149
+ private CorrectionExtent GetCorrection ( PluralizerProxy pluralizer , IScriptExtent extent , string commandName , string noun )
150
+ {
151
+ string singularNoun = pluralizer . Singularize ( noun ) ;
152
+ string newCommandName = commandName . Substring ( 0 , commandName . Length - noun . Length ) + singularNoun ;
153
+ return new CorrectionExtent ( extent , newCommandName , extent . File , $ "Singularized correction of '{ extent . Text } '") ;
154
+ }
155
+
138
156
/// <summary>
139
- /// SplitCamelCaseString: Splits a Camel Case'd string into individual words with space delimited
157
+ /// Gets the last word in a standard syntax, CamelCase cmdlet.
158
+ /// If the cmdlet name is non-standard, returns null.
140
159
/// </summary>
141
- private string SplitCamelCaseString ( string input )
160
+ private string GetLastWordInCmdlet ( string cmdletName )
142
161
{
143
- if ( String . IsNullOrEmpty ( input ) )
162
+ if ( string . IsNullOrEmpty ( cmdletName ) )
144
163
{
145
- return String . Empty ;
164
+ return null ;
146
165
}
147
166
148
- return Regex . Replace ( input , "([A-Z])" , " $1" , RegexOptions . Compiled ) . Trim ( ) ;
167
+ // Cmdlet doesn't use CamelCase, so assume it's something like an initialism that shouldn't be singularized
168
+ if ( ! char . IsLower ( cmdletName [ cmdletName . Length - 1 ] ) )
169
+ {
170
+ return null ;
171
+ }
172
+
173
+ for ( int i = cmdletName . Length - 1 ; i >= 0 ; i -- )
174
+ {
175
+ if ( cmdletName [ i ] == '-' )
176
+ {
177
+ // We got to the dash without seeing a CamelCase word, so nothing to singularize
178
+ return null ;
179
+ }
180
+
181
+ // We just changed from lower case to upper, so we have the end word
182
+ if ( char . IsUpper ( cmdletName [ i ] ) )
183
+ {
184
+ return cmdletName . Substring ( i ) ;
185
+ }
186
+ }
187
+
188
+ // We shouldn't ever get here since we should always eventually hit a '-'
189
+ // But if we do, assume this isn't supported cmdlet name
190
+ return null ;
191
+ }
192
+
193
+ #if CORECLR
194
+ private class PluralizerProxy
195
+ {
196
+ private readonly Pluralizer _pluralizer ;
197
+
198
+ public PluralizerProxy ( )
199
+ {
200
+ _pluralizer = new Pluralizer ( ) ;
201
+ }
202
+
203
+ public bool CanOnlyBePlural ( string noun ) =>
204
+ ! _pluralizer . IsSingular ( noun ) && _pluralizer . IsPlural ( noun ) ;
205
+
206
+ public string Singularize ( string noun ) => _pluralizer . Singularize ( noun ) ;
207
+ }
208
+ #else
209
+ private class PluralizerProxy
210
+ {
211
+ private static readonly PluralizationService s_pluralizationService = PluralizationService . CreateService (
212
+ CultureInfo . GetCultureInfo ( "en-us" ) ) ;
213
+
214
+ public bool CanOnlyBePlural ( string noun ) =>
215
+ ! s_pluralizationService . IsSingular ( noun ) && s_pluralizationService . IsPlural ( noun ) ;
216
+
217
+ public string Singularize ( string noun ) => s_pluralizationService . Singularize ( noun ) ;
149
218
}
219
+ #endif
150
220
}
151
221
152
222
}
0 commit comments