Skip to content

Commit bba8fed

Browse files
authored
Enhance UseDeclaredVarsMoreThanAssignments to detect also take into account the usage of Get-Variable with an array of variables and usage of named parameter -Name (#1310)
* Parse Array syntax of Get-Variable * check named parameter better to be -Name or shortened versions and add more tests * Fix last edge cases and refactor into method * Apply PR suggestions: Use shorter pattern matching syntax and inline/simplify local method
1 parent 4f82d81 commit bba8fed

File tree

2 files changed

+86
-20
lines changed

2 files changed

+86
-20
lines changed

Rules/UseDeclaredVarsMoreThanAssignments.cs

+50-16
Original file line numberDiff line numberDiff line change
@@ -208,33 +208,67 @@ private IEnumerable<DiagnosticRecord> AnalyzeScriptBlockAst(ScriptBlockAst scrip
208208
}
209209
}
210210

211-
// Detect usages of Get-Variable
211+
AnalyzeGetVariableCommands(scriptBlockAst, assignmentsDictionary_OrdinalIgnoreCase);
212+
213+
foreach (string key in assignmentsDictionary_OrdinalIgnoreCase.Keys)
214+
{
215+
yield return new DiagnosticRecord(
216+
string.Format(CultureInfo.CurrentCulture, Strings.UseDeclaredVarsMoreThanAssignmentsError, key),
217+
assignmentsDictionary_OrdinalIgnoreCase[key].Left.Extent,
218+
GetName(),
219+
DiagnosticSeverity.Warning,
220+
fileName,
221+
key);
222+
}
223+
}
224+
225+
/// <summary>
226+
/// Detects variables retrieved by usage of Get-Variable and remove those
227+
/// variables from the entries in <paramref name="assignmentsDictionary_OrdinalIgnoreCase"/>.
228+
/// </summary>
229+
/// <param name="scriptBlockAst"></param>
230+
/// <param name="assignmentsDictionary_OrdinalIgnoreCase"></param>
231+
private void AnalyzeGetVariableCommands(
232+
ScriptBlockAst scriptBlockAst,
233+
Dictionary<string, AssignmentStatementAst> assignmentsDictionary_OrdinalIgnoreCase)
234+
{
212235
var getVariableCmdletNamesAndAliases = Helper.Instance.CmdletNameAndAliases("Get-Variable");
213236
IEnumerable<Ast> getVariableCommandAsts = scriptBlockAst.FindAll(testAst => testAst is CommandAst commandAst &&
214237
getVariableCmdletNamesAndAliases.Contains(commandAst.GetCommandName(), StringComparer.OrdinalIgnoreCase), true);
238+
215239
foreach (CommandAst getVariableCommandAst in getVariableCommandAsts)
216240
{
217241
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)
242+
// The following extracts the variable name(s) only in the simplest possible usage of Get-Variable.
243+
// Usage of a named parameter and an array of variables is accounted for though.
244+
if (commandElements.Count < 2 || commandElements.Count > 3) { continue; }
245+
246+
var commandElementAstOfVariableName = commandElements[commandElements.Count - 1];
247+
if (commandElements.Count == 3)
220248
{
221-
var variableName = constantExpressionAst.Value;
222-
if (assignmentsDictionary_OrdinalIgnoreCase.ContainsKey(variableName))
249+
if (!(commandElements[1] is CommandParameterAst commandParameterAst)) { continue; }
250+
// Check if the named parameter -Name is used (PowerShell does not need the full
251+
// parameter name and there is no other parameter of Get-Variable starting with n).
252+
if (!commandParameterAst.ParameterName.StartsWith("n", StringComparison.OrdinalIgnoreCase))
223253
{
224-
assignmentsDictionary_OrdinalIgnoreCase.Remove(variableName);
254+
continue;
225255
}
226256
}
227-
}
228257

229-
foreach (string key in assignmentsDictionary_OrdinalIgnoreCase.Keys)
230-
{
231-
yield return new DiagnosticRecord(
232-
string.Format(CultureInfo.CurrentCulture, Strings.UseDeclaredVarsMoreThanAssignmentsError, key),
233-
assignmentsDictionary_OrdinalIgnoreCase[key].Left.Extent,
234-
GetName(),
235-
DiagnosticSeverity.Warning,
236-
fileName,
237-
key);
258+
if (commandElementAstOfVariableName is StringConstantExpressionAst constantExpressionAst)
259+
{
260+
assignmentsDictionary_OrdinalIgnoreCase.Remove(constantExpressionAst.Value);
261+
continue;
262+
}
263+
264+
if (!(commandElementAstOfVariableName is ArrayLiteralAst arrayLiteralAst)) { continue; }
265+
foreach (var expressionAst in arrayLiteralAst.Elements)
266+
{
267+
if (expressionAst is StringConstantExpressionAst constantExpressionAstOfArray)
268+
{
269+
assignmentsDictionary_OrdinalIgnoreCase.Remove(constantExpressionAstOfArray.Value);
270+
}
271+
}
238272
}
239273
}
240274
}

Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1

+36-4
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ function MyFunc2() {
7272
It "returns no violations" {
7373
$noViolations.Count | Should -Be 0
7474
}
75-
75+
7676
It "Does not flag += operator" {
7777
$results = Invoke-ScriptAnalyzer -ScriptDefinition '$array=@(); $list | ForEach-Object { $array += $c }' | Where-Object { $_.RuleName -eq $violationName }
7878
$results.Count | Should -Be 0
@@ -88,9 +88,41 @@ function MyFunc2() {
8888
$results.Count | Should -Be 0
8989
}
9090

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
91+
It "No warning when using 'Get-Variable' with variables declaration '<DeclareVariables>' and command parameter <GetVariableCommandParameter>" -TestCases @(
92+
@{
93+
DeclareVariables = '$a = 1'; GetVariableCommandParameter = 'a';
94+
}
95+
@{
96+
DeclareVariables = '$a = 1'; GetVariableCommandParameter = '-Name a';
97+
}
98+
@{
99+
DeclareVariables = '$a = 1'; GetVariableCommandParameter = '-n a';
100+
}
101+
@{
102+
DeclareVariables = '$a = 1; $b = 2'; GetVariableCommandParameter = 'a,b'
103+
}
104+
@{
105+
DeclareVariables = '$a = 1; $b = 2'; GetVariableCommandParameter = '-Name a,b'
106+
}
107+
@{
108+
DeclareVariables = '$a = 1; $b = 2'; GetVariableCommandParameter = '-n a,b'
109+
}
110+
@{
111+
DeclareVariables = '$a = 1; $b = 2'; GetVariableCommandParameter = 'A,B'
112+
}
113+
) {
114+
Param(
115+
$DeclareVariables,
116+
$GetVariableCommandParameter
117+
)
118+
$scriptDefinition = "$DeclareVariables; Get-Variable $GetVariableCommandParameter"
119+
$noViolations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition
120+
$noViolations.Count | Should -Be 0 -Because $scriptDefinition
121+
}
122+
123+
It "Does not misinterpret switch parameter of Get-Variable as variable" {
124+
$scriptDefinition = '$ArbitrarySwitchParameter = 1; Get-Variable -ArbitrarySwitchParameter'
125+
(Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition).Count | Should -Be 1 -Because $scriptDefinition
94126
}
95127
}
96128
}

0 commit comments

Comments
 (0)