Wednesday, December 7, 2011

ScriptCop rule to find Return in ForEach cmdlet loop

I love the ForEach-Object cmdlet; it embodies the spirit of pipeline processing and helps make logic clear. To me, this simple ForEach-Object cmdlet command:
Split-Path -Path $Home -Parent | dir | foreach {$_.Name}
is far easier to read and understand than this foreach keyword version:
foreach ($Item in (dir (Split-Path -Path $Home -Parent))) {$Item.Name}

However there's a quirky issue with ForEach-Object cmdlet loops: return statements do not work. (They do work in foreach keyword loops.) Here's a quick example:

function f1 {
  foreach ($i in 1..4) { $i; if ($i -gt 2) { return }; "Inside" }
  "After"
}

function f2 {
  1..4 | foreach { $_; if ($_ -gt 2) { return }; "Inside" }
  "After"
}

C:\> f1
1
Inside
2
Inside
3

C:\> f2
1
Inside
2
Inside
3
4
After

As you can see, the return in the foreach keyword loop exits the function entirely at the moment it is run - good! However, in the foreach cmdlet loop the return only exits the current iteration of the loop - it doesn't run/output the following "Inside". But it does keep running the loop AND outputs the "After" after the loop in the function - bad! (See PowerShell in Action V2 section "Using the return statement with 
ForEach-Object" for more information.)

After discovering ScriptCop, the first custom rule I wanted to write for it was a return-inside-foreach-cmdlet detector. So here it is. The rule works by matching the contents of a foreach cmdlet loop using a regex then it takes the matching content, tokenizes it and looks for return Keyword. The regex is a little nasty but works great. Tokenizing the content and searching through the tokens was far easier and more accurate than trying to match the word 'return' with a regular expression that didn't accidentally match that word in a string, comment or block comment.

See the ScriptCop documentation for notes about adding this file to your setup. If you aren't familiar with ScriptCop, check out the ScriptCop site or these other links:
The first time you run it you'll find a whole bunch of items to clean up but after that it's easy to keep your code nice and clean and proper.