Index: coder.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/coder.module,v
retrieving revision 1.88.2.59.2.1
diff -u -u -p -r1.88.2.59.2.1 coder.module
--- coder.module	28 Sep 2008 03:05:01 -0000	1.88.2.59.2.1
+++ coder.module	28 Sep 2008 17:55:40 -0000
@@ -350,7 +350,6 @@ function _coder_settings_form($settings,
     }
     asort($system_links);
 
-
     // Display what to review options.
     $form['coder_what'] = array(
       '#type' => 'fieldset',
@@ -369,10 +368,11 @@ function _coder_settings_form($settings,
       '#default_value' => isset($settings['coder_core']) ? $settings['coder_core'] : 0,
       '#title' => t('core files (php, modules, and includes)'),
     );
+    $ext = variable_get('coder_php_ext', array('inc', 'php', 'install', 'test'));
     $form['coder_what']['coder_includes'] = array(
       '#type' => 'checkbox',
       '#default_value' => isset($settings['coder_includes']) ? $settings['coder_includes'] : 0,
-      '#title' => t('include files (.inc, .php, .test, .js, and .install files)'),
+      '#title' => t('include files (@ext)', array('@ext' => implode(' | ', $ext))),
     );
     $form['coder_what']['coder_includes_exclusion_fieldset'] = array(
       '#type' => 'fieldset',
@@ -653,6 +653,25 @@ function coder_page_form_submit($form, &
 }
 
 /**
+ * Return the extensions used by the reviews, that aren's part of the default extensions.
+ */
+function _coder_get_reviews_extensions($defaults, $reviews) {
+  $extensions = array();
+  foreach ($reviews as $review) {
+    foreach ($review['#rules'] as $rule) {
+      if (isset($rule['#filename'])) {
+        foreach ($rule['#filename'] as $ext) {
+          if (!in_array($ext, $defaults) && !in_array($ext, $extensions)) {
+            $extensions[] = $ext;
+          }
+        }
+      }
+    }
+  }
+  return $extensions;
+}
+
+/**
  * Implementation of hook_form().
  *
  * Implements coder's main form, in which a user can select reviews and
@@ -723,6 +742,11 @@ function coder_page_form($form_state) {
 
     // Loop through the selected modules and themes.
     if (isset($system)) {
+      // Determine which file extensions to include, based on the rules.
+      $php_extensions = variable_get('coder_php_ext', array('inc', 'php', 'install', 'test'));
+      $include_extensions = _coder_get_reviews_extensions($php_extensions, $reviews);
+      $includes = array_merge($php_extensions, $include_extensions);
+
       // Used to avoid duplicate includes.
       $dups = array();
       $stats = array();
@@ -744,6 +768,8 @@ function coder_page_form($form_state) {
             '#severity' => $settings['coder_severity'],
             '#filename' => $filename,
             '#patch' => $patch,
+            '#php_extensions' => $php_extensions,
+            '#include_extensions' => $include_extensions,
           );
           $results = do_coder_reviews($coder_args);
           $stats[$filename] = $results['#stats'];
@@ -778,7 +804,8 @@ function coder_page_form($form_state) {
                   $coder_args['#php_minor'] = 1;
                 }
                 $dups[$path] = 1;
-                $includefiles = drupal_system_listing('.*\.(inc|php|js|install|test)$', dirname($filename), 'filename', 0);
+                $regex = '\.('. implode('|', $includes) .')$';
+                $includefiles = drupal_system_listing($regex, dirname($filename), 'filename', 0);
                 $offset = strpos($filename, dirname($filename));
                 $stats[$filename]['#includes'] = _coder_page_form_includes($form, $coder_args, $name, $includefiles, $offset, $coder_includes_exclusions);
               }
@@ -1010,6 +1037,23 @@ function do_coder_reviews($coder_args) {
  *   Integer 1 if success.
  */
 function _coder_read_and_parse_file(&$coder_args) {
+  // If this isn't a php file, then don't try to parse it.
+  $regex = '/\.('. implode('|', array_merge(array('module'), $coder_args['#php_extensions'])) .')$/';
+  if (!preg_match($regex, $coder_args['#filename'])) {
+    if ((($filepath = realpath($coder_args['#filename'])) && file_exists($filepath))) {
+      $full_lines = file($filepath);
+      if ($full_lines[0] != '<?php') {
+        foreach ($full_lines as $lineno => $line) {
+          if (($line = trim($line, "\r\n")) != '') {
+            $all_lines[$lineno] = $line;
+          }
+        }
+        $coder_args['#all_lines'] = $all_lines;
+        return 1;
+      }
+    }
+  }
+
   // Get the path to the module file.
   if (!empty($coder_args['#patch']) || !empty($coder_args['#test']) || (($filepath = realpath($coder_args['#filename'])) && file_exists($filepath))) {
     $in_php = 0;
@@ -1018,7 +1062,7 @@ function _coder_read_and_parse_file(&$co
 
     if (!empty($coder_args['#patch'])) {
       $content = $coder_args['#patch'];
-      if (preg_match("/^\s*\*/", $content)) {
+      if (preg_match('/^\s*\*/', $content)) {
         $in_comment = '*';
       }
       else {
@@ -1434,16 +1478,17 @@ function do_coder_review($coder_args, $r
     foreach ($review['#rules'] as $rule) {
       // Ignore rules that don't match the file extension.
       $filename = empty($coder_args['#patch']) ? $coder_args['#filename'] : 'coder.patch';
-      if (!empty($rule['#filename'])) {
-        $regex = '/'. $rule['#filename'] .'/i';
+      if ($filename_includes = isset($rule['#filename']) ? $rule['#filename'] : (isset($rule['#filename-not']) ? $coder_args['#php_extensions'] : null)) {
+        $regex = '/\.('. implode('|', $filename_includes) .')$/i';
         if (!preg_match($regex, $filename, $matches)) {
           continue;
         }
       }
+
       // Ignore rules that do match the file extension, javascript files are excluded by default.
-      $not = isset($rule['#filename-not']) ? $rule['#filename-not'] : (empty($rule['#filename']) || !preg_match('/\\\.js\$/i', $rule['#filename'], $matches) ? '\.js$' : null);
+      $not = isset($rule['#filename-not']) ? $rule['#filename-not'] : (isset($rule['#filename']) ? null : $coder_args['#include_extensions']);
       if ($not) {
-        $regex = '/'. $not .'/i';
+        $regex = '/\.('. implode('|', $not) .')$/i';
         if (preg_match($regex, $filename, $matches)) {
           continue;
         }
@@ -1457,23 +1502,21 @@ function do_coder_review($coder_args, $r
           $lines = $coder_args[$source];
         }
         else {
-          $lines = $coder_args['#php_array_lines'];
+          $lines = isset($coder_args['#php_array_lines']) ? $coder_args['#php_array_lines'] : array();
         }
-        if ($lines) {
-          switch ($rule['#type']) {
-            case 'regex':
-              do_coder_review_regex($coder_args, $review, $rule, $lines, $results);
-              break;
-            case 'grep':
-              do_coder_review_grep($coder_args, $review, $rule, $lines, $results);
-              break;
-            case 'grep_invert':
-              do_coder_review_grep_invert($coder_args, $review, $rule, $lines, $results);
-              break;
-            case 'callback':
-              do_coder_review_callback($coder_args, $review, $rule, $lines, $results);
-              break;
-          }
+        switch ($rule['#type']) {
+          case 'regex':
+            do_coder_review_regex($coder_args, $review, $rule, $lines, $results);
+            break;
+          case 'grep':
+            do_coder_review_grep($coder_args, $review, $rule, $lines, $results);
+            break;
+          case 'grep_invert':
+            do_coder_review_grep_invert($coder_args, $review, $rule, $lines, $results);
+            break;
+          case 'callback':
+            do_coder_review_callback($coder_args, $review, $rule, $lines, $results);
+            break;
         }
       }
     }
@@ -1731,13 +1774,16 @@ function coder_simpletest() {
  * Helper function to run the review on the code snippet.
  */
 function coder_test($code, $type, $severity = SEVERITY_MINOR) {
+  $reviews = coder_reviews();
+  $ext = variable_get('coder_php_ext', array('inc', 'php', 'install', 'test'));
   $coder_args = array(
     '#severity' => $severity,
     '#filename' => 'snippet',
     '#test' => $code,
+    '#php_extensions' => $ext,
+    '#include_extensions' => _coder_get_reviews_extensions($ext, $reviews),
   );
   _coder_read_and_parse_file($coder_args);
-  $reviews = coder_reviews();
   $results = do_coder_review($coder_args, $reviews[$type]);
   unset($results['#stats']);
   return $results;
Index: includes/coder_comment.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/includes/coder_comment.inc,v
retrieving revision 1.1.4.11
diff -u -u -p -r1.1.4.11 coder_comment.inc
--- includes/coder_comment.inc	20 Aug 2008 16:08:47 -0000	1.1.4.11
+++ includes/coder_comment.inc	28 Sep 2008 17:55:40 -0000
@@ -17,7 +17,7 @@ function coder_comment_reviews() {
       '#value' => '$Id',
       '#case-sensitive' => TRUE,
       '#warning_callback' => '_coder_comment_Id_warning',
-      '#filename-not' => '\.patch$',
+      '#filename-not' => array('patch'),
     ),
     array(
       '#type' => 'regex',
@@ -84,7 +84,7 @@ function coder_comment_reviews() {
       '#source' => 'comment',
       '#value' => '@'.'file',
       '#warning_callback' => '_coder_comment_missing_file_block',
-      '#filename-not' => '\.patch$',
+      '#filename-not' => array('patch'),
     ),
     array(
       '#type' => 'regex',
Index: includes/coder_i18n.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/includes/Attic/coder_i18n.inc,v
retrieving revision 1.1.2.11
diff -u -u -p -r1.1.2.11 coder_i18n.inc
--- includes/coder_i18n.inc	28 Sep 2008 02:49:30 -0000	1.1.2.11
+++ includes/coder_i18n.inc	28 Sep 2008 17:55:40 -0000
@@ -15,7 +15,7 @@ function coder_i18n_reviews() {
     array(
       '#type' => 'regex',
       '#value' => '[\s\(]l\s*\(\s*["\']',
-      '#filename-not' => '\.install$',
+      '#filename-not' => array('install'),
       '#warning_callback' => '_coder_i18n_l_without_t',
     ),
     array(
@@ -33,7 +33,7 @@ function coder_i18n_reviews() {
     array(
       '#type' => 'regex',
       '#value' => '[\s\(]alert\s*\(\s*[\'"]',
-      '#filename' => '\.js$',
+      '#filename' => array('js'),
       '#warning' => 'Javascript strings should be passed through Drupal.t().',
     ),
     array(
@@ -83,6 +83,11 @@ function coder_i18n_reviews() {
       '#source' => 'allphp',
       '#warning_callback' => '_coder_i18n_space_starts_or_ends_t',
     ),
+    array(
+      '#type' => 'callback',
+      '#filename' => array('po'),
+      '#value' => '_coder_translation_callback',
+    ),
   );
   $review = array(
     '#title' => t('Internationalization'),
@@ -92,6 +97,91 @@ function coder_i18n_reviews() {
 }
 
 /**
+ * Define the rule callbacks for style.
+ */
+function _coder_translation_callback(&$coder_args, $review, $rule, $lines, &$results) {
+  $severity_name = _coder_severity_name($coder_args, $review, $rule);
+
+  // Parse the translation file into msgid/msgstr pairs.
+  $translations = array();
+  foreach ($coder_args['#all_lines'] as $lineno => $line) {
+    if (preg_match('/^(msgid|msgstr) "(.*)"$/', $line, $matches)) {
+      if ($matches[1] == 'msgid') {
+        $msgid = $matches[2];
+      }
+      elseif (!empty($msgid)) {
+        $translations[$lineno] = array('msgid' => $msgid, 'msgstr' => $matches[2]);
+        unset($msgid);
+      }
+    }
+  }
+
+  // Check each translation.
+  foreach ($translations as $lineno => $translation) {
+    $msgid = $translation['msgid'];
+    $msgstr = $translation['msgstr'];
+
+    // Check the translation first capitable letter.
+    if (ctype_upper($msgid[0]) != ctype_upper($msgstr[0])) {
+      $rule = array(
+        '#warning' => "The first letter in the translation text should have the same capitalization as it's original text.",
+      );
+      _coder_error($results, $rule, $severity_name, $lineno, $msgstr);
+    }
+
+    // Check punctuation characters.
+    if (preg_match_all('/[\.,:;?!]/', $msgid, $matches)) {
+      foreach ($matches[0] as $html) {
+        if (stripos($msgstr, $html) === FALSE) {
+          $rule = array(
+            '#warning' => 'Punctuation characters (.,:;?!) from the original text should exist in the translation.',
+          );
+          _coder_error($results, $rule, $severity_name, $lineno, $msgstr);
+        }
+      }
+    }
+    if (preg_match_all('/[\.,:;?!]/', $msgstr, $matches)) {
+      foreach ($matches[0] as $html) {
+        if (stripos($msgid, $html) === FALSE) {
+          $rule = array(
+            '#warning' => 'Punctuations characters (.,:;?!) in the translation should also exist in the original text.',
+          );
+          _coder_error($results, $rule, $severity_name, $lineno, $msgstr);
+        }
+      }
+    }
+
+    // Check HTML tags.
+    if (preg_match_all('/<[^>]*>/', $msgid, $matches)) {
+      foreach ($matches[0] as $html) {
+        if (stripos($msgstr, $html) === FALSE) {
+          $rule = array(
+            '#warning' => 'HTML from the original text should also exist in the translation.'
+          );
+          _coder_error($results, $rule, $severity_name, $lineno, $msgstr);
+        }
+      }
+    }
+
+    // Check placeholders.
+    if (preg_match_all('/[\!\@\%]\w+/', $msgid, $matches)) {
+      foreach ($matches[0] as $placeholder) {
+        if (stripos($msgstr, $placeholder) === FALSE) {
+          $rule = array(
+            '#warning' => 'If placeholders like !name, @name or %name exist in the original text, they must also exist in the translation.',
+          );
+          _coder_error($results, $rule, $severity_name, $lineno, $msgstr);
+        }
+      }
+    }
+
+    // @TODO: Check translations for opening/closing tags if they contain HTML.
+    // @TODO: Quotation checks.
+    // @TODO: Parenthesis (()[]{}) checks.
+  }
+}
+
+/**
  * Define the warning callbacks.
  */
 
Index: includes/coder_sql.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/includes/Attic/coder_sql.inc,v
retrieving revision 1.1.4.12
diff -u -u -p -r1.1.4.12 coder_sql.inc
--- includes/coder_sql.inc	23 Sep 2008 13:51:50 -0000	1.1.4.12
+++ includes/coder_sql.inc	28 Sep 2008 17:55:40 -0000
@@ -49,7 +49,7 @@ function coder_sql_reviews() {
     ),
     array(
       '#type' => 'regex',
-      '#filename' => '\.install$',
+      '#filename' => array('install'),
       '#function' => '_update_[0-9]+$',
       '#source' => 'allphp',
       '#value' => 'db_query\s*\(\s*[\'"](UPDATE|INSERT|DELETE)\s+',
Index: includes/coder_style.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/coder/includes/coder_style.inc,v
retrieving revision 1.16.2.18.2.1
diff -u -u -p -r1.16.2.18.2.1 coder_style.inc
--- includes/coder_style.inc	28 Sep 2008 03:05:01 -0000	1.16.2.18.2.1
+++ includes/coder_style.inc	28 Sep 2008 17:55:40 -0000
@@ -70,7 +70,7 @@ function coder_style_reviews() {
     array(
       '#type' => 'callback',
       '#source' => 'all',
-      '#value' => '_coder_style_callback',
+      '#value' => '_coder_style_closing_php_callback',
       '#warning' => 'the final ?> should be omitted from all code files',
     ),
     array(
@@ -157,7 +157,7 @@ function coder_style_reviews() {
 /**
  * Define the rule callbacks for style.
  */
-function _coder_style_callback(&$coder_args, $review, $rule, $lines, &$results) {
+function _coder_style_closing_php_callback(&$coder_args, $review, $rule, $lines, &$results) {
   for ($lineno = -1; $last = array_slice($lines, $lineno); $lineno --) {
     $lastline = $last[0][0];
     if (preg_match('/\S/', $lastline)) {
