Skip to content

Commit 6b46bb7

Browse files
author
Quoc Truong
committed
Use the FindPipelineOutput Class in Return Correct Types For DSC Functions Rule. Add test cases
1 parent 748a313 commit 6b46bb7

11 files changed

+227
-71
lines changed

Engine/Helper.cs

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,18 @@ public CommandInfo GetCommandInfo(string name)
235235
return Helper.Instance.MyCmdlet.InvokeCommand.GetCommand(name, CommandTypes.All);
236236
}
237237

238+
/// <summary>
239+
/// Returns the get, set and test targetresource dsc function
240+
/// </summary>
241+
/// <param name="ast"></param>
242+
/// <returns></returns>
243+
public IEnumerable<Ast> DscResourceFunctions(Ast ast)
244+
{
245+
List<string> resourceFunctionNames = new List<string>(new string[] { "Set-TargetResource", "Get-TargetResource", "Test-TargetResource" });
246+
return ast.FindAll(item => item is FunctionDefinitionAst
247+
&& resourceFunctionNames.Contains((item as FunctionDefinitionAst).Name, StringComparer.OrdinalIgnoreCase), true);
248+
}
249+
238250
/// <summary>
239251
/// Returns true if the block should be skipped as it has a name
240252
/// that matches keyword
@@ -436,6 +448,8 @@ public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret,
436448

437449
PipelineAst pipe = ret.Pipeline as PipelineAst;
438450

451+
String result = String.Empty;
452+
439453
// Handle the case with 1 pipeline element first
440454
if (pipe != null && pipe.PipelineElements.Count == 1)
441455
{
@@ -444,24 +458,30 @@ public string GetTypeFromReturnStatementAst(Ast funcAst, ReturnStatementAst ret,
444458
{
445459
if (cmAst.Expression.StaticType != typeof(object))
446460
{
447-
return cmAst.Expression.StaticType.FullName;
461+
result = cmAst.Expression.StaticType.FullName;
448462
}
449-
450-
VariableExpressionAst varAst = cmAst.Expression as VariableExpressionAst;
451-
452-
if (varAst != null)
463+
else
453464
{
454-
return GetVariableTypeFromAnalysis(varAst, funcAst);
455-
}
465+
VariableExpressionAst varAst = cmAst.Expression as VariableExpressionAst;
456466

457-
if (cmAst.Expression is MemberExpressionAst)
458-
{
459-
return GetTypeFromMemberExpressionAstHelper(cmAst.Expression as MemberExpressionAst, funcAst, classes);
467+
if (varAst != null)
468+
{
469+
result = GetVariableTypeFromAnalysis(varAst, funcAst);
470+
}
471+
else if (cmAst.Expression is MemberExpressionAst)
472+
{
473+
result = GetTypeFromMemberExpressionAstHelper(cmAst.Expression as MemberExpressionAst, funcAst, classes);
474+
}
460475
}
461476
}
462477
}
463478

464-
return String.Empty;
479+
if (String.IsNullOrWhiteSpace(result) && pipe != null && pipe.PipelineElements.Count > 0)
480+
{
481+
result = typeof(object).FullName;
482+
}
483+
484+
return result;
465485
}
466486

467487
internal string GetTypeFromMemberExpressionAstHelper(MemberExpressionAst memberAst, Ast scopeAst, IEnumerable<TypeDefinitionAst> classes)
@@ -604,7 +624,7 @@ public int Compare(Tuple<int, int> t1, Tuple<int, int> t2)
604624
/// </summary>
605625
public class FindPipelineOutput : ICustomAstVisitor
606626
{
607-
List<string> outputTypes;
627+
List<Tuple<string, StatementAst>> outputTypes;
608628

609629
IEnumerable<TypeDefinitionAst> classes;
610630

@@ -645,7 +665,7 @@ static FindPipelineOutput()
645665
/// <param name="ast"></param>
646666
public FindPipelineOutput(FunctionDefinitionAst ast, IEnumerable<TypeDefinitionAst> classes)
647667
{
648-
outputTypes = new List<string>();
668+
outputTypes = new List<Tuple<string, StatementAst>>();
649669
Helper.Instance.InitializeVariableAnalysis(ast);
650670
this.classes = classes;
651671
myFunction = ast;
@@ -657,15 +677,12 @@ public FindPipelineOutput(FunctionDefinitionAst ast, IEnumerable<TypeDefinitionA
657677
}
658678

659679
/// <summary>
660-
/// Get list of outputTypes
680+
/// Get list of outputTypes from functiondefinitionast funcast
661681
/// </summary>
662682
/// <returns></returns>
663-
public List<string> OutputTypes
683+
public static List<Tuple<string, StatementAst>> OutputTypes(FunctionDefinitionAst funcAst, IEnumerable<TypeDefinitionAst> classes)
664684
{
665-
get
666-
{
667-
return outputTypes;
668-
}
685+
return (new FindPipelineOutput(funcAst, classes)).outputTypes;
669686
}
670687

671688
/// <summary>
@@ -790,12 +807,12 @@ public object VisitNamedBlock(NamedBlockAst namedBlockAst)
790807
{
791808
if (namedBlockAst != null)
792809
{
793-
foreach (var block in namedBlockAst.Statements)
810+
foreach (StatementAst block in namedBlockAst.Statements)
794811
{
795812
object type = block.Visit(this);
796813
if (type != null && type is string && !String.IsNullOrWhiteSpace(type as string))
797814
{
798-
outputTypes.Add(type as string);
815+
outputTypes.Add(Tuple.Create(type as string, block));
799816
}
800817
}
801818
}
@@ -812,12 +829,12 @@ public object VisitStatementBlock(StatementBlockAst statementBlockAst)
812829
{
813830
if (statementBlockAst != null)
814831
{
815-
foreach (var block in statementBlockAst.Statements)
832+
foreach (StatementAst block in statementBlockAst.Statements)
816833
{
817834
object type = block.Visit(this);
818835
if (type != null && type is string && !String.IsNullOrWhiteSpace(type as string))
819836
{
820-
outputTypes.Add(type as string);
837+
outputTypes.Add(Tuple.Create(type as string, block));
821838
}
822839
}
823840
}

Rules/ReturnCorrectTypesForDSCFunctions.cs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,9 @@ public IEnumerable<DiagnosticRecord> AnalyzeDSCResource(Ast ast, string fileName
3232
{
3333
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
3434

35-
List<string> resourceFunctionNames = new List<string>(new string[] { "Set-TargetResource", "Get-TargetResource", "Test-TargetResource" });
36-
3735
// TODO: Add logic for DSC Resources
3836

39-
IEnumerable<Ast> functionDefinitionAsts = ast.FindAll(item => item is FunctionDefinitionAst && resourceFunctionNames.Contains((item as FunctionDefinitionAst).Name, StringComparer.OrdinalIgnoreCase), true);
37+
IEnumerable<Ast> functionDefinitionAsts = Helper.Instance.DscResourceFunctions(ast);
4038

4139
IEnumerable<TypeDefinitionAst> classes = ast.FindAll(item =>
4240
item is TypeDefinitionAst
@@ -45,11 +43,42 @@ item is TypeDefinitionAst
4543
foreach (FunctionDefinitionAst func in functionDefinitionAsts)
4644
{
4745
Helper.Instance.InitializeVariableAnalysis(func);
48-
var test = new FindPipelineOutput(func, classes);
49-
var test2 = test.OutputTypes;
50-
}
46+
List<Tuple<string, StatementAst>> outputTypes = FindPipelineOutput.OutputTypes(func, classes);
47+
48+
if (String.Equals(func.Name, "Set-TargetResource", StringComparison.OrdinalIgnoreCase))
49+
{
50+
foreach (Tuple<string, StatementAst> outputType in outputTypes)
51+
{
52+
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.ReturnCorrectTypesForSetTargetResourceFunctionsDSCError),
53+
outputType.Item2.Extent, GetName(), DiagnosticSeverity.Strict, fileName);
54+
}
55+
}
56+
else
57+
{
58+
Dictionary<string, string> returnTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
59+
returnTypes["Get-TargetResource"] = typeof(System.Collections.Hashtable).FullName;
60+
returnTypes["Test-TargetResource"] = typeof(bool).FullName;
61+
62+
foreach (Tuple<string, StatementAst> outputType in outputTypes)
63+
{
64+
string type = outputType.Item1;
5165

52-
return Enumerable.Empty<DiagnosticRecord>();
66+
if (String.IsNullOrEmpty(type)
67+
|| String.Equals(typeof(Unreached).FullName, type, StringComparison.OrdinalIgnoreCase)
68+
|| String.Equals(typeof(Undetermined).FullName, type, StringComparison.OrdinalIgnoreCase)
69+
|| String.Equals(typeof(object).FullName, type, StringComparison.OrdinalIgnoreCase)
70+
|| String.Equals(type, returnTypes[func.Name], StringComparison.OrdinalIgnoreCase))
71+
{
72+
continue;
73+
}
74+
else
75+
{
76+
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.ReturnCorrectTypesForGetTestTargetResourceFunctionsDSCResourceError,
77+
func.Name, returnTypes[func.Name], type), outputType.Item2.Extent, GetName(), DiagnosticSeverity.Strict, fileName);
78+
}
79+
}
80+
}
81+
}
5382
}
5483

5584
/// <summary>
@@ -73,8 +102,8 @@ item is TypeDefinitionAst
73102
foreach (TypeDefinitionAst dscClass in dscClasses)
74103
{
75104
Dictionary<string, string> returnTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
76-
returnTypes["Get"] = dscClass.Name;
77105
returnTypes["Test"] = typeof(bool).FullName;
106+
returnTypes["Get"] = dscClass.Name;
78107

79108
foreach (var member in dscClass.Members)
80109
{

Rules/Strings.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rules/Strings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,4 +648,10 @@
648648
<data name="ReturnCorrectTypesForSetFunctionsDSCError" xml:space="preserve">
649649
<value>Set function in DSC Class {0} should not return anything</value>
650650
</data>
651+
<data name="ReturnCorrectTypesForGetTestTargetResourceFunctionsDSCResourceError" xml:space="preserve">
652+
<value>{0} function in DSC Resource should return object of type {1} instead of {2}</value>
653+
</data>
654+
<data name="ReturnCorrectTypesForSetTargetResourceFunctionsDSCError" xml:space="preserve">
655+
<value>Set-TargetResource function in DSC Resource should not output anything to the pipeline.</value>
656+
</data>
651657
</root>

Rules/UseIdenticalMandatoryParametersDSC.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ public IEnumerable<DiagnosticRecord> AnalyzeDSCResource(Ast ast, string fileName
2626
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
2727

2828
// Expected TargetResource functions in the DSC Resource module
29-
List<string> expectedTargetResourceFunctionNames = new List<string>(new string[] { "Set-TargetResource", "Test-TargetResource", "Get-TargetResource" });
30-
31-
IEnumerable<Ast> functionDefinitionAsts = ast.FindAll(item => item is FunctionDefinitionAst && expectedTargetResourceFunctionNames.Contains((item as FunctionDefinitionAst).Name, StringComparer.OrdinalIgnoreCase), true);
29+
List<string> expectedTargetResourceFunctionNames = new List<string>(new string[] { "Set-TargetResource", "Test-TargetResource", "Get-TargetResource" });
30+
31+
IEnumerable<Ast> functionDefinitionAsts = Helper.Instance.DscResourceFunctions(ast);
3232

3333
// Dictionary to keep track of Mandatory parameters and their presence in Get/Test/Set TargetResource cmdlets
3434
Dictionary<string, List<string>> mandatoryParameters = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

Rules/UseIdenticalParametersDSC.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,10 @@ public IEnumerable<DiagnosticRecord> AnalyzeDSCResource(Ast ast, string fileName
3232
{
3333
if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage);
3434

35-
// Expected TargetResource functions in the DSC Resource module
36-
List<string> expectedTargetResourceFunctionNames = new List<string>(new string[] { "Set-TargetResource", "Test-TargetResource" });
37-
3835
// parameters
3936
Dictionary<string, ParameterAst> paramNames = new Dictionary<string, ParameterAst>(StringComparer.OrdinalIgnoreCase);
4037

41-
IEnumerable<Ast> functionDefinitionAsts = ast.FindAll(item => item is FunctionDefinitionAst
42-
&& expectedTargetResourceFunctionNames.Contains((item as FunctionDefinitionAst).Name, StringComparer.OrdinalIgnoreCase), true);
38+
IEnumerable<Ast> functionDefinitionAsts = Helper.Instance.DscResourceFunctions(ast);
4339

4440
if (functionDefinitionAsts.Count() == 2)
4541
{

Tests/Rules/DSCResources/BadDscResource/BadDscResource.psm1

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,14 @@ class FileResource
103103
{
104104
return $present
105105
}
106-
else
106+
elseif ($true)
107107
{
108108
return -not $present
109109
}
110+
else
111+
{
112+
return 5
113+
}
110114
}
111115

112116

@@ -119,20 +123,27 @@ class FileResource
119123
[FileResource] Get()
120124
{
121125
$present = $this.TestFilePath($this.Path)
126+
127+
$hashtable = @{"3"="4"}
122128

123129
if ($present)
124130
{
125131
$file = Get-ChildItem -LiteralPath $this.Path
126132
$this.CreationTime = $file.CreationTime
127133
$this.Ensure = [Ensure]::Present
134+
return $this.TestFilePath($this.Path)
128135
}
129-
else
136+
elseif ($true)
130137
{
131138
$this.CreationTime = $null
132139
$this.Ensure = [Ensure]::Absent
133-
}
140+
return $present
141+
}
142+
else
143+
{
144+
return $hashtable
145+
}
134146

135-
return $this
136147
}
137148

138149
<#

Tests/Rules/DSCResources/MSFT_WaitForAll/MSFT_WaitForAll.psm1

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ function Set-TargetResource
9494
-RetryCount $RetryCount `
9595
-ThrottleLimit $ThrottleLimit
9696
}
97+
98+
$true -and $false
99+
100+
if ($true) {
101+
return 4;
102+
}
97103
}
98104

99105
#
@@ -118,14 +124,22 @@ function Test-TargetResource
118124

119125
Import-Module $PSScriptRoot\..\..\PSDSCxMachine.psm1
120126

121-
return PSDSCxMachine\Test-_InternalPSDscXMachineTR `
122-
-RemoteResourceId $ResourceName `
123-
-RemoteMachine $NodeName `
124-
-RemoteCredential $Credential `
125-
-MinimalNumberOfMachineInState $NodeName.Count `
126-
-RetryIntervalSec $RetryIntervalSec `
127-
-RetryCount $RetryCount `
128-
-ThrottleLimit $ThrottleLimit
127+
$b = @{"Test"=3}
128+
$b
129+
130+
$c = [Math]::Sin(3)
131+
$c
132+
133+
$d = [bool[]]@($true, $false)
134+
135+
$d
136+
137+
foreach ($d in $b)
138+
{
139+
$test
140+
}
141+
142+
return $true
129143
}
130144

131145

0 commit comments

Comments
 (0)