@@ -42,19 +42,27 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
4242#if ! CORECLR
4343 [ Export ( typeof ( IScriptRule ) ) ]
4444#endif
45- public class UseFullyQualifiedCmdletNames : IScriptRule
45+ public class UseFullyQualifiedCmdletNames : ConfigurableRule
4646 {
4747 private ConcurrentDictionary < string , string > resolutionCache = new ConcurrentDictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
4848
4949 internal const string AnalyzerName = "Microsoft.Windows.PowerShell.ScriptAnalyzer" ;
5050
51+ /// <summary>
52+ /// Modules to ignore when applying this rule.
53+ /// Commands from these modules will not be expanded to their fully qualified names.
54+ /// Default is empty array (no modules ignored - all cmdlets are processed).
55+ /// </summary>
56+ [ ConfigurableRuleProperty ( defaultValue : new string [ ] { } ) ]
57+ public string [ ] IgnoredModules { get ; protected set ; }
58+
5159 /// <summary>
5260 /// Analyzes the given ast to find cmdlet invocations that are not fully qualified.
5361 /// </summary>
5462 /// <param name="ast">The script's ast</param>
5563 /// <param name="fileName">The script's file name</param>
5664 /// <returns>The diagnostic results of this rule</returns>
57- public IEnumerable < DiagnosticRecord > AnalyzeScript ( Ast ast , string fileName )
65+ public override IEnumerable < DiagnosticRecord > AnalyzeScript ( Ast ast , string fileName )
5866 {
5967 if ( ast == null )
6068 {
@@ -76,13 +84,17 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
7684 var resolvedCommand = ResolveCommand ( commandName ) ;
7785 if ( resolvedCommand == null )
7886 {
87+ // Cache null results to avoid repeated lookups
88+ resolutionCache [ commandName ] = null ;
7989 continue ;
8090 }
8191
8292 if ( resolvedCommand . CommandType != CommandTypes . Cmdlet &&
8393 resolvedCommand . CommandType != CommandTypes . Function &&
8494 resolvedCommand . CommandType != CommandTypes . Alias )
8595 {
96+ // Cache null results for non-cmdlet/function/alias commands
97+ resolutionCache [ commandName ] = null ;
8698 continue ;
8799 }
88100
@@ -93,6 +105,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
93105 {
94106 if ( aliasInfo . ResolvedCommand == null )
95107 {
108+ resolutionCache [ commandName ] = null ;
96109 continue ;
97110 }
98111
@@ -102,12 +115,36 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
102115
103116 if ( string . IsNullOrEmpty ( moduleName ) || string . IsNullOrEmpty ( actualCmdletName ) )
104117 {
118+ resolutionCache [ commandName ] = null ;
119+ continue ;
120+ }
121+
122+ // Check if the module is in the ignored list
123+ if ( IgnoredModules != null && IgnoredModules . Contains ( moduleName , StringComparer . OrdinalIgnoreCase ) )
124+ {
125+ // Cache null for ignored modules to avoid re-checking
126+ resolutionCache [ commandName ] = null ;
105127 continue ;
106128 }
107129
108130 fullyQualifiedName = $ "{ moduleName } \\ { actualCmdletName } ";
109131 resolutionCache [ commandName ] = fullyQualifiedName ;
110132 }
133+ else
134+ {
135+ // If we have a cached result but it's null/empty, it means we should skip this command
136+ if ( string . IsNullOrEmpty ( fullyQualifiedName ) )
137+ {
138+ continue ;
139+ }
140+
141+ // Re-check ignored modules for cached results (in case IgnoredModules was changed)
142+ var moduleName = fullyQualifiedName . Split ( '\\ ' ) [ 0 ] ;
143+ if ( IgnoredModules != null && IgnoredModules . Contains ( moduleName , StringComparer . OrdinalIgnoreCase ) )
144+ {
145+ continue ;
146+ }
147+ }
111148
112149 var extent = commandAst . CommandElements [ 0 ] . Extent ;
113150
@@ -140,7 +177,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
140177 message ,
141178 extent ,
142179 GetName ( ) ,
143- ( DiagnosticSeverity ) GetSeverity ( ) ,
180+ DiagnosticSeverity . Warning ,
144181 fileName ,
145182 null ,
146183 suggestedCorrections ) ;
@@ -161,7 +198,7 @@ private CommandInfo ResolveCommand(string commandName)
161198 /// Retrieves the localized name of this rule.
162199 /// </summary>
163200 /// <returns>The localized name of this rule</returns>
164- public string GetName ( )
201+ public override string GetName ( )
165202 {
166203 return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , GetSourceName ( ) , Strings . UseFullyQualifiedCmdletNamesName ) ;
167204 }
@@ -170,7 +207,7 @@ public string GetName()
170207 /// Retrieves the common name of this rule.
171208 /// </summary>
172209 /// <returns>The common name of this rule</returns>
173- public string GetCommonName ( )
210+ public override string GetCommonName ( )
174211 {
175212 return string . Format ( CultureInfo . CurrentCulture , Strings . UseFullyQualifiedCmdletNamesCommonName ) ;
176213 }
@@ -179,7 +216,7 @@ public string GetCommonName()
179216 /// Retrieves the localized description of this rule.
180217 /// </summary>
181218 /// <returns>The localized description of this rule</returns>
182- public string GetDescription ( )
219+ public override string GetDescription ( )
183220 {
184221 return string . Format ( CultureInfo . CurrentCulture , Strings . UseFullyQualifiedCmdletNamesDescription ) ;
185222 }
@@ -188,7 +225,7 @@ public string GetDescription()
188225 /// Retrieves the source type of this rule.
189226 /// </summary>
190227 /// <returns>The source type of this rule</returns>
191- public SourceType GetSourceType ( )
228+ public override SourceType GetSourceType ( )
192229 {
193230 return SourceType . Builtin ;
194231 }
@@ -197,7 +234,7 @@ public SourceType GetSourceType()
197234 /// Retrieves the source name of this rule.
198235 /// </summary>
199236 /// <returns>The source name of this rule</returns>
200- public string GetSourceName ( )
237+ public override string GetSourceName ( )
201238 {
202239 return "PS" ;
203240 }
@@ -206,7 +243,7 @@ public string GetSourceName()
206243 /// Retrieves the severity of this rule.
207244 /// </summary>
208245 /// <returns>The severity of this rule</returns>
209- public RuleSeverity GetSeverity ( )
246+ public override RuleSeverity GetSeverity ( )
210247 {
211248 return RuleSeverity . Warning ;
212249 }
0 commit comments