Skip to content

Initial commit for ScriptDefinition #357

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 12, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions Engine/Commands/InvokeScriptAnalyzerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,19 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Commands
/// <summary>
/// InvokeScriptAnalyzerCommand: Cmdlet to statically check PowerShell scripts.
/// </summary>
[Cmdlet(VerbsLifecycle.Invoke, "ScriptAnalyzer", HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")]
[Cmdlet(VerbsLifecycle.Invoke,
"ScriptAnalyzer",
DefaultParameterSetName="File",
HelpUri = "http://go.microsoft.com/fwlink/?LinkId=525914")]
public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter
{
#region Parameters
/// <summary>
/// Path: The path to the file or folder to invoke PSScriptAnalyzer on.
/// </summary>
[Parameter(Position = 0, Mandatory = true)]
[Parameter(Position = 0,
ParameterSetName = "File",
Mandatory = true)]
[ValidateNotNull]
[Alias("PSPath")]
public string Path
Expand All @@ -48,6 +53,20 @@ public string Path
}
private string path;

/// <summary>
/// ScriptDefinition: a script definition in the form of a string to run rules on.
/// </summary>
[Parameter(Position = 0,
ParameterSetName = "ScriptDefinition",
Mandatory = true)]
[ValidateNotNull]
public string ScriptDefinition
{
get { return scriptDefinition; }
set { scriptDefinition = value; }
}
private string scriptDefinition;

/// <summary>
/// CustomRulePath: The path to the file containing custom rules to run.
/// </summary>
Expand Down Expand Up @@ -160,22 +179,37 @@ protected override void BeginProcessing()
/// </summary>
protected override void ProcessRecord()
{
// throws Item Not Found Exception
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
foreach (PathInfo p in paths)
if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase))
{
ProcessPath(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path));
// throws Item Not Found Exception
Collection<PathInfo> paths = this.SessionState.Path.GetResolvedPSPathFromPSPath(path);
foreach (PathInfo p in paths)
{
ProcessPathOrScriptDefinition(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(p.Path));
}
}
else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase))
{
ProcessPathOrScriptDefinition(scriptDefinition);
}
}

#endregion

#region Methods

private void ProcessPath(string path)
private void ProcessPathOrScriptDefinition(string pathOrScriptDefinition)
{
IEnumerable<DiagnosticRecord> diagnosticsList =
ScriptAnalyzer.Instance.AnalyzePath(path, this.recurse);
IEnumerable<DiagnosticRecord> diagnosticsList = Enumerable.Empty<DiagnosticRecord>();

if (String.Equals(this.ParameterSetName, "File", StringComparison.OrdinalIgnoreCase))
{
diagnosticsList = ScriptAnalyzer.Instance.AnalyzePath(pathOrScriptDefinition);
}
else if (String.Equals(this.ParameterSetName, "ScriptDefinition", StringComparison.OrdinalIgnoreCase))
{
diagnosticsList = ScriptAnalyzer.Instance.AnalyzeScriptDefinition(pathOrScriptDefinition);
}

//Output through loggers
foreach (ILogger logger in ScriptAnalyzer.Instance.Loggers)
Expand Down
11 changes: 10 additions & 1 deletion Engine/Generic/DiagnosticRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,16 @@ public string ScriptName
{
get { return scriptName; }
//Trim down to the leaf element of the filePath and pass it to Diagnostic Record
set { scriptName = System.IO.Path.GetFileName(value); }
set {
if (!string.IsNullOrWhiteSpace(value))
{
scriptName = System.IO.Path.GetFileName(value);
}
else
{
scriptName = string.Empty;
}
}
}

/// <summary>
Expand Down
24 changes: 20 additions & 4 deletions Engine/Generic/RuleSuppression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,15 @@ public RuleSuppression(AttributeAst attrAst, int start, int end)

if (!String.IsNullOrWhiteSpace(Error))
{
Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, StartAttributeLine,
System.IO.Path.GetFileName(attrAst.Extent.File), Error);
if (String.IsNullOrWhiteSpace(attrAst.Extent.File))
{
Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, StartAttributeLine, Error);
}
else
{
Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, StartAttributeLine,
System.IO.Path.GetFileName(attrAst.Extent.File), Error);
}
}
}

Expand Down Expand Up @@ -372,8 +379,17 @@ public static List<RuleSuppression> GetSuppressions(IEnumerable<AttributeAst> at
{
if (targetAsts.Count() == 0)
{
ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSupp.StartAttributeLine,
System.IO.Path.GetFileName(scopeAst.Extent.File), String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope));
if (String.IsNullOrWhiteSpace(scopeAst.Extent.File))
{
ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, ruleSupp.StartAttributeLine,
String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope));
}
else
{
ruleSupp.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSupp.StartAttributeLine,
System.IO.Path.GetFileName(scopeAst.Extent.File), String.Format(Strings.TargetCannotBeFoundError, ruleSupp.Target, ruleSupp.Scope));
}

result.Add(ruleSupp);
continue;
}
Expand Down
14 changes: 12 additions & 2 deletions Engine/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -956,8 +956,18 @@ public Tuple<List<SuppressedRecord>, List<DiagnosticRecord>> SuppressRule(string
// If we cannot found any error but the rulesuppression has a rulesuppressionid then it must be used wrongly
if (!String.IsNullOrWhiteSpace(ruleSuppression.RuleSuppressionID) && suppressionCount == 0)
{
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine,
System.IO.Path.GetFileName(diagnostics.First().Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
// checks whether are given a string or a file path
if (String.IsNullOrWhiteSpace(diagnostics.First().Extent.File))
{
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormatScriptDefinition, ruleSuppression.StartAttributeLine,
String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
}
else
{
ruleSuppression.Error = String.Format(CultureInfo.CurrentCulture, Strings.RuleSuppressionErrorFormat, ruleSuppression.StartAttributeLine,
System.IO.Path.GetFileName(diagnostics.First().Extent.File), String.Format(Strings.RuleSuppressionIDError, ruleSuppression.RuleSuppressionID));
}

this.outputWriter.WriteError(new ErrorRecord(new ArgumentException(ruleSuppression.Error), ruleSuppression.Error, ErrorCategory.InvalidArgument, ruleSuppression));
}
}
Expand Down
59 changes: 54 additions & 5 deletions Engine/ScriptAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ public Dictionary<string, List<string>> CheckRuleExtension(string[] path, PathIn
}

#endregion


/// <summary>
/// Analyzes a script file or a directory containing script files.
Expand Down Expand Up @@ -924,6 +924,49 @@ public IEnumerable<DiagnosticRecord> AnalyzePath(string path, bool searchRecursi
}
}

/// <summary>
/// Analyzes a script definition in the form of a string input
/// </summary>
/// <param name="scriptDefinition">The script to be analyzed</param>
/// <returns></returns>
public IEnumerable<DiagnosticRecord> AnalyzeScriptDefinition(string scriptDefinition)
{
ScriptBlockAst scriptAst = null;
Token[] scriptTokens = null;
ParseError[] errors = null;

this.outputWriter.WriteVerbose(string.Format(CultureInfo.CurrentCulture, Strings.VerboseScriptDefinitionMessage));

try
{
scriptAst = Parser.ParseInput(scriptDefinition, out scriptTokens, out errors);
}
catch (Exception e)
{
this.outputWriter.WriteWarning(e.ToString());
return null;
}

if (errors != null && errors.Length > 0)
{
foreach (ParseError error in errors)
{
string parseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParseErrorFormatForScriptDefinition, error.Message.TrimEnd('.'), error.Extent.StartLineNumber, error.Extent.StartColumnNumber);
this.outputWriter.WriteError(new ErrorRecord(new ParseException(parseErrorMessage), parseErrorMessage, ErrorCategory.ParserError, error.ErrorId));
}
}

if (errors != null && errors.Length > 10)
{
string manyParseErrorMessage = String.Format(CultureInfo.CurrentCulture, Strings.ParserErrorMessageForScriptDefinition);
this.outputWriter.WriteError(new ErrorRecord(new ParseException(manyParseErrorMessage), manyParseErrorMessage, ErrorCategory.ParserError, scriptDefinition));

return new List<DiagnosticRecord>();
}

return this.AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty);
}

private void BuildScriptPathList(
string path,
bool searchRecursively,
Expand Down Expand Up @@ -1038,7 +1081,9 @@ private IEnumerable<DiagnosticRecord> AnalyzeFile(string filePath)
/// </summary>
/// <param name="scriptAst">The ScriptBlockAst from the parsed script.</param>
/// <param name="scriptTokens">The tokens found in the script.</param>
/// <param name="filePath">The path to the file that was parsed.</param>
/// <param name="filePath">The path to the file that was parsed.
/// If AnalyzeSyntaxTree is called from an ast that we get from ParseInput, then this field will be String.Empty
/// </param>
/// <returns>An enumeration of DiagnosticRecords that were found by rules.</returns>
public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
ScriptBlockAst scriptAst,
Expand All @@ -1052,8 +1097,12 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(

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

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

if (!helpFile)
{
Expand Down Expand Up @@ -1083,7 +1132,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(

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

if (this.ScriptRules != null)
{
Expand Down Expand Up @@ -1285,7 +1334,7 @@ public IEnumerable<DiagnosticRecord> AnalyzeSyntaxTree(
}

// Check if the supplied artifact is indeed part of the DSC resource
if (Helper.Instance.IsDscResourceModule(filePath))
if (!filePathIsNullOrWhiteSpace && Helper.Instance.IsDscResourceModule(filePath))
{
// Run all DSC Rules
foreach (IDSCResourceRule dscResourceRule in this.DSCResourceRules)
Expand Down
36 changes: 36 additions & 0 deletions Engine/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Engine/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,16 @@
<data name="InvalidProfile" xml:space="preserve">
<value>Profile file '{0}' is invalid because it does not contain a hashtable.</value>
</data>
<data name="ParseErrorFormatForScriptDefinition" xml:space="preserve">
<value>Parse error in script definition: {0} at line {1} column {2}.</value>
</data>
<data name="ParserErrorMessageForScriptDefinition" xml:space="preserve">
<value>There are too many parser errors in the script definition. Please correct them before running ScriptAnalyzer.</value>
</data>
<data name="RuleSuppressionErrorFormatScriptDefinition" xml:space="preserve">
<value>Suppression Message Attribute error at line {0} in script definition : {1}</value>
</data>
<data name="VerboseScriptDefinitionMessage" xml:space="preserve">
<value>Analyzing Script Definition.</value>
</data>
</root>
2 changes: 1 addition & 1 deletion PSScriptAnalyzer.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptAnalyzerEngine", "Engine\ScriptAnalyzerEngine.csproj", "{F4BDE3D0-3EEF-4157-8A3E-722DF7ADEF60}"
EndProject
Expand Down
15 changes: 12 additions & 3 deletions Rules/AvoidDefaultTrueValueSwitchParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,18 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
if (paramAst.Attributes.Any(attr => attr.TypeName.GetReflectionType() == typeof(System.Management.Automation.SwitchParameter))
&& paramAst.DefaultValue != null && String.Equals(paramAst.DefaultValue.Extent.Text, "$true", StringComparison.OrdinalIgnoreCase))
{
yield return new DiagnosticRecord(
String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterError, System.IO.Path.GetFileName(fileName)),
paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
if (String.IsNullOrWhiteSpace(fileName))
{
yield return new DiagnosticRecord(
String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterErrorScriptDefinition),
paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
}
else
{
yield return new DiagnosticRecord(
String.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueSwitchParameterError, System.IO.Path.GetFileName(fileName)),
paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName);
}
}
}
}
Expand Down
Loading