Skip to content

Commit f45c20b

Browse files
author
Kapil Borle
authored
Merge pull request #759 from PowerShell/kapilmb/fix-align-assignment-rule
Fix align assignment statements in DSC configurations
2 parents 950e5f2 + 3fcb226 commit f45c20b

File tree

4 files changed

+160
-31
lines changed

4 files changed

+160
-31
lines changed

CHANGELOG.MD

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
## [1.12.0](https://github.com/PowerShell/PSScriptAnalyzer/tree/1.11.1) - 2017-05-09
1+
## [unreleased]
2+
3+
## Fixed
4+
- `PSAlignAssignmentStatement` to align assignment statements in DSC configurations that have *Undefined DSC Resource* parse errors.
5+
6+
## [1.12.0](https://github.com/PowerShell/PSScriptAnalyzer/tree/1.11.1) - 2017-05-09
27

38
### Added
49
- [PSAlignAssignmentRuleStatement](https://github.com/PowerShell/PSScriptAnalyzer/blob/cca9a2d7ee35be7322f8c5a09b6c500a0a8bd101/RuleDocumentation/AlignAssignmentStatement.md) rule to align assignment statements in property value pairs (#753).

Engine/Commands/InvokeScriptAnalyzerCommand.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,16 @@ public SwitchParameter SaveDscDependency
206206
}
207207
private bool saveDscDependency;
208208
#endif // !PSV3
209+
210+
#if DEBUG
211+
[Parameter(Mandatory = false)]
212+
public SwitchParameter AttachAndDebug
213+
{
214+
get { return attachAndDebug; }
215+
set { attachAndDebug = value; }
216+
}
217+
private bool attachAndDebug = false;
218+
#endif
209219
#endregion Parameters
210220

211221
#region Overrides
@@ -216,6 +226,19 @@ public SwitchParameter SaveDscDependency
216226
protected override void BeginProcessing()
217227
{
218228
// Initialize helper
229+
#if DEBUG
230+
if (attachAndDebug)
231+
{
232+
if (System.Diagnostics.Debugger.IsAttached)
233+
{
234+
System.Diagnostics.Debugger.Break();
235+
}
236+
else
237+
{
238+
System.Diagnostics.Debugger.Launch();
239+
}
240+
}
241+
#endif
219242
Helper.Instance = new Helper(
220243
SessionState.InvokeCommand,
221244
this);

Rules/AlignAssignmentStatement.cs

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,33 @@ public override SourceType GetSourceType()
145145
private IEnumerable<DiagnosticRecord> FindHashtableViolations(TokenOperations tokenOps)
146146
{
147147
var hashtableAsts = tokenOps.Ast.FindAll(ast => ast is HashtableAst, true);
148-
if (hashtableAsts == null)
148+
var groups = new List<List<Tuple<IScriptExtent, IScriptExtent>>>();
149+
if (hashtableAsts != null)
149150
{
150-
yield break;
151+
foreach (var astItem in hashtableAsts)
152+
{
153+
groups.Add(GetExtents(tokenOps, (HashtableAst)astItem));
154+
}
151155
}
152156

157+
#if !PSV3
158+
var configAsts = tokenOps.Ast.FindAll(ast => ast is ConfigurationDefinitionAst, true);
159+
if (configAsts != null)
160+
{
161+
// There are probably parse errors caused by an "Undefined DSC resource"
162+
// which prevents the parser from detecting the property value pairs as
163+
// hashtable. Hence, this is a workaround to format configurations which
164+
// have "Undefined DSC resource" parse errors.
165+
166+
// find all commandAsts of the form "prop" "=" "val" that have the same parent
167+
// and format those pairs.
168+
foreach (var configAst in configAsts)
169+
{
170+
groups.AddRange(GetCommandElementExtentGroups(configAst));
171+
}
172+
}
173+
#endif
174+
153175
// it is probably much easier have a hashtable writer that formats the hashtable and writes it
154176
// but it makes handling comments hard. So we need to use this approach.
155177

@@ -162,16 +184,13 @@ private IEnumerable<DiagnosticRecord> FindHashtableViolations(TokenOperations to
162184
// find the distance between the assignment operators and their corresponding LHS
163185
// find the longest left expression
164186
// make sure all the assignment operators are in the same column as that of the longest left hand.
165-
166-
foreach (var astItem in hashtableAsts)
187+
foreach (var extentTuples in groups)
167188
{
168-
var hashtableAst = (HashtableAst)astItem;
169-
if (!HasKeysOnSeparateLines(hashtableAst))
189+
if (!HasPropertiesOnSeparateLines(extentTuples))
170190
{
171191
continue;
172192
}
173193

174-
var extentTuples = GetExtents(tokenOps, hashtableAst);
175194
if (extentTuples == null
176195
|| extentTuples.Count == 0
177196
|| !extentTuples.All(t => t.Item1.StartLineNumber == t.Item2.EndLineNumber))
@@ -181,11 +200,12 @@ private IEnumerable<DiagnosticRecord> FindHashtableViolations(TokenOperations to
181200

182201
var widestKeyExtent = extentTuples
183202
.Select(t => t.Item1)
184-
.Aggregate((t1, tAggregate) => {
185-
return TokenOperations.GetExtentWidth(tAggregate) > TokenOperations.GetExtentWidth(t1)
186-
? tAggregate
187-
: t1;
188-
});
203+
.Aggregate((t1, tAggregate) =>
204+
{
205+
return TokenOperations.GetExtentWidth(tAggregate) > TokenOperations.GetExtentWidth(t1)
206+
? tAggregate
207+
: t1;
208+
});
189209
var expectedStartColumnNumber = widestKeyExtent.EndColumnNumber + 1;
190210
foreach (var extentTuple in extentTuples)
191211
{
@@ -204,6 +224,53 @@ private IEnumerable<DiagnosticRecord> FindHashtableViolations(TokenOperations to
204224
}
205225
}
206226

227+
private List<List<Tuple<IScriptExtent, IScriptExtent>>> GetCommandElementExtentGroups(Ast configAst)
228+
{
229+
var result = new List<List<Tuple<IScriptExtent, IScriptExtent>>>();
230+
var commandAstGroups = GetCommandElementGroups(configAst);
231+
foreach (var commandAstGroup in commandAstGroups)
232+
{
233+
var list = new List<Tuple<IScriptExtent, IScriptExtent>>();
234+
foreach (var commandAst in commandAstGroup)
235+
{
236+
var elems = commandAst.CommandElements;
237+
list.Add(new Tuple<IScriptExtent, IScriptExtent>(elems[0].Extent, elems[1].Extent));
238+
}
239+
240+
result.Add(list);
241+
}
242+
243+
return result;
244+
}
245+
246+
private List<List<CommandAst>> GetCommandElementGroups(Ast configAst)
247+
{
248+
var result = new List<List<CommandAst>>();
249+
var astsFound = configAst.FindAll(ast => IsPropertyValueCommandAst(ast), true);
250+
if (astsFound == null)
251+
{
252+
return result;
253+
}
254+
255+
var parentChildrenGroup = from ast in astsFound
256+
select (CommandAst)ast into commandAst
257+
group commandAst by commandAst.Parent.Parent; // parent is pipeline and pipeline's parent is namedblockast
258+
foreach (var group in parentChildrenGroup)
259+
{
260+
result.Add(group.ToList());
261+
}
262+
263+
return result;
264+
}
265+
266+
private bool IsPropertyValueCommandAst(Ast ast)
267+
{
268+
var commandAst = ast as CommandAst;
269+
return commandAst != null
270+
&& commandAst.CommandElements.Count() == 3
271+
&& commandAst.CommandElements[1].Extent.Text.Equals("=");
272+
}
273+
207274
private IEnumerable<CorrectionExtent> GetHashtableCorrections(
208275
Tuple<IScriptExtent, IScriptExtent> extentTuple,
209276
int expectedStartColumnNumber)
@@ -225,7 +292,7 @@ private string GetError()
225292
return String.Format(CultureInfo.CurrentCulture, Strings.AlignAssignmentStatementError);
226293
}
227294

228-
private static IList<Tuple<IScriptExtent, IScriptExtent>> GetExtents(
295+
private static List<Tuple<IScriptExtent, IScriptExtent>> GetExtents(
229296
TokenOperations tokenOps,
230297
HashtableAst hashtableAst)
231298
{
@@ -250,18 +317,18 @@ private static IList<Tuple<IScriptExtent, IScriptExtent>> GetExtents(
250317
return nodeTuples;
251318
}
252319

253-
private bool HasKeysOnSeparateLines(HashtableAst hashtableAst)
320+
private bool HasPropertiesOnSeparateLines(IEnumerable<Tuple<IScriptExtent, IScriptExtent>> tuples)
254321
{
255322
var lines = new HashSet<int>();
256-
foreach (var kvp in hashtableAst.KeyValuePairs)
323+
foreach (var kvp in tuples)
257324
{
258-
if (lines.Contains(kvp.Item1.Extent.StartLineNumber))
325+
if (lines.Contains(kvp.Item1.StartLineNumber))
259326
{
260327
return false;
261328
}
262329
else
263330
{
264-
lines.Add(kvp.Item1.Extent.StartLineNumber);
331+
lines.Add(kvp.Item1.StartLineNumber);
265332
}
266333
}
267334

Tests/Rules/AlignAssignmentStatement.tests.ps1

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ Import-Module PSScriptAnalyzer
55
Import-Module (Join-Path $testRootDirectory "PSScriptAnalyzerTestHelper.psm1")
66

77
$ruleConfiguration = @{
8-
Enable = $true
8+
Enable = $true
99
CheckHashtable = $true
1010
}
1111

1212
$settings = @{
1313
IncludeRules = @("PSAlignAssignmentStatement")
14-
Rules = @{
14+
Rules = @{
1515
PSAlignAssignmentStatement = $ruleConfiguration
1616
}
1717
}
@@ -26,30 +26,30 @@ $hashtable = @{
2626
}
2727
'@
2828

29-
# Expected output after correction should be the following
30-
# $hashtable = @{
31-
# property1 = "value"
32-
# anotherProperty = "another value"
33-
# }
29+
# Expected output after correction should be the following
30+
# $hashtable = @{
31+
# property1 = "value"
32+
# anotherProperty = "another value"
33+
# }
3434

3535
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
3636
$violations.Count | Should Be 1
3737
Test-CorrectionExtentFromContent $def $violations 1 ' ' ' '
3838
}
3939

40-
It "Should find violation when assignment statements are not aligned (whitespace needs to be removed)" {
40+
It "Should find violation when assignment statements are not aligned (whitespace needs to be removed)" {
4141
$def = @'
4242
$hashtable = @{
4343
property1 = "value"
4444
anotherProperty = "another value"
4545
}
4646
'@
4747

48-
# Expected output should be the following
49-
# $hashtable = @{
50-
# property1 = "value"
51-
# anotherProperty = "another value"
52-
# }
48+
# Expected output should be the following
49+
# $hashtable = @{
50+
# property1 = "value"
51+
# anotherProperty = "another value"
52+
# }
5353

5454
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings
5555
$violations.Count | Should Be 1
@@ -87,5 +87,39 @@ Configuration MyDscConfiguration {
8787
'@
8888
Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Get-Count | Should Be 2
8989
}
90+
}
91+
92+
if ($PSVersionTable.PSVersion.Major -ge 5) {
93+
Context "When assignment statements are in DSC Configuration that has parse errors" {
94+
It "Should find violations when assignment statements are not aligned" {
95+
$def = @'
96+
Configuration Sample_ChangeDescriptionAndPermissions
97+
{
98+
Import-DscResource -Module NonExistentModule
99+
# A Configuration block can have zero or more Node blocks
100+
Node $NodeName
101+
{
102+
# Next, specify one or more resource blocks
103+
104+
NonExistentModule MySMBShare
105+
{
106+
Ensure = "Present"
107+
Name = "MyShare"
108+
Path = "C:\Demo\Temp"
109+
ReadAccess = "author"
110+
FullAccess = "some other author"
111+
Description = "This is an updated description for this share"
112+
}
113+
}
90114
}
115+
'@
116+
# This invocation will throw parse error caused by "Undefined DSC resource" because
117+
# NonExistentModule is not really avaiable to load. Therefore we set erroraction to
118+
# SilentlyContinue
119+
Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings -ErrorAction SilentlyContinue |
120+
Get-Count |
121+
Should Be 4
122+
}
123+
}
124+
}
91125
}

0 commit comments

Comments
 (0)