Kumar McMillan avatar Kumar McMillan committed 9b35590

Added first stab at support for javascript lint using the jsl tool

Comments (0)

Files changed (3)

+
+import sys
+import subprocess
+import logging
+
+log = logging.getLogger('nose.plugins.nosejs')
+
+class JsLintError(Exception):
+    """JavaScript Lint Error"""
+
+class JsLintTestCase(object):
+    """A test case that runs a file through the jsl lint executable."""
+    __test__ = False # do not collect
+    
+    def __init__(self, filename, jsl_bin, jsl_options=None, stop_on_error=False):
+        self.jsl_bin = jsl_bin
+        self.jsl_options = jsl_options or []
+        self.filename = filename
+        self.shortDescription = self.__repr__
+        self.stop_on_error = stop_on_error
+    
+    def __call__(self, result):
+        cmd = [self.jsl_bin]
+        cmd.extend(self.jsl_options)
+        
+        start = '=NOSEJS=START='
+        sep = '=NOSEJS=SEP='
+        
+        cmd.extend([
+            '-output-format', (start + '__FILE__' + sep + '__LINE__' + sep + '__ERROR__'), 
+            '-nologo', '-nosummary',
+            '-process', self.filename])
+        log.debug("jsl command: %s" % " ".join(cmd))
+        p = subprocess.Popen(
+            cmd,
+            stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+        )
+        output = p.stdout.read()        
+        returncode = p.wait()
+        if returncode != 0:
+            # or use result.stream ?
+            # sys.stdout.write(output)
+            
+            # strip off the first line, which 
+            # seems to be the filename.  hmmm.
+            st = 0
+            while output[st] != "\n":
+                st += 1
+            output = output[st:].strip()
+                
+            msgs = output.split(start)
+            for msg in msgs:
+                if msg.strip() == '':
+                    continue
+                try:
+                    file, line, error = msg.split(sep)
+                except:
+                    log.debug("Could not split %s using markers" % msg)
+                    raise
+                
+                # log.debug("Extracted file %s, line %s, error %s" % (file, line, error))
+                result.addError(self, (JsLintError, "%s:%s %s" % (file, line, error), None))
+                
+                if self.stop_on_error:
+                    break
+    
+    def __repr__(self):
+        return "javascript-lint: %s" % self.filename
+        
 from nose.config import ConfigError
 import logging
 import java
+from lint import JsLintTestCase
 
 log = logging.getLogger('nose.plugins.nosejs')
 
     
     def options(self, parser, env=os.environ):
         Plugin.options(self, parser, env)
+        parser.add_option('--no-javascript-tests', dest="run_javascript_tests", 
+                            default=True, action="store_false", help=(
+            "Disables default behavior of looking for JavaScript test files and "
+            "loading them if they match nose's testMatch (i.e. [tT]est.*\.js)"
+        ))
+        parser.add_option('--no-javascript-lint', dest="run_javascript_lint", 
+                            default=True, action="store_false", help=(
+            "Disables default behavior of looking for JavaScript files and running them "
+            "through the jsl command line tool to report lint errors and warnings."
+        ))
+        parser.add_option('--jsl-bin', default="jsl", help=(
+            "Path to jsl executable.  Default: %default (using $PATH)"
+        ))
+        parser.add_option('--jsl-opt', default=[], action="append", help=(
+            "Additional command line options to send to the jsl executable.  "
+            "You can specify this multiple times."
+        ))
         parser.add_option('--java-bin', default="java", help=(
             "Path to java executable.  Default: %default (using $PATH)"
         ))
             "before any other JavaScript files."
         ))
         parser.add_option('--load-js-lib', dest='javascript_libs_to_load', 
-            action='append', metavar='FILEPATH', default=[], help=(
+            action='append', metavar='JS_FILEPATH', default=[], help=(
             "Path to a JavaScript file that should be loaded before the tests. "
             "This option can be specified multiple times."
         ))
         parser.add_option('--js-test-dir', dest='javascript_test_dirs', 
-            action='append', metavar='FILEPATH', default=[], help=(
+            action='append', metavar='JS_DIRECTORY', default=[], help=(
             "Path to where JavaScript tests live.  Must be a subdirectory of where "
             "Nose is already looking for tests (i.e. os.getcwd()).  "
             "This option can be specified multiple times."
         Plugin.configure(self, options, config)
         self.files = [] # used by self.wantFile()
         
+        if options.run_javascript_tests:
+            self._configureJsTesters(options, config)
+        if options.run_javascript_lint:
+            self._configureJsLint(options, config)
+        
+        self.options = options
+        self.config = config
+    
+    def _configureJsLint(self, options, config):
+        found_jsl = False
+        for prefix in os.environ.get('PATH','').split(':'):
+            exe = os.path.join(prefix, options.jsl_bin)
+            if os.path.exists(exe):
+                found_jsl = True
+                options.jsl_bin = exe
+                break
+        if not found_jsl:
+            raise ConfigError(
+                "Could not find jsl binary for JavaScript lint ($PATH=%r).  "
+                "Try setting an explicit path with --jsl-bin or else disable lint with "
+                "--no-javascript-lint" % (os.environ.get('PATH','')))
+    
+    def _configureJsTesters(self, options, config):
         wd = options.nosejs_work_dir
         if not wd:
             wd = os.getcwd()
                         incremental_part = incremental_part[len(os.sep):]
                     self.javascript_test_dirs.add(os.path.join(root, incremental_part))
         log.debug("Exploded --js-test-dir(s): %s" % self.javascript_test_dirs)
+    
+    def loadTestsFromFile(self, filename):
+        """yield all test cases in a file or if there are none, yield False."""
+        log.debug("nosejs is loading tests from: %s" % filename)
         
-        self.options = options
+        if not self.options.run_javascript_lint:
+            yield False
+            return
+        else:
+            yield JsLintTestCase(filename, self.options.jsl_bin, 
+                                    jsl_options=self.options.jsl_opt,
+                                    stop_on_error=self.config.stopOnError)
     
     def wantDirectory(self, dirpath):
         want_dir = dirpath in self.javascript_test_dirs
     
     def wantFile(self, file):
         # fixme: provide custom extensions?
+        found_some = None
         if file.endswith('.js'):
+            found_some = True
             # fixme: provide custom test matches?
             if self.conf.testMatch.search(os.path.basename(file)):
                 # just store it, don't return True
                 log.debug("Storing file: %s" % file)
                 self.files.append(file)
             
-        return None
+        return found_some
         
     def report(self, stream):
-        print >> stream, "----------------------------------------------------------------------"
         
-        if self.options.spidermonkey:
-            js_test = SpidermonkeyJavaScriptTester(stream, self.options)
-        else:
-            js_test = RhinoJavaScriptTester(stream, self.options)
+        if self.options.run_javascript_tests:
+            print >> stream, "----------------------------------------------------------------------"
+        
+            if self.options.spidermonkey:
+                js_test = SpidermonkeyJavaScriptTester(stream, self.options)
+            else:
+                js_test = RhinoJavaScriptTester(stream, self.options)
             
-        files = [self.options.rhino_testrunner] # the main program
-        for js_lib in self.options.javascript_libs_to_load:
-            files.append(js_lib)
-        files.extend(self.files)
-        js_test.load_files(files)
+            files = [self.options.rhino_testrunner] # the main program
+            for js_lib in self.options.javascript_libs_to_load:
+                files.append(js_lib)
+            files.extend(self.files)
+            js_test.load_files(files)
             
 # the plugin.  needs self-contained unit tests
 source _env/bin/activate
 cd ~/dev/fudge/
-nosetests --with-javascript --rhino-jar  ~/src/rhino1_7R1/js.jar --with-dom --js-test-dir javascript/fudge/tests/
+nosetests -v --with-javascript --rhino-jar  ~/src/rhino1_7R1/js.jar --with-dom --js-test-dir javascript/fudge/tests/ $@
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.