1717using Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic ;
1818using System . ComponentModel . Composition ;
1919using System . Globalization ;
20+ using System . Text . RegularExpressions ;
2021
2122namespace Microsoft . Windows . PowerShell . ScriptAnalyzer . BuiltinRules
2223{
2324 /// <summary>
24- /// UseManifestExportFields: Run Test Module Manifest to check that no deprecated fields are being used.
25+ /// UseToExportFieldsInManifest: Checks if AliasToExport, CmdletsToExport, FunctionsToExport and VariablesToExport
26+ /// fields do not use wildcards and $null in their entries.
2527 /// </summary>
2628 [ Export ( typeof ( IScriptRule ) ) ]
27- public class UseManifestExportFields : IScriptRule
29+ public class UseToExportFieldsInManifest : IScriptRule
2830 {
2931 /// <summary>
30- /// AnalyzeScript: Run Test Module Manifest to check that no deprecated fields are being used.
32+ /// AnalyzeScript: Analyzes the AST to check if AliasToExport, CmdletsToExport, FunctionsToExport
33+ /// and VariablesToExport fields do not use wildcards and $null in their entries.
3134 /// </summary>
3235 /// <param name="ast">The script's ast</param>
3336 /// <param name="fileName">The script's file name</param>
@@ -44,37 +47,62 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
4447 yield break ;
4548 }
4649
50+ if ( ! IsValidManifest ( ast , fileName ) )
51+ {
52+ yield break ;
53+ }
54+
4755 String [ ] manifestFields = { "FunctionsToExport" , "CmdletsToExport" , "VariablesToExport" , "AliasesToExport" } ;
4856 var hashtableAst = ast . Find ( x => x is HashtableAst , false ) as HashtableAst ;
49-
57+
5058 if ( hashtableAst == null )
51- {
52- //Should we emit a warning if the parser cannot find a hashtable?
59+ {
5360 yield break ;
5461 }
5562
5663 foreach ( String field in manifestFields )
5764 {
5865 IScriptExtent extent ;
59- if ( ! HasAcceptableExportField ( field , hashtableAst , out extent ) && extent != null )
66+ if ( ! HasAcceptableExportField ( field , hashtableAst , ast . Extent . Text , out extent ) && extent != null )
6067 {
6168 yield return new DiagnosticRecord ( GetError ( field ) , extent , GetName ( ) , DiagnosticSeverity . Warning , fileName ) ;
6269 }
6370 }
6471
6572 }
6673
67- private bool HasAcceptableExportField ( string key , HashtableAst hast , out IScriptExtent extent )
74+ /// <summary>
75+ /// Checks if the manifest file is valid.
76+ /// </summary>
77+ /// <param name="ast"></param>
78+ /// <param name="fileName"></param>
79+ /// <returns>A boolean value indicating the validity of the manifest file.</returns>
80+ private bool IsValidManifest ( Ast ast , string fileName )
81+ {
82+ var missingManifestRule = new MissingModuleManifestField ( ) ;
83+ return ! missingManifestRule . AnalyzeScript ( ast , fileName ) . GetEnumerator ( ) . MoveNext ( ) ;
84+
85+ }
86+
87+ /// <summary>
88+ /// Checks if the *ToExport fields are explicitly set to lists, @(...)
89+ /// </summary>
90+ /// <param name="key"></param>
91+ /// <param name="hast"></param>
92+ /// <param name="scriptText"></param>
93+ /// <param name="extent"></param>
94+ /// <returns>A boolean value indicating if the the ToExport fields are explicitly set to lists or not.</returns>
95+ private bool HasAcceptableExportField ( string key , HashtableAst hast , string scriptText , out IScriptExtent extent )
6896 {
6997 extent = null ;
7098 foreach ( var pair in hast . KeyValuePairs )
7199 {
72100 if ( key . Equals ( pair . Item1 . Extent . Text . Trim ( ) , StringComparison . OrdinalIgnoreCase ) )
73101 {
74- var arrayAst = pair . Item2 . Find ( x => x is ArrayLiteralAst , true ) as ArrayLiteralAst ;
102+ var arrayAst = pair . Item2 . Find ( x => x is ArrayLiteralAst || x is ArrayExpressionAst , true ) ;
75103 if ( arrayAst == null )
76104 {
77- extent = GetScriptExtent ( pair ) ;
105+ extent = GetScriptExtent ( pair , scriptText ) ;
78106 return false ;
79107 }
80108 else
@@ -85,23 +113,31 @@ private bool HasAcceptableExportField(string key, HashtableAst hast, out IScript
85113 }
86114 return true ;
87115 }
88-
89-
90- private ScriptExtent GetScriptExtent ( Tuple < ExpressionAst , StatementAst > pair )
116+
117+ /// <summary>
118+ /// Gets the script extent.
119+ /// </summary>
120+ /// <param name="pair"></param>
121+ /// <param name="scriptText"></param>
122+ /// <returns></returns>
123+ private ScriptExtent GetScriptExtent ( Tuple < ExpressionAst , StatementAst > pair , string scriptText )
91124 {
92- return new ScriptExtent ( new ScriptPosition ( pair . Item1 . Extent . StartScriptPosition . File ,
93- pair . Item1 . Extent . StartScriptPosition . LineNumber ,
94- pair . Item1 . Extent . StartScriptPosition . Offset ,
95- pair . Item1 . Extent . StartScriptPosition . Line ) ,
96- new ScriptPosition ( pair . Item2 . Extent . EndScriptPosition . File ,
97- pair . Item2 . Extent . EndScriptPosition . LineNumber ,
98- pair . Item2 . Extent . EndScriptPosition . Offset ,
99- pair . Item2 . Extent . EndScriptPosition . Line ) ) ;
125+ string [ ] scriptLines = Regex . Split ( scriptText , "\r \n |\r |\n " ) ;
126+ return new ScriptExtent ( new ScriptPosition ( pair . Item1 . Extent . File ,
127+ pair . Item1 . Extent . StartLineNumber ,
128+ pair . Item1 . Extent . StartColumnNumber ,
129+ scriptLines [ pair . Item1 . Extent . StartLineNumber - 1 ] ) , //line number begins with 1
130+ new ScriptPosition ( pair . Item2 . Extent . File ,
131+ pair . Item2 . Extent . EndLineNumber ,
132+ pair . Item2 . Extent . EndColumnNumber ,
133+ scriptLines [ pair . Item2 . Extent . EndLineNumber - 1 ] ) ) ; //line number begins with 1
134+
135+
100136 }
101137
102138 public string GetError ( string field )
103139 {
104- return string . Format ( CultureInfo . CurrentCulture , Strings . UseManifestExportFieldsError , field ) ;
140+ return string . Format ( CultureInfo . CurrentCulture , Strings . UseToExportFieldsInManifestError , field ) ;
105141 }
106142
107143 /// <summary>
@@ -110,7 +146,7 @@ public string GetError(string field)
110146 /// <returns>The name of this rule</returns>
111147 public string GetName ( )
112148 {
113- return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , GetSourceName ( ) , Strings . UseManifestExportFieldsName ) ;
149+ return string . Format ( CultureInfo . CurrentCulture , Strings . NameSpaceFormat , GetSourceName ( ) , Strings . UseToExportFieldsInManifestName ) ;
114150 }
115151
116152 /// <summary>
@@ -119,7 +155,7 @@ public string GetName()
119155 /// <returns>The common name of this rule</returns>
120156 public string GetCommonName ( )
121157 {
122- return String . Format ( CultureInfo . CurrentCulture , Strings . UseManifestExportFieldsCommonName ) ;
158+ return String . Format ( CultureInfo . CurrentCulture , Strings . UseToExportFieldsInManifestCommonName ) ;
123159 }
124160
125161 /// <summary>
@@ -128,7 +164,7 @@ public string GetCommonName()
128164 /// <returns>The description of this rule</returns>
129165 public string GetDescription ( )
130166 {
131- return String . Format ( CultureInfo . CurrentCulture , Strings . UseManifestExportFieldsDescription ) ;
167+ return String . Format ( CultureInfo . CurrentCulture , Strings . UseToExportFieldsInManifestDescription ) ;
132168 }
133169
134170 /// <summary>
0 commit comments