Skip to content

Commit 5866510

Browse files
author
quoctruong
committed
Initial commit for ScriptDefinition
1 parent 3929d66 commit 5866510

24 files changed

+440
-51
lines changed

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,19 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands
3131
/// <summary>
3232
/// InvokeScriptAnalyzerCommand: Cmdlet to statically check PowerShell scripts.
3333
/// </summary>
34-
[Cmdlet(VerbsLifecycle.Invoke, "ScriptAnalyzer", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")]
34+
[Cmdlet(VerbsLifecycle.Invoke,
35+
"ScriptAnalyzer",
36+
DefaultParameterSetName="File",
37+
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")]
3538
public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter
3639
{
3740
#region Parameters
3841
/// <summary>
3942
/// Path: The path to the file or folder to invoke PSScriptAnalyzer on.
4043
/// </summary>
41-
[Parameter(Position = 0, Mandatory = true)]
44+
[Parameter(Position = 0,
45+
ParameterSetName = "File",
46+
Mandatory = true)]
4247
[ValidateNotNull]
4348
[Alias("PSPath")]
4449
public string Path
@@ -48,6 +53,20 @@ public string Path
4853
}
4954
private string path;
5055

56+
/// <summary>
57+
/// ScriptDefinition: a script definition in the form of a string to run rules on.
58+
/// </summary>
59+
[Parameter(Position = 0,
60+
ParameterSetName = "ScriptDefinition",
61+
Mandatory = true)]
62+
[ValidateNotNull]
63+
public string ScriptDefinition
64+
{
65+
get { return scriptDefinition; }
66+
set { scriptDefinition = value; }
67+
}
68+
private string scriptDefinition;
69+
5170
/// <summary>
5271
/// CustomRulePath: The path to the file containing custom rules to run.
5372
/// </summary>
@@ -160,22 +179,37 @@ protected override void BeginProcessing()
160179
/// </summary>
161180
protected override void ProcessRecord()
162181
{
163-
// throws Item Not Found Exception
164-
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
165-
foreach (PathInfo p in paths)
182+
if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase))
166183
{
167-
ProcessPath(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path));
184+
// throws Item Not Found Exception
185+
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
186+
foreach (PathInfo p in paths)
187+
{
188+
ProcessPathOrScriptDefinition(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path));
189+
}
190+
}
191+
else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase))
192+
{
193+
ProcessPathOrScriptDefinition(scriptDefinition);
168194
}
169195
}
170196

171197
#endregion
172198

173199
#region Methods
174200

175-
private void ProcessPath(string path)
201+
private void ProcessPathOrScriptDefinition(string pathOrScriptDefinition)
176202
{
177-
IEnumerable<DiagnosticRecord> diagnosticsList =
178-
ScriptAnalyzer.Instance.AnalyzePath(path, this.recurse);
203+
IEnumerable<DiagnosticRecord> diagnosticsList = Enumerable.Empty<DiagnosticRecord>();
204+
205+
if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase))
206+
{
207+
diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(pathOrScriptDefinition);
208+
}
209+
else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase))
210+
{
211+
diagnosticsList = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(pathOrScriptDefinition);
212+
}
179213

180214
//Output through loggers
181215
foreach (ILogger logger in ScriptAnalyzer.Instance.Loggers)

Engine/Generic/DiagnosticRecord.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,16 @@ public string ScriptName
7070
{
7171
get { return scriptName; }
7272
//Trim down to the leaf element of the filePath and pass it to Diagnostic Record
73-
set { scriptName = System.IO.Path.GetFileName(value); }
73+
set {
74+
if (!string.IsNullOrWhiteSpace(value))
75+
{
76+
scriptName = System.IO.Path.GetFileName(value);
77+
}
78+
else
79+
{
80+
scriptName = string.Empty;
81+
}
82+
}
7483
}
7584

7685
/// <summary>

Engine/Generic/RuleSuppression.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,15 @@ public RuleSuppression(AttributeAst attrAst, int start, int end)
294294

295295
if (!String.IsNullOrWhiteSpace(Error))
296296
{
297-
Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, StartAttributeLine,
298-
System.IO.Path.GetFileName(attrAst.Extent.File), Error);
297+
if (String.IsNullOrWhiteSpace(attrAst.Extent.File))
298+
{
299+
Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, StartAttributeLine, Error);
300+
}
301+
else
302+
{
303+
Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, StartAttributeLine,
304+
System.IO.Path.GetFileName(attrAst.Extent.File), Error);
305+
}
299306
}
300307
}
301308

@@ -372,8 +379,17 @@ public static List<RuleSuppression> GetSuppressions(IEnumerable<AttributeAst> at
372379
{
373380
if (targetAsts.Count() == 0)
374381
{
375-
ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSupp.StartAttributeLine,
376-
System.IO.Path.GetFileName(scopeAst.Extent.File), String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope));
382+
if (String.IsNullOrWhiteSpace(scopeAst.Extent.File))
383+
{
384+
ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, ruleSupp.StartAttributeLine,
385+
String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope));
386+
}
387+
else
388+
{
389+
ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSupp.StartAttributeLine,
390+
System.IO.Path.GetFileName(scopeAst.Extent.File), String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope));
391+
}
392+
377393
result.Add(ruleSupp);
378394
continue;
379395
}

Engine/Helper.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,8 +956,18 @@ public Tuple<List<SuppressedRecord>, List<DiagnosticRecord>> SuppressRule(string
956956
// If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
957957
if (!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && suppressionCount == 0)
958958
{
959-
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine,
960-
System.IO.Path.GetFileName(diagnostics.First().Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
959+
// checks whether are given a string or a file path
960+
if (String.IsNullOrWhiteSpace(diagnostics.First().Extent.File))
961+
{
962+
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, ruleSuppression.StartAttributeLine,
963+
String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
964+
}
965+
else
966+
{
967+
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine,
968+
System.IO.Path.GetFileName(diagnostics.First().Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
969+
}
970+
961971
this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression));
962972
}
963973
}

Engine/ScriptAnalyzer.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ public Dictionary<string, List<string>> CheckRuleExtension(string[] path, PathIn
883883
}
884884

885885
#endregion
886-
886+
887887

888888
/// <summary>
889889
/// Analyzes a script file or a directory containing script files.
@@ -924,6 +924,49 @@ public IEnumerable<DiagnosticRecord> AnalyzePath(string path, bool searchRecursi
924924
}
925925
}
926926

927+
/// <summary>
928+
/// Analyzes a script definition in the form of a string input
929+
/// </summary>
930+
/// <param name="scriptDefinition">The script to be analyzed</param>
931+
/// <returns></returns>
932+
public IEnumerable<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefinition)
933+
{
934+
ScriptBlockAst scriptAst = null;
935+
Token[] scriptTokens = null;
936+
ParseError[] errors = null;
937+
938+
this.outputWriter.WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseScriptDefinitionMessage));
939+
940+
try
941+
{
942+
scriptAst = Parser.ParseInput(scriptDefinition, out scriptTokens, out errors);
943+
}
944+
catch (Exception e)
945+
{
946+
this.outputWriter.WriteWarning(e.ToString());
947+
return null;
948+
}
949+
950+
if (errors != null && errors.Length > 0)
951+
{
952+
foreach (ParseError error in errors)
953+
{
954+
string parseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParseErrorFormatForScriptDefinition, error.Message.TrimEnd('.'), error.Extent.StartLineNumber, error.Extent.StartColumnNumber);
955+
this.outputWriter.WriteError(new ErrorRecord(new ParseException(parseErrorMessage), parseErrorMessage, ErrorCategory.ParserError, error.ErrorId));
956+
}
957+
}
958+
959+
if (errors != null && errors.Length > 10)
960+
{
961+
string manyParseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParserErrorMessageForScriptDefinition);
962+
this.outputWriter.WriteError(new ErrorRecord(new ParseException(manyParseErrorMessage), manyParseErrorMessage, ErrorCategory.ParserError, scriptDefinition));
963+
964+
return new List<DiagnosticRecord>();
965+
}
966+
967+
return this.AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty);
968+
}
969+
927970
private void BuildScriptPathList(
928971
string path,
929972
bool searchRecursively,
@@ -1038,7 +1081,9 @@ private IEnumerable<DiagnosticRecord> AnalyzeFile(string filePath)
10381081
/// </summary>
10391082
/// <param name="scriptAst">The ScriptBlockAst from the parsed script.</param>
10401083
/// <param name="scriptTokens">The tokens found in the script.</param>
1041-
/// <param name="filePath">The path to the file that was parsed.</param>
1084+
/// <param name="filePath">The path to the file that was parsed.
1085+
/// If AnalyzeSyntaxTree is called from an ast that we get from ParseInput, then this field will be String.Empty
1086+
/// </param>
10421087
/// <returns>An enumeration of DiagnosticRecords that were found by rules.</returns>
10431088
public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
10441089
ScriptBlockAst scriptAst,
@@ -1052,8 +1097,12 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
10521097

10531098
// Use a List of KVP rather than dictionary, since for a script containing inline functions with same signature, keys clash
10541099
List<KeyValuePair<CommandInfo, IScriptExtent>> cmdInfoTable = new List<KeyValuePair<CommandInfo, IScriptExtent>>();
1100+
bool filePathIsNullOrWhiteSpace = String.IsNullOrWhiteSpace(filePath);
1101+
filePath = filePathIsNullOrWhiteSpace ? String.Empty : filePath;
10551102

1056-
bool helpFile = (scriptAst == null) && Helper.Instance.IsHelpFile(filePath);
1103+
// check whether the script we are analyzing is a help file or not.
1104+
// this step is not applicable for scriptdefinition, whose filepath is null
1105+
bool helpFile = (scriptAst == null) && (!filePathIsNullOrWhiteSpace) && Helper.Instance.IsHelpFile(filePath);
10571106

10581107
if (!helpFile)
10591108
{
@@ -1083,7 +1132,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
10831132

10841133
#region Run ScriptRules
10851134
//Trim down to the leaf element of the filePath and pass it to Diagnostic Record
1086-
string fileName = System.IO.Path.GetFileName(filePath);
1135+
string fileName = filePathIsNullOrWhiteSpace ? String.Empty : System.IO.Path.GetFileName(filePath);
10871136

10881137
if (this.ScriptRules != null)
10891138
{
@@ -1285,7 +1334,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
12851334
}
12861335

12871336
// Check if the supplied artifact is indeed part of the DSC resource
1288-
if (Helper.Instance.IsDscResourceModule(filePath))
1337+
if (!filePathIsNullOrWhiteSpace && Helper.Instance.IsDscResourceModule(filePath))
12891338
{
12901339
// Run all DSC Rules
12911340
foreach (IDSCResourceRule dscResourceRule in this.DSCResourceRules)

Engine/Strings.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Engine/Strings.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,16 @@
204204
<data name="InvalidProfile" xml:space="preserve">
205205
<value>Profile file '{0}' is invalid because it does not contain a hashtable.</value>
206206
</data>
207+
<data name="ParseErrorFormatForScriptDefinition" xml:space="preserve">
208+
<value>Parse error in script definition: {0} at line {1} column {2}.</value>
209+
</data>
210+
<data name="ParserErrorMessageForScriptDefinition" xml:space="preserve">
211+
<value>There are too many parser errors in the script definition. Please correct them before running ScriptAnalyzer.</value>
212+
</data>
213+
<data name="RuleSuppressionErrorFormatScriptDefinition" xml:space="preserve">
214+
<value>Suppression Message Attribute error at line {0} in script definition : {1}</value>
215+
</data>
216+
<data name="VerboseScriptDefinitionMessage" xml:space="preserve">
217+
<value>Analyzing Script Definition.</value>
218+
</data>
207219
</root>

PSScriptAnalyzer.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 2013
4-
VisualStudioVersion = 12.0.21005.1
4+
VisualStudioVersion = 12.0.31101.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptAnalyzerEngine", "Engine\ScriptAnalyzerEngine.csproj", "{F4BDE3D0-3EEF-4157-8A3E-722DF7ADEF60}"
77
EndProject

Rules/AvoidDefaultTrueValueSwitchParameter.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,18 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
4242
if (paramAst.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(System.Management.Automation.SwitchParameter))
4343
&& paramAst.DefaultValue != null && String.Equals(paramAst.DefaultValue.Extent.Text, "$true", StringComparison.OrdinalIgnoreCase))
4444
{
45-
yield return new DiagnosticRecord(
46-
String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterError, System.IO.Path.GetFileName(fileName)),
47-
paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
45+
if (String.IsNullOrWhiteSpace(fileName))
46+
{
47+
yield return new DiagnosticRecord(
48+
String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterErrorScriptDefinition),
49+
paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
50+
}
51+
else
52+
{
53+
yield return new DiagnosticRecord(
54+
String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterError, System.IO.Path.GetFileName(fileName)),
55+
paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
56+
}
4857
}
4958
}
5059
}

0 commit comments

Comments
 (0)