Skip to content

Commit 068b1fc

Browse files
Jos KoelewijnJos Koelewijn
Jos Koelewijn
authored and
Jos Koelewijn
committed
wrestling with Ast in C# - not working
1 parent 7182edb commit 068b1fc

File tree

1 file changed

+173
-0
lines changed

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
#if !CORECLR
8+
using System.ComponentModel.Composition;
9+
#endif
10+
using System.Globalization;
11+
using System.Linq;
12+
using System.Management.Automation.Language;
13+
using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic;
14+
15+
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
16+
{
17+
/// <summary>
18+
/// AvoidUnInitializedVarsInNewRunspaces: Analyzes the ast to check that variables in script blocks that run in new run spaces are properly initialized or passed in with '$using:(varName)'.
19+
/// </summary>
20+
#if !CORECLR
21+
[Export(typeof(IScriptRule))]
22+
#endif
23+
public class AvoidUnInitializedVarsInNewRunspaces : IScriptRule
24+
{
25+
/// <summary>
26+
/// AnalyzeScript: Analyzes the ast to check variables in script blocks that will run in new runspaces are properly initialized or passed in with $using:
27+
/// </summary>
28+
/// <param name="ast">The script's ast</param>
29+
/// <param name="fileName">The script's file name</param>
30+
/// <returns>A List of results from this rule</returns>
31+
public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
32+
{
33+
if (ast == null)
34+
{
35+
throw new ArgumentNullException(Strings.NullAstErrorMessage);
36+
}
37+
38+
var scriptBlockAsts = ast.FindAll(x => x is ScriptBlockAst, true);
39+
if (scriptBlockAsts == null)
40+
{
41+
yield break;
42+
}
43+
44+
foreach (var scriptBlockAst in scriptBlockAsts)
45+
{
46+
var sbAst = scriptBlockAst as ScriptBlockAst;
47+
foreach (var diagnosticRecord in AnalyzeScriptBlockAst(sbAst, fileName))
48+
{
49+
yield return diagnosticRecord;
50+
}
51+
}
52+
}
53+
54+
/// <summary>
55+
/// GetName: Retrieves the name of this rule.
56+
/// </summary>
57+
/// <returns>The name of this rule</returns>
58+
public string GetName()
59+
{
60+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidUnInitializedVarsInNewRunspacesName);
61+
}
62+
63+
/// <summary>
64+
/// GetCommonName: Retrieves the common name of this rule.
65+
/// </summary>
66+
/// <returns>The common name of this rule</returns>
67+
public string GetCommonName()
68+
{
69+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUnInitializedVarsInNewRunspacesCommonName);
70+
}
71+
72+
/// <summary>
73+
/// GetDescription: Retrieves the description of this rule.
74+
/// </summary>
75+
/// <returns>The description of this rule</returns>
76+
public string GetDescription()
77+
{
78+
return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUnInitializedVarsInNewRunspacesDescription);
79+
}
80+
81+
/// <summary>
82+
/// GetSourceType: Retrieves the type of the rule: builtin, managed or module.
83+
/// </summary>
84+
public SourceType GetSourceType()
85+
{
86+
return SourceType.Builtin;
87+
}
88+
89+
/// <summary>
90+
/// GetSeverity: Retrieves the severity of the rule: error, warning of information.
91+
/// </summary>
92+
/// <returns></returns>
93+
public RuleSeverity GetSeverity()
94+
{
95+
return RuleSeverity.Warning;
96+
}
97+
98+
/// <summary>
99+
/// GetSourceName: Retrieves the module/assembly name the rule is from.
100+
/// </summary>
101+
public string GetSourceName()
102+
{
103+
return string.Format(CultureInfo.CurrentCulture, Strings.SourceName);
104+
}
105+
106+
/// <summary>
107+
/// Checks if a variable is initialized and referenced in either its assignment or children scopes
108+
/// </summary>
109+
/// <param name="scriptBlockAst">Ast of type ScriptBlock</param>
110+
/// <param name="fileName">Name of file containing the ast</param>
111+
/// <returns>An enumerable containing diagnostic records</returns>
112+
private IEnumerable<DiagnosticRecord> AnalyzeScriptBlockAst(ScriptBlockAst scriptBlockAst, string fileName)
113+
{
114+
var foreachObjectCmdletNamesAndAliases = Helper.Instance.CmdletNameAndAliases("Foreach-Object");
115+
116+
// Find all commandAst objects for `Foreach-Object -Parallel`. As for parametername matching, there are three
117+
// parameters starting with a 'p': Parallel, PipelineVariable and Process, so we use startsWith 'pa' as the shortest unambiguous form.
118+
// Because we are already going trough all ScriptBlockAst objects, we do not need to look for nested script blocks here.
119+
if (!(scriptBlockAst.FindAll(
120+
predicate: c => c is CommandAst commandAst &&
121+
foreachObjectCmdletNamesAndAliases.Contains(commandAst.GetCommandName(), StringComparer.OrdinalIgnoreCase) &&
122+
commandAst.CommandElements.Any(
123+
e => e is CommandParameterAst parameterAst &&
124+
parameterAst.ParameterName.StartsWith("pa", StringComparison.OrdinalIgnoreCase)),
125+
searchNestedScriptBlocks: true) is IEnumerable<Ast> foreachObjectParallelAsts))
126+
{
127+
yield break;
128+
}
129+
130+
foreach (var ast in foreachObjectParallelAsts)
131+
{
132+
var commandAst = ast as CommandAst;
133+
134+
if (commandAst == null)
135+
{
136+
continue;
137+
}
138+
139+
var varsInAssignments = commandAst.FindAll(
140+
predicate: a => a is AssignmentStatementAst assignment &&
141+
assignment.Left.FindAll(
142+
predicate: aa => aa is VariableExpressionAst,
143+
searchNestedScriptBlocks: true) != null,
144+
searchNestedScriptBlocks: true);
145+
146+
var commandElements = commandAst.CommandElements;
147+
var nonAssignedNonUsingVars = new List<Ast>() { };
148+
foreach (var cmdEl in commandElements)
149+
{
150+
nonAssignedNonUsingVars.AddRange(
151+
cmdEl.FindAll(
152+
predicate: aa => aa is VariableExpressionAst varAst &&
153+
!(varAst.Parent is UsingExpressionAst) &&
154+
!varsInAssignments.Contains(varAst), true));
155+
}
156+
157+
foreach (var variableExpression in nonAssignedNonUsingVars)
158+
{
159+
var _temp = variableExpression as VariableExpressionAst;
160+
161+
yield return new DiagnosticRecord(
162+
message: string.Format(CultureInfo.CurrentCulture,
163+
Strings.UseDeclaredVarsMoreThanAssignmentsError, _temp?.ToString()),
164+
extent: _temp?.Extent,
165+
ruleName: GetName(),
166+
severity: DiagnosticSeverity.Warning,
167+
scriptPath: fileName,
168+
ruleId: _temp?.ToString());
169+
}
170+
}
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)