Index: coder.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/Attic/coder.install,v
retrieving revision 1.1.4.2
diff -u -u -p -r1.1.4.2 coder.install
--- coder.install	21 Jan 2008 11:23:26 -0000	1.1.4.2
+++ coder.install	20 Aug 2008 23:03:54 -0000
@@ -14,3 +14,9 @@ function coder_uninstall() {
   cache_clear_all('coder:', 'cache', true);
 }
 
+function coder_update_1() {
+  $ret = array();
+  // This update adds a theming function, so we need to clear the entire cache.
+  $ret[] = update_sql("DELETE FROM {cache}");
+  return $ret;
+}
Index: coder.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/coder.module,v
retrieving revision 1.88.2.41
diff -u -u -p -r1.88.2.41 coder.module
--- coder.module	20 Aug 2008 16:10:16 -0000	1.88.2.41
+++ coder.module	20 Aug 2008 23:03:54 -0000
@@ -940,6 +940,7 @@ function do_coder_reviews($coder_args) {
   // Read the file.
   if (_coder_read_and_parse_file($coder_args)) {
     // Do all of the code reviews.
+    $errors = array();
     foreach ($coder_args['#reviews'] as $review) {
       if ($result = do_coder_review($coder_args, $review)) {
         foreach (array('critical', 'normal', 'minor') as $severity_level) {
@@ -947,7 +948,14 @@ function do_coder_reviews($coder_args) {
             $results['#stats'][$severity_level] += $result['#stats'][$severity_level];
           }
         }
-        $results += $result;
+        $errors += $result;
+      }
+    }
+
+    // Theme the error messages.
+    foreach ($errors as $key => $error) {
+      if (is_numeric($key)) {
+        $results[$key] = theme('coder_warning_msg', $error);
       }
     }
 
@@ -955,7 +963,7 @@ function do_coder_reviews($coder_args) {
     ksort($results, SORT_NUMERIC);
   }
   else {
-    _coder_error_msg($results, t('Could not read the file'), 'critical');
+    $results[] = theme('coder_warning', t('Could not read the file'), 'critical');
   }
 
   // Save the results in the cache.
@@ -1473,51 +1481,13 @@ function do_coder_review_regex(&$coder_a
  * @param $original
  *   Deprecated.
  */
-function _coder_error(&$results, $rule, $severity_name, $lineno = -1, $line = '', $original = '') {
-  if (isset($rule['#warning_callback'])) {
-    if (function_exists($rule['#warning_callback'])) {
-      $warning = $rule['#warning_callback']();
-    }
-    else { // If this happens, there is an error in the rule definition.
-      $warning = t('please <a href="@report">report</a> this !warning',
-        array(
-          '@report' => 'http://drupal.org/node/add/project_issue/coder/bug',
-          '!warning' => $rule['#warning_callback'],
-        )
-      );
-    }
-  }
-  else {
-    $warning = t($rule['#warning']);
-  }
-
-  return _coder_error_msg($results, $warning, $severity_name, $lineno, $line);
-}
-
-/**
- * Does the actual saving of error to results array and generating its
- * unique numeric id.
- *
- * @param $results
- *   Results array variable to save errors to.
- * @param $warning
- *   Warning array/string to be themed, returned from '#warning_callback' or
- *   is a translated string from the rule. See theme_coder_warning() for
- *   array format.
- * @param $severity_name
- *   String severity of error.
- * @param $lineno
- *   Integer line number error occured on.
- * @param $line
- *   String line contents.
- */
-function _coder_error_msg(&$results, $warning, $severity_name, $lineno = -1, $line = '') {
+function _coder_error(&$results, $rule, $severity_name, $lineno = -1, $line = '') {
   // Note: The use of the $key allows multiple errors on one line.
   // This assumes that no line of source has more than 10000 lines of code
   // and that we have fewer than 10000 errors.
   global $_coder_errno;
   $key = ($lineno + 1) * 10000 + ($_coder_errno ++);
-  $results[$key] = theme('coder_warning', $warning, $severity_name, $lineno + 1, $line);
+  $results[$key] = array('rule' => $rule, 'severity_name' => $severity_name, 'lineno' => $lineno, 'line' => $line);
   $results['#stats'][$severity_name] ++;
 }
 
@@ -1664,6 +1634,26 @@ function _coder_is_drupal_core($module) 
   return isset($core[$module->name]) ? 1 : 0;
 }
 
+/**
+ * Implementation of hook_simpletest().
+ */
+function coder_simpletest() {
+  return array_keys(file_scan_directory(drupal_get_path('module', 'coder') .'/tests', '\.test'));
+}
+
+/**
+ * Helper function to run the review on the code snippet.
+ */
+function coder_test($code, $review, $severity = SEVERITY_MINOR) {
+  $coder_args = array(
+    '#severity' => $severity,
+    '#filename' => 'snippet',
+    '#patch' => $code,
+  );
+  _coder_read_and_parse_file($coder_args);
+  return do_coder_review($coder_args, $reviews['style']);
+}
+
 // Theming functions
 
 /**
@@ -1673,6 +1663,7 @@ function coder_theme() {
   return array(
     'coder' => array('arguments' => array('name', 'filename', 'results')),
     'coder_warning' => array('arguments' => array('warning', 'severity_name', 'lineno', 'line')),
+    'coder_warning_msg' => array('arguments' => array('error')),
     'cols' => array('arguments' => array('form')),
     'drupalapi' => array('arguments' => array('function', 'version')),
   );
@@ -1703,6 +1694,37 @@ function theme_coder($name, $filename, $
 }
 
 /**
+ * Format a coder warning to be included in results, creating the text.
+ *
+ * @param $results
+ *   Results array variable to save errors to.
+ * @param $error
+ *   Error array from _coder_error().
+ */
+function theme_coder_warning_msg($error) {
+  if (isset($error['rule']['#warning_callback'])) {
+    if (function_exists($error['rule']['#warning_callback'])) {
+      $warning = $error['rule']['#warning_callback']();
+    }
+    else { // If this happens, there is an error in the rule definition.
+      $warning = t('please <a href="@report">report</a> this !warning',
+        array(
+          '@report' => 'http://drupal.org/node/add/project_issue/coder/bug',
+          '!warning' => $error['rule']['#warning_callback'],
+        )
+      );
+    }
+  }
+  else {
+    $warning = t($error['rule']['#warning']);
+  }
+
+  // @TODO: we can combine theme_coder_warning() and theme_coder_warning_msg(),
+  // But let's do this on the 7.x upgrade in case anyone's actually using it.
+  return theme('coder_warning', $warning, $error['severity_name'], $error['lineno'], $error['line']);
+}
+
+/**
  * Format a coder warning to be included in results.
  *
  * @param $warning
Index: includes/coder_style.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/includes/coder_style.inc,v
retrieving revision 1.16.2.17
diff -u -u -p -r1.16.2.17 coder_style.inc
--- includes/coder_style.inc	14 Jul 2008 13:36:29 -0000	1.16.2.17
+++ includes/coder_style.inc	20 Aug 2008 23:03:54 -0000
@@ -71,6 +71,7 @@ function coder_style_reviews() {
       '#type' => 'callback',
       '#source' => 'all',
       '#value' => '_coder_style_callback',
+      '#warning' => 'the final ?> should be omitted from all code files',
     ),
     array(
       '#type' => 'regex',
@@ -165,7 +166,7 @@ function _coder_style_callback(&$coder_a
   }
   if ($last && $lastline && preg_match('/\?>\s*$/i', $lastline)) {
     $severity_name = _coder_severity_name($coder_args, $review, $rule);
-    _coder_error_msg($results, 'the final ?> should be omitted from all code files', $severity_name, count($lines));
+    _coder_error($results, $rule, $severity_name, count($lines));
   }
 }
 
Index: tests/coder_style.test
===================================================================
RCS file: tests/coder_style.test
diff -N tests/coder_style.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/coder_style.test	20 Aug 2008 23:03:54 -0000
@@ -0,0 +1,45 @@
+<?php
+define(CODER_OK, 1);
+define(CODER_NOT_OK, 0);
+
+class CoderStyleTest extends DrupalTestCase {
+  function get_info() {
+    return array(
+      'name' => t('Coder Style Tests'),
+      'desc' => t('Tests for the coder style review.'),
+      'group' => 'Coder'
+    );
+  }
+  
+  function setUp() {
+  }
+
+  function testStyle() {
+    $snippets = array(
+      '$some_array[FOO_BAR] = $baz;' => CODER_OK,
+      '$some_array[foo_bar] = $baz;' => CODER_NOT_OK,
+      '// Tab in	comment' => CODER_OK,
+      '$var = "tab in	double quote"' => CODER_OK,
+      '$var = \'tab in	single quote\'' => CODER_OK,
+      '	$var = "tab in line";' => CODER_NOT_OK,
+    );
+
+    $reviews = coder_reviews();
+    foreach ($snippets as $code => $status) {
+      $coder_args = array(
+        '#severity' => SEVERITY_MINOR,
+        '#filename' => 'coder_style.test',
+        '#patch' => $code,
+      );
+      _coder_read_and_parse_file($coder_args);
+      $results = do_coder_review($coder_args, $reviews['style']);
+
+      if ($status == CODER_OK) {
+        $this->assertTrue(count($results) == 1, 'Expect NO warning: '. $code);
+      }
+      else {
+        $this->assertTrue(count($results) > 1, 'Expect a warning: '. $code);
+      }
+    }
+  }
+}
