@@ -149,15 +149,17 @@ private class Visitor : AstVisitor, IAstPostVisitHandler
149
149
private readonly IRule _rule ;
150
150
private readonly string _scriptPath ;
151
151
private readonly List < DiagnosticRecord > _diagnostics ;
152
- private readonly Stack < KeyValuePair < ScriptBlockAst , Dictionary < string , ExpressionAst > > > _scriptBlockContext ;
152
+ private readonly Stack < KeyValuePair < ScriptBlockAst , Dictionary < string , Ast > > > _scriptBlockContext ;
153
+ private readonly Dictionary < string , Ast > _scriptScopeVariables ;
153
154
private readonly HashSet < ScriptBlockAst > _dotSourcedScriptBlocks ;
154
155
155
156
public Visitor ( IRule rule , string scriptPath )
156
157
{
157
158
_rule = rule ;
158
159
_scriptPath = scriptPath ;
159
160
_diagnostics = new List < DiagnosticRecord > ( ) ;
160
- _scriptBlockContext = new Stack < KeyValuePair < ScriptBlockAst , Dictionary < string , ExpressionAst > > > ( ) ;
161
+ _scriptScopeVariables = new Dictionary < string , Ast > ( StringComparer . OrdinalIgnoreCase ) ;
162
+ _scriptBlockContext = new Stack < KeyValuePair < ScriptBlockAst , Dictionary < string , Ast > > > ( ) ;
161
163
_dotSourcedScriptBlocks = new HashSet < ScriptBlockAst > ( ) ;
162
164
}
163
165
@@ -182,7 +184,7 @@ public void PostVisit(Ast ast)
182
184
// looking up the stack only happens at runtime, so it's something we can't analyze
183
185
if ( _scriptBlockContext . Peek ( ) . Key == ast )
184
186
{
185
- Dictionary < string , ExpressionAst > unusedVariables = _scriptBlockContext . Pop ( ) . Value ;
187
+ Dictionary < string , Ast > unusedVariables = _scriptBlockContext . Pop ( ) . Value ;
186
188
foreach ( ExpressionAst variableDefinition in unusedVariables . Values )
187
189
{
188
190
if ( ! TryGetVariableNameFromExpression ( variableDefinition , out string variableName ) )
@@ -209,17 +211,17 @@ public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst)
209
211
if ( ! _dotSourcedScriptBlocks . Remove ( scriptBlockAst ) )
210
212
{
211
213
_scriptBlockContext . Push (
212
- new KeyValuePair < ScriptBlockAst , Dictionary < string , ExpressionAst > > (
214
+ new KeyValuePair < ScriptBlockAst , Dictionary < string , Ast > > (
213
215
scriptBlockAst ,
214
- new Dictionary < string , ExpressionAst > ( StringComparer . OrdinalIgnoreCase ) ) ) ;
216
+ new Dictionary < string , Ast > ( StringComparer . OrdinalIgnoreCase ) ) ) ;
215
217
}
216
218
217
219
return AstVisitAction . Continue ;
218
220
}
219
221
220
222
public override AstVisitAction VisitAssignmentStatement ( AssignmentStatementAst assignmentStatementAst )
221
223
{
222
- Dictionary < string , ExpressionAst > scopeVariables = _scriptBlockContext . Peek ( ) . Value ;
224
+ Dictionary < string , Ast > scopeVariables = _scriptBlockContext . Peek ( ) . Value ;
223
225
224
226
// Want to visit the RHS to check for used variables
225
227
// We visit it first since it's evaluated first, so we catch '$x = $x' when $x has never been set
@@ -228,6 +230,7 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a
228
230
switch ( assignmentStatementAst . Left )
229
231
{
230
232
case MemberExpressionAst memberExpressionAst :
233
+ memberExpressionAst . Visit ( this ) ;
231
234
break ;
232
235
233
236
case ArrayLiteralAst arrayLhs :
@@ -288,7 +291,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst)
288
291
return AstVisitAction . Continue ;
289
292
}
290
293
291
- Dictionary < string , ExpressionAst > scopeVariables = _scriptBlockContext . Peek ( ) . Value ;
294
+ Dictionary < string , Ast > scopeVariables = _scriptBlockContext . Peek ( ) . Value ;
292
295
293
296
// We may encounter a Set-Variable (etc), which we treat as assignment
294
297
// The parameters here happen to be common to the variable definition cmdlets
@@ -342,6 +345,38 @@ public IEnumerable<ScriptBlockAst> GetScriptBlockAstsFromCommandElements(
342
345
}
343
346
}
344
347
348
+ private void RegisterVariableDeclaration ( string variableName , Ast definingAst , VariableScope scope )
349
+ {
350
+ switch ( scope )
351
+ {
352
+ case VariableScope . Private :
353
+ case VariableScope . Local :
354
+ case VariableScope . Normal :
355
+ _scriptBlockContext . Peek ( ) . Value [ variableName ] = definingAst ;
356
+ return ;
357
+
358
+ case VariableScope . Script :
359
+ _scriptScopeVariables [ variableName ] = definingAst ;
360
+ return ;
361
+ }
362
+ }
363
+
364
+ private void RegisterVariableUse ( string variableName , VariableScope scope )
365
+ {
366
+ switch ( scope )
367
+ {
368
+ case VariableScope . Private :
369
+ case VariableScope . Local :
370
+ case VariableScope . Normal :
371
+ _scriptBlockContext . Peek ( ) . Value . Remove ( variableName ) ;
372
+ return ;
373
+
374
+ case VariableScope . Script :
375
+ _scriptScopeVariables . Remove ( variableName ) ;
376
+ return ;
377
+ }
378
+ }
379
+
345
380
private static bool TryGetVariableNameFromParameters (
346
381
ReadOnlyCollection < CommandElementAst > commandElements ,
347
382
IReadOnlyDictionary < string , string > switchParameters ,
@@ -483,5 +518,17 @@ private static bool IsInParameterDict(IReadOnlyDictionary<string, string> parame
483
518
return false ;
484
519
}
485
520
}
521
+
522
+ private enum VariableScope
523
+ {
524
+ Unknown = 0 ,
525
+ Normal ,
526
+ Private ,
527
+ Local ,
528
+ Script ,
529
+ Global ,
530
+ Env ,
531
+ Using ,
532
+ }
486
533
}
487
534
}
0 commit comments