1+ // Copyright (c) Microsoft Corporation. All rights reserved.
2+ // Licensed under the MIT License.
3+
4+ using System ;
5+ using System . Collections . Generic ;
6+ using System . Management . Automation . Language ;
7+ using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
8+ #if ! CORECLR
9+ using System . ComponentModel . Composition ;
10+ #endif
11+ using System . Globalization ;
12+
13+ namespace Microsoft . Windows . PowerShell . ScriptAnalyzer . BuiltinRules
14+ {
15+ /// <summary>
16+ /// UseCorrectCasing: Check if cmdlet is cased correctly.
17+ /// </summary>
18+ #if ! CORECLR
19+ [ Export ( typeof ( IScriptRule ) ) ]
20+ #endif
21+ public class UseCorrectCasing : ConfigurableRule
22+ {
23+ /// <summary>
24+ /// AnalyzeScript: Analyze the script to check if cmdlet alias is used.
25+ /// </summary>
26+ public override IEnumerable < DiagnosticRecord > AnalyzeScript ( Ast ast , string fileName )
27+ {
28+ if ( ast == null ) throw new ArgumentNullException ( Strings . NullAstErrorMessage ) ;
29+
30+ IEnumerable < Ast > commandAsts = ast . FindAll ( testAst => testAst is CommandAst , true ) ;
31+
32+ // Iterates all CommandAsts and check the command name.
33+ foreach ( CommandAst commandAst in commandAsts )
34+ {
35+ string commandName = commandAst . GetCommandName ( ) ;
36+
37+ // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}.
38+ // You can also review the remark section in following document,
39+ // MSDN: CommandAst.GetCommandName Method
40+ if ( commandName == null )
41+ {
42+ continue ;
43+ }
44+
45+ var commandInfo = Helper . Instance . GetCommandInfo ( commandName ) ;
46+ if ( commandInfo == null )
47+ {
48+ continue ;
49+ }
50+
51+ var shortName = commandInfo . Name ;
52+ var fullyqualifiedName = $ "{ commandInfo . ModuleName } \\ { shortName } ";
53+ var isFullyQualified = commandName . Equals ( fullyqualifiedName , StringComparison . OrdinalIgnoreCase ) ;
54+ var correctlyCasedCommandName = isFullyQualified ? fullyqualifiedName : shortName ;
55+
56+ if ( ! commandName . Equals ( correctlyCasedCommandName , StringComparison . Ordinal ) )
57+ {
58+ yield return new DiagnosticRecord (
59+ string . Format ( CultureInfo . CurrentCulture , Strings . UseCorrectCasingError , commandName , shortName ) ,
60+ GetCommandExtent ( commandAst ) ,
61+ GetName ( ) ,
62+ DiagnosticSeverity . Warning ,
63+ fileName ,
64+ commandName ,
65+ suggestedCorrections : GetCorrectionExtent ( commandAst , correctlyCasedCommandName ) ) ;
66+ }
67+ }
68+ }
69+
70+ /// <summary>
71+ /// For a command like "gci -path c:", returns the extent of "gci" in the command
72+ /// </summary>
73+ private IScriptExtent GetCommandExtent ( CommandAst commandAst )
74+ {
75+ var cmdName = commandAst . GetCommandName ( ) ;
76+ foreach ( var cmdElement in commandAst . CommandElements )
77+ {
78+ var stringConstExpressinAst = cmdElement as StringConstantExpressionAst ;
79+ if ( stringConstExpressinAst != null )
80+ {
81+ if ( stringConstExpressinAst . Value . Equals ( cmdName ) )
82+ {
83+ return stringConstExpressinAst . Extent ;
84+ }
85+ }
86+ }
87+ return commandAst . Extent ;
88+ }
89+
90+ private IEnumerable < CorrectionExtent > GetCorrectionExtent ( CommandAst commandAst , string correctlyCaseName )
91+ {
92+ var description = string . Format (
93+ CultureInfo . CurrentCulture ,
94+ Strings . UseCorrectCasingDescription ,
95+ correctlyCaseName ,
96+ correctlyCaseName ) ;
97+ var cmdExtent = GetCommandExtent ( commandAst ) ;
98+ var correction = new CorrectionExtent (
99+ cmdExtent . StartLineNumber ,
100+ cmdExtent . EndLineNumber ,
101+ cmdExtent . StartColumnNumber ,
102+ cmdExtent . EndColumnNumber ,
103+ correctlyCaseName ,
104+ commandAst . Extent . File ,
105+ description ) ;
106+ yield return correction ;
107+ }
108+
109+ /// <summary>
110+ /// GetName: Retrieves the name of this rule.
111+ /// </summary>
112+ /// <returns>The name of this rule</returns>
113+ public override string GetName ( )
114+ {
115+ return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , GetSourceName ( ) , Strings . UseCorrectCasingName ) ;
116+ }
117+
118+ /// <summary>
119+ /// GetCommonName: Retrieves the common name of this rule.
120+ /// </summary>
121+ /// <returns>The common name of this rule</returns>
122+ public override string GetCommonName ( )
123+ {
124+ return string . Format ( CultureInfo . CurrentCulture , Strings . UseCorrectCasingCommonName ) ;
125+ }
126+
127+ /// <summary>
128+ /// GetDescription: Retrieves the description of this rule.
129+ /// </summary>
130+ /// <returns>The description of this rule</returns>
131+ public override string GetDescription ( )
132+ {
133+ return string . Format ( CultureInfo . CurrentCulture , Strings . UseCorrectCasingDescription ) ;
134+ }
135+
136+ /// <summary>
137+ /// GetSourceType: Retrieves the type of the rule, Builtin, Managed or Module.
138+ /// </summary>
139+ public override SourceType GetSourceType ( )
140+ {
141+ return SourceType . Builtin ;
142+ }
143+
144+ /// <summary>
145+ /// GetSeverity: Retrieves the severity of the rule: error, warning of information.
146+ /// </summary>
147+ /// <returns></returns>
148+ public override RuleSeverity GetSeverity ( )
149+ {
150+ return RuleSeverity . Information ;
151+ }
152+
153+ /// <summary>
154+ /// GetSourceName: Retrieves the name of the module/assembly the rule is from.
155+ /// </summary>
156+ public override string GetSourceName ( )
157+ {
158+ return string . Format ( CultureInfo . CurrentCulture , Strings . SourceName ) ;
159+ }
160+ }
161+ }
0 commit comments