Skip to content

Commit 703ebe4

Browse files
bergmeisterJamesWTruher
authored andcommitted
Do not trigger UseDeclaredVarsMoreThanAssignment for variables being used via Get-Variable (#925)
* Do not trigger UseDeclaredVarsMoreThanAssignment when using Get-Variable * update licence text * address PR comments
1 parent f02b392 commit 703ebe4

File tree

2 files changed

+33
-9
lines changed

2 files changed

+33
-9
lines changed

Rules/UseDeclaredVarsMoreThanAssignments.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#endif
1010
using System.Globalization;
1111
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
12+
using System.Linq;
1213

1314
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
1415
{
@@ -113,7 +114,7 @@ private IEnumerable<DiagnosticRecord> AnalyzeScriptBlockAst(ScriptBlockAst scrip
113114
IEnumerable<Ast> varAsts = scriptBlockAst.FindAll(testAst => testAst is VariableExpressionAst, true);
114115
IEnumerable<Ast> varsInAssignment;
115116

116-
Dictionary<string, AssignmentStatementAst> assignments = new Dictionary<string, AssignmentStatementAst>(StringComparer.OrdinalIgnoreCase);
117+
Dictionary<string, AssignmentStatementAst> assignmentsDictionary_OrdinalIgnoreCase = new Dictionary<string, AssignmentStatementAst>(StringComparer.OrdinalIgnoreCase);
117118

118119
string varKey;
119120
bool inAssignment;
@@ -148,9 +149,9 @@ private IEnumerable<DiagnosticRecord> AnalyzeScriptBlockAst(ScriptBlockAst scrip
148149
{
149150
string variableName = Helper.Instance.VariableNameWithoutScope(assignmentVarAst.VariablePath);
150151

151-
if (!assignments.ContainsKey(variableName))
152+
if (!assignmentsDictionary_OrdinalIgnoreCase.ContainsKey(variableName))
152153
{
153-
assignments.Add(variableName, assignmentAst);
154+
assignmentsDictionary_OrdinalIgnoreCase.Add(variableName, assignmentAst);
154155
}
155156
}
156157
}
@@ -163,9 +164,9 @@ private IEnumerable<DiagnosticRecord> AnalyzeScriptBlockAst(ScriptBlockAst scrip
163164
varKey = Helper.Instance.VariableNameWithoutScope(varAst.VariablePath);
164165
inAssignment = false;
165166

166-
if (assignments.ContainsKey(varKey))
167+
if (assignmentsDictionary_OrdinalIgnoreCase.ContainsKey(varKey))
167168
{
168-
varsInAssignment = assignments[varKey].Left.FindAll(testAst => testAst is VariableExpressionAst, true);
169+
varsInAssignment = assignmentsDictionary_OrdinalIgnoreCase[varKey].Left.FindAll(testAst => testAst is VariableExpressionAst, true);
169170

170171
// Checks if this variableAst is part of the logged assignment
171172
foreach (VariableExpressionAst varInAssignment in varsInAssignment)
@@ -195,23 +196,41 @@ private IEnumerable<DiagnosticRecord> AnalyzeScriptBlockAst(ScriptBlockAst scrip
195196

196197
if (!inAssignment)
197198
{
198-
assignments.Remove(varKey);
199+
assignmentsDictionary_OrdinalIgnoreCase.Remove(varKey);
199200
}
200201

201202
// Check if variable belongs to PowerShell built-in variables
202203
if (Helper.Instance.HasSpecialVars(varKey))
203204
{
204-
assignments.Remove(varKey);
205+
assignmentsDictionary_OrdinalIgnoreCase.Remove(varKey);
205206
}
206207
}
207208
}
208209
}
209210

210-
foreach (string key in assignments.Keys)
211+
// Detect usages of Get-Variable
212+
var getVariableCmdletNamesAndAliases = Helper.Instance.CmdletNameAndAliases("Get-Variable");
213+
IEnumerable<Ast> getVariableCommandAsts = scriptBlockAst.FindAll(testAst => testAst is CommandAst commandAst &&
214+
getVariableCmdletNamesAndAliases.Contains(commandAst.GetCommandName(), StringComparer.OrdinalIgnoreCase), true);
215+
foreach (CommandAst getVariableCommandAst in getVariableCommandAsts)
216+
{
217+
var commandElements = getVariableCommandAst.CommandElements.ToList();
218+
// The following extracts the variable name only in the simplest possibe case 'Get-Variable variableName'
219+
if (commandElements.Count == 2 && commandElements[1] is StringConstantExpressionAst constantExpressionAst)
220+
{
221+
var variableName = constantExpressionAst.Value;
222+
if (assignmentsDictionary_OrdinalIgnoreCase.ContainsKey(variableName))
223+
{
224+
assignmentsDictionary_OrdinalIgnoreCase.Remove(variableName);
225+
}
226+
}
227+
}
228+
229+
foreach (string key in assignmentsDictionary_OrdinalIgnoreCase.Keys)
211230
{
212231
yield return new DiagnosticRecord(
213232
string.Format(CultureInfo.CurrentCulture, Strings.UseDeclaredVarsMoreThanAssignmentsError, key),
214-
assignments[key].Left.Extent,
233+
assignmentsDictionary_OrdinalIgnoreCase[key].Left.Extent,
215234
GetName(),
216235
DiagnosticSeverity.Warning,
217236
fileName,

Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,10 @@ function MyFunc2() {
8787
$results = Invoke-ScriptAnalyzer -ScriptDefinition '$env:foo = 1; function foo(){ $env:bar = 42 }'
8888
$results.Count | Should -Be 0
8989
}
90+
91+
It "Using a variable via 'Get-Variable' does not trigger a warning" {
92+
$noViolations = Invoke-ScriptAnalyzer -ScriptDefinition '$a=4; get-variable a'
93+
$noViolations.Count | Should -Be 0
94+
}
9095
}
9196
}

0 commit comments

Comments
 (0)