Skip to content

Commit 0d984bd

Browse files
author
Kapil Borle
committed
Resolve merge conflict
2 parents d1293c1 + f0bd654 commit 0d984bd

28 files changed

+925
-90
lines changed

Engine/Generic/CorrectionExtent.cs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Management.Automation.Language;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
10+
{
11+
public class CorrectionExtent
12+
{
13+
public int EndColumnNumber
14+
{
15+
get
16+
{
17+
return endColumnNumber;
18+
}
19+
}
20+
21+
public int EndLineNumber
22+
{
23+
get
24+
{
25+
return endLineNumber;
26+
}
27+
}
28+
29+
public string File
30+
{
31+
get
32+
{
33+
return file;
34+
}
35+
}
36+
37+
public int StartColumnNumber
38+
{
39+
get
40+
{
41+
return startColumnNumber;
42+
}
43+
}
44+
45+
public int StartLineNumber
46+
{
47+
get
48+
{
49+
return startLineNumber;
50+
}
51+
}
52+
53+
public string Text
54+
{
55+
get
56+
{
57+
return text;
58+
}
59+
}
60+
61+
public string Description
62+
{
63+
get
64+
{
65+
return description;
66+
}
67+
}
68+
69+
private string file;
70+
private int startLineNumber;
71+
private int endLineNumber;
72+
private int startColumnNumber;
73+
private int endColumnNumber;
74+
private string text;
75+
private string description;
76+
77+
public CorrectionExtent(
78+
int startLineNumber,
79+
int endLineNumber,
80+
int startColumnNumber,
81+
int endColumnNumber,
82+
string text,
83+
string file)
84+
: this(
85+
startLineNumber,
86+
endLineNumber,
87+
startColumnNumber,
88+
endColumnNumber,
89+
text,
90+
file,
91+
null)
92+
{
93+
}
94+
95+
public CorrectionExtent(
96+
int startLineNumber,
97+
int endLineNumber,
98+
int startColumnNumber,
99+
int endColumnNumber,
100+
string text,
101+
string file,
102+
string description)
103+
{
104+
this.startLineNumber = startLineNumber;
105+
this.endLineNumber = endLineNumber;
106+
this.startColumnNumber = startColumnNumber;
107+
this.endColumnNumber = endColumnNumber;
108+
this.file = file;
109+
this.text = text;
110+
this.description = description;
111+
ThrowIfInvalidArguments();
112+
}
113+
114+
115+
116+
private void ThrowIfInvalidArguments()
117+
{
118+
ThrowIfNull<string>(file, "filename");
119+
ThrowIfNull<string>(text, "text");
120+
ThrowIfDecreasing(startLineNumber, endLineNumber, "start line number cannot be less than end line number");
121+
if (startLineNumber == endLineNumber)
122+
{
123+
ThrowIfDecreasing(StartColumnNumber, endColumnNumber, "start column number cannot be less than end column number for a one line extent");
124+
}
125+
}
126+
127+
private void ThrowIfDecreasing(int start, int end, string message)
128+
{
129+
if (start > end)
130+
{
131+
throw new ArgumentException(message);
132+
}
133+
}
134+
135+
private void ThrowIfNull<T>(T arg, string argName)
136+
{
137+
if (arg == null)
138+
{
139+
throw new ArgumentNullException(argName);
140+
}
141+
}
142+
143+
}
144+
}

Engine/Generic/DiagnosticRecord.cs

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
// THE SOFTWARE.
1111
//
1212

13+
14+
1315
using System;
16+
using System.Collections.Generic;
1417
using System.Management.Automation.Language;
1518

1619
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic
@@ -27,6 +30,7 @@ public class DiagnosticRecord
2730
private DiagnosticSeverity severity;
2831
private string scriptPath;
2932
private string ruleSuppressionId;
33+
private List<CorrectionExtent> suggestedCorrections;
3034

3135
/// <summary>
3236
/// Represents a string from the rule about why this diagnostic was created.
@@ -90,43 +94,43 @@ public string RuleSuppressionID
9094
set { ruleSuppressionId = value; }
9195
}
9296

97+
/// <summary>
98+
/// Returns suggested correction
99+
/// return value can be null
100+
/// </summary>
101+
public IEnumerable<CorrectionExtent> SuggestedCorrections
102+
{
103+
get { return suggestedCorrections; }
104+
}
105+
93106
/// <summary>
94107
/// DiagnosticRecord: The constructor for DiagnosticRecord class.
95108
/// </summary>
96109
public DiagnosticRecord()
97110
{
98111

99112
}
100-
113+
101114
/// <summary>
102-
/// DiagnosticRecord: The constructor for DiagnosticRecord class.
115+
/// DiagnosticRecord: The constructor for DiagnosticRecord class that takes in suggestedCorrection
103116
/// </summary>
104117
/// <param name="message">A string about why this diagnostic was created</param>
105118
/// <param name="extent">The place in the script this diagnostic refers to</param>
106119
/// <param name="ruleName">The name of the rule that created this diagnostic</param>
107120
/// <param name="severity">The severity of this diagnostic</param>
108-
/// <param name="scriptName">The path of the script file being analyzed</param>
109-
public DiagnosticRecord(string message, IScriptExtent extent, string ruleName, DiagnosticSeverity severity, string scriptPath, string ruleId = null)
121+
/// <param name="scriptPath">The full path of the script file being analyzed</param>
122+
/// <param name="suggestedCorrections">The correction suggested by the rule to replace the extent text</param>
123+
public DiagnosticRecord(string message, IScriptExtent extent, string ruleName, DiagnosticSeverity severity, string scriptPath, string ruleId = null, List<CorrectionExtent> suggestedCorrections = null)
110124
{
111125
Message = message;
112126
RuleName = ruleName;
113127
Extent = extent;
114128
Severity = severity;
115129
ScriptPath = scriptPath;
116-
ruleSuppressionId = ruleId;
130+
RuleSuppressionID = ruleId;
131+
this.suggestedCorrections = suggestedCorrections;
117132
}
118133

119-
/// <summary>
120-
/// Copy Constructor
121-
/// </summary>
122-
/// <param name="record"></param>
123-
public DiagnosticRecord(DiagnosticRecord diagnosticRecord)
124-
{
125-
if (diagnosticRecord == null)
126-
{
127-
throw new ArgumentNullException("diagnosticRecord");
128-
}
129-
}
130134
}
131135

132136

Engine/Helper.cs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using System.Management.Automation.Language;
2020
using System.Globalization;
2121
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
22+
using System.Management.Automation.Runspaces;
2223

2324
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer
2425
{
@@ -104,14 +105,14 @@ internal set
104105
private string[] functionScopes = new string[] { "global:", "local:", "script:", "private:"};
105106

106107
private string[] variableScopes = new string[] { "global:", "local:", "script:", "private:", "variable:", ":"};
107-
108108
#endregion
109109

110110
/// <summary>
111111
/// Initializes the Helper class.
112112
/// </summary>
113113
private Helper()
114114
{
115+
115116
}
116117

117118
/// <summary>
@@ -229,6 +230,61 @@ public bool IsDscResourceModule(string filePath)
229230

230231
return false;
231232
}
233+
234+
/// <summary>
235+
/// Gets the module manifest
236+
/// </summary>
237+
/// <param name="filePath"></param>
238+
/// <param name="errorRecord"></param>
239+
/// <returns>Returns a object of type PSModuleInfo</returns>
240+
public PSModuleInfo GetModuleManifest(string filePath, out IEnumerable<ErrorRecord> errorRecord)
241+
{
242+
errorRecord = null;
243+
PSModuleInfo psModuleInfo = null;
244+
Collection<PSObject> psObj = null;
245+
var ps = System.Management.Automation.PowerShell.Create();
246+
try
247+
{
248+
ps.AddCommand("Test-ModuleManifest");
249+
ps.AddParameter("Path", filePath);
250+
ps.AddParameter("WarningAction", ActionPreference.SilentlyContinue);
251+
psObj = ps.Invoke();
252+
}
253+
catch (CmdletInvocationException e)
254+
{
255+
// Invoking Test-ModuleManifest on a module manifest that doesn't have all the valid keys
256+
// throws a NullReferenceException. This is probably a bug in Test-ModuleManifest and hence
257+
// we consume it to allow execution of the of this method.
258+
if (e.InnerException == null || e.InnerException.GetType() != typeof(System.NullReferenceException))
259+
{
260+
throw;
261+
}
262+
}
263+
if (ps.HadErrors && ps.Streams != null && ps.Streams.Error != null)
264+
{
265+
var errorRecordArr = new ErrorRecord[ps.Streams.Error.Count];
266+
ps.Streams.Error.CopyTo(errorRecordArr, 0);
267+
errorRecord = errorRecordArr;
268+
}
269+
if (psObj != null && psObj.Any() && psObj[0] != null)
270+
{
271+
psModuleInfo = psObj[0].ImmediateBaseObject as PSModuleInfo;
272+
}
273+
ps.Dispose();
274+
return psModuleInfo;
275+
}
276+
277+
/// <summary>
278+
/// Checks if the error record is MissingMemberException
279+
/// </summary>
280+
/// <param name="errorRecord"></param>
281+
/// <returns>Returns a boolean value indicating the presence of MissingMemberException</returns>
282+
public static bool IsMissingManifestMemberException(ErrorRecord errorRecord)
283+
{
284+
return errorRecord.CategoryInfo != null
285+
&& errorRecord.CategoryInfo.Category == ErrorCategory.ResourceUnavailable
286+
&& string.Equals("MissingMemberException", errorRecord.CategoryInfo.Reason, StringComparison.OrdinalIgnoreCase);
287+
}
232288

233289
/// <summary>
234290
/// Get the list of exported function by analyzing the ast
@@ -1328,7 +1384,7 @@ public static string[] ProcessCustomRulePaths(string[] rulePaths, SessionState s
13281384
return outPaths.ToArray();
13291385

13301386
}
1331-
1387+
13321388
/// <summary>
13331389
/// Check if the function name starts with one of potentailly state changing verbs
13341390
/// </summary>
@@ -1458,7 +1514,7 @@ public bool GetNamedArgumentAttributeValue(NamedAttributeArgumentAst namedAttrib
14581514
return false;
14591515
}
14601516

1461-
#endregion
1517+
#endregion Methods
14621518
}
14631519

14641520

Engine/ScriptAnalyzerEngine.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<Compile Include="Generic\AvoidCmdletGeneric.cs" />
7272
<Compile Include="Generic\AvoidParameterGeneric.cs" />
7373
<Compile Include="Generic\ModuleDependencyHandler.cs" />
74+
<Compile Include="Generic\CorrectionExtent.cs" />
7475
<Compile Include="Generic\SuppressedRecord.cs" />
7576
<Compile Include="Generic\DiagnosticRecord.cs" />
7677
<Compile Include="Generic\ExternalRule.cs" />

Engine/about_PSScriptAnalyzer.help.txt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,47 @@ RULE SUPPRESSSION
183183
Target="*")]
184184
param()
185185
}
186+
187+
VIOLATION CORRECTION
188+
189+
Most violations can be fixed by replacing the violation causing content with the correct alternative. In an attempt to provide the user with the ability to correct the violation we provide a property - `SuggestedCorrections`, in each DiagnosticRecord instance. This property contains the information needed to rectify the violation. For example, consider a script `C:\tmp\test.ps1` with the following content.
190+
191+
PS> Get-Content C:\tmp\test.ps1
192+
gci C:\
193+
194+
Invoking PSScriptAnalyzer on the file gives the following output.
195+
196+
PS>$diagnosticRecord = Invoke-ScriptAnalyzer -Path C:\tmp\test.p1
197+
PS>$diagnosticRecord | select SuggestedCorrections | Format-Custom
198+
199+
class DiagnosticRecord
200+
{
201+
SuggestedCorrections =
202+
[
203+
class CorrectionExtent
204+
{
205+
EndColumnNumber = 4
206+
EndLineNumber = 1
207+
File = C:\Users\kabawany\tmp\test3.ps1
208+
StartColumnNumber = 1
209+
StartLineNumber = 1
210+
Text = Get-ChildItem
211+
Description = Replace gci with Get-ChildItem
212+
}
213+
]
214+
215+
}
216+
217+
The *LineNumber and *ColumnNumber properties give the region of the script that can be replaced by the contents of Text property, i.e., replace gci with Get-ChildItem.
218+
219+
The main motivation behind having SuggestedCorrections is to enable quick-fix like scenarios in editors like VSCode, Sublime, etc. At present, we provide valid SuggestedCorrection only for the following rules, while gradually adding this feature to more rules.
220+
221+
* AvoidAlias.cs
222+
* AvoidUsingPlainTextForPassword.cs
223+
* MisleadingBacktick.cs
224+
* MissingModuleManifestField.cs
225+
* UseToExportFieldsInManifest.cs
226+
186227

187228
EXTENSIBILITY
188229

0 commit comments

Comments
 (0)